KKSU
2024-11-15 2147ca2f66dd5ff83db5080988f4832bd10ac213
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package cc.mrbird.febs.unisoftiot.utils;
 
import cc.mrbird.febs.unisoftiot.enums.DeviceRequestUrlEnum;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SecureUtil;
 
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
 
/**
 * URL工具类,用于构造请求物联网控制台接口的URL
 */
public class UrlUtils {
 
    /**
     * http(s)://iot-api.unisoft.cn/{AppID}/{接口列表中的path}/?{其他参数}&sign={sign}&ts={ts}
     *
     * sign    签名    所有请求物联网控制台接口,均需在url中携带此参数sign={sign}
     * 取值方法:md5(md5(开发者密码) + 上面的ts参数),32位字符串
     * ts    时间戳    所有请求物联网控制台接口,均需在url中携带此参数ts={timestap}
     * 取值方法:请求时间(timezone,东八区),10位数字
     */
    public static void main(String[] args) {
        System.out.println(getUrl(BASE_URL,APP_ID,APP_SECRET, DeviceRequestUrlEnum.DEVICE_CONTROL.getRequestUrl(), null));
        LinkedHashMap<String, Object> parameters = new LinkedHashMap<>();
        parameters.put("group", "123456789");
        parameters.put("product", "test");
        System.out.println(getUrl(BASE_URL,APP_ID,APP_SECRET,"device/info",parameters));
    }
    // todo 优化到后台配置中
    public static final String BASE_URL = " http://iot-api.unisoft.cn/rtyVWcgmlr";
    public static final String APP_ID = "rtyVWcgmlr";
    public static final String APP_SECRET = "rtyVWcgmlr";
    /**
     * 构造请求物联网控制台接口的URL
     *
     * @param baseUrl       基础URL地址
     * @param appId         应用ID,用于标识应用
     * @param appSecret     应用密钥,用于生成签名
     * @param requestPath   请求路径,标识具体的接口
     * @param parameters    请求参数,包含除sign和ts之外的其他参数
     * @return              返回构造完成的URL字符串
     *
     * 该方法首先会构建基础URL,然后添加appId和请求路径,接着添加其他参数,
     * 最后通过appSecret和时间戳生成签名(sign),以及添加时间戳(ts)到URL中。
     */
    public static String getUrl(String baseUrl,
                                String appId,
                                String appSecret,
                                String requestPath,
                                LinkedHashMap<String, Object> parameters){
        StringBuilder fullUrl = new StringBuilder(baseUrl);
//        fullUrl.append("/"+appId);
        fullUrl.append(requestPath);
        fullUrl.append("?");
        //判断parameters参数是否为空,不为空则拼接在fullUrl
        parametersSetToUrl(fullUrl,parameters);
        getSignAndTs(appSecret,fullUrl,parameters);
        return fullUrl.toString();
    }
 
    /**
     * 将查询参数附加到URL路径上
     * 此方法接受一个URL路径和一个查询参数的映射,然后将这些参数附加到URL路径上
     * 如果没有查询参数,URL路径保持不变
     *
     * @param urlPath URL路径的StringBuilder对象,用于构建最终的URL
     * @param parameters 查询参数的LinkedHashMap,键为参数名,值为参数值
     * @return 返回包含查询参数的URL路径的StringBuilder对象
     */
    public static StringBuilder parametersSetToUrl(StringBuilder urlPath, LinkedHashMap<String, Object> parameters) {
        // 检查参数是否为空,如果为空则直接返回URL路径
        if (parameters == null || parameters.isEmpty()) {
            return urlPath;
        }
        joinQueryParameters(urlPath, parameters);
        // 返回包含查询参数的URL路径
        return urlPath;
    }
 
    /**
     * 将查询参数拼接到URL路径后
     *
     * @param urlPath   URL路径
     * @param parameters 查询参数
     * @return 拼接后的URL路径
     */
    public static StringBuilder joinQueryParameters(StringBuilder urlPath, LinkedHashMap<String, Object> parameters) {
        if (parameters == null || parameters.isEmpty()) {
            return urlPath;
        }
 
        boolean isFirst = true;
        for (Map.Entry<String, Object> mapElement : parameters.entrySet()) {
            Object value = mapElement.getValue();
            // 根据是否是第一个参数来决定是否添加&
            if (isFirst) {
                isFirst = false;
            } else {
                urlPath.append('&');
            }
 
            // 拼接参数到URL路径
            urlPath.append(mapElement.getKey())
                    .append('=')
                    .append(urlEncode(value.toString()));
        }
        return urlPath;
    }
 
    /**
     * 对URL参数进行编码
     *
     * @param s 参数字符串
     * @return 编码后的参数字符串
     */
    public static String urlEncode(String s) {
        try {
            return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(StandardCharsets.UTF_8.name() + " is unsupported", e);
        }
    }
 
 
 
    /**
     * 生成签名和时间戳参数
     * 该方法用于给定的URL路径添加签名(sign)和时间戳(ts)参数,以确保请求的安全性和有效性
     * 签名是通过将appSecret进行MD5加密,然后与当前时间戳拼接后再进行MD5加密生成
     * 这种方法确保了每个请求都有一个唯一的、基于时间的签名,从而防止了重放攻击
     *
     * @param appSecret 应用密钥,用于生成签名
     * @param urlPath StringBuilder对象,用于拼接URL路径和查询参数
     * @param parameters 请求参数,用于判断是否需要添加额外的连接符
     * @return 返回包含签名和时间戳参数的URL路径字符串
     */
    public static String getSignAndTs(String appSecret,StringBuilder urlPath, LinkedHashMap<String, Object> parameters) {
        //使用MD5加密appSecret
        String md5AppSecret = SecureUtil.md5(appSecret);
        //获取当前时间戳
        String timestamp = String.valueOf(DateUtil.currentSeconds());
        //生成sign参数,通过md5加密(appSecret的md5值 + 时间戳)
        String md5Sign = SecureUtil.md5(md5AppSecret + timestamp);
        //返回包含sign和ts参数的字符串
        if (CollUtil.isNotEmpty(parameters)) {
            urlPath.append("&");
        }
        urlPath.append("sign=");
        urlPath.append(md5Sign);
        urlPath.append("&ts=");
        urlPath.append(timestamp);
        return urlPath.toString();
    }
 
}