create: 新增抖店、电话外呼依赖

This commit is contained in:
Damonny 2025-02-06 16:37:50 +08:00
parent 203bb87b66
commit 7d8271ba59
16 changed files with 821 additions and 1 deletions

View File

@ -72,6 +72,13 @@
<justauth.version>2.0.5</justauth.version>
<jimureport.version>1.8.1</jimureport.version>
<weixin-java.version>4.6.0</weixin-java.version>
<!-- qetesh 相关依赖-->
<feign-slf4j.version>11.9.1</feign-slf4j.version>
<feign-okhttp.version>11.9.1</feign-okhttp.version>
<feign-jackson.version>11.9.1</feign-jackson.version>
<feign-core.version>11.9.1</feign-core.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
</properties>
<dependencyManagement>
@ -592,6 +599,40 @@
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>${mqtt.version}</version>
</dependency>
<!-- qetesh-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${feign-core.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${feign-jackson.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>${feign-okhttp.version}</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
<version>${feign-slf4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -67,6 +67,31 @@
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- 抖店api本地导入-->
<dependency>
<groupId>com.doudian.open</groupId>
<artifactId>doudian-sdk-java</artifactId>
<version>1.1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/src/main/resources/lib/doudian-sdk-java-1.1.0.jar</systemPath>
</dependency>
<!-- qetesh-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.haoka.qetesh;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.List;
import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
/**
* 允许进行特定是序列化和反序列化
*/
public class CustomizeDatabind {
public static class EscapedOrListToString extends JsonDeserializer<List<String>> {
@Override
public List<String> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
if (p.currentToken().isStructStart()) {
//list
return p.readValueAs(new TypeReference<List<String>>() {
});
} else {
//string
String valueAsString = p.getValueAsString();
return JacksonHolder.objectMapper.readValue(valueAsString,
new TypeReference<List<String>>() {
});
}
}
}
public static class EpochMilliToLocalDateTime extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
LocalDateTime localDateTime = Instant.ofEpochMilli(value).atZone(ZoneId.systemDefault()).toLocalDateTime();
gen.writeString(localDateTime.toString());
}
}
public static class StrToLongDeserializer extends JsonDeserializer<Long> {
@Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return Long.valueOf(p.getValueAsString());
}
}
public static class LongToStrSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(String.valueOf(value));
}
}
public static class TsStrDeserializer extends JsonDeserializer<Timestamp> {
@Override
public Timestamp deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
if (isNotEmpty(p.getLongValue())) {
return new Timestamp(p.getLongValue());
}
return null;
}
}
public static class InstantToLongSerializer extends JsonSerializer<Instant> {
@Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeNumber(value.toEpochMilli());
}
}
public static class LongToInstantDeserializer extends JsonDeserializer<Instant> {
@Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return Instant.ofEpochMilli(p.getLongValue());
}
}
public static class LocalTimeToString extends JsonSerializer<LocalTime> {
@Override
public void serialize(LocalTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value.equals(LocalTime.MAX)) {
gen.writeString("24:00");
} else {
gen.writeString(value.toString());
}
}
}
public static class StringToLocalTime extends JsonDeserializer<LocalTime> {
@Override
public LocalTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
if (p.getValueAsString().startsWith("24:")) {
return LocalTime.MAX;
} else {
return LocalTime.parse(p.getValueAsString());
}
}
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.haoka.qetesh;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
public class JacksonHolder {
public static final ObjectMapper objectMapper;
//init objectMapper
static {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new ParameterNamesModule());
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
public static String writeValueAsString(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.haoka.qetesh;
import feign.Headers;
import feign.RequestLine;
import lombok.Data;
@Headers({
"Content-Type: application/json"
})
public interface OpenapiAuthClient {
@RequestLine("POST /oauth/v1/token")
ServiceResponse<GetTokeResponse> getToken(GetTokenRequest request);
@Data
class GetTokeResponse {
private String token;
private Integer timeExpire;
}
@Data
class GetTokenRequest {
private String appKey;
private String appSecret;
}
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.haoka.qetesh;
import feign.Feign;
import feign.Logger.Level;
import feign.Retryer;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.slf4j.Slf4jLogger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenapiAuthClientConfig {
@Bean
public OpenapiAuthClient openapiAuthClient(@Value("${openapi-demo.host}") String openapiHost) {
return Feign.builder()
.logger(new Slf4jLogger(OpenapiAuthClient.class))
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.logLevel(Level.FULL)
.retryer(Retryer.NEVER_RETRY)
.target(OpenapiAuthClient.class, openapiHost);
}
}

View File

@ -0,0 +1,409 @@
package cn.iocoder.yudao.module.haoka.qetesh;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import feign.Headers;
import feign.Param;
import feign.QueryMap;
import feign.RequestLine;
import lombok.Data;
import lombok.Value;
import java.time.Instant;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;
@Headers({
"Content-Type: application/json"
})
public interface OpenapiClient {
@RequestLine("POST /api/v1/speechskill/querySpeechSkillPage")
ServiceResponse<?> getSpeechSkillList(GetSpeechSkillListRequest request);
@Data
class GetSpeechSkillListRequest {
private int current = 1;
private int size = 10;
}
@RequestLine("GET /cti/queryCpnList")
ServiceResponse<List<GetCpnResponse>> getCpnList();
@Data
class GetCpnResponse {
//线路id
private Long id;
private String cpnCode;
private String contact;
private Integer concurrent;
private String explicitNumber;
private String limitBeginTime;
private String limitEndTime;
private String expirationTime;
private Integer useStatus;
private Long createTime;
private Integer cpnStatus;
private String prefix;
}
/**
* 创建普通任务
* @param request
* @return
*/
@RequestLine("POST /outbound/tasks/temporary/fileless")
ServiceResponse<String> createFilelessTask(CreateFilelessTaskRequest request);
/**
* 创建普通任务数据
* @param request
* @return
*/
@RequestLine("POST /outbound/task/temporary/fileless/data")
ServiceResponse<List<String>> createFilelessTaskData(CreateFilelessTaskDataRequest request);
/**
* 启动普通任务
* @param taskId
* @return
*/
@RequestLine("PUT /outbound/task/temporary/fileless/{taskId}/start")
ServiceResponse<?> startFilelessTask(@Param("taskId") String taskId);
/**
* 更新普通任务
*
* @return
*/
@RequestLine("PUT /outbound/tasks/temporary/fileless")
ServiceResponse<?> updateFilelessTask(UpdateFilelessTaskRequest request);
/**
* 更新任务状态
* @param request
* @return
*/
@RequestLine("PUT /outbound/task/status")
ServiceResponse<?> updateTaskStatus(UpdateTaskStatusRequest request);
/**
* 创建长期任务
* @param request
* @return
*/
@RequestLine("POST /outbound/tasks/permanent")
ServiceResponse<String> createPermanentTask(CreatePermanentTaskRequest request);
/**
* 创建长期任务数据
* @param request
* @return
*/
@RequestLine("POST /outbound/task/permanent/data")
ServiceResponse<List<String>> createPermanentTaskData(CreatePermanentTaskDataRequest request);
/**
* 更新长期任务
* @param request
* @return
*/
@RequestLine("PUT /outbound/tasks/permanent")
ServiceResponse<?> updatePermanentTask(UpdatePermanentTaskRequest request);
@RequestLine("GET /outbound/tasks")
ServiceResponse<?> queryTaskList(@QueryMap QueryTaskListRequest request);
@Data
class QueryTaskListRequest {
private String taskId;
private Long startTime;
private Long endTime;
private String taskName;
private List<Integer> taskStatusList;
private String sortName;
private SortOrder sortOrder;
int pageIndex = 1;
int pageSize = 10;
public enum SortOrder {
ASC,DESC
}
}
@Data
class UpdatePermanentTaskRequest{
private String taskId;
/**
* 名称
*/
private String name;
/**
* 工作时间
*/
private WorkTime workTime;
/**
* 起止时间
*/
private Life life;
private Boolean skipHolidays;
}
@Data
class CreatePermanentTaskDataRequest {
/**
* 任务id
*/
private String taskId;
/**
* 载体
*/
private List<TaskDataPayload> payloads;
/**
* 优先级 只支持0和1
*/
private int priority = 0;
private Long speechSkillId;
private List<Long> lineIds;
}
@Data
class CreatePermanentTaskRequest {
/**
* 任务名称
*/
private String name;
/**
* 起止设置
*/
private Life life;
/**
* 工作时间设置
*/
private WorkTime workTime;
}
@Data
class UpdateTaskStatusRequest {
/**
* 任务id
*/
private String taskId;
/**
* 状态 2:允许 3:暂停 8:删除
*/
private Integer status;
}
@Data
class UpdateFilelessTaskRequest {
private List<Long> lineIds;
private String taskId;
/**
* 起止时间
*/
private Life life;
/**
* 工作时间
*/
private WorkTime workTime;
private Boolean skipHolidays;
}
/**
* 任务的生命周期何时开始何时结束
*/
@Value
class Life {
@JsonSerialize(using = CustomizeDatabind.InstantToLongSerializer.class)
Instant begin;
@JsonSerialize(using = CustomizeDatabind.InstantToLongSerializer.class)
Instant end;
@JsonCreator
public Life(
@JsonProperty("begin") @JsonDeserialize(using = CustomizeDatabind.LongToInstantDeserializer.class) Instant begin,
@JsonProperty("end") @JsonDeserialize(using = CustomizeDatabind.LongToInstantDeserializer.class) Instant end) {
if (!begin.isBefore(end)) {
throw new LifeNotValidExp();
}
this.begin = begin;
this.end = end;
}
public static Life forever() {
return new Life(Instant.EPOCH, Instant.ofEpochMilli(2147483647000L));
}
public static class LifeNotValidExp extends RuntimeException {
}
}
/**
* 工作时间段列表
*/
@Value
class WorkTime {
List<WorkHours> workHoursList;
@JsonCreator
public WorkTime(@JsonProperty("workHoursList") List<WorkHours> workHoursList) {
this.workHoursList = workHoursList;
}
public static WorkTime allDay() {
WorkHours partOne = new WorkHours(LocalTime.MIN, LocalTime.NOON);
WorkHours partTwo = new WorkHours(LocalTime.NOON, LocalTime.MAX);
return new WorkTime(Lists.newArrayList(partOne, partTwo));
}
/**
* 一天中的一段时间
*/
@Value
public static class WorkHours {
@JsonSerialize(using = CustomizeDatabind.LocalTimeToString.class)
LocalTime begin;
@JsonSerialize(using = CustomizeDatabind.LocalTimeToString.class)
LocalTime end;
@JsonCreator
public WorkHours(
@JsonProperty("begin") @JsonDeserialize(using = CustomizeDatabind.StringToLocalTime.class) LocalTime begin,
@JsonProperty("end") @JsonDeserialize(using = CustomizeDatabind.StringToLocalTime.class) LocalTime end) {
this.begin = begin;
this.end = end;
}
}
}
@Data
class RedialSetting {
/**
* 最大次数
*/
Integer maxTimes;
/**
* 重播之间的间隔 单位ms
*/
Long interval;
/**
* 需要重拨的结果类型
*/
List<Integer> results;
}
@Data
class CreateFilelessTaskRequest {
/**
* 任务名称不要超过1024个字符
*/
private String name;
/**
* 起止时间
*/
private Life life;
/**
* 工作时间
*/
private WorkTime workTime;
/**
* 话术id
*/
private Long speechSkillId;//话术id
/**
* 重呼设置
*/
private RedialSetting redialSetting;
/**
* 线路id列表
*/
private List<Long> lineIds;
/**
* 跳过节假日
*/
private Boolean skipHolidays;
}
@Value
class TaskDataPayload {
Map<String, Object> payload;
@JsonCreator
public TaskDataPayload(@JsonProperty("payload") Map<String, Object> payload) {
Preconditions.checkNotNull(payload);
this.payload = payload;
}
}
@Data
class CreateFilelessTaskDataRequest{
/**
* 任务id
*/
private String taskId;
/**
* 数据列表
*/
private List<TaskDataPayload> payloads;
}
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.haoka.qetesh;
import feign.Feign;
import feign.Logger.Level;
import feign.Retryer;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.slf4j.Slf4jLogger;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class OpenapiClientConfig {
@Bean
public OpenapiClient openapiClient(@Value("${openapi-demo.host}") String openapiHost,
TokenRequestInterceptor tokenRequestInterceptor) {
return Feign.builder()
.logger(new Slf4jLogger(OpenapiClient.class))
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.requestInterceptor(tokenRequestInterceptor)
.logLevel(Level.FULL)
.retryer(Retryer.NEVER_RETRY)
.target(OpenapiClient.class, openapiHost);
}
}

View File

@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.haoka.qetesh;
public class ServiceResponse<T> {
private int code;
private String msg;
private T data;
public int getCode() {
return this.code;
}
public ServiceResponse<T> setCode(int code) {
this.code = code;
return this;
}
public String getMsg() {
return this.msg;
}
public ServiceResponse<T> setMsg(String msg) {
this.msg = msg;
return this;
}
public T getData() {
return this.data;
}
public ServiceResponse<T> setData(T data) {
this.data = data;
return this;
}
public ServiceResponse(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public ServiceResponse() {
}
public ServiceResponse(int code, String msg) {
this.code = code;
this.msg = msg;
}
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.haoka.qetesh;
//import com.kxjl.qeteshopenapidemo.OpenapiAuthClient.GetTokeResponse;
//import com.kxjl.qeteshopenapidemo.OpenapiAuthClient.GetTokenRequest;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class TokenRequestInterceptor implements RequestInterceptor {
@Autowired
private OpenapiAuthClient openapiAuthClient;
@Value("${openapi-demo.appKey}")
private String appKey;
@Value("${openapi-demo.appSecret}")
private String appSecret;
@Override
public void apply(RequestTemplate template) {
if (template.request().url()
.contains("/oauth/v1/token")) {
return;
}
OpenapiAuthClient.GetTokenRequest request = new OpenapiAuthClient.GetTokenRequest();
request.setAppKey(appKey);
request.setAppSecret(appSecret);
ServiceResponse<OpenapiAuthClient.GetTokeResponse> response = openapiAuthClient.getToken(request);
if (response.getCode() != 0) {
log.warn("获取token失败");
}
template.header("Authorization", "Bearer " + response.getData().getToken());
}
}

View File

@ -165,6 +165,9 @@
</goals>
</execution>
</executions>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>

View File

@ -213,4 +213,8 @@ iot:
# 保持连接
keepalive: 60
# 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
clearSession: true
clearSession: true
openapi-demo:
host: http://qetesh.kxjlcc.com:31880/qetesh-openapi
appKey: 0257a273c480433b85e141e61a5c6fe1
appSecret: $2a$10$0nARFdymExxJ9spsMsbyJeuzVKfmOMO0m6OoJsHOhgbxr4InimA9W

View File

@ -177,6 +177,8 @@ logging:
cn.iocoder.yudao.module.iot.dal.mysql: debug
cn.iocoder.yudao.module.ai.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿先禁用Spring Boot 3.X 存在部分错误的 WARN 提示
cn.iocoder.yudao.module.haoka.qetesh.OpenapiAuthClient: debug
cn.iocoder.yudao.module.haoka.qetesh.OpenapiClient: debug
debug: false
@ -273,3 +275,7 @@ iot:
keepalive: 60
# 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
clearSession: true
openapi-demo:
host: http://qetesh.kxjlcc.com:31880/qetesh-openapi
appKey: 0257a273c480433b85e141e61a5c6fe1
appSecret: $2a$10$0nARFdymExxJ9spsMsbyJeuzVKfmOMO0m6OoJsHOhgbxr4InimA9W

View File

@ -177,6 +177,8 @@ logging:
cn.iocoder.yudao.module.iot.dal.mysql: debug
cn.iocoder.yudao.module.ai.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿先禁用Spring Boot 3.X 存在部分错误的 WARN 提示
cn.iocoder.yudao.module.haoka.qetesh.OpenapiAuthClient: debug
cn.iocoder.yudao.module.haoka.qetesh.OpenapiClient: debug
debug: false
@ -273,3 +275,7 @@ iot:
keepalive: 60
# 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
clearSession: true
openapi-demo:
host: http://qetesh.kxjlcc.com:31880/qetesh-openapi
appKey: 0257a273c480433b85e141e61a5c6fe1
appSecret: $2a$10$0nARFdymExxJ9spsMsbyJeuzVKfmOMO0m6OoJsHOhgbxr4InimA9W

View File

@ -177,6 +177,8 @@ logging:
cn.iocoder.yudao.module.iot.dal.mysql: debug
cn.iocoder.yudao.module.ai.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿先禁用Spring Boot 3.X 存在部分错误的 WARN 提示
cn.iocoder.yudao.module.haoka.qetesh.OpenapiAuthClient: debug
cn.iocoder.yudao.module.haoka.qetesh.OpenapiClient: debug
debug: false
@ -273,3 +275,7 @@ iot:
keepalive: 60
# 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)
clearSession: true
openapi-demo:
host: http://qetesh.kxjlcc.com:31880/qetesh-openapi
appKey: 0257a273c480433b85e141e61a5c6fe1
appSecret: $2a$10$0nARFdymExxJ9spsMsbyJeuzVKfmOMO0m6OoJsHOhgbxr4InimA9W