Commit 9d89fc70564a58cc0ee9ffc4a7f928da6437aaa7
Merge branch '20221019' into 'master'
refactor: 脚本引擎模板调整 See merge request huang/thingsboard3.3.2!139
Showing
75 changed files
with
5949 additions
and
126 deletions
... | ... | @@ -83,6 +83,10 @@ |
83 | 83 | </dependency> |
84 | 84 | <dependency> |
85 | 85 | <groupId>org.thingsboard.common.transport</groupId> |
86 | + <artifactId>tcp</artifactId> | |
87 | + </dependency> | |
88 | + <dependency> | |
89 | + <groupId>org.thingsboard.common.transport</groupId> | |
86 | 90 | <artifactId>http</artifactId> |
87 | 91 | </dependency> |
88 | 92 | <dependency> | ... | ... |
... | ... | @@ -28,30 +28,13 @@ import org.springframework.security.core.Authentication; |
28 | 28 | import org.springframework.security.core.context.SecurityContextHolder; |
29 | 29 | import org.springframework.web.bind.annotation.ExceptionHandler; |
30 | 30 | import org.thingsboard.server.cluster.TbClusterService; |
31 | -import org.thingsboard.server.common.data.Customer; | |
32 | -import org.thingsboard.server.common.data.Dashboard; | |
33 | -import org.thingsboard.server.common.data.DashboardInfo; | |
34 | -import org.thingsboard.server.common.data.Device; | |
35 | -import org.thingsboard.server.common.data.DeviceInfo; | |
36 | -import org.thingsboard.server.common.data.DeviceProfile; | |
37 | -import org.thingsboard.server.common.data.EntityType; | |
38 | -import org.thingsboard.server.common.data.EntityView; | |
39 | -import org.thingsboard.server.common.data.EntityViewInfo; | |
40 | -import org.thingsboard.server.common.data.HasName; | |
41 | -import org.thingsboard.server.common.data.HasTenantId; | |
42 | -import org.thingsboard.server.common.data.OtaPackage; | |
43 | -import org.thingsboard.server.common.data.OtaPackageInfo; | |
44 | -import org.thingsboard.server.common.data.TbResource; | |
45 | -import org.thingsboard.server.common.data.TbResourceInfo; | |
46 | -import org.thingsboard.server.common.data.Tenant; | |
47 | -import org.thingsboard.server.common.data.TenantInfo; | |
48 | -import org.thingsboard.server.common.data.TenantProfile; | |
49 | -import org.thingsboard.server.common.data.User; | |
31 | +import org.thingsboard.server.common.data.*; | |
50 | 32 | import org.thingsboard.server.common.data.alarm.Alarm; |
51 | 33 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
52 | 34 | import org.thingsboard.server.common.data.asset.Asset; |
53 | 35 | import org.thingsboard.server.common.data.asset.AssetInfo; |
54 | 36 | import org.thingsboard.server.common.data.audit.ActionType; |
37 | +import org.thingsboard.server.common.data.device.profile.*; | |
55 | 38 | import org.thingsboard.server.common.data.edge.Edge; |
56 | 39 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
57 | 40 | import org.thingsboard.server.common.data.edge.EdgeEventType; |
... | ... | @@ -919,4 +902,29 @@ public abstract class BaseController { |
919 | 902 | return MediaType.APPLICATION_OCTET_STREAM; |
920 | 903 | } |
921 | 904 | } |
905 | + | |
906 | + /** | |
907 | + * 构建设备配置的配置数据 | |
908 | + * @param transportType 产品的通信协议 | |
909 | + * @param deviceProfileData 空的设备配置数据 | |
910 | + * @param transportConfiguration 传输配置 | |
911 | + * @param scriptText 自定义数据协议的解析脚本 | |
912 | + */ | |
913 | + protected void buildDeviceProfileData(String transportType,DeviceProfileData deviceProfileData,DeviceProfileTransportConfiguration transportConfiguration,String scriptText) { | |
914 | + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); | |
915 | + deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); | |
916 | + | |
917 | + // 传输类型默认都是Default | |
918 | + if(transportType ==null || DeviceTransportType.DEFAULT.name().equals(transportType)){ | |
919 | + deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); | |
920 | + }else if(DeviceTransportType.TCP.name().equals(transportType)){ | |
921 | + YtTcpDeviceProfileTransportConfiguration tcpDeviceProfileTransportConfiguration = (YtTcpDeviceProfileTransportConfiguration) transportConfiguration; | |
922 | + String scriptId = tcpDeviceProfileTransportConfiguration.getScriptId(); | |
923 | + tcpDeviceProfileTransportConfiguration.setPingText(scriptText); | |
924 | + deviceProfileData.setTransportConfiguration(tcpDeviceProfileTransportConfiguration); | |
925 | + }else{ | |
926 | + deviceProfileData.setTransportConfiguration(transportConfiguration); | |
927 | + } | |
928 | + | |
929 | + } | |
922 | 930 | } | ... | ... |
... | ... | @@ -59,6 +59,7 @@ public class YtAdminController extends BaseController { |
59 | 59 | private final UserService tbUserService; |
60 | 60 | |
61 | 61 | @PostMapping("/tenant") |
62 | + @Deprecated | |
62 | 63 | @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN'},{'api:yt:admin:tenant:post'})") |
63 | 64 | public ResponseEntity<TenantDTO> saveTenant(@RequestBody TenantReqDTO tenantReqDTO) { |
64 | 65 | TenantDTO newTenant = ytTenantService.createNewTenant(tenantReqDTO); | ... | ... |
... | ... | @@ -13,10 +13,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; |
13 | 13 | import org.thingsboard.server.common.data.DeviceProfileType; |
14 | 14 | import org.thingsboard.server.common.data.DeviceTransportType; |
15 | 15 | import org.thingsboard.server.common.data.audit.ActionType; |
16 | -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; | |
17 | -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; | |
18 | -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; | |
19 | -import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; | |
16 | +import org.thingsboard.server.common.data.device.profile.*; | |
20 | 17 | import org.thingsboard.server.common.data.edge.EdgeEventActionType; |
21 | 18 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
22 | 19 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
... | ... | @@ -24,6 +21,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; |
24 | 21 | import org.thingsboard.server.common.data.id.TenantId; |
25 | 22 | import org.thingsboard.server.common.data.page.PageLink; |
26 | 23 | import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
24 | +import org.thingsboard.server.common.data.rule.RuleChain; | |
27 | 25 | import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; |
28 | 26 | import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; |
29 | 27 | import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException; |
... | ... | @@ -34,6 +32,7 @@ import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; |
34 | 32 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; |
35 | 33 | import org.thingsboard.server.common.msg.queue.ServiceQueue; |
36 | 34 | import org.thingsboard.server.controller.BaseController; |
35 | +import org.thingsboard.server.dao.yunteng.service.YtDeviceScriptService; | |
37 | 36 | import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService; |
38 | 37 | import org.thingsboard.server.service.security.permission.Operation; |
39 | 38 | |
... | ... | @@ -51,7 +50,7 @@ import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant. |
51 | 50 | @Api(tags = {"设备配置管理"}) |
52 | 51 | public class YtDeviceProfileController extends BaseController { |
53 | 52 | private final YtDeviceProfileService ytDeviceProfileService; |
54 | - | |
53 | + private final YtDeviceScriptService javaScriptService; | |
55 | 54 | @PostMapping() |
56 | 55 | @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:post','api:yt:deviceProfile:update'})") |
57 | 56 | @ApiOperation("创建 | 编辑") |
... | ... | @@ -66,11 +65,14 @@ public class YtDeviceProfileController extends BaseController { |
66 | 65 | * 2/3.处理TB业务逻辑 |
67 | 66 | * 3/3.处理业务平台的业务逻辑 |
68 | 67 | */ |
69 | - | |
70 | - deviceProfileDTO.setTenantId(getCurrentUser().getCurrentTenantId()); | |
68 | + String tenantId = getCurrentUser().getCurrentTenantId(); | |
69 | + deviceProfileDTO.setTenantId(tenantId); | |
71 | 70 | DeviceProfile tbDeviceProfile = buildTbDeviceProfileFromDeviceProfileDTO(deviceProfileDTO); |
71 | + | |
72 | 72 | updateTbDeviceProfile(tbDeviceProfile, created); |
73 | 73 | |
74 | + ytDeviceProfileService.insertOrUpdate(deviceProfileDTO); | |
75 | + | |
74 | 76 | return ResponseEntity.ok(deviceProfileDTO); |
75 | 77 | } |
76 | 78 | |
... | ... | @@ -130,9 +132,7 @@ public class YtDeviceProfileController extends BaseController { |
130 | 132 | @RequestParam(value = "transportType", required = false) String transportType, |
131 | 133 | @RequestParam(value = ORDER_FILED, required = false) String orderBy, |
132 | 134 | @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType) throws ThingsboardException { |
133 | - | |
134 | - PageLink pageLink = createPageLink(pageSize, page > 1 ? page - 1 : 0, name, orderBy==null ? "createdTime":orderBy, orderType == null ? OrderTypeEnum.DESC.name(): orderType.name()); | |
135 | - return ytDeviceProfileService.page(pageLink, getCurrentUser().getCurrentTenantId(), transportType); | |
135 | + return ytDeviceProfileService.page(page,pageSize,orderBy,orderType, getCurrentUser().getCurrentTenantId(),name, transportType); | |
136 | 136 | } |
137 | 137 | |
138 | 138 | @GetMapping("/me/list") |
... | ... | @@ -146,11 +146,13 @@ public class YtDeviceProfileController extends BaseController { |
146 | 146 | @ApiOperation("删除") |
147 | 147 | @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:delete'})") |
148 | 148 | public void deleteDevices(@Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO) throws ThingsboardException { |
149 | - ytDeviceProfileService.checkDeviceProfiles(getCurrentUser().getCurrentTenantId(), deleteDTO.getIds()); | |
149 | + String tenantId = getCurrentUser().getCurrentTenantId(); | |
150 | + ytDeviceProfileService.checkDeviceProfiles(tenantId, deleteDTO.getIds()); | |
150 | 151 | |
151 | 152 | for (String id : deleteDTO.getIds()) { |
152 | 153 | deleteTbDeviceProfile(id); |
153 | 154 | } |
155 | + ytDeviceProfileService.deleteDeviceProfiles(tenantId,deleteDTO.getIds()); | |
154 | 156 | |
155 | 157 | } |
156 | 158 | |
... | ... | @@ -217,33 +219,50 @@ public class YtDeviceProfileController extends BaseController { |
217 | 219 | tbDeviceProfile.setTenantId(TenantId.fromUUID(tenantId)); |
218 | 220 | tbDeviceProfile.setDefault(deviceProfileDTO.isDefault()); |
219 | 221 | |
222 | + | |
223 | + String chainStr = deviceProfileDTO.getDefaultRuleChainId(); | |
224 | + if(StringUtils.isBlank(chainStr)){ | |
225 | + throw new YtDataValidationException(ErrorMessage.RULE_CHAIN_NOT_ENABLE.getMessage()); | |
226 | + } | |
227 | + UUID chainId = UUID.fromString(chainStr); | |
228 | + RuleChain chain = ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID,new RuleChainId(chainId)); | |
229 | + if(chain==null || !deviceProfileDTO.getTenantId().equals(chain.getTenantId().getId().toString())){ | |
230 | + throw new YtDataValidationException(ErrorMessage.RULE_CHAIN_NOT_ENABLE.getMessage()); | |
231 | + } | |
232 | + | |
220 | 233 | // 获取当前租户的默认规则链 |
221 | 234 | if (StringUtils.isNotBlank(deviceProfileDTO.getDefaultRuleChainId())) { |
222 | - UUID chainId = UUID.fromString(deviceProfileDTO.getDefaultRuleChainId()); | |
223 | 235 | tbDeviceProfile.setDefaultRuleChainId(new RuleChainId(chainId)); |
224 | 236 | } |
225 | 237 | |
226 | 238 | tbDeviceProfile.setDefaultQueueName(ServiceQueue.MAIN); |
227 | 239 | tbDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); |
228 | 240 | |
229 | - DeviceProfileData deviceProfileData = new DeviceProfileData(); | |
230 | - deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); | |
231 | - deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); | |
232 | 241 | |
233 | 242 | // 传输类型默认都是Default |
234 | 243 | String transportType = deviceProfileDTO.getTransportType(); |
244 | + String scriptText=null; | |
235 | 245 | if(transportType ==null || DeviceTransportType.DEFAULT.name().equals(transportType)){ |
236 | 246 | tbDeviceProfile.setTransportType(DeviceTransportType.DEFAULT); |
237 | - deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); | |
238 | 247 | }else{ |
239 | 248 | tbDeviceProfile.setTransportType(DeviceTransportType.valueOf(transportType)); |
240 | - deviceProfileData.setTransportConfiguration(deviceProfileDTO.getProfileData().getTransportConfiguration()); | |
241 | 249 | } |
242 | 250 | |
251 | + if(DeviceTransportType.TCP.name().equals(transportType)){ | |
252 | + YtTcpDeviceProfileTransportConfiguration tcpDeviceProfileTransportConfiguration = (YtTcpDeviceProfileTransportConfiguration) deviceProfileDTO.getProfileData().getTransportConfiguration(); | |
253 | + String scriptId = tcpDeviceProfileTransportConfiguration.getScriptId(); | |
254 | + scriptText =javaScriptService.getScriptText(deviceProfileDTO.getTenantId(), scriptId); | |
255 | + deviceProfileDTO.setScriptId(scriptId); | |
256 | + } | |
257 | + | |
258 | + DeviceProfileData deviceProfileData = new DeviceProfileData(); | |
259 | + buildDeviceProfileData(transportType,deviceProfileData,deviceProfileDTO.getProfileData().getTransportConfiguration(),scriptText); | |
260 | + | |
243 | 261 | if(deviceProfileDTO.getProfileData()!=null |
244 | 262 | && deviceProfileDTO.getProfileData().getAlarms() !=null){ |
245 | 263 | deviceProfileData.setAlarms(deviceProfileDTO.getProfileData().getAlarms()); |
246 | 264 | } |
265 | + | |
247 | 266 | tbDeviceProfile.setProfileData(deviceProfileData); |
248 | 267 | |
249 | 268 | return tbDeviceProfile; | ... | ... |
application/src/main/java/org/thingsboard/server/controller/yunteng/YtDeviceScriptController.java
0 → 100644
1 | +package org.thingsboard.server.controller.yunteng; | |
2 | + | |
3 | +import com.fasterxml.jackson.databind.JsonNode; | |
4 | +import com.fasterxml.jackson.databind.ObjectMapper; | |
5 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
6 | +import com.google.common.util.concurrent.Futures; | |
7 | +import com.google.common.util.concurrent.ListenableFuture; | |
8 | +import com.google.common.util.concurrent.MoreExecutors; | |
9 | +import com.google.gson.JsonElement; | |
10 | +import com.google.gson.JsonParser; | |
11 | +import io.swagger.annotations.Api; | |
12 | +import io.swagger.annotations.ApiOperation; | |
13 | +import lombok.RequiredArgsConstructor; | |
14 | +import org.apache.commons.lang3.StringUtils; | |
15 | +import org.springframework.http.ResponseEntity; | |
16 | +import org.springframework.security.access.prepost.PreAuthorize; | |
17 | +import org.springframework.validation.annotation.Validated; | |
18 | +import org.springframework.web.bind.annotation.*; | |
19 | +import org.thingsboard.rule.engine.api.ScriptEngine; | |
20 | +import org.thingsboard.server.common.data.DeviceProfile; | |
21 | +import org.thingsboard.server.common.data.DeviceProfileProvisionType; | |
22 | +import org.thingsboard.server.common.data.DeviceProfileType; | |
23 | +import org.thingsboard.server.common.data.DeviceTransportType; | |
24 | +import org.thingsboard.server.common.data.audit.ActionType; | |
25 | +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; | |
26 | +import org.thingsboard.server.common.data.edge.EdgeEventActionType; | |
27 | +import org.thingsboard.server.common.data.exception.ThingsboardException; | |
28 | +import org.thingsboard.server.common.data.id.DeviceProfileId; | |
29 | +import org.thingsboard.server.common.data.id.RuleChainId; | |
30 | +import org.thingsboard.server.common.data.id.TenantId; | |
31 | +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; | |
32 | +import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; | |
33 | +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants; | |
34 | +import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException; | |
35 | +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; | |
36 | +import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO; | |
37 | +import org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO; | |
38 | +import org.thingsboard.server.common.data.yunteng.dto.YtDeviceScriptDTO; | |
39 | +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; | |
40 | +import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; | |
41 | +import org.thingsboard.server.common.msg.queue.ServiceQueue; | |
42 | +import org.thingsboard.server.common.yunteng.script.YtScriptInvokeService; | |
43 | +import org.thingsboard.server.common.yunteng.script.YtScriptType; | |
44 | +import org.thingsboard.server.controller.BaseController; | |
45 | +import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService; | |
46 | +import org.thingsboard.server.dao.yunteng.service.YtDeviceScriptService; | |
47 | + | |
48 | +import java.time.LocalDateTime; | |
49 | +import java.time.ZoneOffset; | |
50 | +import java.util.List; | |
51 | +import java.util.Objects; | |
52 | +import java.util.UUID; | |
53 | +import java.util.concurrent.TimeUnit; | |
54 | + | |
55 | +import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*; | |
56 | + | |
57 | +@RestController | |
58 | +@RequiredArgsConstructor | |
59 | +@RequestMapping("api/yt/js") | |
60 | +@Api(tags = {"设备数据解析脚本管理"}) | |
61 | +public class YtDeviceScriptController extends BaseController { | |
62 | + private final YtDeviceScriptService scriptService; | |
63 | + private final YtDeviceProfileService ytDeviceProfileService; | |
64 | + private final YtScriptInvokeService jsEngine; | |
65 | + private static final JsonParser parser = new JsonParser(); | |
66 | + | |
67 | + private static final ObjectMapper objectMapper = new ObjectMapper(); | |
68 | + | |
69 | + @PostMapping() | |
70 | + @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:js:post','api:yt:js:update'})") | |
71 | + @ApiOperation("创建 | 编辑") | |
72 | + public ResponseEntity<YtDeviceScriptDTO> saveJavaScript( | |
73 | + @RequestBody YtDeviceScriptDTO scriptDTO) throws ThingsboardException { | |
74 | + String id = scriptDTO.getId(); | |
75 | + boolean created = StringUtils.isBlank(id); | |
76 | + String tenantId = getCurrentUser().getCurrentTenantId(); | |
77 | + scriptDTO.setTenantId(tenantId); | |
78 | + scriptService.validateFormdata(scriptDTO,created); | |
79 | + /** | |
80 | + * 业务流程 | |
81 | + * 1/3.编辑时同步刷新相关的设备配置 | |
82 | + * 2/3.处理TB业务逻辑 | |
83 | + * 3/3.处理业务平台的业务逻辑 | |
84 | + */ | |
85 | + if(!created){ | |
86 | + List<DeviceProfileDTO> usedProfiles =ytDeviceProfileService.findDeviceProfile(tenantId,id); | |
87 | + for(DeviceProfileDTO profile:usedProfiles){ | |
88 | + DeviceProfile tbDeviceProfile = buildTbDeviceProfileFromDeviceProfileDTO(profile,scriptDTO.getTenantId(),scriptDTO.getConvertJs()); | |
89 | + updateTbDeviceProfile(tbDeviceProfile); | |
90 | + } | |
91 | + | |
92 | + } | |
93 | + | |
94 | + | |
95 | + String creator = getCurrentUser().getCurrentUserId(); | |
96 | + scriptDTO.setCreator(creator); | |
97 | + scriptDTO.setUpdater(creator); | |
98 | + scriptService.insertOrUpdate(scriptDTO); | |
99 | + | |
100 | + return ResponseEntity.ok(scriptDTO); | |
101 | + } | |
102 | + | |
103 | + /** | |
104 | + * 更新thingsboard的设备配置信息 | |
105 | + * | |
106 | + * @param deviceProfile 设备配置 | |
107 | + * @throws ThingsboardException | |
108 | + */ | |
109 | + private DeviceProfile updateTbDeviceProfile(DeviceProfile deviceProfile) throws ThingsboardException { | |
110 | + boolean isFirmwareChanged = false; | |
111 | + boolean isSoftwareChanged = false; | |
112 | + | |
113 | + DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfile.getId()); | |
114 | + if (!Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId())) { | |
115 | + isFirmwareChanged = true; | |
116 | + } | |
117 | + if (!Objects.equals(deviceProfile.getSoftwareId(), oldDeviceProfile.getSoftwareId())) { | |
118 | + isSoftwareChanged = true; | |
119 | + } | |
120 | + if (FastIotConstants.ASSERT_DEFAULT_NAME.equals(oldDeviceProfile.getName()) && !Objects.equals(deviceProfile.getName(), oldDeviceProfile.getName())) { | |
121 | + throw new YtDataValidationException(ErrorMessage.ASSERT_DEFAULT_NAME_NO_CHANGED.getMessage()); | |
122 | + } | |
123 | + | |
124 | + DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); | |
125 | + | |
126 | + tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); | |
127 | + tbClusterService.broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), savedDeviceProfile.getId(), ComponentLifecycleEvent.UPDATED); | |
128 | + | |
129 | + logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile, | |
130 | + null,ActionType.UPDATED, null); | |
131 | + | |
132 | + otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); | |
133 | + | |
134 | + sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(),EdgeEventActionType.UPDATED); | |
135 | + return savedDeviceProfile; | |
136 | + } | |
137 | + | |
138 | + @GetMapping("{id}") | |
139 | + @ApiOperation("详情") | |
140 | +// @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:get'})") | |
141 | + public ResponseEntity<YtDeviceScriptDTO> getDevice(@PathVariable("id") String id) throws ThingsboardException { | |
142 | + return ResponseEntity.of(scriptService.getDeviceScript(getCurrentUser().getCurrentTenantId(), id)); | |
143 | + } | |
144 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | |
145 | + @GetMapping(params = {PAGE_SIZE, PAGE}) | |
146 | + @ApiOperation("分页查询") | |
147 | + public YtPageData<YtDeviceScriptDTO> pageDeviceProfile( | |
148 | + @RequestParam(PAGE_SIZE) int pageSize, | |
149 | + @RequestParam(PAGE) int page, | |
150 | + @RequestParam(value = "name", required = false) String name, | |
151 | + @RequestParam(value = "transportType", required = false) String transportType, | |
152 | + @RequestParam(value = ORDER_FILED, required = false) String orderFiled, | |
153 | + @RequestParam(value = ORDER_TYPE, required = false) OrderTypeEnum orderType) throws ThingsboardException { | |
154 | + | |
155 | + return scriptService.page(page, pageSize, orderFiled, orderType, getCurrentUser().getCurrentTenantId()); | |
156 | + } | |
157 | + | |
158 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | |
159 | + @GetMapping("/me/list") | |
160 | + @ApiOperation("选项列表") | |
161 | + public ResponseEntity listDeviceProfile() throws ThingsboardException { | |
162 | + List<YtDeviceScriptDTO> results = scriptService.findDeviceScript(getCurrentUser().getCurrentTenantId()); | |
163 | + return ResponseEntity.ok(results); | |
164 | + } | |
165 | + | |
166 | + @DeleteMapping | |
167 | + @ApiOperation("删除") | |
168 | + @PreAuthorize("@check.checkPermissions({'TENANT_ADMIN'},{'api:yt:deviceProfile:delete'})") | |
169 | + public void deleteDevices(@Validated({DeleteGroup.class}) @RequestBody DeleteDTO deleteDTO) throws ThingsboardException { | |
170 | + scriptService.deleteScriptes(getCurrentUser().getCurrentTenantId(), deleteDTO.getIds()); | |
171 | + } | |
172 | + | |
173 | + | |
174 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") | |
175 | + @PostMapping("/test") | |
176 | + @ApiOperation("测试脚本执行结果") | |
177 | + public ResponseEntity<JsonNode> test(@RequestBody JsonNode inputParams) throws ThingsboardException { | |
178 | + try { | |
179 | + String script = inputParams.get("script").asText(); | |
180 | + String jsParam = inputParams.get("params").asText(); | |
181 | + | |
182 | + | |
183 | + String output = ""; | |
184 | + String errorText = ""; | |
185 | + ScriptEngine engine = null; | |
186 | + try { | |
187 | + UUID scriptId = jsEngine.eval(YtScriptType.TCP_TRANSPORT_SCRIPT, script).get(); | |
188 | + ListenableFuture<String> result = Futures.transformAsync(jsEngine.invokeFunction(scriptId, jsParam), | |
189 | + o -> { | |
190 | + try { | |
191 | + JsonElement json = parser.parse(o.toString()); | |
192 | + return Futures.immediateFuture(json.toString()); | |
193 | + } catch (Exception e) { | |
194 | + return Futures.immediateFailedFuture(e); | |
195 | + } | |
196 | + }, MoreExecutors.directExecutor()); | |
197 | + | |
198 | + output =result.get(20, TimeUnit.SECONDS); | |
199 | + } catch (Exception e) { | |
200 | + errorText = e.getMessage(); | |
201 | + } finally { | |
202 | + if (engine != null) { | |
203 | + engine.destroy(); | |
204 | + } | |
205 | + } | |
206 | + ObjectNode result = objectMapper.createObjectNode(); | |
207 | + result.put("output", output); | |
208 | + result.put("error", errorText); | |
209 | + return ResponseEntity.ok(result); | |
210 | + } catch (Exception e) { | |
211 | + throw handleException(e); | |
212 | + } | |
213 | + | |
214 | + | |
215 | + } | |
216 | + | |
217 | + | |
218 | + | |
219 | + | |
220 | + | |
221 | + /** | |
222 | + * 构造调用TBDeviceProfile需要的参数 | |
223 | + * | |
224 | + * @param deviceProfileDTO 页面接收的参数 | |
225 | + * @return 封装好的TBDeviceProfile | |
226 | + */ | |
227 | + private DeviceProfile buildTbDeviceProfileFromDeviceProfileDTO(DeviceProfileDTO deviceProfileDTO,String scriptId,String scriptText) { | |
228 | + DeviceProfile tbDeviceProfile = new DeviceProfile(); | |
229 | + if (StringUtils.isNotBlank(deviceProfileDTO.getId())) { | |
230 | + UUID profileId = UUID.fromString(deviceProfileDTO.getId()); | |
231 | + tbDeviceProfile.setId(new DeviceProfileId(profileId)); | |
232 | + tbDeviceProfile.setCreatedTime(deviceProfileDTO.getCreateTime().toInstant(ZoneOffset.of("+8")).toEpochMilli()); | |
233 | + }else{ | |
234 | + tbDeviceProfile.setCreatedTime(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli()); | |
235 | + } | |
236 | + tbDeviceProfile.setName(deviceProfileDTO.getName()); | |
237 | + tbDeviceProfile.setImage(deviceProfileDTO.getImage()); | |
238 | + tbDeviceProfile.setDescription(deviceProfileDTO.getDescription()); | |
239 | + tbDeviceProfile.setType(DeviceProfileType.DEFAULT); | |
240 | + UUID tenantId = UUID.fromString(deviceProfileDTO.getTenantId()); | |
241 | + tbDeviceProfile.setTenantId(TenantId.fromUUID(tenantId)); | |
242 | + tbDeviceProfile.setDefault(deviceProfileDTO.isDefault()); | |
243 | + | |
244 | + // 获取当前租户的默认规则链 | |
245 | + if (StringUtils.isNotBlank(deviceProfileDTO.getDefaultRuleChainId())) { | |
246 | + UUID chainId = UUID.fromString(deviceProfileDTO.getDefaultRuleChainId()); | |
247 | + tbDeviceProfile.setDefaultRuleChainId(new RuleChainId(chainId)); | |
248 | + } | |
249 | + | |
250 | + tbDeviceProfile.setDefaultQueueName(ServiceQueue.MAIN); | |
251 | + tbDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); | |
252 | + | |
253 | + | |
254 | + // 传输类型默认都是Default | |
255 | + String transportType = deviceProfileDTO.getTransportType(); | |
256 | + tbDeviceProfile.setTransportType(DeviceTransportType.valueOf(transportType)); | |
257 | + | |
258 | + | |
259 | + deviceProfileDTO.setScriptId(scriptId); | |
260 | + | |
261 | + DeviceProfileData deviceProfileData = new DeviceProfileData(); | |
262 | + buildDeviceProfileData(transportType,deviceProfileData,deviceProfileDTO.getProfileData().getTransportConfiguration(),scriptText); | |
263 | + | |
264 | + | |
265 | + tbDeviceProfile.setProfileData(deviceProfileData); | |
266 | + | |
267 | + return tbDeviceProfile; | |
268 | + } | |
269 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -176,7 +176,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
176 | 176 | |
177 | 177 | @Override |
178 | 178 | public void createSysAdmin() { |
179 | - createUser(Authority.SYS_ADMIN, null, null, "sysadmin@thingsboard.org", "sysadmin"); | |
179 | + createUser(Authority.SYS_ADMIN, null, null, "sysadmin@qq.com", "Sysadmin@123"); | |
180 | 180 | } |
181 | 181 | |
182 | 182 | @Override | ... | ... |
... | ... | @@ -170,9 +170,9 @@ ui: |
170 | 170 | database: |
171 | 171 | ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records |
172 | 172 | ts: |
173 | - type: "${DATABASE_TS_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) | |
173 | + type: "${DATABASE_TS_TYPE:timescale}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) | |
174 | 174 | ts_latest: |
175 | - type: "${DATABASE_TS_LATEST_TYPE:sql}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) | |
175 | + type: "${DATABASE_TS_LATEST_TYPE:timescale}" # cassandra, sql, or timescale (for hybrid mode, DATABASE_TS_TYPE value should be cassandra, or timescale) | |
176 | 176 | |
177 | 177 | # note: timescale works only with postgreSQL database for DATABASE_ENTITIES_TYPE. |
178 | 178 | |
... | ... | @@ -539,9 +539,9 @@ spring: |
539 | 539 | database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}" |
540 | 540 | datasource: |
541 | 541 | driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" |
542 | - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://47.99.141.212:20638/thingsboard-3.3.4}" | |
542 | + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://47.99.141.212:20638/timescale-3.3.4}" | |
543 | 543 | username: "${SPRING_DATASOURCE_USERNAME:postgres}" |
544 | - password: "${SPRING_DATASOURCE_PASSWORD:Vrr861!@waja}" | |
544 | + password: "${SPRING_DATASOURCE_PASSWORD:Xga751++nqnk}" | |
545 | 545 | # url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://101.133.234.90:28776/thingsboard-3.3.2}" |
546 | 546 | # username: "${SPRING_DATASOURCE_USERNAME:postgres}" |
547 | 547 | # password: "${SPRING_DATASOURCE_PASSWORD:Bua312!!iwcw}" |
... | ... | @@ -708,6 +708,59 @@ transport: |
708 | 708 | # Skip certificate validity check for client certificates. |
709 | 709 | skip_validity_check_for_client_cert: "${MQTT_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" |
710 | 710 | # Local CoAP transport parameters |
711 | + tcp: | |
712 | + # Enable/disable tcp transport protocol. | |
713 | + enabled: "${TCP_ENABLED:true}" | |
714 | + bind_address: "${TCP_BIND_ADDRESS:0.0.0.0}" | |
715 | + bind_port: "${TCP_BIND_PORT:8088}" | |
716 | + # Enable proxy protocol support. Disabled by default. If enabled, supports both v1 and v2. | |
717 | + # Useful to get the real IP address of the client in the logs and for rate limits. | |
718 | + proxy_enabled: "${TCP_PROXY_PROTOCOL_ENABLED:false}" | |
719 | + timeout: "${TCP_TIMEOUT:10000}" | |
720 | + msg_queue_size_per_device_limit: "${TCP_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism | |
721 | + netty: | |
722 | + leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}" | |
723 | + boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" | |
724 | + worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" | |
725 | + max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" | |
726 | + so_keep_alive: "${NETTY_SO_KEEPALIVE:false}" | |
727 | + # TCP SSL configuration | |
728 | + ssl: | |
729 | + # Enable/disable SSL support | |
730 | + enabled: "${TCP_SSL_ENABLED:false}" | |
731 | + # TCP SSL bind address | |
732 | + bind_address: "${TCP_SSL_BIND_ADDRESS:0.0.0.0}" | |
733 | + # TCP SSL bind port | |
734 | + bind_port: "${TCP_SSL_BIND_PORT:8888}" | |
735 | + # SSL protocol: See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms | |
736 | + protocol: "${TCP_SSL_PROTOCOL:TLSv1.2}" | |
737 | + # Server SSL credentials | |
738 | + credentials: | |
739 | + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) | |
740 | + type: "${TCP_SSL_CREDENTIALS_TYPE:PEM}" | |
741 | + # PEM server credentials | |
742 | + pem: | |
743 | + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) | |
744 | + cert_file: "${TCP_SSL_PEM_CERT:tcpserver.pem}" | |
745 | + # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; | |
746 | + key_file: "${TCP_SSL_PEM_KEY:tcpserver_key.pem}" | |
747 | + # Server certificate private key password (optional) | |
748 | + key_password: "${TCP_SSL_PEM_KEY_PASSWORD:server_key_password}" | |
749 | + # Keystore server credentials | |
750 | + keystore: | |
751 | + # Type of the key store | |
752 | + type: "${TCP_SSL_KEY_STORE_TYPE:JKS}" | |
753 | + # Path to the key store that holds the SSL certificate | |
754 | + store_file: "${TCP_SSL_KEY_STORE:tcpserver.jks}" | |
755 | + # Password used to access the key store | |
756 | + store_password: "${TCP_SSL_KEY_STORE_PASSWORD:server_ks_password}" | |
757 | + # Optional alias of the private key; If not set, the platform will load the first private key from the keystore; | |
758 | + key_alias: "${TCP_SSL_KEY_ALIAS:}" | |
759 | + # Optional password to access the private key. If not set, the platform will attempt to load the private keys that are not protected with the password; | |
760 | + key_password: "${TCP_SSL_KEY_PASSWORD:server_key_password}" | |
761 | + # Skip certificate validity check for client certificates. | |
762 | + skip_validity_check_for_client_cert: "${TCP_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" | |
763 | + # Local CoAP transport parameters | |
711 | 764 | coap: |
712 | 765 | # Enable/disable coap transport protocol. |
713 | 766 | enabled: "${COAP_ENABLED:true}" |
... | ... | @@ -1143,8 +1196,8 @@ file: |
1143 | 1196 | staticUrl: /oss/files/** #oss静态访问路径 只有type = local需要 |
1144 | 1197 | randomFileName: ${file.storage.randomFileName} |
1145 | 1198 | minio: |
1146 | - minioUrl: ${MINIO_URL:https://dev.thingskit.com:9000} #minio储存地址 | |
1147 | -# minioUrl: https://demo.thingskit.com:9000 #minio储存地址 | |
1199 | +# minioUrl: ${MINIO_URL:https://dev.thingskit.com:9000} #minio储存地址 | |
1200 | + minioUrl: https://demo.thingskit.com:9000 #minio储存地址 | |
1148 | 1201 | minioName: ${MINIO_NAME:YunTeng} #minio账户 |
1149 | 1202 | minioPass: ${MINIO_PWD:YunTeng123456} #minio访问密码 |
1150 | 1203 | bucketName: yunteng #minio储存桶名称 | ... | ... |
... | ... | @@ -43,6 +43,8 @@ public class DataConstants { |
43 | 43 | public static final String COAP_TRANSPORT_NAME = "COAP"; |
44 | 44 | public static final String LWM2M_TRANSPORT_NAME = "LWM2M"; |
45 | 45 | public static final String MQTT_TRANSPORT_NAME = "MQTT"; |
46 | + //Thingskit function | |
47 | + public static final String TCP_TRANSPORT_NAME = "TCP"; | |
46 | 48 | public static final String HTTP_TRANSPORT_NAME = "HTTP"; |
47 | 49 | public static final String SNMP_TRANSPORT_NAME = "SNMP"; |
48 | 50 | ... | ... |
... | ... | @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; |
21 | 21 | import com.fasterxml.jackson.annotation.JsonTypeInfo; |
22 | 22 | import io.swagger.annotations.ApiModel; |
23 | 23 | import org.thingsboard.server.common.data.DeviceTransportType; |
24 | +import org.thingsboard.server.common.data.device.profile.YtTcpDeviceProfileTransportConfiguration; | |
24 | 25 | |
25 | 26 | import java.io.Serializable; |
26 | 27 | |
... | ... | @@ -33,6 +34,7 @@ import java.io.Serializable; |
33 | 34 | @JsonSubTypes({ |
34 | 35 | @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"), |
35 | 36 | @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"), |
37 | + @JsonSubTypes.Type(value = YtTcpDeviceTransportConfiguration.class, name = "TCP"), | |
36 | 38 | @JsonSubTypes.Type(value = CoapDeviceTransportConfiguration.class, name = "COAP"), |
37 | 39 | @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"), |
38 | 40 | @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")}) | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.data; | |
17 | + | |
18 | +import com.fasterxml.jackson.annotation.JsonAnyGetter; | |
19 | +import com.fasterxml.jackson.annotation.JsonAnySetter; | |
20 | +import com.fasterxml.jackson.annotation.JsonIgnore; | |
21 | +import lombok.Data; | |
22 | +import org.thingsboard.server.common.data.DeviceTransportType; | |
23 | + | |
24 | +import java.util.HashMap; | |
25 | +import java.util.Map; | |
26 | + | |
27 | +@Data | |
28 | +public class YtTcpDeviceTransportConfiguration implements DeviceTransportConfiguration { | |
29 | + | |
30 | + | |
31 | + @Override | |
32 | + public DeviceTransportType getType() { | |
33 | + return DeviceTransportType.TCP; | |
34 | + } | |
35 | + | |
36 | +} | ... | ... |
... | ... | @@ -31,6 +31,7 @@ import java.io.Serializable; |
31 | 31 | @JsonSubTypes({ |
32 | 32 | @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), |
33 | 33 | @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), |
34 | + @JsonSubTypes.Type(value = YtTcpDeviceProfileTransportConfiguration.class, name = "TCP"), | |
34 | 35 | @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"), |
35 | 36 | @JsonSubTypes.Type(value = CoapDeviceProfileTransportConfiguration.class, name = "COAP"), |
36 | 37 | @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP") | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data.device.profile; | |
17 | + | |
18 | +import lombok.Data; | |
19 | +import org.thingsboard.server.common.data.DeviceTransportType; | |
20 | +import org.thingsboard.server.common.data.validation.NoXss; | |
21 | +import org.thingsboard.server.common.data.yunteng.enums.TcpDataTypeEnum; | |
22 | + | |
23 | +@Data | |
24 | +public class YtTcpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { | |
25 | + | |
26 | + @NoXss | |
27 | + private TcpDataTypeEnum dataFormat = TcpDataTypeEnum.HEX; | |
28 | + private String scriptId; | |
29 | + private String scriptText; | |
30 | + private String pingText; | |
31 | + | |
32 | + @Override | |
33 | + public DeviceTransportType getType() { | |
34 | + return DeviceTransportType.TCP; | |
35 | + } | |
36 | + | |
37 | + | |
38 | + | |
39 | + | |
40 | +} | ... | ... |
... | ... | @@ -49,6 +49,11 @@ public final class ModelConstants { |
49 | 49 | "iotfs_user_organization_mapping"; |
50 | 50 | /** 设备表 */ |
51 | 51 | public static final String IOTFS_DEVICE_TABLE_NAME = "iotfs_device"; |
52 | + /** 产品信息(设备配置)扩展表 */ | |
53 | + public static final String IOTFS_DEVICE_PROFILE_TABLE_NAME = "iotfs_device_profile"; | |
54 | + /** 自定义数据协议解析脚本 */ | |
55 | + public static final String IOTFS_DEVICE_SCRIPT_TABLE_NAME = "iotfs_java_script"; | |
56 | + | |
52 | 57 | /** 设备类型表 */ |
53 | 58 | public static final String IOTFS_DEVICE_TYPE_TABLE_NAME = "iotfs_device_type"; |
54 | 59 | /** 告警配置表 */ | ... | ... |
... | ... | @@ -81,7 +81,9 @@ public enum ErrorMessage { |
81 | 81 | DATA_BOARD_IS_PRIVATE(400057,"该数据看板不是公有视图"), |
82 | 82 | MESSAGE_SEND_TOO_FAST(400058,"1分钟内请求次数过多,请休息一下!"), |
83 | 83 | PASSWORD_INCORRECT(4010059, "密码错误!"), |
84 | - MESSAGE_SEND_FAILED(4010059, "消息发送失败!"), | |
84 | + MESSAGE_SEND_FAILED(4010060, "消息发送失败!"), | |
85 | + PROJECT_USED_SCRIPT(400061,"产品【%s】正在使用待删除的解析脚本"), | |
86 | + RULE_CHAIN_NOT_ENABLE(400062,"规则链不是有效的!"), | |
85 | 87 | HAVE_NO_PERMISSION(500002,"没有修改权限"); |
86 | 88 | private final int code; |
87 | 89 | private String message; | ... | ... |
... | ... | @@ -7,11 +7,15 @@ import lombok.EqualsAndHashCode; |
7 | 7 | import org.thingsboard.server.common.data.DeviceProfileType; |
8 | 8 | import org.thingsboard.server.common.data.DeviceTransportType; |
9 | 9 | import org.thingsboard.server.common.data.device.profile.DeviceProfileData; |
10 | +import org.thingsboard.server.common.data.id.DeviceProfileId; | |
11 | +import org.thingsboard.server.common.data.id.TenantId; | |
10 | 12 | import org.thingsboard.server.common.data.yunteng.common.AddGroup; |
13 | +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | |
11 | 14 | import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil; |
12 | 15 | |
13 | 16 | import javax.validation.Valid; |
14 | 17 | import javax.validation.constraints.NotEmpty; |
18 | +import javax.validation.constraints.NotNull; | |
15 | 19 | import java.time.Instant; |
16 | 20 | import java.time.LocalDateTime; |
17 | 21 | import java.time.ZoneId; |
... | ... | @@ -21,27 +25,38 @@ import java.util.UUID; |
21 | 25 | |
22 | 26 | @EqualsAndHashCode(callSuper = true) |
23 | 27 | @Data |
24 | -public class DeviceProfileDTO extends TenantDTO { | |
28 | +public class DeviceProfileDTO extends BaseDTO { | |
25 | 29 | @NotEmpty(message = "设备配置名称不能为空或者空字符串", groups = AddGroup.class) |
26 | - @ApiModelProperty(value = "设备配置名称") | |
30 | + @ApiModelProperty(value = "产品(设备配置)名称") | |
27 | 31 | private String name; |
28 | 32 | |
29 | 33 | private String description; |
34 | + | |
30 | 35 | /** |
31 | - * 转换脚本:TCP才会使用 | |
36 | + * TCP协议的解析脚本ID,由后端自己回填 | |
32 | 37 | */ |
33 | - private String convertJs; | |
38 | + private String scriptId; | |
34 | 39 | |
40 | + @ApiModelProperty(value = "租户ID") | |
41 | + private String tenantId; | |
35 | 42 | @NotEmpty(message = "传输协议不能为空或者空字符串", groups = AddGroup.class) |
36 | 43 | @ApiModelProperty(value = "传输协议", required = true) |
37 | - @NotEmpty(message = "传输协议不能为空或者二空字符串") | |
38 | 44 | private String transportType; |
45 | + | |
46 | + private String provisionType; | |
47 | + | |
48 | + @NotNull(message = "产品类型不能为空", groups = {AddGroup.class}) | |
49 | + @ApiModelProperty(value = "产品类型:GATEWAY,DIRECT_CONNECTION,SENSOR", required = true) | |
50 | + private DeviceTypeEnum deviceType; | |
51 | + | |
39 | 52 | /** |
40 | 53 | * TB的设备配置文件 |
41 | 54 | */ |
42 | 55 | @Deprecated |
43 | 56 | private String tbProfileId; |
44 | 57 | |
58 | + | |
59 | + | |
45 | 60 | @Valid |
46 | 61 | private transient DeviceProfileData profileData; |
47 | 62 | @ApiModelProperty(value = "关联规则链,默认关联根规则链", required = false) |
... | ... | @@ -76,17 +91,11 @@ public class DeviceProfileDTO extends TenantDTO { |
76 | 91 | this.profileData = JacksonUtil.convertValue(profileData, DeviceProfileData.class); |
77 | 92 | |
78 | 93 | } |
79 | - | |
80 | - public DeviceProfileDTO(UUID id, String name, Long time, String description, DeviceTransportType transportType, String defaultRuleChainId,boolean isDefault,String image,DeviceProfileType type, String convertJs) { | |
81 | - setId(id.toString()); | |
82 | - setCreateTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.of("+8"))); | |
94 | + public DeviceProfileDTO(String name, TenantId tenantId, DeviceProfileId tbProfileId, DeviceTypeEnum deviceType) { | |
83 | 95 | this.name = name; |
84 | - this.isDefault = isDefault; | |
85 | - this.image = image; | |
86 | - this.description = description; | |
87 | - this.type = type.name(); | |
88 | - this.convertJs = convertJs; | |
89 | - this.transportType = transportType.name(); | |
90 | - this.defaultRuleChainId = defaultRuleChainId.toString(); | |
96 | + this.tenantId =tenantId.getId().toString(); | |
97 | + this.tbProfileId = tbProfileId.getId().toString(); | |
98 | + this.deviceType = deviceType; | |
91 | 99 | } |
100 | + | |
92 | 101 | } | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/dto/YtDeviceScriptDTO.java
0 → 100644
1 | +package org.thingsboard.server.common.data.yunteng.dto; | |
2 | + | |
3 | +import io.swagger.annotations.ApiModelProperty; | |
4 | +import lombok.Data; | |
5 | +import lombok.EqualsAndHashCode; | |
6 | +import org.thingsboard.server.common.data.yunteng.common.AddGroup; | |
7 | +import org.thingsboard.server.common.data.yunteng.common.UpdateGroup; | |
8 | + | |
9 | +import javax.validation.constraints.NotEmpty; | |
10 | +import javax.validation.constraints.NotNull; | |
11 | + | |
12 | +@EqualsAndHashCode(callSuper = true) | |
13 | +@Data | |
14 | +public class YtDeviceScriptDTO extends BaseDTO { | |
15 | + @NotEmpty(message = "解析脚本名称不能为空或者空字符串", groups = {AddGroup.class,UpdateGroup.class}) | |
16 | + @ApiModelProperty(value = "解析脚本名称") | |
17 | + private String name; | |
18 | + | |
19 | + /** TB的租户ID */ | |
20 | + @ApiModelProperty(value = "租户ID") | |
21 | + private String tenantId; | |
22 | + | |
23 | + @ApiModelProperty(value = "脚本描述") | |
24 | + private String description; | |
25 | + @NotEmpty(message = "解析脚本内容不能为空或者空字符串", groups = {AddGroup.class,UpdateGroup.class}) | |
26 | + @ApiModelProperty(value = "解析脚本方法体内容") | |
27 | + private String convertJs; | |
28 | + | |
29 | + @ApiModelProperty(value = "状态:0禁用 1启用") | |
30 | + @NotNull(message = "状态不能为空",groups = UpdateGroup.class) | |
31 | + private Integer status; | |
32 | + | |
33 | + public YtDeviceScriptDTO() { | |
34 | + } | |
35 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/TcpDataTypeEnum.java
renamed from
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/DeviceCredentialsEnum.java
... | ... | @@ -3,9 +3,8 @@ package org.thingsboard.server.common.data.yunteng.enums; |
3 | 3 | /** |
4 | 4 | * 设备凭证 |
5 | 5 | */ |
6 | -public enum DeviceCredentialsEnum { | |
7 | - ACCESS_TOKEN, | |
8 | - X509_CERTIFICATE, | |
9 | - MQTT_BASIC, | |
10 | - LWM2M_CREDENTIALS | |
6 | +public enum TcpDataTypeEnum { | |
7 | + HEX, | |
8 | + JSON, | |
9 | + ASCII | |
11 | 10 | } | ... | ... |
common/transport/tcp/pom.xml
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2022 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
20 | + <modelVersion>4.0.0</modelVersion> | |
21 | + <parent> | |
22 | + <groupId>org.thingsboard.common</groupId> | |
23 | + <version>3.3.4-SNAPSHOT</version> | |
24 | + <artifactId>transport</artifactId> | |
25 | + </parent> | |
26 | + <groupId>org.thingsboard.common.transport</groupId> | |
27 | + <artifactId>tcp</artifactId> | |
28 | + <packaging>jar</packaging> | |
29 | + | |
30 | + <name>Thingsboard TCP Transport Common</name> | |
31 | + <url>https://thingsboard.io</url> | |
32 | + | |
33 | + <properties> | |
34 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
35 | + <main.dir>${basedir}/../../..</main.dir> | |
36 | + </properties> | |
37 | + | |
38 | + <dependencies> | |
39 | + <dependency> | |
40 | + <groupId>org.thingsboard.common.transport</groupId> | |
41 | + <artifactId>transport-api</artifactId> | |
42 | + </dependency> | |
43 | + <dependency> | |
44 | + <groupId>io.netty</groupId> | |
45 | + <artifactId>netty-all</artifactId> | |
46 | + </dependency> | |
47 | + <dependency> | |
48 | + <groupId>io.netty</groupId> | |
49 | + <artifactId>netty-tcnative-boringssl-static</artifactId> | |
50 | + </dependency> | |
51 | + <dependency> | |
52 | + <groupId>org.springframework</groupId> | |
53 | + <artifactId>spring-context-support</artifactId> | |
54 | + </dependency> | |
55 | + <dependency> | |
56 | + <groupId>org.springframework</groupId> | |
57 | + <artifactId>spring-context</artifactId> | |
58 | + </dependency> | |
59 | + <dependency> | |
60 | + <groupId>org.slf4j</groupId> | |
61 | + <artifactId>slf4j-api</artifactId> | |
62 | + </dependency> | |
63 | + <dependency> | |
64 | + <groupId>org.slf4j</groupId> | |
65 | + <artifactId>log4j-over-slf4j</artifactId> | |
66 | + </dependency> | |
67 | + <dependency> | |
68 | + <groupId>ch.qos.logback</groupId> | |
69 | + <artifactId>logback-core</artifactId> | |
70 | + </dependency> | |
71 | + <dependency> | |
72 | + <groupId>ch.qos.logback</groupId> | |
73 | + <artifactId>logback-classic</artifactId> | |
74 | + </dependency> | |
75 | + <dependency> | |
76 | + <groupId>com.google.guava</groupId> | |
77 | + <artifactId>guava</artifactId> | |
78 | + </dependency> | |
79 | + <dependency> | |
80 | + <groupId>com.google.code.findbugs</groupId> | |
81 | + <artifactId>jsr305</artifactId> | |
82 | + <version>3.0.1</version> | |
83 | + <optional>true</optional> | |
84 | + </dependency> | |
85 | + <dependency> | |
86 | + <groupId>org.springframework.boot</groupId> | |
87 | + <artifactId>spring-boot-starter-test</artifactId> | |
88 | + <scope>test</scope> | |
89 | + </dependency> | |
90 | + <dependency> | |
91 | + <groupId>org.junit.vintage</groupId> | |
92 | + <artifactId>junit-vintage-engine</artifactId> | |
93 | + <scope>test</scope> | |
94 | + </dependency> | |
95 | + <dependency> | |
96 | + <groupId>org.awaitility</groupId> | |
97 | + <artifactId>awaitility</artifactId> | |
98 | + <scope>test</scope> | |
99 | + </dependency> | |
100 | + | |
101 | + </dependencies> | |
102 | + | |
103 | +</project> | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpSslHandlerProvider.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp; | |
17 | + | |
18 | +import io.netty.handler.ssl.SslHandler; | |
19 | +import lombok.extern.slf4j.Slf4j; | |
20 | +import org.springframework.beans.factory.annotation.Autowired; | |
21 | +import org.springframework.beans.factory.annotation.Qualifier; | |
22 | +import org.springframework.beans.factory.annotation.Value; | |
23 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
24 | +import org.springframework.boot.context.properties.ConfigurationProperties; | |
25 | +import org.springframework.context.annotation.Bean; | |
26 | +import org.springframework.stereotype.Component; | |
27 | +import org.springframework.util.StringUtils; | |
28 | +import org.thingsboard.server.common.data.DeviceTransportType; | |
29 | +import org.thingsboard.server.common.msg.EncryptionUtil; | |
30 | +import org.thingsboard.server.common.transport.TransportService; | |
31 | +import org.thingsboard.server.common.transport.TransportServiceCallback; | |
32 | +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; | |
33 | +import org.thingsboard.server.common.transport.config.ssl.SslCredentials; | |
34 | +import org.thingsboard.server.common.transport.config.ssl.SslCredentialsConfig; | |
35 | +import org.thingsboard.server.common.transport.util.SslUtil; | |
36 | +import org.thingsboard.server.gen.transport.TransportProtos; | |
37 | + | |
38 | +import javax.net.ssl.KeyManager; | |
39 | +import javax.net.ssl.KeyManagerFactory; | |
40 | +import javax.net.ssl.SSLContext; | |
41 | +import javax.net.ssl.SSLEngine; | |
42 | +import javax.net.ssl.TrustManager; | |
43 | +import javax.net.ssl.TrustManagerFactory; | |
44 | +import javax.net.ssl.X509TrustManager; | |
45 | +import java.security.cert.CertificateEncodingException; | |
46 | +import java.security.cert.CertificateException; | |
47 | +import java.security.cert.X509Certificate; | |
48 | +import java.util.concurrent.CountDownLatch; | |
49 | +import java.util.concurrent.TimeUnit; | |
50 | + | |
51 | +/** | |
52 | + * Created by valerii.sosliuk on 11/6/16. | |
53 | + */ | |
54 | +@Slf4j | |
55 | +@Component("TcpSslHandlerProvider") | |
56 | +@ConditionalOnProperty(prefix = "transport.tcp.ssl", value = "enabled", havingValue = "true", matchIfMissing = false) | |
57 | +public class TcpSslHandlerProvider { | |
58 | + | |
59 | + @Value("${transport.tcp.ssl.protocol}") | |
60 | + private String sslProtocol; | |
61 | + | |
62 | + @Autowired | |
63 | + private TransportService transportService; | |
64 | + | |
65 | + @Bean | |
66 | + @ConfigurationProperties(prefix = "transport.tcp.ssl.credentials") | |
67 | + public SslCredentialsConfig tcpSslCredentials() { | |
68 | + return new SslCredentialsConfig("tcp SSL Credentials", false); | |
69 | + } | |
70 | + | |
71 | + @Autowired | |
72 | + @Qualifier("tcpSslCredentials") | |
73 | + private SslCredentialsConfig tcpSslCredentialsConfig; | |
74 | + | |
75 | + private SSLContext sslContext; | |
76 | + | |
77 | + public SslHandler getSslHandler() { | |
78 | + if (sslContext == null) { | |
79 | + sslContext = createSslContext(); | |
80 | + } | |
81 | + SSLEngine sslEngine = sslContext.createSSLEngine(); | |
82 | + sslEngine.setUseClientMode(false); | |
83 | + sslEngine.setNeedClientAuth(false); | |
84 | + sslEngine.setWantClientAuth(true); | |
85 | + sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols()); | |
86 | + sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites()); | |
87 | + sslEngine.setEnableSessionCreation(true); | |
88 | + return new SslHandler(sslEngine); | |
89 | + } | |
90 | + | |
91 | + private SSLContext createSslContext() { | |
92 | + try { | |
93 | + SslCredentials sslCredentials = this.tcpSslCredentialsConfig.getCredentials(); | |
94 | + TrustManagerFactory tmFactory = sslCredentials.createTrustManagerFactory(); | |
95 | + KeyManagerFactory kmf = sslCredentials.createKeyManagerFactory(); | |
96 | + | |
97 | + KeyManager[] km = kmf.getKeyManagers(); | |
98 | + TrustManager x509wrapped = getX509TrustManager(tmFactory); | |
99 | + TrustManager[] tm = {x509wrapped}; | |
100 | + if (StringUtils.isEmpty(sslProtocol)) { | |
101 | + sslProtocol = "TLS"; | |
102 | + } | |
103 | + SSLContext sslContext = SSLContext.getInstance(sslProtocol); | |
104 | + sslContext.init(km, tm, null); | |
105 | + return sslContext; | |
106 | + } catch (Exception e) { | |
107 | + log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e); | |
108 | + throw new RuntimeException("Failed to get SSL context", e); | |
109 | + } | |
110 | + } | |
111 | + | |
112 | + private TrustManager getX509TrustManager(TrustManagerFactory tmf) throws Exception { | |
113 | + X509TrustManager x509Tm = null; | |
114 | + for (TrustManager tm : tmf.getTrustManagers()) { | |
115 | + if (tm instanceof X509TrustManager) { | |
116 | + x509Tm = (X509TrustManager) tm; | |
117 | + break; | |
118 | + } | |
119 | + } | |
120 | + return new ThingsboardTcpX509TrustManager(x509Tm, transportService); | |
121 | + } | |
122 | + | |
123 | + static class ThingsboardTcpX509TrustManager implements X509TrustManager { | |
124 | + | |
125 | + private final X509TrustManager trustManager; | |
126 | + private TransportService transportService; | |
127 | + | |
128 | + ThingsboardTcpX509TrustManager(X509TrustManager trustManager, TransportService transportService) { | |
129 | + this.trustManager = trustManager; | |
130 | + this.transportService = transportService; | |
131 | + } | |
132 | + | |
133 | + @Override | |
134 | + public X509Certificate[] getAcceptedIssuers() { | |
135 | + return trustManager.getAcceptedIssuers(); | |
136 | + } | |
137 | + | |
138 | + @Override | |
139 | + public void checkServerTrusted(X509Certificate[] chain, | |
140 | + String authType) throws CertificateException { | |
141 | + trustManager.checkServerTrusted(chain, authType); | |
142 | + } | |
143 | + | |
144 | + @Override | |
145 | + public void checkClientTrusted(X509Certificate[] chain, | |
146 | + String authType) throws CertificateException { | |
147 | + String credentialsBody = null; | |
148 | + for (X509Certificate cert : chain) { | |
149 | + try { | |
150 | + String strCert = SslUtil.getCertificateString(cert); | |
151 | + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); | |
152 | + final String[] credentialsBodyHolder = new String[1]; | |
153 | + CountDownLatch latch = new CountDownLatch(1); | |
154 | + transportService.process(DeviceTransportType.TCP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), | |
155 | + new TransportServiceCallback<ValidateDeviceCredentialsResponse>() { | |
156 | + @Override | |
157 | + public void onSuccess(ValidateDeviceCredentialsResponse msg) { | |
158 | + if (!StringUtils.isEmpty(msg.getCredentials())) { | |
159 | + credentialsBodyHolder[0] = msg.getCredentials(); | |
160 | + } | |
161 | + latch.countDown(); | |
162 | + } | |
163 | + | |
164 | + @Override | |
165 | + public void onError(Throwable e) { | |
166 | + log.error(e.getMessage(), e); | |
167 | + latch.countDown(); | |
168 | + } | |
169 | + }); | |
170 | + latch.await(10, TimeUnit.SECONDS); | |
171 | + if (strCert.equals(credentialsBodyHolder[0])) { | |
172 | + credentialsBody = credentialsBodyHolder[0]; | |
173 | + break; | |
174 | + } | |
175 | + } catch (InterruptedException | CertificateEncodingException e) { | |
176 | + log.error(e.getMessage(), e); | |
177 | + } | |
178 | + } | |
179 | + if (credentialsBody == null) { | |
180 | + throw new CertificateException("Invalid Device Certificate"); | |
181 | + } | |
182 | + } | |
183 | + } | |
184 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportContext.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp; | |
17 | + | |
18 | +import io.netty.handler.ssl.SslHandler; | |
19 | +import lombok.Getter; | |
20 | +import lombok.Setter; | |
21 | +import lombok.extern.slf4j.Slf4j; | |
22 | +import org.springframework.beans.factory.annotation.Autowired; | |
23 | +import org.springframework.beans.factory.annotation.Value; | |
24 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | |
25 | +import org.springframework.stereotype.Component; | |
26 | +import org.thingsboard.server.common.transport.TransportContext; | |
27 | +import org.thingsboard.server.transport.tcp.adaptors.JsonTcpAdaptor; | |
28 | + | |
29 | +import javax.annotation.PostConstruct; | |
30 | +import java.net.InetSocketAddress; | |
31 | +import java.util.concurrent.atomic.AtomicInteger; | |
32 | + | |
33 | +/** | |
34 | + * Created by ashvayka on 04.10.18. | |
35 | + */ | |
36 | +@Slf4j | |
37 | +@Component | |
38 | +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.tcp.enabled}'=='true')") | |
39 | +public class TcpTransportContext extends TransportContext { | |
40 | + | |
41 | + @Getter | |
42 | + @Autowired(required = false) | |
43 | + private TcpSslHandlerProvider sslHandlerProvider; | |
44 | + | |
45 | + @Getter | |
46 | + @Autowired | |
47 | + private JsonTcpAdaptor jsonTcpAdaptor; | |
48 | + | |
49 | + | |
50 | + @Getter | |
51 | + @Value("${transport.tcp.netty.max_payload_size}") | |
52 | + private Integer maxPayloadSize; | |
53 | + | |
54 | + @Getter | |
55 | + @Value("${transport.tcp.ssl.skip_validity_check_for_client_cert:false}") | |
56 | + private boolean skipValidityCheckForClientCert; | |
57 | + | |
58 | + @Getter | |
59 | + @Setter | |
60 | + private SslHandler sslHandler; | |
61 | + | |
62 | + @Getter | |
63 | + @Value("${transport.tcp.msg_queue_size_per_device_limit:100}") | |
64 | + private int messageQueueSizePerDeviceLimit; | |
65 | + | |
66 | + @Getter | |
67 | + @Value("${transport.tcp.timeout:10000}") | |
68 | + private long timeout; | |
69 | + | |
70 | + @Getter | |
71 | + @Value("${transport.tcp.proxy_enabled:false}") | |
72 | + private boolean proxyEnabled; | |
73 | + | |
74 | + private final AtomicInteger connectionsCounter = new AtomicInteger(); | |
75 | + | |
76 | + @PostConstruct | |
77 | + public void init() { | |
78 | + super.init(); | |
79 | + transportService.createGaugeStats("openConnections", connectionsCounter); | |
80 | + } | |
81 | + | |
82 | + public void channelRegistered() { | |
83 | + connectionsCounter.incrementAndGet(); | |
84 | + } | |
85 | + | |
86 | + public void channelUnregistered() { | |
87 | + connectionsCounter.decrementAndGet(); | |
88 | + } | |
89 | + | |
90 | + public boolean checkAddress(InetSocketAddress address) { | |
91 | + return rateLimitService.checkAddress(address); | |
92 | + } | |
93 | + | |
94 | + public void onAuthSuccess(InetSocketAddress address) { | |
95 | + rateLimitService.onAuthSuccess(address); | |
96 | + } | |
97 | + | |
98 | + public void onAuthFailure(InetSocketAddress address) { | |
99 | + rateLimitService.onAuthFailure(address); | |
100 | + } | |
101 | + | |
102 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportHandler.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp; | |
17 | + | |
18 | +import com.google.gson.JsonParseException; | |
19 | +import io.netty.buffer.ByteBuf; | |
20 | +import io.netty.buffer.Unpooled; | |
21 | +import io.netty.channel.ChannelFuture; | |
22 | +import io.netty.channel.ChannelHandlerContext; | |
23 | +import io.netty.channel.ChannelInboundHandlerAdapter; | |
24 | +import io.netty.handler.codec.mqtt.*; | |
25 | +import io.netty.handler.ssl.SslHandler; | |
26 | +import io.netty.util.ReferenceCountUtil; | |
27 | +import io.netty.util.concurrent.Future; | |
28 | +import io.netty.util.concurrent.GenericFutureListener; | |
29 | +import lombok.extern.slf4j.Slf4j; | |
30 | +import org.apache.commons.lang3.StringUtils; | |
31 | +import org.thingsboard.server.common.data.*; | |
32 | +import org.thingsboard.server.common.data.device.profile.MqttTopics; | |
33 | +import org.thingsboard.server.common.data.id.DeviceId; | |
34 | +import org.thingsboard.server.common.data.id.OtaPackageId; | |
35 | +import org.thingsboard.server.common.data.ota.OtaPackageType; | |
36 | +import org.thingsboard.server.common.data.yunteng.enums.TcpDataTypeEnum; | |
37 | +import org.thingsboard.server.common.msg.EncryptionUtil; | |
38 | +import org.thingsboard.server.common.msg.tools.TbRateLimitsException; | |
39 | +import org.thingsboard.server.common.transport.SessionMsgListener; | |
40 | +import org.thingsboard.server.common.transport.TransportService; | |
41 | +import org.thingsboard.server.common.transport.TransportServiceCallback; | |
42 | +import org.thingsboard.server.common.transport.adaptor.AdaptorException; | |
43 | +import org.thingsboard.server.common.transport.auth.SessionInfoCreator; | |
44 | +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; | |
45 | +import org.thingsboard.server.common.transport.util.SslUtil; | |
46 | +import org.thingsboard.server.gen.transport.TransportProtos; | |
47 | +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; | |
48 | +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; | |
49 | +import org.thingsboard.server.queue.scheduler.SchedulerComponent; | |
50 | +import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor; | |
51 | +import org.thingsboard.server.transport.tcp.session.DeviceSessionCtx; | |
52 | +import org.thingsboard.server.transport.tcp.session.TCPMessage; | |
53 | +import org.thingsboard.server.transport.tcp.util.ByteUtils; | |
54 | + | |
55 | +import javax.net.ssl.SSLPeerUnverifiedException; | |
56 | +import java.net.InetSocketAddress; | |
57 | +import java.security.cert.Certificate; | |
58 | +import java.security.cert.X509Certificate; | |
59 | +import java.util.List; | |
60 | +import java.util.Optional; | |
61 | +import java.util.UUID; | |
62 | +import java.util.concurrent.ConcurrentHashMap; | |
63 | +import java.util.concurrent.ConcurrentMap; | |
64 | +import java.util.regex.Matcher; | |
65 | +import java.util.regex.Pattern; | |
66 | + | |
67 | +import static com.amazonaws.util.StringUtils.UTF8; | |
68 | +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; | |
69 | +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; | |
70 | +import static io.netty.handler.codec.mqtt.MqttMessageType.*; | |
71 | +import static io.netty.handler.codec.mqtt.MqttQoS.*; | |
72 | +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; | |
73 | +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; | |
74 | + | |
75 | +/** | |
76 | + * @author Andrew Shvayka | |
77 | + */ | |
78 | +@Slf4j | |
79 | +public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener<Future<? super Void>>, SessionMsgListener { | |
80 | + | |
81 | + private static final Pattern FW_REQUEST_PATTERN = Pattern.compile(MqttTopics.DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN); | |
82 | + private static final Pattern SW_REQUEST_PATTERN = Pattern.compile(MqttTopics.DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN); | |
83 | + | |
84 | + | |
85 | + private static final String PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE"; | |
86 | + | |
87 | + private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE; | |
88 | + | |
89 | + private final UUID sessionId; | |
90 | + private final TcpTransportContext context; | |
91 | + private final TransportService transportService; | |
92 | + private final SchedulerComponent scheduler; | |
93 | + private final SslHandler sslHandler; | |
94 | + | |
95 | + | |
96 | + /**需要处理的消息队列,例如:需要下发给设备的,设备上传的。*/ | |
97 | + final DeviceSessionCtx deviceSessionCtx; | |
98 | + volatile InetSocketAddress address; | |
99 | + | |
100 | + private final ConcurrentHashMap<String, String> otaPackSessions; | |
101 | + private final ConcurrentHashMap<String, Integer> chunkSizes; | |
102 | + private final ConcurrentMap<Integer, TransportProtos.ToDeviceRpcRequestMsg> rpcAwaitingAck; | |
103 | + | |
104 | + | |
105 | + | |
106 | + TcpTransportHandler(TcpTransportContext context, SslHandler sslHandler) { | |
107 | + this.sessionId = UUID.randomUUID(); | |
108 | + this.context = context; | |
109 | + this.transportService = context.getTransportService(); | |
110 | + this.scheduler = context.getScheduler(); | |
111 | + this.sslHandler = sslHandler; | |
112 | + this.deviceSessionCtx = new DeviceSessionCtx(sessionId, context); | |
113 | + this.otaPackSessions = new ConcurrentHashMap<>(); | |
114 | + this.chunkSizes = new ConcurrentHashMap<>(); | |
115 | + this.rpcAwaitingAck = new ConcurrentHashMap<>(); | |
116 | + } | |
117 | + | |
118 | + @Override | |
119 | + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { | |
120 | + super.channelRegistered(ctx); | |
121 | + context.channelRegistered(); | |
122 | + } | |
123 | + | |
124 | + @Override | |
125 | + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { | |
126 | + super.channelUnregistered(ctx); | |
127 | + context.channelUnregistered(); | |
128 | + } | |
129 | + | |
130 | + @Override | |
131 | + public void channelRead(ChannelHandlerContext ctx, Object msg) { | |
132 | + log.trace("【{}】 Processing msg: 【{}】", sessionId, msg); | |
133 | + if (address == null) { | |
134 | + address = getAddress(ctx); | |
135 | + } | |
136 | + try { | |
137 | + if (msg instanceof ByteBuf) { | |
138 | + ByteBuf message = (ByteBuf) msg; | |
139 | + | |
140 | + processTcpMsg(ctx, ByteUtils.buf2Bytes(message)); | |
141 | + | |
142 | + } else { | |
143 | + log.debug("【{}】 Received non tcp message: 【{}】", sessionId, msg.getClass().getSimpleName()); | |
144 | + ctx.close(); | |
145 | + } | |
146 | + } finally { | |
147 | + ReferenceCountUtil.safeRelease(msg); | |
148 | + } | |
149 | + } | |
150 | + | |
151 | + InetSocketAddress getAddress(ChannelHandlerContext ctx) { | |
152 | + var address = ctx.channel().attr(TcpTransportService.ADDRESS).get(); | |
153 | + if (address == null) { | |
154 | + log.trace("[{}] Received empty address.", ctx.channel().id()); | |
155 | + InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); | |
156 | + log.trace("[{}] Going to use address: {}", ctx.channel().id(), remoteAddress); | |
157 | + return remoteAddress; | |
158 | + } else { | |
159 | + log.trace("[{}] Received address: {}", ctx.channel().id(), address); | |
160 | + } | |
161 | + return address; | |
162 | + } | |
163 | + | |
164 | + void processTcpMsg(ChannelHandlerContext ctx, byte[] msg) { | |
165 | + deviceSessionCtx.setChannel(ctx); | |
166 | + if (deviceSessionCtx.getDeviceInfo() == null || deviceSessionCtx.getDeviceProfile() == null) { | |
167 | + processConnect(ctx, ByteUtils.getString(msg, ByteUtils.UTF_8)); | |
168 | + } else { | |
169 | + enqueueRegularSessionMsg(ctx,msg); | |
170 | + } | |
171 | + } | |
172 | + | |
173 | + | |
174 | + | |
175 | + void enqueueRegularSessionMsg(ChannelHandlerContext ctx, byte[] msg) { | |
176 | + final int queueSize = deviceSessionCtx.getMsgQueueSize(); | |
177 | + if (queueSize >= context.getMessageQueueSizePerDeviceLimit()) { | |
178 | + log.info("Closing current session because msq queue size for device {} exceed limit {} with msgQueueSize counter {} and actual queue size {}", | |
179 | + deviceSessionCtx.getDeviceId(), context.getMessageQueueSizePerDeviceLimit(), queueSize, deviceSessionCtx.getMsgQueueSize()); | |
180 | + ctx.close(); | |
181 | + return; | |
182 | + } | |
183 | + | |
184 | + deviceSessionCtx.addToQueue(new TCPMessage(MqttMessageType.PUBLISH,msg)); | |
185 | + processMsgQueue(ctx); //Under the normal conditions the msg queue will contain 0 messages. Many messages will be processed on device connect event in separate thread pool | |
186 | + } | |
187 | + | |
188 | + | |
189 | + | |
190 | + void processMsgQueue(ChannelHandlerContext ctx) { | |
191 | + if (!deviceSessionCtx.isConnected()) { | |
192 | + log.trace("[{}][{}] Postpone processing msg due to device is not connected. Msg queue size is {}", sessionId, deviceSessionCtx.getDeviceId(), deviceSessionCtx.getMsgQueueSize()); | |
193 | + return; | |
194 | + } | |
195 | + deviceSessionCtx.tryProcessQueuedMsgs(msg -> processRegularSessionMsg(ctx, msg)); | |
196 | + } | |
197 | + | |
198 | + void processRegularSessionMsg(ChannelHandlerContext ctx, TCPMessage msg) { | |
199 | + switch (msg.getMessageType()) { | |
200 | + case PUBLISH: | |
201 | + processPublish(ctx, msg); | |
202 | + break; | |
203 | + case SUBSCRIBE: | |
204 | +// processSubscribe(ctx, (MqttSubscribeMessage) msg); | |
205 | + break; | |
206 | + case UNSUBSCRIBE: | |
207 | +// processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg); | |
208 | + break; | |
209 | + | |
210 | + case DISCONNECT: | |
211 | + ctx.close(); | |
212 | + break; | |
213 | + case PUBACK: | |
214 | +// int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId(); | |
215 | +// TransportProtos.ToDeviceRpcRequestMsg rpcRequest = rpcAwaitingAck.remove(msgId); | |
216 | +// if (rpcRequest != null) { | |
217 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY); | |
218 | +// } | |
219 | + break; | |
220 | + default: | |
221 | + break; | |
222 | + } | |
223 | + } | |
224 | + | |
225 | + private ByteBuf toDeviceMsg(byte[] msgs){ | |
226 | + return Unpooled.copiedBuffer(msgs); | |
227 | + } | |
228 | + | |
229 | + private String textFromMessage(byte[] payload){ | |
230 | + if(deviceSessionCtx.getPayloadType().equals(TcpDataTypeEnum.ASCII)){ | |
231 | + return ByteUtils.getString(payload,ByteUtils.UTF_8); | |
232 | + }else{ | |
233 | + return ByteUtils.bytesToHex(payload); | |
234 | + } | |
235 | + } | |
236 | + private void processPublish(ChannelHandlerContext ctx, TCPMessage mqttMsg) { | |
237 | + if (!checkConnected(ctx, mqttMsg)) { | |
238 | + return; | |
239 | + } | |
240 | + if(deviceSessionCtx.getPingText().equals(mqttMsg.getMessage())){ | |
241 | + ctx.channel().writeAndFlush(toDeviceMsg(mqttMsg.getMessage())); | |
242 | + transportService.reportActivity(deviceSessionCtx.getSessionInfo()); | |
243 | + return; | |
244 | + } | |
245 | + String dataStr = textFromMessage(mqttMsg.getMessage()); | |
246 | + log.trace("[{}][{}] Processing publish msg [{}]!", sessionId, deviceSessionCtx.getDeviceId(), dataStr); | |
247 | + | |
248 | + processDevicePublish(ctx, dataStr); | |
249 | + } | |
250 | + | |
251 | + | |
252 | + private void processDevicePublish(ChannelHandlerContext ctx, String mqttMsg) { | |
253 | + try { | |
254 | +// Matcher fwMatcher; | |
255 | + TcpTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); | |
256 | + TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); | |
257 | + transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, null); | |
258 | +// if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { | |
259 | +// TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); | |
260 | +// transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); | |
261 | +// } else if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { | |
262 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { | |
263 | +// TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); | |
264 | +// transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); | |
265 | +// attrReqTopicType = TopicType.V1; | |
266 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) { | |
267 | +// TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC); | |
268 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); | |
269 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) { | |
270 | +// TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC); | |
271 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); | |
272 | +// toServerRpcSubTopicType = TopicType.V1; | |
273 | +// } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { | |
274 | +// TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); | |
275 | +// transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); | |
276 | +// } else if ((fwMatcher = FW_REQUEST_PATTERN.matcher(topicName)).find()) { | |
277 | +// getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.FIRMWARE); | |
278 | +// } else if ((fwMatcher = SW_REQUEST_PATTERN.matcher(topicName)).find()) { | |
279 | +// getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.SOFTWARE); | |
280 | +// } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC)) { | |
281 | +// TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); | |
282 | +// transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); | |
283 | +// } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC)) { | |
284 | +// TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getJsonMqttAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg); | |
285 | +// transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); | |
286 | +// } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC)) { | |
287 | +// TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getAscallAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg); | |
288 | +// transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); | |
289 | +// } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC)) { | |
290 | +// TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); | |
291 | +// transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); | |
292 | +// } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC)) { | |
293 | +// TransportProtos.PostAttributeMsg postAttributeMsg = context.getJsonMqttAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg); | |
294 | +// transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); | |
295 | +// } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC)) { | |
296 | +// TransportProtos.PostAttributeMsg postAttributeMsg = context.getAscallAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg); | |
297 | +// transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); | |
298 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC)) { | |
299 | +// TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getJsonMqttAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC); | |
300 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); | |
301 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_PROTO_TOPIC)) { | |
302 | +// TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getAscallAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_PROTO_TOPIC); | |
303 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); | |
304 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_TOPIC)) { | |
305 | +// TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = payloadAdaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_TOPIC); | |
306 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); | |
307 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_JSON_TOPIC)) { | |
308 | +// TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = context.getJsonMqttAdaptor().convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_JSON_TOPIC); | |
309 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); | |
310 | +// toServerRpcSubTopicType = TopicType.V2_JSON; | |
311 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_PROTO_TOPIC)) { | |
312 | +// TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = context.getAscallAdaptor().convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_PROTO_TOPIC); | |
313 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); | |
314 | +// toServerRpcSubTopicType = TopicType.V2_PROTO; | |
315 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_SHORT_TOPIC)) { | |
316 | +// TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = payloadAdaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_REQUESTS_SHORT_TOPIC); | |
317 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); | |
318 | +// toServerRpcSubTopicType = TopicType.V2; | |
319 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX)) { | |
320 | +// TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getJsonMqttAdaptor().convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX); | |
321 | +// transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); | |
322 | +// attrReqTopicType = TopicType.V2_JSON; | |
323 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX)) { | |
324 | +// TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getAscallAdaptor().convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX); | |
325 | +// transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); | |
326 | +// attrReqTopicType = TopicType.V2_PROTO; | |
327 | +// } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX)) { | |
328 | +// TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX); | |
329 | +// transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); | |
330 | +// attrReqTopicType = TopicType.V2; | |
331 | +// } else { | |
332 | +// transportService.reportActivity(deviceSessionCtx.getSessionInfo()); | |
333 | +// ack(ctx, msgId); | |
334 | +// } | |
335 | + } catch (AdaptorException e) { | |
336 | + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, mqttMsg, e); | |
337 | + ctx.close(); | |
338 | + } | |
339 | + } | |
340 | + | |
341 | + private void getOtaPackageCallback(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, int msgId, Matcher fwMatcher, OtaPackageType type) { | |
342 | + String payload = mqttMsg.content().toString(UTF8); | |
343 | + int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0; | |
344 | + String requestId = fwMatcher.group("requestId"); | |
345 | + int chunk = Integer.parseInt(fwMatcher.group("chunk")); | |
346 | + | |
347 | + if (chunkSize > 0) { | |
348 | + this.chunkSizes.put(requestId, chunkSize); | |
349 | + } else { | |
350 | + chunkSize = chunkSizes.getOrDefault(requestId, 0); | |
351 | + } | |
352 | + | |
353 | + if (chunkSize > context.getMaxPayloadSize()) { | |
354 | + sendOtaPackageError(ctx, PAYLOAD_TOO_LARGE); | |
355 | + return; | |
356 | + } | |
357 | + | |
358 | + String otaPackageId = otaPackSessions.get(requestId); | |
359 | + | |
360 | + if (otaPackageId != null) { | |
361 | + sendOtaPackage(ctx, mqttMsg.variableHeader().packetId(), otaPackageId, requestId, chunkSize, chunk, type); | |
362 | + } else { | |
363 | + TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo(); | |
364 | + TransportProtos.GetOtaPackageRequestMsg getOtaPackageRequestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() | |
365 | + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) | |
366 | + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) | |
367 | + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) | |
368 | + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) | |
369 | + .setType(type.name()) | |
370 | + .build(); | |
371 | + transportService.process(deviceSessionCtx.getSessionInfo(), getOtaPackageRequestMsg, | |
372 | + new OtaPackageCallback(ctx, msgId, getOtaPackageRequestMsg, requestId, chunkSize, chunk)); | |
373 | + } | |
374 | + } | |
375 | + | |
376 | + private void ack(ChannelHandlerContext ctx, int msgId) { | |
377 | + if (msgId > 0) { | |
378 | + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); | |
379 | + } | |
380 | + } | |
381 | + | |
382 | + private <T> TransportServiceCallback<Void> getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { | |
383 | + return new TransportServiceCallback<>() { | |
384 | + @Override | |
385 | + public void onSuccess(Void dummy) { | |
386 | + log.trace("[{}] Published msg: {}", sessionId, msg); | |
387 | + ack(ctx, msgId); | |
388 | + } | |
389 | + | |
390 | + @Override | |
391 | + public void onError(Throwable e) { | |
392 | + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); | |
393 | + ctx.close(); | |
394 | + } | |
395 | + }; | |
396 | + } | |
397 | + | |
398 | + private class DeviceProvisionCallback implements TransportServiceCallback<ProvisionDeviceResponseMsg> { | |
399 | + private final ChannelHandlerContext ctx; | |
400 | + private final int msgId; | |
401 | + private final TransportProtos.ProvisionDeviceRequestMsg msg; | |
402 | + | |
403 | + DeviceProvisionCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.ProvisionDeviceRequestMsg msg) { | |
404 | + this.ctx = ctx; | |
405 | + this.msgId = msgId; | |
406 | + this.msg = msg; | |
407 | + } | |
408 | + | |
409 | + @Override | |
410 | + public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg provisionResponseMsg) { | |
411 | +// log.trace("[{}] Published msg: {}", sessionId, msg); | |
412 | +// ack(ctx, msgId); | |
413 | +// try { | |
414 | +// if (deviceSessionCtx.getProvisionPayloadType().equals(TransportPayloadType.JSON)) { | |
415 | +// deviceSessionCtx.getContext().getJsonMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); | |
416 | +// } else { | |
417 | +// deviceSessionCtx.getContext().getAscallAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); | |
418 | +// } | |
419 | +// scheduler.schedule((Callable<ChannelFuture>) ctx::close, 60, TimeUnit.SECONDS); | |
420 | +// } catch (Exception e) { | |
421 | +// log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); | |
422 | +// } | |
423 | + } | |
424 | + | |
425 | + @Override | |
426 | + public void onError(Throwable e) { | |
427 | + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); | |
428 | + ctx.close(); | |
429 | + } | |
430 | + } | |
431 | + | |
432 | + private class OtaPackageCallback implements TransportServiceCallback<TransportProtos.GetOtaPackageResponseMsg> { | |
433 | + private final ChannelHandlerContext ctx; | |
434 | + private final int msgId; | |
435 | + private final TransportProtos.GetOtaPackageRequestMsg msg; | |
436 | + private final String requestId; | |
437 | + private final int chunkSize; | |
438 | + private final int chunk; | |
439 | + | |
440 | + OtaPackageCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetOtaPackageRequestMsg msg, String requestId, int chunkSize, int chunk) { | |
441 | + this.ctx = ctx; | |
442 | + this.msgId = msgId; | |
443 | + this.msg = msg; | |
444 | + this.requestId = requestId; | |
445 | + this.chunkSize = chunkSize; | |
446 | + this.chunk = chunk; | |
447 | + } | |
448 | + | |
449 | + @Override | |
450 | + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { | |
451 | + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { | |
452 | + OtaPackageId firmwareId = new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())); | |
453 | + otaPackSessions.put(requestId, firmwareId.toString()); | |
454 | + sendOtaPackage(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk, OtaPackageType.valueOf(response.getType())); | |
455 | + } else { | |
456 | + sendOtaPackageError(ctx, response.getResponseStatus().toString()); | |
457 | + } | |
458 | + } | |
459 | + | |
460 | + @Override | |
461 | + public void onError(Throwable e) { | |
462 | + log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e); | |
463 | + ctx.close(); | |
464 | + } | |
465 | + } | |
466 | + | |
467 | + private void sendOtaPackage(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, OtaPackageType type) { | |
468 | + log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId); | |
469 | + ack(ctx, msgId); | |
470 | + try { | |
471 | + byte[] firmwareChunk = context.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk); | |
472 | + deviceSessionCtx.getPayloadAdaptor() | |
473 | + .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk, type) | |
474 | + .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); | |
475 | + } catch (Exception e) { | |
476 | + log.trace("[{}] Failed to send firmware response!", sessionId, e); | |
477 | + } | |
478 | + } | |
479 | + | |
480 | + private void sendOtaPackageError(ChannelHandlerContext ctx, String error) { | |
481 | + log.warn("[{}] {}", sessionId, error); | |
482 | + deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx | |
483 | + .getPayloadAdaptor() | |
484 | + .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes())); | |
485 | + ctx.close(); | |
486 | + } | |
487 | + | |
488 | + private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { | |
489 | +// if (!checkConnected(ctx, mqttMsg)) { | |
490 | +// return; | |
491 | +// } | |
492 | +// log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); | |
493 | +// List<Integer> grantedQoSList = new ArrayList<>(); | |
494 | +// boolean activityReported = false; | |
495 | +// for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) { | |
496 | +// String topic = subscription.topicName(); | |
497 | +// MqttQoS reqQoS = subscription.qualityOfService(); | |
498 | +// try { | |
499 | +// switch (topic) { | |
500 | +// case MqttTopics.DEVICE_ATTRIBUTES_TOPIC: { | |
501 | +// processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1); | |
502 | +// activityReported = true; | |
503 | +// break; | |
504 | +// } | |
505 | +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC: { | |
506 | +// processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2); | |
507 | +// activityReported = true; | |
508 | +// break; | |
509 | +// } | |
510 | +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC: { | |
511 | +// processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_JSON); | |
512 | +// activityReported = true; | |
513 | +// break; | |
514 | +// } | |
515 | +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC: { | |
516 | +// processAttributesSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_PROTO); | |
517 | +// activityReported = true; | |
518 | +// break; | |
519 | +// } | |
520 | +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC: { | |
521 | +// processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V1); | |
522 | +// activityReported = true; | |
523 | +// break; | |
524 | +// } | |
525 | +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC: { | |
526 | +// processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2); | |
527 | +// activityReported = true; | |
528 | +// break; | |
529 | +// } | |
530 | +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC: { | |
531 | +// processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_JSON); | |
532 | +// activityReported = true; | |
533 | +// break; | |
534 | +// } | |
535 | +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC: { | |
536 | +// processRpcSubscribe(grantedQoSList, topic, reqQoS, TopicType.V2_PROTO); | |
537 | +// activityReported = true; | |
538 | +// break; | |
539 | +// } | |
540 | +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC: | |
541 | +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_TOPIC: | |
542 | +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_JSON_TOPIC: | |
543 | +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_PROTO_TOPIC: | |
544 | +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC: | |
545 | +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC: | |
546 | +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC: | |
547 | +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC: | |
548 | +// case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: | |
549 | +// case MqttTopics.GATEWAY_RPC_TOPIC: | |
550 | +// case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: | |
551 | +// case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: | |
552 | +// case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC: | |
553 | +// case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC: | |
554 | +// case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC: | |
555 | +// | |
556 | +// break; | |
557 | +// default: | |
558 | +// log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS); | |
559 | +// grantedQoSList.add(FAILURE.value()); | |
560 | +// break; | |
561 | +// } | |
562 | +// } catch (Exception e) { | |
563 | +// log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS, e); | |
564 | +// grantedQoSList.add(FAILURE.value()); | |
565 | +// } | |
566 | +// } | |
567 | +// if (!activityReported) { | |
568 | +// transportService.reportActivity(deviceSessionCtx.getSessionInfo()); | |
569 | +// } | |
570 | +// ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList)); | |
571 | + } | |
572 | + | |
573 | +// private void processRpcSubscribe(List<Integer> grantedQoSList, String topic, MqttQoS reqQoS, TopicType topicType) { | |
574 | +// transportService.process(deviceSessionCtx.getSessionInfo(), TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null); | |
575 | +// rpcSubTopicType = topicType; | |
576 | +// } | |
577 | +// | |
578 | +// private void processAttributesSubscribe(List<Integer> grantedQoSList, String topic, MqttQoS reqQoS, TopicType topicType) { | |
579 | +// transportService.process(deviceSessionCtx.getSessionInfo(), TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); | |
580 | +// attrSubTopicType = topicType; | |
581 | +// } | |
582 | + | |
583 | + | |
584 | + | |
585 | + private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { | |
586 | +// if (!checkConnected(ctx, mqttMsg)) { | |
587 | +// return; | |
588 | +// } | |
589 | +// boolean activityReported = false; | |
590 | +// log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); | |
591 | +// for (String topicName : mqttMsg.payload().topics()) { | |
592 | +// try { | |
593 | +// switch (topicName) { | |
594 | +// case MqttTopics.DEVICE_ATTRIBUTES_TOPIC: | |
595 | +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC: | |
596 | +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC: | |
597 | +// case MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC: { | |
598 | +// transportService.process(deviceSessionCtx.getSessionInfo(), | |
599 | +// TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), null); | |
600 | +// activityReported = true; | |
601 | +// break; | |
602 | +// } | |
603 | +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC: | |
604 | +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC: | |
605 | +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC: | |
606 | +// case MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC: { | |
607 | +// transportService.process(deviceSessionCtx.getSessionInfo(), | |
608 | +// TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), null); | |
609 | +// activityReported = true; | |
610 | +// break; | |
611 | +// } | |
612 | +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC: | |
613 | +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_TOPIC: | |
614 | +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_JSON_TOPIC: | |
615 | +// case MqttTopics.DEVICE_RPC_RESPONSE_SUB_SHORT_PROTO_TOPIC: | |
616 | +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC: | |
617 | +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC: | |
618 | +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC: | |
619 | +// case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC: | |
620 | +// case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: | |
621 | +// case MqttTopics.GATEWAY_RPC_TOPIC: | |
622 | +// case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: | |
623 | +// case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: | |
624 | +// case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC: | |
625 | +// case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC: | |
626 | +// case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC: | |
627 | +// case MqttTopics.DEVICE_SOFTWARE_ERROR_TOPIC: { | |
628 | +// activityReported = true; | |
629 | +// break; | |
630 | +// } | |
631 | +// } | |
632 | +// } catch (Exception e) { | |
633 | +// log.debug("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName); | |
634 | +// } | |
635 | +// } | |
636 | +// if (!activityReported) { | |
637 | +// transportService.reportActivity(deviceSessionCtx.getSessionInfo()); | |
638 | +// } | |
639 | +// ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId())); | |
640 | + } | |
641 | + | |
642 | + private MqttMessage createUnSubAckMessage(int msgId) { | |
643 | + MqttFixedHeader mqttFixedHeader = | |
644 | + new MqttFixedHeader(UNSUBACK, false, AT_MOST_ONCE, false, 0); | |
645 | + MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); | |
646 | + return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader); | |
647 | + } | |
648 | + | |
649 | + void processConnect(ChannelHandlerContext ctx, String accessToken) { | |
650 | + log.debug("[{}][{}] Processing connect msg for client: {}!", address, sessionId, accessToken); | |
651 | + | |
652 | + if (DataConstants.PROVISION.equals(accessToken) || DataConstants.PROVISION.equals(accessToken)) { | |
653 | + deviceSessionCtx.setProvisionOnly(true); | |
654 | + ctx.writeAndFlush(createTcpConnAckMsg(CONNECTION_ACCEPTED)); | |
655 | + } else { | |
656 | + X509Certificate cert; | |
657 | + if (sslHandler != null && (cert = getX509Certificate()) != null) { | |
658 | +// processX509CertConnect(ctx, cert, msg); | |
659 | + } else { | |
660 | + processAuthTokenConnect(ctx, accessToken); | |
661 | + } | |
662 | + } | |
663 | + } | |
664 | + | |
665 | + private void processAuthTokenConnect(ChannelHandlerContext ctx, String accessToken) { | |
666 | + | |
667 | + log.debug("[{}][{}] Processing connect msg for client with user name: {}!", address, sessionId, accessToken); | |
668 | + TransportProtos.ValidateDeviceTokenRequestMsg.Builder request = TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder() | |
669 | + .setToken(accessToken); | |
670 | + | |
671 | + transportService.process(DeviceTransportType.TCP, request.build(), | |
672 | + new TransportServiceCallback<>() { | |
673 | + @Override | |
674 | + public void onSuccess(ValidateDeviceCredentialsResponse msg) { | |
675 | + onValidateDeviceResponse(msg, ctx); | |
676 | + } | |
677 | + | |
678 | + @Override | |
679 | + public void onError(Throwable e) { | |
680 | + log.trace("[{}] Failed to process credentials: {}", address, accessToken, e); | |
681 | + ctx.writeAndFlush(createTcpConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); | |
682 | + ctx.close(); | |
683 | + } | |
684 | + }); | |
685 | + } | |
686 | + | |
687 | + private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert, MqttConnectMessage connectMessage) { | |
688 | + try { | |
689 | + if (!context.isSkipValidityCheckForClientCert()) { | |
690 | + cert.checkValidity(); | |
691 | + } | |
692 | + String strCert = SslUtil.getCertificateString(cert); | |
693 | + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); | |
694 | + transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), | |
695 | + new TransportServiceCallback<>() { | |
696 | + @Override | |
697 | + public void onSuccess(ValidateDeviceCredentialsResponse msg) { | |
698 | + onValidateDeviceResponse(msg, ctx); | |
699 | + } | |
700 | + | |
701 | + @Override | |
702 | + public void onError(Throwable e) { | |
703 | + log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e); | |
704 | + ctx.writeAndFlush(createTcpConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); | |
705 | + ctx.close(); | |
706 | + } | |
707 | + }); | |
708 | + } catch (Exception e) { | |
709 | + context.onAuthFailure(address); | |
710 | + ctx.writeAndFlush(createTcpConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); | |
711 | + log.trace("[{}] X509 auth failure: {}", sessionId, address, e); | |
712 | + ctx.close(); | |
713 | + } | |
714 | + } | |
715 | + | |
716 | + private X509Certificate getX509Certificate() { | |
717 | + try { | |
718 | + Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); | |
719 | + if (certChain.length > 0) { | |
720 | + return (X509Certificate) certChain[0]; | |
721 | + } | |
722 | + } catch (SSLPeerUnverifiedException e) { | |
723 | + log.warn(e.getMessage()); | |
724 | + return null; | |
725 | + } | |
726 | + return null; | |
727 | + } | |
728 | + | |
729 | + private ByteBuf createTcpConnAckMsg(MqttConnectReturnCode msg) { | |
730 | + return Unpooled.copiedBuffer(ByteUtils.hexStr2Bytes(msg.name())); | |
731 | + } | |
732 | + | |
733 | + @Override | |
734 | + public void channelReadComplete(ChannelHandlerContext ctx) { | |
735 | + ctx.flush(); | |
736 | + } | |
737 | + | |
738 | + @Override | |
739 | + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { | |
740 | + log.error("[{}] Unexpected Exception", sessionId, cause); | |
741 | + ctx.close(); | |
742 | + if (cause instanceof OutOfMemoryError) { | |
743 | + log.error("Received critical error. Going to shutdown the service."); | |
744 | + System.exit(1); | |
745 | + } | |
746 | + } | |
747 | + | |
748 | + private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) { | |
749 | + MqttFixedHeader mqttFixedHeader = | |
750 | + new MqttFixedHeader(SUBACK, false, AT_MOST_ONCE, false, 0); | |
751 | + MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); | |
752 | + MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(grantedQoSList); | |
753 | + return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload); | |
754 | + } | |
755 | + | |
756 | + | |
757 | + | |
758 | + public static MqttPubAckMessage createMqttPubAckMsg(int requestId) { | |
759 | + MqttFixedHeader mqttFixedHeader = | |
760 | + new MqttFixedHeader(PUBACK, false, AT_MOST_ONCE, false, 0); | |
761 | + MqttMessageIdVariableHeader mqttMsgIdVariableHeader = | |
762 | + MqttMessageIdVariableHeader.from(requestId); | |
763 | + return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader); | |
764 | + } | |
765 | + | |
766 | + private boolean checkConnected(ChannelHandlerContext ctx, TCPMessage msg) { | |
767 | + if (deviceSessionCtx.isConnected()) { | |
768 | + return true; | |
769 | + } else { | |
770 | + log.info("[{}] Closing current session due to invalid msg order: {}", sessionId, msg); | |
771 | + return false; | |
772 | + } | |
773 | + } | |
774 | + | |
775 | + | |
776 | + | |
777 | + @Override | |
778 | + public void operationComplete(Future<? super Void> future) throws Exception { | |
779 | + log.trace("[{}] Channel closed!", sessionId); | |
780 | + doDisconnect(); | |
781 | + } | |
782 | + | |
783 | + public void doDisconnect() { | |
784 | + if (deviceSessionCtx.isConnected()) { | |
785 | + log.debug("[{}] Client disconnected!", sessionId); | |
786 | + transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null); | |
787 | + transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); | |
788 | + deviceSessionCtx.setDisconnected(); | |
789 | + } | |
790 | + deviceSessionCtx.release(); | |
791 | + } | |
792 | + | |
793 | + | |
794 | + private void onValidateDeviceResponse(ValidateDeviceCredentialsResponse msg, ChannelHandlerContext ctx) { | |
795 | + if (!msg.hasDeviceInfo()) { | |
796 | + context.onAuthFailure(address); | |
797 | + ctx.writeAndFlush(createTcpConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); | |
798 | + ctx.close(); | |
799 | + } else { | |
800 | + context.onAuthSuccess(address); | |
801 | + deviceSessionCtx.setDeviceInfo(msg.getDeviceInfo()); | |
802 | + deviceSessionCtx.setDeviceProfile(msg.getDeviceProfile()); | |
803 | + deviceSessionCtx.setSessionInfo(SessionInfoCreator.create(msg, context, sessionId)); | |
804 | + transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_OPEN, new TransportServiceCallback<Void>() { | |
805 | + @Override | |
806 | + public void onSuccess(Void msg) { | |
807 | + transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), TcpTransportHandler.this); | |
808 | + ctx.writeAndFlush(createTcpConnAckMsg(CONNECTION_ACCEPTED)); | |
809 | + deviceSessionCtx.setConnected(true); | |
810 | + log.debug("[{}] Client connected!", sessionId); | |
811 | + transportService.getCallbackExecutor().execute(() -> processMsgQueue(ctx)); //this callback will execute in Producer worker thread and hard or blocking work have to be submitted to the separate thread. | |
812 | + } | |
813 | + | |
814 | + @Override | |
815 | + public void onError(Throwable e) { | |
816 | + if (e instanceof TbRateLimitsException) { | |
817 | + log.trace("[{}] Failed to submit session event: {}", sessionId, e.getMessage()); | |
818 | + } else { | |
819 | + log.warn("[{}] Failed to submit session event", sessionId, e); | |
820 | + } | |
821 | + ctx.writeAndFlush(createTcpConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); | |
822 | + ctx.close(); | |
823 | + } | |
824 | + }); | |
825 | + } | |
826 | + } | |
827 | + | |
828 | + @Override | |
829 | + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { | |
830 | +// log.trace("[{}] Received get attributes response", sessionId); | |
831 | +// String topicBase = attrReqTopicType.getAttributesResponseTopicBase(); | |
832 | +// TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(attrReqTopicType); | |
833 | +// try { | |
834 | +// adaptor.convertToPublish(deviceSessionCtx, response, topicBase).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); | |
835 | +// } catch (Exception e) { | |
836 | +// log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); | |
837 | +// } | |
838 | + } | |
839 | + | |
840 | + @Override | |
841 | + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg notification) { | |
842 | +// log.trace("[{}] Received attributes update notification to device", sessionId); | |
843 | +// String topic = attrSubTopicType.getAttributesSubTopic(); | |
844 | +// TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(attrSubTopicType); | |
845 | +// try { | |
846 | +// adaptor.convertToPublish(deviceSessionCtx, notification, topic).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); | |
847 | +// } catch (Exception e) { | |
848 | +// log.trace("[{}] Failed to convert device attributes update to MQTT msg", sessionId, e); | |
849 | +// } | |
850 | + } | |
851 | + | |
852 | + @Override | |
853 | + public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { | |
854 | + log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); | |
855 | + deviceSessionCtx.getChannel().close(); | |
856 | + } | |
857 | + | |
858 | + @Override | |
859 | + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { | |
860 | +// log.trace("[{}] Received RPC command to device", sessionId); | |
861 | +// String baseTopic = rpcSubTopicType.getRpcRequestTopicBase(); | |
862 | +// TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(rpcSubTopicType); | |
863 | +// try { | |
864 | +// adaptor.convertToPublish(deviceSessionCtx, rpcRequest, baseTopic).ifPresent(payload -> { | |
865 | +// int msgId = ((MqttPublishMessage) payload).variableHeader().packetId(); | |
866 | +// if (isAckExpected(payload)) { | |
867 | +// rpcAwaitingAck.put(msgId, rpcRequest); | |
868 | +// context.getScheduler().schedule(() -> { | |
869 | +// TransportProtos.ToDeviceRpcRequestMsg msg = rpcAwaitingAck.remove(msgId); | |
870 | +// if (msg != null) { | |
871 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.TIMEOUT, TransportServiceCallback.EMPTY); | |
872 | +// } | |
873 | +// }, Math.max(0, Math.min(deviceSessionCtx.getContext().getTimeout(), rpcRequest.getExpirationTime() - System.currentTimeMillis())), TimeUnit.MILLISECONDS); | |
874 | +// } | |
875 | +// var cf = publish(payload, deviceSessionCtx); | |
876 | +// cf.addListener(result -> { | |
877 | +// if (result.cause() == null) { | |
878 | +// if (!isAckExpected(payload)) { | |
879 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.DELIVERED, TransportServiceCallback.EMPTY); | |
880 | +// } else if (rpcRequest.getPersisted()) { | |
881 | +// transportService.process(deviceSessionCtx.getSessionInfo(), rpcRequest, RpcStatus.SENT, TransportServiceCallback.EMPTY); | |
882 | +// } | |
883 | +// } else { | |
884 | +// // TODO: send error | |
885 | +// } | |
886 | +// }); | |
887 | +// }); | |
888 | +// } catch (Exception e) { | |
889 | +// transportService.process(deviceSessionCtx.getSessionInfo(), | |
890 | +// TransportProtos.ToDeviceRpcResponseMsg.newBuilder() | |
891 | +// .setRequestId(rpcRequest.getRequestId()).setError("Failed to convert device RPC command to MQTT msg").build(), TransportServiceCallback.EMPTY); | |
892 | +// log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e); | |
893 | +// } | |
894 | + } | |
895 | + | |
896 | + @Override | |
897 | + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg rpcResponse) { | |
898 | +// log.trace("[{}] Received RPC response from server", sessionId); | |
899 | +// String baseTopic = toServerRpcSubTopicType.getRpcResponseTopicBase(); | |
900 | +// TcpTransportAdaptor adaptor = deviceSessionCtx.getAdaptor(toServerRpcSubTopicType); | |
901 | +// try { | |
902 | +// adaptor.convertToPublish(deviceSessionCtx, rpcResponse, baseTopic).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); | |
903 | +// } catch (Exception e) { | |
904 | +// log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e); | |
905 | +// } | |
906 | + } | |
907 | + | |
908 | + private ChannelFuture publish(MqttMessage message, DeviceSessionCtx deviceSessionCtx) { | |
909 | + return deviceSessionCtx.getChannel().writeAndFlush(message); | |
910 | + } | |
911 | + | |
912 | + private boolean isAckExpected(MqttMessage message) { | |
913 | + return message.fixedHeader().qosLevel().value() > 0; | |
914 | + } | |
915 | + | |
916 | + @Override | |
917 | + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { | |
918 | + deviceSessionCtx.onDeviceProfileUpdate(sessionInfo, deviceProfile); | |
919 | + } | |
920 | + | |
921 | + @Override | |
922 | + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) { | |
923 | + deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); | |
924 | + } | |
925 | + | |
926 | + @Override | |
927 | + public void onDeviceDeleted(DeviceId deviceId) { | |
928 | + context.onAuthFailure(address); | |
929 | + ChannelHandlerContext ctx = deviceSessionCtx.getChannel(); | |
930 | + ctx.close(); | |
931 | + } | |
932 | + | |
933 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp; | |
17 | + | |
18 | +import io.netty.channel.ChannelInitializer; | |
19 | +import io.netty.channel.ChannelPipeline; | |
20 | +import io.netty.channel.socket.SocketChannel; | |
21 | +import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; | |
22 | +import io.netty.handler.codec.mqtt.MqttDecoder; | |
23 | +import io.netty.handler.codec.mqtt.MqttEncoder; | |
24 | +import io.netty.handler.codec.string.StringDecoder; | |
25 | +import io.netty.handler.codec.string.StringEncoder; | |
26 | +import io.netty.handler.ssl.SslHandler; | |
27 | +import org.thingsboard.server.transport.tcp.limits.IpFilter; | |
28 | +import org.thingsboard.server.transport.tcp.limits.ProxyIpFilter; | |
29 | + | |
30 | +/** | |
31 | + * @author Andrew Shvayka | |
32 | + */ | |
33 | +public class TcpTransportServerInitializer extends ChannelInitializer<SocketChannel> { | |
34 | + | |
35 | + private final TcpTransportContext context; | |
36 | + private final boolean sslEnabled; | |
37 | + | |
38 | + public TcpTransportServerInitializer(TcpTransportContext context, boolean sslEnabled) { | |
39 | + this.context = context; | |
40 | + this.sslEnabled = sslEnabled; | |
41 | + } | |
42 | + | |
43 | + @Override | |
44 | + public void initChannel(SocketChannel ch) { | |
45 | + ChannelPipeline pipeline = ch.pipeline(); | |
46 | + SslHandler sslHandler = null; | |
47 | + if (context.isProxyEnabled()) { | |
48 | + pipeline.addLast("proxy", new HAProxyMessageDecoder()); | |
49 | + pipeline.addLast("ipFilter", new ProxyIpFilter(context)); | |
50 | + } else { | |
51 | + pipeline.addLast("ipFilter", new IpFilter(context)); | |
52 | + } | |
53 | + if (sslEnabled && context.getSslHandlerProvider() != null) { | |
54 | + sslHandler = context.getSslHandlerProvider().getSslHandler(); | |
55 | + pipeline.addLast(sslHandler); | |
56 | + } | |
57 | +// pipeline.addLast("decoder", new StringDecoder()); | |
58 | +// pipeline.addLast("encoder", new StringEncoder()); | |
59 | + | |
60 | + TcpTransportHandler handler = new TcpTransportHandler(context, sslHandler); | |
61 | + | |
62 | + pipeline.addLast(handler); | |
63 | + ch.closeFuture().addListener(handler); | |
64 | + } | |
65 | + | |
66 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/TcpTransportService.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp; | |
17 | + | |
18 | +import io.netty.bootstrap.ServerBootstrap; | |
19 | +import io.netty.channel.Channel; | |
20 | +import io.netty.channel.ChannelOption; | |
21 | +import io.netty.channel.EventLoopGroup; | |
22 | +import io.netty.channel.nio.NioEventLoopGroup; | |
23 | +import io.netty.channel.socket.nio.NioServerSocketChannel; | |
24 | +import io.netty.util.AttributeKey; | |
25 | +import io.netty.util.ResourceLeakDetector; | |
26 | +import lombok.extern.slf4j.Slf4j; | |
27 | +import org.springframework.beans.factory.annotation.Autowired; | |
28 | +import org.springframework.beans.factory.annotation.Value; | |
29 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | |
30 | +import org.springframework.stereotype.Service; | |
31 | +import org.thingsboard.server.common.data.DataConstants; | |
32 | +import org.thingsboard.server.common.data.TbTransportService; | |
33 | + | |
34 | +import javax.annotation.PostConstruct; | |
35 | +import javax.annotation.PreDestroy; | |
36 | +import java.net.InetSocketAddress; | |
37 | + | |
38 | +/** | |
39 | + * @author Andrew Shvayka | |
40 | + */ | |
41 | +@Service("TcpTransportService") | |
42 | +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.tcp.enabled}'=='true')") | |
43 | +@Slf4j | |
44 | +public class TcpTransportService implements TbTransportService { | |
45 | + | |
46 | + public static AttributeKey<InetSocketAddress> ADDRESS = AttributeKey.newInstance("TCP_SRC_ADDRESS"); | |
47 | + | |
48 | + @Value("${transport.tcp.bind_address}") | |
49 | + private String host; | |
50 | + @Value("${transport.tcp.bind_port}") | |
51 | + private Integer port; | |
52 | + | |
53 | + @Value("${transport.tcp.ssl.enabled}") | |
54 | + private boolean sslEnabled; | |
55 | + | |
56 | + @Value("${transport.tcp.ssl.bind_address}") | |
57 | + private String sslHost; | |
58 | + @Value("${transport.tcp.ssl.bind_port}") | |
59 | + private Integer sslPort; | |
60 | + | |
61 | + @Value("${transport.tcp.netty.leak_detector_level}") | |
62 | + private String leakDetectorLevel; | |
63 | + @Value("${transport.tcp.netty.boss_group_thread_count}") | |
64 | + private Integer bossGroupThreadCount; | |
65 | + @Value("${transport.tcp.netty.worker_group_thread_count}") | |
66 | + private Integer workerGroupThreadCount; | |
67 | + @Value("${transport.tcp.netty.so_keep_alive}") | |
68 | + private boolean keepAlive; | |
69 | + | |
70 | + @Autowired | |
71 | + private TcpTransportContext context; | |
72 | + | |
73 | + private Channel serverChannel; | |
74 | + private Channel sslServerChannel; | |
75 | + private EventLoopGroup bossGroup; | |
76 | + private EventLoopGroup workerGroup; | |
77 | + | |
78 | + @PostConstruct | |
79 | + public void init() throws Exception { | |
80 | + log.info("Setting resource leak detector level to {}", leakDetectorLevel); | |
81 | + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.valueOf(leakDetectorLevel.toUpperCase())); | |
82 | + | |
83 | + log.info("Starting TCP transport..."); | |
84 | + bossGroup = new NioEventLoopGroup(bossGroupThreadCount); | |
85 | + workerGroup = new NioEventLoopGroup(workerGroupThreadCount); | |
86 | + ServerBootstrap b = new ServerBootstrap(); | |
87 | + b.group(bossGroup, workerGroup) | |
88 | + .channel(NioServerSocketChannel.class) | |
89 | + .childHandler(new TcpTransportServerInitializer(context, false)) | |
90 | + .childOption(ChannelOption.SO_KEEPALIVE, keepAlive); | |
91 | + | |
92 | + serverChannel = b.bind(host, port).sync().channel(); | |
93 | + if (sslEnabled) { | |
94 | + b = new ServerBootstrap(); | |
95 | + b.group(bossGroup, workerGroup) | |
96 | + .channel(NioServerSocketChannel.class) | |
97 | + .childHandler(new TcpTransportServerInitializer(context, true)) | |
98 | + .childOption(ChannelOption.SO_KEEPALIVE, keepAlive); | |
99 | + sslServerChannel = b.bind(sslHost, sslPort).sync().channel(); | |
100 | + } | |
101 | + log.info("TCP transport started!"); | |
102 | + } | |
103 | + | |
104 | + @PreDestroy | |
105 | + public void shutdown() throws InterruptedException { | |
106 | + log.info("Stopping TCP transport!"); | |
107 | + try { | |
108 | + serverChannel.close().sync(); | |
109 | + if (sslEnabled) { | |
110 | + sslServerChannel.close().sync(); | |
111 | + } | |
112 | + } finally { | |
113 | + workerGroup.shutdownGracefully(); | |
114 | + bossGroup.shutdownGracefully(); | |
115 | + } | |
116 | + log.info("TCP transport stopped!"); | |
117 | + } | |
118 | + | |
119 | + @Override | |
120 | + public String getName() { | |
121 | + return DataConstants.TCP_TRANSPORT_NAME; | |
122 | + } | |
123 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/adaptors/JsonTcpAdaptor.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.adaptors; | |
17 | + | |
18 | +import com.google.common.util.concurrent.Futures; | |
19 | +import com.google.common.util.concurrent.ListenableFuture; | |
20 | +import com.google.common.util.concurrent.MoreExecutors; | |
21 | +import com.google.gson.JsonElement; | |
22 | +import com.google.gson.JsonObject; | |
23 | +import com.google.gson.JsonParser; | |
24 | +import com.google.gson.JsonSyntaxException; | |
25 | +import io.netty.buffer.ByteBuf; | |
26 | +import io.netty.handler.codec.mqtt.MqttFixedHeader; | |
27 | +import io.netty.handler.codec.mqtt.MqttMessage; | |
28 | +import io.netty.handler.codec.mqtt.MqttPublishMessage; | |
29 | +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; | |
30 | +import lombok.extern.slf4j.Slf4j; | |
31 | +import org.springframework.beans.factory.annotation.Autowired; | |
32 | +import org.springframework.stereotype.Component; | |
33 | +import org.springframework.util.StringUtils; | |
34 | +import org.thingsboard.server.common.data.device.profile.MqttTopics; | |
35 | +import org.thingsboard.server.common.data.ota.OtaPackageType; | |
36 | +import org.thingsboard.server.common.transport.adaptor.AdaptorException; | |
37 | +import org.thingsboard.server.common.transport.adaptor.JsonConverter; | |
38 | +import org.thingsboard.server.gen.transport.TransportProtos; | |
39 | +import org.thingsboard.server.common.yunteng.script.YtScriptInvokeService; | |
40 | +import org.thingsboard.server.common.yunteng.script.YtScriptType; | |
41 | +import org.thingsboard.server.transport.tcp.session.DeviceSessionCtx; | |
42 | + | |
43 | +import java.nio.charset.Charset; | |
44 | +import java.nio.charset.StandardCharsets; | |
45 | +import java.util.*; | |
46 | +import java.util.concurrent.ExecutionException; | |
47 | + | |
48 | +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT; | |
49 | + | |
50 | + | |
51 | +/** | |
52 | + * @author Andrew Shvayka | |
53 | + */ | |
54 | +@Component | |
55 | +@Slf4j | |
56 | +public class JsonTcpAdaptor implements TcpTransportAdaptor { | |
57 | + | |
58 | + protected static final Charset UTF8 = StandardCharsets.UTF_8; | |
59 | + @Autowired | |
60 | + private YtScriptInvokeService jsEngine; | |
61 | + private static final JsonParser parser = new JsonParser(); | |
62 | + @Override | |
63 | + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(DeviceSessionCtx ctx, String inbound) throws AdaptorException { | |
64 | + try { | |
65 | + JsonElement payload = validatePayload(ctx, inbound, false); | |
66 | + return JsonConverter.convertToTelemetryProto(payload); | |
67 | + } catch (IllegalStateException | JsonSyntaxException ex) { | |
68 | + log.debug("Failed to decode post telemetry request", ex); | |
69 | + throw new AdaptorException(ex); | |
70 | + } catch (ExecutionException e) { | |
71 | + throw new RuntimeException(e); | |
72 | + } catch (InterruptedException e) { | |
73 | + throw new RuntimeException(e); | |
74 | + } | |
75 | + } | |
76 | + | |
77 | + @Override | |
78 | + public UUID getJsScriptEngineFunctionId(String scriptBody, String... argNames) throws ExecutionException, InterruptedException { | |
79 | + return jsEngine.eval(YtScriptType.TCP_TRANSPORT_SCRIPT, scriptBody, argNames).get(); | |
80 | + } | |
81 | + | |
82 | + @Override | |
83 | + public TransportProtos.PostAttributeMsg convertToPostAttributes(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException { | |
84 | + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false); | |
85 | + try { | |
86 | + return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); | |
87 | + } catch (IllegalStateException | JsonSyntaxException ex) { | |
88 | + log.debug("Failed to decode post attributes request", ex); | |
89 | + throw new AdaptorException(ex); | |
90 | + } | |
91 | + } | |
92 | + | |
93 | + @Override | |
94 | + public TransportProtos.ClaimDeviceMsg convertToClaimDevice(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException { | |
95 | + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), true); | |
96 | + try { | |
97 | + return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload); | |
98 | + } catch (IllegalStateException | JsonSyntaxException ex) { | |
99 | + log.debug("Failed to decode claim device request", ex); | |
100 | + throw new AdaptorException(ex); | |
101 | + } | |
102 | + } | |
103 | + | |
104 | + @Override | |
105 | + public TransportProtos.ProvisionDeviceRequestMsg convertToProvisionRequestMsg(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException { | |
106 | + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false); | |
107 | + try { | |
108 | + return JsonConverter.convertToProvisionRequestMsg(payload); | |
109 | + } catch (IllegalStateException | JsonSyntaxException ex) { | |
110 | + throw new AdaptorException(ex); | |
111 | + } | |
112 | + } | |
113 | + | |
114 | + @Override | |
115 | + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException { | |
116 | + return processGetAttributeRequestMsg(inbound, topicBase); | |
117 | + } | |
118 | + | |
119 | + @Override | |
120 | + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException { | |
121 | + return processToDeviceRpcResponseMsg(inbound, topicBase); | |
122 | + } | |
123 | + | |
124 | + @Override | |
125 | + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException { | |
126 | + return processToServerRpcRequestMsg(ctx, inbound, topicBase); | |
127 | + } | |
128 | + | |
129 | + @Override | |
130 | + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException { | |
131 | + return processConvertFromAttributeResponseMsg(ctx, responseMsg, topicBase); | |
132 | + } | |
133 | + | |
134 | + @Override | |
135 | + public Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { | |
136 | + return processConvertFromGatewayAttributeResponseMsg(ctx, deviceName, responseMsg); | |
137 | + } | |
138 | + | |
139 | + @Override | |
140 | + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg, String topic) { | |
141 | + return Optional.of(createMqttPublishMsg(ctx, topic, JsonConverter.toJson(notificationMsg))); | |
142 | + } | |
143 | + | |
144 | + @Override | |
145 | + public Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) { | |
146 | + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, notificationMsg); | |
147 | + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, result)); | |
148 | + } | |
149 | + | |
150 | + @Override | |
151 | + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, String topicBase) { | |
152 | + return Optional.of(createMqttPublishMsg(ctx, topicBase + rpcRequest.getRequestId(), JsonConverter.toJson(rpcRequest, false))); | |
153 | + } | |
154 | + | |
155 | + @Override | |
156 | + public Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { | |
157 | + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, JsonConverter.toGatewayJson(deviceName, rpcRequest))); | |
158 | + } | |
159 | + | |
160 | + @Override | |
161 | + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.ToServerRpcResponseMsg rpcResponse, String topicBase) { | |
162 | + return Optional.of(createMqttPublishMsg(ctx, topicBase + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse))); | |
163 | + } | |
164 | + | |
165 | + @Override | |
166 | + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, TransportProtos.ProvisionDeviceResponseMsg provisionResponse) { | |
167 | + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse))); | |
168 | + } | |
169 | + | |
170 | + @Override | |
171 | + public Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) { | |
172 | + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); | |
173 | + } | |
174 | + | |
175 | + public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { | |
176 | + String payload = validatePayload(sessionId, payloadData, false); | |
177 | + try { | |
178 | + return new JsonParser().parse(payload); | |
179 | + } catch (JsonSyntaxException ex) { | |
180 | + log.debug("Payload is in incorrect format: {}", payload); | |
181 | + throw new AdaptorException(ex); | |
182 | + } | |
183 | + } | |
184 | + | |
185 | + private TransportProtos.GetAttributeRequestMsg processGetAttributeRequestMsg(MqttPublishMessage inbound, String topicBase) throws AdaptorException { | |
186 | + String topicName = inbound.variableHeader().topicName(); | |
187 | + try { | |
188 | + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); | |
189 | + result.setRequestId(getRequestId(topicName, topicBase)); | |
190 | + String payload = inbound.payload().toString(UTF8); | |
191 | + JsonElement requestBody = new JsonParser().parse(payload); | |
192 | + Set<String> clientKeys = toStringSet(requestBody, "clientKeys"); | |
193 | + Set<String> sharedKeys = toStringSet(requestBody, "sharedKeys"); | |
194 | + if (clientKeys != null) { | |
195 | + result.addAllClientAttributeNames(clientKeys); | |
196 | + } | |
197 | + if (sharedKeys != null) { | |
198 | + result.addAllSharedAttributeNames(sharedKeys); | |
199 | + } | |
200 | + return result.build(); | |
201 | + } catch (RuntimeException e) { | |
202 | + log.debug("Failed to decode get attributes request", e); | |
203 | + throw new AdaptorException(e); | |
204 | + } | |
205 | + } | |
206 | + | |
207 | + private TransportProtos.ToDeviceRpcResponseMsg processToDeviceRpcResponseMsg(MqttPublishMessage inbound, String topicBase) throws AdaptorException { | |
208 | + String topicName = inbound.variableHeader().topicName(); | |
209 | + try { | |
210 | + int requestId = getRequestId(topicName, topicBase); | |
211 | + String payload = inbound.payload().toString(UTF8); | |
212 | + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); | |
213 | + } catch (RuntimeException e) { | |
214 | + log.debug("Failed to decode rpc response", e); | |
215 | + throw new AdaptorException(e); | |
216 | + } | |
217 | + } | |
218 | + | |
219 | + private TransportProtos.ToServerRpcRequestMsg processToServerRpcRequestMsg(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException { | |
220 | + String topicName = inbound.variableHeader().topicName(); | |
221 | + String payload = validatePayload(ctx.getSessionId(), inbound.payload(), false); | |
222 | + try { | |
223 | + int requestId = getRequestId(topicName, topicBase); | |
224 | + return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId); | |
225 | + } catch (IllegalStateException | JsonSyntaxException ex) { | |
226 | + log.debug("Failed to decode to server rpc request", ex); | |
227 | + throw new AdaptorException(ex); | |
228 | + } | |
229 | + } | |
230 | + | |
231 | + private Optional<MqttMessage> processConvertFromAttributeResponseMsg(DeviceSessionCtx ctx, TransportProtos.GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException { | |
232 | + if (!StringUtils.isEmpty(responseMsg.getError())) { | |
233 | + throw new AdaptorException(responseMsg.getError()); | |
234 | + } else { | |
235 | + int requestId = responseMsg.getRequestId(); | |
236 | + if (requestId >= 0) { | |
237 | + return Optional.of(createMqttPublishMsg(ctx, | |
238 | + topicBase + requestId, | |
239 | + JsonConverter.toJson(responseMsg))); | |
240 | + } | |
241 | + return Optional.empty(); | |
242 | + } | |
243 | + } | |
244 | + | |
245 | + private Optional<MqttMessage> processConvertFromGatewayAttributeResponseMsg(DeviceSessionCtx ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { | |
246 | + if (!StringUtils.isEmpty(responseMsg.getError())) { | |
247 | + throw new AdaptorException(responseMsg.getError()); | |
248 | + } else { | |
249 | + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, responseMsg); | |
250 | + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result)); | |
251 | + } | |
252 | + } | |
253 | + | |
254 | + protected MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, JsonElement json) { | |
255 | + MqttFixedHeader mqttFixedHeader = null; | |
256 | +// new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); | |
257 | + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId()); | |
258 | + ByteBuf payload = ALLOCATOR.buffer(); | |
259 | + payload.writeBytes(json.toString().getBytes(UTF8)); | |
260 | + return new MqttPublishMessage(mqttFixedHeader, header, payload); | |
261 | + } | |
262 | + | |
263 | + private Set<String> toStringSet(JsonElement requestBody, String name) { | |
264 | + JsonElement element = requestBody.getAsJsonObject().get(name); | |
265 | + if (element != null) { | |
266 | + return new HashSet<>(Arrays.asList(element.getAsString().split(","))); | |
267 | + } else { | |
268 | + return null; | |
269 | + } | |
270 | + } | |
271 | + | |
272 | + private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException { | |
273 | + String payload = payloadData.toString(UTF8); | |
274 | + if (payload == null) { | |
275 | + log.debug("[{}] Payload is empty!", sessionId); | |
276 | + if (!isEmptyPayloadAllowed) { | |
277 | + throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); | |
278 | + } | |
279 | + } | |
280 | + return payload; | |
281 | + } | |
282 | + | |
283 | + private JsonElement validatePayload(DeviceSessionCtx session, String payload, boolean isEmptyPayloadAllowed) throws AdaptorException, ExecutionException, InterruptedException { | |
284 | + if (payload == null) { | |
285 | + log.debug("[{}] Payload is empty!", session.getSessionId()); | |
286 | + if (!isEmptyPayloadAllowed) { | |
287 | + throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); | |
288 | + } | |
289 | + } | |
290 | +// jsEngine.invokeFunction(); | |
291 | +// new JsonParser().parse(payload) | |
292 | +// return payload; | |
293 | + ListenableFuture<JsonElement> result = Futures.transformAsync(jsEngine.invokeFunction(session.getScriptId(), payload), | |
294 | + o -> { | |
295 | + try { | |
296 | + return Futures.immediateFuture(parser.parse(o.toString())); | |
297 | + } catch (Exception e) { | |
298 | + return Futures.immediateFailedFuture(e); | |
299 | + } | |
300 | + }, MoreExecutors.directExecutor()); | |
301 | + | |
302 | + return result.get(); | |
303 | + } | |
304 | + | |
305 | + private int getRequestId(String topicName, String topic) { | |
306 | + return Integer.parseInt(topicName.substring(topic.length())); | |
307 | + } | |
308 | + | |
309 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.adaptors; | |
17 | + | |
18 | +import io.netty.buffer.ByteBuf; | |
19 | +import io.netty.buffer.ByteBufAllocator; | |
20 | +import io.netty.buffer.UnpooledByteBufAllocator; | |
21 | +import io.netty.handler.codec.mqtt.MqttFixedHeader; | |
22 | +import io.netty.handler.codec.mqtt.MqttMessage; | |
23 | +import io.netty.handler.codec.mqtt.MqttMessageType; | |
24 | +import io.netty.handler.codec.mqtt.MqttPublishMessage; | |
25 | +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; | |
26 | +import org.thingsboard.server.common.data.ota.OtaPackageType; | |
27 | +import org.thingsboard.server.common.transport.adaptor.AdaptorException; | |
28 | +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; | |
29 | +import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; | |
30 | +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; | |
31 | +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; | |
32 | +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; | |
33 | +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; | |
34 | +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; | |
35 | +import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; | |
36 | +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; | |
37 | +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; | |
38 | +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; | |
39 | +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; | |
40 | +import org.thingsboard.server.transport.tcp.session.DeviceSessionCtx; | |
41 | + | |
42 | +import java.util.Optional; | |
43 | +import java.util.UUID; | |
44 | +import java.util.concurrent.ExecutionException; | |
45 | + | |
46 | +/** | |
47 | + * @author Andrew Shvayka | |
48 | + */ | |
49 | +public interface TcpTransportAdaptor { | |
50 | + | |
51 | + ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); | |
52 | + | |
53 | + PostTelemetryMsg convertToPostTelemetry(DeviceSessionCtx ctx, String inbound) throws AdaptorException; | |
54 | + | |
55 | + UUID getJsScriptEngineFunctionId(String scriptBody, String... argNames) throws ExecutionException, InterruptedException; | |
56 | + PostAttributeMsg convertToPostAttributes(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException; | |
57 | + | |
58 | + GetAttributeRequestMsg convertToGetAttributes(DeviceSessionCtx ctx, MqttPublishMessage inbound, String topicBase) throws AdaptorException; | |
59 | + | |
60 | + ToDeviceRpcResponseMsg convertToDeviceRpcResponse(DeviceSessionCtx ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException; | |
61 | + | |
62 | + ToServerRpcRequestMsg convertToServerRpcRequest(DeviceSessionCtx ctx, MqttPublishMessage mqttMsg, String topicBase) throws AdaptorException; | |
63 | + | |
64 | + ClaimDeviceMsg convertToClaimDevice(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException; | |
65 | + | |
66 | + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, GetAttributeResponseMsg responseMsg, String topicBase) throws AdaptorException; | |
67 | + | |
68 | + Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, GetAttributeResponseMsg responseMsg) throws AdaptorException; | |
69 | + | |
70 | + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, AttributeUpdateNotificationMsg notificationMsg, String topic) throws AdaptorException; | |
71 | + | |
72 | + Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; | |
73 | + | |
74 | + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, ToDeviceRpcRequestMsg rpcRequest, String topicBase) throws AdaptorException; | |
75 | + | |
76 | + Optional<MqttMessage> convertToGatewayPublish(DeviceSessionCtx ctx, String deviceName, ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; | |
77 | + | |
78 | + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, ToServerRpcResponseMsg rpcResponse, String topicBase) throws AdaptorException; | |
79 | + | |
80 | + ProvisionDeviceRequestMsg convertToProvisionRequestMsg(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException; | |
81 | + | |
82 | + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; | |
83 | + | |
84 | + Optional<MqttMessage> convertToPublish(DeviceSessionCtx ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException; | |
85 | + | |
86 | + default MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, byte[] payloadInBytes) { | |
87 | + MqttFixedHeader mqttFixedHeader =null; | |
88 | +// new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); | |
89 | + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId()); | |
90 | + ByteBuf payload = ALLOCATOR.buffer(); | |
91 | + payload.writeBytes(payloadInBytes); | |
92 | + return new MqttPublishMessage(mqttFixedHeader, header, payload); | |
93 | + } | |
94 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/limits/IpFilter.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.limits; | |
17 | + | |
18 | +import io.netty.channel.ChannelHandlerContext; | |
19 | +import io.netty.handler.ipfilter.AbstractRemoteAddressFilter; | |
20 | +import lombok.extern.slf4j.Slf4j; | |
21 | +import org.thingsboard.server.transport.tcp.TcpTransportContext; | |
22 | +import org.thingsboard.server.transport.tcp.TcpTransportService; | |
23 | + | |
24 | +import java.net.InetSocketAddress; | |
25 | + | |
26 | +@Slf4j | |
27 | +public class IpFilter extends AbstractRemoteAddressFilter<InetSocketAddress> { | |
28 | + | |
29 | + private TcpTransportContext context; | |
30 | + | |
31 | + public IpFilter(TcpTransportContext context) { | |
32 | + this.context = context; | |
33 | + } | |
34 | + | |
35 | + @Override | |
36 | + protected boolean accept(ChannelHandlerContext ctx, InetSocketAddress remoteAddress) throws Exception { | |
37 | + log.trace("[{}] Received msg: {}", ctx.channel().id(), remoteAddress); | |
38 | + if(context.checkAddress(remoteAddress)){ | |
39 | + log.trace("[{}] Setting address: {}", ctx.channel().id(), remoteAddress); | |
40 | + ctx.channel().attr(TcpTransportService.ADDRESS).set(remoteAddress); | |
41 | + return true; | |
42 | + } else { | |
43 | + return false; | |
44 | + } | |
45 | + } | |
46 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/limits/ProxyIpFilter.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.limits; | |
17 | + | |
18 | +import io.netty.channel.ChannelHandler; | |
19 | +import io.netty.channel.ChannelHandlerContext; | |
20 | +import io.netty.channel.ChannelInboundHandlerAdapter; | |
21 | +import io.netty.handler.codec.haproxy.HAProxyMessage; | |
22 | +import lombok.extern.slf4j.Slf4j; | |
23 | +import org.thingsboard.server.transport.tcp.TcpTransportContext; | |
24 | +import org.thingsboard.server.transport.tcp.TcpTransportService; | |
25 | + | |
26 | +import java.net.InetSocketAddress; | |
27 | + | |
28 | +@Slf4j | |
29 | +public class ProxyIpFilter extends ChannelInboundHandlerAdapter { | |
30 | + | |
31 | + | |
32 | + private TcpTransportContext context; | |
33 | + | |
34 | + public ProxyIpFilter(TcpTransportContext context) { | |
35 | + this.context = context; | |
36 | + } | |
37 | + | |
38 | + @Override | |
39 | + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { | |
40 | + log.trace("[{}] Received msg: {}", ctx.channel().id(), msg); | |
41 | + if (msg instanceof HAProxyMessage) { | |
42 | + HAProxyMessage proxyMsg = (HAProxyMessage) msg; | |
43 | + if (proxyMsg.sourceAddress() != null && proxyMsg.sourcePort() > 0) { | |
44 | + InetSocketAddress address = new InetSocketAddress(proxyMsg.sourceAddress(), proxyMsg.sourcePort()); | |
45 | + if (!context.checkAddress(address)) { | |
46 | + closeChannel(ctx); | |
47 | + } else { | |
48 | + log.trace("[{}] Setting address: {}", ctx.channel().id(), address); | |
49 | + ctx.channel().attr(TcpTransportService.ADDRESS).set(address); | |
50 | + // We no longer need this channel in the pipeline. Similar to HAProxyMessageDecoder | |
51 | + ctx.pipeline().remove(this); | |
52 | + } | |
53 | + } else { | |
54 | + log.trace("Received local health-check connection message: {}", proxyMsg); | |
55 | + closeChannel(ctx); | |
56 | + } | |
57 | + } | |
58 | + } | |
59 | + | |
60 | + private void closeChannel(ChannelHandlerContext ctx) { | |
61 | + while (ctx.pipeline().last() != this) { | |
62 | + ChannelHandler handler = ctx.pipeline().removeLast(); | |
63 | + if (handler instanceof ChannelInboundHandlerAdapter) { | |
64 | + try { | |
65 | + ((ChannelInboundHandlerAdapter) handler).channelUnregistered(ctx); | |
66 | + } catch (Exception e) { | |
67 | + log.error("Failed to unregister channel: [{}]", ctx, e); | |
68 | + } | |
69 | + } | |
70 | + | |
71 | + } | |
72 | + ctx.pipeline().remove(this); | |
73 | + ctx.close(); | |
74 | + } | |
75 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * <p> | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.session; | |
17 | + | |
18 | +import io.netty.channel.ChannelHandlerContext; | |
19 | +import io.netty.util.ReferenceCountUtil; | |
20 | +import lombok.Getter; | |
21 | +import lombok.Setter; | |
22 | +import lombok.extern.slf4j.Slf4j; | |
23 | +import org.thingsboard.server.common.data.DeviceProfile; | |
24 | +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; | |
25 | +import org.thingsboard.server.common.data.device.profile.YtTcpDeviceProfileTransportConfiguration; | |
26 | +import org.thingsboard.server.common.data.yunteng.enums.TcpDataTypeEnum; | |
27 | +import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; | |
28 | +import org.thingsboard.server.gen.transport.TransportProtos; | |
29 | +import org.thingsboard.server.transport.tcp.TcpTransportContext; | |
30 | +import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor; | |
31 | +import org.thingsboard.server.transport.tcp.util.ByteUtils; | |
32 | + | |
33 | +import java.util.UUID; | |
34 | +import java.util.concurrent.ConcurrentLinkedQueue; | |
35 | +import java.util.concurrent.ExecutionException; | |
36 | +import java.util.concurrent.atomic.AtomicInteger; | |
37 | +import java.util.concurrent.locks.Lock; | |
38 | +import java.util.concurrent.locks.ReentrantLock; | |
39 | +import java.util.function.Consumer; | |
40 | + | |
41 | +/** | |
42 | + * @author Andrew Shvayka | |
43 | + */ | |
44 | +@Slf4j | |
45 | +public class DeviceSessionCtx extends DeviceAwareSessionContext { | |
46 | + | |
47 | + @Getter | |
48 | + @Setter | |
49 | + private ChannelHandlerContext channel; | |
50 | + | |
51 | + @Getter | |
52 | + private final TcpTransportContext context; | |
53 | + | |
54 | + private final AtomicInteger msgIdSeq = new AtomicInteger(0); | |
55 | + | |
56 | + private final ConcurrentLinkedQueue<TCPMessage> msgQueue = new ConcurrentLinkedQueue<>(); | |
57 | + | |
58 | + @Getter | |
59 | + private final Lock msgQueueProcessorLock = new ReentrantLock(); | |
60 | + | |
61 | + private final AtomicInteger msgQueueSize = new AtomicInteger(0); | |
62 | + | |
63 | + @Getter | |
64 | + @Setter | |
65 | + private boolean provisionOnly = false; | |
66 | + | |
67 | + // private volatile TcpTopicFilter telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); | |
68 | +// private volatile TcpTopicFilter attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); | |
69 | + @Getter | |
70 | + private volatile TcpDataTypeEnum payloadType = TcpDataTypeEnum.HEX; | |
71 | + @Getter | |
72 | + private volatile byte[] pingText ; | |
73 | + // private volatile Descriptors.Descriptor attributesDynamicMessageDescriptor; | |
74 | +// private volatile Descriptors.Descriptor telemetryDynamicMessageDescriptor; | |
75 | +// private volatile Descriptors.Descriptor rpcResponseDynamicMessageDescriptor; | |
76 | +// private volatile DynamicMessage.Builder rpcRequestDynamicMessageBuilder; | |
77 | + private volatile TcpTransportAdaptor adaptor; | |
78 | + @Getter | |
79 | + private UUID scriptId; | |
80 | + | |
81 | + public DeviceSessionCtx(UUID sessionId, TcpTransportContext context) { | |
82 | + super(sessionId); | |
83 | + this.context = context; | |
84 | + this.adaptor = context.getJsonTcpAdaptor(); | |
85 | + } | |
86 | + | |
87 | + public int nextMsgId() { | |
88 | + return msgIdSeq.incrementAndGet(); | |
89 | + } | |
90 | + | |
91 | + | |
92 | + public TcpTransportAdaptor getPayloadAdaptor() { | |
93 | + return adaptor; | |
94 | + } | |
95 | + | |
96 | + | |
97 | + | |
98 | + | |
99 | + @Override | |
100 | + public void setDeviceProfile(DeviceProfile deviceProfile) { | |
101 | + super.setDeviceProfile(deviceProfile); | |
102 | + updateDeviceSessionConfiguration(deviceProfile); | |
103 | + } | |
104 | + | |
105 | + @Override | |
106 | + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { | |
107 | + super.onDeviceProfileUpdate(sessionInfo, deviceProfile); | |
108 | + updateDeviceSessionConfiguration(deviceProfile); | |
109 | + } | |
110 | + | |
111 | + private void updateDeviceSessionConfiguration(DeviceProfile deviceProfile){ | |
112 | + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); | |
113 | + | |
114 | + YtTcpDeviceProfileTransportConfiguration tcpConfiguration = (YtTcpDeviceProfileTransportConfiguration) transportConfiguration; | |
115 | + payloadType = tcpConfiguration.getDataFormat(); | |
116 | + if (TcpDataTypeEnum.ASCII.equals(payloadType)) { | |
117 | + payloadType = TcpDataTypeEnum.ASCII; | |
118 | + } else { | |
119 | + payloadType = TcpDataTypeEnum.HEX; | |
120 | + } | |
121 | + this.pingText = ByteUtils.getBytes(tcpConfiguration.getPingText(),ByteUtils.UTF_8); | |
122 | + String scriptBody = tcpConfiguration.getScriptText(); | |
123 | + try { | |
124 | + this.scriptId = this.adaptor.getJsScriptEngineFunctionId(scriptBody); | |
125 | + } catch (ExecutionException e) { | |
126 | + log.warn("设备配置【{}】的脚本【{}】解析异常",deviceProfile.getSearchText(),scriptBody); | |
127 | + throw new RuntimeException(e); | |
128 | + } catch (InterruptedException e) { | |
129 | + throw new RuntimeException(e); | |
130 | + } | |
131 | + } | |
132 | + | |
133 | + | |
134 | + | |
135 | + | |
136 | + | |
137 | + | |
138 | + public void addToQueue(TCPMessage msg) { | |
139 | + msgQueueSize.incrementAndGet(); | |
140 | + ReferenceCountUtil.retain(msg); | |
141 | + msgQueue.add(msg); | |
142 | + } | |
143 | + | |
144 | + public void tryProcessQueuedMsgs(Consumer<TCPMessage> msgProcessor) { | |
145 | + while (!msgQueue.isEmpty()) { | |
146 | + if (msgQueueProcessorLock.tryLock()) { | |
147 | + try { | |
148 | + TCPMessage msg; | |
149 | + while ((msg = msgQueue.poll()) != null) { | |
150 | + try { | |
151 | + msgQueueSize.decrementAndGet(); | |
152 | + msgProcessor.accept(msg); | |
153 | + } finally { | |
154 | + ReferenceCountUtil.safeRelease(msg); | |
155 | + } | |
156 | + } | |
157 | + } finally { | |
158 | + msgQueueProcessorLock.unlock(); | |
159 | + } | |
160 | + } else { | |
161 | + return; | |
162 | + } | |
163 | + } | |
164 | + } | |
165 | + | |
166 | + public int getMsgQueueSize() { | |
167 | + return msgQueueSize.get(); | |
168 | + } | |
169 | + | |
170 | + public void release() { | |
171 | + if (!msgQueue.isEmpty()) { | |
172 | + log.warn("doDisconnect for device {} but unprocessed messages {} left in the msg queue", getDeviceId(), msgQueue.size()); | |
173 | + msgQueue.forEach(ReferenceCountUtil::safeRelease); | |
174 | + msgQueue.clear(); | |
175 | + } | |
176 | + } | |
177 | + | |
178 | + | |
179 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/session/TCPMessage.java
0 → 100644
1 | +package org.thingsboard.server.transport.tcp.session; | |
2 | + | |
3 | +import io.netty.handler.codec.mqtt.MqttMessageType; | |
4 | +import lombok.Data; | |
5 | +import lombok.NoArgsConstructor; | |
6 | +import lombok.ToString; | |
7 | + | |
8 | +import java.io.Serializable; | |
9 | + | |
10 | +@Data | |
11 | +public class TCPMessage implements Serializable { | |
12 | + private String requestId; | |
13 | + private MqttMessageType messageType; | |
14 | + | |
15 | + private byte[] message; | |
16 | + private boolean hex; | |
17 | + | |
18 | + private String deviceId; | |
19 | + | |
20 | + /** | |
21 | + * TCP 消息 | |
22 | + * @param sn 设备sn | |
23 | + * @param message 消息体 | |
24 | + * @param hex 是否是16进制 | |
25 | + */ | |
26 | + public TCPMessage(MqttMessageType messageType, String deviceId, byte[] message, boolean hex){ | |
27 | + this.messageType = messageType; | |
28 | + this.deviceId = deviceId; | |
29 | + this.message = message; | |
30 | + this.hex = hex; | |
31 | + } | |
32 | + | |
33 | + public TCPMessage(MqttMessageType messageType, byte[] message){ | |
34 | + this.messageType = messageType; | |
35 | + this.message = message; | |
36 | + } | |
37 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.util; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public class AlwaysTrueTopicFilter implements TcpTopicFilter { | |
22 | + | |
23 | + @Override | |
24 | + public boolean filter(String topic) { | |
25 | + return true; | |
26 | + } | |
27 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/ByteUtils.java
0 → 100644
1 | +package org.thingsboard.server.transport.tcp.util; | |
2 | + | |
3 | +import io.netty.buffer.ByteBuf; | |
4 | +import org.apache.commons.lang3.ArrayUtils; | |
5 | + | |
6 | +import java.io.*; | |
7 | +import java.nio.charset.Charset; | |
8 | + | |
9 | +/** | |
10 | + * @Description :数据类型转换工具类 | |
11 | + * 二进制的最高位是符号位,0表示正,1表示负。 | |
12 | + * 负数采用二进制的补码表示,10进制转为二进制得到的是源码,将源码按位取反得到的是反码,反码加1得到补码 | |
13 | + * | |
14 | + * | |
15 | + * *¥¥¥【基本数据类型】¥¥¥¥¥¥¥¥¥¥¥¥ | |
16 | + 1/8:boolean【1 位】 | |
17 | + 3/8:byte 【1个字节,8 位】 | |
18 | + 3/8:short 【2个字节,16位】 | |
19 | + 4/8:int 【4个字节,32位】 | |
20 | + 5/8:long 【8个字节,64位】 | |
21 | + 6/8:float 【4个字节,32位】 | |
22 | + 7/8:double 【8个字节,64位】 | |
23 | + 8/8:chart 【2个字节,16位】 | |
24 | + * | |
25 | + * | |
26 | + * | |
27 | + * *¥¥¥【位运算】¥¥¥¥¥¥¥¥¥¥¥¥ | |
28 | + 左移【<< n】:向左移动,左边高位舍弃,右边的低位补0,例如:2左移3位,结果为16 | |
29 | + 右移【>>n】:向右移动,右边的舍弃掉,左边补的值取决于原来最高位,原来是1就补1,原来是0就补0 | |
30 | + 无符号右移【>>>n】向右移动,右边的舍弃掉,左边补0。 | |
31 | + * | |
32 | + * | |
33 | + * | |
34 | + * 1.其它地方抛出异常,交由控制层统一处理 | |
35 | + * 2.服务层注意持久化的事务管理 | |
36 | + * @Author: junlianglee | |
37 | + * @Date Created in 2019/12/11. | |
38 | + * @Modified by Administrator on 2019/12/11. | |
39 | + */ | |
40 | +public class ByteUtils { | |
41 | + public static final char[] ascii = "0123456789ABCDEF".toCharArray(); | |
42 | + private static char[] HEX_VOCABLE = {'0', '1', '2', '3', '4', '5', '6', '7', | |
43 | + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; | |
44 | + | |
45 | + public static final String UTF_8 = "UTF-8"; | |
46 | + /** | |
47 | + * 将short整型数值转换为字节数组 | |
48 | + * | |
49 | + * @param data | |
50 | + * @return | |
51 | + */ | |
52 | + public static byte[] getBytes(short data) { | |
53 | + byte[] bytes = new byte[2]; | |
54 | + bytes[0] = (byte) ((data & 0xff00) >> 8);//1个十六进制位匹配4个二进制位 | |
55 | + bytes[1] = (byte) (data & 0xff); | |
56 | + return bytes; | |
57 | + } | |
58 | + | |
59 | + /** | |
60 | + * 将字符转换为字节数组 | |
61 | + * | |
62 | + * @param data | |
63 | + * @return | |
64 | + */ | |
65 | + public static byte[] getBytes(char data) { | |
66 | + byte[] bytes = new byte[2]; | |
67 | + bytes[0] = (byte) (data >> 8); | |
68 | + bytes[1] = (byte) (data); | |
69 | + return bytes; | |
70 | + } | |
71 | + | |
72 | + /** | |
73 | + * 将布尔值转换为字节数组 | |
74 | + * | |
75 | + * @param data | |
76 | + * @return | |
77 | + */ | |
78 | + public static byte[] getBytes(boolean data) { | |
79 | + byte[] bytes = new byte[1]; | |
80 | + bytes[0] = (byte) (data ? 1 : 0); | |
81 | + return bytes; | |
82 | + } | |
83 | + | |
84 | + /** | |
85 | + * 将整型数值转换为字节数组 | |
86 | + * | |
87 | + * @param data | |
88 | + * @return | |
89 | + */ | |
90 | + public static byte[] getBytes(int data) { | |
91 | + byte[] bytes = new byte[4]; | |
92 | + bytes[0] = (byte) ((data & 0xff000000) >> 24); | |
93 | + bytes[1] = (byte) ((data & 0xff0000) >> 16); | |
94 | + bytes[2] = (byte) ((data & 0xff00) >> 8); | |
95 | + bytes[3] = (byte) (data & 0xff); | |
96 | + return bytes; | |
97 | + } | |
98 | + | |
99 | + /** | |
100 | + * 将long整型数值转换为字节数组 | |
101 | + * | |
102 | + * @param data | |
103 | + * @return | |
104 | + */ | |
105 | + public static byte[] getBytes(long data) { | |
106 | + byte[] bytes = new byte[8]; | |
107 | + bytes[0] = (byte) ((data >> 56) & 0xff); | |
108 | + bytes[1] = (byte) ((data >> 48) & 0xff); | |
109 | + bytes[2] = (byte) ((data >> 40) & 0xff); | |
110 | + bytes[3] = (byte) ((data >> 32) & 0xff); | |
111 | + bytes[4] = (byte) ((data >> 24) & 0xff); | |
112 | + bytes[5] = (byte) ((data >> 16) & 0xff); | |
113 | + bytes[6] = (byte) ((data >> 8) & 0xff); | |
114 | + bytes[7] = (byte) (data & 0xff); | |
115 | + return bytes; | |
116 | + } | |
117 | + | |
118 | + /** | |
119 | + * 将float型数值转换为字节数组 | |
120 | + * | |
121 | + * @param data | |
122 | + * @return | |
123 | + */ | |
124 | + public static byte[] getBytes(float data) { | |
125 | + int intBits = Float.floatToIntBits(data); | |
126 | + return getBytes(intBits); | |
127 | + } | |
128 | + | |
129 | + /** | |
130 | + * 将double型数值转换为字节数组 | |
131 | + * | |
132 | + * @param data | |
133 | + * @return | |
134 | + */ | |
135 | + public static byte[] getBytes(double data) { | |
136 | + long intBits = Double.doubleToLongBits(data); | |
137 | + return getBytes(intBits); | |
138 | + } | |
139 | + | |
140 | + /** | |
141 | + * 将字符串按照charsetName编码格式的字节数组 | |
142 | + * | |
143 | + * @param data | |
144 | + * 字符串 | |
145 | + * @param charsetName | |
146 | + * 编码格式 | |
147 | + * @return | |
148 | + */ | |
149 | + public static byte[] getBytes(String data, String charsetName) { | |
150 | + Charset charset = Charset.forName(charsetName); | |
151 | + return data.getBytes(charset); | |
152 | + } | |
153 | + | |
154 | + | |
155 | + | |
156 | + | |
157 | + | |
158 | + /** | |
159 | + * 将字节数组前8字节转换为long整型数值 | |
160 | + * | |
161 | + * @param bytes | |
162 | + * @return | |
163 | + */ | |
164 | + public static long getLong(byte[] bytes) { | |
165 | + return (0xff00000000000000L & ((long) bytes[0] << 56) | |
166 | + | (0xff000000000000L & ((long) bytes[1] << 48)) | |
167 | + | (0xff0000000000L & ((long) bytes[2] << 40)) | |
168 | + | (0xff00000000L & ((long) bytes[3] << 32)) | |
169 | + | (0xff000000L & ((long) bytes[4] << 24)) | |
170 | + | (0xff0000L & ((long) bytes[5] << 16)) | |
171 | + | (0xff00L & ((long) bytes[6] << 8)) | |
172 | + | (0xffL & (long) bytes[7])); | |
173 | + } | |
174 | + | |
175 | + | |
176 | + | |
177 | + /** | |
178 | + * 将charsetName编码格式的字节数组转换为字符串 | |
179 | + * | |
180 | + * @param bytes | |
181 | + * @param charsetName | |
182 | + * @return | |
183 | + */ | |
184 | + public static String getString(byte[] bytes, String charsetName) { | |
185 | + return new String(bytes, Charset.forName(charsetName)); | |
186 | + } | |
187 | + | |
188 | + | |
189 | + public static byte[] hexStr2Bytes(String hexString) { | |
190 | + if (hexString == null || hexString.equals("")) { | |
191 | + return null; | |
192 | + } | |
193 | + // toUpperCase将字符串中的所有字符转换为大写 | |
194 | + hexString = hexString.toUpperCase(); | |
195 | + int length = hexString.length() / 2;// 2个16进制字符表示一个字节,所以字节数组长度是字符串长度除以2 | |
196 | + // toCharArray将此字符串转换为一个新的字符数组。 | |
197 | + char[] hexChars = hexString.toCharArray(); | |
198 | + byte[] d = new byte[length]; | |
199 | + for (int i = 0; i < length; i++) { | |
200 | + int pos = i * 2; | |
201 | + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | |
202 | + | charToByte(hexChars[pos + 1])); | |
203 | + } | |
204 | + return d; | |
205 | + } | |
206 | + | |
207 | + // charToByte返回在指定字符的第一个发生的字符串中的索引,即返回匹配字符 | |
208 | + private static byte charToByte(char c) { | |
209 | + return (byte) "0123456789ABCDEF".indexOf(c); | |
210 | + } | |
211 | + /** | |
212 | + * 将16进制字符串转换为字节数组 | |
213 | + * | |
214 | + * @param hex | |
215 | + * @return | |
216 | + */ | |
217 | + public static byte[] hexToBytes(String hex) { | |
218 | + if (hex.length() % 2 != 0) | |
219 | + throw new IllegalArgumentException( | |
220 | + "input string should be any multiple of 2!"); | |
221 | + hex.toUpperCase(); | |
222 | + | |
223 | + byte[] byteBuffer = new byte[hex.length() / 2]; | |
224 | + | |
225 | + byte padding = 0x00; | |
226 | + boolean paddingTurning = false; | |
227 | + for (int i = 0; i < hex.length(); i++) { | |
228 | + if (paddingTurning) { | |
229 | + char c = hex.charAt(i); | |
230 | + int index = indexOf(hex, c); | |
231 | + padding = (byte) ((padding << 4) | index); | |
232 | + byteBuffer[i / 2] = padding; | |
233 | + padding = 0x00; | |
234 | + paddingTurning = false; | |
235 | + } else { | |
236 | + char c = hex.charAt(i); | |
237 | + int index = indexOf(hex, c); | |
238 | + padding = (byte) (padding | index); | |
239 | + paddingTurning = true; | |
240 | + } | |
241 | + | |
242 | + } | |
243 | + return byteBuffer; | |
244 | + } | |
245 | + | |
246 | + private static int indexOf(String input, char c) { | |
247 | + int index = ArrayUtils.indexOf(HEX_VOCABLE, c); | |
248 | + | |
249 | + if (index < 0) { | |
250 | + throw new IllegalArgumentException("err input:" + input); | |
251 | + } | |
252 | + return index; | |
253 | + | |
254 | + } | |
255 | + /** | |
256 | + * 字节数组转16进制字符串 | |
257 | + * | |
258 | + * @param bs | |
259 | + * @return | |
260 | + */ | |
261 | + public static String bytesToHex(byte[] bs) { | |
262 | + StringBuilder sb = new StringBuilder(); | |
263 | + for (byte b : bs) { | |
264 | + int high = (b >> 4) & 0x0f; | |
265 | + int low = b & 0x0f; | |
266 | + sb.append(HEX_VOCABLE[high]); | |
267 | + sb.append(HEX_VOCABLE[low]); | |
268 | + } | |
269 | + return sb.toString(); | |
270 | + } | |
271 | + | |
272 | + /** | |
273 | + * 字节数组取前len个字节转16进制字符串 | |
274 | + * | |
275 | + * @param bs | |
276 | + * @param len | |
277 | + * @return | |
278 | + */ | |
279 | + public static String bytesToHex(byte[] bs, int len) { | |
280 | + StringBuilder sb = new StringBuilder(); | |
281 | + for (int i = 0; i < len; i++) { | |
282 | + byte b = bs[i]; | |
283 | + int high = (b >> 4) & 0x0f; | |
284 | + int low = b & 0x0f; | |
285 | + sb.append(HEX_VOCABLE[high]); | |
286 | + sb.append(HEX_VOCABLE[low]); | |
287 | + } | |
288 | + return sb.toString(); | |
289 | + } | |
290 | + /** | |
291 | + * 字节数组偏移offset长度之后的取len个字节转16进制字符串 | |
292 | + * | |
293 | + * @param bs | |
294 | + * @param offset | |
295 | + * @param len | |
296 | + * @return | |
297 | + */ | |
298 | + public static String bytesToHex(byte[] bs, int offset, int len) { | |
299 | + StringBuilder sb = new StringBuilder(); | |
300 | + for (int i = 0; i < len; i++) { | |
301 | + byte b = bs[offset + i]; | |
302 | + int high = (b >> 4) & 0x0f; | |
303 | + int low = b & 0x0f; | |
304 | + sb.append(HEX_VOCABLE[high]); | |
305 | + sb.append(HEX_VOCABLE[low]); | |
306 | + } | |
307 | + return sb.toString(); | |
308 | + } | |
309 | + /** | |
310 | + * 字节数组转16进制字符串 | |
311 | + * | |
312 | + * @param bs | |
313 | + * @return | |
314 | + */ | |
315 | + public static String byteToHex(byte bs) { | |
316 | + StringBuilder sb = new StringBuilder(); | |
317 | + int high = (bs >> 4) & 0x0f; | |
318 | + int low = bs & 0x0f; | |
319 | + sb.append(HEX_VOCABLE[high]); | |
320 | + sb.append(HEX_VOCABLE[low]); | |
321 | + return sb.toString(); | |
322 | + } | |
323 | + /** | |
324 | + * 将字节数组取反 | |
325 | + * | |
326 | + * @param src | |
327 | + * @return | |
328 | + */ | |
329 | + public static String negate(byte[] src) { | |
330 | + if (src == null || src.length == 0) { | |
331 | + return null; | |
332 | + } | |
333 | + byte[] temp = new byte[2 * src.length]; | |
334 | + for (int i = 0; i < src.length; i++) { | |
335 | + byte tmp = (byte) (0xFF ^ src[i]); | |
336 | + temp[i * 2] = (byte) ((tmp >> 4) & 0x0f); | |
337 | + temp[i * 2 + 1] = (byte) (tmp & 0x0f); | |
338 | + } | |
339 | + StringBuffer res = new StringBuffer(); | |
340 | + for (int i = 0; i < temp.length; i++) { | |
341 | + res.append(ascii[temp[i]]); | |
342 | + } | |
343 | + return res.toString(); | |
344 | + } | |
345 | + | |
346 | + | |
347 | + | |
348 | + /** | |
349 | + * 数组合并 | |
350 | + * @param before | |
351 | + * @param end | |
352 | + * @return | |
353 | + */ | |
354 | + public static byte[] merge(byte[]before,byte[]end){ | |
355 | + byte[] result= new byte[before.length+end.length]; | |
356 | + System.arraycopy(before,0,result,0,before.length); | |
357 | + System.arraycopy(end,0,result,before.length,end.length); | |
358 | + return result; | |
359 | + } | |
360 | + | |
361 | + public static byte[] subBytes(byte[]before,Integer start,Integer length){ | |
362 | + byte[] result= new byte[length]; | |
363 | + System.arraycopy(before,start,result,0,length); | |
364 | + return result; | |
365 | + } | |
366 | + | |
367 | + /** | |
368 | + * 比较字节数组是否相同 | |
369 | + * | |
370 | + * @param a | |
371 | + * @param b | |
372 | + * @return | |
373 | + */ | |
374 | + public static boolean compareBytes(byte[] a, byte[] b) { | |
375 | + if (a == null || a.length == 0 || b == null || b.length == 0 | |
376 | + || a.length != b.length) { | |
377 | + return false; | |
378 | + } | |
379 | + if (a.length == b.length) { | |
380 | + for (int i = 0; i < a.length; i++) { | |
381 | + if (a[i] != b[i]) { | |
382 | + return false; | |
383 | + } | |
384 | + } | |
385 | + } else { | |
386 | + return false; | |
387 | + } | |
388 | + return true; | |
389 | + } | |
390 | + /** | |
391 | + * 只比对指定长度byte | |
392 | + * | |
393 | + * @param a | |
394 | + * @param b | |
395 | + * @param len | |
396 | + * @return | |
397 | + */ | |
398 | + public static boolean compareBytes(byte[] a, byte[] b, int len) { | |
399 | + if (a == null || a.length == 0 || b == null || b.length == 0 | |
400 | + || a.length < len || b.length < len) { | |
401 | + return false; | |
402 | + } | |
403 | + for (int i = 0; i < len; i++) { | |
404 | + if (a[i] != b[i]) { | |
405 | + return false; | |
406 | + } | |
407 | + } | |
408 | + return true; | |
409 | + } | |
410 | + | |
411 | + /** | |
412 | + * 将字节数组转换为二进制字符串 | |
413 | + * | |
414 | + * @param items | |
415 | + * @return | |
416 | + */ | |
417 | + public static String bytesToBinaryString(byte[] items) { | |
418 | + if (items == null || items.length == 0) { | |
419 | + return null; | |
420 | + } | |
421 | + StringBuffer buf = new StringBuffer(); | |
422 | + for (byte item : items) { | |
423 | + buf.append(byteToBinaryString(item)); | |
424 | + } | |
425 | + return buf.toString(); | |
426 | + } | |
427 | + | |
428 | + /** | |
429 | + * 将字节转换为二进制字符串 | |
430 | + * | |
431 | + * @param item | |
432 | + * @return | |
433 | + */ | |
434 | + public static String byteToBinaryString(byte item) { | |
435 | + byte a = item; | |
436 | + StringBuffer buf = new StringBuffer(); | |
437 | + for (int i = 0; i < 8; i++) { | |
438 | + buf.insert(0, a % 2); | |
439 | + a = (byte) (a >> 1); | |
440 | + } | |
441 | + return buf.toString(); | |
442 | + } | |
443 | + | |
444 | + /** | |
445 | + * 对数组a,b进行异或运算 | |
446 | + * | |
447 | + * @param a | |
448 | + * @param b | |
449 | + * @return | |
450 | + */ | |
451 | + public static byte[] xor(byte[] a, byte[] b) { | |
452 | + if (a == null || a.length == 0 || b == null || b.length == 0 | |
453 | + || a.length != b.length) { | |
454 | + return null; | |
455 | + } | |
456 | + byte[] result = new byte[a.length]; | |
457 | + for (int i = 0; i < a.length; i++) { | |
458 | + result[i] = (byte) (a[i] ^ b[i]); | |
459 | + } | |
460 | + return result; | |
461 | + } | |
462 | + | |
463 | + /** | |
464 | + * 对数组a,b进行异或运算 运算长度len | |
465 | + * | |
466 | + * @param a | |
467 | + * @param b | |
468 | + * @param len | |
469 | + * @return | |
470 | + */ | |
471 | + public static byte[] xor(byte[] a, byte[] b, int len) { | |
472 | + if (a == null || a.length == 0 || b == null || b.length == 0) { | |
473 | + return null; | |
474 | + } | |
475 | + if (a.length < len || b.length < len) { | |
476 | + return null; | |
477 | + } | |
478 | + byte[] result = new byte[len]; | |
479 | + for (int i = 0; i < len; i++) { | |
480 | + result[i] = (byte) (a[i] ^ b[i]); | |
481 | + } | |
482 | + return result; | |
483 | + } | |
484 | + /** | |
485 | + * 将short整型数值转换为字节数组 | |
486 | + * | |
487 | + * @param num | |
488 | + * @return | |
489 | + */ | |
490 | + public static byte[] shortToBytes(int num) { | |
491 | + byte[] temp = new byte[2]; | |
492 | + for (int i = 0; i < 2; i++) { | |
493 | + temp[i] = (byte) ((num >>> (8 - i * 8)) & 0xFF); | |
494 | + } | |
495 | + return temp; | |
496 | + } | |
497 | + | |
498 | + /** | |
499 | + * 将字节数组转为整型 | |
500 | + * | |
501 | + * @param arr | |
502 | + * @return | |
503 | + */ | |
504 | + public static int bytesToShort(byte[] arr) { | |
505 | + int mask = 0xFF; | |
506 | + int temp = 0; | |
507 | + int result = 0; | |
508 | + for (int i = 0; i < 2; i++) { | |
509 | + result <<= 8; | |
510 | + temp = arr[i] & mask; | |
511 | + result |= temp; | |
512 | + } | |
513 | + return result; | |
514 | + } | |
515 | + | |
516 | + /** | |
517 | + * 将整型数值转换为指定长度的字节数组 | |
518 | + * | |
519 | + * @param num | |
520 | + * @return | |
521 | + */ | |
522 | + public static byte[] intToBytes(int num) { | |
523 | + byte[] temp = new byte[4]; | |
524 | + for (int i = 0; i < 4; i++) { | |
525 | + temp[i] = (byte) ((num >>> (24 - i * 8)) & 0xFF); | |
526 | + } | |
527 | + return temp; | |
528 | + } | |
529 | + | |
530 | + /** | |
531 | + * 将整型数值转换为指定长度的字节数组 | |
532 | + * | |
533 | + * @param src | |
534 | + * @param len | |
535 | + * @return | |
536 | + */ | |
537 | + public static byte[] intToBytes(int src, int len) { | |
538 | + if (len < 1 || len > 4) { | |
539 | + return null; | |
540 | + } | |
541 | + byte[] temp = new byte[len]; | |
542 | + for (int i = 0; i < len; i++) { | |
543 | + temp[len - 1 - i] = (byte) ((src >>> (8 * i)) & 0xFF); | |
544 | + } | |
545 | + return temp; | |
546 | + } | |
547 | + | |
548 | + /** | |
549 | + * 将字节数组转换为整型数值 | |
550 | + * | |
551 | + * @param arr | |
552 | + * @return | |
553 | + */ | |
554 | + public static int bytesToInt(byte[] arr) { | |
555 | + int mask = 0xFF; | |
556 | + int temp = 0; | |
557 | + int result = 0; | |
558 | + for (int i = 0; i < 4; i++) { | |
559 | + result <<= 8; | |
560 | + temp = arr[i] & mask; | |
561 | + result |= temp; | |
562 | + } | |
563 | + return result; | |
564 | + } | |
565 | + | |
566 | + /** | |
567 | + * 将long整型数值转换为字节数组 | |
568 | + * | |
569 | + * @param num | |
570 | + * @return | |
571 | + */ | |
572 | + public static byte[] longToBytes(long num) { | |
573 | + byte[] temp = new byte[8]; | |
574 | + for (int i = 0; i < 8; i++) { | |
575 | + temp[i] = (byte) ((num >>> (56 - i * 8)) & 0xFF); | |
576 | + } | |
577 | + return temp; | |
578 | + } | |
579 | + | |
580 | + /** | |
581 | + * 将字节数组转换为long整型数值 | |
582 | + * | |
583 | + * @param arr | |
584 | + * @return | |
585 | + */ | |
586 | + public static long bytesToLong(byte[] arr) { | |
587 | + int mask = 0xFF; | |
588 | + int temp = 0; | |
589 | + long result = 0; | |
590 | + int len = Math.min(8, arr.length); | |
591 | + for (int i = 0; i < len; i++) { | |
592 | + result <<= 8; | |
593 | + temp = arr[i] & mask; | |
594 | + result |= temp; | |
595 | + } | |
596 | + return result; | |
597 | + } | |
598 | + | |
599 | + /** | |
600 | + * 将16进制字符转换为字节 | |
601 | + * | |
602 | + * @param c | |
603 | + * @return | |
604 | + */ | |
605 | + public static byte toByte(char c) { | |
606 | + byte b = (byte) "0123456789ABCDEF".indexOf(c); | |
607 | + return b; | |
608 | + } | |
609 | + | |
610 | + /** | |
611 | + * 功能描述:把两个字节的字节数组转化为整型数据,高位补零,例如:<br/> | |
612 | + * 有字节数组byte[] data = new byte[]{1,2};转换后int数据的字节分布如下:<br/> | |
613 | + * 00000000 00000000 00000001 00000010,函数返回258 | |
614 | + * | |
615 | + * @param lenData | |
616 | + * 需要进行转换的字节数组 | |
617 | + * @return 字节数组所表示整型值的大小 | |
618 | + */ | |
619 | + public static int bytesToIntWhereByteLengthEquals2(byte lenData[]) { | |
620 | + if (lenData.length != 2) { | |
621 | + return -1; | |
622 | + } | |
623 | + byte fill[] = new byte[]{0, 0}; | |
624 | + byte real[] = new byte[4]; | |
625 | + System.arraycopy(fill, 0, real, 0, 2); | |
626 | + System.arraycopy(lenData, 0, real, 2, 2); | |
627 | + int len = byteToInt(real); | |
628 | + return len; | |
629 | + | |
630 | + } | |
631 | + | |
632 | + /** | |
633 | + * 功能描述:将byte数组转化为int类型的数据 | |
634 | + * | |
635 | + * @param byteVal | |
636 | + * 需要转化的字节数组 | |
637 | + * @return 字节数组所表示的整型数据 | |
638 | + */ | |
639 | + public static int byteToInt(byte[] byteVal) { | |
640 | + int result = 0; | |
641 | + for (int i = 0; i < byteVal.length; i++) { | |
642 | + int tmpVal = (byteVal[i] << (8 * (3 - i))); | |
643 | + switch (i) { | |
644 | + case 0 : | |
645 | + tmpVal = tmpVal & 0xFF000000; | |
646 | + break; | |
647 | + case 1 : | |
648 | + tmpVal = tmpVal & 0x00FF0000; | |
649 | + break; | |
650 | + case 2 : | |
651 | + tmpVal = tmpVal & 0x0000FF00; | |
652 | + break; | |
653 | + case 3 : | |
654 | + tmpVal = tmpVal & 0x000000FF; | |
655 | + break; | |
656 | + } | |
657 | + | |
658 | + result = result | tmpVal; | |
659 | + } | |
660 | + return result; | |
661 | + } | |
662 | + public static byte CheckXORSum(byte[] bData) { | |
663 | + byte sum = 0x00; | |
664 | + for (int i = 0; i < bData.length; i++) { | |
665 | + sum ^= bData[i]; | |
666 | + } | |
667 | + return sum; | |
668 | + } | |
669 | + /** | |
670 | + * 从offset开始 将后续长度为len的byte字节转为int | |
671 | + * | |
672 | + * @param data | |
673 | + * @param offset | |
674 | + * @param len | |
675 | + * @return | |
676 | + */ | |
677 | + public static int bytesToInt(byte[] data, int offset, int len) { | |
678 | + int mask = 0xFF; | |
679 | + int temp = 0; | |
680 | + int result = 0; | |
681 | + len = Math.min(len, 4); | |
682 | + for (int i = 0; i < len; i++) { | |
683 | + result <<= 8; | |
684 | + temp = data[offset + i] & mask; | |
685 | + result |= temp; | |
686 | + } | |
687 | + return result; | |
688 | + } | |
689 | + | |
690 | + /** | |
691 | + * byte字节数组中的字符串的长度 | |
692 | + * | |
693 | + * @param data | |
694 | + * @return | |
695 | + */ | |
696 | + public static int getBytesStringLen(byte[] data) { | |
697 | + int count = 0; | |
698 | + for (byte b : data) { | |
699 | + if (b == 0x00) | |
700 | + break; | |
701 | + count++; | |
702 | + } | |
703 | + return count; | |
704 | + } | |
705 | + | |
706 | + /** | |
707 | + * 校验和 | |
708 | + * | |
709 | + * @param msg | |
710 | + * 需要计算校验和的byte数组 | |
711 | + * @param length | |
712 | + * 校验和位数 | |
713 | + * @return 计算出的校验和数组 | |
714 | + */ | |
715 | + public static byte[] SumCheck(byte[] msg, int length) { | |
716 | + long mSum = 0; | |
717 | + byte[] mByte = new byte[length]; | |
718 | + | |
719 | + /** 逐Byte添加位数和 */ | |
720 | + for (byte byteMsg : msg) { | |
721 | + long mNum = ((long) byteMsg >= 0) | |
722 | + ? (long) byteMsg | |
723 | + : ((long) byteMsg + 256); | |
724 | + mSum += mNum; | |
725 | + } /** end of for (byte byteMsg : msg) */ | |
726 | + | |
727 | + /** 位数和转化为Byte数组 */ | |
728 | + for (int liv_Count = 0; liv_Count < length; liv_Count++) { | |
729 | + mByte[length - liv_Count | |
730 | + - 1] = (byte) (mSum >> (liv_Count * 8) & 0xff); | |
731 | + } /** end of for (int liv_Count = 0; liv_Count < length; liv_Count++) */ | |
732 | + | |
733 | + return mByte; | |
734 | + } | |
735 | + | |
736 | + /** | |
737 | + * 字节数据转十进制字符(补码-无符号) | |
738 | + * | |
739 | + * @param data | |
740 | + * @return | |
741 | + */ | |
742 | + public static String getByteToStr(byte[] data) { | |
743 | + StringBuffer str = new StringBuffer(); | |
744 | + for (int i = 0; i < data.length; i++) { | |
745 | + if (data[i] < 0) { | |
746 | + int tem = data[i] + 256; | |
747 | + str.append(tem + " "); | |
748 | + } else { | |
749 | + str.append(data[i] + " "); | |
750 | + } | |
751 | + } | |
752 | + return str.toString(); | |
753 | + } | |
754 | + | |
755 | + public static byte[] buf2Bytes(ByteBuf in) { | |
756 | + return buf2Bytes( in, in.readableBytes()) ; | |
757 | + } | |
758 | + public static byte[] buf2Bytes(ByteBuf in,int readable) { | |
759 | + byte[] dst = new byte[readable]; | |
760 | + in.getBytes(0, dst); | |
761 | + return dst; | |
762 | + } | |
763 | + | |
764 | + public static byte[] fileBytes(String pathname) throws IOException { | |
765 | + File filename = new File(pathname); | |
766 | + BufferedInputStream in = new BufferedInputStream(new FileInputStream(filename)); | |
767 | + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); | |
768 | + byte[] temp = new byte[1024]; | |
769 | + int size = 0; | |
770 | + while((size = in.read(temp)) != -1){ | |
771 | + out.write(temp, 0, size); | |
772 | + } | |
773 | + in.close(); | |
774 | + out.close(); | |
775 | + byte[] content = out.toByteArray(); | |
776 | + return content; | |
777 | + } | |
778 | + /** | |
779 | + * 单字节高低位互换 | |
780 | + * @param src | |
781 | + * @return | |
782 | + */ | |
783 | + public static int revert(int src) { | |
784 | + int lowByte = (src & 0xFF00) >> 8; | |
785 | + int highByte = (src & 0x00FF) << 8; | |
786 | + return lowByte | highByte; | |
787 | + } | |
788 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/EqualsTopicFilter.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.util; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public class EqualsTopicFilter implements TcpTopicFilter { | |
22 | + | |
23 | + private final String filter; | |
24 | + | |
25 | + @Override | |
26 | + public boolean filter(String topic) { | |
27 | + return filter.equals(topic); | |
28 | + } | |
29 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.util; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.thingsboard.server.common.data.device.profile.MqttTopics; | |
20 | + | |
21 | +import java.util.concurrent.ConcurrentHashMap; | |
22 | +import java.util.concurrent.ConcurrentMap; | |
23 | + | |
24 | +@Slf4j | |
25 | +public class MqttTopicFilterFactory { | |
26 | + | |
27 | + private static final ConcurrentMap<String, TcpTopicFilter> filters = new ConcurrentHashMap<>(); | |
28 | + private static final TcpTopicFilter DEFAULT_TELEMETRY_TOPIC_FILTER = toFilter(MqttTopics.DEVICE_TELEMETRY_TOPIC); | |
29 | + private static final TcpTopicFilter DEFAULT_ATTRIBUTES_TOPIC_FILTER = toFilter(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); | |
30 | + | |
31 | + public static TcpTopicFilter toFilter(String topicFilter) { | |
32 | + if (topicFilter == null || topicFilter.isEmpty()) { | |
33 | + throw new IllegalArgumentException("Topic filter can't be empty!"); | |
34 | + } | |
35 | + return filters.computeIfAbsent(topicFilter, filter -> { | |
36 | + if (filter.equals("#")) { | |
37 | + return new AlwaysTrueTopicFilter(); | |
38 | + } else if (filter.contains("+") || filter.contains("#")) { | |
39 | + String regex = filter | |
40 | + .replace("\\", "\\\\") | |
41 | + .replace("+", "[^/]+") | |
42 | + .replace("/#", "($|/.*)"); | |
43 | + log.debug("Converting [{}] to [{}]", filter, regex); | |
44 | + return new RegexTopicFilter(regex); | |
45 | + } else { | |
46 | + return new EqualsTopicFilter(filter); | |
47 | + } | |
48 | + }); | |
49 | + } | |
50 | + | |
51 | + public static TcpTopicFilter getDefaultTelemetryFilter() { | |
52 | + return DEFAULT_TELEMETRY_TOPIC_FILTER; | |
53 | + } | |
54 | + | |
55 | + public static TcpTopicFilter getDefaultAttributesFilter() { | |
56 | + return DEFAULT_ATTRIBUTES_TOPIC_FILTER; | |
57 | + } | |
58 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/RegexTopicFilter.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.util; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +import java.util.regex.Pattern; | |
21 | + | |
22 | +@Data | |
23 | +public class RegexTopicFilter implements TcpTopicFilter { | |
24 | + | |
25 | + private final Pattern regex; | |
26 | + | |
27 | + public RegexTopicFilter(String regex) { | |
28 | + this.regex = Pattern.compile(regex); | |
29 | + } | |
30 | + | |
31 | + @Override | |
32 | + public boolean filter(String topic) { | |
33 | + return regex.matcher(topic).matches(); | |
34 | + } | |
35 | +} | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/util/TcpTopicFilter.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.transport.tcp.util; | |
17 | + | |
18 | +public interface TcpTopicFilter { | |
19 | + | |
20 | + boolean filter(String topic); | |
21 | + | |
22 | +} | ... | ... |
... | ... | @@ -137,6 +137,11 @@ |
137 | 137 | <groupId>org.bouncycastle</groupId> |
138 | 138 | <artifactId>bcpkix-jdk15on</artifactId> |
139 | 139 | </dependency> |
140 | + | |
141 | + <dependency> | |
142 | + <groupId>org.javadelight</groupId> | |
143 | + <artifactId>delight-nashorn-sandbox</artifactId> | |
144 | + </dependency> | |
140 | 145 | </dependencies> |
141 | 146 | |
142 | 147 | <build> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import com.google.common.util.concurrent.FutureCallback; | |
19 | +import com.google.common.util.concurrent.Futures; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import com.google.common.util.concurrent.MoreExecutors; | |
22 | +import delight.nashornsandbox.NashornSandbox; | |
23 | +import delight.nashornsandbox.NashornSandboxes; | |
24 | +import lombok.Getter; | |
25 | +import lombok.extern.slf4j.Slf4j; | |
26 | +import org.springframework.beans.factory.annotation.Value; | |
27 | +import org.springframework.scheduling.annotation.Scheduled; | |
28 | +import org.thingsboard.common.util.ThingsBoardExecutors; | |
29 | +import org.thingsboard.server.common.yunteng.script.AbstractYtScriptInvokeService; | |
30 | +import org.thingsboard.server.common.yunteng.script.YtScriptExecutorService; | |
31 | +import org.thingsboard.server.common.yunteng.script.YtScriptStatCallback; | |
32 | +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; | |
33 | + | |
34 | +import javax.annotation.PostConstruct; | |
35 | +import javax.annotation.PreDestroy; | |
36 | +import javax.script.Invocable; | |
37 | +import javax.script.ScriptEngine; | |
38 | +import javax.script.ScriptEngineManager; | |
39 | +import javax.script.ScriptException; | |
40 | +import java.util.UUID; | |
41 | +import java.util.concurrent.ExecutionException; | |
42 | +import java.util.concurrent.ExecutorService; | |
43 | +import java.util.concurrent.TimeUnit; | |
44 | +import java.util.concurrent.atomic.AtomicInteger; | |
45 | +import java.util.concurrent.locks.ReentrantLock; | |
46 | + | |
47 | +@Slf4j | |
48 | +public abstract class AbstractNashornYtScriptInvokeService extends AbstractYtScriptInvokeService { | |
49 | + | |
50 | + private NashornSandbox sandbox; | |
51 | + private ScriptEngine engine; | |
52 | + private ExecutorService monitorExecutorService; | |
53 | + | |
54 | + private final AtomicInteger jsPushedMsgs = new AtomicInteger(0); | |
55 | + private final AtomicInteger jsInvokeMsgs = new AtomicInteger(0); | |
56 | + private final AtomicInteger jsEvalMsgs = new AtomicInteger(0); | |
57 | + private final AtomicInteger jsFailedMsgs = new AtomicInteger(0); | |
58 | + private final AtomicInteger jsTimeoutMsgs = new AtomicInteger(0); | |
59 | + private final FutureCallback<UUID> evalCallback = new YtScriptStatCallback<>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs); | |
60 | + private final FutureCallback<Object> invokeCallback = new YtScriptStatCallback<>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs); | |
61 | + | |
62 | + private final ReentrantLock evalLock = new ReentrantLock(); | |
63 | + | |
64 | + @Getter | |
65 | + private final YtScriptExecutorService jsExecutor; | |
66 | + | |
67 | + @Value("${js.local.max_requests_timeout:0}") | |
68 | + private long maxRequestsTimeout; | |
69 | + | |
70 | + @Value("${js.local.stats.enabled:false}") | |
71 | + private boolean statsEnabled; | |
72 | + | |
73 | + public AbstractNashornYtScriptInvokeService(TbApiUsageClient apiUsageClient, YtScriptExecutorService jsExecutor) { | |
74 | + super(); | |
75 | + this.jsExecutor = jsExecutor; | |
76 | + } | |
77 | + | |
78 | + @Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}") | |
79 | + public void printStats() { | |
80 | + if (statsEnabled) { | |
81 | + int pushedMsgs = jsPushedMsgs.getAndSet(0); | |
82 | + int invokeMsgs = jsInvokeMsgs.getAndSet(0); | |
83 | + int evalMsgs = jsEvalMsgs.getAndSet(0); | |
84 | + int failed = jsFailedMsgs.getAndSet(0); | |
85 | + int timedOut = jsTimeoutMsgs.getAndSet(0); | |
86 | + if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { | |
87 | + log.info("Nashorn JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", | |
88 | + pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); | |
89 | + } | |
90 | + } | |
91 | + } | |
92 | + | |
93 | + @PostConstruct | |
94 | + public void init() { | |
95 | + super.init(maxRequestsTimeout); | |
96 | + if (useJsSandbox()) { | |
97 | + sandbox = NashornSandboxes.create(); | |
98 | + monitorExecutorService = ThingsBoardExecutors.newWorkStealingPool(getMonitorThreadPoolSize(), "nashorn-js-monitor"); | |
99 | + sandbox.setExecutor(monitorExecutorService); | |
100 | + sandbox.setMaxCPUTime(getMaxCpuTime()); | |
101 | + sandbox.allowNoBraces(false); | |
102 | + sandbox.allowLoadFunctions(true); | |
103 | + sandbox.setMaxPreparedStatements(30); | |
104 | + } else { | |
105 | + ScriptEngineManager factory = new ScriptEngineManager(); | |
106 | + engine = factory.getEngineByName("nashorn"); | |
107 | + } | |
108 | + } | |
109 | + | |
110 | + @PreDestroy | |
111 | + public void stop() { | |
112 | + super.stop(); | |
113 | + if (monitorExecutorService != null) { | |
114 | + monitorExecutorService.shutdownNow(); | |
115 | + } | |
116 | + } | |
117 | + | |
118 | + protected abstract boolean useJsSandbox(); | |
119 | + | |
120 | + protected abstract int getMonitorThreadPoolSize(); | |
121 | + | |
122 | + protected abstract long getMaxCpuTime(); | |
123 | + | |
124 | + @Override | |
125 | + protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) { | |
126 | + jsPushedMsgs.incrementAndGet(); | |
127 | + ListenableFuture<UUID> result = jsExecutor.executeAsync(() -> { | |
128 | + try { | |
129 | + evalLock.lock(); | |
130 | + try { | |
131 | + if (useJsSandbox()) { | |
132 | + sandbox.eval(jsScript); | |
133 | + } else { | |
134 | + engine.eval(jsScript); | |
135 | + } | |
136 | + } finally { | |
137 | + evalLock.unlock(); | |
138 | + } | |
139 | + scriptIdToNameMap.put(scriptId, functionName); | |
140 | + return scriptId; | |
141 | + } catch (Exception e) { | |
142 | + log.debug("Failed to compile JS script: {}", e.getMessage(), e); | |
143 | + throw new ExecutionException(e); | |
144 | + } | |
145 | + }); | |
146 | + if (maxRequestsTimeout > 0) { | |
147 | + result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); | |
148 | + } | |
149 | + Futures.addCallback(result, evalCallback, MoreExecutors.directExecutor()); | |
150 | + return result; | |
151 | + } | |
152 | + | |
153 | + @Override | |
154 | + protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { | |
155 | + jsPushedMsgs.incrementAndGet(); | |
156 | + ListenableFuture<Object> result = jsExecutor.executeAsync(() -> { | |
157 | + try { | |
158 | + if (useJsSandbox()) { | |
159 | + return sandbox.getSandboxedInvocable().invokeFunction(functionName, args); | |
160 | + } else { | |
161 | + return ((Invocable) engine).invokeFunction(functionName, args); | |
162 | + } | |
163 | + } catch (Exception e) { | |
164 | + onScriptExecutionError(scriptId, e, functionName); | |
165 | + throw new ExecutionException(e); | |
166 | + } | |
167 | + }); | |
168 | + | |
169 | + if (maxRequestsTimeout > 0) { | |
170 | + result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); | |
171 | + } | |
172 | + Futures.addCallback(result, invokeCallback, MoreExecutors.directExecutor()); | |
173 | + return result; | |
174 | + } | |
175 | + | |
176 | + protected void doRelease(UUID scriptId, String functionName) throws ScriptException { | |
177 | + if (useJsSandbox()) { | |
178 | + sandbox.eval(functionName + " = undefined;"); | |
179 | + } else { | |
180 | + engine.eval(functionName + " = undefined;"); | |
181 | + } | |
182 | + } | |
183 | + | |
184 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import com.google.common.util.concurrent.Futures; | |
19 | +import com.google.common.util.concurrent.ListenableFuture; | |
20 | +import lombok.extern.slf4j.Slf4j; | |
21 | +import org.thingsboard.common.util.ThingsBoardThreadFactory; | |
22 | + | |
23 | +import java.util.Map; | |
24 | +import java.util.UUID; | |
25 | +import java.util.concurrent.ConcurrentHashMap; | |
26 | +import java.util.concurrent.Executors; | |
27 | +import java.util.concurrent.ScheduledExecutorService; | |
28 | +import java.util.concurrent.atomic.AtomicInteger; | |
29 | + | |
30 | +/** | |
31 | + * Created by ashvayka on 26.09.18. | |
32 | + */ | |
33 | +@Slf4j | |
34 | +public abstract class AbstractYtScriptInvokeService implements YtScriptInvokeService { | |
35 | + | |
36 | + | |
37 | + | |
38 | + protected ScheduledExecutorService timeoutExecutorService; | |
39 | + protected Map<UUID, String> scriptIdToNameMap = new ConcurrentHashMap<>(); | |
40 | + protected Map<UUID, DisableListInfo> disabledFunctions = new ConcurrentHashMap<>(); | |
41 | + | |
42 | + | |
43 | + | |
44 | + public void init(long maxRequestsTimeout) { | |
45 | + if (maxRequestsTimeout > 0) { | |
46 | + timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout")); | |
47 | + } | |
48 | + } | |
49 | + | |
50 | + public void stop() { | |
51 | + if (timeoutExecutorService != null) { | |
52 | + timeoutExecutorService.shutdownNow(); | |
53 | + } | |
54 | + } | |
55 | + | |
56 | + @Override | |
57 | + public ListenableFuture<UUID> eval(YtScriptType scriptType, String scriptBody, String... argNames) { | |
58 | + UUID scriptId = UUID.randomUUID(); | |
59 | + String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); | |
60 | + String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); | |
61 | + return doEval(scriptId, functionName, jsScript); | |
62 | + } | |
63 | + | |
64 | + @Override | |
65 | + public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) { | |
66 | + String functionName = scriptIdToNameMap.get(scriptId); | |
67 | + if (functionName == null) { | |
68 | + return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); | |
69 | + } | |
70 | + if (!isDisabled(scriptId)) { | |
71 | + return doInvokeFunction(scriptId, functionName, args); | |
72 | + } else { | |
73 | + String message = "Script invocation is blocked due to maximum error count " | |
74 | + + getMaxErrors() + ", scriptId " + scriptId + "!"; | |
75 | + log.warn(message); | |
76 | + return Futures.immediateFailedFuture(new RuntimeException(message)); | |
77 | + } | |
78 | + } | |
79 | + | |
80 | + @Override | |
81 | + public ListenableFuture<Void> release(UUID scriptId) { | |
82 | + String functionName = scriptIdToNameMap.get(scriptId); | |
83 | + if (functionName != null) { | |
84 | + try { | |
85 | + scriptIdToNameMap.remove(scriptId); | |
86 | + disabledFunctions.remove(scriptId); | |
87 | + doRelease(scriptId, functionName); | |
88 | + } catch (Exception e) { | |
89 | + return Futures.immediateFailedFuture(e); | |
90 | + } | |
91 | + } | |
92 | + return Futures.immediateFuture(null); | |
93 | + } | |
94 | + | |
95 | + protected abstract ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String scriptBody); | |
96 | + | |
97 | + protected abstract ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args); | |
98 | + | |
99 | + protected abstract void doRelease(UUID scriptId, String functionName) throws Exception; | |
100 | + | |
101 | + protected abstract int getMaxErrors(); | |
102 | + | |
103 | + protected abstract long getMaxBlacklistDuration(); | |
104 | + | |
105 | + protected void onScriptExecutionError(UUID scriptId, Throwable t, String scriptBody) { | |
106 | + DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()); | |
107 | + log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}", | |
108 | + disableListInfo.get(), scriptId, t, t.getCause(), scriptBody); | |
109 | + disableListInfo.incrementAndGet(); | |
110 | + } | |
111 | + | |
112 | + private String generateJsScript(YtScriptType scriptType, String functionName, String scriptBody, String... argNames) { | |
113 | + if (scriptType == YtScriptType.TCP_TRANSPORT_SCRIPT) { | |
114 | + return YtScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames); | |
115 | + | |
116 | + } | |
117 | + throw new RuntimeException("No script factory implemented for scriptType: " + scriptType); | |
118 | + } | |
119 | + | |
120 | + private boolean isDisabled(UUID scriptId) { | |
121 | + DisableListInfo errorCount = disabledFunctions.get(scriptId); | |
122 | + if (errorCount != null) { | |
123 | + if (errorCount.getExpirationTime() <= System.currentTimeMillis()) { | |
124 | + disabledFunctions.remove(scriptId); | |
125 | + return false; | |
126 | + } else { | |
127 | + return errorCount.get() >= getMaxErrors(); | |
128 | + } | |
129 | + } else { | |
130 | + return false; | |
131 | + } | |
132 | + } | |
133 | + | |
134 | + private class DisableListInfo { | |
135 | + private final AtomicInteger counter; | |
136 | + private long expirationTime; | |
137 | + | |
138 | + private DisableListInfo() { | |
139 | + this.counter = new AtomicInteger(0); | |
140 | + } | |
141 | + | |
142 | + public int get() { | |
143 | + return counter.get(); | |
144 | + } | |
145 | + | |
146 | + public int incrementAndGet() { | |
147 | + int result = counter.incrementAndGet(); | |
148 | + expirationTime = System.currentTimeMillis() + getMaxBlacklistDuration(); | |
149 | + return result; | |
150 | + } | |
151 | + | |
152 | + public long getExpirationTime() { | |
153 | + return expirationTime; | |
154 | + } | |
155 | + } | |
156 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.springframework.beans.factory.annotation.Value; | |
20 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |
21 | +import org.springframework.stereotype.Service; | |
22 | +import org.thingsboard.server.common.yunteng.script.AbstractNashornYtScriptInvokeService; | |
23 | +import org.thingsboard.server.common.yunteng.script.YtScriptExecutorService; | |
24 | +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; | |
25 | + | |
26 | +import java.util.concurrent.TimeUnit; | |
27 | + | |
28 | +@Slf4j | |
29 | +@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "local", matchIfMissing = true) | |
30 | +@Service | |
31 | +public class | |
32 | +NashornYtScriptInvokeService extends AbstractNashornYtScriptInvokeService { | |
33 | + | |
34 | + @Value("${js.local.use_js_sandbox}") | |
35 | + private boolean useJsSandbox; | |
36 | + | |
37 | + @Value("${js.local.monitor_thread_pool_size}") | |
38 | + private int monitorThreadPoolSize; | |
39 | + | |
40 | + @Value("${js.local.max_cpu_time}") | |
41 | + private long maxCpuTime; | |
42 | + | |
43 | + @Value("${js.local.max_errors}") | |
44 | + private int maxErrors; | |
45 | + | |
46 | + @Value("${js.local.max_black_list_duration_sec:60}") | |
47 | + private int maxBlackListDurationSec; | |
48 | + | |
49 | + public NashornYtScriptInvokeService(TbApiUsageClient apiUsageClient, YtScriptExecutorService jsExecutor) { | |
50 | + super(apiUsageClient, jsExecutor); | |
51 | + } | |
52 | + | |
53 | + @Override | |
54 | + protected boolean useJsSandbox() { | |
55 | + return useJsSandbox; | |
56 | + } | |
57 | + | |
58 | + @Override | |
59 | + protected int getMonitorThreadPoolSize() { | |
60 | + return monitorThreadPoolSize; | |
61 | + } | |
62 | + | |
63 | + @Override | |
64 | + protected long getMaxCpuTime() { | |
65 | + return maxCpuTime; | |
66 | + } | |
67 | + | |
68 | + @Override | |
69 | + protected int getMaxErrors() { | |
70 | + return maxErrors; | |
71 | + } | |
72 | + | |
73 | + @Override | |
74 | + protected long getMaxBlacklistDuration() { | |
75 | + return TimeUnit.SECONDS.toMillis(maxBlackListDurationSec); | |
76 | + } | |
77 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import com.google.protobuf.InvalidProtocolBufferException; | |
19 | +import com.google.protobuf.util.JsonFormat; | |
20 | +import org.thingsboard.server.gen.js.JsInvokeProtos; | |
21 | +import org.thingsboard.server.queue.common.TbProtoQueueMsg; | |
22 | +import org.thingsboard.server.queue.kafka.TbKafkaEncoder; | |
23 | + | |
24 | +import java.nio.charset.StandardCharsets; | |
25 | + | |
26 | +/** | |
27 | + * Created by ashvayka on 25.09.18. | |
28 | + */ | |
29 | +public class RemoteJsRequestEncoder implements TbKafkaEncoder<TbProtoQueueMsg<JsInvokeProtos.RemoteJsRequest>> { | |
30 | + @Override | |
31 | + public byte[] encode(TbProtoQueueMsg<JsInvokeProtos.RemoteJsRequest> value) { | |
32 | + try { | |
33 | + return JsonFormat.printer().print(value.getValue()).getBytes(StandardCharsets.UTF_8); | |
34 | + } catch (InvalidProtocolBufferException e) { | |
35 | + throw new RuntimeException(e); | |
36 | + } | |
37 | + } | |
38 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import com.google.protobuf.util.JsonFormat; | |
19 | +import org.thingsboard.server.gen.js.JsInvokeProtos; | |
20 | +import org.thingsboard.server.queue.TbQueueMsg; | |
21 | +import org.thingsboard.server.queue.common.TbProtoQueueMsg; | |
22 | +import org.thingsboard.server.queue.kafka.TbKafkaDecoder; | |
23 | + | |
24 | +import java.io.IOException; | |
25 | +import java.nio.charset.StandardCharsets; | |
26 | + | |
27 | +/** | |
28 | + * Created by ashvayka on 25.09.18. | |
29 | + */ | |
30 | +public class RemoteJsResponseDecoder implements TbKafkaDecoder<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> { | |
31 | + | |
32 | + @Override | |
33 | + public TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> decode(TbQueueMsg msg) throws IOException { | |
34 | + JsInvokeProtos.RemoteJsResponse.Builder builder = JsInvokeProtos.RemoteJsResponse.newBuilder(); | |
35 | + JsonFormat.parser().ignoringUnknownFields().merge(new String(msg.getData(), StandardCharsets.UTF_8), builder); | |
36 | + return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders()); | |
37 | + } | |
38 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import com.google.common.util.concurrent.FutureCallback; | |
19 | +import com.google.common.util.concurrent.Futures; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import lombok.Getter; | |
22 | +import lombok.extern.slf4j.Slf4j; | |
23 | +import org.springframework.beans.factory.annotation.Autowired; | |
24 | +import org.springframework.beans.factory.annotation.Value; | |
25 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; | |
26 | +import org.springframework.scheduling.annotation.Scheduled; | |
27 | +import org.springframework.stereotype.Service; | |
28 | +import org.springframework.util.StopWatch; | |
29 | +import org.thingsboard.common.util.ThingsBoardThreadFactory; | |
30 | +import org.thingsboard.server.gen.js.JsInvokeProtos; | |
31 | +import org.thingsboard.server.queue.TbQueueRequestTemplate; | |
32 | +import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; | |
33 | +import org.thingsboard.server.queue.common.TbProtoQueueMsg; | |
34 | +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; | |
35 | + | |
36 | +import javax.annotation.Nullable; | |
37 | +import javax.annotation.PostConstruct; | |
38 | +import javax.annotation.PreDestroy; | |
39 | +import java.util.Map; | |
40 | +import java.util.UUID; | |
41 | +import java.util.concurrent.*; | |
42 | +import java.util.concurrent.atomic.AtomicInteger; | |
43 | + | |
44 | +@Slf4j | |
45 | +@ConditionalOnExpression("'${js.evaluator:null}'=='remote' && ('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core' || '${service.type:null}'=='tb-transport')") | |
46 | +@Service | |
47 | +public class YtRemoteJsInvokeService extends AbstractYtScriptInvokeService { | |
48 | + | |
49 | + @Value("${queue.js.max_eval_requests_timeout}") | |
50 | + private long maxEvalRequestsTimeout; | |
51 | + | |
52 | + @Value("${queue.js.max_requests_timeout}") | |
53 | + private long maxRequestsTimeout; | |
54 | + | |
55 | + @Getter | |
56 | + @Value("${js.remote.max_errors}") | |
57 | + private int maxErrors; | |
58 | + | |
59 | + @Value("${js.remote.max_black_list_duration_sec:60}") | |
60 | + private int maxBlackListDurationSec; | |
61 | + | |
62 | + @Value("${js.remote.stats.enabled:false}") | |
63 | + private boolean statsEnabled; | |
64 | + | |
65 | + private final AtomicInteger queuePushedMsgs = new AtomicInteger(0); | |
66 | + private final AtomicInteger queueInvokeMsgs = new AtomicInteger(0); | |
67 | + private final AtomicInteger queueEvalMsgs = new AtomicInteger(0); | |
68 | + private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); | |
69 | + private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); | |
70 | + private final ExecutorService callbackExecutor = Executors.newFixedThreadPool( | |
71 | + Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("js-executor-remote-callback")); | |
72 | + | |
73 | + public YtRemoteJsInvokeService(TbApiUsageClient apiUsageClient) { | |
74 | + super(); | |
75 | + } | |
76 | + | |
77 | + @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") | |
78 | + public void printStats() { | |
79 | + if (statsEnabled) { | |
80 | + int pushedMsgs = queuePushedMsgs.getAndSet(0); | |
81 | + int invokeMsgs = queueInvokeMsgs.getAndSet(0); | |
82 | + int evalMsgs = queueEvalMsgs.getAndSet(0); | |
83 | + int failed = queueFailedMsgs.getAndSet(0); | |
84 | + int timedOut = queueTimeoutMsgs.getAndSet(0); | |
85 | + if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { | |
86 | + log.info("Queue JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", | |
87 | + pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); | |
88 | + } | |
89 | + } | |
90 | + } | |
91 | + | |
92 | + @Autowired | |
93 | + private TbQueueRequestTemplate<TbProtoJsQueueMsg<JsInvokeProtos.RemoteJsRequest>, TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> requestTemplate; | |
94 | + | |
95 | + private Map<UUID, String> scriptIdToBodysMap = new ConcurrentHashMap<>(); | |
96 | + | |
97 | + @PostConstruct | |
98 | + public void init() { | |
99 | + super.init(maxRequestsTimeout); | |
100 | + requestTemplate.init(); | |
101 | + } | |
102 | + | |
103 | + @PreDestroy | |
104 | + public void destroy() { | |
105 | + super.stop(); | |
106 | + if (requestTemplate != null) { | |
107 | + requestTemplate.stop(); | |
108 | + } | |
109 | + } | |
110 | + | |
111 | + @Override | |
112 | + protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String scriptBody) { | |
113 | + JsInvokeProtos.JsCompileRequest jsRequest = JsInvokeProtos.JsCompileRequest.newBuilder() | |
114 | + .setScriptIdMSB(scriptId.getMostSignificantBits()) | |
115 | + .setScriptIdLSB(scriptId.getLeastSignificantBits()) | |
116 | + .setFunctionName(functionName) | |
117 | + .setScriptBody(scriptBody).build(); | |
118 | + | |
119 | + JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() | |
120 | + .setCompileRequest(jsRequest) | |
121 | + .build(); | |
122 | + | |
123 | + log.trace("Post compile request for scriptId [{}]", scriptId); | |
124 | + ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); | |
125 | + if (maxEvalRequestsTimeout > 0) { | |
126 | + future = Futures.withTimeout(future, maxEvalRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); | |
127 | + } | |
128 | + queuePushedMsgs.incrementAndGet(); | |
129 | + Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() { | |
130 | + @Override | |
131 | + public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) { | |
132 | + queueEvalMsgs.incrementAndGet(); | |
133 | + } | |
134 | + | |
135 | + @Override | |
136 | + public void onFailure(Throwable t) { | |
137 | + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { | |
138 | + queueTimeoutMsgs.incrementAndGet(); | |
139 | + } | |
140 | + queueFailedMsgs.incrementAndGet(); | |
141 | + } | |
142 | + }, callbackExecutor); | |
143 | + return Futures.transform(future, response -> { | |
144 | + JsInvokeProtos.JsCompileResponse compilationResult = response.getValue().getCompileResponse(); | |
145 | + UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); | |
146 | + if (compilationResult.getSuccess()) { | |
147 | + scriptIdToNameMap.put(scriptId, functionName); | |
148 | + scriptIdToBodysMap.put(scriptId, scriptBody); | |
149 | + return compiledScriptId; | |
150 | + } else { | |
151 | + log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails()); | |
152 | + throw new RuntimeException(compilationResult.getErrorDetails()); | |
153 | + } | |
154 | + }, callbackExecutor); | |
155 | + } | |
156 | + | |
157 | + @Override | |
158 | + protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { | |
159 | + log.trace("doInvokeFunction js-request for uuid {} with timeout {}ms", scriptId, maxRequestsTimeout); | |
160 | + final String scriptBody = scriptIdToBodysMap.get(scriptId); | |
161 | + if (scriptBody == null) { | |
162 | + return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!")); | |
163 | + } | |
164 | + JsInvokeProtos.JsInvokeRequest.Builder jsRequestBuilder = JsInvokeProtos.JsInvokeRequest.newBuilder() | |
165 | + .setScriptIdMSB(scriptId.getMostSignificantBits()) | |
166 | + .setScriptIdLSB(scriptId.getLeastSignificantBits()) | |
167 | + .setFunctionName(functionName) | |
168 | + .setTimeout((int) maxRequestsTimeout) | |
169 | + .setScriptBody(scriptBody); | |
170 | + | |
171 | + for (Object arg : args) { | |
172 | + jsRequestBuilder.addArgs(arg.toString()); | |
173 | + } | |
174 | + | |
175 | + JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() | |
176 | + .setInvokeRequest(jsRequestBuilder.build()) | |
177 | + .build(); | |
178 | + | |
179 | + StopWatch stopWatch = new StopWatch(); | |
180 | + stopWatch.start(); | |
181 | + | |
182 | + ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); | |
183 | + if (maxRequestsTimeout > 0) { | |
184 | + future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); | |
185 | + } | |
186 | + queuePushedMsgs.incrementAndGet(); | |
187 | + Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() { | |
188 | + @Override | |
189 | + public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) { | |
190 | + queueInvokeMsgs.incrementAndGet(); | |
191 | + } | |
192 | + | |
193 | + @Override | |
194 | + public void onFailure(Throwable t) { | |
195 | + onScriptExecutionError(scriptId, t, scriptBody); | |
196 | + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { | |
197 | + queueTimeoutMsgs.incrementAndGet(); | |
198 | + } | |
199 | + queueFailedMsgs.incrementAndGet(); | |
200 | + } | |
201 | + }, callbackExecutor); | |
202 | + return Futures.transform(future, response -> { | |
203 | + stopWatch.stop(); | |
204 | + log.trace("doInvokeFunction js-response took {}ms for uuid {}", stopWatch.getTotalTimeMillis(), response.getKey()); | |
205 | + JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse(); | |
206 | + if (invokeResult.getSuccess()) { | |
207 | + return invokeResult.getResult(); | |
208 | + } else { | |
209 | + final RuntimeException e = new RuntimeException(invokeResult.getErrorDetails()); | |
210 | + onScriptExecutionError(scriptId, e, scriptBody); | |
211 | + log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); | |
212 | + throw e; | |
213 | + } | |
214 | + }, callbackExecutor); | |
215 | + } | |
216 | + | |
217 | + @Override | |
218 | + protected void doRelease(UUID scriptId, String functionName) throws Exception { | |
219 | + JsInvokeProtos.JsReleaseRequest jsRequest = JsInvokeProtos.JsReleaseRequest.newBuilder() | |
220 | + .setScriptIdMSB(scriptId.getMostSignificantBits()) | |
221 | + .setScriptIdLSB(scriptId.getLeastSignificantBits()) | |
222 | + .setFunctionName(functionName).build(); | |
223 | + | |
224 | + JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() | |
225 | + .setReleaseRequest(jsRequest) | |
226 | + .build(); | |
227 | + | |
228 | + ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); | |
229 | + if (maxRequestsTimeout > 0) { | |
230 | + future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); | |
231 | + } | |
232 | + JsInvokeProtos.RemoteJsResponse response = future.get().getValue(); | |
233 | + | |
234 | + JsInvokeProtos.JsReleaseResponse compilationResult = response.getReleaseResponse(); | |
235 | + UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); | |
236 | + if (compilationResult.getSuccess()) { | |
237 | + scriptIdToBodysMap.remove(scriptId); | |
238 | + } else { | |
239 | + log.debug("[{}] Failed to release script due", compiledScriptId); | |
240 | + } | |
241 | + } | |
242 | + | |
243 | + @Override | |
244 | + protected long getMaxBlacklistDuration() { | |
245 | + return TimeUnit.SECONDS.toMillis(maxBlackListDurationSec); | |
246 | + } | |
247 | + | |
248 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import org.springframework.beans.factory.annotation.Value; | |
19 | +import org.springframework.stereotype.Component; | |
20 | +import org.thingsboard.common.util.AbstractListeningExecutor; | |
21 | + | |
22 | +@Component | |
23 | +public class YtScriptExecutorService extends AbstractListeningExecutor { | |
24 | + | |
25 | + @Value("${actors.rule.js_thread_pool_size}") | |
26 | + private int jsExecutorThreadPoolSize; | |
27 | + | |
28 | + @Override | |
29 | + protected int getThreadPollSize() { | |
30 | + return Math.max(jsExecutorThreadPoolSize, 1); | |
31 | + } | |
32 | + | |
33 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +public class YtScriptFactory { | |
19 | + | |
20 | + public static final String MSG = "params"; | |
21 | + | |
22 | + public static final String RULE_NODE_FUNCTION_NAME = "tcpTransportFunc"; | |
23 | + | |
24 | + private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(params) { " + | |
25 | + " var out = new Object(); " + | |
26 | + " return JSON.stringify(%s(params));" + | |
27 | + " function %s(%s) {"; | |
28 | + private static final String JS_WRAPPER_SUFFIX = "return out; \n }" + | |
29 | + "\n}"; | |
30 | + | |
31 | + | |
32 | + public static String generateRuleNodeScript(String functionName, String scriptBody, String... argNames) { | |
33 | + String msgArg; | |
34 | + | |
35 | + msgArg = (argNames != null && argNames.length >= 1) ? argNames[0]:MSG; | |
36 | + | |
37 | + String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE | |
38 | + , functionName | |
39 | + ,RULE_NODE_FUNCTION_NAME | |
40 | + , RULE_NODE_FUNCTION_NAME, msgArg); | |
41 | + String result = jsWrapperPrefix + scriptBody + JS_WRAPPER_SUFFIX; | |
42 | + return result; | |
43 | + } | |
44 | + | |
45 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import java.util.List; | |
19 | + | |
20 | +/** | |
21 | + * Created by ashvayka on 25.09.18. | |
22 | + */ | |
23 | +public class YtScriptInvokeRequest { | |
24 | + | |
25 | + private String scriptId; | |
26 | + private String scriptBody; | |
27 | + private List<String> args; | |
28 | + | |
29 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import java.util.List; | |
19 | + | |
20 | +/** | |
21 | + * Created by ashvayka on 25.09.18. | |
22 | + */ | |
23 | +public class YtScriptInvokeResponse { | |
24 | + | |
25 | + private String scriptId; | |
26 | + private String scriptBody; | |
27 | + private List<String> args; | |
28 | + | |
29 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | + | |
20 | +import java.util.UUID; | |
21 | + | |
22 | +public interface YtScriptInvokeService { | |
23 | + | |
24 | + ListenableFuture<UUID> eval(YtScriptType scriptType, String scriptBody, String... argNames); | |
25 | + | |
26 | + ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args); | |
27 | + | |
28 | + ListenableFuture<Void> release(UUID scriptId); | |
29 | + | |
30 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +import com.google.common.util.concurrent.FutureCallback; | |
19 | +import lombok.AllArgsConstructor; | |
20 | + | |
21 | +import javax.annotation.Nullable; | |
22 | +import java.util.concurrent.TimeoutException; | |
23 | +import java.util.concurrent.atomic.AtomicInteger; | |
24 | + | |
25 | +@AllArgsConstructor | |
26 | +public class YtScriptStatCallback<T> implements FutureCallback<T> { | |
27 | + | |
28 | + private final AtomicInteger jsSuccessMsgs; | |
29 | + private final AtomicInteger jsTimeoutMsgs; | |
30 | + private final AtomicInteger jsFailedMsgs; | |
31 | + | |
32 | + | |
33 | + @Override | |
34 | + public void onSuccess(@Nullable T result) { | |
35 | + jsSuccessMsgs.incrementAndGet(); | |
36 | + } | |
37 | + | |
38 | + @Override | |
39 | + public void onFailure(Throwable t) { | |
40 | + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { | |
41 | + jsTimeoutMsgs.incrementAndGet(); | |
42 | + } else { | |
43 | + jsFailedMsgs.incrementAndGet(); | |
44 | + } | |
45 | + } | |
46 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.yunteng.script; | |
17 | + | |
18 | +public enum YtScriptType { | |
19 | + TCP_TRANSPORT_SCRIPT | |
20 | +} | ... | ... |
... | ... | @@ -31,29 +31,12 @@ import org.springframework.transaction.annotation.Transactional; |
31 | 31 | import org.springframework.util.CollectionUtils; |
32 | 32 | import org.springframework.util.StringUtils; |
33 | 33 | import org.thingsboard.common.util.JacksonUtil; |
34 | -import org.thingsboard.server.common.data.Device; | |
35 | -import org.thingsboard.server.common.data.DeviceInfo; | |
36 | -import org.thingsboard.server.common.data.DeviceProfile; | |
37 | -import org.thingsboard.server.common.data.DeviceTransportType; | |
38 | -import org.thingsboard.server.common.data.EntitySubtype; | |
39 | -import org.thingsboard.server.common.data.EntityType; | |
40 | -import org.thingsboard.server.common.data.EntityView; | |
34 | +import org.thingsboard.server.common.data.*; | |
41 | 35 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
42 | 36 | import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; |
43 | -import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; | |
44 | -import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; | |
45 | -import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; | |
46 | -import org.thingsboard.server.common.data.device.data.DeviceData; | |
47 | -import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; | |
48 | -import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; | |
49 | -import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; | |
37 | +import org.thingsboard.server.common.data.device.data.*; | |
50 | 38 | import org.thingsboard.server.common.data.edge.Edge; |
51 | -import org.thingsboard.server.common.data.id.CustomerId; | |
52 | -import org.thingsboard.server.common.data.id.DeviceId; | |
53 | -import org.thingsboard.server.common.data.id.DeviceProfileId; | |
54 | -import org.thingsboard.server.common.data.id.EdgeId; | |
55 | -import org.thingsboard.server.common.data.id.EntityId; | |
56 | -import org.thingsboard.server.common.data.id.TenantId; | |
39 | +import org.thingsboard.server.common.data.id.*; | |
57 | 40 | import org.thingsboard.server.common.data.ota.OtaPackageType; |
58 | 41 | import org.thingsboard.server.common.data.page.PageData; |
59 | 42 | import org.thingsboard.server.common.data.page.PageLink; |
... | ... | @@ -73,21 +56,13 @@ import org.thingsboard.server.dao.service.DataValidator; |
73 | 56 | import org.thingsboard.server.dao.service.PaginatedRemover; |
74 | 57 | |
75 | 58 | import javax.annotation.Nullable; |
76 | -import java.util.ArrayList; | |
77 | -import java.util.Collections; | |
78 | -import java.util.Comparator; | |
79 | -import java.util.List; | |
80 | -import java.util.Optional; | |
81 | -import java.util.UUID; | |
59 | +import java.util.*; | |
82 | 60 | import java.util.concurrent.ExecutionException; |
83 | 61 | import java.util.stream.Collectors; |
84 | 62 | |
85 | 63 | import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE; |
86 | 64 | import static org.thingsboard.server.dao.DaoUtil.toUUIDs; |
87 | -import static org.thingsboard.server.dao.service.Validator.validateId; | |
88 | -import static org.thingsboard.server.dao.service.Validator.validateIds; | |
89 | -import static org.thingsboard.server.dao.service.Validator.validatePageLink; | |
90 | -import static org.thingsboard.server.dao.service.Validator.validateString; | |
65 | +import static org.thingsboard.server.dao.service.Validator.*; | |
91 | 66 | |
92 | 67 | @Service |
93 | 68 | @Slf4j |
... | ... | @@ -288,6 +263,12 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
288 | 263 | case SNMP: |
289 | 264 | deviceData.setTransportConfiguration(new SnmpDeviceTransportConfiguration()); |
290 | 265 | break; |
266 | + | |
267 | + //Thingskit function | |
268 | + case TCP: | |
269 | + deviceData.setTransportConfiguration(new YtTcpDeviceTransportConfiguration()); | |
270 | + break; | |
271 | + | |
291 | 272 | } |
292 | 273 | } |
293 | 274 | return deviceData; | ... | ... |
... | ... | @@ -19,12 +19,15 @@ import com.google.common.util.concurrent.ListenableFuture; |
19 | 19 | import lombok.extern.slf4j.Slf4j; |
20 | 20 | import org.springframework.beans.factory.annotation.Autowired; |
21 | 21 | import org.springframework.stereotype.Service; |
22 | +import org.thingsboard.server.common.data.DeviceProfile; | |
22 | 23 | import org.thingsboard.server.common.data.Tenant; |
23 | 24 | import org.thingsboard.server.common.data.TenantInfo; |
24 | 25 | import org.thingsboard.server.common.data.TenantProfile; |
25 | 26 | import org.thingsboard.server.common.data.id.TenantId; |
26 | 27 | import org.thingsboard.server.common.data.page.PageData; |
27 | 28 | import org.thingsboard.server.common.data.page.PageLink; |
29 | +import org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO; | |
30 | +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | |
28 | 31 | import org.thingsboard.server.dao.asset.AssetService; |
29 | 32 | import org.thingsboard.server.dao.customer.CustomerService; |
30 | 33 | import org.thingsboard.server.dao.dashboard.DashboardService; |
... | ... | @@ -42,6 +45,7 @@ import org.thingsboard.server.dao.service.Validator; |
42 | 45 | import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; |
43 | 46 | import org.thingsboard.server.dao.user.UserService; |
44 | 47 | import org.thingsboard.server.dao.widget.WidgetsBundleService; |
48 | +import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService; | |
45 | 49 | |
46 | 50 | import static org.thingsboard.server.dao.service.Validator.validateId; |
47 | 51 | |
... | ... | @@ -99,7 +103,8 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe |
99 | 103 | |
100 | 104 | @Autowired |
101 | 105 | private DataValidator<Tenant> tenantValidator; |
102 | - | |
106 | + @Autowired | |
107 | + private YtDeviceProfileService ytDeviceProfileService; | |
103 | 108 | @Override |
104 | 109 | public Tenant findTenantById(TenantId tenantId) { |
105 | 110 | log.trace("Executing findTenantById [{}]", tenantId); |
... | ... | @@ -132,8 +137,13 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe |
132 | 137 | tenantValidator.validate(tenant, Tenant::getId); |
133 | 138 | Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); |
134 | 139 | if (tenant.getId() == null) { |
135 | - deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); | |
140 | + | |
141 | + //Thingskit function | |
142 | + DeviceProfile deviceProfile = deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); | |
143 | + DeviceProfileDTO profileDTO = new DeviceProfileDTO(deviceProfile.getName(),deviceProfile.getTenantId(),deviceProfile.getId(), DeviceTypeEnum.DIRECT_CONNECTION); | |
144 | + ytDeviceProfileService.insertOrUpdate(profileDTO); | |
136 | 145 | apiUsageStateService.createDefaultApiUsageState(savedTenant.getId(), null); |
146 | + | |
137 | 147 | } |
138 | 148 | return savedTenant; |
139 | 149 | } | ... | ... |
... | ... | @@ -8,12 +8,8 @@ import lombok.Data; |
8 | 8 | import lombok.EqualsAndHashCode; |
9 | 9 | import org.apache.ibatis.type.EnumTypeHandler; |
10 | 10 | import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; |
11 | -import org.thingsboard.server.common.data.yunteng.enums.DeviceCredentialsEnum; | |
12 | -import org.thingsboard.server.common.data.yunteng.enums.DeviceState; | |
13 | 11 | import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; |
14 | 12 | |
15 | -import java.time.LocalDateTime; | |
16 | - | |
17 | 13 | @Data |
18 | 14 | @EqualsAndHashCode(callSuper = true) |
19 | 15 | @TableName(value = ModelConstants.Table.IOTFS_DEVICE_TABLE_NAME, autoResultMap = true) | ... | ... |
1 | +package org.thingsboard.server.dao.yunteng.entities; | |
2 | + | |
3 | +import com.baomidou.mybatisplus.annotation.TableField; | |
4 | +import com.baomidou.mybatisplus.annotation.TableName; | |
5 | +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | |
6 | +import com.fasterxml.jackson.databind.JsonNode; | |
7 | +import lombok.Data; | |
8 | +import lombok.EqualsAndHashCode; | |
9 | +import org.apache.ibatis.type.EnumTypeHandler; | |
10 | +import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; | |
11 | +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | |
12 | + | |
13 | +@Data | |
14 | +@EqualsAndHashCode(callSuper = true) | |
15 | +@TableName(value = ModelConstants.Table.IOTFS_DEVICE_PROFILE_TABLE_NAME, autoResultMap = true) | |
16 | +public class YtDeviceProfileEntity extends TenantBaseEntity { | |
17 | + private String name; | |
18 | + | |
19 | + private String scriptId; | |
20 | + private String tbProfileId; | |
21 | + @TableField(typeHandler = EnumTypeHandler.class) | |
22 | + private DeviceTypeEnum deviceType; | |
23 | + private String description; | |
24 | +} | ... | ... |
1 | +package org.thingsboard.server.dao.yunteng.entities; | |
2 | + | |
3 | +import com.baomidou.mybatisplus.annotation.TableField; | |
4 | +import com.baomidou.mybatisplus.annotation.TableName; | |
5 | +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | |
6 | +import com.fasterxml.jackson.databind.JsonNode; | |
7 | +import lombok.Data; | |
8 | +import lombok.EqualsAndHashCode; | |
9 | +import org.apache.ibatis.type.EnumTypeHandler; | |
10 | +import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; | |
11 | +import org.thingsboard.server.common.data.yunteng.enums.DeviceTypeEnum; | |
12 | + | |
13 | +@Data | |
14 | +@TableName(value = ModelConstants.Table.IOTFS_DEVICE_SCRIPT_TABLE_NAME, autoResultMap = true) | |
15 | +@EqualsAndHashCode(callSuper = true) | |
16 | +public class YtDeviceScriptEntity extends TenantBaseEntity { | |
17 | + private String name; | |
18 | + private String convertJs; | |
19 | + /** | |
20 | + * 告警状态:0:正常 1:告警 | |
21 | + */ | |
22 | + private Integer status; | |
23 | + private String description; | |
24 | +} | ... | ... |
... | ... | @@ -2,6 +2,7 @@ package org.thingsboard.server.dao.yunteng.impl; |
2 | 2 | |
3 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
4 | 4 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
5 | +import com.baomidou.mybatisplus.core.metadata.IPage; | |
5 | 6 | import lombok.RequiredArgsConstructor; |
6 | 7 | import lombok.extern.slf4j.Slf4j; |
7 | 8 | import org.apache.commons.lang3.StringUtils; |
... | ... | @@ -17,30 +18,33 @@ import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; |
17 | 18 | import org.thingsboard.server.common.data.yunteng.core.cache.CacheUtils; |
18 | 19 | import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException; |
19 | 20 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
20 | -import org.thingsboard.server.common.data.yunteng.dto.AlarmProfileDTO; | |
21 | -import org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO; | |
21 | +import org.thingsboard.server.common.data.yunteng.dto.*; | |
22 | +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; | |
22 | 23 | import org.thingsboard.server.common.data.yunteng.utils.ReflectUtils; |
23 | 24 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; |
24 | 25 | import org.thingsboard.server.dao.service.Validator; |
25 | -import org.thingsboard.server.dao.yunteng.entities.Menu; | |
26 | +import org.thingsboard.server.dao.yunteng.entities.*; | |
26 | 27 | import org.thingsboard.server.dao.yunteng.jpa.dao.YtJpaDeviceProfileDao; |
27 | -import org.thingsboard.server.dao.yunteng.entities.AlarmProfile; | |
28 | -import org.thingsboard.server.dao.yunteng.entities.YtDevice; | |
29 | 28 | import org.thingsboard.server.dao.yunteng.mapper.AlarmProfileMapper; |
30 | 29 | import org.thingsboard.server.dao.yunteng.mapper.DeviceMapper; |
30 | +import org.thingsboard.server.dao.yunteng.mapper.YtDeviceProfileMapper; | |
31 | +import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; | |
31 | 32 | import org.thingsboard.server.dao.yunteng.service.YtDeviceProfileService; |
32 | 33 | |
33 | 34 | import java.util.*; |
35 | +import java.util.stream.Collectors; | |
34 | 36 | |
35 | 37 | @Service |
36 | 38 | @RequiredArgsConstructor |
37 | 39 | @Slf4j |
38 | -public class YtDeviceProfileServiceImpl implements YtDeviceProfileService { | |
40 | +public class YtDeviceProfileServiceImpl extends AbstractBaseService<YtDeviceProfileMapper, YtDeviceProfileEntity> | |
41 | + implements YtDeviceProfileService { | |
39 | 42 | |
40 | 43 | private final DeviceMapper deviceMapper; |
41 | 44 | |
42 | 45 | private final YtJpaDeviceProfileDao deviceProfileDao; |
43 | 46 | |
47 | + | |
44 | 48 | @Override |
45 | 49 | public boolean validateFormdata(DeviceProfileDTO ytDeviceProfileDTO) { |
46 | 50 | TenantId tenantId = TenantId.fromUUID(UUID.fromString(ytDeviceProfileDTO.getTenantId())); |
... | ... | @@ -63,6 +67,61 @@ public class YtDeviceProfileServiceImpl implements YtDeviceProfileService { |
63 | 67 | } |
64 | 68 | |
65 | 69 | @Override |
70 | + public DeviceProfileDTO insertOrUpdate( DeviceProfileDTO deviceDTO) { | |
71 | + if (StringUtils.isBlank(deviceDTO.getId())) { | |
72 | + return insert(deviceDTO); | |
73 | + } else { | |
74 | + return update(deviceDTO); | |
75 | + } | |
76 | + } | |
77 | + | |
78 | + private DeviceProfileDTO insert(DeviceProfileDTO deviceDTO) { | |
79 | + | |
80 | + YtDeviceProfileEntity profile = new YtDeviceProfileEntity(); | |
81 | + deviceDTO.copyToEntity( | |
82 | + profile, | |
83 | + ModelConstants.TablePropertyMapping.ACTIVE_TIME, | |
84 | + ModelConstants.TablePropertyMapping.DEVICE_STATE, | |
85 | + ModelConstants.TablePropertyMapping.CREATOR, | |
86 | + ModelConstants.TablePropertyMapping.UPDATER, | |
87 | + ModelConstants.TablePropertyMapping.CREATE_TIME, | |
88 | + ModelConstants.TablePropertyMapping.UPDATE, | |
89 | + ModelConstants.TablePropertyMapping.UPDATE_TIME); | |
90 | + | |
91 | + | |
92 | + baseMapper.insert(profile); | |
93 | + return profile.getDTO(DeviceProfileDTO.class); | |
94 | + } | |
95 | + | |
96 | + private DeviceProfileDTO update(DeviceProfileDTO deviceDTO) { | |
97 | + YtDeviceProfileEntity device = new YtDeviceProfileEntity(); | |
98 | + deviceDTO.copyToEntity( | |
99 | + device, | |
100 | + ModelConstants.TablePropertyMapping.ACTIVE_TIME, | |
101 | + ModelConstants.TablePropertyMapping.DEVICE_STATE, | |
102 | + ModelConstants.TablePropertyMapping.TB_DEVICE_ID, | |
103 | + ModelConstants.TablePropertyMapping.TENANT_CODE, | |
104 | + ModelConstants.TablePropertyMapping.CREATOR, | |
105 | + ModelConstants.TablePropertyMapping.UPDATER, | |
106 | + ModelConstants.TablePropertyMapping.CREATE_TIME, | |
107 | + ModelConstants.TablePropertyMapping.UPDATE, | |
108 | + ModelConstants.TablePropertyMapping.UPDATE_TIME); | |
109 | + baseMapper.updateById(device); | |
110 | + return device.getDTO(DeviceProfileDTO.class); | |
111 | + } | |
112 | + | |
113 | + @Override | |
114 | + public void deleteDeviceProfiles(String tenantId, Set<String> ids) { | |
115 | + LambdaQueryWrapper<YtDeviceProfileEntity> queryWrapper = | |
116 | + new QueryWrapper<YtDeviceProfileEntity>() | |
117 | + .lambda() | |
118 | + .eq(YtDeviceProfileEntity::getTenantId, tenantId) | |
119 | + .in(YtDeviceProfileEntity::getId, ids); | |
120 | + | |
121 | + baseMapper.delete(queryWrapper); | |
122 | + } | |
123 | + | |
124 | + @Override | |
66 | 125 | @Transactional |
67 | 126 | public void checkDeviceProfiles(String tenantId, Set<String> ids) { |
68 | 127 | // check if ids bind to device |
... | ... | @@ -76,28 +135,17 @@ public class YtDeviceProfileServiceImpl implements YtDeviceProfileService { |
76 | 135 | |
77 | 136 | @Override |
78 | 137 | public Optional<DeviceProfileDTO> getDeviceProfile(String tenantId, String id) { |
79 | - TenantId tenant = TenantId.fromUUID(UUID.fromString(tenantId)); | |
80 | - DeviceProfile profile = deviceProfileDao.findById(tenant, UUID.fromString(id)); | |
81 | - return Optional.ofNullable(profile) | |
82 | - .map( | |
83 | - entity -> { | |
84 | - DeviceProfileDTO result = ReflectUtils.getDeviceProfileDTO(entity); | |
85 | - return result; | |
86 | - }); | |
138 | + return Optional.ofNullable(baseMapper.selectDetail(tenantId,id)); | |
87 | 139 | } |
88 | 140 | |
89 | 141 | @Override |
90 | - public YtPageData<DeviceProfileDTO> page( | |
91 | - PageLink pageLink, String tenantIdStr, String transportType) { | |
142 | + public YtPageData<DeviceProfileDTO> page(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType | |
143 | + , String tenantIdStr, String profileName, String transportType) { | |
92 | 144 | |
93 | - TenantId tenantId = TenantId.fromUUID(UUID.fromString(tenantIdStr)); | |
94 | - Validator.validatePageLink(pageLink); | |
95 | - PageData<DeviceProfileDTO> tbDatas = | |
96 | - deviceProfileDao.findDeviceProfileInfos(pageLink, tenantId, transportType); | |
97 | - YtPageData<DeviceProfileDTO> result = | |
98 | - new YtPageData<>(tbDatas.getData(), tbDatas.getTotalElements()); | |
145 | + IPage<YtDeviceProfileEntity> currentPage = getCurrentPage(page,pageSize,orderField,orderType); | |
146 | + IPage<DeviceProfileDTO> result = baseMapper.getProfilePage(currentPage,tenantIdStr,profileName,transportType); | |
99 | 147 | |
100 | - return result; | |
148 | + return getPageData(result, DeviceProfileDTO.class); | |
101 | 149 | } |
102 | 150 | |
103 | 151 | @Override |
... | ... | @@ -107,4 +155,17 @@ public class YtDeviceProfileServiceImpl implements YtDeviceProfileService { |
107 | 155 | deviceProfileDao.findDeviceProfileByTenantId(TenantId.fromUUID(profileId)); |
108 | 156 | return results; |
109 | 157 | } |
158 | + | |
159 | + @Override | |
160 | + public List<DeviceProfileDTO> findDeviceProfile(String tenantId, String scriptId) { | |
161 | + LambdaQueryWrapper<YtDeviceProfileEntity> queryWrapper = | |
162 | + new QueryWrapper<YtDeviceProfileEntity>() | |
163 | + .lambda() | |
164 | + .eq(YtDeviceProfileEntity::getTenantId, tenantId) | |
165 | + .eq(YtDeviceProfileEntity::getScriptId, scriptId); | |
166 | + List<DeviceProfileDTO> results = baseMapper.selectList(queryWrapper).stream() | |
167 | + .map(item -> item.getDTO(DeviceProfileDTO.class)) | |
168 | + .collect(Collectors.toList()); | |
169 | + return results; | |
170 | + } | |
110 | 171 | } | ... | ... |
1 | +package org.thingsboard.server.dao.yunteng.impl; | |
2 | + | |
3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |
4 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | |
5 | +import com.baomidou.mybatisplus.core.metadata.IPage; | |
6 | +import lombok.RequiredArgsConstructor; | |
7 | +import lombok.extern.slf4j.Slf4j; | |
8 | +import org.apache.commons.lang3.StringUtils; | |
9 | +import org.springframework.stereotype.Service; | |
10 | +import org.springframework.transaction.annotation.Transactional; | |
11 | +import org.thingsboard.server.common.data.yunteng.constant.ModelConstants; | |
12 | +import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException; | |
13 | +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; | |
14 | +import org.thingsboard.server.common.data.yunteng.dto.YtDeviceScriptDTO; | |
15 | +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; | |
16 | +import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; | |
17 | +import org.thingsboard.server.dao.yunteng.entities.YtDeviceProfileEntity; | |
18 | +import org.thingsboard.server.dao.yunteng.entities.YtDeviceScriptEntity; | |
19 | +import org.thingsboard.server.dao.yunteng.mapper.YtDeviceProfileMapper; | |
20 | +import org.thingsboard.server.dao.yunteng.mapper.YtDeviceScriptMapper; | |
21 | +import org.thingsboard.server.dao.yunteng.service.AbstractBaseService; | |
22 | +import org.thingsboard.server.dao.yunteng.service.YtDeviceScriptService; | |
23 | + | |
24 | +import java.util.List; | |
25 | +import java.util.Optional; | |
26 | +import java.util.Set; | |
27 | +import java.util.stream.Collectors; | |
28 | + | |
29 | +@Service | |
30 | +@RequiredArgsConstructor | |
31 | +@Slf4j | |
32 | +public class YtDeviceScriptServiceImpl extends AbstractBaseService<YtDeviceScriptMapper, YtDeviceScriptEntity> | |
33 | + implements YtDeviceScriptService { | |
34 | + | |
35 | + | |
36 | + private final YtDeviceProfileMapper profileMapper; | |
37 | + | |
38 | + | |
39 | + @Override | |
40 | + public boolean validateFormdata(YtDeviceScriptDTO scriptDTO,boolean created) { | |
41 | + String tenantId = scriptDTO.getTenantId(); | |
42 | + if (created) { | |
43 | + // 判断数据库是否已存在名字相同的设备配置 | |
44 | + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper = | |
45 | + new QueryWrapper<YtDeviceScriptEntity>() | |
46 | + .lambda() | |
47 | + .eq(YtDeviceScriptEntity::getTenantId, tenantId) | |
48 | + .eq(YtDeviceScriptEntity::getName, scriptDTO.getName()); | |
49 | + int results = baseMapper.selectCount(queryWrapper); | |
50 | + if (results > 0) { | |
51 | + throw new YtDataValidationException(ErrorMessage.NAME_ALREADY_EXISTS.getMessage()); | |
52 | + } | |
53 | + } else { | |
54 | + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper = | |
55 | + new QueryWrapper<YtDeviceScriptEntity>() | |
56 | + .lambda() | |
57 | + .eq(YtDeviceScriptEntity::getTenantId, tenantId) | |
58 | + .and( | |
59 | + second -> | |
60 | + second | |
61 | + .eq(YtDeviceScriptEntity::getId, scriptDTO.getId()) | |
62 | + .or(f -> f.eq(YtDeviceScriptEntity::getName, scriptDTO.getName()))); | |
63 | + | |
64 | + | |
65 | + List<YtDeviceScriptEntity> results = baseMapper.selectList(queryWrapper); | |
66 | + for(YtDeviceScriptEntity item:results){ | |
67 | + if(item.getId().equals(scriptDTO.getId()) && !item.getTenantId().equals(scriptDTO.getTenantId())){ | |
68 | + throw new YtDataValidationException(ErrorMessage.TENANT_MISMATCHING.getMessage()); | |
69 | + } | |
70 | + if(!item.getId().equals(scriptDTO.getId()) && item.getName().equals(scriptDTO.getName())){ | |
71 | + throw new YtDataValidationException(ErrorMessage.NAME_ALREADY_EXISTS.getMessage()); | |
72 | + } | |
73 | + } | |
74 | + } | |
75 | + | |
76 | + return false; | |
77 | + } | |
78 | + | |
79 | + @Override | |
80 | + public String getScriptText(String tenantId, String scriptId) { | |
81 | + return null; | |
82 | + } | |
83 | + | |
84 | + @Override | |
85 | + public YtDeviceScriptDTO insertOrUpdate( YtDeviceScriptDTO deviceDTO) { | |
86 | + if (StringUtils.isBlank(deviceDTO.getId())) { | |
87 | + return insert(deviceDTO); | |
88 | + } else { | |
89 | + return update(deviceDTO); | |
90 | + } | |
91 | + } | |
92 | + | |
93 | + @Override | |
94 | + public void checkDeviceScriptes(String tenantId, Set<String> ids) { | |
95 | + // check if ids bind to device | |
96 | + List<YtDeviceProfileEntity> usedList = | |
97 | + profileMapper.selectList( | |
98 | + new QueryWrapper<YtDeviceProfileEntity>().lambda().in(YtDeviceProfileEntity::getScriptId, ids)); | |
99 | + if (usedList !=null &&usedList.size() > 0) { | |
100 | + List<String> names = usedList.stream() | |
101 | + .map(i -> i.getName()) | |
102 | + .collect(Collectors.toList()); | |
103 | + throw new YtDataValidationException(String.format(ErrorMessage.PROJECT_USED_SCRIPT.getMessage(),names)); | |
104 | + } | |
105 | + } | |
106 | + @Override | |
107 | + @Transactional | |
108 | + public void deleteScriptes(String tenantId, Set<String> ids) { | |
109 | + checkDeviceScriptes(tenantId, ids); | |
110 | + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper = | |
111 | + new QueryWrapper<YtDeviceScriptEntity>() | |
112 | + .lambda() | |
113 | + .eq(YtDeviceScriptEntity::getTenantId, tenantId) | |
114 | + .in(YtDeviceScriptEntity::getId, ids); | |
115 | + | |
116 | + baseMapper.delete(queryWrapper); | |
117 | + | |
118 | + } | |
119 | + | |
120 | + private YtDeviceScriptDTO insert(YtDeviceScriptDTO deviceDTO) { | |
121 | + | |
122 | + YtDeviceScriptEntity profile = new YtDeviceScriptEntity(); | |
123 | + deviceDTO.copyToEntity( | |
124 | + profile, | |
125 | + ModelConstants.TablePropertyMapping.ACTIVE_TIME, | |
126 | + ModelConstants.TablePropertyMapping.DEVICE_STATE, | |
127 | + ModelConstants.TablePropertyMapping.UPDATER, | |
128 | + ModelConstants.TablePropertyMapping.CREATE_TIME, | |
129 | + ModelConstants.TablePropertyMapping.UPDATE_TIME); | |
130 | + | |
131 | + baseMapper.insert(profile); | |
132 | + return profile.getDTO(YtDeviceScriptDTO.class); | |
133 | + } | |
134 | + | |
135 | + private YtDeviceScriptDTO update(YtDeviceScriptDTO deviceDTO) { | |
136 | + YtDeviceScriptEntity device = new YtDeviceScriptEntity(); | |
137 | + deviceDTO.copyToEntity( | |
138 | + device, | |
139 | + ModelConstants.TablePropertyMapping.ACTIVE_TIME, | |
140 | + ModelConstants.TablePropertyMapping.DEVICE_STATE, | |
141 | + ModelConstants.TablePropertyMapping.TB_DEVICE_ID, | |
142 | + ModelConstants.TablePropertyMapping.TENANT_CODE, | |
143 | + ModelConstants.TablePropertyMapping.CREATOR, | |
144 | + ModelConstants.TablePropertyMapping.UPDATER, | |
145 | + ModelConstants.TablePropertyMapping.CREATE_TIME, | |
146 | + ModelConstants.TablePropertyMapping.UPDATE, | |
147 | + ModelConstants.TablePropertyMapping.UPDATE_TIME); | |
148 | + baseMapper.updateById(device); | |
149 | + return device.getDTO(YtDeviceScriptDTO.class); | |
150 | + } | |
151 | + | |
152 | + | |
153 | + | |
154 | + | |
155 | + @Override | |
156 | + public Optional<YtDeviceScriptDTO> getDeviceScript(String tenantId, String id) { | |
157 | + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper = | |
158 | + new QueryWrapper<YtDeviceScriptEntity>() | |
159 | + .lambda() | |
160 | + .eq(YtDeviceScriptEntity::getTenantId, tenantId) | |
161 | + .eq(YtDeviceScriptEntity::getId, id); | |
162 | + YtDeviceScriptEntity profile = baseMapper.selectOne(queryWrapper); | |
163 | + return Optional.ofNullable(profile) | |
164 | + .map( | |
165 | + entity -> { | |
166 | + YtDeviceScriptDTO result = entity.getDTO(YtDeviceScriptDTO.class); | |
167 | + return result; | |
168 | + }); | |
169 | + } | |
170 | + | |
171 | + @Override | |
172 | + public YtPageData<YtDeviceScriptDTO> page(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType,String tenantId) { | |
173 | + IPage<YtDeviceScriptEntity> currentPage = getCurrentPage(page,pageSize,orderField,orderType); | |
174 | + | |
175 | + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper = | |
176 | + new QueryWrapper<YtDeviceScriptEntity>() | |
177 | + .lambda() | |
178 | + .eq(YtDeviceScriptEntity::getTenantId, tenantId); | |
179 | + | |
180 | + | |
181 | + | |
182 | + | |
183 | + IPage<YtDeviceScriptEntity> scriptes = baseMapper.selectPage(currentPage, queryWrapper); | |
184 | + List<YtDeviceScriptDTO> records = scriptes.getRecords().stream() | |
185 | + .map(entity -> entity.getDTO(YtDeviceScriptDTO.class)) | |
186 | + .collect(Collectors.toList()); | |
187 | + return new YtPageData<>(records, scriptes.getTotal()); | |
188 | + } | |
189 | + | |
190 | + @Override | |
191 | + public List<YtDeviceScriptDTO> findDeviceScript(String tenantId) { | |
192 | + LambdaQueryWrapper<YtDeviceScriptEntity> queryWrapper = | |
193 | + new QueryWrapper<YtDeviceScriptEntity>() | |
194 | + .lambda() | |
195 | + .eq(YtDeviceScriptEntity::getTenantId, tenantId); | |
196 | + List<YtDeviceScriptDTO> results = baseMapper.selectList(queryWrapper).stream() | |
197 | + .map(item -> item.getDTO(YtDeviceScriptDTO.class)) | |
198 | + .collect(Collectors.toList()); | |
199 | + return results; | |
200 | + } | |
201 | + | |
202 | + | |
203 | +} | ... | ... |
1 | +package org.thingsboard.server.dao.yunteng.mapper; | |
2 | + | |
3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
4 | +import com.baomidou.mybatisplus.core.metadata.IPage; | |
5 | +import org.apache.ibatis.annotations.Mapper; | |
6 | +import org.apache.ibatis.annotations.Param; | |
7 | +import org.thingsboard.server.common.data.yunteng.dto.*; | |
8 | +import org.thingsboard.server.dao.yunteng.entities.YtDevice; | |
9 | +import org.thingsboard.server.dao.yunteng.entities.YtDeviceProfileEntity; | |
10 | + | |
11 | +import java.util.List; | |
12 | +import java.util.Map; | |
13 | + | |
14 | +@Mapper | |
15 | +public interface YtDeviceProfileMapper extends BaseMapper<YtDeviceProfileEntity> { | |
16 | + | |
17 | + /** | |
18 | + * 获取产品(设备配置)详情 | |
19 | + * @return | |
20 | + */ | |
21 | + public DeviceProfileDTO selectDetail( @Param("tenantId") String tenantId, @Param("id") String id); | |
22 | + | |
23 | + public IPage<DeviceProfileDTO> getProfilePage(IPage<?> page, @Param("tenantId") String tenantId, @Param("profileName") String profileName, @Param("transportType") String transportType); | |
24 | + | |
25 | +} | ... | ... |
1 | +package org.thingsboard.server.dao.yunteng.mapper; | |
2 | + | |
3 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |
4 | +import org.apache.ibatis.annotations.Mapper; | |
5 | +import org.thingsboard.server.dao.yunteng.entities.YtDeviceScriptEntity; | |
6 | + | |
7 | +@Mapper | |
8 | +public interface YtDeviceScriptMapper extends BaseMapper<YtDeviceScriptEntity> { | |
9 | +} | ... | ... |
... | ... | @@ -76,6 +76,33 @@ public abstract class AbstractBaseService<M extends BaseMapper<T>, T extends Bas |
76 | 76 | } |
77 | 77 | } |
78 | 78 | |
79 | + protected IPage<T> getCurrentPage(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType) { | |
80 | + if(page == null){ | |
81 | + page = 1; | |
82 | + } | |
83 | + if(pageSize == null){ | |
84 | + pageSize=10; | |
85 | + } | |
86 | + if(StringUtils.isEmpty(orderField)){ | |
87 | + orderField = FastIotConstants.DefaultOrder.CREATE_TIME; | |
88 | + } | |
89 | + if(orderType == null){ | |
90 | + orderType = OrderTypeEnum.DESC; | |
91 | + } | |
92 | + | |
93 | + Page<T> pageInform = new Page<>(page, pageSize); | |
94 | + | |
95 | + switch (orderType){ | |
96 | + case ASC: | |
97 | + pageInform.addOrder(OrderItem.asc(orderField)); | |
98 | + break; | |
99 | + default: | |
100 | + pageInform.addOrder(OrderItem.desc(orderField)); | |
101 | + } | |
102 | + | |
103 | + return pageInform; | |
104 | + } | |
105 | + | |
79 | 106 | /** |
80 | 107 | * 分页信息 |
81 | 108 | * @param pageNum 第几页 | ... | ... |
1 | 1 | package org.thingsboard.server.dao.yunteng.service; |
2 | 2 | |
3 | +import com.baomidou.mybatisplus.core.metadata.IPage; | |
3 | 4 | import org.thingsboard.server.common.data.page.PageLink; |
5 | +import org.thingsboard.server.common.data.yunteng.dto.DeviceDTO; | |
4 | 6 | import org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO; |
7 | +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; | |
5 | 8 | import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; |
6 | 9 | |
7 | 10 | import java.util.List; |
... | ... | @@ -10,13 +13,19 @@ import java.util.Set; |
10 | 13 | |
11 | 14 | public interface YtDeviceProfileService { |
12 | 15 | |
16 | + DeviceProfileDTO insertOrUpdate(DeviceProfileDTO deviceDTO); | |
17 | + | |
18 | + void deleteDeviceProfiles(String tenantId, Set<String> ids); | |
19 | + | |
13 | 20 | void checkDeviceProfiles(String tenantId, Set<String> ids); |
14 | 21 | |
15 | 22 | Optional<DeviceProfileDTO> getDeviceProfile(String tenantId, String id); |
16 | 23 | |
17 | - YtPageData<DeviceProfileDTO> page(PageLink pageLink, String tenantId, String transportType); | |
24 | + YtPageData<DeviceProfileDTO> page(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType | |
25 | + ,String tenantIdStr, String profileName, String transportType); | |
18 | 26 | |
19 | 27 | List<DeviceProfileDTO> findDeviceProfile(String tenantId); |
28 | + List<DeviceProfileDTO> findDeviceProfile(String tenantId,String scriptId); | |
20 | 29 | |
21 | 30 | |
22 | 31 | /** | ... | ... |
1 | +package org.thingsboard.server.dao.yunteng.service; | |
2 | + | |
3 | +import org.thingsboard.server.common.data.page.PageLink; | |
4 | +import org.thingsboard.server.common.data.yunteng.dto.YtDeviceScriptDTO; | |
5 | +import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum; | |
6 | +import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData; | |
7 | +import org.thingsboard.server.dao.yunteng.entities.Trigger; | |
8 | +import org.thingsboard.server.dao.yunteng.entities.YtDeviceScriptEntity; | |
9 | + | |
10 | +import java.util.List; | |
11 | +import java.util.Map; | |
12 | +import java.util.Optional; | |
13 | +import java.util.Set; | |
14 | + | |
15 | +public interface YtDeviceScriptService extends BaseService<YtDeviceScriptEntity>{ | |
16 | + | |
17 | + /** | |
18 | + * 查找脚本解析的脚本内容 | |
19 | + * @param tenantId | |
20 | + * @param scriptId | |
21 | + * @return | |
22 | + */ | |
23 | + String getScriptText(String tenantId, String scriptId); | |
24 | + | |
25 | + YtDeviceScriptDTO insertOrUpdate(YtDeviceScriptDTO deviceDTO); | |
26 | + | |
27 | + void deleteScriptes(String tenantId, Set<String> ids); | |
28 | + | |
29 | + void checkDeviceScriptes(String tenantId, Set<String> ids); | |
30 | + | |
31 | + Optional<YtDeviceScriptDTO> getDeviceScript(String tenantId, String id); | |
32 | + | |
33 | + YtPageData<YtDeviceScriptDTO> page(Integer page, Integer pageSize, String orderField, OrderTypeEnum orderType, String tenantId); | |
34 | + | |
35 | + List<YtDeviceScriptDTO> findDeviceScript(String tenantId); | |
36 | + | |
37 | + | |
38 | + /** | |
39 | + * 验证表单数据有效性 | |
40 | + * @param scriptDTO | |
41 | + * @param created | |
42 | + * @return | |
43 | + */ | |
44 | + boolean validateFormdata( YtDeviceScriptDTO scriptDTO,boolean created); | |
45 | +} | ... | ... |
1 | +<?xml version="1.0" encoding="UTF-8"?> | |
2 | +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |
3 | + | |
4 | +<mapper namespace="org.thingsboard.server.dao.yunteng.mapper.YtDeviceProfileMapper"> | |
5 | + <resultMap type="org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO" id="detail"> | |
6 | + <result property="id" column="id"/> | |
7 | + <result property="createTime" column="create_time"/> | |
8 | + <result property="updateTime" column="update_time"/> | |
9 | + <result property="creator" column="creator"/> | |
10 | + <result property="updater" column="updater"/> | |
11 | + <result property="name" column="name"/> | |
12 | + <result property="image" column="image"/> | |
13 | + <result property="description" column="description"/> | |
14 | + <result property="tenantId" column="tenant_id"/> | |
15 | + <result property="scriptId" column="script_id"/> | |
16 | + <result property="transportType" column="transport_type"/> | |
17 | + <result property="provisionType" column="provision_type"/> | |
18 | + <result property="deviceType" column="device_type" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/> | |
19 | + <result property="tbProfileId" column="tb_profile_id"/> | |
20 | + <result property="profileData" column="profile_data" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/> | |
21 | + <result property="defaultQueueName" column="default_queue_name"/> | |
22 | + <result property="defaultRuleChainId" column="default_rule_chain_id"/> | |
23 | + <result property="default" column="is_default"/> | |
24 | + <result property="type" column="type"/> | |
25 | + </resultMap> | |
26 | + | |
27 | + | |
28 | + <sql id="basicColumns"> | |
29 | + base.name,base.image,base.description,base.tenant_id,base.transport_type,base.provision_type,base.profile_data,base.default_queue_name,base.default_rule_chain_id,base.is_default,base.type | |
30 | + ,iot.id,iot.script_id,iot.device_type,iot.create_time,iot.update_time,iot.creator,iot.updater | |
31 | + </sql> | |
32 | + | |
33 | + <select id="getProfilePage" resultMap="detail"> | |
34 | + SELECT | |
35 | + <include refid="basicColumns"/> | |
36 | + FROM device_profile base | |
37 | + LEFT JOIN iotfs_device_profile iot ON iot.tb_profile_id = base.id::TEXT | |
38 | + <where> | |
39 | + <if test="tenantId !=null and tenantId !=''"> | |
40 | + AND base.tenant_id::TEXT = #{tenantId} | |
41 | + </if> | |
42 | + <if test="profileName !=null and profileName !=''"> | |
43 | + AND base.name = #{profileName} | |
44 | + </if> | |
45 | + <if test="transportType !=null and transportType !=''"> | |
46 | + AND base.transport_type = #{transportType} | |
47 | + </if> | |
48 | + </where> | |
49 | + </select> | |
50 | + | |
51 | + | |
52 | + <select id="selectDetail" resultMap="detail"> | |
53 | + SELECT | |
54 | + <include refid="basicColumns"/>,d.customer_id::TEXT AS customer_id,cus.title AS cusotomer_name | |
55 | + FROM device_profile base | |
56 | + LEFT JOIN iotfs_device_profile iot ON iot.tb_profile_id = base.id::TEXT | |
57 | + <where> | |
58 | + <if test="tenantId !=null and tenantId !=''"> | |
59 | + AND iot.tenant_id = #{tenantId} | |
60 | + </if> | |
61 | + <if test="id !=null and id !=''"> | |
62 | + AND base.id = #{id} | |
63 | + </if> | |
64 | + </where> | |
65 | + </select> | |
66 | +</mapper> | ... | ... |
... | ... | @@ -939,6 +939,11 @@ |
939 | 939 | </dependency> |
940 | 940 | <dependency> |
941 | 941 | <groupId>org.thingsboard.common.transport</groupId> |
942 | + <artifactId>tcp</artifactId> | |
943 | + <version>${project.version}</version> | |
944 | + </dependency> | |
945 | + <dependency> | |
946 | + <groupId>org.thingsboard.common.transport</groupId> | |
942 | 947 | <artifactId>http</artifactId> |
943 | 948 | <version>${project.version}</version> |
944 | 949 | </dependency> | ... | ... |
... | ... | @@ -41,7 +41,7 @@ |
41 | 41 | <pkg.copyInstallScripts>false</pkg.copyInstallScripts> |
42 | 42 | <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist> |
43 | 43 | <pkg.implementationTitle>ThingsBoard MQTT Transport Service</pkg.implementationTitle> |
44 | - <pkg.mainClass>org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication</pkg.mainClass> | |
44 | + <pkg.mainClass>org.thingsboard.server.tcp.ThingsboardMqttTransportApplication</pkg.mainClass> | |
45 | 45 | </properties> |
46 | 46 | |
47 | 47 | <dependencies> | ... | ... |
transport/tcp/pom.xml
0 → 100644
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2022 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
19 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
20 | + <modelVersion>4.0.0</modelVersion> | |
21 | + <parent> | |
22 | + <groupId>org.thingsboard</groupId> | |
23 | + <version>3.3.4-SNAPSHOT</version> | |
24 | + <artifactId>transport</artifactId> | |
25 | + </parent> | |
26 | + <groupId>org.thingsboard.transport</groupId> | |
27 | + <artifactId>tcp</artifactId> | |
28 | + <packaging>jar</packaging> | |
29 | + | |
30 | + <name>Thingsboard TCP Transport Service</name> | |
31 | + <url>https://thingsboard.io</url> | |
32 | + | |
33 | + <properties> | |
34 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
35 | + <main.dir>${basedir}/../..</main.dir> | |
36 | + <pkg.type>java</pkg.type> | |
37 | + <pkg.disabled>false</pkg.disabled> | |
38 | + <pkg.process-resources.phase>process-resources</pkg.process-resources.phase> | |
39 | + <pkg.package.phase>package</pkg.package.phase> | |
40 | + <pkg.name>tb-tcp-transport</pkg.name> | |
41 | + <pkg.copyInstallScripts>false</pkg.copyInstallScripts> | |
42 | + <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist> | |
43 | + <pkg.implementationTitle>ThingsBoard TCP Transport Service</pkg.implementationTitle> | |
44 | + <pkg.mainClass>org.thingsboard.server.tcp.ThingsboardTcpTransportApplication</pkg.mainClass> | |
45 | + </properties> | |
46 | + | |
47 | + <dependencies> | |
48 | + <dependency> | |
49 | + <groupId>org.thingsboard.common.transport</groupId> | |
50 | + <artifactId>tcp</artifactId> | |
51 | + </dependency> | |
52 | + <dependency> | |
53 | + <groupId>org.thingsboard.common</groupId> | |
54 | + <artifactId>queue</artifactId> | |
55 | + </dependency> | |
56 | + <dependency> | |
57 | + <groupId>org.springframework.boot</groupId> | |
58 | + <artifactId>spring-boot-starter-web</artifactId> | |
59 | + </dependency> | |
60 | + <dependency> | |
61 | + <groupId>com.sun.winsw</groupId> | |
62 | + <artifactId>winsw</artifactId> | |
63 | + <classifier>bin</classifier> | |
64 | + <type>exe</type> | |
65 | + <scope>provided</scope> | |
66 | + </dependency> | |
67 | + <dependency> | |
68 | + <groupId>org.springframework.boot</groupId> | |
69 | + <artifactId>spring-boot-starter-test</artifactId> | |
70 | + <scope>test</scope> | |
71 | + </dependency> | |
72 | + <dependency> | |
73 | + <groupId>org.junit.vintage</groupId> | |
74 | + <artifactId>junit-vintage-engine</artifactId> | |
75 | + <scope>test</scope> | |
76 | + </dependency> | |
77 | + <dependency> | |
78 | + <groupId>org.awaitility</groupId> | |
79 | + <artifactId>awaitility</artifactId> | |
80 | + <scope>test</scope> | |
81 | + </dependency> | |
82 | + </dependencies> | |
83 | + | |
84 | + <build> | |
85 | + <finalName>${pkg.name}-${project.version}</finalName> | |
86 | + <resources> | |
87 | + <resource> | |
88 | + <directory>${project.basedir}/src/main/resources</directory> | |
89 | + </resource> | |
90 | + </resources> | |
91 | + <plugins> | |
92 | + <plugin> | |
93 | + <groupId>org.apache.maven.plugins</groupId> | |
94 | + <artifactId>maven-resources-plugin</artifactId> | |
95 | + </plugin> | |
96 | + <plugin> | |
97 | + <groupId>org.apache.maven.plugins</groupId> | |
98 | + <artifactId>maven-dependency-plugin</artifactId> | |
99 | + </plugin> | |
100 | + <plugin> | |
101 | + <groupId>org.apache.maven.plugins</groupId> | |
102 | + <artifactId>maven-jar-plugin</artifactId> | |
103 | + </plugin> | |
104 | + <plugin> | |
105 | + <groupId>org.springframework.boot</groupId> | |
106 | + <artifactId>spring-boot-maven-plugin</artifactId> | |
107 | + </plugin> | |
108 | + <plugin> | |
109 | + <groupId>org.thingsboard</groupId> | |
110 | + <artifactId>gradle-maven-plugin</artifactId> | |
111 | + </plugin> | |
112 | + <plugin> | |
113 | + <groupId>org.apache.maven.plugins</groupId> | |
114 | + <artifactId>maven-assembly-plugin</artifactId> | |
115 | + </plugin> | |
116 | + <plugin> | |
117 | + <groupId>org.apache.maven.plugins</groupId> | |
118 | + <artifactId>maven-install-plugin</artifactId> | |
119 | + </plugin> | |
120 | + </plugins> | |
121 | + </build> | |
122 | + <repositories> | |
123 | + <repository> | |
124 | + <id>jenkins</id> | |
125 | + <name>Jenkins Repository</name> | |
126 | + <url>https://repo.jenkins-ci.org/releases</url> | |
127 | + <snapshots> | |
128 | + <enabled>false</enabled> | |
129 | + </snapshots> | |
130 | + </repository> | |
131 | + </repositories> | |
132 | +</project> | ... | ... |
transport/tcp/src/main/conf/logback.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8" ?> | |
2 | +<!-- | |
3 | + | |
4 | + Copyright © 2016-2022 The Thingsboard Authors | |
5 | + | |
6 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
7 | + you may not use this file except in compliance with the License. | |
8 | + You may obtain a copy of the License at | |
9 | + | |
10 | + http://www.apache.org/licenses/LICENSE-2.0 | |
11 | + | |
12 | + Unless required by applicable law or agreed to in writing, software | |
13 | + distributed under the License is distributed on an "AS IS" BASIS, | |
14 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 | + See the License for the specific language governing permissions and | |
16 | + limitations under the License. | |
17 | + | |
18 | +--> | |
19 | +<!DOCTYPE configuration> | |
20 | +<configuration> | |
21 | + | |
22 | + <appender name="fileLogAppender" | |
23 | + class="ch.qos.logback.core.rolling.RollingFileAppender"> | |
24 | + <file>${pkg.logFolder}/${pkg.name}.log</file> | |
25 | + <rollingPolicy | |
26 | + class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | |
27 | + <fileNamePattern>${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | |
28 | + <maxFileSize>100MB</maxFileSize> | |
29 | + <maxHistory>30</maxHistory> | |
30 | + <totalSizeCap>3GB</totalSizeCap> | |
31 | + </rollingPolicy> | |
32 | + <encoder> | |
33 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | |
34 | + </encoder> | |
35 | + </appender> | |
36 | + | |
37 | + <logger name="org.thingsboard.server" level="INFO" /> | |
38 | + | |
39 | + <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" /> | |
40 | + | |
41 | + <root level="INFO"> | |
42 | + <appender-ref ref="fileLogAppender"/> | |
43 | + </root> | |
44 | + | |
45 | +</configuration> | ... | ... |
1 | +# | |
2 | +# Copyright © 2016-2022 The Thingsboard Authors | |
3 | +# | |
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +# you may not use this file except in compliance with the License. | |
6 | +# You may obtain a copy of the License at | |
7 | +# | |
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +# | |
10 | +# Unless required by applicable law or agreed to in writing, software | |
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +# See the License for the specific language governing permissions and | |
14 | +# limitations under the License. | |
15 | +# | |
16 | + | |
17 | +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" | |
18 | +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" | |
19 | +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | |
20 | +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" | |
21 | +export LOG_FILENAME=${pkg.name}.out | |
22 | +export LOADER_PATH=${pkg.installFolder}/conf | ... | ... |
transport/tcp/src/main/java/org/thingsboard/server/tcp/ThingsboardTcpTransportApplication.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2022 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.tcp; | |
17 | + | |
18 | +import org.springframework.boot.SpringApplication; | |
19 | +import org.springframework.boot.SpringBootConfiguration; | |
20 | +import org.springframework.context.annotation.ComponentScan; | |
21 | +import org.springframework.scheduling.annotation.EnableAsync; | |
22 | +import org.springframework.scheduling.annotation.EnableScheduling; | |
23 | + | |
24 | +import java.util.Arrays; | |
25 | + | |
26 | +@SpringBootConfiguration | |
27 | +@EnableAsync | |
28 | +@EnableScheduling | |
29 | +@ComponentScan({"org.thingsboard.server.tcp", "org.thingsboard.server.common", "org.thingsboard.server.transport.tcp", "org.thingsboard.server.queue", "org.thingsboard.server.cache"}) | |
30 | +public class ThingsboardTcpTransportApplication { | |
31 | + | |
32 | + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; | |
33 | + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-tcp-transport"; | |
34 | + | |
35 | + public static void main(String[] args) { | |
36 | + SpringApplication.run(ThingsboardTcpTransportApplication.class, updateArguments(args)); | |
37 | + } | |
38 | + | |
39 | + private static String[] updateArguments(String[] args) { | |
40 | + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { | |
41 | + String[] modifiedArgs = new String[args.length + 1]; | |
42 | + System.arraycopy(args, 0, modifiedArgs, 0, args.length); | |
43 | + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; | |
44 | + return modifiedArgs; | |
45 | + } | |
46 | + return args; | |
47 | + } | |
48 | +} | ... | ... |
transport/tcp/src/main/resources/logback.xml
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8" ?> | |
2 | +<!-- | |
3 | + | |
4 | + Copyright © 2016-2022 The Thingsboard Authors | |
5 | + | |
6 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
7 | + you may not use this file except in compliance with the License. | |
8 | + You may obtain a copy of the License at | |
9 | + | |
10 | + http://www.apache.org/licenses/LICENSE-2.0 | |
11 | + | |
12 | + Unless required by applicable law or agreed to in writing, software | |
13 | + distributed under the License is distributed on an "AS IS" BASIS, | |
14 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 | + See the License for the specific language governing permissions and | |
16 | + limitations under the License. | |
17 | + | |
18 | +--> | |
19 | +<!DOCTYPE configuration> | |
20 | +<configuration scan="true" scanPeriod="10 seconds"> | |
21 | + | |
22 | + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | |
23 | + <encoder> | |
24 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | |
25 | + </encoder> | |
26 | + </appender> | |
27 | + | |
28 | + <logger name="org.thingsboard.server" level="TRACE" /> | |
29 | + | |
30 | + <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" /> | |
31 | + | |
32 | + <root level="INFO"> | |
33 | + <appender-ref ref="STDOUT"/> | |
34 | + </root> | |
35 | + | |
36 | +</configuration> | |
\ No newline at end of file | ... | ... |
1 | +# | |
2 | +# Copyright © 2016-2022 The Thingsboard Authors | |
3 | +# | |
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +# you may not use this file except in compliance with the License. | |
6 | +# You may obtain a copy of the License at | |
7 | +# | |
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +# | |
10 | +# Unless required by applicable law or agreed to in writing, software | |
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +# See the License for the specific language governing permissions and | |
14 | +# limitations under the License. | |
15 | +# | |
16 | + | |
17 | +# If you enabled process metrics you should also enable 'web-environment'. | |
18 | +spring.main.web-environment: "${WEB_APPLICATION_ENABLE:false}" | |
19 | +# If you enabled process metrics you should set 'web-application-type' to 'servlet' value. | |
20 | +spring.main.web-application-type: "${WEB_APPLICATION_TYPE:none}" | |
21 | + | |
22 | +server: | |
23 | + # Server bind address (has no effect if web-environment is disabled). | |
24 | + address: "${HTTP_BIND_ADDRESS:0.0.0.0}" | |
25 | + # Server bind port (has no effect if web-environment is disabled). | |
26 | + port: "${HTTP_BIND_PORT:8083}" | |
27 | + | |
28 | +# Zookeeper connection parameters. Used for service discovery. | |
29 | +zk: | |
30 | + # Enable/disable zookeeper discovery service. | |
31 | + enabled: "${ZOOKEEPER_ENABLED:true}" | |
32 | + # Zookeeper connect string | |
33 | + url: "${ZOOKEEPER_URL:47.99.141.212:2181}" | |
34 | + # Zookeeper retry interval in milliseconds | |
35 | + retry_interval_ms: "${ZOOKEEPER_RETRY_INTERVAL_MS:3000}" | |
36 | + # Zookeeper connection timeout in milliseconds | |
37 | + connection_timeout_ms: "${ZOOKEEPER_CONNECTION_TIMEOUT_MS:3000}" | |
38 | + # Zookeeper session timeout in milliseconds | |
39 | + session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" | |
40 | + # Name of the directory in zookeeper 'filesystem' | |
41 | + zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" | |
42 | + | |
43 | +cache: | |
44 | + type: "${CACHE_TYPE:redis}" | |
45 | + | |
46 | +redis: | |
47 | + # standalone or cluster | |
48 | + connection: | |
49 | + type: "${REDIS_CONNECTION_TYPE:standalone}" | |
50 | + standalone: | |
51 | + host: "${REDIS_HOST:localhost}" | |
52 | + port: "${REDIS_PORT:6379}" | |
53 | + useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}" | |
54 | + # this value may be used only if you used not default ClientConfig | |
55 | + clientName: "${REDIS_CLIENT_NAME:standalone}" | |
56 | + # this value may be used only if you used not default ClientConfig | |
57 | + connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}" | |
58 | + # this value may be used only if you used not default ClientConfig | |
59 | + readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" | |
60 | + # this value may be used only if you used not default ClientConfig | |
61 | + usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}" | |
62 | + cluster: | |
63 | + # Comma-separated list of "host:port" pairs to bootstrap from. | |
64 | + nodes: "${REDIS_NODES:}" | |
65 | + # Maximum number of redirects to follow when executing commands across the cluster. | |
66 | + max-redirects: "${REDIS_MAX_REDIRECTS:12}" | |
67 | + useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}" | |
68 | + # db index | |
69 | + db: "${REDIS_DB:0}" | |
70 | + # db password | |
71 | + password: "${REDIS_PASSWORD:redis@6379}" | |
72 | + # pool config | |
73 | + pool_config: | |
74 | + maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" | |
75 | + maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}" | |
76 | + minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}" | |
77 | + testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}" | |
78 | + testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}" | |
79 | + testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}" | |
80 | + minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}" | |
81 | + evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}" | |
82 | + maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}" | |
83 | + numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}" | |
84 | + blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" | |
85 | + | |
86 | +# TCP_ server parameters | |
87 | +transport: | |
88 | + tcp: | |
89 | + bind_address: "${TCP_BIND_ADDRESS:0.0.0.0}" | |
90 | + bind_port: "${TCP_BIND_PORT:8088}" | |
91 | + # Enable proxy protocol support. Disabled by default. If enabled, supports both v1 and v2. | |
92 | + # Useful to get the real IP address of the client in the logs and for rate limits. | |
93 | + proxy_enabled: "${TCP_PROXY_PROTOCOL_ENABLED:false}" | |
94 | + timeout: "${TCP_TIMEOUT:10000}" | |
95 | + msg_queue_size_per_device_limit: "${TCP_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism | |
96 | + netty: | |
97 | + leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}" | |
98 | + boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" | |
99 | + worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" | |
100 | + max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" | |
101 | + so_keep_alive: "${NETTY_SO_KEEPALIVE:false}" | |
102 | + # TCP_ SSL configuration | |
103 | + ssl: | |
104 | + # Enable/disable SSL support | |
105 | + enabled: "${TCP_SSL_ENABLED:false}" | |
106 | + # TCP_ SSL bind address | |
107 | + bind_address: "${TCP_SSL_BIND_ADDRESS:0.0.0.0}" | |
108 | + # TCP_ SSL bind port | |
109 | + bind_port: "${TCP_SSL_BIND_PORT:8883}" | |
110 | + # SSL protocol: See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html#sslcontext-algorithms | |
111 | + protocol: "${TCP_SSL_PROTOCOL:TLSv1.2}" | |
112 | + # Server SSL credentials | |
113 | + credentials: | |
114 | + # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) | |
115 | + type: "${TCP_SSL_CREDENTIALS_TYPE:PEM}" | |
116 | + # PEM server credentials | |
117 | + pem: | |
118 | + # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) | |
119 | + cert_file: "${TCP_SSL_PEM_CERT:tcpserver.pem}" | |
120 | + # Path to the server certificate private key file. Optional by default. Required if the private key is not present in server certificate file; | |
121 | + key_file: "${TCP_SSL_PEM_KEY:tcpserver_key.pem}" | |
122 | + # Server certificate private key password (optional) | |
123 | + key_password: "${TCP_SSL_PEM_KEY_PASSWORD:server_key_password}" | |
124 | + # Keystore server credentials | |
125 | + keystore: | |
126 | + # Type of the key store | |
127 | + type: "${TCP_SSL_KEY_STORE_TYPE:JKS}" | |
128 | + # Path to the key store that holds the SSL certificate | |
129 | + store_file: "${TCP_SSL_KEY_STORE:tcpserver.jks}" | |
130 | + # Password used to access the key store | |
131 | + store_password: "${TCP_SSL_KEY_STORE_PASSWORD:server_ks_password}" | |
132 | + # Optional alias of the private key; If not set, the platform will load the first private key from the keystore; | |
133 | + key_alias: "${TCP_SSL_KEY_ALIAS:}" | |
134 | + # Password used to access the key | |
135 | + key_password: "${TCP_SSL_KEY_PASSWORD:server_key_password}" | |
136 | + # Skip certificate validity check for client certificates. | |
137 | + skip_validity_check_for_client_cert: "${TCP_SSL_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" | |
138 | + sessions: | |
139 | + inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" | |
140 | + report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" | |
141 | + json: | |
142 | + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON | |
143 | + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" | |
144 | + # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) | |
145 | + max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" | |
146 | + log: | |
147 | + enabled: "${TB_TRANSPORT_LOG_ENABLED:true}" | |
148 | + max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}" | |
149 | + stats: | |
150 | + enabled: "${TB_TRANSPORT_STATS_ENABLED:true}" | |
151 | + print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}" | |
152 | + client_side_rpc: | |
153 | + timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" | |
154 | + rate_limits: | |
155 | + # Enable or disable generic rate limits. Device and Tenant specific rate limits are controlled in Tenant Profile. | |
156 | + ip_limits_enabled: "${TB_TRANSPORT_IP_RATE_LIMITS_ENABLED:false}" | |
157 | + # Maximum number of connect attempts with invalid credentials | |
158 | + max_wrong_credentials_per_ip: "${TB_TRANSPORT_MAX_WRONG_CREDENTIALS_PER_IP:10}" | |
159 | + # Timeout to expire block IP addresses | |
160 | + ip_block_timeout: "${TB_TRANSPORT_IP_BLOCK_TIMEOUT:60000}" | |
161 | + | |
162 | + | |
163 | +queue: | |
164 | + type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) | |
165 | + kafka: | |
166 | + bootstrap.servers: "${TB_KAFKA_SERVERS:47.99.141.212:9092}" | |
167 | + acks: "${TB_KAFKA_ACKS:all}" | |
168 | + retries: "${TB_KAFKA_RETRIES:1}" | |
169 | + batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" | |
170 | + linger.ms: "${TB_KAFKA_LINGER_MS:1}" | |
171 | + max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" | |
172 | + max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}" | |
173 | + buffer.memory: "${TB_BUFFER_MEMORY:33554432}" | |
174 | + replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" | |
175 | + use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" | |
176 | + confluent: | |
177 | + ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}" | |
178 | + sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" | |
179 | + sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" | |
180 | + security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" | |
181 | + other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside | |
182 | + - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms | |
183 | + value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) | |
184 | + - key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms | |
185 | + value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) | |
186 | + topic-properties: | |
187 | + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" | |
188 | + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" | |
189 | + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" | |
190 | + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" | |
191 | + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" | |
192 | + aws_sqs: | |
193 | + use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}" | |
194 | + access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}" | |
195 | + secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}" | |
196 | + region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}" | |
197 | + threads_per_topic: "${TB_QUEUE_AWS_SQS_THREADS_PER_TOPIC:1}" | |
198 | + queue-properties: | |
199 | + rule-engine: "${TB_QUEUE_AWS_SQS_RE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" | |
200 | + core: "${TB_QUEUE_AWS_SQS_CORE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" | |
201 | + transport-api: "${TB_QUEUE_AWS_SQS_TA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" | |
202 | + notifications: "${TB_QUEUE_AWS_SQS_NOTIFICATIONS_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" | |
203 | + js-executor: "${TB_QUEUE_AWS_SQS_JE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" | |
204 | + # VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds | |
205 | + pubsub: | |
206 | + project_id: "${TB_QUEUE_PUBSUB_PROJECT_ID:YOUR_PROJECT_ID}" | |
207 | + service_account: "${TB_QUEUE_PUBSUB_SERVICE_ACCOUNT:YOUR_SERVICE_ACCOUNT}" | |
208 | + max_msg_size: "${TB_QUEUE_PUBSUB_MAX_MSG_SIZE:1048576}" #in bytes | |
209 | + max_messages: "${TB_QUEUE_PUBSUB_MAX_MESSAGES:1000}" | |
210 | + queue-properties: | |
211 | + rule-engine: "${TB_QUEUE_PUBSUB_RE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" | |
212 | + core: "${TB_QUEUE_PUBSUB_CORE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" | |
213 | + transport-api: "${TB_QUEUE_PUBSUB_TA_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" | |
214 | + notifications: "${TB_QUEUE_PUBSUB_NOTIFICATIONS_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" | |
215 | + js-executor: "${TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" | |
216 | + service_bus: | |
217 | + namespace_name: "${TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME:YOUR_NAMESPACE_NAME}" | |
218 | + sas_key_name: "${TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME:YOUR_SAS_KEY_NAME}" | |
219 | + sas_key: "${TB_QUEUE_SERVICE_BUS_SAS_KEY:YOUR_SAS_KEY}" | |
220 | + max_messages: "${TB_QUEUE_SERVICE_BUS_MAX_MESSAGES:1000}" | |
221 | + queue-properties: | |
222 | + rule-engine: "${TB_QUEUE_SERVICE_BUS_RE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" | |
223 | + core: "${TB_QUEUE_SERVICE_BUS_CORE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" | |
224 | + transport-api: "${TB_QUEUE_SERVICE_BUS_TA_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" | |
225 | + notifications: "${TB_QUEUE_SERVICE_BUS_NOTIFICATIONS_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" | |
226 | + js-executor: "${TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" | |
227 | + rabbitmq: | |
228 | + exchange_name: "${TB_QUEUE_RABBIT_MQ_EXCHANGE_NAME:}" | |
229 | + host: "${TB_QUEUE_RABBIT_MQ_HOST:localhost}" | |
230 | + port: "${TB_QUEUE_RABBIT_MQ_PORT:5672}" | |
231 | + virtual_host: "${TB_QUEUE_RABBIT_MQ_VIRTUAL_HOST:/}" | |
232 | + username: "${TB_QUEUE_RABBIT_MQ_USERNAME:YOUR_USERNAME}" | |
233 | + password: "${TB_QUEUE_RABBIT_MQ_PASSWORD:YOUR_PASSWORD}" | |
234 | + automatic_recovery_enabled: "${TB_QUEUE_RABBIT_MQ_AUTOMATIC_RECOVERY_ENABLED:false}" | |
235 | + connection_timeout: "${TB_QUEUE_RABBIT_MQ_CONNECTION_TIMEOUT:60000}" | |
236 | + handshake_timeout: "${TB_QUEUE_RABBIT_MQ_HANDSHAKE_TIMEOUT:10000}" | |
237 | + queue-properties: | |
238 | + rule-engine: "${TB_QUEUE_RABBIT_MQ_RE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" | |
239 | + core: "${TB_QUEUE_RABBIT_MQ_CORE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" | |
240 | + transport-api: "${TB_QUEUE_RABBIT_MQ_TA_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" | |
241 | + notifications: "${TB_QUEUE_RABBIT_MQ_NOTIFICATIONS_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" | |
242 | + js-executor: "${TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" | |
243 | + partitions: | |
244 | + hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" | |
245 | + virtual_nodes_size: "${TB_QUEUE_PARTITIONS_VIRTUAL_NODES_SIZE:16}" | |
246 | + transport_api: | |
247 | + requests_topic: "${TB_QUEUE_TRANSPORT_API_REQUEST_TOPIC:tb_transport.api.requests}" | |
248 | + responses_topic: "${TB_QUEUE_TRANSPORT_API_RESPONSE_TOPIC:tb_transport.api.responses}" | |
249 | + max_pending_requests: "${TB_QUEUE_TRANSPORT_MAX_PENDING_REQUESTS:10000}" | |
250 | + max_requests_timeout: "${TB_QUEUE_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" | |
251 | + max_callback_threads: "${TB_QUEUE_TRANSPORT_MAX_CALLBACK_THREADS:100}" | |
252 | + request_poll_interval: "${TB_QUEUE_TRANSPORT_REQUEST_POLL_INTERVAL_MS:25}" | |
253 | + response_poll_interval: "${TB_QUEUE_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}" | |
254 | + core: | |
255 | + topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" | |
256 | + poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" | |
257 | + partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" | |
258 | + pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" | |
259 | + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" | |
260 | + stats: | |
261 | + enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" | |
262 | + print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" | |
263 | + js: | |
264 | + # JS Eval request topic | |
265 | + request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}" | |
266 | + # JS Eval responses topic prefix that is combined with node id | |
267 | + response_topic_prefix: "${REMOTE_JS_EVAL_RESPONSE_TOPIC:js_eval.responses}" | |
268 | + # JS Eval max pending requests | |
269 | + max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}" | |
270 | + # JS Eval max request timeout | |
271 | + max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" | |
272 | + # JS response poll interval | |
273 | + response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" | |
274 | + rule-engine: | |
275 | + topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" | |
276 | + poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" | |
277 | + pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}" | |
278 | + stats: | |
279 | + enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}" | |
280 | + print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}" | |
281 | + queues: | |
282 | + - name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}" | |
283 | + topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}" | |
284 | + poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}" | |
285 | + partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}" | |
286 | + pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}" | |
287 | + submit-strategy: | |
288 | + type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL | |
289 | + # For BATCH only | |
290 | + batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch | |
291 | + processing-strategy: | |
292 | + type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT | |
293 | + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT | |
294 | + retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited | |
295 | + failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; | |
296 | + pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries; | |
297 | + - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}" | |
298 | + topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" | |
299 | + poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" | |
300 | + partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}" | |
301 | + pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}" | |
302 | + submit-strategy: | |
303 | + type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL | |
304 | + # For BATCH only | |
305 | + batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch | |
306 | + processing-strategy: | |
307 | + type: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT | |
308 | + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT | |
309 | + retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited | |
310 | + failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; | |
311 | + pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; | |
312 | + - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}" | |
313 | + topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" | |
314 | + poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" | |
315 | + partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}" | |
316 | + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}" | |
317 | + submit-strategy: | |
318 | + type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL | |
319 | + # For BATCH only | |
320 | + batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch | |
321 | + processing-strategy: | |
322 | + type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT | |
323 | + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT | |
324 | + retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited | |
325 | + failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; | |
326 | + pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; | |
327 | + transport: | |
328 | + # For high priority notifications that require minimum latency and processing time | |
329 | + notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" | |
330 | + poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" | |
331 | + | |
332 | +service: | |
333 | + type: "${TB_SERVICE_TYPE:tb-transport}" | |
334 | + # Unique id for this service (autogenerated if empty) | |
335 | + id: "${TB_SERVICE_ID:}" | |
336 | + tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id. | |
337 | + | |
338 | +metrics: | |
339 | + # Enable/disable actuator metrics. | |
340 | + enabled: "${METRICS_ENABLED:false}" | |
341 | + | |
342 | +management: | |
343 | + endpoints: | |
344 | + web: | |
345 | + exposure: | |
346 | + # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics). | |
347 | + include: '${METRICS_ENDPOINTS_EXPOSE:info}' | ... | ... |
... | ... | @@ -44,6 +44,7 @@ export enum DeviceProfileType { |
44 | 44 | export enum DeviceTransportType { |
45 | 45 | DEFAULT = 'DEFAULT', |
46 | 46 | MQTT = 'MQTT', |
47 | + TCP = 'TCP', | |
47 | 48 | COAP = 'COAP', |
48 | 49 | LWM2M = 'LWM2M', |
49 | 50 | SNMP = 'SNMP' |
... | ... | @@ -99,6 +100,7 @@ export const deviceTransportTypeTranslationMap = new Map<DeviceTransportType, st |
99 | 100 | [ |
100 | 101 | [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default'], |
101 | 102 | [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt'], |
103 | + [DeviceTransportType.TCP, 'device-profile.transport-type-tcp'], | |
102 | 104 | [DeviceTransportType.COAP, 'device-profile.transport-type-coap'], |
103 | 105 | [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m'], |
104 | 106 | [DeviceTransportType.SNMP, 'device-profile.transport-type-snmp'] |
... | ... | @@ -118,6 +120,7 @@ export const deviceTransportTypeHintMap = new Map<DeviceTransportType, string>( |
118 | 120 | [ |
119 | 121 | [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], |
120 | 122 | [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], |
123 | + [DeviceTransportType.TCP, 'device-profile.transport-type-mqtt-hint'], | |
121 | 124 | [DeviceTransportType.COAP, 'device-profile.transport-type-coap-hint'], |
122 | 125 | [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'], |
123 | 126 | [DeviceTransportType.SNMP, 'device-profile.transport-type-snmp-hint'] |
... | ... | @@ -202,6 +205,13 @@ export const deviceTransportTypeConfigurationInfoMap = new Map<DeviceTransportTy |
202 | 205 | } |
203 | 206 | ], |
204 | 207 | [ |
208 | + DeviceTransportType.TCP, | |
209 | + { | |
210 | + hasProfileConfiguration: true, | |
211 | + hasDeviceConfiguration: false, | |
212 | + } | |
213 | + ], | |
214 | + [ | |
205 | 215 | DeviceTransportType.LWM2M, |
206 | 216 | { |
207 | 217 | hasProfileConfiguration: true, |
... | ... | @@ -250,6 +260,11 @@ export interface MqttDeviceProfileTransportConfiguration { |
250 | 260 | [key: string]: any; |
251 | 261 | } |
252 | 262 | |
263 | +//Thingskit function | |
264 | +export interface YtTcpDeviceProfileTransportConfiguration { | |
265 | + [key: string]: any; | |
266 | +} | |
267 | + | |
253 | 268 | export interface CoapClientSetting { |
254 | 269 | powerMode?: PowerMode | null; |
255 | 270 | edrxCycle?: number; |
... | ... | @@ -306,6 +321,7 @@ export interface SnmpMapping { |
306 | 321 | |
307 | 322 | export type DeviceProfileTransportConfigurations = DefaultDeviceProfileTransportConfiguration & |
308 | 323 | MqttDeviceProfileTransportConfiguration & |
324 | + YtTcpDeviceProfileTransportConfiguration & | |
309 | 325 | CoapDeviceProfileTransportConfiguration & |
310 | 326 | Lwm2mDeviceProfileTransportConfiguration & |
311 | 327 | SnmpDeviceProfileTransportConfiguration; |
... | ... | @@ -366,6 +382,10 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT |
366 | 382 | }; |
367 | 383 | transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT}; |
368 | 384 | break; |
385 | + case DeviceTransportType.TCP: | |
386 | + const tcpTransportConfiguration: YtTcpDeviceProfileTransportConfiguration = {}; | |
387 | + transportConfiguration = {...tcpTransportConfiguration, type: DeviceTransportType.TCP}; | |
388 | + break; | |
369 | 389 | case DeviceTransportType.COAP: |
370 | 390 | const coapTransportConfiguration: CoapDeviceProfileTransportConfiguration = { |
371 | 391 | coapDeviceTypeConfiguration: { |
... | ... | @@ -411,6 +431,10 @@ export function createDeviceTransportConfiguration(type: DeviceTransportType): D |
411 | 431 | const mqttTransportConfiguration: MqttDeviceTransportConfiguration = {}; |
412 | 432 | transportConfiguration = {...mqttTransportConfiguration, type: DeviceTransportType.MQTT}; |
413 | 433 | break; |
434 | + case DeviceTransportType.TCP: | |
435 | + const tcpTransportConfiguration: YtTcpDeviceTransportConfiguration = {}; | |
436 | + transportConfiguration = {...tcpTransportConfiguration, type: DeviceTransportType.TCP}; | |
437 | + break; | |
414 | 438 | case DeviceTransportType.COAP: |
415 | 439 | const coapTransportConfiguration: CoapDeviceTransportConfiguration = { |
416 | 440 | powerMode: null |
... | ... | @@ -600,6 +624,10 @@ export interface MqttDeviceTransportConfiguration { |
600 | 624 | [key: string]: any; |
601 | 625 | } |
602 | 626 | |
627 | +export interface YtTcpDeviceTransportConfiguration { | |
628 | + [key: string]: any; | |
629 | +} | |
630 | + | |
603 | 631 | export interface CoapDeviceTransportConfiguration { |
604 | 632 | powerMode?: PowerMode | null; |
605 | 633 | edrxCycle?: number; |
... | ... | @@ -725,6 +753,9 @@ export const credentialTypesByTransportType = new Map<DeviceTransportType, Devic |
725 | 753 | [DeviceTransportType.MQTT, [ |
726 | 754 | DeviceCredentialsType.ACCESS_TOKEN, DeviceCredentialsType.X509_CERTIFICATE, DeviceCredentialsType.MQTT_BASIC |
727 | 755 | ]], |
756 | + [DeviceTransportType.TCP, [ | |
757 | + DeviceCredentialsType.ACCESS_TOKEN | |
758 | + ]], | |
728 | 759 | [DeviceTransportType.COAP, [DeviceCredentialsType.ACCESS_TOKEN, DeviceCredentialsType.X509_CERTIFICATE]], |
729 | 760 | [DeviceTransportType.LWM2M, [DeviceCredentialsType.LWM2M_CREDENTIALS]], |
730 | 761 | [DeviceTransportType.SNMP, [DeviceCredentialsType.ACCESS_TOKEN]] | ... | ... |
... | ... | @@ -999,6 +999,7 @@ |
999 | 999 | "transport-type-lwm2m": "LWM2M", |
1000 | 1000 | "transport-type-lwm2m-hint": "LWM2M传输类型", |
1001 | 1001 | "transport-type-mqtt": "MQTT", |
1002 | + "transport-type-tcp": "TCP", | |
1002 | 1003 | "transport-type-mqtt-hint": "启用高级MQTT传输设置", |
1003 | 1004 | "transport-type-required": "传输方式必填。", |
1004 | 1005 | "transport-type-snmp-hint": "指定 SNMP 传输配置", | ... | ... |