目录
技术不开源 毫无意义
刚到新公司的第一个需求,java项目需要和刷卡机对接。郁闷了好几天。解决完心情舒畅!相信对你有用!(我发现网上关于浩顺的设备对接java很少,而且这里面用到了非对称加密的密钥,在这我会简单说下加密。不了解的同学可以自行百度下。
一、什么是刷卡机?浩顺刷卡机用在哪?
说的具体点,是用于食堂吃饭刷卡的机器,其实在这应该贴个图。具体想了解的三连私信我。免费给你讲解。
java集成刷卡机,采用http传输协议。具体的功能对接在第三章里会讲到。
二、什么是RSA,密钥又是什么?
RSA 是一种非对称加密算法,它的安全性基于大整数的因数分解难题。RSA 算法包括公钥和私钥两部分,其中公钥用于加密数据,私钥用于解密数据。RSA 加密过程如下:
- 选择两个不相等的质数 p 和 q,计算它们的乘积 n = p * q。
- 计算欧拉函数 φ(n) = (p-1) * (q-1)。
- 选择一个整数 e,使得 1 < e < φ(n),且 e 与 φ(n) 互质,即 e 和 φ(n) 没有公共因子。
- 计算 e 的模反元素 d,使得 (d * e) mod φ(n) = 1。
- 公钥为 (n, e),私钥为 (n, d)。
- 加密数据 M:C = M^e mod n。
- 解密密文 C:M = C^d mod n。
在这里,n 称为模数,e 称为加密指数,d 称为解密指数。公钥 (n, e) 可以公开,私钥 (n, d) 必须保密。
使用 RSA 算法,可以实现安全的数据传输和数字签名。因为其安全性依赖于大整数的因数分解问题,在合适的参数选取下,RSA 算法是一种安全可靠的加密算法。
以上解释为gpt,只做科普。说点干货
简单的说,就是使用openssl生成两对公钥密钥,一对是设备、一对是服务器。下发的时候要将设备的 密钥和服务器的公钥下发到设备。这是设备里要有的。
服务器有服务器私钥,设备公钥。
我们不使用RSA加密解密,因为需要花费很多的时间。这里只做签名,验签(可不验。 看你安全问题
三、java对接的步骤
先说下对接流程,硬件部分供应商应该会给对应的接口文档以及硬件的设置。下面也会附上代码
1)网络
首先拿到设备后,设备和软件系统也就是idea运行的java后台。要在一个网段下(我是本地起的一个项目
2)初次请求类型
会不停的发送请求类型:get_key获取密钥到服务器(java程序。 请求方式设备会以post向服务器请求,接口用 @RequestBody JSONObject jsonObject 接收就可以
这是因为默认服务器返回给设备错误的密钥导致一直跟服务器要密钥。
在第二点重点说下设备和服务器都需要有一对RSA密钥 ,还有一个服务器和设备通用的3des密钥
RSA密钥用openssl生成pem文件,java程序读取pem文件
通过下发get_key下发密钥后设备的显示屏右上角云朵标志正常,非正常为中间有红色感叹号
屏幕未连接服务器标志消失
下图为未连接
3)下发整体流程
下发密钥后设备会发送心跳到服务器,reqType为 heart_heat,这时候就要在对应的逻辑里去查询数据指令表是否存在该设备id未下发的指令信息 有就下发
下图为下发流程
4)下列是处理请求类型为get_key的代码(向服务器获取密钥 按这个格式去写
if (reqType.equals("get_key")){
//查询des3数据
//硬件的返回参数
JSONObject buLeJ=new JSONObject();
JSONObject zJ=new JSONObject();
//服务器公钥
zJ.put("serverPubKey",publicKeyFromPem);
//设备私钥
zJ.put("devicePrivateKey",devicePrivateKeyFromPem);
//desKey 3des密钥
zJ.put("desKey",des3);//为32位为3des密钥
String zJstring = zJ.toString();
/**
* 通过devicekey和封装好的zJ进行加密
*/
String cipherText = desEncript(zJstring, "0138787B19136209EE04552C63E03926");
buLeJ.put("sign","");
buLeJ.put("msg",cipherText);
buLeJ.put("succ",true);
buLeJ.put("reqType",reqType);
System.out.println("buLeJ"+buLeJ);
return buLeJ;
}
5)正常发送心跳指令
发送心跳指到服务器上,我们需要去查询数据库是否该设备待执行的指令,查询到封装好数据返回给设备,下面为处理心跳时处理指令的代码
if (reqType.equals("heart_heat")){//心跳
List<Device> details = deviceService.list(new LambdaQueryWrapper<Device>()
.eq(Device :: getDeviceId, jsonObject.getString("deviceId")));
if (details.isEmpty()){
//为空,有可能设备数据被恶意删除。新增一条数据
}
//心跳后的指令
String deviceId="611C4457C4CDE53D";
String serverTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String cmdType="set_heartcycle";//指令类型为改变心跳周期
String signs = sign(deviceId+serverTime, privateKey);
System.out.println("服务器私钥签名后:"+signs);
JSONObject zL=new JSONObject();//指令
zL.put("heartCycle",30);
String zLstring = zL.toString();
String zLDes = desEncript(zLstring, des3);
JSONObject cmd_json=new JSONObject();
cmd_json.put("transId",1);//指令id
cmd_json.put("cmdType",cmdType);
cmd_json.put("cmdParam",zLDes);//代表指令的内容,需要des一起加密
String cmd_string = cmd_json.toString();
buLeJ.put("reqType",reqType);
buLeJ.put("succ",true);
buLeJ.put("serverTime",serverTime);
buLeJ.put("sign",signs);
String s = desEncript(cmd_string, des3);
buLeJ.put("msg",s);
System.out.println("bule:"+buLeJ);
return buLeJ;
}
6) 处理cmd_result 指令上传请求
成功失败都会返回cmd_result,使用3des解密data数据。修改指令状态
if (reqType.equals("cmd_result")){//指令处理结果上传
//进行对data3des解密,查看执行结果
String data3des = jsonObject.getString("data");
String s = desDecript(data3des, des3);
JSONObject result=JSONObject.parseObject(s);
String transId = result.getString("transId");
System.out.println("transId:"+transId);
System.out.println("解密格式为:"+s);
if (result.getString("cmdResult").equals("1")){//代表执行成功,修改指令表状态。完事后去调查询指令表
boolean update = instructioTableService.update(new LambdaUpdateWrapper<InstructioTable>()
.set(InstructioTable::getIsa, 1).in(InstructioTable::getId, transId));
if (update){
buLeJ.put("succ",true);
buLeJ.put("msg","状态修改为执行成功完毕!");
}else {
buLeJ.put("succ",false);
buLeJ.put("msg","状态修改为执行成功失败!");
}
}else {//指令执行失败,将数据状态改为执行失败
boolean update = instructioTableService.update(new LambdaUpdateWrapper<InstructioTable>()
.set(InstructioTable::getIsa, 2).in(InstructioTable::getId, transId));
if (update){
buLeJ.put("succ",true);
buLeJ.put("msg","状态修改为指令执行失败完毕!");
}else {
buLeJ.put("succ",true);
buLeJ.put("msg","状态修改为指令执行失败!");
}
}
return buLeJ;
}
到这里整体的流程就结束了,如果不明白的可以让供应商给你操作一下正确的流程。良好的沟通是决定成功的关键。你也一定可以!
三、数据库的设计
1)指令表
CREATE TABLE instructio_table
(
id
int(10) NOT NULL AUTO_INCREMENT COMMENT ‘主键’,
device_id
varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘设备id’,
Instruction_type
varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘指令类型’,
data
longtext COLLATE utf8mb4_unicode_ci COMMENT ‘指令加密数据’,
des3
varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘设备的3des-32位字符串’,
isa
varchar(5) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘是否处理 0:否 1:是’,
create_usr
varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘创建人’,
create_time
bigint(20) DEFAULT NULL COMMENT ‘创建时间’,
update_usr
varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘修改人’,
update_time
bigint(20) DEFAULT NULL COMMENT ‘修改时间’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT=‘指令表’;
2)设备表
CREATE TABLE device
(
id
int(11) NOT NULL AUTO_INCREMENT COMMENT ‘id’,
device_id
varchar(255) DEFAULT NULL COMMENT ‘设备id’,
heartbea_cycle
varchar(255) DEFAULT NULL COMMENT ‘心跳周期’,
des3
varchar(255) DEFAULT NULL COMMENT ‘des3’,
device_private_key
longtext COMMENT ‘设备私钥’,
device_pub_key
longtext COMMENT ‘设备公钥’,
server_private_key
longtext COMMENT ‘服务器私钥’,
server_pub_key
longtext COMMENT ‘服务器公钥’,
create_usr
varchar(20) DEFAULT NULL COMMENT ‘创建人’,
create_time
bigint(20) DEFAULT NULL COMMENT ‘创建时间’,
update_usr
varchar(20) DEFAULT NULL COMMENT ‘修改人’,
update_time
bigint(20) DEFAULT NULL COMMENT ‘修改时间’,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT=‘设备表’;
这里有的字段可以不要,比如设备表的公钥私钥,如果读取文件的形式其实可以不用此字段。
四、预期达到的效果,后续的操作维护
1、通过读卡器读到卡的序号,从页面进行充值。
2、设备设置金额
3、进行刷卡后将数据推送过来,带着指令。后端处理逻辑进行扣除金额。
4、使用webSocket,卡的余额可以在小程序端看到