Commit 2a0f1012 by xushaohua

feat:部署

parent e43f611f
*.iml
*target/
*.svn/
*.settings/
*.project
*.classpath
*logs/
*.idea/
*bin/
*download/
*.log
\ No newline at end of file
call mvn -Ptest clean source:jar deploy -Denforcer.skip=true -Dmaven.test.skip=true -U -N
@pause
\ No newline at end of file
call mvn -Ptest clean install -Denforcer.skip=true -Dmaven.test.skip=true -U
call pause
\ No newline at end of file
......@@ -7,195 +7,196 @@ import org.springframework.stereotype.Component;
import java.time.Duration;
@Data
@Component
@ConfigurationProperties(prefix = "mj")
public class ProxyProperties {
/**
* task存储配置.
*/
private final TaskStore taskStore = new TaskStore();
/**
* discord配置.
*/
private final DiscordConfig discord = new DiscordConfig();
/**
* 代理配置.
*/
private final ProxyConfig proxy = new ProxyConfig();
/**
* 反代配置.
*/
private final NgDiscordConfig ngDiscord = new NgDiscordConfig();
/**
* 任务队列配置.
*/
private final TaskQueueConfig queue = new TaskQueueConfig();
/**
* 百度翻译配置.
*/
private final BaiduTranslateConfig baiduTranslate = new BaiduTranslateConfig();
/**
* openai配置.
*/
private final OpenaiConfig openai = new OpenaiConfig();
/**
* 中文prompt翻译方式.
*/
private TranslateWay translateWay = TranslateWay.NULL;
/**
* 接口密钥,为空不启用鉴权;调用接口时需要加请求头 mj-api-secret.
*/
private String apiSecret;
/**
* 任务状态变更回调地址.
*/
private String notifyHook;
/**
* 通知回调线程池大小.
*/
private int notifyPoolSize = 10;
/**
* 接口是否返回任务扩展属性.
*/
private boolean includeTaskExtended = false;
/**
* task存储配置.
*/
private final TaskStore taskStore = new TaskStore();
/**
* discord配置.
*/
private final DiscordConfig discord = new DiscordConfig();
/**
* 代理配置.
*/
private final ProxyConfig proxy = new ProxyConfig();
/**
* 反代配置.
*/
private final NgDiscordConfig ngDiscord = new NgDiscordConfig();
/**
* 任务队列配置.
*/
private final TaskQueueConfig queue = new TaskQueueConfig();
/**
* 百度翻译配置.
*/
private final BaiduTranslateConfig baiduTranslate = new BaiduTranslateConfig();
/**
* openai配置.
*/
private final OpenaiConfig openai = new OpenaiConfig();
/**
* 中文prompt翻译方式.
*/
private TranslateWay translateWay = TranslateWay.NULL;
/**
* 接口密钥,为空不启用鉴权;调用接口时需要加请求头 mj-api-secret.
*/
private String apiSecret;
/**
* 任务状态变更回调地址.
*/
private String notifyHook;
/**
* 通知回调线程池大小.
*/
private int notifyPoolSize = 10;
/**
* 接口是否返回任务扩展属性.
*/
private boolean includeTaskExtended = false;
@Data
public static class DiscordConfig {
/**
* 你的服务器id.
*/
private String guildId;
/**
* 你的频道id.
*/
private String channelId;
/**
* 你的登录token.
*/
private String userToken;
/**
* 你的频道id.
*/
private String sessionId = "9c4055428e13bcbf2248a6b36084c5f3";
/**
* 调用discord接口、连接wss时的user-agent.
*/
private String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36";
/**
* 是否使用user_token连接wss,默认启用.
*/
private boolean userWss = true;
/**
* 你的机器人token.
*/
private String botToken;
}
@Data
public static class DiscordConfig {
/**
* 你的服务器id.
*/
private String guildId;
/**
* 你的频道id.
*/
private String channelId;
/**
* 你的登录token.
*/
private String userToken;
/**
* 你的频道id.
*/
private String sessionId = "9c4055428e13bcbf2248a6b36084c5f3";
/**
* 调用discord接口、连接wss时的user-agent.
*/
private String userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36";
/**
* 是否使用user_token连接wss,默认启用.
*/
private boolean userWss = true;
/**
* 你的机器人token.
*/
private String botToken;
}
@Data
public static class BaiduTranslateConfig {
/**
* 百度翻译的APP_ID.
*/
private String appid;
/**
* 百度翻译的密钥.
*/
private String appSecret;
}
@Data
public static class BaiduTranslateConfig {
/**
* 百度翻译的APP_ID.
*/
private String appid;
/**
* 百度翻译的密钥.
*/
private String appSecret;
}
@Data
public static class OpenaiConfig {
/**
* 自定义gpt的api-url.
*/
private String gptApiUrl;
/**
* gpt的api-key.
*/
private String gptApiKey;
/**
* 超时时间.
*/
private Duration timeout = Duration.ofSeconds(30);
/**
* 使用的模型.
*/
private String model = "gpt-3.5-turbo";
/**
* 返回结果的最大分词数.
*/
private int maxTokens = 2048;
/**
* 相似度,取值 0-2.
*/
private double temperature = 0;
}
@Data
public static class OpenaiConfig {
/**
* 自定义gpt的api-url.
*/
private String gptApiUrl;
/**
* gpt的api-key.
*/
private String gptApiKey;
/**
* 超时时间.
*/
private Duration timeout = Duration.ofSeconds(30);
/**
* 使用的模型.
*/
private String model = "gpt-3.5-turbo";
/**
* 返回结果的最大分词数.
*/
private int maxTokens = 2048;
/**
* 相似度,取值 0-2.
*/
private double temperature = 0;
}
@Data
public static class TaskStore {
/**
* 任务过期时间,默认30天.
*/
private Duration timeout = Duration.ofDays(30);
/**
* 任务存储方式: redis(默认)、in_memory.
*/
private Type type = Type.IN_MEMORY;
@Data
public static class TaskStore {
/**
* 任务过期时间,默认30天.
*/
private Duration timeout = Duration.ofDays(30);
/**
* 任务存储方式: redis(默认)、in_memory.
*/
private Type type = Type.IN_MEMORY;
public enum Type {
/**
* redis.
*/
REDIS,
/**
* in_memory.
*/
IN_MEMORY
}
}
public enum Type {
/**
* redis.
*/
REDIS,
/**
* in_memory.
*/
IN_MEMORY
}
}
@Data
public static class ProxyConfig {
/**
* 代理host.
*/
private String host;
/**
* 代理端口.
*/
private Integer port;
}
@Data
public static class ProxyConfig {
/**
* 代理host.
*/
private String host;
/**
* 代理端口.
*/
private Integer port;
}
@Data
public static class NgDiscordConfig {
/**
* https://discord.com 反代.
*/
private String server;
/**
* https://cdn.discordapp.com 反代.
*/
private String cdn;
/**
* wss://gateway.discord.gg 反代.
*/
private String wss;
}
@Data
public static class NgDiscordConfig {
/**
* https://discord.com 反代.
*/
private String server;
/**
* https://cdn.discordapp.com 反代.
*/
private String cdn;
/**
* wss://gateway.discord.gg 反代.
*/
private String wss;
}
@Data
public static class TaskQueueConfig {
/**
* 并发数.
*/
private int coreSize = 3;
/**
* 等待队列长度.
*/
private int queueSize = 10;
/**
* 任务超时时间(分钟).
*/
private int timeoutMinutes = 5;
}
@Data
public static class TaskQueueConfig {
/**
* 并发数.
*/
private int coreSize = 8;
/**
* 等待队列长度.
*/
private int queueSize = 32;
/**
* 任务超时时间(分钟).
*/
private int timeoutMinutes = 5;
}
}
......@@ -29,6 +29,9 @@ import eu.maxschuster.dataurl.IDataUrlSerializer;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -45,6 +48,7 @@ import java.util.Set;
@RequiredArgsConstructor
public class SubmitController {
private final TranslateService translateService;
private final TaskStoreService taskStoreService;
private final ProxyProperties properties;
private final TaskService taskService;
......
......@@ -51,6 +51,9 @@ public class DiscordServiceImpl implements DiscordService {
private String discordChannelId;
private String discordSessionId;
/**
* 启动时初始化
*/
@PostConstruct
void init() {
ProxyProperties.DiscordConfig discord = this.properties.getDiscord();
......@@ -65,6 +68,7 @@ public class DiscordServiceImpl implements DiscordService {
this.discordUploadUrl = serverUrl + "/api/v9/channels/" + this.discordChannelId + "/attachments";
this.discordSendMessageUrl = serverUrl + "/api/v9/channels/" + this.discordChannelId + "/messages";
// 生成图片的json请求
this.imagineParamsJson = ResourceUtil.readUtf8Str("api-params/imagine.json");
this.upscaleParamsJson = ResourceUtil.readUtf8Str("api-params/upscale.json");
this.variationParamsJson = ResourceUtil.readUtf8Str("api-params/variation.json");
......@@ -74,6 +78,11 @@ public class DiscordServiceImpl implements DiscordService {
this.messageParamsJson = ResourceUtil.readUtf8Str("api-params/message.json");
}
/**
* 调用mj绘图
* @param prompt
* @return
*/
@Override
public Message<Void> imagine(String prompt) {
String paramsStr = this.imagineParamsJson.replace("$guild_id", this.discordGuildId)
......@@ -85,6 +94,14 @@ public class DiscordServiceImpl implements DiscordService {
return postJsonAndCheckStatus(params.toString());
}
/**
* 调用mj放大
* @param messageId
* @param index
* @param messageHash
* @param messageFlags
* @return
*/
@Override
public Message<Void> upscale(String messageId, int index, String messageHash, int messageFlags) {
String paramsStr = this.upscaleParamsJson.replace("$guild_id", this.discordGuildId)
......
......@@ -23,6 +23,12 @@ public class TaskServiceImpl implements TaskService {
private final DiscordService discordService;
private final TaskQueueHelper taskQueueHelper;
/**
* 提交绘图任务
* @param task
* @param dataUrl
* @return
*/
@Override
public SubmitResultVO submitImagine(Task task, DataUrl dataUrl) {
return this.taskQueueHelper.submitTask(task, () -> {
......
......@@ -3,15 +3,20 @@ package com.github.novicezk.midjourney.service.store;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.stream.StreamUtil;
import cn.hutool.core.util.ObjectUtil;
import com.github.novicezk.midjourney.service.TaskStoreService;
import com.github.novicezk.midjourney.support.Task;
import com.github.novicezk.midjourney.support.TaskCondition;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class InMemoryTaskStoreServiceImpl implements TaskStoreService {
private final TimedCache<String, Task> taskMap;
......@@ -31,12 +36,34 @@ public class InMemoryTaskStoreServiceImpl implements TaskStoreService {
@Override
public Task get(String key) {
return this.taskMap.get(key);
Task task = this.taskMap.get(key);
if(ObjectUtil.isNotNull(task)) {
try {
task.setFinishDate(DateUtil.formatDateTime(new Date(task.getFinishTime())));
task.setSubmitDate(DateUtil.formatDateTime(new Date(task.getSubmitTime())));
task.setStartDate(DateUtil.formatDateTime(new Date(task.getStartTime())));
Long costTime = (task.getFinishTime() - task.getStartTime()) / 1000;
task.setCostTime(costTime);
} catch (Exception e) {
}
}
return this.taskMap.get(key);
}
@Override
public List<Task> list() {
return ListUtil.toList(this.taskMap.iterator());
List<Task> tasks = ListUtil.toList(this.taskMap.iterator());
tasks.stream().forEach(x -> {
try {
x.setFinishDate(DateUtil.formatDateTime(new Date(x.getFinishTime())));
x.setSubmitDate(DateUtil.formatDateTime(new Date(x.getSubmitTime())));
x.setStartDate(DateUtil.formatDateTime(new Date(x.getStartTime())));
Long costTime = (x.getFinishTime() - x.getStartTime()) / 1000;
x.setCostTime(costTime);
} catch (Exception e) {
}
});
return ListUtil.toList(this.taskMap.iterator());
}
@Override
......
......@@ -8,6 +8,7 @@ import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Collections;
......
......@@ -14,6 +14,9 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
/**
* 百度翻译
*/
@Slf4j
public class BaiduTranslateServiceImpl implements TranslateService {
private static final String TRANSLATE_API = "https://fanyi-api.baidu.com/api/trans/vip/translate";
......
......@@ -24,6 +24,9 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* gpt翻译
*/
@Slf4j
public class GPTTranslateServiceImpl implements TranslateService {
private final OpenAiClient openAiClient;
......
......@@ -9,6 +9,7 @@ import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
......@@ -36,6 +37,13 @@ public class Task implements Serializable {
private Long startTime;
@ApiModelProperty("结束时间")
private Long finishTime;
@ApiModelProperty("提交时间")
private String submitDate;
@ApiModelProperty("开始执行时间")
private String startDate;
@ApiModelProperty("结束时间")
private String finishDate;
@ApiModelProperty("图片url")
private String imageUrl;
@ApiModelProperty("任务状态")
......@@ -45,6 +53,9 @@ public class Task implements Serializable {
@ApiModelProperty("失败原因")
private String failReason;
@ApiModelProperty("耗时:s")
private Long costTime;
// 任务扩展属性,仅支持基本类型
private Map<String, Object> properties;
......
......@@ -67,11 +67,20 @@ public class TaskQueueHelper {
return this.taskFutureMap.get(taskId);
}
/**
* 提交绘图任务
* @param task
* @param discordSubmit
* @return
*/
public SubmitResultVO submitTask(Task task, Callable<Message<Void>> discordSubmit) {
// 添加/更新任务到本地内存map中
this.taskStoreService.save(task);
int size;
try {
// 获取核心线程数
size = this.taskExecutor.getThreadPoolExecutor().getQueue().size();
// 异步执行绘图任务
Future<?> future = this.taskExecutor.submit(() -> executeTask(task, discordSubmit));
this.taskFutureMap.put(task.getId(), future);
} catch (RejectedExecutionException e) {
......@@ -89,16 +98,24 @@ public class TaskQueueHelper {
}
}
/**
* 执行具体绘图任务
* @param task
* @param discordSubmit
*/
private void executeTask(Task task, Callable<Message<Void>> discordSubmit) {
this.runningTasks.add(task);
try {
task.start();
// 获取discord绘图结果数据
Message<Void> result = discordSubmit.call();
// 看状态是否是成功,不是成功就更新任务状态为失败
if (result.getCode() != ReturnCode.SUCCESS) {
task.fail(result.getDescription());
changeStatusAndNotify(task, TaskStatus.FAILURE);
return;
}
// 更新任务状态为已提交
changeStatusAndNotify(task, TaskStatus.SUBMITTED);
do {
task.sleep();
......@@ -117,9 +134,17 @@ public class TaskQueueHelper {
}
}
/**
* 更新任务状态
* @param task
* @param status
*/
public void changeStatusAndNotify(Task task, TaskStatus status) {
// 更新任务状态
task.setStatus(status);
// 更新map值
this.taskStoreService.save(task);
this.notifyService.notifyTaskChange(task);
// 给对应的业务系统发送回调通知(暂时屏蔽)
// this.notifyService.notifyTaskChange(task);
}
}
\ No newline at end of file
......@@ -9,6 +9,10 @@ public interface WebSocketStarter {
void start() throws Exception;
/**
* 初始化代理服务器,这样整个java应用就可以通过代理服务器进行科学上网
* @param properties
*/
default void initProxy(ProxyProperties properties) {
ProxyProperties.ProxyConfig proxy = properties.getProxy();
if (Strings.isNotBlank(proxy.getHost())) {
......
......@@ -24,6 +24,10 @@ public class UserMessageListener implements ApplicationListener<ApplicationStart
this.messageHandlers.addAll(event.getApplicationContext().getBeansOfType(MessageHandler.class).values());
}
/**
* mj返回消息进行不同handler处理
* @param raw
*/
public void onMessage(DataObject raw) {
MessageType messageType = MessageType.of(raw.getString("t"));
if (messageType == null || MessageType.DELETE == messageType) {
......
......@@ -60,6 +60,10 @@ public class UserWebSocketStarter extends WebSocketAdapter implements WebSocketS
this.auth = createAuthData();
}
/**
* 连接websocket
* @throws Exception
*/
@Override
public synchronized void start() throws Exception {
this.decompressor = new ZlibDecompressor(2048);
......@@ -76,18 +80,24 @@ public class UserWebSocketStarter extends WebSocketAdapter implements WebSocketS
@Override
public void onConnected(WebSocket websocket, Map<String, List<String>> headers) {
log.debug("[gateway] Connected to websocket.");
log.debug("[gateway]连接上了websocket.");
this.connected = true;
}
/**
* 二进制消息事件
* @param websocket
* @param binary
* @throws Exception
*/
@Override
public void onBinaryMessage(WebSocket websocket, byte[] binary) throws Exception {
byte[] decompressBinary = this.decompressor.decompress(binary);
if (decompressBinary == null) {
return;
}
String json = new String(decompressBinary, StandardCharsets.UTF_8);
DataObject data = DataObject.fromJson(json);
if (decompressBinary == null) {
return;
}
String json = new String(decompressBinary, StandardCharsets.UTF_8);
DataObject data = DataObject.fromJson(json);
int opCode = data.getInt("op");
if (opCode != WebSocketCode.HEARTBEAT_ACK) {
this.sequence.incrementAndGet();
......@@ -220,7 +230,7 @@ public class UserWebSocketStarter extends WebSocketAdapter implements WebSocketS
}
protected void send(DataObject message) {
log.trace("[gateway] > {}", message);
log.trace("[gateway] > 发送消息:{}", message);
this.socket.sendText(message.toString());
}
......
proxy:
host: 127.0.0.1
port: 33210
\ No newline at end of file
mj:
proxy:
host: 127.0.0.1
port: 33210
\ No newline at end of file
......@@ -5,16 +5,27 @@ mj:
user-token: MTA5Mjc5MjYxNTI5ODE0NjQxNw.G4Pf5u.ScNr8HlfKbFq2-uwsnXgBZdctUaoPQsLstsFdY
bot-token: MTA5Mjc5MjYxNTI5ODE0NjQxNw.G4Pf5u.ScNr8HlfKbFq2-uwsnXgBZdctUaoPQsLstsFdY
session-id: 6768fe4d1fe14a87bc186f794c8a087e
# guild-id: 662267976984297473
# channel-id: 1008571165893197845
# user-token: MTEyMDYwODU1Njc2MTIyMzIzNA.GisBnJ.ucFpf4kCXWcQgMj1CPjL2QJ-1ciiv84x28cH_4
# bot-token: MTA5Mjc5MjYxNTI5ODE0NjQxNw.G4Pf5u.ScNr8HlfKbFq2-uwsnXgBZdctUaoPQsLstsFdY
# session-id: cfad344f1eb90cc724deb44b5e47edaa
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
user-wss: true
task-store:
type: in_memory
type: IN_MEMORY
timeout: 30d
translate-way: null
translate-way: baidu
queue:
timeout-minutes: 5
core-size: 3
queue-size: 10
#百度翻译配置
baidu-translate:
#百度翻译的APP_ID
appid: 20230620001718328
#百度翻译的密钥
app-secret: qZetHWpeRv9jgNwg8pUb
spring:
......@@ -22,6 +33,9 @@ spring:
name: midjourney-proxy
profiles:
active: dev
redis:
cluster:
nodes: 192.168.25.10:7001,192.168.25.10:7002,192.168.25.10:7003,192.168.25.10:7004,192.168.25.10:7005,192.168.25.10:7006
server:
port: 9090
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pcloud.universe</groupId>
<artifactId>universe-commons-parent</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<groupId>com.pcloud.common</groupId>
<artifactId>pcloud-book-parent</artifactId>
<version>3.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>pcloud-book-parent</name>
<modules>
<module>pcloud-facade-book</module>
<module>pcloud-service-book</module>
</modules>
<properties>
<reversion>3.1.0-SNAPSHOT</reversion>
</properties>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
<!-- 全局属性配置 -->
<profiles>
<!-- 开发环境 -->
<profile>
<id>dev</id>
<properties>
<!-- 部署环境(对应配置文件版本) -->
<env>dev</env>
<wxgroup-sdk.version>1.1.0-SNAPSHOT</wxgroup-sdk.version>
<coolq-sdk.version>1.0-SNAPSHOT</coolq-sdk.version>
</properties>
</profile>
<!-- 测试环境 -->
<profile>
<id>test</id>
<properties>
<!-- 部署环境(对应配置文件版本) -->
<env>test</env>
<maven.test.skip>false</maven.test.skip>
<wxgroup-sdk.version>1.0.0-SNAPSHOT</wxgroup-sdk.version>
<coolq-sdk.version>1.0-SNAPSHOT</coolq-sdk.version>
</properties>
</profile>
<!-- UAT -->
<profile>
<id>uat</id>
<properties>
<!-- 部署环境(对应配置文件版本) -->
<env>uat</env>
<wxgroup-sdk.version>1.0.0-RELEASE</wxgroup-sdk.version>
<coolq-sdk.version>1.0-RELEASE</coolq-sdk.version>
</properties>
<!-- 设置默认环境 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!-- 压测环境 -->
<profile>
<id>perf</id>
<properties>
<!-- 部署环境(对应配置文件版本) -->
<env>perf</env>
<wxgroup-sdk.version>1.0.0-SNAPSHOT</wxgroup-sdk.version>
<coolq-sdk.version>1.0-SNAPSHOT</coolq-sdk.version>
</properties>
</profile>
<!-- 生产环境 -->
<profile>
<id>prod</id>
<properties>
<!-- 部署环境(对应配置文件版本) -->
<env>prod</env>
<wxgroup-sdk.version>1.1.0-SNAPSHOT</wxgroup-sdk.version>
<coolq-sdk.version>1.1.0-RELEASE</coolq-sdk.version>
</properties>
</profile>
<profile>
<id>tsrpd</id>
<properties>
<!-- 部署环境(对应配置文件版本) -->
<env>tsrpd</env>
<wxgroup-sdk.version>1.1.0-SNAPSHOT</wxgroup-sdk.version>
<coolq-sdk.version>1.1.0-RELEASE</coolq-sdk.version>
</properties>
</profile>
</profiles>
</project>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment