diff --git a/module-aicc/pom.xml b/module-aicc/pom.xml
new file mode 100644
index 0000000..e8a9d1b
--- /dev/null
+++ b/module-aicc/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+
+ com.whdc
+ fxkh-txl-parent
+ 1.0
+ ../poms/dependency.xml
+
+
+ fxkh-txl-aicc
+ jar
+ 防汛抗旱通讯录API - 自动外呼模块
+
+
+
+
+ com.whdc
+ fxkh-txl-common
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+
+ com.dameng
+ DmJdbcDriver18
+
+
+ com.dameng
+ DmDialect-for-hibernate4.0
+
+
+ com.alibaba
+ druid
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+ org.apache.commons
+ commons-pool2
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpcore
+
+
+ com.squareup.okhttp3
+ okhttp
+
+
+ org.projectlombok
+ lombok
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
\ No newline at end of file
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/AutoCall.java b/module-aicc/src/main/java/com/whdc/aicc/model/AutoCall.java
new file mode 100644
index 0000000..6cc4bb6
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/AutoCall.java
@@ -0,0 +1,77 @@
+package com.whdc.aicc.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * @author lyf
+ * @since 2025-06-14
+ */
+@Data
+@Accessors(chain = true)
+@ApiModel(description = "自动拨号")
+@TableName("FXKH_TXL.AUTOCALL")
+@ToString
+public class AutoCall {
+ /*
+ new的时候必填responderId,responderName,wcmId,createTm
+ query后必填__开头的
+ */
+ @TableId(value = "ID", type = IdType.AUTO)
+ private Integer id;
+ @TableField(value = "responder_id")
+ @JsonProperty("addressBookOldId")
+ private Integer responderId;
+ @JsonProperty("addressBookOldName")
+ @TableField(value = "responder_name")
+ private String responderName;
+ @TableField(value = "status")
+ private String status;//接通,空号,停机,关机,未接,拒接,占线,呼叫失败,null(空代表未呼叫)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "_create_tm")
+ private Date createTm;
+ @TableField(value = "wcm_id")
+ private Integer wcmId;
+ @TableField(value = "level")
+ private Integer level;
+
+ @TableField(value = "__talk_times")
+ private Integer talkTimes;//通话时长,单位秒
+ @TableField(value = "__sip_term_cause")
+ private String sipTermCause; //例如”对方挂机“
+ @TableField(value = "__caller")
+ private String caller;
+ @TableField(value = "__number")
+ private String number;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__startring_at")
+ private Date startringAt; //-3
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__connected_at")
+ private Date connectedAt; //-3
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__started_at")
+ private Date startedAt;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__endring_at")
+ private Date endringAt; //-3
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__disconnected_at")
+ private Date disconnectedAt; //-3
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__stopped_at")
+ private Date stopedAt;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__last_modify")
+ private Date lastModify;
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallConfig.java b/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallConfig.java
new file mode 100644
index 0000000..172db8d
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallConfig.java
@@ -0,0 +1,15 @@
+package com.whdc.aicc.model;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+/**
+ * @author lyf
+ * @since 2025-06-19
+ */
+@Data
+@TableName("FXKH_TXL.AUTOCALL_CONFIG")
+public class AutoCallConfig {
+ private String key;
+ private String value;
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallPerson.java b/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallPerson.java
new file mode 100644
index 0000000..3c8ef04
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallPerson.java
@@ -0,0 +1,98 @@
+package com.whdc.aicc.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * @author lyf
+ * @since 2025-07-08
+ */
+@Data
+@TableName("FXKH_TXL.AUTO_CALL_PERSON")
+public class AutoCallPerson {
+ public static final int STATUS_DEFAULT = 0;
+ public static final int STATUS_UPLOADED = 1;
+ public static final int STATUS_CALLED = 2;
+ public static final int STATUS_PUT = 3;
+ public static final int STATUS_CANCELLED = 4;
+ public static final int STATUS_MANUAL_CLOSE = 6;
+ public static final int ERRCODE_ENCODE = 1;
+ public static final int ERRCODE_UPLOAD_FAIL = 2;
+ public static final String TAG_DONE = "已知晓";
+
+ @TableId(value = "ID", type = IdType.AUTO)
+ private Integer id;
+ @TableField(value = "task_id")
+ private Integer taskId;
+ @TableField(value = "status")
+ private Integer status; //0:未上传 default 0
+ @TableField(value = "level")
+ private Integer level;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "_create_tm")
+ private Date createTm;
+ @TableField(value = "error_code")
+ private Integer errorCode; //default 0
+ @TableField(value = "uploaded_times")
+ private Integer uploadedTimes; //default 0
+ @TableField(value = "sms_content")
+ private String smsContent; //短信内容
+ @TableField(value = "manual_close")
+ private Integer manualClose; //default 0
+ @TableField(value = "position")
+ private String position;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "manual_close_tm")
+ private Date manualCloseTm;
+
+ @TableField(value = "__request_id")
+ private String uploadRequestId; //任务名,同时作为参数的taskName,用custId
+ @TableField(value = "__cust_id")
+ private String uploadCustId; //联系人id,用warningResponderId加时间戳毫秒
+ @TableField(value = "__cust_name")
+ private String uploadCustName; //联系人
+ @TableField(value = "__content")
+ private String uploadContent; //外呼内容
+ @TableField(value = "__number")
+ private String uploadNumber;
+ @TableField(value = "__upload_resp_msg")
+ private String uploadRespMsg; //上传结果
+ @TableField(value = "__remark")
+ private String detailRemark;//接通,空号,停机,关机,未接,拒接,占线,呼叫失败,null(空代表未呼叫)
+ @TableField(value = "__talk_times")
+ private Integer detailTalkTimes;//通话时长,单位秒
+ @TableField(value = "__sip_term_cause")
+ private String detailSipTermCause; //例如”对方挂机“
+ @TableField(value = "__tag")
+ private String tag; //话术识别:未识别,已知晓
+ @TableField("__is_complete")
+ private Integer isComplete; //default 0
+
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__startring_at")
+ private Date detailStartringAt; //-3
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__connected_at")
+ private Date detailConnectedAt; //-3
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__started_at")
+ private Date detailStartedAt;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__endring_at")
+ private Date detailEndringAt; //-3
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__disconnected_at")
+ private Date detailDisconnectedAt; //-3
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__stopped_at")
+ private Date detailStopedAt;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ @TableField(value = "__last_modify")
+ private Date detailLastModify;
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallTask.java b/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallTask.java
new file mode 100644
index 0000000..0d4dd7d
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/AutoCallTask.java
@@ -0,0 +1,67 @@
+package com.whdc.aicc.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author lyf
+ * @since 2025-07-08
+ */
+@Data
+@TableName("FXKH_TXL.AUTO_CALL_TASK")
+public class AutoCallTask {
+ public static final int STATUS_DEFAULT = 0; // 不生成
+ public static final int STATUS_SHOULD_GENERATE = 1;
+ public static final int STATUS_GENERATED_AKA_READY_TO_UPLOAD = 2;
+ public static final int STATUS_ANY_SUCCESS = 3;
+ public static final int STATUS_ALL_FAIL = 4;
+ public static final int STATUS_CANCELLED = 5;
+ public static final int STATUS_MANUAL_CLOSE = 6; //人工处置
+ public static final int ERRCODE_NO_PERSON = 1;
+ public static final int ERRCODE_DB_ERROR = 2;
+
+ @TableId(value = "ID", type = IdType.AUTO)
+ private Integer id;
+ @TableField(value = "warn_id")
+ private Integer warnId; //not null
+ @TableField(value = "status")
+ private Integer status; //0:不拨打 default 0
+ @TableField(value = "error_code")
+ private Integer errorCode; //default 0
+ @TableField(value = "warn_name")
+ private String warnName;
+ @TableField(value = "warn_tm")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
+ private Date warnTm;
+ @TableField(value = "warn_level")
+ private String warnLevel;
+ @TableField(value = "WARN_CTNM")
+ private String warnCtnm;
+ @TableField(value = "warn_cnnm")
+ private String warnCnnm;
+ @TableField(value = "warn_content")
+ private String warnContent;
+ @TableField(value = "__tag")
+ private String tag; //话术识别:未识别,已知晓
+ @TableField(value = "submit")
+ private Integer submit; //是否已提交线程池,default 0
+
+ @TableField(value = "_create_tm")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
+ private Date createTm;
+ @TableField(value = "_remark")
+ private String remark;
+
+ @TableField(exist = false)
+ private List callList;
+
+ @TableField(exist = false)
+ private AutoCallPerson successPerson;
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespDetail.java b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespDetail.java
new file mode 100644
index 0000000..81309b9
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespDetail.java
@@ -0,0 +1,71 @@
+package com.whdc.aicc.model.aicc;
+
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.ToString;
+
+import java.util.List;
+
+/**
+ * 联通查询任务状态接口返回对象
+ *
+ * @author lyf
+ * @since 2025-06-17
+ */
+@lombok.Data
+@ToString
+public class AICCCallRespDetail {
+ private Data data;
+
+ @lombok.Data
+ @ToString
+ public static class Data {
+ List records;
+ }
+
+ @lombok.Data
+ @ToString
+ public static class Record {
+ private String caller; //外呼号码
+ private Long dataTime;
+ private String processId; //机器人id
+ private String sessionDetailId; //联系人id,等同返回值的custId
+ private Integer talkTimes;//通话时长,单位秒
+ private String remark; //中文状态,接通,空号,停机,关机,未接,拒接,占线,呼叫失败,""代表未呼叫
+ private String taskName; //任务名
+ private Integer status; //0进行中 2结束 4暂停
+ private Integer totalCount;
+ private Integer sendCount;
+ private boolean isCompleted;
+
+ private RawVarListMap rawVarListMap;
+ private List tags;
+ }
+
+ @lombok.Data
+ @ToString
+ public static class RawVarListMap {
+ private String taskName; //任务名,作为参数的taskName
+ private String dialTaskMainSn; //任务编号,作为参数的requestId
+ private String custId; //联系人id
+ private String sipTermCause; //例如”对方挂机“
+ private String sipTermStatus; //接通是200
+ private String caller;
+ @JSONField(name = "@NUMBER")
+ private String number;
+ private String startringAt;
+ private String connectedAt;
+ private String startedAt;
+ private String endringAt;
+ private String disconnectedAt;
+ private String stopedAt;
+ private String lastModify;
+ }
+
+ @lombok.Data
+ @ToString
+ public static class Tag {
+ @JSONField(name = "tag_name")
+ private String tagName;
+ }
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespTask.java b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespTask.java
new file mode 100644
index 0000000..ded08a8
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespTask.java
@@ -0,0 +1,38 @@
+package com.whdc.aicc.model.aicc;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * uploadCallData接口的返回值
+ *
+ * @author lyf
+ * @since 2025-06-20
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class AICCCallRespTask extends AICCCallRespWrapper {
+ /*
+ {
+ "msg": "操作成功!",
+ "result": {
+ "msg": "任务名称重复" | "导入成功",
+ "count": "0",
+ "status": "-1"
+ },
+ "code": 0,
+ "redirected": false,
+ "success": true,
+ "errorType": 0,
+ "errorCode": "",
+ "timestamp": 1750383985979
+ }
+ */
+ //code 0 success true
+ public static final String MSG_SUCCESS = "导入成功";
+ public static final String MSG_REPEAT = "任务名称重复";
+
+ private String msg;
+ private String count;
+ private String status;
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespWrapper.java b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespWrapper.java
new file mode 100644
index 0000000..55d48dc
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCCallRespWrapper.java
@@ -0,0 +1,37 @@
+package com.whdc.aicc.model.aicc;
+
+import lombok.Data;
+
+/**
+ * @author lyf
+ * @since 2025-06-20
+ */
+@Data
+public class AICCCallRespWrapper {
+ /*
+ {
+ "code": 406,
+ "errorCode": "",
+ "errorType": 0,
+ "msg": "Token失效,请重新登录!",
+ "redirected": true,
+ "success": false,
+ "timestamp": 1750384243314
+ }
+ */
+ //code 406 success false
+ private String msg;
+ private int code;
+ private boolean redirected;
+ private boolean success;
+ private int errorType;
+ private String errorCode;
+ private long timestamp;
+
+ //token失效没有result
+ private T result;
+
+ public boolean isTokenInvalid() {
+ return code == 406 && !success;
+ }
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCLogin.java b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCLogin.java
new file mode 100644
index 0000000..ee6a3f5
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCLogin.java
@@ -0,0 +1,19 @@
+package com.whdc.aicc.model.aicc;
+
+import lombok.Data;
+
+/**
+ * @author lyf
+ * @since 2025-06-20
+ */
+@Data
+public class AICCLogin {
+ /*
+ {"msg":"操作成功!","result":{"msg":"操作成功","tenantId":"963936517","status":"0","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkhCU0wwMSIsInRlbmFudElkIjoiOTYzOTM2NTE3IiwiZXhwIjoxNzUwNjg4NjE4fQ.INUcA-keg7T3HrZXH87K2ZqOT2trxLF38kmA9Wu301w"},"code":0,"redirected":false,"success":true,"errorType":0,"errorCode":"","timestamp":1750386218853}
+ */
+ //code 0 success true
+ private String msg;
+ private String tenantId;
+ private String status;
+ private String token;
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCUploadTask.java b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCUploadTask.java
new file mode 100644
index 0000000..4d446fb
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/model/aicc/AICCUploadTask.java
@@ -0,0 +1,120 @@
+package com.whdc.aicc.model.aicc;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author lyf
+ * @since 2025-06-20
+ */
+@Data
+public class AICCUploadTask {
+ /*
+ JSONObject data = new JSONObject();
+ data.put("taskName", taskName);
+ data.put("processId", processId);
+ data.put("callerGroup", callerGroup);
+ data.put("requestId", requestId);
+ data.put("calleeType", 2);
+ data.put("repeatTimes", 2);
+ data.put("autoCall", "0");
+ data.put("spanSeconds", 60);
+ data.put("processType", "3");
+ data.put("mutiTimeRange", timeRange);
+ JSONArray params = new JSONArray();
+ JSONObject param = new JSONObject();
+ param.put("@common_user_name", custName);
+ param.put("@NUMBER", number);
+ if (bakNumbers != null && bakNumbers.length > 0) {
+ for (int i = 0; i < bakNumbers.length; i++) {
+ if (i == 3) break;
+ param.put("备用号码" + (i + 1), bakNumbers[i]);
+ }
+ }
+ param.put("custId", custId);
+ param.put("content", "我是省防办智能外呼系统," + content + "如需咨询请拨打02787221781");
+ params.add(param);
+ data.put("param", params);
+ */
+ private String taskName;
+ private String processId;
+ private String callerGroup;
+ private String requestId;
+ private int calleeType = 0; //默认0 重乎1 顺乎2 默认为0
+// private int repeatTimes = 1;//呼叫次数,CallType=0不传; CalleeType=1重乎必传; CalleeType=2顺乎并且autoCall为0必传
+ private String autoCall = "1";//顺乎未接通是否重乎,开启=0; 关闭=1; CalleeType=2顺乎必传
+// private int spanSeconds = 3;//重乎间隔时间(秒),CallType=0不传; CalleeType=1重乎必传; CalleeType=2顺乎并且autoCall为0必传
+ private String processType = "3";
+ private String mutiTimeRange;
+ private List param;
+// private String smsSend = "1"; //1不发短信,0发短信
+// private String smsSendType = "0"; //挂机短信0,未接通短信1,smsSend为0必传
+// private String templateId; //短信模板id,smsSend为0必传
+
+ @Data
+ public static class Cust {
+ @JSONField(name = "@common_user_name")
+ private String custName;
+ @JSONField(name = "@NUMBER")
+ private String number;
+ @JSONField(name = "备用号码1")
+ private String backNumber1;
+ @JSONField(name = "备用号码2")
+ private String backNumber2;
+ @JSONField(name = "备用号码3")
+ private String backNumber3;
+ private String custId;
+ private String content;
+ @JSONField(serialize = false, deserialize = false)
+ private List _numbers;
+
+ public static CustBuilder builder() {
+ return new CustBuilder();
+ }
+
+ @Data
+ @Accessors(chain = true)
+ public static class CustBuilder {
+ private String custName;
+ private String custId;
+ private String content;
+ @JSONField(serialize = false, deserialize = false)
+ private List _numbers;
+
+ public Cust build() {
+ Cust cust = new Cust();
+ cust.setCustName(custName);
+ cust.setCustId(custId);
+ cust.setContent(content);
+ for (int i = 0; i < _numbers.size(); i++) {
+ switch (i) {
+ case 0:
+ cust.setNumber(_numbers.get(i));
+ break;
+ case 1:
+ cust.setBackNumber1(_numbers.get(i));
+ break;
+ case 2:
+ cust.setBackNumber2(_numbers.get(i));
+ break;
+ case 3:
+ cust.setBackNumber3(_numbers.get(i));
+ break;
+ }
+ }
+ return cust;
+ }
+ }
+
+ }
+
+ public void genMutiTimeRange() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ this.mutiTimeRange = sdf.format(new Date()) + "_" + sdf.format(new Date(System.currentTimeMillis() + 1000 * 60 * 60));
+ }
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/utils/AICCHelper.java b/module-aicc/src/main/java/com/whdc/aicc/utils/AICCHelper.java
new file mode 100644
index 0000000..583f94d
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/utils/AICCHelper.java
@@ -0,0 +1,282 @@
+package com.whdc.aicc.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.whdc.aicc.model.aicc.*;
+import com.whdc.common.utils.AESpkcs7paddingUtil;
+import com.whdc.common.utils.HttpHelper;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @author lyf
+ * @since 2025-06-19
+ */
+@Component
+@Slf4j
+public class AICCHelper {
+ @Setter
+ @Value("${autocall.processId}")
+ private String processId;
+ @Setter
+ @Value("${autocall.callerGroup}")
+ private String callerGroup;
+ @Setter
+ @Value("${autocall.secret}")
+ private String secret;
+ @Setter
+ @Value("${autocall.sysUserId}")
+ private String sysUserId;
+ @Autowired
+ private HttpHelper httpHelper = new HttpHelper();
+
+ private static final AtomicBoolean isAcquiringToken = new AtomicBoolean(false);
+ private static volatile CountDownLatch latch = new CountDownLatch(0);
+ private static final AtomicReference globalToken = new AtomicReference<>();
+
+ public String getToken() {
+ if (globalToken.get() == null) {
+ initToken();
+ }
+ return globalToken.get();
+ }
+
+ public void initToken() throws RuntimeException {
+ if (isAcquiringToken.compareAndSet(false, true)) {
+ latch = new CountDownLatch(1);
+ globalToken.set(null);
+ try {
+ String data = "{\"sysUserId\":\"" + sysUserId + "\",\"expire\":1000000}";
+ String encrypt;
+ try {
+ encrypt = AESpkcs7paddingUtil.encrypt(data, secret);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ JSONObject request = new JSONObject();
+ request.put("request", encrypt);
+ Map headers = new HashMap<>();
+// headers.put("X-Access-Token", getToken());
+ String resp = httpHelper.postJsonString("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/login", request.toJSONString(), headers);
+
+ TypeReference> type = new TypeReference>() {
+ };
+ AICCCallRespWrapper AICCCallRespWrapper = JSON.parseObject(resp, type);
+ if (AICCCallRespWrapper == null || !AICCCallRespWrapper.isSuccess() || AICCCallRespWrapper.getResult() == null) {
+ log.warn("获取外呼系统token失败");
+ return;
+ }
+
+ AICCLogin result = AICCCallRespWrapper.getResult();
+ String token = result.getToken();
+// if (token != null && !token.isEmpty()) {
+ globalToken.set(token);
+// }
+ } finally {
+ isAcquiringToken.set(false);
+ latch.countDown();
+ }
+ } else {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ public AICCCallRespWrapper apiUploadCallData(AICCUploadTask data) throws GeneralSecurityException, UnsupportedEncodingException {
+ return apiUploadCallData(data, getToken());
+ }
+
+ public AICCCallRespWrapper apiUploadCallData(AICCUploadTask data, String token) throws GeneralSecurityException, UnsupportedEncodingException {
+ String e;
+ try {
+ e = AESpkcs7paddingUtil.encrypt(JSON.toJSONString(data), secret);
+ } catch (GeneralSecurityException | UnsupportedEncodingException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ JSONObject request = new JSONObject();
+ request.put("request", e);
+ Map headers = new HashMap<>();
+ headers.put("X-Access-Token", token);
+ String resp = httpHelper.postJsonString("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/task/uploadCallData", request.toJSONString(), headers);
+ log.info("apiUploadCallData: {}", resp);
+ TypeReference> type = new TypeReference>() {
+ };
+ AICCCallRespWrapper AICCCallRespWrapper = null;
+ if (!resp.contains("请求失败") && !resp.contains("网络异常")) {
+ try {
+ AICCCallRespWrapper = JSON.parseObject(resp, type);
+ } catch (Exception ex) {
+ log.warn("apiUploadCallData first time: {}", resp);
+ log.warn("error: ", ex);
+ }
+ } else {
+ log.info("apiUploadCallData first time: {}", resp);
+ }
+
+ if (AICCCallRespWrapper == null || !AICCCallRespWrapper.isSuccess()) {
+ initToken();
+ headers.put("X-Access-Token", getToken());
+ resp = httpHelper.postJsonString("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/task/uploadCallData", request.toJSONString(), headers);
+ if (!resp.contains("请求失败") && !resp.contains("网络异常")) {
+ try {
+ AICCCallRespWrapper = JSON.parseObject(resp, type);
+ } catch (Exception ex) {
+ log.warn("apiUploadCallData second time: {}", resp);
+ log.warn("error: ", ex);
+ }
+ } else {
+ log.info("apiUploadCallData second time: {}", resp);
+ }
+ }
+
+ if (AICCCallRespWrapper == null || !AICCCallRespWrapper.isSuccess()) {
+ return null;
+ }
+ return AICCCallRespWrapper;
+ }
+
+ public AICCCallRespWrapper apiGetTaskCallDetail(String requestId, String custId) {
+ return apiGetTaskCallDetail(requestId, custId, getToken());
+ }
+
+ public AICCCallRespWrapper apiGetTaskCallDetail(String requestId, String custId, String token) {
+ JSONObject request = new JSONObject();
+ request.put("requestId", requestId);
+ request.put("custId", custId);
+ Map headers = new HashMap<>();
+ headers.put("X-Access-Token", token);
+ String resp = httpHelper.postJsonString("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/task/getTaskCallDetail", request.toJSONString(), headers);
+ TypeReference> type = new TypeReference>() {
+ };
+ AICCCallRespWrapper aICCCallRespWrapper = null;
+ try {
+ aICCCallRespWrapper = JSON.parseObject(resp, type);
+ } catch (Exception e) {
+ log.warn("apiGetTaskCallDetail first time: {}", resp);
+ log.warn("error: ", e);
+ }
+ if (aICCCallRespWrapper == null || !aICCCallRespWrapper.isSuccess()) {
+ initToken();
+ headers.put("X-Access-Token", getToken());
+ resp = httpHelper.postJsonString("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/task/getTaskCallDetail", request.toJSONString(), headers);
+ try {
+ aICCCallRespWrapper = JSON.parseObject(resp, type);
+ } catch (Exception e) {
+ log.warn("apiGetTaskCallDetail second time: {}", resp);
+ log.warn("error: ", e);
+ }
+ }
+
+ if (aICCCallRespWrapper == null || !aICCCallRespWrapper.isSuccess()) {
+ log.info("获取任务详情失败:{}", resp);
+ return null;
+ }
+ return aICCCallRespWrapper;
+ }
+
+ public AICCUploadTask newTask(String requestId, String custId, String custName, String content, List numbers) {
+ AICCUploadTask task = new AICCUploadTask();
+ task.setTaskName(requestId);
+ task.setProcessId(processId);
+ task.setCallerGroup(callerGroup);
+ task.setRequestId(requestId);
+ task.genMutiTimeRange();
+ AICCUploadTask.Cust param = AICCUploadTask.Cust.builder()
+ .setCustName(custName)
+ .setCustId(custId)
+ .setContent(content)
+ .set_numbers(numbers)
+ .build();
+ task.setParam(Collections.singletonList(param));
+
+ return task;
+ }
+
+// private String sendPost(String url, JSONObject jsonData) {
+// String resp = sendPost(url, jsonData.toJSONString());
+// if (resp == null) {
+// resp = sendPost(url, jsonData.toJSONString());
+// }
+// return resp;
+// }
+//
+// private String sendPost(String url, JSONObject jsonData, String token) {
+// String resp = sendPost(url, jsonData.toJSONString(), token);
+// if (resp == null) {
+// resp = sendPost(url, jsonData.toJSONString(), token);
+// }
+// return resp;
+// }
+//
+// private String sendPost(String url, String jsonData) {
+// return sendPost(url, jsonData, getToken());
+// }
+//
+// private String sendPost(String url, String jsonData, String token) {
+// CloseableHttpResponse response = null;
+// CloseableHttpClient httpClient = null;
+// String responseContent = null;
+// try {
+// httpClient = HttpClients.createDefault();
+// HttpPost httpPost = new HttpPost(url);
+// httpPost.addHeader("Content-Type", "application/json");
+// httpPost.addHeader("X-Access-Token", token);
+//
+// if (StringUtils.isNotBlank(jsonData)) {
+// httpPost.setEntity(new StringEntity(jsonData, "UTF-8"));
+// }
+//
+// log.info("请求地址: " + url);
+// log.info("token: " + getToken());
+// log.info("请求参数: " + jsonData);
+//
+// response = httpClient.execute(httpPost);
+// HttpEntity entity = response.getEntity();
+// responseContent = EntityUtils.toString(entity, "UTF-8");
+// responseContent = jsonFormat(responseContent);
+// if (responseContent.length() < 200) {
+// log.info("响应参数: " + responseContent);
+// }
+// } catch (Exception e) {
+// log.error("发送请求异常", e);
+// return null;
+// } finally {
+// try {
+// if (null != response) {
+// response.close();
+// }
+// httpClient.close();
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// }
+// return responseContent;
+// }
+
+ private static String jsonFormat(String str) {
+ if (JSON.isValidObject(str)) {
+ str = JSON.parseObject(str).toJSONString();
+ }
+
+ return str;
+ }
+}
diff --git a/module-aicc/src/main/java/com/whdc/aicc/utils/AutoCallHelper.java b/module-aicc/src/main/java/com/whdc/aicc/utils/AutoCallHelper.java
new file mode 100644
index 0000000..5f24348
--- /dev/null
+++ b/module-aicc/src/main/java/com/whdc/aicc/utils/AutoCallHelper.java
@@ -0,0 +1,236 @@
+package com.whdc.aicc.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
+import com.whdc.aicc.model.aicc.*;
+import com.whdc.common.utils.AESpkcs7paddingUtil;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @author lyf
+ * @since 2025-06-19
+ */
+@Component
+@Slf4j
+public class AutoCallHelper {
+ @Setter
+ @Value("${autocall.processId}")
+ private String processId;
+ @Setter
+ @Value("${autocall.callerGroup}")
+ private String callerGroup;
+ @Setter
+ @Value("${autocall.secret}")
+ private String secret;
+ @Setter
+ @Value("${autocall.sysUserId}")
+ private String sysUserId;
+
+ private static final AtomicBoolean isAcquiringToken = new AtomicBoolean(false);
+ private static volatile CountDownLatch latch = new CountDownLatch(0);
+ private static final AtomicReference globalToken = new AtomicReference<>();
+
+ public String getToken() {
+ if (globalToken.get() == null) {
+ initToken();
+ }
+ return globalToken.get();
+ }
+
+ public void initToken() throws RuntimeException {
+ if (isAcquiringToken.compareAndSet(false, true)) {
+ globalToken.set(null);
+ try {
+ String data = "{\"sysUserId\":\"" + sysUserId + "\",\"expire\":1000000}";
+ String encrypt;
+ try {
+ encrypt = AESpkcs7paddingUtil.encrypt(data, secret);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ JSONObject request = new JSONObject();
+ request.put("request", encrypt);
+ String resp = sendPost("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/login", request);
+
+ TypeReference> type = new TypeReference>() {
+ };
+ AICCCallRespWrapper AICCCallRespWrapper = JSON.parseObject(resp, type);
+ if (AICCCallRespWrapper == null || !AICCCallRespWrapper.isSuccess() || AICCCallRespWrapper.getResult() == null) {
+ log.warn("获取外呼系统token失败");
+ return;
+ }
+
+ AICCLogin result = AICCCallRespWrapper.getResult();
+ String token = result.getToken();
+ if (token != null && !token.isEmpty()) {
+ globalToken.set(token);
+ }
+ } finally {
+ isAcquiringToken.set(false);
+ }
+ } else {
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ public AICCCallRespWrapper apiUploadCallData(AICCUploadTask data) {
+ return apiUploadCallData(data, getToken());
+ }
+
+ public AICCCallRespWrapper apiUploadCallData(AICCUploadTask data, String token) {
+ String e;
+ try {
+ e = AESpkcs7paddingUtil.encrypt(JSON.toJSONString(data), secret);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ JSONObject request = new JSONObject();
+ request.put("request", e);
+ String resp = sendPost("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/task/uploadCallData", request, token);
+
+ TypeReference> type = new TypeReference>() {
+ };
+ AICCCallRespWrapper AICCCallRespWrapper = JSON.parseObject(resp, type);
+ if (AICCCallRespWrapper == null || !AICCCallRespWrapper.isSuccess()) {
+ initToken();
+ resp = sendPost("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/task/uploadCallData", request, token);
+ AICCCallRespWrapper = JSON.parseObject(resp, type);
+ }
+
+ if (AICCCallRespWrapper == null || !AICCCallRespWrapper.isSuccess()) {
+ return null;
+ }
+ return AICCCallRespWrapper;
+ }
+
+ public AICCCallRespWrapper apiGetTaskCallDetail(String requestId, String custId) {
+ JSONObject request = new JSONObject();
+ request.put("requestId", requestId);
+ request.put("custId", custId);
+ String resp = sendPost("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/task/getTaskCallDetail", request);
+
+ TypeReference> type = new TypeReference>() {
+ };
+ AICCCallRespWrapper AICCCallRespWrapper = JSON.parseObject(resp, type);
+ if (AICCCallRespWrapper == null || !AICCCallRespWrapper.isSuccess()) {
+ initToken();
+ resp = sendPost("https://aicc.cuopen.net:9801/aicc-api/ssb/callout/thirdParty/task/getTaskCallDetail", request);
+ AICCCallRespWrapper = JSON.parseObject(resp, type);
+ }
+
+ if (AICCCallRespWrapper == null || !AICCCallRespWrapper.isSuccess()) {
+ return null;
+ }
+ return AICCCallRespWrapper;
+ }
+
+ public AICCUploadTask newTask(String requestId, String custId, String custName, String content, List numbers) {
+ AICCUploadTask task = new AICCUploadTask();
+ task.setTaskName(requestId);
+ task.setProcessId(processId);
+ task.setCallerGroup(callerGroup);
+ task.setRequestId(requestId);
+ task.genMutiTimeRange();
+ AICCUploadTask.Cust param = AICCUploadTask.Cust.builder()
+ .setCustName(custName)
+ .setCustId(custId)
+ .setContent(content)
+ .set_numbers(numbers)
+ .build();
+ task.setParam(Collections.singletonList(param));
+
+ return task;
+ }
+
+ private String sendPost(String url, JSONObject jsonData) {
+ String resp = sendPost(url, jsonData.toJSONString());
+ if (resp == null) {
+ resp = sendPost(url, jsonData.toJSONString());
+ }
+ return resp;
+ }
+
+ private String sendPost(String url, JSONObject jsonData, String token) {
+ String resp = sendPost(url, jsonData.toJSONString(), token);
+ if (resp == null) {
+ resp = sendPost(url, jsonData.toJSONString(), token);
+ }
+ return resp;
+ }
+
+ private String sendPost(String url, String jsonData) {
+ return sendPost(url, jsonData, getToken());
+ }
+
+ private String sendPost(String url, String jsonData, String token) {
+ CloseableHttpResponse response = null;
+ CloseableHttpClient httpClient = null;
+ String responseContent = null;
+ try {
+ httpClient = HttpClients.createDefault();
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.addHeader("Content-Type", "application/json");
+ httpPost.addHeader("X-Access-Token", token);
+
+ if (StringUtils.isNotBlank(jsonData)) {
+ httpPost.setEntity(new StringEntity(jsonData, "UTF-8"));
+ }
+
+ log.info("请求地址: " + url);
+ log.info("token: " + getToken());
+ log.info("请求参数: " + jsonData);
+
+ response = httpClient.execute(httpPost);
+ HttpEntity entity = response.getEntity();
+ responseContent = EntityUtils.toString(entity, "UTF-8");
+ responseContent = jsonFormat(responseContent);
+ if (responseContent.length() < 200) {
+ log.info("响应参数: " + responseContent);
+ }
+ } catch (Exception e) {
+ log.error("发送请求异常", e);
+ return null;
+ } finally {
+ try {
+ if (null != response) {
+ response.close();
+ }
+ httpClient.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return responseContent;
+ }
+
+ private static String jsonFormat(String str) {
+ if (JSON.isValidObject(str)) {
+ str = JSON.parseObject(str).toJSONString();
+ }
+
+ return str;
+ }
+}
diff --git a/module-aicc/src/main/resources/mapper/AutoCallMapper.xml b/module-aicc/src/main/resources/mapper/AutoCallMapper.xml
new file mode 100644
index 0000000..18377b1
--- /dev/null
+++ b/module-aicc/src/main/resources/mapper/AutoCallMapper.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/module-aicc/src/main/resources/mapper/AutoCallPersonMapper.xml b/module-aicc/src/main/resources/mapper/AutoCallPersonMapper.xml
new file mode 100644
index 0000000..aaddd8d
--- /dev/null
+++ b/module-aicc/src/main/resources/mapper/AutoCallPersonMapper.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/module-aicc/src/main/resources/mapper/AutoCallTaskMapper.xml b/module-aicc/src/main/resources/mapper/AutoCallTaskMapper.xml
new file mode 100644
index 0000000..ec0807a
--- /dev/null
+++ b/module-aicc/src/main/resources/mapper/AutoCallTaskMapper.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/module-aicc/src/main/resources/mapper/WarnCallMapMapper.xml b/module-aicc/src/main/resources/mapper/WarnCallMapMapper.xml
new file mode 100644
index 0000000..fc82017
--- /dev/null
+++ b/module-aicc/src/main/resources/mapper/WarnCallMapMapper.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/module-common/pom.xml b/module-common/pom.xml
new file mode 100644
index 0000000..515c501
--- /dev/null
+++ b/module-common/pom.xml
@@ -0,0 +1,102 @@
+
+
+ 4.0.0
+
+
+ com.whdc
+ fxkh-txl-parent
+ 1.0
+ ../poms/dependency.xml
+
+
+ fxkh-txl-common
+ jar
+ 防汛抗旱通讯录API - 公共模块
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+ com.alibaba
+ fastjson
+
+
+ commons-beanutils
+ commons-beanutils
+
+
+ joda-time
+ joda-time
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.apache.httpcomponents
+ httpcore
+
+
+ com.squareup.okhttp3
+ okhttp
+
+
+ com.github.xiaoymin
+ knife4j-spring-boot-starter
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ com.github.binarywang
+ weixin-java-miniapp
+
+
+
+ cn.afterturn
+ easypoi-spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
\ No newline at end of file
diff --git a/module-common/src/main/java/com/whdc/common/annotation/DateTimeRange.java b/module-common/src/main/java/com/whdc/common/annotation/DateTimeRange.java
new file mode 100644
index 0000000..9a4593b
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/annotation/DateTimeRange.java
@@ -0,0 +1,53 @@
+package com.whdc.common.annotation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author 李赛
+ * @date 2022-04-27 12:08
+ */
+@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Constraint(validatedBy = DateTimeRangeValidator.class)
+@Documented
+public @interface DateTimeRange {
+
+ String message() default "{message}";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+ String datafmt() default "yyyy-MM-dd HH:mm:ss";
+
+ /**
+ * 起始属性字段名称
+ *
+ * @return
+ */
+ String startField();
+
+ /**
+ * 截止属性字段名称
+ *
+ * @return
+ */
+ String endField();
+
+ /**
+ * 时间间隔
+ *
+ * @return
+ */
+ long interval();
+
+ /**
+ * 时间间隔单位
+ *
+ * @return
+ */
+ TimeUnit timeUnit() default TimeUnit.HOURS;
+}
diff --git a/module-common/src/main/java/com/whdc/common/annotation/DateTimeRangeValidator.java b/module-common/src/main/java/com/whdc/common/annotation/DateTimeRangeValidator.java
new file mode 100644
index 0000000..2222567
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/annotation/DateTimeRangeValidator.java
@@ -0,0 +1,63 @@
+package com.whdc.common.annotation;
+
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author 李赛
+ * @date 2022-04-27 11:55
+ */
+public class DateTimeRangeValidator implements ConstraintValidator {
+
+ private String datefmt;
+ private String startField;
+ private String endField;
+ private long interval;
+ private TimeUnit timeUnit;
+
+ @Override
+ public void initialize(DateTimeRange dateTimeRange) {
+ this.datefmt = dateTimeRange.datafmt();
+ this.startField = dateTimeRange.startField();
+ this.endField = dateTimeRange.endField();
+ this.interval = dateTimeRange.interval();
+ this.timeUnit = dateTimeRange.timeUnit();
+ }
+
+ @Override
+ public boolean isValid(Object obj, ConstraintValidatorContext context) {
+ try {
+ if (obj == null) {
+ return false;
+ }
+
+ if (StringUtils.isBlank(this.startField) || StringUtils.isBlank(this.endField)) {
+ return false;
+ }
+
+ String stmval = BeanUtils.getProperty(obj, this.startField);
+ String etmval = BeanUtils.getProperty(obj, this.endField);
+
+ if (StringUtils.isBlank(stmval) || StringUtils.isBlank(etmval)) {
+ return false;
+ }
+
+ DateFormat df = new SimpleDateFormat(this.datefmt);
+ Date stm = df.parse(stmval);
+ Date etm = df.parse(etmval);
+
+ long dateDiff = stm.getTime() - etm.getTime();
+
+ return Math.abs(dateDiff) < this.timeUnit.toMillis(this.interval);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/module-common/src/main/java/com/whdc/common/aspect/WebLogAspect.java b/module-common/src/main/java/com/whdc/common/aspect/WebLogAspect.java
new file mode 100644
index 0000000..463384b
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/aspect/WebLogAspect.java
@@ -0,0 +1,86 @@
+package com.whdc.common.aspect;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.support.spring.PropertyPreFilters;
+import com.whdc.common.utils.ReqUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @author 李赛
+ * @date 2022-06-26 0:34
+ */
+
+@Aspect
+@Component
+@Slf4j
+public class WebLogAspect {
+
+ private void print(String fmt, Object... str) {
+ log.info(fmt, str);
+ }
+
+ @Pointcut("execution(public * com.whdc.controller.*Controller.*(..))")
+ public void controllerPointcut() {
+ }
+
+ @Before("controllerPointcut()")
+ public void doBefore(JoinPoint joinPoint) throws Throwable {
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = attributes.getRequest();
+ Signature signature = joinPoint.getSignature();
+ String name = signature.getName();
+
+ print("------------- 开始 -------------");
+ print("请求: {} {}", name, request.getRequestURI(), request.getMethod());
+ print("方法: {} {}.{}", name, signature.getDeclaringTypeName(), name);
+ print("地址: {} {}", name, ReqUtils.getIpAddress(request));
+
+ Object[] args = joinPoint.getArgs();
+ Object[] arguments = new Object[args.length];
+ for (int i = 0; i < args.length; i++) {
+ if (args[i] instanceof ServletRequest
+ || args[i] instanceof ServletResponse
+ || args[i] instanceof MultipartFile) {
+ continue;
+ }
+ arguments[i] = args[i];
+ }
+
+ String[] excludeProperties = {"password", "file"};
+ PropertyPreFilters filters = new PropertyPreFilters();
+ PropertyPreFilters.MySimplePropertyPreFilter excludeFilter = filters.addFilter();
+ excludeFilter.addExcludes(excludeProperties);
+ print("参数: {} {}", name, JSONObject.toJSONString(arguments, excludeFilter));
+ }
+
+ @Around("controllerPointcut()")
+ public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+ long startTime = System.currentTimeMillis();
+ Signature signature = proceedingJoinPoint.getSignature();
+ String name = signature.getName();
+ Object result = proceedingJoinPoint.proceed();
+
+ String[] excludeProperties = {"password", "file"};
+ PropertyPreFilters filters = new PropertyPreFilters();
+ PropertyPreFilters.MySimplePropertyPreFilter excludeFilter = filters.addFilter();
+ excludeFilter.addExcludes(excludeProperties);
+// print("返回结果: {}", JSONObject.toJSONString(result, excludeFilter));
+ print("------------- 耗时:{} {} ms -------------", name, System.currentTimeMillis() - startTime);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/module-common/src/main/java/com/whdc/common/config/CommonModuleConfig.java b/module-common/src/main/java/com/whdc/common/config/CommonModuleConfig.java
new file mode 100644
index 0000000..21f41d9
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/config/CommonModuleConfig.java
@@ -0,0 +1,20 @@
+package com.whdc.common.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * SMS模块配置
+ * 根据配置决定是否启用SMS模块
+ *
+ * @author lyf
+ * @since 2025-10-29
+ */
+@Configuration
+@ComponentScan(basePackages = "com.whdc.common")
+public class CommonModuleConfig {
+
+ // 在启动类上注解,扫描com.whdc.common包下的所有组件
+
+}
\ No newline at end of file
diff --git a/module-common/src/main/java/com/whdc/common/config/InterceptorConfig.java b/module-common/src/main/java/com/whdc/common/config/InterceptorConfig.java
new file mode 100644
index 0000000..0595126
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/config/InterceptorConfig.java
@@ -0,0 +1,46 @@
+package com.whdc.common.config;
+
+import okhttp3.OkHttpClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @author 李赛
+ * @date 2022-06-26 1:09
+ */
+@EnableWebMvc
+@Configuration
+public class InterceptorConfig implements WebMvcConfigurer {
+ @Bean
+ public OkHttpClient okHttpClient() {
+ return new OkHttpClient.Builder()
+ .build();
+ }
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
+ registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
+ registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+ }
+
+ /**
+ * 开启跨域
+ */
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ // 设置允许跨域的路由
+ registry.addMapping("/**")
+ // 设置允许跨域请求的域名------------修改此行
+ .allowedOriginPatterns("*")
+ // 是否允许证书(cookies)
+ .allowCredentials(true)
+ // 设置允许的方法
+ .allowedMethods("*")
+ // 跨域允许时间
+ .maxAge(3600);
+ }
+}
diff --git a/module-common/src/main/java/com/whdc/common/config/Knife4jConfiguration.java b/module-common/src/main/java/com/whdc/common/config/Knife4jConfiguration.java
new file mode 100644
index 0000000..6bcf2bd
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/config/Knife4jConfiguration.java
@@ -0,0 +1,66 @@
+package com.whdc.common.config;
+
+import com.github.xiaoymin.knife4j.spring.extension.OpenApiExtensionResolver;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+/**
+ * @author 李赛
+ * @date 2022-04-06 9:49
+ */
+@EnableSwagger2
+@Configuration
+public class Knife4jConfiguration {
+ private final OpenApiExtensionResolver openApiExtensionResolver;
+
+ @Autowired
+ public Knife4jConfiguration(OpenApiExtensionResolver openApiExtensionResolver) {
+ this.openApiExtensionResolver = openApiExtensionResolver;
+ }
+
+
+ private ApiInfo getApiInfoBuilder() {
+ return new ApiInfoBuilder()
+ .title("防汛抗旱通讯录api")
+ .description("# 防汛抗旱通讯录api RESTful APIs")
+ .termsOfServiceUrl("http://219.138.108.99:19000/jszx")
+ .contact(new Contact("湖北纬皓端成", null, null))
+ .version("1.0")
+ .build();
+ }
+
+ @Bean(value = "base")
+ public Docket base() {
+ return new Docket(DocumentationType.OAS_30)
+ .apiInfo(getApiInfoBuilder())
+ //分组名称
+ .groupName("v1.0")
+ .select()
+ //这里指定Controller扫描包路径
+ .apis(RequestHandlerSelectors.basePackage("com.whdc.controller"))
+ .paths(PathSelectors.any())
+ .build()
+ .extensions(openApiExtensionResolver.buildExtensions("v1.0"));
+ }
+
+ @Bean(value = "md")
+ public Docket md() {
+ return new Docket(DocumentationType.OAS_30)
+ .apiInfo(getApiInfoBuilder())
+ .select()
+ // ...
+ .build()
+ // 构建扩展插件-自定义文档 group
+ .extensions(openApiExtensionResolver.buildExtensions("doc-knife4j-1.0.0"))
+ .groupName("接口说明文档");
+ }
+}
\ No newline at end of file
diff --git a/module-common/src/main/java/com/whdc/common/config/MyBatisPlusConfig.java b/module-common/src/main/java/com/whdc/common/config/MyBatisPlusConfig.java
new file mode 100644
index 0000000..0135343
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/config/MyBatisPlusConfig.java
@@ -0,0 +1,34 @@
+package com.whdc.common.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author 李赛
+ * @date 2022-04-08 0:33
+ */
+@Configuration
+@MapperScan("com.whdc.mapper")
+public class MyBatisPlusConfig {
+
+ /* 旧版本配置
+ @Bean
+ public PaginationInterceptor paginationInterceptor(){
+ return new PaginationInterceptor();
+ }*/
+
+ /**
+ * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题
+ */
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.DM)); // 分页插件
+ return interceptor;
+ }
+}
+
diff --git a/module-common/src/main/java/com/whdc/common/config/MybatisInterceptor.java b/module-common/src/main/java/com/whdc/common/config/MybatisInterceptor.java
new file mode 100644
index 0000000..ee3d275
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/config/MybatisInterceptor.java
@@ -0,0 +1,85 @@
+package com.whdc.common.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Date;
+
+import static com.whdc.common.model.MyConstant.REC;
+
+@Slf4j
+@Component
+@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
+public class MybatisInterceptor implements Interceptor {
+
+ @Override
+ public Object intercept(Invocation invocation) throws Throwable {
+ MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
+ String sqlId = mappedStatement.getId();
+ log.debug("------sqlId------" + sqlId);
+ SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
+ Object parameter = invocation.getArgs()[1];
+ log.debug("------sqlCommandType------" + sqlCommandType);
+
+ if (parameter == null) {
+ return invocation.proceed();
+ }
+ if (SqlCommandType.INSERT == sqlCommandType || SqlCommandType.UPDATE == sqlCommandType) {
+ Field[] fields = parameter.getClass().getDeclaredFields();
+// Field[] fields = oConvertUtils.getAllFields(parameter);
+ for (Field field : fields) {
+
+ if (Modifier.isFinal(field.getModifiers())) {
+ continue;
+ }
+
+// if (field.getType().equals(String.class)) {
+// field.setAccessible(true);
+// Object o = field.get(parameter);
+// field.setAccessible(false);
+// String newVal = o == null ? "" : String.valueOf(o).trim();
+// field.setAccessible(true);
+// field.set(parameter, newVal);
+// field.setAccessible(false);
+// }
+
+ // 注入创建时间
+ if ("createtime".equals(field.getName()) || ("createTime".equals(field.getName()))) {
+ field.setAccessible(true);
+ Object local_createDate = field.get(parameter);
+ field.setAccessible(false);
+ if (local_createDate == null || "".equals(local_createDate)) {
+ field.setAccessible(true);
+ field.set(parameter, new Date());
+ field.setAccessible(false);
+ }
+ }
+
+ // 删除标识
+ if ("del".equals(field.getName())) {
+ field.setAccessible(true);
+ Object local_createDate = field.get(parameter);
+ field.setAccessible(false);
+ if (local_createDate == null || "".equals(local_createDate)) {
+ field.setAccessible(true);
+ field.set(parameter, REC);
+ field.setAccessible(false);
+ }
+ }
+
+ }
+ }
+
+ return invocation.proceed();
+ }
+
+}
\ No newline at end of file
diff --git a/module-common/src/main/java/com/whdc/common/config/RedisConfig.java b/module-common/src/main/java/com/whdc/common/config/RedisConfig.java
new file mode 100644
index 0000000..77b7450
--- /dev/null
+++ b/module-common/src/main/java/com/whdc/common/config/RedisConfig.java
@@ -0,0 +1,111 @@
+package com.whdc.common.config;
+
+/**
+ * Description:
+ * Created by XuSan on 2024/6/3.
+ *
+ * @author XuSan
+ * @version 1.0
+ */
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+
+import java.io.Serializable;
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ */
+@Configuration
+@EnableCaching
+public class RedisConfig implements Serializable {
+
+ /**
+ * 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)
+ * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值
+ */
+ /* @Bean
+ public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+ return RedisCacheManager.create(redisConnectionFactory);
+ }
+
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
+ // 创建一个模板类
+ RedisTemplate template = new RedisTemplate();
+ // 将刚才的redis连接工厂设置到模板类中
+ template.setConnectionFactory(factory);
+ // 设置key的序列化器
+ template.setKeySerializer(new StringRedisSerializer());
+ // 设置value的序列化器
+ //使用Jackson 2,将对象序列化为JSON
+ Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
+ //json转对象类,不设置默认的会将json转成hashmap
+ ObjectMapper om = new ObjectMapper();
+ om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+ om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+ jackson2JsonRedisSerializer.setObjectMapper(om);
+ template.setValueSerializer(jackson2JsonRedisSerializer);
+
+ return template;
+ }*/
+
+
+ /**
+ * 最新版,设置redis缓存过期时间
+ */
+// @Bean
+// public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+// RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
+// .entryTtl(Duration.ofDays(1))
+// .computePrefixWith(cacheName -> "caching:" + cacheName);
+//
+// return new CustomRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), defaultCacheConfig);
+// }
+
+ @Bean
+ public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
+ return new RedisCacheManager(
+ RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
+ this.getRedisCacheConfigurationWithTtl( -1), // 默认策略,未配置的 key 会使用这个
+ this.getRedisCacheConfigurationMap() // 指定 key 策略
+ );
+ }
+
+ private Map getRedisCacheConfigurationMap() {
+ Map redisCacheConfigurationMap = new HashMap<>();
+
+ //自定义设置缓存时间
+ redisCacheConfigurationMap.put("fxkh:txl:warning", this.getRedisCacheConfigurationWithTtl(120));
+
+ return redisCacheConfigurationMap;
+ }
+
+ private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
+ Jackson2JsonRedisSerializer