kedao中间件-Java服务
一、概述
本文主要介绍在kedao中间件中如何注册服务、编写服务代码、调用服务。
使用 VSCode 作为开发工具,通过远程连接到开发服务器的方式,开展远程开发。
kedao中间件官网:https://www.yckj-kedao.com
系列文章:
《kedao中间件-安装篇》
《kedao中间件-C++服务篇》
《kedao中间件-Vue开发篇》
《kedao中间件-集群及发布篇》
《kedao中间件-并发测试篇》
《kedao中间件-Linux系统安装篇》
《kedao中间件-数据库安装篇》
二、前提条件
已经部署 kedao中间件 开发服务器、数据库,并且安装了 kedao creator 客户端,注册了用户。
本文的涉及到开发用户、开发服务器和数据库服务器相关信息:
1)开发服务器的IP:192.168.43.30,操作系统:Ubuntu24.04,系统用户:kedao
2)kedao中间件的安装路径:/opt/kedao
3)kedao中间件监听的端口:80,协议:http
4)kedao中间件的注册用户:lym_test
5)数据库服务器IP:192.168.43.135,端口:5432,用户:postgres,密码:postgres,数据库实例:asv_db
如果还没有了解如何部署 kedao中间件 开发服务器,请参考《kedao中间件-安装篇》。
三、创建Java系统
1、创建系统
登录 kedao creator,在【基础功能】-【创建系统】功能中,点击右上角的创建系统按钮,打开创建系统对话框,填入系统名称为 prj_java_test,数据为 PostgreSQL,开发语言为Java,签名算法为 SHA256,如图:
创建成功后,左侧菜单将自动出现刚才创建的系统,展开菜单,如图:
2、源代码目录结构
Java项目源码的目录在: kedao中间件的安装路径/src_java,即 /opt/kedao/src_java
进入 /opt/kedao/src_java 目录:
kedao@kedao:~$ cd /opt/kedao/src_java
kedao@kedao:/opt/kedao/src_java$ ls
lym_test
目录说明:
2)lym_test:kedao中间件的注册用户名,所有Java系统,都在该目录下
进入 lym_test 目录:
kedao@kedao:/opt/kedao/src_java$ cd lym_test
kedao@kedao:/opt/kedao/src_java/lym_test$ ls
prj_java_test
这时,看到刚才生成的Java工程prj_java_test,如果创建了多套Java工程,这里将会以工程名称生成相应的工程目录。
继续进入prj_java_test工程目录:
kedao@kedao:/opt/kedao/src_java/lym_test$ cd prj_java_test
kedao@kedao:/opt/kedao/src_java/lym_test/prj_java_test$ ls
lib pom.xml src target
目录及文件说明:
1)lib:第三方库文件目录,通常为空。第三方库通过Maven管理。
2)pom.xml:Maven的配置文件。
3)src:源代码目录。
4)target:编译打包后生成文件的目录,每次通过Maven打包后会自动将*.jar包放拷贝到运行目录下。
这里,需要重点了解 src 的结构,它的结构为:src/main/java/kedao用户/系统名称/,即:src/main/java/lym_test/prj_java_test
进入 src/main/java/lym_test/prj_java_test:
kedao@kedao:/opt/kedao/src_java/lym_test/prj_java_test$ cd src/main/java/lym_test/prj_java_test
kedao@kedao:/opt/kedao/src_java/lym_test/prj_java_test/src/main/java/lym_test/prj_java_test$ ls
Main.java model services utils
系统 prj_java_test 的目录说明:
1)Main.java:工程默认入口 main() 方法的源代码文件。这里主要看下 package ,它的结构为 kedao用户.系统名称,即 lym_test.prj_java_test,如:
package lym_test.prj_java_test;
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
2)model:数据结构类文件夹;
3)services:服务文件夹;
这里,需要重点了解 services 文件夹,进入到 services 目录
kedao@kedao:/opt/kedao/src_java/lym_test/prj_java_test/src/main/java/lym_test/prj_java_test$ cd services/
kedao@kedao:/opt/kedao/src_java/lym_test/prj_java_test/src/main/java/lym_test/prj_java_test/services$ ls
basic_func_employee basic_func_funcRounter basic_func_organization basic_func_post basic_func_role_permission sys_login
在services 是一个个模块文件夹,主要实现了组织机构、员工/用户、岗位、权限、菜单/路由配置、登录等基础功能。
进入一个模块目录,以sys_login为例来了解一个模块的组成,进入sys_login目录:
kedao@kedao:/opt/kedao/src_java/lym_test/prj_java_test/src/main/java/lym_test/prj_java_test/services$ cd sys_login/
kedao@kedao:/opt/kedao/src_java/lym_test/prj_java_test/src/main/java/lym_test/prj_java_test/services/sys_login$ ls
svc_get_userInfo.java svc_login.java
sys_login包含了2个 *.java 文件,即服务源代码文件,每一个服务对应一个同名的 *.java 文件。
4)utils:工具类文件夹,这里要关注 JDBCHelper.java 和 CheckUserLogin.java ,两个类文件,一个是数据库帮助类,一个是服务中的身份验证类,具体代码不在这里展开说明,自行阅读。
到这里,对Java工程源代码的结构有了初步了解。
3、编译工程
点击操作下的编译按钮,进行一次全编译,第一次编译需要下载依赖的第三方 jar 包,时间比较长,,如图:
编译完成后,在 /opt/kedao/bin/lib/lym_test/prj_java_test 目录下看到编译好的动态库文件。
kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_java_test$ ls
backup db_connect.ini db_sql prj_java_test-1.0.jar prj_java_test-1.0-jar-with-dependencies.jar
也可以在 VSCode 中通过 Maven 进行编译打包,如图:
4、执行文件目录结构
编译后生成的jar包存放路径为:kedao中间件的安装路径/bin/lib/kedao中间件的注册用户/工程名称/,即:/opt/kedao/bin/lib/lym_test/prj_java_test/
进入**/opt/kedao/bin/lib/lym_test/prj_java_test/**目录:
kedao@kedao:~$ cd /opt/kedao/bin/lib/lym_test/prj_java_test/
kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_java_test$ ls
backup db_connect.ini db_sql prj_java_test-1.0.jar prj_java_test-1.0-jar-with-dependencies.jar
目录说明:
1)backup:备份目录,目前没有使用。
2)db_sql:数据库脚本目录,包含了数据初始化 和 查询表结构的SQL语句。注意:在【数据模型】功能中表结构导入(数据库)用到了这里的SQL语句。
3)db_connect.ini:数据库连接配置文件,根据 odbc.ini 对应的配置来设置。
4).jar:jar包文件,这里分为2个文件,一个服务工程的jar文件,一个是第三方依赖包的jar文件。
5、数据库配置
1)配置数据 /ect/odbc.ini:
kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_java_test$ sudo vi /etc/odbc.ini
根据预先建好的数据库配置,数据库服务器IP:192.168.43.135,端口:5432,用户:postgres,密码:postgres,数据库实例:asv_db。配置如下:
[myPgDB]
Description = link to pg
Driver = PostgreSQL ANSI
Database = asv_db
Servername = 192.168.43.135
UserName = postgres
Password = postgres
Port = 5432
ReadOnly = 0
ConnSettings = set client_encoding to UTF8
验证配置,执行命令 isql -v myPgDB:
kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_java_test$ isql -v myPgDB
+---------------------------------------+
| Connected! |
| |
| sql-statement |
| help [tablename] |
| echo [string] |
| quit |
| |
+---------------------------------------+
SQL>
连接成功,quit 退出。
2)配置 db_connect.ini :
kedao@kedao:/opt/kedao/bin/lib/lym_test/prj_java_test$ sudo vi db_connect.ini
文件内容设置如下:
[lym_test]
prj_java_test=postgres/postgres@myPgDB
说明:
- lym_test 为 kedao中间件的注册用户;
- prj_java_test 为系统名称;
- postgres分别为数据库的用户和密码;
- myPgDB 为 /ect/odbc.ini 中数据库连接的节点名称
3)java工程中配置数据库连接:
在源代码的资源文件目录下 /opt/kedao/src_java/lym_test/prj_java_test/src/main/resources,编辑资源文件 prj_java_test.properties
根据预先建好的数据库配置,数据库服务器IP:192.168.43.135,端口:5432,用户:postgres,密码:postgres,数据库实例:asv_db。配置如下:
# 数据库连接配置,此为 PostGreSQL 数据库的设置例子
database.driver=org.postgresql.Driver
database.url=jdbc:postgresql://192.168.43.135:5432/asv_db?stringtype=unspecified
database.user=postgres
database.password=postgres
6、数据库初始化
如果已经初始化过,则跳过。初始化步骤:
1)使用数据库管理工具连接到数据库,并打开预先创建好的数据库实例 asv_db:
2)在 db_sql 目录找到 PostgreSQL 数据库的初始化脚本:initialize_table_struct_for_PostgreSQL.sql
3)在 asv_db 实例中执行初始化的脚本:initialize_table_struct_for_PostgreSQL.sql
初始化完成后,数据库初始创建了:sys_employee、sys_function、sys_organization、sys_post、sys_role、sys_role_function_relation、sys_user、sys_user_role 等8张数据表,满足了一套系统最基本的框架结构需求,包括:组织管理、员工、角色、岗位、权限、登录、路由(菜单)配置等基础功能。
这已经满足绝大多数的系统需求,可以直接使用,只需设计自己的业务数据表,添加业务即可;
当然,如果不能满足自己系统的需求,也可以自行修改。
四、注册服务
在kedao creator的菜单中找到【我的系统】-【prj_java_test】功能,如图:
这里提供了系统基础功能的服务,只需根据业务需求注册新的服务。
1、增加模块
点击增加模块按钮,在弹出的对话框中增加一个测试模块 mdl_test,如:
2、增加服务
在模块列表中选中mdl_test,点击增加服务按钮,在弹出的对话框中增加一个测试服务 svc_test,如:
3、选择服务参数
参数说明:
1)默认的服务入参和出参分别是:SVC_REQUEST_OBJ<string> 和 SVC_RESPONSE_OBJ<string>
2)SVC_REQUEST_OBJ 是默认的服务入参数据结构,Json 结构如下:
{
"sys_head": {
"usr_id": "",
"org_id": "",
"sys_id": "",
"mdl_func_id": "",
"login_key": ""
},
"data": ""
}
其中data是一个泛型对象,也就是真正的请求业务数据;而 sys_head 主要保存服务请求者的身份信息,用于身份认证。
Java中对应的数据结构:
public class SYS_HEAD {
public String usr_id = ""; // 用户登录名称
public String org_id = ""; // 组织代码
public String sys_id = ""; // 系统代码
public String mdl_func_id = ""; // 功能代码
public String login_key = ""; // 登录验证信息,每次登录成功后更新该值。服务调用时,必须传该值
}
public class SVC_REQUEST_OBJ <T> {
public SYS_HEAD sys_head; // 头包对象
public T data; // 请求数据
}
3)SVC_RESPONSE_OBJ 是默认的服务出参数据结构,Json 结构如下:
{
"code": 0,
"err_msg": "",
"otl_exc": {
"code": 0,
"msg": "",
"stm_text": "",
"sqlstate": "",
"var_info": ""
},
"data": ""
}
其中data是一个泛型对象,也就是真正的返回业务数据;code 是返回码;err_msg 是错误信息;otl_exc 是数据库错误信息。
Java中对应的数据结构:
public class OTL_EXC {
public int code = 0; // 错误码
public String msg = ""; // 错误消息
public String stm_text = ""; // 导致错误的SQL语句
public String sqlstate = ""; // SQLSTATE消息
public String var_info = ""; // 导致错误的变量
}
public class SVC_RESPONSE_OBJ <T> {
public int code = 0; // 返回码
public String err_msg = ""; // 错误信息
public OTL_EXC otl_exc; // 错误信息
public T data; // 响应数据
}
根据业务需求选择自己的参数,这里,我们注册一个查询系统功能的服务,涉及到数据表是 sys_function,服务的入参是一个对象,返回是一个列表,入参选择界面,如:
出参选择界面,如:
注:选择参数的数据结构,在【数据模型】功能中维护。【数据模型】支持直接从数据库导入表结构功能,只需在数据库中设计好数据表,通过表结构导入功能一键导入,能节省很大部分的时间
4、保存服务
选择完参数的最终服务注册界面,如:
点击保存按钮,完成注册,返回服务列表界面:
现在到系统源代码目录下,直接进入到服务所在目录:
kedao@kedao:/$ cd /opt/kedao/src_java/lym_test/prj_java_test/src/main/java/lym_test/prj_java_test/services/mdl_test
kedao@kedao:/opt/kedao/src_java/lym_test/prj_java_test/src/main/java/lym_test/prj_java_test/services/mdl_test$ ls
svc_test.java
服务源代码文件 svc_test.java 已经生成。
五、编写代码
1、使用 VSCode 开发
使用 VSCode 远程连接到开发服务器 192.168.43.30,连接用户为:kedao,选择根目录为 /opt/kedao,连接成功后,打开服务源码文件 svc_test.java,如图:
下面是服务函数代码,代码结构比较简单,自己花点时间阅读理解。现在直接在服务函数中增加业务逻辑代码,,为了便演示调用服务,先把调用服务者的身份认证注释掉,整个服务代码如下:
package lym_test.prj_java_test.services.mdl_test;
import java.lang.reflect.Type;
import java.util.*;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lym_test.prj_java_test.model.*;
import lym_test.prj_java_test.utils.*;
import kedaoUtils.SyncLog;
public class svc_test {
SyncLog syncLog = new SyncLog();
public void init_log(int msg_type) { syncLog.setType(msg_type); }
public String exec_svc(final String in_data, final String http_headers) {
Gson gson = new Gson();
SVC_REQUEST_OBJ<SYS_FUNCTION> req = new SVC_REQUEST_OBJ<SYS_FUNCTION>();
SVC_RESPONSE_OBJ<List<SYS_FUNCTION>> resp = new SVC_RESPONSE_OBJ<List<SYS_FUNCTION>>();
int iRst = 0;
StringBuilder sql_txt = new StringBuilder();
ArrayList<String> lst_params = new ArrayList<>();
OTL_EXC sqlMsg = new OTL_EXC();
// 建立数据库连接
JDBCHelper dbConn = new JDBCHelper();
try {
// 获取请求头包信息
// HttpHeaders httpHeaders = gson.fromJson(http_headers, HttpHeaders.class);
// 建立数据库连接
dbConn.getConnection(sqlMsg);
// 解析入参: 入参 in_data 字符串转对象 req
Type type = new TypeToken<SVC_REQUEST_OBJ<SYS_FUNCTION>>(){}.getType();
req = gson.fromJson(in_data, type);
if (req == null) {
resp.err_msg = "服务 svc_test :解包失败。";
resp.code = -1;
// 打印错误日志
syncLog.cerr(resp.err_msg);
// 断开数据库连接
dbConn.close();
return gson.toJson(resp);
}
// 检查用户登录有效性
/*if (!CheckUserLogin.check_user_login(dbConn, req.sys_head.usr_id, req.sys_head.login_key)) {
iRst = -1;
resp.code = iRst;
resp.err_msg = "服务[svc_test]验证身份信息失败。";
syncLog.cerr(resp.err_msg);
dbConn.close();
return gson.toJson(resp);
}*/
// 开始事务
dbConn.begin(sqlMsg);
// ---------------------------------------------------------------------
// 编写业务逻辑代码
// ---------------------------------------------------------------------
// 清空参数
lst_params.clear();
// 组织SQL语句
sql_txt.setLength(0);
sql_txt.append("SELECT * FROM sys_function ");
sql_txt.append("WHERE 1 = 1 ");
if (!req.data.func_code.equals(""))
{
sql_txt.append(" AND func_code = ? ");
// 参数
lst_params.add(req.data.func_code);
}
if (!req.data.menu_title.equals(""))
{
sql_txt.append(" AND menu_title = ? ");
// 参数
lst_params.add(req.data.menu_title);
}
// 声明变量,接收查询结果
ArrayList<SYS_FUNCTION> v_sys_functions = new ArrayList<>();
// 执行查询
dbConn.executeQueryToObjs(sql_txt, lst_params, v_sys_functions, SYS_FUNCTION.class, sqlMsg);
if (sqlMsg.code != 0)
{
resp.code = sqlMsg.code;
resp.err_msg = "服务[svc_test]-查询 sys_function 时发生错误";
// 打印错误日志
syncLog.cerr(resp.err_msg);
// 事务回滚
dbConn.rollback(sqlMsg);
// 断开数据库连接
dbConn.close();
// 返回结果
return gson.toJson(resp);
}
// ---------------------------------------------------------------------
// 将结果赋值给返回对象
// ---------------------------------------------------------------------
resp.data = v_sys_functions;
// 提交事务
dbConn.commit(sqlMsg);
// ------------------------------------------------------------------------------------------
// 返回结果
// ------------------------------------------------------------------------------------------
resp.code = iRst;
// 断开数据库连接
dbConn.close();
return gson.toJson(resp);
}
catch(Exception e) {
// 回滚事务
dbConn.rollback(sqlMsg);
// 错误码
iRst = -1;
resp.code = iRst;
resp.err_msg = e.getMessage();
String out_str = gson.toJson(resp);
// 打印错误日志
syncLog.cerr(resp.err_msg);
// 断开数据库连接
dbConn.close();
return out_str;
}
}
}
服务开发说明:
每一个服务对应一个同名的类文件,每个服务中都有一个 exec_svc() 方法,服务的业务逻辑在该方法中实现:
在utils工具类中封装了JDBCHelper.java帮助类,数据库操作通过它来实现 ,JDBC使用 hikari 连接池,需要在 JDBCHelper.java 中设置连接池参数,比如最小、最大连接数:
config = new HikariConfig();
config.setJdbcUrl(url); // 修改为你的数据库URL
config.setUsername(usr); // 修改为你的数据库用户名
config.setPassword(pw); // 修改为你的数据库密码
config.setDriverClassName(driver);
config.setPoolName("prj_java_test");
// 连接池中允许的最小连接数,默认为10
config.setMinimumIdle(10);
// 连接池中允许的最大连接数,根据服务器性能设置
config.setMaximumPoolSize(32);
config.addDataSourceProperty("cachePrepStmts", "true");
// 驱动为 每个连接缓存预准备的语句数,默认25。建议250-500
config.addDataSourceProperty("prepStmtCacheSize", "500");
// 驱动缓存SQL语句的最大长度,默认256。建议设置值大于执行的SQL语句的长度
config.addDataSourceProperty("prepStmtCacheSqlLimit", "20480");
// 提升数据库预处理语句的性能
config.addDataSourceProperty("useServerPrepStmts", "true");
JDBCHelper 中常用的函数有:
1)原子服务,实现数据表的增删改查;使用原子服务时,数据表必须有唯一健字段
atom_exec_delete()
atom_exec_insert()
atom_exec_select()
atom_exec_update()
2)执行SQL语句,无结果返回
execute()
executeUpdate()
executeBatch()
3)执行SQL语句,返回单个值(字符串)
executeQueryToStringValue()
executeQueryToIntegerValue()
executeQueryToDoubleValue()
4)执行SQL语句,返回多条记录容器 ArrayList<>
executeQueryToObjs()
5)执行SQL语句,返回单条记录的数据对象
executeQueryToObj()
6)执行SQL语句,返回多条记录 ArrayList<Map<string, string>>
executeQueryToMaps()
7)执行SQL语句,返回单条记录的 Map<string, string>
executeQueryToMap()
关于日志输出
日志输出有3种方式,都支持多线程高并发:
1)日志输出到 stdout,通常用在开发过程中打印日志,用来辅助测试,发布生产环境时,应该删除此类日志的输出
syncLog.cout(“错误日志输出”);
2)错误日志输出到 stderr 文件,通常用在服务发生错误时的日志打印
syncLog.cerr(“日志输出”);
3)日志输出到 log 日志文件,每天产生一个log日志文件,在 bin/log 目录下;kedao中间件默认输出每一个服务的执行情况
syncLog.log(“日志输出”);
注:整个服务编码过程是比较简单的,只需要关心业务逻辑的实现,对于数据库操作来说,就是组织编写业务逻辑的 SQL 语句 和 组织返回数据的过程。
数据库开发帮助类 JDBCHelper,建议重点掌握。
服务以模块分组,一个模块为一个文件夹。
六、编译/打包服务
在 VSCode 中操作,如图:
编译打包完成后,jar包自动生成到 /opt/kedao/bin/lib/lym_test/prj_java_test/ 目录。
七、调用服务
示例一
在 kedao creator【注册服务】功能界面的操作列中,点击【测试】按钮,弹出测试服务界面,增加测试用例,然后点保存并测试,如:
示例二
在 Postman 中调用,由于服务默认需求进行签名,才能调用,手动签名比较麻烦,先将服务设置为无签名模式。
在 kedao creator【注册服务】功能界面的操作列中,点击【修改】按钮,将签名算法改为:无
由于svc_test 服务已经加载为需要签名验证模式,需要重启服务 sudo systemctl restart kedao 才会加载成不需要签名模式。
kedao@kedao:/opt$ sudo systemctl restart kedao
从示例一的测试服务界面中赋值 url 和 请求参数 到Postman 中(或者点服务操作列中的修改,在弹出编辑界面中复制请求参数)。这里需要注意2点,一是 url 后面的 /api,不要忘记了;二是请求参数中的服务名称 svc_name 要把测试标识 @test去掉
http方法:POST
Url:http://192.168.43.30/api
请求参数:
{
"appid": "f4137243797841788feffdb4e4d1d15e",
"sys_name": "prj_java_test",
"mdl_name": "mdl_test",
"svc_name": "svc_test",
"body": {
"sys_head": {
"usr_id": "",
"org_id": "",
"sys_id": "",
"mdl_func_id": "",
"login_key": ""
},
"data": {
"func_id": "",
"func_parent_id": "",
"func_code": "",
"menu_title": "",
"router_path": "",
"component_path": "",
"icon": "",
"func_type": 0,
"sort_num": 0
}
}
}
Postman调用如图:
八、并发测试
测试服务器配置:
Windows操作系统:Windows10;CPU:酷睿9;硬盘:SSD PCIe 4.0 x4
Linux操作系统:虚拟机 Ubuntu24.04-live-server
CPU:2核心
内存:4G
kedao并发数:server_concurrency_process=16
在 kedao creator【注册服务】功能界面的操作列中,点击【测试】按钮,打开测试界面,调用服务仍然是 svc_test ,但服务代码中去掉了业务逻辑,同时去掉数据库连接(避免数据库对测试的影响),只保留基本的入参和出参,并且加上签名算法,这次测试服务次数 10万次,并发 1000(模拟客户端数量),测试结果如下图:
同时,在 /opt/kedao/bin/log 目录下查看服务调用日志,服务在服务器端的执行时长在毫秒级别,QPS ≈ 900。
注:这里通过客户端模拟并发访问服务器上的服务,受到客户端与服务器之间的连接数和请求参数、响应参数网络传输时间的影响,实际服务器端处理服务能力的QPS比现在看到的值要高得多。
关于server_concurrency_process的一点说明:
1)server_concurrency_process的值要根据CPU核心数、服务的业务类型来设置;
2)假如服务的逻辑都在本服务器上运算,那么server_concurrency_process的值要与CPU核心(超线程)数量相当;
3)假如服务的部分逻辑在其他服务器上,比如访问数据库,执行SQL的运算在数据库服务器上,在执行SQL语句期间,服务处于等待状态(CPU处于闲置,可以让给其他服务运算),那么server_concurrency_process的值要与大于CPU核心(超线程)数量;至于大多少,就要一点一点的测试调整。可以按CPU核心(超线程)数量的倍数调整。
4)如果服务器运算量很少,主要用于服务分发(比如集群主服务器),那就要设置的比较大了。
注:这里并非服务器并发性能测试,而是测试某个服务中并发能力,同时检测服务的平均执行时间,以验证单个服务的性能,为服务代码性能调优提供参考依据。
具体的服务器性能测试,参见《kedao中间件-并发测试篇》
至此,Java服务篇完成。
如何在代码中调用服务,将在Vue工程篇讲解。
九、总结
kedao中间件提供了一套完整体系的Java系统结构,包括服务源代码、数据库结构和前端vue工程源码,只需添加业务数据表、设计服务、开发功能即可。
初学者可以通过本系统深入学习,快速入门,缩短编程学习周期,快速晋升高手行列。
久经沙场的老将也如虎添翼,利刃在手,所向披靡。