diff --git a/AutoSprout-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/AutoSprout-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java index 1866771..3abea07 100644 --- a/AutoSprout-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java +++ b/AutoSprout-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java @@ -12,19 +12,12 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; import java.util.ArrayList; -import java.util.Base64; import java.util.List; /** @@ -170,64 +163,4 @@ public class CommonController } } - /** - * 功能描述 消息摘要计算 - * - * @param msg 平台推送消息 - * @param signature 根据token生成的签名 - * @param nonce 平台生成的随机字符串 - * @return - * @throws Exception - */ - @RequestMapping("/signature") - public String signature(String msg, String nonce, String signature){ - log.info("接收到参数:msg="+msg+",token="+token+",nonce="+nonce+",signature="+signature); - if(StringUtils.isNotEmpty(msg)){ - // 第一步 计算 MD5 并编码为 Base64 字符串 - String calculatedSignature = calculateBase64MD5(token + nonce + msg); - - // 第二步 进行 URL 解码 - calculatedSignature = urlDecode(calculatedSignature); - - // 第三步 比较计算得到的签名与请求参数中的签名是否相等 - if (calculatedSignature.equals(signature)) { - System.out.println("Token verification successful!"); - } else { - System.out.println("Token verification failed!"); - } - - // 第4步 将URL Decode编码后的值与请求参数signature的值进行对比 - if (StringUtils.isNotEmpty(signature) && signature.equals(calculatedSignature)) { - log.info("签名验证正确,返回msg=【{}】",msg); - return msg; - } - log.info("加密后的base64:【{}】,返回invalid token",calculatedSignature); - return "invalid token"; - }else{ - return msg; - } - } - - // 计算 MD5 并编码为 Base64 字符串 - private static String calculateBase64MD5(String input) { - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] md5Bytes = md.digest(input.getBytes(StandardCharsets.UTF_8)); - // 编码为 Base64 字符串 - return Base64.getEncoder().encodeToString(md5Bytes); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - // URL 解码 - private static String urlDecode(String input) { - try { - return URLDecoder.decode(input, StandardCharsets.UTF_8.toString()); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } } diff --git a/AutoSprout-admin/src/main/resources/application-druid.yml b/AutoSprout-admin/src/main/resources/application-druid.yml index 02d4a29..d4eb9c2 100644 --- a/AutoSprout-admin/src/main/resources/application-druid.yml +++ b/AutoSprout-admin/src/main/resources/application-druid.yml @@ -6,9 +6,9 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://localhost:3306/autosprout?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: 123456 + url: jdbc:mysql://117.50.163.143:3306/autosprout?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: autosprout + password: 2fT5hHLbj8Nis6fD # 从库数据源 slave: # 从数据源开关/默认关闭 diff --git a/AutoSprout-common/pom.xml b/AutoSprout-common/pom.xml index f524afb..7f7c434 100644 --- a/AutoSprout-common/pom.xml +++ b/AutoSprout-common/pom.xml @@ -133,6 +133,24 @@ provided + + org.bouncycastle + bcprov-jdk15on + 1.54 + + + + commons-codec + commons-codec + 1.10 + + + + org.json + json + 20160212 + + com.baomidou diff --git a/AutoSprout-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java b/AutoSprout-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java index 569aaa7..78b507c 100644 --- a/AutoSprout-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java +++ b/AutoSprout-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java @@ -111,7 +111,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter // 过滤请求 .authorizeRequests() // 对于登录login 注册register 验证码captchaImage 允许匿名访问 - .antMatchers("/login", "/register", "/captchaImage","/common/signature").permitAll() + .antMatchers("/login", "/register", "/captchaImage","/common/receive").permitAll() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() diff --git a/AutoSprout-quartz/src/main/java/com/ruoyi/quartz/task/IotLogTask.java b/AutoSprout-quartz/src/main/java/com/ruoyi/quartz/task/IotLogTask.java index 7b1dcb7..c9c978e 100644 --- a/AutoSprout-quartz/src/main/java/com/ruoyi/quartz/task/IotLogTask.java +++ b/AutoSprout-quartz/src/main/java/com/ruoyi/quartz/task/IotLogTask.java @@ -13,5 +13,11 @@ public class IotLogTask { public void getDeviceLog() { System.out.println("获取浇花器日志"); + /**1.获取到浇花器日志*/ + /**2.根据最新时间保存数据*/ + /**3.*/ + + + } } diff --git a/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/domain/BodyObj.java b/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/domain/BodyObj.java new file mode 100644 index 0000000..dfac458 --- /dev/null +++ b/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/domain/BodyObj.java @@ -0,0 +1,29 @@ +package com.ruoyi.device.iot.domain; + +import lombok.Data; + +/** + * onenet接收到的body对象 + * + * @author ruoyi + */ + +@Data +public class BodyObj { + + /** 设备推送数据,包括设备的生命周期,数据点,物模型属性、事件、服务等 */ + private Object msg; + + /** 用于计算签名字符的随机串 */ + private String nonce; + + /** 加密签名,用以校验推送客户端身份合法性,校验方法见实例验证 */ + private String signature; + + /** 推送时间戳(毫秒) */ + private Long time; + + /** 消息ID */ + private String id; + +} diff --git a/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/domain/DataPonit.java b/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/domain/DataPonit.java new file mode 100644 index 0000000..cec147b --- /dev/null +++ b/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/domain/DataPonit.java @@ -0,0 +1,31 @@ +package com.ruoyi.device.iot.domain; + +import lombok.Data; + +/** + * 数据点数据 + * + * @author ruoyi + */ + +@Data +public class DataPonit { + + /** 设备名称 */ + private String dev_name; + + /** 设备上报的时间戳 */ + private Long at; + + /** 产品id */ + private String pid; + + /** 固定值:1 */ + private Integer type; + + /** 数据点id */ + private String ds_id; + + /** 消息值 */ + private String value; +} diff --git a/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/receive/ReceiveController.java b/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/receive/ReceiveController.java new file mode 100644 index 0000000..bfbe38f --- /dev/null +++ b/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/receive/ReceiveController.java @@ -0,0 +1,187 @@ +package com.ruoyi.device.iot.receive; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.device.iot.domain.BodyObj; +import com.ruoyi.device.iot.util.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; + +import java.io.UnsupportedEncodingException; + +/** + * 接收硬件参数 + * + * @author ruoyi + */ +@RestController +@RequestMapping("/common") +public class ReceiveController { + + private static final Logger log = LoggerFactory.getLogger(ReceiveController.class); + + @Value(value = "${watering.token}") + private String token; + + /** + * 功能描述:第三方平台数据接收。

+ *

+ * @param body 数据消息 + * @return 任意字符串。OneNet平台接收到http 200的响应,才会认为数据推送成功,否则会重发。 + */ + @RequestMapping(value = "/receive",method = RequestMethod.POST) + @ResponseBody + public String receive(@RequestBody String body){ + + log.info("receive方法接收到参数: body String --- " +body); + /************************************************ + * 解析数据推送请求,非加密模式。 + * 如果是明文模式使用以下代码 + **************************************************/ + /*************明文模式 start****************/ + BodyObj obj = Util.resolveBody(body, false); + log.info("receive方法解析对象: body Object --- " + JSON.toJSONString(obj)); + if (obj != null){ + boolean dataRight = Util.checkSignature(obj, token); + if (dataRight){ + log.info("receive方法验证签名正确: content" + JSON.toJSONString(obj)); + Object msg = obj.getMsg(); + /**TODO 接收到msg 更新设备参数*/ +// msg. + log.info("receive方法-获取到消息体: msg---" + JSON.toJSONString(msg)); + + }else { + log.info("receive方法验证签名错误: signature error"); + } + + }else { + log.info("receive方法参数为空: body empty error"); + } + /*************明文模式 end****************/ + + + /******************************************************** + * 解析数据推送请求,加密模式 + * + * 如果是加密模式使用以下代码 + ********************************************************/ + /*************加密模式 start****************/ +// Util.BodyObj obj1 = Util.resolveBody(body, true); +// logger.info("data receive: body Object--- " +obj1); +// if (obj1 != null){ +// boolean dataRight1 = Util.checkSignature(obj1, token); +// if (dataRight1){ +// String msg = Util.decryptMsg(obj1, aeskey); +// logger.info("data receive: content" + msg); +// }else { +// logger.info("data receive: signature error " ); +// } +// }else { +// logger.info("data receive: body empty error" ); +// } + /*************加密模式 end****************/ + return "ok"; + } + + /** + * 功能说明: URL&Token验证接口。如果验证成功返回msg的值,否则返回其他值。 + * @param msg 验证消息 + * @param nonce 随机串 + * @param signature 签名 + * @return msg值 + */ + + @RequestMapping(value = "/receive", method = RequestMethod.GET) + @ResponseBody + public String check(@RequestParam(value = "msg") String msg, + @RequestParam(value = "nonce") String nonce, + @RequestParam(value = "signature") String signature) throws UnsupportedEncodingException { + + log.info("check方法接收到参数:: msg:{} nonce{} signature:{}",msg,nonce,signature); + if (Util.checkToken(msg,nonce,signature,token)){ + return msg; + }else { + return "error"; + } + + } + +// public static void main(String[] args) throws Exception { +// SpringApplication.run(ReceiverDemo.class, args); +// } + +// /** +// * 功能描述 消息摘要计算 +// * +//// * @param requestData 平台推送消息 +// * @return +// * @throws Exception +// */ +// @RequestMapping("/signature") +// public String signature(@RequestParam String nonce,@RequestParam String msg, @RequestParam String signature){// @RequestBody String msg, String nonce, String signature +//// log.info("接收到参数:requestData=【{}】", JSON.toJSON(requestData)); +// log.info("接收到参数:msg="+msg+",token="+token+",nonce="+nonce+",signature="+signature); +//// log.info("接收到参数:requestData=【{}】", requestData); +// +//// String signature = requestData.getSignature(); +//// String msg = requestData.getMsg(); +//// if(Objects.nonNull(requestData)){ +// if(StringUtils.isNotEmpty(msg)){ +// // 第一步 计算 MD5 并编码为 Base64 字符串 +// String calculatedSignature = calculateBase64MD5(token + nonce + msg); +// +// // 第二步 进行 URL 解码 +// calculatedSignature = urlDecode(calculatedSignature); +// +// // 第三步 比较计算得到的签名与请求参数中的签名是否相等 +// if (calculatedSignature.equals(signature)) { +// System.out.println("Token verification successful!"); +// } else { +// System.out.println("Token verification failed!"); +// } +// +// // 第4步 将URL Decode编码后的值与请求参数signature的值进行对比 +// if (StringUtils.isNotEmpty(signature) && signature.equals(calculatedSignature)) { +// log.info("签名验证正确,返回msg=【{}】",msg); +// +// /** TODO 处理msg +// * +// * */ +// return msg; +// } +// log.info("加密后的base64:【{}】,返回invalid token",calculatedSignature); +// return "invalid token"; +// }else{ +// log.info("msg,nonce为空,返回msg=【{}】",msg); +// return msg; +// } +// } +// +// // 计算 MD5 并编码为 Base64 字符串 +// private static String calculateBase64MD5(String input) { +// try { +// MessageDigest md = MessageDigest.getInstance("MD5"); +// byte[] md5Bytes = md.digest(input.getBytes(StandardCharsets.UTF_8)); +// // 编码为 Base64 字符串 +// return Base64.getEncoder().encodeToString(md5Bytes); +// } catch (Exception e) { +// e.printStackTrace(); +// return null; +// } +// } +// +// // URL 解码 +// private static String urlDecode(String input) { +// try { +// return URLDecoder.decode(input, StandardCharsets.UTF_8.toString()); +// } catch (Exception e) { +// e.printStackTrace(); +// return null; +// } +// } +} diff --git a/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/util/Util.java b/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/util/Util.java new file mode 100644 index 0000000..91a9262 --- /dev/null +++ b/AutoSprout-watering/src/main/java/com/ruoyi/device/iot/util/Util.java @@ -0,0 +1,141 @@ +package com.ruoyi.device.iot.util; + +import com.ruoyi.device.iot.domain.BodyObj; +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.*; + + +/** + * 功能描述: OneNet数据推送接收程序工具类。 + * + * Created by Roy on 2017/5/17. + * + */ +public class Util { + + private static Logger logger = LoggerFactory.getLogger(Util.class); + + private static MessageDigest mdInst; + + static { + try { + mdInst = MessageDigest.getInstance("MD5"); + Security.addProvider(new BouncyCastleProvider()); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + + /** + * 功能描述:在OneNet平台配置数据接收地址时,平台会发送URL&token验证请求

+ * 使用此功能函数验证token + * @param msg 请求参数 的值 + * @param nonce 请求参数 的值 + * @param signature 请求参数 的值 + * @param token OneNet平台配置页面token的值 + * @return token检验成功返回true;token校验失败返回false + */ + public static boolean checkToken(String msg,String nonce,String signature, String token) throws UnsupportedEncodingException { + + byte[] paramB = new byte[token.length() + 8 + msg.length()]; + System.arraycopy(token.getBytes(), 0, paramB, 0, token.length()); + System.arraycopy(nonce.getBytes(), 0, paramB, token.length(), 8); + System.arraycopy(msg.getBytes(), 0, paramB, token.length() + 8, msg.length()); + String sig = com.sun.org.apache.xerces.internal.impl.dv.util.Base64.encode(mdInst.digest(paramB)); + logger.info("url&token validation: result {}, detail receive:{} calculate:{}", sig.equals(signature.replace(' ','+')),signature,sig); + return sig.equals(signature.replace(' ','+')); + } + + /** + * 功能描述: 检查接收数据的信息摘要是否正确。

+ * 方法非线程安全。 + * @param obj 消息体对象 + * @param token OneNet平台配置页面token的值 + * @return + */ + public static boolean checkSignature(BodyObj obj, String token) { + //计算接受到的消息的摘要 + //token长度 + 8B随机字符串长度 + 消息长度 + byte[] signature = new byte[token.length() + 8 + obj.getMsg().toString().length()]; + System.arraycopy(token.getBytes(), 0, signature, 0, token.length()); + System.arraycopy(obj.getNonce().getBytes(), 0, signature, token.length(), 8); + System.arraycopy(obj.getMsg().toString().getBytes(), 0, signature, token.length() + 8, obj.getMsg().toString().length()); + String calSig = Base64.encodeBase64String(mdInst.digest(signature)); + logger.info("check signature: result:{} receive sig:{},calculate sig: {}",calSig.equals(obj.getSignature()),obj.getSignature(),calSig); + return calSig.equals(obj.getSignature()); + } + + /** + * 功能描述 解密消息 + * @param obj 消息体对象 + * @param encodeKey OneNet平台第三方平台配置页面为用户生成的AES的BASE64编码格式秘钥 + * @return + * @throws NoSuchPaddingException + * @throws NoSuchAlgorithmException + * @throws InvalidAlgorithmParameterException + * @throws InvalidKeyException + * @throws BadPaddingException + * @throws IllegalBlockSizeException + */ + public static String decryptMsg(BodyObj obj, String encodeKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { + byte[] encMsg = Base64.decodeBase64(obj.getMsg().toString()); + byte[] aeskey = Base64.decodeBase64(encodeKey + "="); + SecretKey secretKey = new SecretKeySpec(aeskey, 0, 32, "AES"); + Cipher cipher = null; + cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(aeskey, 0, 16)); + byte[] allmsg = cipher.doFinal(encMsg); + byte[] msgLenBytes = new byte[4]; + System.arraycopy(allmsg, 16, msgLenBytes, 0, 4); + int msgLen = getMsgLen(msgLenBytes); + byte[] msg = new byte[msgLen]; + System.arraycopy(allmsg, 20, msg, 0, msgLen); + return new String(msg); + } + + /** + * 功能描述 解析数据推送请求,生成code>BodyObj消息对象 + * @param body 数据推送请求body部分 + * @param encrypted 表征是否为加密消息 + * @return 生成的BodyObj消息对象 + */ + public static BodyObj resolveBody(String body, boolean encrypted) { + JSONObject jsonMsg = new JSONObject(body); + BodyObj obj = new BodyObj(); + obj.setNonce(jsonMsg.getString("nonce")); + obj.setSignature(jsonMsg.getString("signature")); + if (encrypted) { + if (!jsonMsg.has("enc_msg")) { + return null; + } + obj.setMsg(jsonMsg.getString("enc_msg")); + } else { + if (!jsonMsg.has("msg")) { + return null; + } + obj.setMsg(jsonMsg.get("msg")); + } + return obj; + } + + private static int getMsgLen(byte[] arrays) { + int len = 0; + len += (arrays[0] & 0xFF) << 24; + len += (arrays[1] & 0xFF) << 16; + len += (arrays[2] & 0xFF) << 8; + len += (arrays[3] & 0xFF); + return len; + } + + +} \ No newline at end of file