diff --git a/integrations/pom.xml b/integrations/pom.xml index 84c30ba..b9ef8bd 100644 --- a/integrations/pom.xml +++ b/integrations/pom.xml @@ -39,6 +39,7 @@ sample-integrations ping msc-integration + wp-integration diff --git a/integrations/wp-integration/pom.xml b/integrations/wp-integration/pom.xml new file mode 100644 index 0000000..42f0ee0 --- /dev/null +++ b/integrations/wp-integration/pom.xml @@ -0,0 +1,53 @@ + + + + beaver-iot-integrations + com.milesight.beaveriot + 1.0.0 + ../../pom.xml + + 4.0.0 + + wp-integration + + + 17 + 17 + UTF-8 + + + + + com.milesight.beaveriot + context + ${project.version} + provided + + + org.projectlombok + lombok + provided + + + cn.hutool + hutool-all + 5.8.20 + + + com.fasterxml.jackson.core + jackson-annotations + + + org.springframework.boot + spring-boot-actuator + 3.3.4 + compile + + + com.milesight.beaveriot + entity-service + + + \ No newline at end of file diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/WpIntegrationBootstrap.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/WpIntegrationBootstrap.java new file mode 100644 index 0000000..eb6d409 --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/WpIntegrationBootstrap.java @@ -0,0 +1,33 @@ +package com.milesight.beaveriot.integration.wp; + +import com.milesight.beaveriot.context.integration.bootstrap.IntegrationBootstrap; +import com.milesight.beaveriot.context.integration.model.Integration; +import lombok.extern.slf4j.Slf4j; +import org.apache.camel.CamelContext; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class WpIntegrationBootstrap implements IntegrationBootstrap { + + + @Override + public void onPrepared(Integration integrationConfig) { + + } + + @Override + public void onStarted(Integration integrationConfig) { + log.info("WP integration started"); + } + + @Override + public void onDestroy(Integration integrationConfig) { + log.info("WP integration stopping"); + } + + @Override + public void customizeRoute(CamelContext context) throws Exception { + IntegrationBootstrap.super.customizeRoute(context); + } +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/constant/WpIntegrationConstants.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/constant/WpIntegrationConstants.java new file mode 100644 index 0000000..9e79dbb --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/constant/WpIntegrationConstants.java @@ -0,0 +1,39 @@ +package com.milesight.beaveriot.integration.wp.constant; + +import com.milesight.beaveriot.base.utils.StringUtils; + +public interface WpIntegrationConstants { + + String INTEGRATION_IDENTIFIER = "wp-integration"; + + public static String getKey(String propertyKey) { + return WpIntegrationConstants.INTEGRATION_IDENTIFIER + ".integration." + StringUtils.toSnakeCase(propertyKey); + } + + interface DeviceAdditionalDataName { + + String DEVICE_ID = "id"; + + } + + interface InternalPropertyIdentifier { + + interface Pattern { + String PREFIX = "_#"; + String SUFFIX = "#_"; + String TEMPLATE = "_#%s#_"; + + static boolean match(String key) { + return key.startsWith(PREFIX) && key.endsWith(SUFFIX); + } + } + + String LAST_SYNC_TIME = "_#last_sync_time#_"; + + static String getLastSyncTimeKey(String deviceKey) { + return String.format("%s.%s", deviceKey, LAST_SYNC_TIME); + } + + } + +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/controller/WpController.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/controller/WpController.java new file mode 100644 index 0000000..54bede3 --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/controller/WpController.java @@ -0,0 +1,32 @@ +package com.milesight.beaveriot.integration.wp.controller; + +import com.milesight.beaveriot.base.response.ResponseBody; +import com.milesight.beaveriot.base.response.ResponseBuilder; +import com.milesight.beaveriot.integration.wp.model.WpMeetingRequest; +import com.milesight.beaveriot.integration.wp.model.WpMeetingResponse; +import com.milesight.beaveriot.integration.wp.service.WpMeetingRoomService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + */ +@Slf4j +@RestController +@RequestMapping("/meeting") +public class WpController { + + + @Autowired + private WpMeetingRoomService wpMeetingRoomService; + + @PostMapping + public ResponseBody addMeeting(@RequestBody WpMeetingRequest wpMeetingRequest) { + return ResponseBuilder.success(wpMeetingRoomService.addMeetingRoom(wpMeetingRequest, null)); + } + +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/entity/WpIntegrationEntities.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/entity/WpIntegrationEntities.java new file mode 100644 index 0000000..be1266a --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/entity/WpIntegrationEntities.java @@ -0,0 +1,92 @@ +package com.milesight.beaveriot.integration.wp.entity; + +import com.milesight.beaveriot.context.integration.context.AddDeviceAware; +import com.milesight.beaveriot.context.integration.context.DeleteDeviceAware; +import com.milesight.beaveriot.context.integration.entity.annotation.Attribute; +import com.milesight.beaveriot.context.integration.entity.annotation.Entities; +import com.milesight.beaveriot.context.integration.entity.annotation.Entity; +import com.milesight.beaveriot.context.integration.entity.annotation.IntegrationEntities; +import com.milesight.beaveriot.context.integration.enums.EntityType; +import com.milesight.beaveriot.context.integration.model.ExchangePayload; +import lombok.*; +import lombok.experimental.FieldNameConstants; + +@Data +@EqualsAndHashCode(callSuper = true) +@IntegrationEntities +public class WpIntegrationEntities extends ExchangePayload { + + @Entity(type = EntityType.SERVICE, identifier = "add_device") + private AddDevice addDevice; + + @Entity(type = EntityType.SERVICE) + private SyncDevice syncDevice; + + @Entity(type = EntityType.SERVICE, identifier = "delete_device") + private DeleteDevice deleteDevice; + + + @Entity + private Openapi openapi; + + @Data + @EqualsAndHashCode(callSuper = true) + @Entities + public static class DetectReport extends ExchangePayload { + // Entity type inherits from parent entity (DetectReport) + @Entity + private Long consumedTime; + + @Entity + private Long onlineCount; + + @Entity + private Long offlineCount; + } + + @FieldNameConstants + @EqualsAndHashCode(callSuper = true) + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Entities + public static class Openapi extends ExchangePayload { + + @Entity(attributes = {@Attribute(minLength = 1)}) + private String username; + + @Entity(attributes = {@Attribute(minLength = 1)}) + private String password; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + @Entities + public static class AddDevice extends ExchangePayload implements AddDeviceAware { + + @Entity(attributes = {@Attribute(min = 1, max = 999)}) + private Integer memberCapacity; + + } + + @Data + @EqualsAndHashCode(callSuper = true) + @Entities + public static class DeleteDevice extends ExchangePayload implements DeleteDeviceAware { + } + + public enum DetectStatus { + STANDBY, DETECTING; + } + + @EqualsAndHashCode(callSuper = true) + @Data + @Builder + @NoArgsConstructor + @Entities + public static class SyncDevice extends ExchangePayload { + + } +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeeting.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeeting.java new file mode 100644 index 0000000..04b4162 --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeeting.java @@ -0,0 +1,60 @@ +package com.milesight.beaveriot.integration.wp.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WpMeeting { +// "schedule": { +// "conferenceRecordId": 245698, +// "startTime": 1732710600, +// "endTime": 1732716000, +// "originalStartTime": 1732710600, +// "originalEndTime": 1732716000, +// "allowCheckIn": false, +// "shouldCheckIn": false, +// "allowCheckOut": false, +// "status": "NOT_STARTED", +// "subject": "图像会议室的预约", +// "meetingRoomName": "图像会议室", +// "meetingRoomId": 2551, +// "meetingRoomType": "common", +// "buildingId": 1300, +// "floorId": 2210, +// "conferenceId": 249275, +// "host": { +// "memberId": 73825, +// "name": "test", +// "email": "linzy@milesight.com", +// "role": "HOST", +// "isResource": false, +// "accessibleRealSchedule": false, +// "userId": 78902, +// "hasCheckined": false +// }, +// "notificationTime": 15, +// "willStartReminderMinutes": [ +// 15 +// ], +// "scheduleType": "CONFERENCE", +// "canBeExtended": false, +// "roomExpired": false, +// "checkInStatistics": false +// }, + + private Integer conferenceRecordId; + private String subject; + private String meetingRoomName; + private String meetingRoomId; + private String meetingId; + private String firstStartTime; + private String lastEndTime; + private Long createTime; + private String startDate; + private String startTime; +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeetingRequest.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeetingRequest.java new file mode 100644 index 0000000..f533b54 --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeetingRequest.java @@ -0,0 +1,59 @@ +package com.milesight.beaveriot.integration.wp.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WpMeetingRequest { +// "schedule": { +// "conferenceRecordId": 245698, +// "startTime": 1732710600, +// "endTime": 1732716000, +// "originalStartTime": 1732710600, +// "originalEndTime": 1732716000, +// "allowCheckIn": false, +// "shouldCheckIn": false, +// "allowCheckOut": false, +// "status": "NOT_STARTED", +// "subject": "图像会议室的预约", +// "meetingRoomName": "图像会议室", +// "meetingRoomId": 2551, +// "meetingRoomType": "common", +// "buildingId": 1300, +// "floorId": 2210, +// "conferenceId": 249275, +// "host": { +// "memberId": 73825, +// "name": "test", +// "email": "linzy@milesight.com", +// "role": "HOST", +// "isResource": false, +// "accessibleRealSchedule": false, +// "userId": 78902, +// "hasCheckined": false +// }, +// "notificationTime": 15, +// "willStartReminderMinutes": [ +// 15 +// ], +// "scheduleType": "CONFERENCE", +// "canBeExtended": false, +// "roomExpired": false, +// "checkInStatistics": false +// }, + + private Integer type; + private String meeting; + private String id; + private String key; + private String subject; + private String first; + private String last; + private String date; + private String time; +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeetingResponse.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeetingResponse.java new file mode 100644 index 0000000..2dd7650 --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpMeetingResponse.java @@ -0,0 +1,20 @@ +package com.milesight.beaveriot.integration.wp.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WpMeetingResponse { +// {"data":{"id":249376,"subject":"111","scheduleSetting":{"startTime":"01:00","duration":30,"notificationTime":15,"willStartReminderMinutes":[15],"scheduleMode":"ONCE","simpleRepeatType":"NEVER","startDate":"2024-11-28","syncToThirdParty":true,"timezone":"UTC+8 Asia/Shanghai","enableDst":true,"firstStartTime":1732726800,"lastEndTime":1732730400},"host":{"memberId":73825,"name":"test","email":"linzy@milesight.com","role":"HOST","isResource":false,"accessibleRealSchedule":false,"userId":78902},"participants":[],"meetingRoom":{"id":2566,"name":"111","buildingName":"test","connected":false,"expired":false,"type":"common","buildingId":1300,"meetingRoomSettings":{"id":2512,"meetingRoomId":2566,"enterpriseId":101741,"restrictInWorkingHours":true,"allDay":true,"minStartTime":"00:00","maxEndTime":"00:00","minDuration":30,"maxDuration":180,"allowToBookBefore":365,"repeatable":true,"checkInRequired":true,"checkInNoticeTime":5,"checkInPermission":"Only Organizer","autoRelease":true,"autoReleaseTime":10,"checkOutPermission":"Only Organizer","checkInStatistics":false,"displayBooking":true,"autoCheckOut":true,"autoCheckOutDuration":15,"lightOnCheckIn":false,"lightOffCheckOut":false,"lightOnOffWork":false,"accessControlOn":false,"automaticCheckInWhenPeopleDetected":false,"automaticExtendWhenPeopleDetected":false}},"schedule":{"conferenceRecordId":245794,"startTime":1732726800,"endTime":1732728600,"originalStartTime":1732726800,"originalEndTime":1732728600,"allowCheckIn":false,"shouldCheckIn":false,"allowCheckOut":false,"status":"NOT_STARTED","subject":"111","meetingRoomName":"111","meetingRoomId":2566,"meetingRoomType":"common","buildingId":1300,"conferenceId":249376,"host":{"memberId":73825,"name":"test","email":"linzy@milesight.com","role":"HOST","isResource":false,"accessibleRealSchedule":false,"userId":78902,"hasCheckined":false},"notificationTime":15,"willStartReminderMinutes":[15],"scheduleType":"CONFERENCE","canBeExtended":false,"roomExpired":false,"checkInStatistics":false},"meetingService":false,"createVisitorRecord":false,"checkInStatistics":false,"visitType":0,"allowSelfRegistration":false,"conferenceReserveSource":"WEB_GRID"},"status":"Success","requestId":"59fd4722b9aeae5c8d4bc2c4000885c6"} + + private Integer code; + private String errorCode; + private String message; + + +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpResponsePayload.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpResponsePayload.java new file mode 100644 index 0000000..7c1b199 --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/model/WpResponsePayload.java @@ -0,0 +1,18 @@ +package com.milesight.beaveriot.integration.wp.model; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WpResponsePayload { + + private String status; + private String requestId; + private JsonNode data; +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpClient.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpClient.java new file mode 100644 index 0000000..606e8c2 --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpClient.java @@ -0,0 +1,307 @@ +package com.milesight.beaveriot.integration.wp.service; + + +import cn.hutool.json.JSONObject; +import com.milesight.beaveriot.context.api.EntityServiceProvider; +import com.milesight.beaveriot.context.api.EntityValueServiceProvider; +import com.milesight.beaveriot.integration.wp.constant.WpIntegrationConstants; +import com.milesight.beaveriot.integration.wp.model.WpMeeting; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@Component +public class WpClient { + + + @Autowired + private EntityValueServiceProvider entityValueServiceProvider; + + @Autowired + private EntityServiceProvider entityServiceProvider; + + private static String COOKIE; + + public void getToken() throws Exception { + val username = entityValueServiceProvider.findValueByKey(WpIntegrationConstants.getKey("openapi.username")); + val password = entityValueServiceProvider.findValueByKey(WpIntegrationConstants.getKey("openapi.password")); + if (username == null) { + return; + } + // 创建 HttpClient 实例 + HttpClient client = HttpClient.newHttpClient(); + // 创建请求体 JSON 字符串 + String jsonInputString = String.format("{\"password\":\"%s\",\"username\":\"%s\",\"terminalType\":\"web\"}", password.asText(), username.asText()); + + // 创建 HttpRequest 实例 + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI("https://workplace.yeastar.cn/services/oauth/token")) + .header("User-Agent", "Apifox/1.0.0 (https://apifox.com)") + .header("Content-Type", "application/json") + .header("Accept", "*/*") + .header("Cookie", "Authorization2=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjc4OTAyLCJ2ZXIiOjE3MzI2MjI4OTgsImdyYW50X3R5cGUiOiJwYXNzd29yZCIsInVzZXJfbmFtZSI6Imxpbnp5QG1pbGVzaWdodC5jb20iLCJwd2RfdmVyIjowLCJ0ZXJtaW5hbCI6IndlYiIsInJlZ2lvbiI6IkNOIiwianRpIjoiMjA0N2VmMjYtNmE1Zi00NmE5LTg0MTMtZjAxMTUyYjBkZmJjIiwiY2xpZW50X2lkIjoid2ViR2F0ZXdheSIsInRzIjoxNzMyNjIyODk4LCJleHBfaW4iOjI1OTIwMDB9.D6w5YSA4QGFdz8gfNRsjDdF46qOo8-ouOM0GHJx3ToQ") + .POST(HttpRequest.BodyPublishers.ofString(jsonInputString)) + .build(); + + // 发送请求并获取响应 + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + // 获取响应头中的 Set-Cookie + Map> headers = response.headers().map(); + List cookiesHeader = headers.get("Set-Cookie"); + COOKIE = cookiesHeader.get(0); + } + + public HttpResponse addConferenceRoom(String name, String memberCapacity) throws Exception { + getToken(); + // 请求体 + String jsonPayload = "{" + + "\"name\": \"" + name + "\"," + + "\"buildingId\": 1300," + + "\"facilityIds\": [6864, 6865, 6866, 6867, 6868]," + + "\"type\": \"common\"," + + "\"restrictInWorkingHours\": true," + + "\"minDuration\": 30," + + "\"maxDuration\": 180," + + "\"allowToBookBefore\": 365," + + "\"repeatable\": true," + + "\"checkInRequired\": true," + + "\"checkInNoticeTime\": 5," + + "\"checkInPermission\": \"Only Organizer\"," + + "\"autoRelease\": true," + + "\"autoReleaseTime\": 10," + + "\"checkOutPermission\": \"Only Organizer\"," + + "\"checkInStatistics\": false," + + "\"deviceIds\": []," + + "\"displayBooking\": true," + + "\"lightOnCheckIn\": false," + + "\"lightOffCheckOut\": false," + + "\"lightOnOffWork\": false," + + "\"automaticCheckInWhenPeopleDetected\": 0," + + "\"automaticExtendWhenPeopleDetected\": 0," + + "\"autoCheckOut\": true," + + "\"autoCheckOutDuration\": 15," + + "\"userIds\": []," + + "\"departmentIds\": [1009324]," + + "\"memberCapacity\": \"" + memberCapacity + "\"," + + "\"enterpriseId\": \"101741\"" + + "}"; + String url = "https://workplace.yeastar.cn/services/manager/conference/api/v1/meeting_rooms"; + // 发送请求 + return sendPostRequest(jsonPayload, url); + + } + + public void deleteConferenceRoom(int[] deviceIds) throws Exception { + getToken(); + String jsonPayload = "{\"ids\":" + Arrays.toString(deviceIds) + ",\"enterpriseId\":\"101741\"}"; + String url = "https://workplace.yeastar.cn/services/manager/conference/api/v1/meeting_rooms"; + sendPostRequest(jsonPayload, url); + } + + + public HttpResponse allConferenceRoom() throws Exception { + getToken(); + String url = "https://workplace.yeastar.cn/services/manager/conference/api/v1/meeting_rooms?enterpriseId=101741&pageSize=10&pageNumber=1¤tTime=1732597053"; + return sendPostRequest(null, url); + } + + public HttpResponse sendPostRequest(String jsonPayload, String url) throws Exception { + // 创建 HttpClient + HttpClient client = HttpClient.newHttpClient(); + // 创建 HttpRequest + HttpRequest request; + if (jsonPayload == null) { + // 创建 HttpRequest + request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("accept", "application/json, text/plain, */*") + .header("accept-language", "zh-cn") + .header("cache-control", "no-cache") + .header("enterpriseid", "101741") + .header("origin", "https://workplace.yeastar.cn") + .header("pragma", "no-cache") + .header("priority", "u=1, i") + .header("referer", "https://workplace.yeastar.cn/admin/") + .header("sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"") + .header("sec-ch-ua-mobile", "?0") + .header("sec-ch-ua-platform", "\"Windows\"") + .header("sec-fetch-dest", "empty") + .header("sec-fetch-mode", "cors") + .header("sec-fetch-site", "same-origin") + .header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36") + .header("cookie", COOKIE) + .header("content-type", "application/json;charset=UTF-8") + .GET() + .build(); + } else { + // 创建 HttpRequest + request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("accept", "application/json, text/plain, */*") + .header("accept-language", "zh-cn") + .header("cache-control", "no-cache") + .header("enterpriseid", "101741") + .header("origin", "https://workplace.yeastar.cn") + .header("pragma", "no-cache") + .header("priority", "u=1, i") + .header("referer", "https://workplace.yeastar.cn/admin/") + .header("sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"") + .header("sec-ch-ua-mobile", "?0") + .header("sec-ch-ua-platform", "\"Windows\"") + .header("sec-fetch-dest", "empty") + .header("sec-fetch-mode", "cors") + .header("sec-fetch-site", "same-origin") + .header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36") + .header("cookie", COOKIE) + .header("content-type", "application/json;charset=UTF-8") + .POST(HttpRequest.BodyPublishers.ofString(jsonPayload)) + .build(); + } + // 发送请求并获取响应 + return client.send(request, HttpResponse.BodyHandlers.ofString()); + } + + public HttpResponse addMeeting(WpMeeting wpMeeting) throws Exception { + long firstStartTimeLong = Long.parseLong(wpMeeting.getFirstStartTime()); + long lastEndTimeLong = Long.parseLong(wpMeeting.getLastEndTime()); + String key = extractNumber(wpMeeting.getMeetingRoomId()); + HttpResponse data = addMeetingRequest(key, wpMeeting.getSubject(), firstStartTimeLong, lastEndTimeLong, wpMeeting.getStartTime(), wpMeeting.getStartDate()); + return data; + } + + public static String extractNumber(String input) { + // 固定前缀和后缀 + String prefix = "wp-integration.device."; + String suffix = ".schedule"; + // 确保输入字符串包含前缀和后缀 + if (input.startsWith(prefix) && input.endsWith(suffix)) { + // 提取前缀后的部分 + String withoutPrefix = input.substring(prefix.length()); + // 提取后缀前的部分 + String number = withoutPrefix.substring(0, withoutPrefix.length() - suffix.length()); + return number; + } + return null; // 如果输入字符串不符合预期格式,则返回null + } + + public HttpResponse addMeetingRequest(String id, String subject, long firstStartTime, long lastEndTime, String startTime, String startDate) throws Exception { + getToken(); + String url = String.format("https://workplace.yeastar.cn/services/conference/api/v1/meeting_rooms/%s/conferences", id); + + // 计算两个时间戳之间的差值(以秒为单位) + long differenceInSeconds = lastEndTime - firstStartTime; + + // 将差值从秒转换为分钟 + long differenceInMinutes = differenceInSeconds / 60; + // 创建请求体 + String jsonInputString = String.format( + "{\n" + + " \"subject\": \"%s\",\n" + + " \"scheduleSetting\": {\n" + + " \"duration\": %d,\n" + + " \"willStartReminderMinutes\": [\n" + + " 15\n" + + " ],\n" + + " \"scheduleMode\": \"ONCE\",\n" + + " \"simpleRepeatType\": \"NEVER\",\n" + + " \"firstStartTime\": %d,\n" + + " \"lastEndTime\": %d,\n" + + " \"customRecurrencePattern\": null,\n" + + " \"syncToThirdParty\": true,\n" + + " \"startTime\": \"%s\",\n" + + " \"startDate\": \"%s\",\n" + + " \"timezone\": \"UTC+8 Asia/Shanghai\",\n" + + " \"enableDst\": true\n" + + " },\n" + + " \"host\": {\n" + + " \"id\": 78902,\n" + + " \"memberId\": 73825,\n" + + " \"name\": \"test\",\n" + + " \"onlyName\": \"test\",\n" + + " \"departmentIds\": [\n" + + " 1009324\n" + + " ]\n" + + " },\n" + + " \"participants\": [],\n" + + " \"visitType\": 0,\n" + + " \"allowSelfRegistration\": false,\n" + + " \"conferenceReserveSource\": \"WEB_GRID\"\n" + + "}", subject, differenceInMinutes, firstStartTime, lastEndTime, startTime, startDate); + + return sendPostRequest(jsonInputString, url); + } + + public HttpResponse deleteMeeting(WpMeeting wpMeeting) throws Exception { + getToken(); + String url = String.format("https://workplace.yeastar.cn/services/conference/api/v1/conferences/%s", wpMeeting.getMeetingId()); + String jsonInputString = String.format("{\n" + + " \"selectStartTime\": %s,\n" + + " \"selectMode\": \"SELECTED\",\n" + + " \"cancelVisitorRecord\": false\n" + + "}", wpMeeting); + return sendPostRequest(jsonInputString, url); + } + + public HttpResponse updateMeeting(WpMeeting wpMeeting) throws Exception { + long firstStartTimeLong = Long.parseLong(wpMeeting.getFirstStartTime()); + long lastEndTimeLong = Long.parseLong(wpMeeting.getLastEndTime()); + String key = extractNumber(wpMeeting.getMeetingRoomId()); + HttpResponse data = updateMeetingRequest(key, wpMeeting.getSubject(), firstStartTimeLong, lastEndTimeLong, wpMeeting.getStartTime(), wpMeeting.getStartDate()); + return data; + } + + public HttpResponse updateMeetingRequest(String id, String subject, long firstStartTime, long lastEndTime, String startTime, String startDate) throws Exception { + getToken(); + String url = String.format("https://workplace.yeastar.cn/services/conference/api/v1/conferences/%s", id); + + // 计算两个时间戳之间的差值(以秒为单位) + long differenceInSeconds = lastEndTime - firstStartTime; + + // 将差值从秒转换为分钟 + long differenceInMinutes = differenceInSeconds / 60; + // 创建请求体 + String jsonInputString = String.format( + "{\n" + + " \"subject\": \"%s\",\n" + + " \"scheduleSetting\": {\n" + + " \"duration\": %d,\n" + + " \"willStartReminderMinutes\": [\n" + + " 15\n" + + " ],\n" + + " \"scheduleMode\": \"ONCE\",\n" + + " \"simpleRepeatType\": \"NEVER\",\n" + + " \"firstStartTime\": %d,\n" + + " \"lastEndTime\": %d,\n" + + " \"customRecurrencePattern\": null,\n" + + " \"syncToThirdParty\": true,\n" + + " \"startTime\": \"%s\",\n" + + " \"startDate\": \"%s\",\n" + + " \"timezone\": \"UTC+8 Asia/Shanghai\",\n" + + " \"enableDst\": true\n" + + " },\n" + + " \"host\": {\n" + + " \"id\": 78902,\n" + + " \"memberId\": 73825,\n" + + " \"name\": \"test\",\n" + + " \"onlyName\": \"test\",\n" + + " \"departmentIds\": [\n" + + " 1009324\n" + + " ]\n" + + " },\n" + + " \"participants\": [],\n" + + " \"visitType\": 0,\n" + + " \"allowSelfRegistration\": false,\n" + + " \"conferenceReserveSource\": \"WEB_GRID\"\n" + + "}", subject, differenceInMinutes, firstStartTime, lastEndTime, startTime, startDate); + return sendPostRequest(jsonInputString, url); + } +} diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpDeviceService.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpDeviceService.java new file mode 100644 index 0000000..b71d2f9 --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpDeviceService.java @@ -0,0 +1,168 @@ +package com.milesight.beaveriot.integration.wp.service; + + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.milesight.beaveriot.base.utils.JsonUtils; +import com.milesight.beaveriot.context.api.DeviceServiceProvider; +import com.milesight.beaveriot.context.constants.IntegrationConstants; +import com.milesight.beaveriot.context.integration.enums.AccessMod; +import com.milesight.beaveriot.context.integration.enums.EntityValueType; +import com.milesight.beaveriot.context.integration.model.*; +import com.milesight.beaveriot.context.integration.model.event.ExchangeEvent; +import com.milesight.beaveriot.eventbus.annotations.EventSubscribe; +import com.milesight.beaveriot.eventbus.api.Event; +import com.milesight.beaveriot.integration.wp.constant.WpIntegrationConstants; +import com.milesight.beaveriot.integration.wp.entity.WpIntegrationEntities; +import com.milesight.beaveriot.integration.wp.model.WpMeeting; +import com.milesight.beaveriot.integration.wp.model.WpMeetingRequest; +import com.milesight.beaveriot.integration.wp.model.WpResponsePayload; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.*; + + +@Service +@Slf4j +public class WpDeviceService { + + @Autowired + private DeviceServiceProvider deviceServiceProvider; + + @Autowired + private WpMeetingRoomService wpMeetingRoomService; + + @SneakyThrows + @EventSubscribe(payloadKeyExpression = "wp-integration.device.*", eventType = ExchangeEvent.EventType.DOWN) + public void onDeviceExchangeEvent(ExchangeEvent event) { + val exchangePayload = event.getPayload(); + } + + @SneakyThrows + @EventSubscribe(payloadKeyExpression = "wp-integration.integration.sync_device", eventType = ExchangeEvent.EventType.DOWN) + public void onSyncDevice(Event event) { + try { + + val wpResponsePayload = wpMeetingRoomService.allConferenceRoom(); + if (wpResponsePayload == null) { + return; + } + String integrationId = WpIntegrationConstants.INTEGRATION_IDENTIFIER; + val data = wpResponsePayload.getData(); + ArrayNode arrayNode = (ArrayNode) data.get("list"); + arrayNode.forEach(node -> { + val identifier = node.get("id").asText(); + val deviceName = node.get("name").asText(); + val memberCapacity = node.get("memberCapacity").asInt(); + ArrayNode schedules = (ArrayNode) node.get("schedules"); + // 添加设备 + addMeetingDevice(deviceName, memberCapacity, identifier); + if (schedules.size() != 0) { + val deviceKey = IntegrationConstants.formatIntegrationDeviceKey(integrationId, identifier); + // 使用 Jackson 进行反序列化 + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true); + List wpMeetings = objectMapper.convertValue(schedules, List.class); + wpMeetings.forEach(meeting -> { + // 保存预约的会议历史数据 + try { + wpMeetingRoomService.addMeetingRoom(WpMeetingRequest.builder().type(0).build(), meeting); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + }); + } catch (Exception e) { + log.error("Error in onSyncDevice", e); + } + } + + @SneakyThrows + @EventSubscribe(payloadKeyExpression = "wp-integration.integration.add_device.*", eventType = ExchangeEvent.EventType.DOWN) + public void onAddDevice(Event event) { + val deviceName = event.getPayload().getContext("device_name", "Device Name"); + WpIntegrationEntities.AddDevice addDevice = event.getPayload(); + Integer memberCapacity = addDevice.getMemberCapacity(); + + WpResponsePayload wpResponsePayload = wpMeetingRoomService.addConferenceRoom(addDevice, deviceName); + if (wpResponsePayload == null) { + log.error("Error in addConferenceRoom"); + return; + } + val identifier = wpResponsePayload.getData().get("id").asText(); + addMeetingDevice(deviceName, memberCapacity, identifier); + } + + private void addMeetingDevice(String deviceName, Integer memberCapacity, String identifier) { + List entities = new ArrayList<>(); + String integrationId = WpIntegrationConstants.INTEGRATION_IDENTIFIER; + val deviceKey = IntegrationConstants.formatIntegrationDeviceKey(integrationId, identifier); + WpMeeting wpMeeting = WpMeeting.builder() + .meetingRoomId(identifier) + .build(); + // 添加额外实体 + addAdditionalEntities(deviceKey, entities, wpMeeting); + // 添加设备 + addDevice(deviceName, memberCapacity, identifier, entities); + } + + private static void addAdditionalEntities(String deviceKey, List entities, WpMeeting wpMeeting) { + val integrationId = WpIntegrationConstants.INTEGRATION_IDENTIFIER; + + Map attributes = new HashMap<>(); + if (wpMeeting != null) { + // 遍历wpSchedule的所有属性,把属性值 + for (Map.Entry entry : JsonUtils.toMap(wpMeeting).entrySet()) { + val key = entry.getKey(); + val value = entry.getValue(); + attributes.put(key, value); + + } + } + entities.add(new EntityBuilder(integrationId, deviceKey) + .identifier("schedule") + .property("schedule", AccessMod.RW) + .valueType(EntityValueType.STRING) + .attributes(attributes) + .build()); + entities.add(new EntityBuilder(integrationId, deviceKey) + .identifier(WpIntegrationConstants.InternalPropertyIdentifier.LAST_SYNC_TIME) + .property(WpIntegrationConstants.InternalPropertyIdentifier.LAST_SYNC_TIME, AccessMod.R) + .valueType(EntityValueType.LONG) + .attributes(Map.of("internal", true)) + .build()); + } + + private void addDevice(String deviceName, Integer memberCapacity, String identifier, List entities) { + val integrationId = WpIntegrationConstants.INTEGRATION_IDENTIFIER; + val device = new DeviceBuilder(integrationId) + .name(deviceName) + .identifier(identifier) + .additional(Map.of("memberCapacity", memberCapacity)) + .entities(entities) + .build(); + deviceServiceProvider.save(device); + } + + @SneakyThrows + @EventSubscribe(payloadKeyExpression = "wp-integration.integration.delete_device", eventType = ExchangeEvent.EventType.DOWN) + public void onDeleteDevice(Event event) { + val device = deviceServiceProvider.findByIdentifier( + ((Device) event.getPayload().getContext("device")).getIdentifier(), WpIntegrationConstants.INTEGRATION_IDENTIFIER); + val deviceId = device.getIdentifier(); + wpMeetingRoomService.deleteConferenceRoom(deviceId); + deviceServiceProvider.deleteById(device.getId()); + } + +} + + diff --git a/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpMeetingRoomService.java b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpMeetingRoomService.java new file mode 100644 index 0000000..f2b513d --- /dev/null +++ b/integrations/wp-integration/src/main/java/com/milesight/beaveriot/integration/wp/service/WpMeetingRoomService.java @@ -0,0 +1,179 @@ +package com.milesight.beaveriot.integration.wp.service; + + +import cn.hutool.json.JSONObject; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.milesight.beaveriot.context.api.DeviceServiceProvider; +import com.milesight.beaveriot.context.api.EntityValueServiceProvider; +import com.milesight.beaveriot.context.api.ExchangeFlowExecutor; +import com.milesight.beaveriot.context.integration.model.ExchangePayload; +import com.milesight.beaveriot.entity.repository.EntityHistoryRepository; +import com.milesight.beaveriot.integration.wp.entity.WpIntegrationEntities; +import com.milesight.beaveriot.integration.wp.model.WpMeeting; +import com.milesight.beaveriot.integration.wp.model.WpMeetingRequest; +import com.milesight.beaveriot.integration.wp.model.WpMeetingResponse; +import com.milesight.beaveriot.integration.wp.model.WpResponsePayload; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.net.http.HttpResponse; +import java.util.HashMap; +import java.util.Map; + + +@Service +@Slf4j +public class WpMeetingRoomService { + + + @Autowired + private DeviceServiceProvider deviceServiceProvider; + + @Autowired + private EntityValueServiceProvider entityValueServiceProvider; + + @Autowired + private ExchangeFlowExecutor exchangeFlowExecutor; + + @Autowired + private EntityHistoryRepository entityHistoryRepository; + + @Autowired + private WpClient wpClient; + + + public WpResponsePayload addConferenceRoom(WpIntegrationEntities.AddDevice addDevice, String deviceName) throws Exception { + Integer memberCapacity = addDevice.getMemberCapacity(); + HttpResponse response = wpClient.addConferenceRoom(deviceName, String.valueOf(memberCapacity)); + if (response.statusCode() == 200) { + // 使用 Jackson 进行反序列化 + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true); + val wpResponsePayload = objectMapper.readValue(response.body(), WpResponsePayload.class); + return wpResponsePayload; + } + return null; + } + + public void deleteConferenceRoom(String deviceId) throws Exception { + //String[]转int[] + int[] intArray = new int[1]; + intArray[0] = Integer.parseInt(deviceId); + wpClient.deleteConferenceRoom(intArray); + } + + public WpResponsePayload allConferenceRoom() throws Exception { + HttpResponse response = wpClient.allConferenceRoom(); + if (response.statusCode() == 200) { + // 使用 Jackson 进行反序列化 + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true); + WpResponsePayload wpResponsePayload = objectMapper.readValue(response.body(), WpResponsePayload.class); + return wpResponsePayload; + } + return null; + } + + public WpMeetingResponse addMeetingRoom(WpMeetingRequest wpMeetingRequest, WpMeeting wpMeeting) { + WpMeetingResponse wpMeetingResponse = new WpMeetingResponse(); + String id = ""; + try { + if (wpMeetingRequest.getType() == 0) { + wpMeetingResponse = addMeeting(wpMeeting); + } else if (wpMeetingRequest.getType() == 1) { + wpMeetingResponse = updateMeeting(wpMeeting); + } else if (wpMeetingRequest.getType() == 2) { + wpMeetingResponse = deleteMeeting(wpMeeting); + } + } catch (Exception e) { + log.error("addMeeting error", e); + } + if (wpMeeting == null) { + wpMeeting = WpMeeting.builder() + .subject(wpMeetingRequest.getSubject()) + .meetingRoomId(wpMeetingRequest.getKey()) + .firstStartTime(wpMeetingRequest.getFirst()) + .lastEndTime(wpMeetingRequest.getLast()) + .startDate(wpMeetingRequest.getDate()) + .startTime(wpMeetingRequest.getTime()) + .meetingId(wpMeetingRequest.getMeeting()) + .build(); + } else { + wpMeeting.setMeetingId(id); + } + wpMeeting.setCreateTime(System.currentTimeMillis()); + if (wpMeetingRequest.getType() == 2) { + wpMeeting.setSubject(null); + } + Map result = new HashMap(); + result.put(wpMeeting.getMeetingRoomId(), wpMeeting.toString()); + entityValueServiceProvider.saveHistoryRecord(ExchangePayload.create(result), Long.parseLong(wpMeeting.getFirstStartTime()) * 1000); + return wpMeetingResponse; + } + + public WpMeetingResponse addMeeting(WpMeeting wpMeeting) { + WpMeetingResponse result = WpMeetingResponse.builder().build(); + HttpResponse data = null; + try { + data = wpClient.addMeeting(wpMeeting); + // 解析 JSON 响应 + JSONObject jsonObject = new JSONObject(data.body()); + // 提取 "data" 对象 + JSONObject dataObject = jsonObject.getJSONObject("data"); + if (dataObject == null) { + return null; + } + result.setCode(dataObject.getInt("code")); + result.setMessage(String.valueOf(dataObject.getInt("id"))); + } catch (Exception e) { + result.setCode(500); + } + return result; + } + + public WpMeetingResponse updateMeeting(WpMeeting wpMeeting) { + WpMeetingResponse result = WpMeetingResponse.builder().build(); + HttpResponse data = null; + try { + data = wpClient.updateMeeting(wpMeeting); + // 解析 JSON 响应 + JSONObject jsonObject = new JSONObject(data.body()); + // 提取 "data" 对象 + JSONObject dataObject = jsonObject.getJSONObject("data"); + if (dataObject == null) { + return null; + } + result.setCode(dataObject.getInt("code")); + result.setMessage(String.valueOf(dataObject.getInt("id"))); + } catch (Exception e) { + result.setCode(500); + } + return result; + } + + public WpMeetingResponse deleteMeeting(WpMeeting wpMeeting) { + WpMeetingResponse result = WpMeetingResponse.builder().build(); + HttpResponse data = null; + try { + data = wpClient.deleteMeeting(wpMeeting); + // 解析 JSON 响应 + JSONObject jsonObject = new JSONObject(data.body()); + // 提取 "data" 对象 + JSONObject dataObject = jsonObject.getJSONObject("data"); + if (dataObject == null) { + return null; + } + result.setCode(dataObject.getInt("code")); + result.setMessage(String.valueOf(dataObject.getInt("id"))); + } catch (Exception e) { + result.setCode(500); + } + return result; + } +} diff --git a/integrations/wp-integration/src/main/resources/integration.yaml b/integrations/wp-integration/src/main/resources/integration.yaml new file mode 100644 index 0000000..e90fa92 --- /dev/null +++ b/integrations/wp-integration/src/main/resources/integration.yaml @@ -0,0 +1,10 @@ +integration: + wp-integration: # integration identifier + name: WP Conference Management # integration name + description: "WP Conference Management" # integration description + icon-url: "public/wp_icon.svg" + enabled: true # whether enable this integration. Must be "true" for now + + entity-identifier-add-device: add_device + # the same to deleteDevice identifier + entity-identifier-delete-device: delete_device diff --git a/integrations/wp-integration/src/main/resources/static/public/wp_icon.svg b/integrations/wp-integration/src/main/resources/static/public/wp_icon.svg new file mode 100644 index 0000000..9dc7695 --- /dev/null +++ b/integrations/wp-integration/src/main/resources/static/public/wp_icon.svg @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file