From c8b70718d269df84d776c3e57012c7f91db28585 Mon Sep 17 00:00:00 2001 From: odboy Date: Thu, 5 Dec 2024 21:51:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8A=A8=E6=80=81=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=B5=8B=E8=AF=95=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/constant/ConfigClientMsgType.java | 7 -- .../config/constant/TransferMessageType.java | 27 ++++++ .../cn/odboy/config/model/SmallMessage.java | 5 +- .../{ClientProp.java => ClientInfo.java} | 2 +- .../config/model/msgtype/ConfigFileInfo.java | 11 +++ .../odboy/config/model/msgtype/ConfigKv.java | 15 ---- .../cn/odboy/config/util/MessageUtil.java | 18 ++++ .../cn/odboy/config/util/ProtostuffUtil.java | 7 +- .../config/context/ClientConfigLoader.java | 51 +++++++---- .../cn/odboy/config/netty/ConfigClient.java | 2 + .../config/netty/ConfigClientHandler.java | 86 +++++++++++++++---- .../src/main/java/cn/odboy/GenCode.java | 26 +----- .../main/java/cn/odboy/domain/ConfigApp.java | 36 ++++++++ .../main/java/cn/odboy/domain/ConfigFile.java | 45 ++++++++++ .../java/cn/odboy/domain/ConfigVersion.java | 28 ++++++ .../odboy/infra/netty/ConfigClientManage.java | 33 ++++--- .../odboy/infra/netty/ConfigNettyServer.java | 6 +- .../infra/netty/ConfigServerHandler.java | 85 +++++++++++------- .../java/cn/odboy/mapper/ConfigAppMapper.java | 19 ++++ .../cn/odboy/mapper/ConfigFileMapper.java | 23 +++++ .../cn/odboy/rest/ConfigFileController.java | 18 ++++ .../cn/odboy/service/ConfigAppService.java | 17 ++++ .../cn/odboy/service/ConfigFileService.java | 22 +++++ .../service/impl/ConfigAppServiceImpl.java | 23 +++++ .../service/impl/ConfigFileServiceImpl.java | 28 ++++++ .../resources/mapper/ConfigFileMapper.xml | 13 +++ kenaito_config.sql | 39 ++++++++- 27 files changed, 568 insertions(+), 124 deletions(-) delete mode 100644 kenaito-config-common/src/main/java/cn/odboy/config/constant/ConfigClientMsgType.java create mode 100644 kenaito-config-common/src/main/java/cn/odboy/config/constant/TransferMessageType.java rename kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/{ClientProp.java => ClientInfo.java} (82%) create mode 100644 kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ConfigFileInfo.java delete mode 100644 kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ConfigKv.java create mode 100644 kenaito-config-common/src/main/java/cn/odboy/config/util/MessageUtil.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/domain/ConfigApp.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/domain/ConfigFile.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/domain/ConfigVersion.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/mapper/ConfigAppMapper.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/mapper/ConfigFileMapper.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/rest/ConfigFileController.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/service/ConfigAppService.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/service/ConfigFileService.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/service/impl/ConfigAppServiceImpl.java create mode 100644 kenaito-config-service/src/main/java/cn/odboy/service/impl/ConfigFileServiceImpl.java create mode 100644 kenaito-config-service/src/main/resources/mapper/ConfigFileMapper.xml diff --git a/kenaito-config-common/src/main/java/cn/odboy/config/constant/ConfigClientMsgType.java b/kenaito-config-common/src/main/java/cn/odboy/config/constant/ConfigClientMsgType.java deleted file mode 100644 index 6893e21..0000000 --- a/kenaito-config-common/src/main/java/cn/odboy/config/constant/ConfigClientMsgType.java +++ /dev/null @@ -1,7 +0,0 @@ -package cn.odboy.config.constant; - -public interface ConfigClientMsgType { - int REGISTER = 1; - int PULL_CONFIG = 2; - int UPDATE_CONFIG = 3; -} diff --git a/kenaito-config-common/src/main/java/cn/odboy/config/constant/TransferMessageType.java b/kenaito-config-common/src/main/java/cn/odboy/config/constant/TransferMessageType.java new file mode 100644 index 0000000..81316b1 --- /dev/null +++ b/kenaito-config-common/src/main/java/cn/odboy/config/constant/TransferMessageType.java @@ -0,0 +1,27 @@ +package cn.odboy.config.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 传输的消息类型 + * + * @author odboy + * @date 2024-12-05 + */ +@Getter +@AllArgsConstructor +public enum TransferMessageType { + /** + * 客户端注册 + */ + REGISTER, + /** + * 客户端主动拉取配置文件 + */ + PULL_CONFIG, + /** + * 服务端主动推送配置文件 + */ + PUSH_CONFIG; +} diff --git a/kenaito-config-common/src/main/java/cn/odboy/config/model/SmallMessage.java b/kenaito-config-common/src/main/java/cn/odboy/config/model/SmallMessage.java index ce0cec2..4c86825 100644 --- a/kenaito-config-common/src/main/java/cn/odboy/config/model/SmallMessage.java +++ b/kenaito-config-common/src/main/java/cn/odboy/config/model/SmallMessage.java @@ -1,5 +1,6 @@ package cn.odboy.config.model; +import cn.odboy.config.constant.TransferMessageType; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -11,9 +12,9 @@ import java.io.Serializable; @NoArgsConstructor public class SmallMessage implements Serializable { /** - * 消息类型:cn.odboy.config.constant.ConfigClientMsgType + * 消息类型:cn.odboy.config.constant.TransferMessageType */ - private int type; + private TransferMessageType type; private Response resp; @Data diff --git a/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ClientProp.java b/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ClientInfo.java similarity index 82% rename from kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ClientProp.java rename to kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ClientInfo.java index 94f5a27..32c3fd2 100644 --- a/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ClientProp.java +++ b/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ClientInfo.java @@ -5,7 +5,7 @@ import lombok.Data; import java.io.Serializable; @Data -public class ClientProp implements Serializable { +public class ClientInfo implements Serializable { private String server; private Integer port; private String env; diff --git a/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ConfigFileInfo.java b/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ConfigFileInfo.java new file mode 100644 index 0000000..a28f347 --- /dev/null +++ b/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ConfigFileInfo.java @@ -0,0 +1,11 @@ +package cn.odboy.config.model.msgtype; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class ConfigFileInfo implements Serializable { + private String fileName; + private String fileContent; +} diff --git a/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ConfigKv.java b/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ConfigKv.java deleted file mode 100644 index aab4a7b..0000000 --- a/kenaito-config-common/src/main/java/cn/odboy/config/model/msgtype/ConfigKv.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.odboy.config.model.msgtype; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class ConfigKv implements Serializable { - private String key; - private Object value; -} diff --git a/kenaito-config-common/src/main/java/cn/odboy/config/util/MessageUtil.java b/kenaito-config-common/src/main/java/cn/odboy/config/util/MessageUtil.java new file mode 100644 index 0000000..147d179 --- /dev/null +++ b/kenaito-config-common/src/main/java/cn/odboy/config/util/MessageUtil.java @@ -0,0 +1,18 @@ +package cn.odboy.config.util; + +import cn.odboy.config.model.SmallMessage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public class MessageUtil { + public static ByteBuf toByteBuf(Object data){ + return Unpooled.copiedBuffer(ProtostuffUtil.serializer(data)); + } + + public static SmallMessage getMessage(Object msg) { + ByteBuf buf = (ByteBuf) msg; + byte[] bytes = new byte[buf.readableBytes()]; + buf.readBytes(bytes); + return ProtostuffUtil.deserializer(bytes, SmallMessage.class); + } +} diff --git a/kenaito-config-common/src/main/java/cn/odboy/config/util/ProtostuffUtil.java b/kenaito-config-common/src/main/java/cn/odboy/config/util/ProtostuffUtil.java index f2ee0bc..1cfda1d 100644 --- a/kenaito-config-common/src/main/java/cn/odboy/config/util/ProtostuffUtil.java +++ b/kenaito-config-common/src/main/java/cn/odboy/config/util/ProtostuffUtil.java @@ -1,10 +1,11 @@ package cn.odboy.config.util; -import cn.odboy.config.model.msgtype.ConfigKv; +import cn.odboy.config.model.msgtype.ConfigFileInfo; import com.dyuproject.protostuff.LinkedBuffer; import com.dyuproject.protostuff.ProtostuffIOUtil; import com.dyuproject.protostuff.Schema; import com.dyuproject.protostuff.runtime.RuntimeSchema; + import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -66,8 +67,8 @@ public class ProtostuffUtil { } public static void main(String[] args) { - byte[] userBytes = ProtostuffUtil.serializer(new ConfigKv("app.config", "zhuge")); - ConfigKv user = ProtostuffUtil.deserializer(userBytes, ConfigKv.class); + byte[] userBytes = ProtostuffUtil.serializer(new ConfigFileInfo()); + ConfigFileInfo user = ProtostuffUtil.deserializer(userBytes, ConfigFileInfo.class); System.out.println(user); } } \ No newline at end of file diff --git a/kenaito-config-core/src/main/java/cn/odboy/config/context/ClientConfigLoader.java b/kenaito-config-core/src/main/java/cn/odboy/config/context/ClientConfigLoader.java index 6114611..6f67ef6 100644 --- a/kenaito-config-core/src/main/java/cn/odboy/config/context/ClientConfigLoader.java +++ b/kenaito-config-core/src/main/java/cn/odboy/config/context/ClientConfigLoader.java @@ -1,7 +1,7 @@ package cn.odboy.config.context; import cn.hutool.core.io.FileUtil; -import cn.odboy.config.model.msgtype.ClientProp; +import cn.odboy.config.model.msgtype.ClientInfo; import cn.odboy.config.netty.ConfigClient; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; @@ -10,11 +10,14 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; /** * 配置加载器 @@ -50,7 +53,15 @@ public class ClientConfigLoader { /** * 当前客户端配置 */ - public static ClientProp clientProp = new ClientProp(); + public static final ClientInfo clientInfo = new ClientInfo(); + /** + * 配置是否加载完毕 + */ + public static boolean isConfigLoaded = false; + /** + * 所有的配置信息 + */ + public static Map lastConfigs = new HashMap<>(); @Bean public BeanFactoryPostProcessor configLoader(ConfigurableEnvironment environment) { @@ -58,25 +69,35 @@ public class ClientConfigLoader { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { String defaultCacheDir = getDefaultCacheDir(); - clientProp.setServer(environment.getProperty(DEFAULT_CONFIG_NAME_SERVER, String.class, DEFAULT_CONFIG_SERVER)); - clientProp.setPort(environment.getProperty(DEFAULT_CONFIG_NAME_PORT, Integer.class, DEFAULT_CONFIG_PORT)); - clientProp.setEnv(environment.getProperty(DEFAULT_CONFIG_NAME_ENV, String.class, DEFAULT_CONFIG_ENV)); - clientProp.setDataId(environment.getProperty(DEFAULT_CONFIG_NAME_DATA_ID, String.class, DEFAULT_CONFIG_DATA_ID)); - clientProp.setCacheDir(environment.getProperty(DEFAULT_CONFIG_NAME_CACHE_DIR, String.class, defaultCacheDir)); - log.info("客户端属性: {}", clientProp.toString()); - validateCacheDirPath(defaultCacheDir, clientProp.getCacheDir()); - createCacheDir(clientProp.getCacheDir()); + clientInfo.setServer(environment.getProperty(DEFAULT_CONFIG_NAME_SERVER, String.class, DEFAULT_CONFIG_SERVER)); + clientInfo.setPort(environment.getProperty(DEFAULT_CONFIG_NAME_PORT, Integer.class, DEFAULT_CONFIG_PORT)); + clientInfo.setEnv(environment.getProperty(DEFAULT_CONFIG_NAME_ENV, String.class, DEFAULT_CONFIG_ENV)); + clientInfo.setDataId(environment.getProperty(DEFAULT_CONFIG_NAME_DATA_ID, String.class, DEFAULT_CONFIG_DATA_ID)); + clientInfo.setCacheDir(environment.getProperty(DEFAULT_CONFIG_NAME_CACHE_DIR, String.class, defaultCacheDir)); + log.info("客户端属性: {}", clientInfo); + validateCacheDirPath(defaultCacheDir, clientInfo.getCacheDir()); + createCacheDir(clientInfo.getCacheDir()); try { ConfigClient client = new ConfigClient(); - client.start(clientProp.getServer(), clientProp.getPort()); + client.start(clientInfo.getServer(), clientInfo.getPort()); + // 这里加个同步锁,等客户端准备就绪后,拉取配置完成时 + synchronized (clientInfo) { + while (!isConfigLoaded) { + try { + // 等待配置加载完成 + clientInfo.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Thread interrupted", e); + } + } + MapPropertySource propertySource = new MapPropertySource("loadFormServer", lastConfigs); + environment.getPropertySources().addFirst(propertySource); + } } catch (InterruptedException e) { log.error("Netty Client Start Error", e); throw new RuntimeException(e); } -// ConfigClient client = new ConfigClient("http://your-config-center-url/config"); -// Map configKv = client.fetchConfig(); -// MapPropertySource propertySource = new MapPropertySource("configCenter", configKv); -// environment.getPropertySources().addFirst(propertySource); } }; } diff --git a/kenaito-config-core/src/main/java/cn/odboy/config/netty/ConfigClient.java b/kenaito-config-core/src/main/java/cn/odboy/config/netty/ConfigClient.java index bb8a7f5..a118559 100644 --- a/kenaito-config-core/src/main/java/cn/odboy/config/netty/ConfigClient.java +++ b/kenaito-config-core/src/main/java/cn/odboy/config/netty/ConfigClient.java @@ -43,6 +43,8 @@ public class ConfigClient { bootstrap = new Bootstrap(); bootstrap.group(eventLoopGroup) .option(ChannelOption.SO_KEEPALIVE, true) + // 设置接收缓冲区大小为10MB + .option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(1024 * 1024 * 10)) .channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override diff --git a/kenaito-config-core/src/main/java/cn/odboy/config/netty/ConfigClientHandler.java b/kenaito-config-core/src/main/java/cn/odboy/config/netty/ConfigClientHandler.java index 6ca3204..720d0e6 100644 --- a/kenaito-config-core/src/main/java/cn/odboy/config/netty/ConfigClientHandler.java +++ b/kenaito-config-core/src/main/java/cn/odboy/config/netty/ConfigClientHandler.java @@ -1,13 +1,22 @@ package cn.odboy.config.netty; -import cn.odboy.config.constant.ConfigClientMsgType; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.odboy.config.constant.TransferMessageType; import cn.odboy.config.context.ClientConfigLoader; import cn.odboy.config.model.SmallMessage; -import cn.odboy.config.util.ProtostuffUtil; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import cn.odboy.config.model.msgtype.ConfigFileInfo; +import cn.odboy.config.util.MessageUtil; +import com.alibaba.fastjson.JSON; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import org.yaml.snakeyaml.Yaml; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; public class ConfigClientHandler extends ChannelInboundHandlerAdapter { private final ConfigClient configClient; @@ -30,10 +39,9 @@ public class ConfigClientHandler extends ChannelInboundHandlerAdapter { public void channelActive(ChannelHandlerContext ctx) throws Exception { System.err.println("ConfigClientHandler -> 当Channel处于活动状态(已经连接到它的远程节点)时被调用, 注册客户端"); SmallMessage smallMessage = new SmallMessage(); - smallMessage.setType(ConfigClientMsgType.REGISTER); - smallMessage.setResp(SmallMessage.Response.ok(ClientConfigLoader.clientProp)); - ByteBuf buf = Unpooled.copiedBuffer(ProtostuffUtil.serializer(smallMessage)); - ctx.writeAndFlush(buf); + smallMessage.setType(TransferMessageType.REGISTER); + smallMessage.setResp(SmallMessage.Response.ok(ClientConfigLoader.clientInfo)); + ctx.writeAndFlush(MessageUtil.toByteBuf(smallMessage)); } @Override @@ -44,14 +52,60 @@ public class ConfigClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - System.err.println("ConfigClientHandler -> 当从Channel读取数据时被调用, 收到服务器消息"); - ByteBuf buf = (ByteBuf) msg; - byte[] bytes = new byte[buf.readableBytes()]; - buf.readBytes(bytes); - System.err.println("ConfigClientHandler -> 从服务端读取到Object:" + ProtostuffUtil.deserializer(bytes, SmallMessage.class)); - SmallMessage smallMessage = ProtostuffUtil.deserializer(bytes, SmallMessage.class); - if (smallMessage.getType() == ConfigClientMsgType.REGISTER) { - +// System.err.println("ConfigClientHandler -> 当从Channel读取数据时被调用, 收到服务器消息"); + System.err.println("ConfigClientHandler -> 从服务端读取到Object"); + SmallMessage smallMessage = MessageUtil.getMessage(msg); + SmallMessage.Response resp = smallMessage.getResp(); + switch (smallMessage.getType()) { + case REGISTER: + if (!resp.getSuccess()) { + System.err.println("ConfigClientHandler -> 注册失败, " + resp.getErrorMsg()); + } else { + System.err.println("ConfigClientHandler -> 注册成功, 给服务端发信号, 表明准备好可以拉取配置了"); + // 准备拉取配置,给服务端发信号 + SmallMessage pullConfigMessage = new SmallMessage(); + pullConfigMessage.setType(TransferMessageType.PULL_CONFIG); + pullConfigMessage.setResp(SmallMessage.Response.ok(null, "准备好拉取配置了")); + ctx.writeAndFlush(MessageUtil.toByteBuf(pullConfigMessage)); + } + break; + case PUSH_CONFIG: + if (!resp.getSuccess()) { + throw new RuntimeException(resp.getErrorMsg()); + } + List configFileInfos = (List) resp.getData(); + System.err.println("ConfigClientHandler -> 收到来自服务端推送的配置信息"); + Map configKv = new HashMap<>(1); + try { + for (ConfigFileInfo configFileInfo : configFileInfos) { + String fileName = configFileInfo.getFileName(); + String suffix = FileUtil.getSuffix(fileName); + boolean isYml = "yml".equals(suffix) || "yaml".equals(suffix); + if (isYml) { + Yaml yaml = new Yaml(); + configKv.putAll(yaml.load(configFileInfo.getFileContent())); + } else { + Properties properties = new Properties(); + properties.load(StrUtil.getReader(configFileInfo.getFileContent())); + for (Map.Entry kv : properties.entrySet()) { + String key = (String) kv.getKey(); + configKv.put(key, kv.getValue()); + } + } + } + System.err.println("ConfigClientHandler -> 配置文件转map成功"); + ClientConfigLoader.lastConfigs = configKv; + ClientConfigLoader.isConfigLoaded = true; + synchronized (ClientConfigLoader.clientInfo) { + // 通知所有等待的线程 + ClientConfigLoader.clientInfo.notifyAll(); + } + } catch (IOException e) { + System.err.println("ConfigClientHandler -> 配置文件转map失败"); + } + break; + default: + break; } } } diff --git a/kenaito-config-service/src/main/java/cn/odboy/GenCode.java b/kenaito-config-service/src/main/java/cn/odboy/GenCode.java index df3e230..eb3e58d 100644 --- a/kenaito-config-service/src/main/java/cn/odboy/GenCode.java +++ b/kenaito-config-service/src/main/java/cn/odboy/GenCode.java @@ -13,34 +13,16 @@ import java.util.List; public class GenCode { public static void main(String[] args) { GenCmdHelper generator = new GenCmdHelper(); - generator.setDatabaseUrl(String.format("jdbc:mysql://%s:%s/%s", "kenaito-mysql.odboy.local", 13306, "kenaito_devops")); + generator.setDatabaseUrl(String.format("jdbc:mysql://%s:%s/%s", "kenaito-mysql.odboy.local", 13306, "kenaito_config")); generator.setDatabaseUsername("root"); generator.setDatabasePassword("root"); genCareer(generator); } private static void genCareer(GenCmdHelper generator) { - generator.gen("devops_", List.of( - // 应用 -// "devops_app", -// "devops_app_user", -// "devops_product_line", - // 应用迭代 -// "devops_app_iteration", -// "devops_app_iteration_change", - // 容器 -// "devops_containerd_cluster_config", -// "devops_containerd_cluster_node", -// "devops_ops_config", -// "devops_containerd_spec_config", - // 网络 -// "devops_network_service", -// "devops_network_ingress", - // 流水线 - "devops_pipeline_template_type", - "devops_pipeline_template_language", - "devops_pipeline_template_language_config", - "devops_pipeline_template_app" + generator.gen("", List.of( + "config_file", + "config_version" )); } } diff --git a/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigApp.java b/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigApp.java new file mode 100644 index 0000000..bc3d251 --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigApp.java @@ -0,0 +1,36 @@ +package cn.odboy.domain; + +import cn.odboy.base.MyObject; +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 lombok.Getter; +import lombok.Setter; + +/** + *

+ * 配置应用 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +@Getter +@Setter +@TableName("config_app") +public class ConfigApp extends MyObject { + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + /** + * 应用名称 + */ + @TableField("app_name") + private String appName; + /** + * 应用说明 + */ + @TableField("description") + private String description; +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigFile.java b/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigFile.java new file mode 100644 index 0000000..f0824dd --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigFile.java @@ -0,0 +1,45 @@ +package cn.odboy.domain; + +import cn.odboy.base.MyObject; +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 lombok.Data; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 配置文件 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +@Getter +@Setter +@TableName("config_file") +public class ConfigFile extends MyObject { + + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @TableField("app_name") + private String appName; + + @TableField("env") + private String env; + + /** + * 例如: application-daily.properties + */ + @TableField("file_name") + private String fileName; + + /** + * 当前配置内容版本 + */ + @TableField("version") + private Long version; +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigVersion.java b/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigVersion.java new file mode 100644 index 0000000..bd4df12 --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/domain/ConfigVersion.java @@ -0,0 +1,28 @@ +package cn.odboy.domain; + +import cn.odboy.base.MyObject; +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 lombok.Getter; +import lombok.Setter; + +/** + *

+ * 配置内容版本 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +@Getter +@Setter +@TableName("config_version") +public class ConfigVersion extends MyObject { + @TableField("file_id") + private Long fileId; + + @TableField("file_content") + private byte[] fileContent; +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigClientManage.java b/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigClientManage.java index ca0df0a..caa9b73 100644 --- a/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigClientManage.java +++ b/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigClientManage.java @@ -1,5 +1,6 @@ package cn.odboy.infra.netty; +import cn.odboy.infra.exception.BadRequestException; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelId; @@ -14,32 +15,44 @@ import java.util.concurrent.ConcurrentMap; */ public class ConfigClientManage { /** - * 所有的客户端连接: {env}-{dataId} to ctx + * 所有的客户端连接: {env}_{dataId} to ctx */ - private static final ConcurrentMap clientMap = new ConcurrentHashMap<>(); + private static final ConcurrentMap CLIENT = new ConcurrentHashMap<>(); /** - * 所有的客户端Id: channelId to {env}-{dataId} + * 所有的客户端Id: channelId to {env}_{dataId} */ - private static final ConcurrentMap channelMap = new ConcurrentHashMap<>(); + private static final ConcurrentMap CHANNEL = new ConcurrentHashMap<>(); public static void register(String env, String dataId, ChannelHandlerContext ctx) { String envClientKey = String.format("%s_%s", env, dataId); - clientMap.put(envClientKey, ctx); - channelMap.put(ctx.channel().id(), envClientKey); + CLIENT.put(envClientKey, ctx); + CHANNEL.put(ctx.channel().id(), envClientKey); System.err.println("ConfigClientManage -> 客户端注册成功"); System.err.println("ConfigClientManage -> ctx.channel.id=" + ctx.channel().id()); } public static void unregister(ChannelId channelId) { - String envClientKey = channelMap.getOrDefault(channelId, null); + String envClientKey = CHANNEL.getOrDefault(channelId, null); if (envClientKey != null) { - channelMap.remove(channelId); - ChannelHandlerContext ctx = clientMap.getOrDefault(envClientKey, null); + CHANNEL.remove(channelId); + ChannelHandlerContext ctx = CLIENT.getOrDefault(envClientKey, null); if (ctx != null) { - clientMap.remove(envClientKey); + CLIENT.remove(envClientKey); System.err.println("ConfigClientManage -> 客户端注销成功"); System.err.println("ConfigClientManage -> ctx.channel.id=" + channelId); } } } + + public static String[] getEnvDataId(ChannelId channelId) { + String envDataId = CHANNEL.getOrDefault(channelId, null); + if (envDataId == null) { + throw new BadRequestException("获取配置数据ID失败"); + } + String[] s = envDataId.split("_"); + if (s.length != 2) { + throw new BadRequestException("获取配置数据ID失败"); + } + return s; + } } diff --git a/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigNettyServer.java b/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigNettyServer.java index d67be77..ac22538 100644 --- a/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigNettyServer.java +++ b/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigNettyServer.java @@ -1,6 +1,7 @@ package cn.odboy.infra.netty; import cn.hutool.core.thread.ThreadUtil; +import cn.odboy.service.ConfigFileService; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; @@ -11,6 +12,7 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -19,6 +21,8 @@ import org.springframework.stereotype.Component; public class ConfigNettyServer implements InitializingBean { @Value("${kenaito.config-center.port}") private Integer configCenterPort; + @Autowired + private ConfigFileService configFileService; @Override public void afterPropertiesSet() throws Exception { @@ -43,7 +47,7 @@ public class ConfigNettyServer implements InitializingBean { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); - pipeline.addLast(new ConfigServerHandler()); + pipeline.addLast(new ConfigServerHandler(configFileService)); } }); log.info("Netty Server Start..."); diff --git a/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigServerHandler.java b/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigServerHandler.java index a397c4f..68b784f 100644 --- a/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigServerHandler.java +++ b/kenaito-config-service/src/main/java/cn/odboy/infra/netty/ConfigServerHandler.java @@ -2,42 +2,69 @@ package cn.odboy.infra.netty; import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.util.StrUtil; -import cn.odboy.config.constant.ConfigClientMsgType; +import cn.odboy.config.constant.TransferMessageType; import cn.odboy.config.model.SmallMessage; -import cn.odboy.config.model.msgtype.ClientProp; -import cn.odboy.config.util.ProtostuffUtil; -import io.netty.buffer.ByteBuf; +import cn.odboy.config.model.msgtype.ClientInfo; +import cn.odboy.config.model.msgtype.ConfigFileInfo; +import cn.odboy.config.util.MessageUtil; +import cn.odboy.service.ConfigFileService; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; +import java.util.List; + public class ConfigServerHandler extends ChannelInboundHandlerAdapter { + private final ConfigFileService configFileService; + + public ConfigServerHandler(ConfigFileService configFileService) { + this.configFileService = configFileService; + } + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - System.err.println("ServerHandler -> 当从Channel读取数据时被调用"); - ByteBuf buf = (ByteBuf) msg; - byte[] bytes = new byte[buf.readableBytes()]; - buf.readBytes(bytes); - System.err.println("ServerHandler -> 从客户端读取到Object:" + ProtostuffUtil.deserializer(bytes, SmallMessage.class)); - SmallMessage smallMessage = ProtostuffUtil.deserializer(bytes, SmallMessage.class); - if (ConfigClientMsgType.REGISTER == smallMessage.getType()) { - SmallMessage.Response resp = smallMessage.getResp(); - if (!resp.getSuccess() || resp.getData() == null) { - ctx.channel().writeAndFlush(new SmallMessage(ConfigClientMsgType.REGISTER, SmallMessage.Response.bad("解析客户端属性失败"))); - return; - } - ClientProp clientProp = (ClientProp) resp.getData(); - if (StrUtil.isBlank(clientProp.getEnv())) { - ctx.channel().writeAndFlush(new SmallMessage(ConfigClientMsgType.REGISTER, SmallMessage.Response.bad("解析客户端属性失败"))); - return; - } - if (StrUtil.isBlank(clientProp.getDataId())) { - ctx.channel().writeAndFlush(new SmallMessage(ConfigClientMsgType.REGISTER, SmallMessage.Response.bad("解析客户端属性失败"))); - return; - } - ctx.channel().writeAndFlush(new SmallMessage(ConfigClientMsgType.REGISTER, SmallMessage.Response.ok(null))); - ConfigClientManage.register(clientProp.getEnv(), clientProp.getDataId(), ctx); - } else if (ConfigClientMsgType.PULL_CONFIG == smallMessage.getType()) { - +// System.err.println("ServerHandler -> 当从Channel读取数据时被调用"); + SmallMessage smallMessage = MessageUtil.getMessage(msg); + System.err.println("ServerHandler -> 从客户端读取到Object:" + smallMessage); + SmallMessage.Response resp = smallMessage.getResp(); + switch (smallMessage.getType()) { + case REGISTER: + if (!resp.getSuccess() || resp.getData() == null) { + ctx.writeAndFlush(MessageUtil.toByteBuf(new SmallMessage(TransferMessageType.REGISTER, SmallMessage.Response.bad("解析客户端属性失败")))); + return; + } + ClientInfo clientInfo = (ClientInfo) resp.getData(); + if (StrUtil.isBlank(clientInfo.getEnv())) { + ctx.writeAndFlush(MessageUtil.toByteBuf(new SmallMessage(TransferMessageType.REGISTER, SmallMessage.Response.bad("解析客户端属性失败")))); + return; + } + if (StrUtil.isBlank(clientInfo.getDataId())) { + ctx.writeAndFlush(MessageUtil.toByteBuf(new SmallMessage(TransferMessageType.REGISTER, SmallMessage.Response.bad("解析客户端属性失败")))); + return; + } + ConfigClientManage.register(clientInfo.getEnv(), clientInfo.getDataId(), ctx); + ctx.writeAndFlush(MessageUtil.toByteBuf(new SmallMessage(TransferMessageType.REGISTER, SmallMessage.Response.ok(null)))); + break; + case PULL_CONFIG: + if (!resp.getSuccess()) { + System.err.println("ServerHandler -> 客户端说它没有准备好拉取配置"); + return; + } + System.err.println("ServerHandler -> 客户端说它准备好拉取配置了"); + String[] envDataId = ConfigClientManage.getEnvDataId(ctx.channel().id()); + String env = envDataId[0]; + String dataId = envDataId[1]; + List fileList = configFileService.getFileList(env, dataId); + if (fileList.isEmpty()) { + ctx.writeAndFlush(MessageUtil.toByteBuf(new SmallMessage(TransferMessageType.PUSH_CONFIG, SmallMessage.Response.bad( + String.format("应用 %s 没有环境编码为 %s 的配置", dataId, env) + )))); + } else { + System.err.println("ServerHandler -> 推送配置到客户端"); + ctx.writeAndFlush(MessageUtil.toByteBuf(new SmallMessage(TransferMessageType.PUSH_CONFIG, SmallMessage.Response.ok(fileList)))); + } + break; + default: + break; } } diff --git a/kenaito-config-service/src/main/java/cn/odboy/mapper/ConfigAppMapper.java b/kenaito-config-service/src/main/java/cn/odboy/mapper/ConfigAppMapper.java new file mode 100644 index 0000000..1f16208 --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/mapper/ConfigAppMapper.java @@ -0,0 +1,19 @@ +package cn.odboy.mapper; + +import cn.odboy.domain.ConfigApp; +import cn.odboy.domain.ConfigFile; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + *

+ * 配置应用 Mapper 接口 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +@Mapper +public interface ConfigAppMapper extends BaseMapper { + +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/mapper/ConfigFileMapper.java b/kenaito-config-service/src/main/java/cn/odboy/mapper/ConfigFileMapper.java new file mode 100644 index 0000000..849f34e --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/mapper/ConfigFileMapper.java @@ -0,0 +1,23 @@ +package cn.odboy.mapper; + +import cn.odboy.config.model.msgtype.ConfigFileInfo; +import cn.odboy.domain.ConfigFile; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + *

+ * 配置文件 Mapper 接口 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +@Mapper +public interface ConfigFileMapper extends BaseMapper { + + List selectByEnvAndAppName(@Param("env") String env, @Param("dataId") String dataId); +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/rest/ConfigFileController.java b/kenaito-config-service/src/main/java/cn/odboy/rest/ConfigFileController.java new file mode 100644 index 0000000..f419103 --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/rest/ConfigFileController.java @@ -0,0 +1,18 @@ +package cn.odboy.rest; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

+ * 配置文件 前端控制器 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +@RestController +@RequestMapping("/api/configFile") +public class ConfigFileController { + +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/service/ConfigAppService.java b/kenaito-config-service/src/main/java/cn/odboy/service/ConfigAppService.java new file mode 100644 index 0000000..4a19d5d --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/service/ConfigAppService.java @@ -0,0 +1,17 @@ +package cn.odboy.service; + +import cn.odboy.domain.ConfigApp; +import cn.odboy.domain.ConfigFile; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 配置应用 服务类 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +public interface ConfigAppService extends IService { + +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/service/ConfigFileService.java b/kenaito-config-service/src/main/java/cn/odboy/service/ConfigFileService.java new file mode 100644 index 0000000..6654ce8 --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/service/ConfigFileService.java @@ -0,0 +1,22 @@ +package cn.odboy.service; + +import cn.odboy.config.model.msgtype.ConfigFileInfo; + +import java.util.List; + +/** + *

+ * 配置文件 服务类 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +public interface ConfigFileService { + /** + * @param env 环境编码 + * @param dataId 数据ID,这里是应用名称 + * @return / + */ + List getFileList(String env, String dataId); +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/service/impl/ConfigAppServiceImpl.java b/kenaito-config-service/src/main/java/cn/odboy/service/impl/ConfigAppServiceImpl.java new file mode 100644 index 0000000..29480d8 --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/service/impl/ConfigAppServiceImpl.java @@ -0,0 +1,23 @@ +package cn.odboy.service.impl; + +import cn.odboy.domain.ConfigApp; +import cn.odboy.domain.ConfigFile; +import cn.odboy.mapper.ConfigAppMapper; +import cn.odboy.mapper.ConfigFileMapper; +import cn.odboy.service.ConfigAppService; +import cn.odboy.service.ConfigFileService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +/** + *

+ * 配置应用 服务实现类 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +@Service +public class ConfigAppServiceImpl extends ServiceImpl implements ConfigAppService { + +} diff --git a/kenaito-config-service/src/main/java/cn/odboy/service/impl/ConfigFileServiceImpl.java b/kenaito-config-service/src/main/java/cn/odboy/service/impl/ConfigFileServiceImpl.java new file mode 100644 index 0000000..35f901b --- /dev/null +++ b/kenaito-config-service/src/main/java/cn/odboy/service/impl/ConfigFileServiceImpl.java @@ -0,0 +1,28 @@ +package cn.odboy.service.impl; + +import cn.odboy.config.model.msgtype.ConfigFileInfo; +import cn.odboy.mapper.ConfigFileMapper; +import cn.odboy.service.ConfigFileService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * 配置文件 服务实现类 + *

+ * + * @author odboy + * @since 2024-12-05 + */ +@Service +@RequiredArgsConstructor +public class ConfigFileServiceImpl implements ConfigFileService { + private final ConfigFileMapper configFileMapper; + + @Override + public List getFileList(String env, String dataId) { + return configFileMapper.selectByEnvAndAppName(env, dataId); + } +} diff --git a/kenaito-config-service/src/main/resources/mapper/ConfigFileMapper.xml b/kenaito-config-service/src/main/resources/mapper/ConfigFileMapper.xml new file mode 100644 index 0000000..c1abedc --- /dev/null +++ b/kenaito-config-service/src/main/resources/mapper/ConfigFileMapper.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/kenaito_config.sql b/kenaito_config.sql index 8da7408..c800607 100644 --- a/kenaito_config.sql +++ b/kenaito_config.sql @@ -11,12 +11,28 @@ Target Server Version : 80025 File Encoding : 65001 - Date: 05/12/2024 18:31:07 + Date: 05/12/2024 19:35:22 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; +-- ---------------------------- +-- Table structure for config_app +-- ---------------------------- +DROP TABLE IF EXISTS `config_app`; +CREATE TABLE `config_app` ( + `id` bigint(0) NOT NULL AUTO_INCREMENT, + `app_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '应用名称', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '应用描述', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '配置应用' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of config_app +-- ---------------------------- +INSERT INTO `config_app` VALUES (1, 'kenaito-config-demo', '测试应用'); + -- ---------------------------- -- Table structure for config_file -- ---------------------------- @@ -26,14 +42,29 @@ CREATE TABLE `config_file` ( `app_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `env` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `filename` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '例如: application-daily.properties', - `content` longblob NOT NULL, - `version` bigint(0) NOT NULL DEFAULT 1, + `version` bigint(0) NOT NULL DEFAULT 1 COMMENT '当前配置内容版本', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '配置文件' ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of config_file -- ---------------------------- +INSERT INTO `config_file` VALUES (1, 'kenaito-config-demo', 'daily', 'application-daily.yml', 1); + +-- ---------------------------- +-- Table structure for config_version +-- ---------------------------- +DROP TABLE IF EXISTS `config_version`; +CREATE TABLE `config_version` ( + `file_id` bigint(0) NOT NULL, + `file_content` longblob NULL, + PRIMARY KEY (`file_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '配置内容版本' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of config_version +-- ---------------------------- +INSERT INTO `config_version` VALUES (1, 0x7365727665723A0D0A2020706F72743A2032383030300D0A202068747470323A0D0A202020202320CCE1B8DFCDF8C2E7B4ABCAE4D0A7C2CA2C20C4ACC8CFCEAA66616C73650D0A20202020656E61626C65643A20747275650D0A2020756E646572746F773A0D0A20202020746872656164733A0D0A2020202020202320B9A4D7F7CFDFB3CCCAFDA3ACC4ACC8CFC9E8D6C3CEAA696F2D74687265616473202A2038A1A3C8E7B9FBC4E3B5C4D3A6D3C3B3CCD0F2D3D0BADCB6E0CDACB2BDD7E8C8FBB2D9D7F7A3ACBFC9D2D4CACAB5B1D4F6BCD3D5E2B8F6D6B50D0A202020202020776F726B65723A2031360D0A2020202020202320C9E8D6C3494FCFDFB3CCCAFD2C20CBFCD6F7D2AAD6B4D0D0B7C7D7E8C8FBB5C4C8CECEF12CCBFCC3C7BBE1B8BAD4F0B6E0B8F6C1ACBDD32C20C4ACC8CFC9E8D6C3C3BFB8F6435055BACBD0C4D2BBB8F6CFDFB3CC0D0A2020202020202320D7E8C8FBC8CECEF1CFDFB3CCB3D82C20B5B1D6B4D0D0C0E0CBC6736572766C6574C7EBC7F3D7E8C8FBB2D9D7F72C20756E646572746F77BBE1B4D3D5E2B8F6CFDFB3CCB3D8D6D0C8A1B5C3CFDFB3CC2CCBFCB5C4D6B5C9E8D6C3C8A1BEF6D3DACFB5CDB3B5C4B8BAD4D80D0A202020202020696F3A20320D0A202020202320C9E8D6C3CEAA74727565D2D4CAB9D3C3D6B1BDD3C4DAB4E6A3A8B6D1CDE2C4DAB4E6A3A9C0B4B4E6B4A2BBBAB3E5C7F8A1A3D5E2BFC9D2D4BCF5C9D9C0ACBBF8BBD8CAD5B5C4BFAACFFAA1A30D0A202020206469726563742D627566666572733A20747275650D0A202020202320D2D4CFC2B5C4C5E4D6C3BBE1D3B0CFEC6275666665722CD5E2D0A9627566666572BBE1D3C3D3DAB7FECEF1C6F7C1ACBDD3B5C4494FB2D9D7F72CD3D0B5E3C0E0CBC66E65747479B5C4B3D8BBAFC4DAB4E6B9DCC0ED0D0A202020202320C3BFBFE9627566666572B5C4BFD5BCE4B4F3D0A12CD4BDD0A1B5C4BFD5BCE4B1BBC0FBD3C3D4BDB3E4B7D60D0A202020206275666665722D73697A653A20313032340D0A202020206163636573736C6F673A0D0A2020202020202320B9D8B1D5C8D5D6BE0D0A202020202020656E61626C65643A2066616C73650D0A2020636F6D7072657373696F6E3A0D0A202020202320CAC7B7F1BFAAC6F4D1B9CBF50D0A20202020656E61626C65643A20747275650D0A202020206D696D652D74797065733A20746578742F68746D6C2C746578742F786D6C2C746578742F706C61696E2C746578742F6373732C746578742F6A6176617363726970742C6170706C69636174696F6E2F6A6176617363726970742C6170706C69636174696F6E2F6A736F6E0D0A20202320C6F4D3C3D3C5D1C5CDA3BBFAA3ACB2A2D7F1CAD8737072696E672E6C6966656379636C652E74696D656F75742D7065722D73687574646F776E2D7068617365CAF4D0D4D6D0B8F8B3F6B5C4B3ACCAB10D0A20202320475241434546554C2028D3C5D1C5293A20B5B1D3A6D3C3B3CCD0F2D2D422475241434546554C22C4A3CABDB9D8B1D5CAB1A3ACCBFCB2BBBBE1BDD3CADCD0C2B5C4C7EBC7F3C7D2BBE1B3A2CAD4CDEAB3C9CBF9D3D0D5FDD4DABDF8D0D0B5C4C7EBC7F3BACDB4A6C0EDA3ACC8BBBAF3B2C5BBE1D6D5D6B9A1A3D5E2D6D6B7BDCABDCACAD3C3D3DAC4C7D0A9BFC9C4DCD0E8D2AAD2BBD0A9CAB1BCE4C0B4C7E5C0EDD7CAD4B4BBF2CDEAB3C9D5FDD4DABDF8D0D0B5C4C8CECEF1B5C4B3A1BEB0A1A30D0A20202320494D4D45444941544528C1A2BCB4293A20B5B1D3A6D3C3B3CCD0F2D2D422494D4D45444941544522C4A3CABDB9D8B1D5CAB1A3ACCBFCBBE1C1A2BCB4D6D5D6B9A3ACB6F8B2BBB9DCB5B1C7B0CAC7B7F1D3D0C8CEBACEBBEEB6AFC8CECEF1BBF2C7EBC7F3A1A3D5E2D6D6B7BDCABDCACAD3C3D3DAC4C7D0A9BFC9D2D4C1A2BCB4CDA3D6B9B6F8B2BBBBE1D4ECB3C9D1CFD6D8CECACCE2B5C4C7E9BFF6A1A30D0A202073687574646F776E3A20677261636566756C0D0A6D7962617469732D706C75733A0D0A2020636F6E66696775726174696F6E3A0D0A202020202320BFAAC6F4204D79626174697320B6FEBCB6BBBAB4E6A3ACC4ACC8CFCEAA20747275650D0A2020202063616368652D656E61626C65643A2066616C73650D0A202020202320C9E8D6C3B1BEB5D8BBBAB4E6D7F7D3C3D3F22C204D79626174697320D2BBBCB6BBBAB4E62C20C4ACC8CFCEAA2053455353494F4E0D0A202020202320CDACD2BBB8F62073657373696F6E20CFE0CDACB2E9D1AFD3EFBEE4B2BBBBE1D4D9B4CEB2E9D1AFCAFDBEDDBFE20D0A202020202320CEA2B7FECEF1D6D02C20BDA8D2E9C9E8D6C3CEAA53544154454D454E542C20BCB4B9D8B1D5D2BBBCB6BBBAB4E60D0A202020206C6F63616C2D63616368652D73636F70653A2053544154454D454E540D0A202020202320CAC7B7F1BFAAC6F4D7D4B6AFCDD5B7E5C3FCC3FBB9E6D4F2A3A863616D656C2063617365A3A9D3B3C9E42C20BCB4B4D3BEADB5E4CAFDBEDDBFE2C1D0C3FB20415F434F4C554D4EA3A8CFC2BBAECFDFC3FCC3FBA3A920B5BDBEADB5E4204A61766120CAF4D0D4C3FB2061436F6C756D6EA3A8CDD5B7E5C3FCC3FBA3A920B5C4C0E0CBC6D3B3C9E40D0A202020206D61702D756E64657273636F72652D746F2D63616D656C2D636173653A20747275650D0A20202020232053716CC8D5D6BE0D0A202020206C6F672D696D706C3A206F72672E6170616368652E6962617469732E6C6F6767696E672E7374646F75742E5374644F7574496D706C0D0A20202320C6F4B6AFCAB1CAC7B7F1BCECB2E9204D79426174697320584D4C20CEC4BCFEB5C4B4E6D4DA2C20C4ACC8CFB2BBBCECB2E90D0A2020636865636B2D636F6E6669672D6C6F636174696F6E3A20747275650D0A202023204D794261746973204D617070657220CBF9B6D4D3A6B5C420584D4C20CEC4BCFECEBBD6C30D0A202023204D6176656E20B6E0C4A3BFE9CFEEC4BFB5C4C9A8C3E8C2B7BEB6D0E8D2D420636C617373706174682A3A20BFAACDB720A3A8BCB4BCD3D4D8B6E0B8F6206A617220B0FCCFC2B5C420584D4C20CEC4BCFEA3A90D0A20206D61707065722D6C6F636174696F6E733A20636C617373706174682A3A2F6D61707065722F2A2A2F2A2E786D6C0D0A202023204D7942617469732D506C757320C8ABBED6B2DFC2D4D6D0B5C420444220B2DFC2D4C5E4D6C30D0A2020676C6F62616C2D636F6E6669673A0D0A2020202064622D636F6E6669673A0D0A2020202020202320C2DFBCADD2D1C9BEB3FDD6B528C2DFBCADC9BEB3FDCFC2D3D0D0A7290D0A2020202020206C6F6769632D64656C6574652D76616C75653A20300D0A2020202020202320C2DFBCADCEB4C9BEB3FDD6B528C2DFBCADC9BEB3FDCFC2D3D0D0A7290D0A2020202020206C6F6769632D6E6F742D64656C6574652D76616C75653A20310D0A2020202020202320C8ABBED6C4ACC8CFD6F7BCFCC0E0D0CD2C20D5E2C0EFCEAAD7D4D4F6D6F7BCFC0D0A20202020202069642D747970653A206175746F0D0A2020202020202320B1EDC3FBCAC7B7F1CAB9D3C3CDD5B7E5D7AACFC2BBAECFDFC3FCC3FB2CD6BBB6D4B1EDC3FBC9FAD0A70D0A2020202020207461626C652D756E6465726C696E653A20747275650D0A202020202320CAC7B7F1BFD8D6C6CCA8207072696E74206D7962617469732D706C757320B5C4204C4F474F0D0A2020202062616E6E65723A20747275650D0A737072696E673A0D0A2020667265656D61726B65723A0D0A202020202320CAC7B7F1BCECB2E9C4A3B0E5CEBBD6C3CAC7B7F1B4E6D4DA0D0A20202020636865636B2D74656D706C6174652D6C6F636174696F6E3A2066616C73650D0A202020202320C4A3B0E5B1E0C2EB0D0A20202020636861727365743A207574662D380D0A2020646174613A0D0A2020202072656469733A0D0A2020202020207265706F7369746F726965733A0D0A20202020202020202320CAC7B7F1C6F4D3C35265646973B4E6B4A228B9D8B1D5B7C0D6B9B3F6CFD6204D756C7469706C6520537072696E672044617461206D6F64756C657320666F756E642C20656E746572696E6720737472696374207265706F7369746F727920636F6E66696775726174696F6E206D6F6465290D0A2020202020202020656E61626C65643A2066616C73650D0A20206C6966656379636C653A0D0A202020202320B2C9D3C36A6176612E74696D652E4475726174696F6EB8F1CABDB5C4D6B52CC8E7B9FBD4DAD5E2B8F6CAB1BCE4C4DAA3ACD3C5D1C5CDA3BBFAC3BBD3D0CDA3B5F4D3A6D3C3A3ACB3ACB9FDD5E2B8F6CAB1BCE4BECDBBE1C7BFD6C6CDA3D6B9D3A6D3C30D0A2020202074696D656F75742D7065722D73687574646F776E2D70686173653A203330730D0A20202320D3CABCFEC9E8D6C30D0A20206D61696C3A0D0A20202020686F73743A20736D74702E71712E636F6D0D0A20202020706F72743A203436350D0A20202020757365726E616D653A207469616E6A756E406F64626F792E636E0D0A202020202320D3CACFE4CADAC8A8C2EB0D0A202020202320C8E7BACEBBF1C8A15151D3CACFE4CADAC8A8C2EB3F0D0A20202020232068747470733A2F2F736572766963652E6D61696C2E71712E636F6D2F6367692D62696E2F68656C703F737562747970653D3126266E6F3D31303031323536262669643D32380D0A2020202070617373776F72643A207878787878787878787878780D0A2020202064656661756C742D656E636F64696E673A205554462D380D0A2020202070726F706572746965733A0D0A2020202020206D61696C3A0D0A2020202020202020736D74703A0D0A2020202020202020202073736C3A0D0A20202020202020202020202074727573743A20736D74702E71712E636F6D0D0A20202020202020202020736F636B6574466163746F72793A0D0A202020202020202020202020636C6173733A206A617661782E6E65742E73736C2E53534C536F636B6574466163746F72790D0A202020202020202020202020706F72743A203436350D0A20202020202020202020617574683A20747275650D0A202020202020202020207374617274746C733A0D0A202020202020202020202020656E61626C653A20747275650D0A20202020202020202020202072657175697265643A20747275650D0A20202320C5E4D6C3CAFDBEDDD4B40D0A202064617461736F757263653A0D0A20202020232068747470733A2F2F7777772E636E626C6F67732E636F6D2F6875616E7368696C616E672F702F31373837383934382E68746D6C0D0A2020202064727569643A0D0A20202020202064622D747970653A20636F6D2E616C69626162612E64727569642E706F6F6C2E447275696444617461536F757263650D0A2020202020202320647269766572436C6173734E616D653A20636F6D2E6D7973716C2E636A2E6A6462632E44726976657220202023206D7973716C3820B5C4C1ACBDD3C7FDB6AF0D0A202020202020647269766572436C6173734E616D653A206E65742E73662E6C6F67346A6462632E73716C2E6A6462636170692E4472697665725370790D0A20202020202075726C3A206A6462633A6C6F67346A6462633A6D7973716C3A2F2F247B44425F484F53543A6B656E6169746F2D6D7973716C2E6F64626F792E6C6F63616C7D3A247B44425F504F52543A31333330367D2F247B44425F4E414D453A6B656E6169746F5F636F6E6669677D3F73657276657254696D657A6F6E653D417369612F5368616E6768616926636861726163746572456E636F64696E673D757466382675736553534C3D66616C736526616C6C6F775075626C69634B657952657472696576616C3D747275650D0A202020202020757365726E616D653A20247B44425F555345523A726F6F747D0D0A20202020202070617373776F72643A20247B44425F5057443A726F6F747D0D0A202020202020696E697469616C2D73697A653A203520202320B3F5CABCC1ACBDD3CAFD0D0A2020202020206D696E2D69646C653A203135202320D7EED0A1C1ACBDD3CAFD0D0A2020202020206D61782D6163746976653A20333020202320D7EEB4F3C1ACBDD3CAFD0D0A20202020202072656D6F76652D6162616E646F6E65642D74696D656F75743A2031383020202320B3ACCAB1CAB1BCE428D2D4C3EBCAFDCEAAB5A5CEBB290D0A2020202020206D61782D776169743A2033303030202320C5E4D6C3BCE4B8F4B6E0BEC3B2C5BDF8D0D0D2BBB4CEBCECB2E2A3ACBCECB2E2D0E8D2AAB9D8B1D5B5C4BFD5CFD0C1ACBDD3A3ACB5A5CEBBCAC7BAC1C3EB0D0A20202020202074696D652D6265747765656E2D6576696374696F6E2D72756E732D6D696C6C69733A203630303030202320C5E4D6C3D2BBB8F6C1ACBDD3D4DAB3D8D6D0D7EED0A1C9FAB4E6B5C4CAB1BCE4A3ACB5A5CEBBCAC7BAC1C3EB0D0A2020202020206D696E2D657669637461626C652D69646C652D74696D652D6D696C6C69733A20333030303030202320C1ACBDD3D4DAB3D8D6D0D7EED0A1C9FAB4E6B5C4CAB1BCE40D0A2020202020206D61782D657669637461626C652D69646C652D74696D652D6D696C6C69733A2039303030303020202320C1ACBDD3D4DAB3D8D6D0D7EEB4F3C9FAB4E6B5C4CAB1BCE40D0A202020202020746573742D7768696C652D69646C653A2074727565202320C8E7B9FBCEAA74727565A3ACC4ACC8CFCAC766616C7365A3ACD3A6D3C3CFF2C1ACBDD3B3D8C9EAC7EBC1ACBDD3CAB1A3ACC1ACBDD3B3D8BBE1C5D0B6CFD5E2CCF5C1ACBDD3CAC7B7F1CAC7BFC9D3C3B5C40D0A202020202020746573742D6F6E2D626F72726F773A2066616C7365202320C8E7B9FBCEAA74727565A3A8C4ACC8CF66616C7365A3A9A3ACB5B1D3A6D3C3CAB9D3C3CDEAC1ACBDD3A3ACC1ACBDD3B3D8BBD8CAD5C1ACBDD3B5C4CAB1BAF2BBE1C5D0B6CFB8C3C1ACBDD3CAC7B7F1BBB9BFC9D3C30D0A202020202020746573742D6F6E2D72657475726E3A2066616C7365202320CAC7B7F1BBBAB4E6707265706172656453746174656D656E74A3ACD2B2BECDCAC750534361636865A1A350534361636865B6D4D6A7B3D6D3CEB1EAB5C4CAFDBEDDBFE2D0D4C4DCCCE1C9FDBEDEB4F3A3ACB1C8C8E7CBB56F7261636C650D0A20202020202076616C69646174696F6E2D71756572793A2073656C65637420312023202320D3A6D3C3CFF2C1ACBDD3B3D8C9EAC7EBC1ACBDD3A3ACB2A2C7D2746573744F6E426F72726F77CEAA66616C7365CAB1A3ACC1ACBDD3B3D8BDABBBE1C5D0B6CFC1ACBDD3CAC7B7F1B4A6D3DABFD5CFD0D7B4CCACA3ACC8E7B9FBCAC7A3ACD4F2D1E9D6A4D5E2CCF5C1ACBDD3CAC7B7F1BFC9D3C30D0A2020202020202323232323232323232320C5E4D6C35765625374617446696C746572A3ACD3C3D3DAB2C9BCAF776562B9D8C1AABCE0BFD8B5C4CAFDBEDD20232323232323232323230D0A2020202020207765622D737461742D66696C7465723A0D0A2020202020202020656E61626C65643A2074727565202020202020202020202020202020202020202320C6F4B6AF205374617446696C7465720D0A202020202020202075726C2D7061747465726E3A202F2A20202020202020202020202020202020202320B9FDC2CBCBF9D3D075726C0D0A20202020202020206578636C7573696F6E733A20222A2E6A732C2A2E6769662C2A2E6A70672C2A2E706E672C2A2E6373732C2A2E69636F2C2F64727569642F2A22202320C5C5B3FDD2BBD0A9B2BBB1D8D2AAB5C475726C0D0A202020202020202073657373696F6E2D737461742D656E61626C653A2074727565202020202020202320BFAAC6F473657373696F6ECDB3BCC6B9A6C4DC0D0A202020202020202073657373696F6E2D737461742D6D61782D636F756E743A203130303020202020232073657373696F6EB5C4D7EEB4F3B8F6CAFD2CC4ACC8CF3130300D0A2020202020202323232323232323232320C5E4D6C35374617456696577536572766C6574A3A8BCE0BFD8D2B3C3E6A3A9A3ACD3C3D3DAD5B9CABE4472756964B5C4CDB3BCC6D0C5CFA220232323232323232323230D0A202020202020737461742D766965772D736572766C65743A0D0A2020202020202020656E61626C65643A2074727565202020202020202020202020202020202020202320C6F4D3C35374617456696577536572766C65740D0A202020202020202075726C2D7061747465726E3A202F64727569642F2A20202020202020202020202320B7C3CECAC4DAD6C3BCE0BFD8D2B3C3E6B5C4C2B7BEB6A3ACC4DAD6C3BCE0BFD8D2B3C3E6B5C4CAD7D2B3CAC72F64727569642F696E6465782E68746D6C0D0A202020202020202072657365742D656E61626C653A2066616C7365202020202020202020202020202320B2BBD4CAD0EDC7E5BFD5CDB3BCC6CAFDBEDD2CD6D8D0C2BCC6CBE30D0A2020202020202020616C6C6F773A203132372E302E302E31202020202020202020202020202020202320D4CAD0EDB7C3CECAB5C4B5D8D6B7A3ACC8E7B9FB616C6C6F77C3BBD3D0C5E4D6C3BBF2D5DFCEAABFD5A3ACD4F2D4CAD0EDCBF9D3D0B7C3CECA0D0A202020202020202064656E793A2020202020202020202020202020202020202020202020202020202320BEDCBEF8B7C3CECAB5C4B5D8D6B7A3AC64656E79D3C5CFC8D3DA616C6C6F77A3ACC8E7B9FBD4DA64656E79C1D0B1EDD6D0A3ACBECDCBE3D4DA616C6C6F77C1D0B1EDD6D0A3ACD2B2BBE1B1BBBEDCBEF80D0A2020202020202320C5E4D6C3BCE0BFD8CDB3BCC6C0B9BDD8B5C466696C7465720D0A20202020202066696C7465723A0D0A20202020202020202320BFAAC6F4447275696444617461736F75726365B5C4D7B4CCACBCE0BFD80D0A2020202020202020737461743A0D0A20202020202020202020656E61626C65643A20747275650D0A202020202020202020202320BFAAC6F4C2FD73716CBCE0BFD8A3ACB3ACB9FD327320BECDC8CFCEAACAC7C2FD73716CA3ACBCC7C2BCB5BDC8D5D6BED6D00D0A202020202020202020206C6F672D736C6F772D73716C3A20747275650D0A20202020202020202020736C6F772D73716C2D6D696C6C69733A20323030300D0A202020202020202020206D657267652D73716C3A20747275650D0A202020202020202077616C6C3A0D0A20202020202020202020636F6E6669673A0D0A2020202020202020202020206D756C74692D73746174656D656E742D616C6C6F773A20747275650D0A2020202020202020736C66346A3A0D0A20202020202020202020656E61626C65643A20747275650D0A2020202020202020202073746174656D656E742D6C6F672D6572726F722D656E61626C65643A20747275650D0A2020202020202020202073746174656D656E742D6372656174652D61667465722D6C6F672D656E61626C65643A2066616C73650D0A2020202020202020202073746174656D656E742D636C6F73652D61667465722D6C6F672D656E61626C65643A2066616C73650D0A20202020202020202020726573756C742D7365742D6F70656E2D61667465722D6C6F672D656E61626C65643A2066616C73650D0A20202020202020202020726573756C742D7365742D636C6F73652D61667465722D6C6F672D656E61626C65643A2066616C73650D0A2020202020202320537072696E6720BCE0BFD8A3ACC0FBD3C3616F7020B6D4D6B8B6A8BDD3BFDAB5C4D6B4D0D0CAB1BCE4A3AC6A646263CAFDBDF8D0D0BCC7C2BC0D0A202020202020232068747470733A2F2F626C6F672E6373646E2E6E65742F77656978696E5F33393635313335362F61727469636C652F64657461696C732F3132383136383239300D0A202020202020616F702D7061747465726E733A2022636E2E6F64626F792E6D61707065722E2A2C636E2E6F64626F792E6D6F64756C65732E2A2E6D61707065722E2A220D0A2020202020206B6565702D616C6976653A20747275650D0A2020202020206D61782D6F70656E2D70726570617265642D73746174656D656E74733A203230202320C1ACBDD3B3D8D6D0B5C46D696E49646C65CAFDC1BFD2D4C4DAB5C4C1ACBDD3A3ACBFD5CFD0CAB1BCE4B3ACB9FD6D696E457669637461626C6549646C6554696D654D696C6C6973A3ACD4F2BBE1D6B4D0D06B656570416C697665B2D9D7F70D0A202020202020706F6F6C2D70726570617265642D73746174656D656E74733A2074727565202320D2AAC6F4D3C350534361636865A3AC6D61782D6F70656E2D70726570617265642D73746174656D656E7473B1D8D0EBC5E4D6C3B4F3D3DA30A3ACB5B1B4F3D3DA30CAB1A3AC20706F6F6C507265706172656453746174656D656E7473D7D4B6AFB4A5B7A2D0DEB8C4CEAA747275650D0A202072656469733A0D0A2020202023CAFDBEDDBFE2CBF7D2FD0D0A2020202064617461626173653A20247B52454449535F44423A327D0D0A20202020686F73743A20247B52454449535F484F53543A6B656E6169746F2D72656469732E6F64626F792E6C6F63616C7D0D0A20202020706F72743A20247B52454449535F504F52543A31363337397D0D0A2020202070617373776F72643A20247B52454449535F5057443A3132333435367D0D0A2020202023C1ACBDD3B3ACCAB1CAB1BCE40D0A2020202074696D656F75743A20353030300D0A202020206C6574747563653A0D0A202020202020706F6F6C3A0D0A2020202020202020656E61626C65643A20747275650D0A20202020202020206D61782D6163746976653A2032300D0A20202020202020206D696E2D69646C653A20300D0A20202020202020206D61782D776169743A20353030306D730D0A20202020202020206D61782D69646C653A2031300D0A2320B5C7C2BCCFE0B9D8C5E4D6C30D0A6C6F67696E3A0D0A2020232020CAC7B7F1CFDED6C6B5A5D3C3BBA7B5C7C2BC0D0A202073696E676C652D6C6F67696E3A2066616C73650D0A202023205265646973D3C3BBA7B5C7C2BCBBBAB4E6C5E4D6C30D0A2020757365722D63616368653A0D0A202020202320B4E6BBEECAB1BCE42FC3EB0D0A2020202069646C652D74696D653A2032313630300D0A2020232020D1E9D6A4C2EB0D0A20206C6F67696E2D636F64653A0D0A20202020656E61626C65643A2066616C73650D0A20202020232020D1E9D6A4C2EBC0E0D0CDC5E4D6C320B2E9BFB4204C6F67696E50726F7065727469657320C0E00D0A20202020636F64652D747970653A2061726974686D657469630D0A20202020232020B5C7C2BCCDBCD0CED1E9D6A4C2EBD3D0D0A7CAB1BCE42FB7D6D6D30D0A2020202065787069726174696F6E3A20320D0A20202020232020D1E9D6A4C2EBB8DFB6C80D0A2020202077696474683A203131310D0A20202020232020D1E9D6A4C2EBBFEDB6C80D0A202020206865696768743A2033360D0A202020202320C4DAC8DDB3A4B6C80D0A202020206C656E6774683A20320D0A202020202320D7D6CCE5C3FBB3C6A3ACCEAABFD5D4F2CAB9D3C3C4ACC8CFD7D6CCE50D0A20202020666F6E742D6E616D653A0D0A202020202320D7D6CCE5B4F3D0A10D0A20202020666F6E742D73697A653A2032350D0A23206A77740D0A6A77743A0D0A20206865616465723A20417574686F72697A6174696F6E0D0A20202320C1EEC5C6C7B0D7BA0D0A2020746F6B656E2D73746172742D776974683A204265617265720D0A20202320B1D8D0EBCAB9D3C3D7EEC9D93838CEBBB5C4426173653634B6D4B8C3C1EEC5C6BDF8D0D0B1E0C2EB0D0A20206261736536342D7365637265743A205A6D51305A4749354E6A51304D445177593249344D6A4D78593259335A6D49334D6A64684E325A6D4D6A4E684F4456694F5467315A4745304E54426A4D474D344E4441354E7A59784D6A646A4F574D775957526D5A54426C5A6A6C684E4759335A54673459325533595445314F44566B5A445535593259334F4759775A5745314E7A557A4E575132596A466A5A4463304E474D785A5755324D6D51334D6A59314E7A4A6D4E5445304D7A493D0D0A20202320C1EEC5C6B9FDC6DACAB1BCE420B4CBB4A6B5A5CEBB2FBAC1C3EB20A3ACC4ACC8CF34D0A1CAB1A3ACBFC9D4DAB4CBCDF8D5BEC9FAB3C92068747470733A2F2F7777772E636F6E76657274776F726C642E636F6D2F7A682D68616E732F74696D652F6D696C6C697365636F6E64732E68746D6C0D0A2020746F6B656E2D76616C69646974792D696E2D7365636F6E64733A2031343430303030300D0A20202320D4DACFDFD3C3BBA76B65790D0A20206F6E6C696E652D6B65793A20226F6E6C696E652D746F6B656E3A220D0A20202320D1E9D6A4C2EB0D0A2020636F64652D6B65793A2022636170746368612D636F64653A220D0A20202320746F6B656E20D0F8C6DABCECB2E9CAB1BCE4B7B6CEA7A3A8C4ACC8CF3330B7D6D6D3A3ACB5A5CEBBBAC1C3EBA3A9A3ACD4DA746F6B656EBCB4BDABB9FDC6DAB5C4D2BBB6CECAB1BCE4C4DAD3C3BBA7B2D9D7F7C1CBA3ACD4F2B8F8D3C3BBA7B5C4746F6B656ED0F8C6DA0D0A20206465746563743A20313830303030300D0A20202320D0F8C6DACAB1BCE4B7B6CEA7A3ACC4ACC8CF31D0A1CAB1A3ACB5A5CEBBBAC1C3EB0D0A202072656E65773A20333630303030300D0A2320CEC4BCFEB4E6B4A2C2B7BEB60D0A66696C653A0D0A20206D61633A0D0A20202020706174683A207E2F66696C652F0D0A202020206176617461723A207E2F6176617461722F0D0A20206C696E75783A0D0A20202020706174683A202F686F6D652F6B656E6169746F2F66696C652F0D0A202020206176617461723A202F686F6D652F6B656E6169746F2F6176617461722F0D0A202077696E646F77733A0D0A20202020706174683A20433A5C6B656E6169746F5C66696C655C0D0A202020206176617461723A20433A5C6B656E6169746F5C6176617461725C0D0A20202320CEC4BCFEB4F3D0A1202F4D0D0A20206D617853697A653A203130300D0A20206176617461724D617853697A653A20350D0A6170703A0D0A20202320C3DCC2EBBCD3C3DCB4ABCAE42C20C7B0B6CBB9ABD4BFBCD3C3DC2C20BAF3B6CBCBBDD4BFBDE2C3DC0D0A20202320C3DCD4BFB6D4C9FAB3C920687474703A2F2F7765622E63686163756F2E6E65742F6E65747273616B6579706169720D0A20202320B9ABD4BFD0E8D2AABFBDB1B4D2BBB7DDB5BDC7B0B6CBC2B7BEB6CFC23A206B656E6169746F2D66726F6E742F7372632F7574696C2F727361456E63727970742E6A730D0A2020707269766174652D6B65793A204D494942557749424144414E42676B71686B6947397730424151454641415343415430776767453541674541416B454130766676795464474A6B6462486B42386D7030663346453047595033415950614A46376A5564314D3058784653453263654B336B326B77323059765130394E4A4B6B2B4F4D6A57516C39576974473970423674534351494441514142416B413253696D42725743322F77766175427559716A4346774C7659695259715A4B54685553334D5A6C6562584A694C422B55652F6755696641414B49673161767474555A73484248726F703471664A43774149302B5952416945412B57334E4B2F526158746E52716D6F55556B6235397A735A55424C70765A675150666A314D687948447A30434951445968734168504A336D675336344E62555A6D475775754E4B7035636F593247496A2F7A59444D4A7036765149675575654C4658762F655A31656B677A324F6936374D4E436B356A655446324275725A714E4C52334D536D554349465433513675484D7473423945686134753768533331746A315557452B442B41447A7035394D476E6F66744169426548543767444D7571654A48504C34622B6B432B677A56344647546668523971337454626B6C5A6B4432413D3D0D0A20202320D1E9D6A4C2EB0D0A2020636170746368613A0D0A202020202320D3CACFE4D1E9D6A4C2EB0D0A20202020656D61696C3A0D0A2020202020202320D3D0D0A7CAB1BCE42FC3EB0D0A2020202020206578706972652D74696D653A2033300D0A2320CFDFB3CCB3D80D0A7461736B3A0D0A2020706F6F6C3A0D0A202020202320BACBD0C4CFDFB3CCB3D8B4F3D0A10D0A20202020636F72652D706F6F6C2D73697A653A2031300D0A202020202320D7EEB4F3CFDFB3CCCAFD0D0A202020206D61782D706F6F6C2D73697A653A2033300D0A202020202320BBEED4BECAB1BCE40D0A202020206B6565702D616C6976652D7365636F6E64733A2036300D0A202020202320B6D3C1D0C8DDC1BF0D0A2020202071756575652D63617061636974793A2035300D0A6B656E6169746F3A0D0A2020636F6E6669672D63656E7465723A0D0A20202020706F72743A203238303032); -- ---------------------------- -- Table structure for demo @@ -249,6 +280,8 @@ INSERT INTO `system_menu` VALUES (122, 10, 0, 1, '一键复制', 'ClipboardDemo' INSERT INTO `system_menu` VALUES (123, 10, 0, 1, 'WebSocket', 'WebSocketDemo', 'components/WebSocketDemo', 999, 'app', 'webSocketDemo', b'0', b'1', b'0', NULL, 'admin', 'admin', '2024-11-15 22:12:18', '2024-11-15 22:12:18'); INSERT INTO `system_menu` VALUES (130, NULL, 5, 0, '用户中心', NULL, NULL, 4, 'peoples', 'sso', b'0', b'0', b'0', NULL, 'admin', 'admin', '2024-11-15 22:12:18', '2024-11-15 22:12:18'); INSERT INTO `system_menu` VALUES (131, NULL, 5, 0, '系统管理', NULL, NULL, 999, 'system1', 'sysconfig', b'0', b'0', b'0', NULL, 'admin', 'admin', '2024-11-15 22:12:18', '2024-11-15 22:12:18'); +INSERT INTO `system_menu` VALUES (136, NULL, 1, 0, '配置中心', NULL, NULL, 999, 'menu', 'config', b'0', b'0', b'0', NULL, 'admin', 'admin', '2024-12-05 19:24:14', '2024-12-05 19:24:14'); +INSERT INTO `system_menu` VALUES (137, 136, 3, 1, '配置管理', 'ConfigFile', 'config/file/index', 999, 'menu', 'configFile', b'0', b'0', b'0', NULL, 'admin', 'admin', '2024-12-05 19:25:36', '2024-12-05 19:25:36'); -- ---------------------------- -- Table structure for system_role