diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/BaseHikvisionResponse.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/BaseHikvisionResponse.java new file mode 100644 index 000000000..7fc9aff28 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/BaseHikvisionResponse.java @@ -0,0 +1,13 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor; + +import lombok.Data; + +@Data +public abstract class BaseHikvisionResponse { + private String code; + private String msg; + + public boolean isSuccess() { + return "0".equals(code); + } +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/PageListData.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/PageListData.java new file mode 100644 index 000000000..22684f7ea --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/PageListData.java @@ -0,0 +1,15 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor; + +import lombok.Data; +import org.apache.poi.ss.formula.functions.T; + +import java.util.List; + +@Data +public class PageListData { + + private Long total; + private Integer pageNo; + private Integer pageSize; + private List list; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/PageListRequest.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/PageListRequest.java new file mode 100644 index 000000000..a2cec0ae1 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/PageListRequest.java @@ -0,0 +1,26 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor; + + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.Data; + +/** + * 通用分页请求参数基类 + * 适用于所有分页查询接口 + */ +@Data +public class PageListRequest { + /** + * 当前页码(必须>0) + */ + @Min(value = 1, message = "页码必须大于0") + private Integer pageNo; + + /** + * 每页记录数(1-1000) + */ + @Min(value = 1, message = "每页最少1条记录") + @Max(value = 1000, message = "每页最多1000条记录") + private Integer pageSize; +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/VisitorObjectResponse.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/VisitorObjectResponse.java new file mode 100644 index 000000000..1399ac03e --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/VisitorObjectResponse.java @@ -0,0 +1,29 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor; + + +import lombok.Data; +import org.apache.poi.ss.formula.functions.T; + +/** + * 访客预约响应主类 + */ +@Data +public class VisitorObjectResponse { + + /** + * 返回码 + * 0-成功,其他见错误码表 + */ + private String code; + + /** + * 返回描述 + */ + private String msg; + + /** + * 预约结果数据 + */ + private T data; + +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/VisitorResponse.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/VisitorResponse.java new file mode 100644 index 000000000..8b1dcb64c --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/VisitorResponse.java @@ -0,0 +1,12 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor; + +import lombok.Data; +import org.apache.poi.ss.formula.functions.T; + +@Data +public class VisitorResponse { + + private String code; + private String msg; + private PageListData data; +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorAppointmentRequest.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorAppointmentRequest.java new file mode 100644 index 000000000..316fdb60a --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorAppointmentRequest.java @@ -0,0 +1,59 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor.request.make; + + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +import java.util.List; + +/* + * 访客预约请求参数主类 + * API路径:/api/visitor/v2/appointment + */ +@Data +public class VisitorAppointmentRequest { + + /** + * 被访人唯一标识(必填) + * 通过人员查询接口获取的personId + */ + @NotBlank(message = "被访人ID不能为空") + private String receptionistId; + + /** + * 访客权限配置 + */ + private VisitorPermissionSet visitorPermissionSet; + + /** + * 预计来访时间(必填) + * ISO8601格式:yyyy-MM-ddTHH:mm:ss+时区 + */ + @NotBlank(message = "来访时间不能为空") + private String visitStartTime; + + /** + * 预计离开时间(必填) + * 必须晚于当前时间和来访时间 + */ + @NotBlank(message = "离开时间不能为空") + private String visitEndTime; + + /** + * 来访事由(可选) + * 长度限制:0-32个字符 + */ + @Size(max = 32, message = "来访事由最长32个字符") + private String visitPurpose; + + /** + * 访客信息列表(必填) + * 最大支持128位访客,其中最多4位可带人脸信息 + */ + @Valid + private List visitorInfoList; + +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorInfo.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorInfo.java new file mode 100644 index 000000000..dc3ff54f3 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorInfo.java @@ -0,0 +1,135 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor.request.make; + + +import jakarta.validation.constraints.*; +import lombok.Builder; +import lombok.Data; +import org.hibernate.validator.constraints.Range; + +import java.util.Map; + + +/** + * 访客详细信息实体 + * 对应接口文档中 visitorInfoList 的单个访客对象 + */ +@Data +@Builder +public class VisitorInfo { + + /** + * 访客姓名(必填) + * 长度限制:1-32字符 + * 禁止字符:’ / \ : * ? " < > + */ + @NotBlank(message = "访客姓名不能为空") + @Size(min = 1, max = 32, message = "姓名长度需在1-32个字符之间") + @Pattern(regexp = "^[^'/\\\\:*?\"<>]+$", message = "姓名包含非法字符") + private String visitorName; + + /** + * 性别(必填) + * 1-男,2-女 + */ + @NotNull(message = "性别不能为空") + @Min(value = 1, message = "性别值无效") + @Max(value = 2, message = "性别值无效") + private Integer gender; + + /** + * 联系电话(必填) + * 需符合手机号格式:11位数字 + */ + @NotBlank(message = "联系电话不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + private String phoneNo; + + /** + * 车牌号(可选) + * 格式示例:浙A12345 + * 禁止字符:’ / \ : * ? " < > + */ + @Pattern(regexp = "^[^'/\\\\:*?\"<>]*$", message = "车牌号包含非法字符") + private String plateNo; + + /** + * 证件类型(可选) + * 111-身份证,其他类型参考附录A.11 + */ + @Min(value = 1, message = "证件类型代码无效") + private Integer certificateType; + + /** + * 证件号码(可选) + * 当填写证件号时,证件类型必填 + * 长度限制:1-20位字符 + */ + @Size(min = 1, max = 20, message = "证件号长度需在1-20位之间") + private String certificateNo; + + /** + * 证件地址(可选) + * 格式示例:江苏南京 + * 长度限制:1-128字符 + */ + @Size(max = 128, message = "证件地址最长128个字符") + private String certAddr; + + /** + * 发证机关(可选) + * 格式示例:南京市公安局 + * 长度限制:1-32字符 + */ + @Size(max = 32, message = "发证机关最长32个字符") + private String certIssuer; + + /** + * 民族(可选) + * 使用国标GB/T 3304-1991编码 + */ + @Min(value = 1, message = "民族编码无效") + private Integer nation; + + /** + * 籍贯(可选) + * 格式示例:江苏南京 + * 长度限制:1-32字符 + */ + @Size(max = 32, message = "籍贯最长32个字符") + private String birthplace; + + /** + * 访客头像(可选) + * Base64编码的JPG图片(不超过200KB) + * 需要先通过人脸质量检测(评分≥75) + */ + @Size(max = 300000, message = "头像大小超过200KB限制") // 200KB约对应约273,000字符 + private String visitorPhoto; + + /** + * 来访单位(可选) + * 格式示例:海康威视 + * 长度限制:1-32字符 + * 禁止字符:’ / \ : * ? " < > + */ + @Size(max = 32, message = "单位名称最长32个字符") + @Pattern(regexp = "^[^'/\\\\:*?\"<>]*$", message = "单位名称包含非法字符") + private String visitorWorkUnit; + + /** + * 访客住址(可选) + * 格式示例:人民路一号 + * 长度限制:1-128字符 + */ + @Size(max = 128, message = "地址最长128个字符") + private String visitorAddress; + + /** + * 扩展字段(用于平台自定义属性) + * Key需与平台定义的属性key一致 + * 示例:"emergencyContact": "李四" + */ + private Map extendedFields; + + +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorPermissionSet.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorPermissionSet.java new file mode 100644 index 000000000..5cff89887 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/make/VisitorPermissionSet.java @@ -0,0 +1,32 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor.request.make; + + + +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; + +/** + * 访客权限组配置 + */ + +@Data +public class VisitorPermissionSet { + + /** + * 是否使用默认权限组 + * 1-使用,非1-不使用 + */ + @Pattern(regexp = "1|.*", message = "默认权限组标识格式错误") + private String defaultPrivilegeGroupFlag; + + /** + * 权限组ID列表 + * 当defaultPrivilegeGroupFlag不为1时必填 + */ + @Size(min = 1, message = "权限组ID列表不能为空") + private List privilegeGroupIds; + +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/person/PersonInfo.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/person/PersonInfo.java new file mode 100644 index 000000000..c789bbf84 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/request/person/PersonInfo.java @@ -0,0 +1,38 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor.request.person; + + +import java.time.LocalDateTime; +import java.util.List; + +import lombok.Data; + +/** + * 人员详细信息实体 + */ +@Data +public class PersonInfo { + private String personId; + private String personName; + private Integer gender; + private String orgIndexCode; + private String phoneNo; + private String jobNo; + private Integer certificateType; + private String certificateNo; + private LocalDateTime createTime; + private LocalDateTime updateTime; + private String orgPath; + private String orgPathName; + private List personPhoto; + + /** + * 人员照片信息 + */ + @Data + public static class PersonPhoto { + private String personPhotoIndexCode; + private String picUri; + private String serverIndexCode; + private String personId; + } +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/response/make/AppointmentData.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/response/make/AppointmentData.java new file mode 100644 index 000000000..9f9d78f31 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/response/make/AppointmentData.java @@ -0,0 +1,26 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor.response.make; + + +import lombok.Data; + +import java.util.List; + +/** + * 预约结果数据封装 + */ + +@Data +public class AppointmentData { + + /** + * 预约记录唯一ID + * 用于后续修改/取消操作 + */ + private String appointRecordId; + + /** + * 预约详细信息列表 + */ + private List appointmentInfoList; + +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/response/make/AppointmentInfo.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/response/make/AppointmentInfo.java new file mode 100644 index 000000000..245b73622 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/visitor/response/make/AppointmentInfo.java @@ -0,0 +1,28 @@ +package com.bsd.pjcloud.thirdparty.dto.visitor.response.make; + + +import lombok.Data; + +/** + * 单条预约详细信息 + */ +@Data +public class AppointmentInfo { + + /* + * 访客姓名 + */ + private String visitorName; + + /** + * 被访人ID + */ + private String receptionistId; + + /** + * 动态二维码内容 + * 需转换为二维码图片 + */ + private String QRCode; + +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml index 34dfa75ed..86b71a350 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/pom.xml @@ -13,6 +13,12 @@ pjcloud-thirdparty-biz + + com.hikvision.ga + artemis-http-client + 1.1.3 + + com.bsd pjcloud-upms-api diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/HikvisionProperties.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/HikvisionProperties.java new file mode 100644 index 000000000..643baa086 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/config/HikvisionProperties.java @@ -0,0 +1,31 @@ +package com.bsd.pjcloud.thirdparty.config; + +import com.hikvision.artemis.sdk.config.ArtemisConfig; +import jakarta.annotation.PostConstruct; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +@ConfigurationProperties(prefix = "hikvision") +@Data +public class HikvisionProperties { + private String host; + private String appKey; + private String appSecret; + private String artemisPath; + private Map apis = new HashMap<>(); + + // 初始化配置到SDK + @PostConstruct + public void initConfig() { + ArtemisConfig.host = this.host; + ArtemisConfig.appKey = this.appKey; + ArtemisConfig.appSecret = this.appSecret; + } + + // getters and setters +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionApiException.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionApiException.java new file mode 100644 index 000000000..bb06230a5 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionApiException.java @@ -0,0 +1,7 @@ +package com.bsd.pjcloud.thirdparty.exception; + +public class HikvisionApiException extends HikvisionException { + public HikvisionApiException(String code, String msg) { + super(code, msg); + } +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionConfigException.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionConfigException.java new file mode 100644 index 000000000..15e82e206 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionConfigException.java @@ -0,0 +1,8 @@ +package com.bsd.pjcloud.thirdparty.exception; + +// 特定异常类型(可选) +public class HikvisionConfigException extends HikvisionException { + public HikvisionConfigException(String msg) { + super("CONFIG_ERROR", msg); + } +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionException.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionException.java new file mode 100644 index 000000000..63f4e8fc4 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/exception/HikvisionException.java @@ -0,0 +1,23 @@ +package com.bsd.pjcloud.thirdparty.exception; + +import lombok.Data; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +// 基础业务异常 +@Getter +public class HikvisionException extends RuntimeException { + private final String code; + private final String msg; + + public HikvisionException(String code, String msg) { + super(msg); + this.code = code; + this.msg = msg; + } +} + + diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/BaseHikvisionService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/BaseHikvisionService.java new file mode 100644 index 000000000..f2191ac8c --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/BaseHikvisionService.java @@ -0,0 +1,75 @@ +package com.bsd.pjcloud.thirdparty.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONException; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.bsd.pjcloud.thirdparty.config.HikvisionProperties; +import com.bsd.pjcloud.thirdparty.dto.visitor.BaseHikvisionResponse; +import com.bsd.pjcloud.thirdparty.exception.HikvisionApiException; +import com.bsd.pjcloud.thirdparty.exception.HikvisionException; +import com.hikvision.artemis.sdk.ArtemisHttpUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +import static javax.swing.UIManager.put; + + +@Slf4j +public abstract class BaseHikvisionService { + protected final HikvisionProperties properties; + + protected BaseHikvisionService(HikvisionProperties properties) { + this.properties = properties; + } + +/** + * 公共方法:构建API路径 + */ + protected String buildApiPath(String apiKey) { + String apiSuffix = properties.getApis().get(apiKey); + if (StringUtils.isBlank(apiSuffix)) { + throw new HikvisionException("CONFIG_ERROR", "接口路径未配置: " + apiKey); + } + return properties.getArtemisPath() + apiSuffix; + } + + protected String sendPostRequest(String apiKey, JSONObject body) { + try { + Map path = new HashMap() {{ + put("https://", buildApiPath(apiKey)); + }}; + + return ArtemisHttpUtil.doPostStringArtemis( + path, + body.toJSONString(), + null, + null, + "application/json", + null + ); + } catch (Exception e) { + log.error("系统异常: {}", e.getMessage(), e); + throw new HikvisionException("SYSTEM_ERROR", "系统内部错误"); + } + } + + protected T parseResponse(String json, TypeReference typeRef) { + try { + T response = JSON.parseObject(json, typeRef); + if (response instanceof BaseHikvisionResponse) { + BaseHikvisionResponse baseRes = (BaseHikvisionResponse) response; + if (!baseRes.isSuccess()) { + throw new HikvisionApiException(baseRes.getCode(), baseRes.getMsg()); + } + } + return response; + } catch (JSONException e) { + log.error("响应解析失败: {}", json); + throw new HikvisionException("PARSE_ERROR", "响应数据格式异常"); + } + } +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/HikvisionService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/HikvisionService.java new file mode 100644 index 000000000..811d90b0c --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/HikvisionService.java @@ -0,0 +1,192 @@ +package com.bsd.pjcloud.thirdparty.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONException; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.bsd.pjcloud.thirdparty.config.HikvisionProperties; +import com.bsd.pjcloud.thirdparty.dto.visitor.PageListRequest; +import com.bsd.pjcloud.thirdparty.dto.visitor.VisitorResponse; +import com.bsd.pjcloud.thirdparty.dto.visitor.request.make.VisitorAppointmentRequest; +import com.bsd.pjcloud.thirdparty.dto.visitor.VisitorObjectResponse; +import com.bsd.pjcloud.thirdparty.dto.visitor.request.person.PersonInfo; +import com.bsd.pjcloud.thirdparty.exception.HikvisionException; +import com.hikvision.artemis.sdk.ArtemisHttpUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + +@Service +@RequiredArgsConstructor +@Slf4j +public class HikvisionService { + private final HikvisionProperties properties; + + + public VisitorObjectResponse createVisitorAppointment(VisitorAppointmentRequest request) { + // 1. 构建API路径 + String apiPath = properties.getArtemisPath() + + properties.getApis().get("visitor-appointment"); + + // 2. 构建请求路径参数 + Map pathParams = new HashMap<>(2); + pathParams.put("https://", apiPath); // 根据环境选择协议 + + // 3. 转换请求体 + JSONObject requestBody = convertRequestToJSON(request); + + // 4. 发送请求 + String responseJson = ArtemisHttpUtil.doPostStringArtemis( + pathParams, + requestBody.toJSONString(), + null, + null, + "application/json", + null + ); + + // 5. 解析响应 + return JSON.parseObject(responseJson, VisitorObjectResponse.class); + } + + private JSONObject convertRequestToJSON(VisitorAppointmentRequest request) { + JSONObject json = new JSONObject(); + json.put("receptionistId", request.getReceptionistId()); + + // 处理权限组 + if (request.getVisitorPermissionSet() != null) { + JSONObject permissionSet = new JSONObject(); + permissionSet.put("defaultPrivilegeGroupFlag", + request.getVisitorPermissionSet().getDefaultPrivilegeGroupFlag()); + permissionSet.put("privilegeGroupIds", + request.getVisitorPermissionSet().getPrivilegeGroupIds()); + json.put("visitorPermissionSet", permissionSet); + } + + json.put("visitStartTime", request.getVisitStartTime()); + json.put("visitEndTime", request.getVisitEndTime()); + json.put("visitPurpose", request.getVisitPurpose()); + + // 处理访客列表 + List visitorList = request.getVisitorInfoList().stream().map(visitor -> { + JSONObject visitorJson = new JSONObject(); + visitorJson.put("visitorName", visitor.getVisitorName()); + visitorJson.put("gender", visitor.getGender()); + visitorJson.put("phoneNo", visitor.getPhoneNo()); + visitorJson.put("plateNo", visitor.getPlateNo()); + visitorJson.put("certificateType", visitor.getCertificateType()); + visitorJson.put("certificateNo", visitor.getCertificateNo()); + visitorJson.put("certAddr", visitor.getCertAddr()); + visitorJson.put("certIssuer", visitor.getCertIssuer()); + visitorJson.put("nation", visitor.getNation()); + visitorJson.put("birthplace", visitor.getBirthplace()); + visitorJson.put("visitorPhoto", visitor.getVisitorPhoto()); + visitorJson.put("visitorWorkUnit", visitor.getVisitorWorkUnit()); + visitorJson.put("visitorAddress", visitor.getVisitorAddress()); + visitorJson.put("extendedFields", visitor.getExtendedFields()); + + if (visitor.getExtendedFields() != null) { + visitor.getExtendedFields().forEach(visitorJson::put); + } + return visitorJson; + }).collect(Collectors.toList()); + + json.put("visitorInfoList", visitorList); + return json; + } + + + /** + * 获取人员列表(分页) + * @param request 分页请求参数 + * @return 分页响应结果 + */ + public VisitorResponse getPersonList(PageListRequest request) { + // 参数校验 + validatePageRequest(request); + + try { + // 1. 构建API路径 + String apiPath = buildApiPath("person-list"); + + // 2. 构建请求参数(根据ArtemisHttpUtil的要求) + Map path = new HashMap() {{ + put("https://", apiPath); // 根据实际协议配置 + }}; + + // 3. 构建请求体 + JSONObject body = new JSONObject(); + body.put("pageNo", request.getPageNo()); + body.put("pageSize", request.getPageSize()); + log.debug("请求体: {}", body); + + // 4. 调用工具类发送请求 + String result = ArtemisHttpUtil.doPostStringArtemis( + path, + body.toJSONString(), + null, // 查询参数 + null, // 头信息(可配置Authorization等) + "application/json", + null // 超时配置(如需要) + ); + + // 5. 处理响应 + return parseResponse(result); + } catch (Exception e) { // 捕获工具类可能抛出的异常 + throw new HikvisionException("SERVICE_ERROR", "人员查询失败"); + } + } + + +/** + * 构建完整API路径 + */ + private String buildApiPath(String apiKey) { + String apiSuffix = properties.getApis().get(apiKey); + if (StringUtils.isBlank(apiSuffix)) { + throw new HikvisionException("CONFIG_ERROR", "接口路径未配置"); + } + return properties.getArtemisPath() + apiSuffix; + } + /** + * 解析响应数据 + */ + private VisitorResponse parseResponse(String json) { + try { + VisitorResponse response = JSON.parseObject(json, + new TypeReference>() {}); + + if (!"0".equals(response.getCode())) { + log.warn("海康业务异常 - Code: {}, Msg: {}", + response.getCode(), response.getMsg()); + throw new HikvisionException(response.getCode(), response.getMsg()); + } + return response; + } catch (JSONException e) { + log.error("响应数据解析失败: {}", json, e); + throw new HikvisionException("PARSE_ERROR", "响应数据格式异常"); + } + } + + /** + * 参数校验增强 + */ + private void validatePageRequest(PageListRequest request) { + if (request.getPageNo() == null || request.getPageNo() < 1) { + throw new IllegalArgumentException("页码参数不合法"); + } + if (request.getPageSize() == null || + request.getPageSize() < 1 || + request.getPageSize() > 1000) { + throw new IllegalArgumentException("分页大小需在1-1000之间"); + } + } + +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/PersonService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/PersonService.java new file mode 100644 index 000000000..a969975de --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/PersonService.java @@ -0,0 +1,43 @@ +package com.bsd.pjcloud.thirdparty.service; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.bsd.pjcloud.thirdparty.config.HikvisionProperties; +import com.bsd.pjcloud.thirdparty.dto.visitor.PageListRequest; +import com.bsd.pjcloud.thirdparty.dto.visitor.VisitorResponse; +import com.bsd.pjcloud.thirdparty.dto.visitor.request.person.PersonInfo; +import com.bsd.pjcloud.thirdparty.exception.HikvisionException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class PersonService extends BaseHikvisionService { + public PersonService(HikvisionProperties properties) { + super(properties); + } + + public VisitorResponse getPersonList(PageListRequest request) { + try { + validatePageRequest(request); + + JSONObject body = new JSONObject(); + body.put("pageNo", request.getPageNo()); + body.put("pageSize", request.getPageSize()); + + String response = sendPostRequest("person-list", body); + return parseResponse(response, new TypeReference>() {}); + } catch (IllegalArgumentException e) { + throw new HikvisionException("PARAM_ERROR", e.getMessage()); + } + } + + private void validatePageRequest(PageListRequest request) { + if (request.getPageNo() == null || request.getPageNo() < 1) { + throw new IllegalArgumentException("页码必须大于0"); + } + if (request.getPageSize() == null || request.getPageSize() < 1 || request.getPageSize() > 1000) { + throw new IllegalArgumentException("分页大小需在1-1000之间"); + } + } +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/VisitorService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/VisitorService.java new file mode 100644 index 000000000..16fa8521b --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/VisitorService.java @@ -0,0 +1,72 @@ +package com.bsd.pjcloud.thirdparty.service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.bsd.pjcloud.thirdparty.config.HikvisionProperties; +import com.bsd.pjcloud.thirdparty.dto.visitor.VisitorObjectResponse; +import com.bsd.pjcloud.thirdparty.dto.visitor.request.make.VisitorAppointmentRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class VisitorService extends BaseHikvisionService { + public VisitorService(HikvisionProperties properties) { + super(properties); + } + + public VisitorObjectResponse createVisitorAppointment(VisitorAppointmentRequest request) { + JSONObject body = convertRequestToJSON(request); + String responseJson = sendPostRequest("visitor-appointment", body); + return JSON.parseObject(responseJson, VisitorObjectResponse.class); + } + + private JSONObject convertRequestToJSON(VisitorAppointmentRequest request) { + JSONObject json = new JSONObject(); + json.put("receptionistId", request.getReceptionistId()); + + // 处理权限组 + if (request.getVisitorPermissionSet() != null) { + JSONObject permissionSet = new JSONObject(); + permissionSet.put("defaultPrivilegeGroupFlag", + request.getVisitorPermissionSet().getDefaultPrivilegeGroupFlag()); + permissionSet.put("privilegeGroupIds", + request.getVisitorPermissionSet().getPrivilegeGroupIds()); + json.put("visitorPermissionSet", permissionSet); + } + + json.put("visitStartTime", request.getVisitStartTime()); + json.put("visitEndTime", request.getVisitEndTime()); + json.put("visitPurpose", request.getVisitPurpose()); + + // 处理访客列表 + List visitorList = request.getVisitorInfoList().stream().map(visitor -> { + JSONObject visitorJson = new JSONObject(); + visitorJson.put("visitorName", visitor.getVisitorName()); + visitorJson.put("gender", visitor.getGender()); + visitorJson.put("phoneNo", visitor.getPhoneNo()); + visitorJson.put("plateNo", visitor.getPlateNo()); + visitorJson.put("certificateType", visitor.getCertificateType()); + visitorJson.put("certificateNo", visitor.getCertificateNo()); + visitorJson.put("certAddr", visitor.getCertAddr()); + visitorJson.put("certIssuer", visitor.getCertIssuer()); + visitorJson.put("nation", visitor.getNation()); + visitorJson.put("birthplace", visitor.getBirthplace()); + visitorJson.put("visitorPhoto", visitor.getVisitorPhoto()); + visitorJson.put("visitorWorkUnit", visitor.getVisitorWorkUnit()); + visitorJson.put("visitorAddress", visitor.getVisitorAddress()); + visitorJson.put("extendedFields", visitor.getExtendedFields()); + + if (visitor.getExtendedFields() != null) { + visitor.getExtendedFields().forEach(visitorJson::put); + } + return visitorJson; + }).collect(Collectors.toList()); + + json.put("visitorInfoList", visitorList); + return json; + } +} \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml index 5c30e5ffd..afcd0ff6c 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/application.yml @@ -121,3 +121,14 @@ LianTongSMS: PASSWORD: 'Aa123456' #签名id AUTOGRAPH_ID: '' + + +hikvision: + host: 127.0.0.1:443 + app-key: 29180881 + app-secret: XO0wCAYGi4KV70ybjznx + artemis-path: /artemis + apis: + preview-urls: /api/video/v1/cameras/previewURLs + person-list: /api/resource/v2/person/personList # 新增人员列表接口 + visitor-appointment: /api/visitor/v2/appointment # 新增访客预约接口 \ No newline at end of file