This commit is contained in:
2026-05-09 22:26:03 +03:00
parent 73970c192a
commit 9caefa4c84
48 changed files with 850 additions and 279 deletions
@@ -0,0 +1,332 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="8255b4cc-71e1-4e04-ad77-2f8db881317c" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/client/src/main/kotlin/commands/ClientCommand.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/client/src/main/kotlin/commands/ExecuteScriptCommand.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/client/src/main/kotlin/commands/ExitCommand.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/client/src/main/kotlin/commands/HelpCommand.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/client/src/main/kotlin/commands/UndefinedCommand.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/client/src/main/kotlin/runner/ModelValidator.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common/src/main/kotlin/commands/Command.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/common/src/main/kotlin/network/CommandInfo.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/client/build.gradle" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/main/kotlin/Main.kt" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/main/kotlin/Main.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/main/kotlin/input/HumanBeingBuilder.kt" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/main/kotlin/input/HumanBeingBuilder.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/main/kotlin/input/IOManager.kt" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/main/kotlin/input/IOManager.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/main/kotlin/network/NetworkManager.kt" beforeDir="false" afterPath="$PROJECT_DIR$/client/src/main/kotlin/network/NetworkManager.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/client/src/main/kotlin/runner/CommandValidator.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/common/src/main/kotlin/models/HumanBeing.kt" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/kotlin/models/HumanBeing.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/src/main/kotlin/network/AppJson.kt" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/kotlin/network/AppJson.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/src/main/kotlin/network/Response.kt" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/kotlin/network/Response.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/src/main/kotlin/network/utils/LocalDateSerializer.kt" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/kotlin/network/utils/LocalDateSerializer.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/common/src/main/kotlin/network/utils/UUIDSerializer.kt" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/kotlin/network/utils/UUIDSerializer.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/Main.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/Main.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/collection/CollectionManager.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/collection/CollectionManager.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/AddCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/AddCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/AddIfMaxCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/AddIfMaxCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/AddIfMin.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/AddIfMin.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/ClearCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/ClearCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/Command.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/CommandType.kt" beforeDir="false" afterPath="$PROJECT_DIR$/common/src/main/kotlin/commands/CommandType.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/ExitCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/ExitCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/GetCommandsCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/SyncCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/HelpCommand.kt" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/HistoryCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/HistoryCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/InfoCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/InfoCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/MinByNameCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/MinByNameCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/PrintDescendingMinutesCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/PrintDescendingMinutesCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/RemoveByIdCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/RemoveByIdCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/SaveCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/SaveCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/ShowCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/ShowCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/SumOfMinutesCommands.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/SumOfMinutesCommands.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/commands/UpdateCommand.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/commands/UpdateCommand.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/file/FileManager.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/file/FileManager.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/network/NetworkManager.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/network/NetworkManager.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/kotlin/runner/CommandInvoker.kt" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/kotlin/runner/CommandInvoker.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/server/src/main/resources/logback.xml" beforeDir="false" afterPath="$PROJECT_DIR$/server/src/main/resources/logback.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ExternalProjectsData">
<projectState path="$PROJECT_DIR$">
<ProjectState />
</projectState>
</component>
<component name="ExternalProjectsManager">
<system id="GRADLE">
<state>
<task path="$PROJECT_DIR$">
<activation />
</task>
<task path="$PROJECT_DIR$/server">
<activation />
</task>
<task path="$PROJECT_DIR$/client">
<activation />
</task>
<projects_view>
<tree_state>
<expand>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="Lab6" type="f1a62948:ProjectNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="Lab6" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="Lab6" type="f1a62948:ProjectNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="build" type="c8890929:TasksNode$1" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="Lab6" type="f1a62948:ProjectNode" />
<item name="client" type="2d1252cf:ModuleNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="Lab6" type="f1a62948:ProjectNode" />
<item name="client" type="2d1252cf:ModuleNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
</path>
<path>
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
<item name="Lab6" type="f1a62948:ProjectNode" />
<item name="client" type="2d1252cf:ModuleNode" />
<item name="Tasks" type="e4a08cd1:TasksNode" />
<item name="build" type="c8890929:TasksNode$1" />
</path>
</expand>
<select />
</tree_state>
</projects_view>
</state>
</system>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Kotlin File" />
<option value="Kotlin Object" />
<option value="Kotlin Interface" />
<option value="Class" />
<option value="Kotlin Class" />
<option value="Kotlin Enum" />
<option value="Kotlin Data Class" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="GitHubPullRequestSearchHistory">{
&quot;lastFilter&quot;: {
&quot;state&quot;: &quot;OPEN&quot;,
&quot;assignee&quot;: &quot;notmewhyitsalwaysme&quot;
}
}</component>
<component name="GithubPullRequestsUISettings">{
&quot;selectedUrlAndAccountId&quot;: {
&quot;url&quot;: &quot;git@github.com:notmewhyitsalwaysme/Lab6-prog.git&quot;,
&quot;accountId&quot;: &quot;ffaeab48-780d-4ef5-b729-5e08294300ef&quot;
}
}</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 2
}</component>
<component name="ProjectId" id="3CDaGKZTGiGUMJBmB35RQmnLv2b" />
<component name="ProjectLevelVcsManager">
<ConfirmationsSetting value="2" id="Add" />
</component>
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;Gradle.Build Lab6.executor&quot;: &quot;Run&quot;,
&quot;Gradle.Lab6 [build].executor&quot;: &quot;Run&quot;,
&quot;Gradle.Lab6 [shadowJar].executor&quot;: &quot;Run&quot;,
&quot;Gradle.Lab6:client [build].executor&quot;: &quot;Run&quot;,
&quot;Gradle.Lab6:server [build].executor&quot;: &quot;Run&quot;,
&quot;Kotlin.MainKt (1).executor&quot;: &quot;Run&quot;,
&quot;Kotlin.MainKt.executor&quot;: &quot;Run&quot;,
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/aklivtsov/Projects/Прог/Lab6/server&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;onboarding.tips.debug.path&quot;: &quot;/Users/aklivtsov/Projects/Прог/Lab6/src/main/kotlin/Main.kt&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;project.structure.proportion&quot;: &quot;0.0&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.0&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.keymap&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/server" />
<recent name="$PROJECT_DIR$" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/client/build/libs" />
<recent name="$PROJECT_DIR$/client/build" />
<recent name="$PROJECT_DIR$/client" />
</key>
<key name="K2MoveDeclarationsDialog.RECENT_PACKAGE_KEY">
<recent name="commands" />
</key>
</component>
<component name="RunManager" selected="Gradle.Lab6 [build]">
<configuration name="Lab6 [build]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="build" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
<configuration name="Lab6:client [build]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/client" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="build" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
<configuration name="Lab6:server [build]" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/server" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="build" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
<configuration name="MainKt (1)" type="JetRunConfigurationType" temporary="true" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="lab5.prog.MainKt" />
<module name="Lab6.client.main" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration name="MainKt" type="JetRunConfigurationType" temporary="true" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="MainKt" />
<module name="Lab6.server.main" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
<recent_temporary>
<list>
<item itemvalue="Gradle.Lab6 [build]" />
<item itemvalue="Gradle.Lab6:client [build]" />
<item itemvalue="Gradle.Lab6:server [build]" />
<item itemvalue="Kotlin.MainKt (1)" />
<item itemvalue="Kotlin.MainKt" />
</list>
</recent_temporary>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-jdk-9823dce3aa75-fbdcb00ec9e3-intellij.indexing.shared.core-IU-251.26094.121" />
<option value="bundled-js-predefined-d6986cc7102b-b26f3e71634d-JavaScript-IU-251.26094.121" />
</set>
</attachedChunks>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="8255b4cc-71e1-4e04-ad77-2f8db881317c" name="Changes" comment="" />
<created>1775922497640</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1775922497640</updated>
<workItem from="1775922498637" duration="1487000" />
<workItem from="1775925490281" duration="35000" />
<workItem from="1775926686698" duration="2873000" />
<workItem from="1776006672050" duration="17531000" />
<workItem from="1776083371625" duration="7547000" />
<workItem from="1776169079658" duration="2439000" />
<workItem from="1776370566070" duration="1055000" />
<workItem from="1776425446066" duration="15604000" />
<workItem from="1776496828085" duration="645000" />
<workItem from="1776507809946" duration="5019000" />
<workItem from="1776792937822" duration="18000" />
<workItem from="1776793025237" duration="4361000" />
<workItem from="1776849171223" duration="9126000" />
<workItem from="1776926840347" duration="191000" />
<workItem from="1777054819548" duration="2281000" />
<workItem from="1777137270484" duration="466000" />
<workItem from="1777365159796" duration="1511000" />
<workItem from="1777387591468" duration="598000" />
<workItem from="1777488835350" duration="7461000" />
<workItem from="1777498959887" duration="358000" />
<workItem from="1777581645459" duration="2094000" />
<workItem from="1777708062296" duration="6811000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
</project>
+4
View File
@@ -23,7 +23,11 @@ shadowJar {
dependencies { dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test' testImplementation 'org.jetbrains.kotlin:kotlin-test'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0'
implementation 'io.github.microutils:kotlin-logging-jvm:3.0.5'
implementation "org.jline:jline:3.30.6" implementation "org.jline:jline:3.30.6"
implementation "io.github.oshai:kotlin-logging-jvm:7.0.3"
implementation "org.slf4j:slf4j-api:2.0.13"
implementation 'ch.qos.logback:logback-classic:1.5.13'
implementation project(':common') implementation project(':common')
} }
+26 -59
View File
@@ -1,8 +1,13 @@
import ch.qos.logback.core.net.server.Client
import commands.ExecuteScriptCommand
import commands.ExitCommand
import commands.HelpCommand
import commands.SyncCommand
import commands.UndefinedCommand
import network.NetworkManager import network.NetworkManager
import input.HumanBeingBuilder
import input.IOManager import input.IOManager
import network.Request import network.Request
import runner.CommandValidator import runner.CommandInvoker
fun main(args: Array<String>) { fun main(args: Array<String>) {
val host = if (args.isNotEmpty()) args[0] else "localhost" val host = if (args.isNotEmpty()) args[0] else "localhost"
@@ -10,7 +15,7 @@ fun main(args: Array<String>) {
val io = IOManager() // Name by Boris Bosenko val io = IOManager() // Name by Boris Bosenko
val client = NetworkManager(host, port) val client = NetworkManager(host, port)
val validator = CommandValidator() val invoker = CommandInvoker(io)
// Shutdown hook // Shutdown hook
Runtime.getRuntime().addShutdownHook(Thread { Runtime.getRuntime().addShutdownHook(Thread {
@@ -23,70 +28,32 @@ fun main(args: Array<String>) {
|════════════════════════════════════════════ |════════════════════════════════════════════
""".trimMargin()) """.trimMargin())
runClientRepl(io, client, validator) registerBasicCommands(invoker, client, io)
runClientRepl(io, invoker)
} }
fun runClientRepl(io: IOManager, client: NetworkManager, validator: CommandValidator) { fun registerBasicCommands(invoker: CommandInvoker,client: NetworkManager, io: IOManager) {
listOf(
val initialRequest = Request( HelpCommand(invoker),
commandName = "getCommandsCommand", ExecuteScriptCommand(io),
humanBeing = null SyncCommand(invoker, client)
) ).forEach { invoker.register(it)}
if (client.sendRequest(initialRequest) == null) {
io.print("[!] Сервер недоступен. Попробуйте позже.")
} else {
io.print("|Введите 'help' для справки.")
} }
while (true) { fun runClientRepl(io: IOManager, invoker : CommandInvoker) {
// sync commands w server
invoker.execute("sync")
var isRunning = true
invoker.register(ExitCommand {isRunning = false})
while (isRunning) {
val line = io.readLine(" ☭ ") ?: break val line = io.readLine(" ☭ ") ?: break
if (line.isBlank()) continue if (line.isBlank()) continue
invoker.execute(line)
val parts = line.trim().split(" ")
val commandName = parts[0].lowercase()
val args = parts.drop(1)
val (isValidCommand, message) = validator.validate(commandName, args)
if (!isValidCommand) {
if (message != null) io.print(message)
continue
} }
if (commandName == "exit") { // and here we exit that crazy circus of code and tears
io.print("До свидания!") io.print("До свидания!")
break
}
if (commandName == "execute_script") {
io.addScriptScanner(args[0])
continue
}
val humanBeing = if (validator.isBuildsHumanBeing(commandName)) {
try {
HumanBeingBuilder(io).build()
} catch (e: IllegalStateException) {
io.print("[Ошибка] Ввод прерван: ${e.message}")
continue
}
} else null
// Формируем запрос и отправляем
val request = Request(
commandName = commandName,
args = args,
humanBeing = humanBeing
)
val response = client.sendRequest(request)
// Обрабатываем ответ
if (response == null) {
io.print("[!] Сервер недоступен. Попробуйте позже.")
} else {
io.print(response.message)
}
}
} }
@@ -0,0 +1,13 @@
package commands
import models.HumanBeing
interface ClientCommand {
val name: String
val description: String
val args: List<CommandArgType>
val type: CommandType
fun execute(args: List<String>, humanBeing: HumanBeing? = null): String
}
@@ -0,0 +1,16 @@
package commands
import input.IOManager
import models.HumanBeing
class ExecuteScriptCommand(private val io: IOManager) : ClientCommand {
override val name = "execute_script"
override val description = "Выполняет скрипт из файла"
override val args = listOf(CommandArgType.STRING)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
io.addScriptScanner(args[0])
return ""
}
}
@@ -0,0 +1,15 @@
package commands
import models.HumanBeing
class ExitCommand(private val onExit: () -> Unit) : ClientCommand {
override val name = "exit"
override val description = "завершить клиент"
override val args: List<CommandArgType> = listOf(CommandArgType.NONE)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
onExit()
return ""
}
}
@@ -1,15 +1,16 @@
package commands package commands
import runner.CommandInvoker
import models.HumanBeing import models.HumanBeing
import runner.CommandInvoker
class HelpCommand(private val invoker: CommandInvoker) : Command { class HelpCommand(private val invoker: CommandInvoker) : ClientCommand {
override val name = "help" override val name = "help"
override val description = "вывести справку по доступным командам" override val description = "вывести справку по доступным командам"
override val args = listOf(CommandArgType.NONE)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String = override fun execute(args: List<String>, humanBeing: HumanBeing?): String =
invoker.getCommands().values invoker.getCommands().values
.sortedBy { it.name } .sortedBy { it.name }
.joinToString("\n") { " ${it.name.padEnd(40)} — ${it.description}" } .joinToString("\n") { " ${it.name.padEnd(40)} — ${it.description}" }
} }
@@ -0,0 +1,42 @@
package commands
import input.IOManager
import models.HumanBeing
import network.NetworkManager
import network.Request
import runner.CommandInvoker
class SyncCommand (
private val invoker: CommandInvoker,
private val client: NetworkManager,
) : ClientCommand {
override val name = "sync"
override val description = "получить команды с сервера"
override val args = listOf(CommandArgType.NONE)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
val initialRequest = Request(
commandName = "sync",
humanBeing = null
)
val res = client.sendRequest(initialRequest)
if (res == null) {
return ("[!] Сервер недоступен. Попробуйте позже.")
} else {
res.commands?.forEach { fuckingCommand ->
invoker.register(
UndefinedCommand( // ser
name = fuckingCommand.name,
description = fuckingCommand.description,
type = fuckingCommand.type,
args = fuckingCommand.args,
networkManager = client
)
)
}
return ("| Подключен. | Введите 'help' для справки.")
}
}
}
@@ -0,0 +1,25 @@
package commands
import input.HumanBeingBuilder
import models.HumanBeing
import network.Request
import network.NetworkManager
class UndefinedCommand(
override var name: String,
override var description: String,
override var args: List<CommandArgType>,
override var type: CommandType,
private val networkManager: NetworkManager,
): ClientCommand {
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
val request = Request(
commandName = this.name,
args = args,
humanBeing = humanBeing
)
val response = networkManager.sendRequest(request)
return response?.message ?: "[!] Сервер не ответил"
}
}
@@ -16,6 +16,7 @@ import models.WeaponType
* *
* @property inputManager источник ввода * @property inputManager источник ввода
*/ */
class HumanBeingBuilder(private val inputManager: IOManager) { class HumanBeingBuilder(private val inputManager: IOManager) {
/** /**
+1 -4
View File
@@ -6,16 +6,13 @@ import org.jline.reader.LineReaderBuilder
import org.jline.reader.UserInterruptException import org.jline.reader.UserInterruptException
import org.jline.reader.impl.history.DefaultHistory import org.jline.reader.impl.history.DefaultHistory
import org.jline.terminal.TerminalBuilder import org.jline.terminal.TerminalBuilder
import org.jline.reader.impl.completer.StringsCompleter
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.ArrayDeque import java.util.ArrayDeque
import java.util.Scanner import java.util.Scanner
import runner.CommandValidator
class IOManager { class IOManager {
val isInteractive: Boolean = true val isInteractive: Boolean = true
val commandValidator: CommandValidator = CommandValidator()
private val terminal = TerminalBuilder.builder() private val terminal = TerminalBuilder.builder()
.system(true) .system(true)
@@ -26,7 +23,7 @@ class IOManager {
private val lineReader: LineReader = LineReaderBuilder.builder() private val lineReader: LineReader = LineReaderBuilder.builder()
.terminal(terminal) .terminal(terminal)
.history(history) .history(history)
.completer(StringsCompleter(commandValidator.getKnownCommands())) // .completer(StringsCompleter(invoker.getCommandNames()))
.option(LineReader.Option.HISTORY_IGNORE_DUPS, true) .option(LineReader.Option.HISTORY_IGNORE_DUPS, true)
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true) .option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
.build() .build()
@@ -1,7 +1,6 @@
package network package network
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import lab6.prog.network.AppJson
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.PortUnreachableException import java.net.PortUnreachableException
import java.nio.ByteBuffer import java.nio.ByteBuffer
@@ -0,0 +1,56 @@
package runner
import commands.ClientCommand
import commands.CommandType
import input.HumanBeingBuilder
import input.IOManager
import io.github.oshai.kotlinlogging.KotlinLogging
private val logger = KotlinLogging.logger {}
class CommandInvoker(private val io: IOManager) {
private val commands = mutableMapOf<String, ClientCommand>()
private val history = ArrayDeque<String>(12)
fun register(command: ClientCommand) {
commands[command.name] = command
}
fun execute(input: String) {
val parts = input.trim().split(" ")
val name = parts[0].lowercase().trim()
val args = parts.drop(1)
val command = commands[name]
if (command == null) {
logger.info { "Неизвестная команда: '$name'. Введите 'help' для справки."}
return
}
addToHistory(name)
try {
if (command.type == CommandType.MODEL) {
val model = HumanBeingBuilder(io).build()
val res = command.execute(args, model)
io.print(res)
} else {
val res = command.execute(args)
io.print(res)
}
} catch (e: IllegalStateException) {
logger.info {"[Ошибка выполнения] ${e.message}"}
}
}
fun getHistory(): List<String> = history.toList()
fun getCommands(): Map<String, ClientCommand> = commands.toMap()
private fun addToHistory(name: String) {
if (history.size >= 12) history.removeFirst()
history.addLast(name)
}
fun getCommandNames(): List<String> = commands.keys.toList()
}
@@ -1,42 +0,0 @@
package runner
class CommandValidator {
// Команды, которые требуют ввода HumanBeing
private val humanBeingCommands = setOf("add", "add_if_max", "add_if_min")
// Команды с аргументом
private val commandsWArgs = setOf("update", "remove_by_id", "execute_script")
// Все команды, доступные клиенту
private val knownCommands = setOf(
"help", "info", "show", "add", "update",
"remove_by_id", "clear", "execute_script",
"add_if_max", "add_if_min", "history",
"sum_of_minutes_of_waiting", "min_by_name",
"print_field_descending_minutes_of_waiting",
"exit"
)
fun getKnownCommands(): Set<String> {return knownCommands}
fun validate(command: String, args: List<String>?): Pair<Boolean, String?> {
if (command !in knownCommands){
return Pair(false, "Неизвестная команда: '$command'. Введите 'help' для справки.")
}
if (command in commandsWArgs && args?.isEmpty() == true) {
return if (command == "execute_script") {
Pair(false, "[Ошибка] Укажите путь к файлу. Пример: execute_script script.txt")
} else {
// maybe create another set w commands w file args and another for other args
Pair(false, "[Ошибка] Укажите id элемента. Пример: $command <uuid>")
}
}
return Pair(true, null)
}
fun isBuildsHumanBeing(command: String): Boolean {
return command in humanBeingCommands
}
}
@@ -0,0 +1,7 @@
package runner
class ModelValidator {
}
@@ -0,0 +1,13 @@
package commands
import models.HumanBeing
import network.Response
interface Command {
val name: String
val description: String
val args: List<CommandArgType>
val type: CommandType
fun execute(args: List<String>, humanBeing: HumanBeing? = null): Response
}
@@ -0,0 +1,10 @@
package commands
import kotlinx.serialization.Serializable
// why there's only two types? yes.
@Serializable
enum class CommandArgType {
STRING,
NONE
}
@@ -0,0 +1,6 @@
package commands
enum class CommandType {
SIMPLE,
MODEL
}
+2 -2
View File
@@ -1,8 +1,8 @@
package models package models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import lab6.prog.network.utils.LocalDateSerializer import network.utils.LocalDateSerializer
import lab6.prog.network.utils.UUIDSerializer import network.utils.UUIDSerializer
import java.time.LocalDate import java.time.LocalDate
import java.util.UUID import java.util.UUID
+1 -1
View File
@@ -1,4 +1,4 @@
package lab6.prog.network package network
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@@ -0,0 +1,13 @@
package network
import commands.CommandArgType
import commands.CommandType
import kotlinx.serialization.Serializable
@Serializable
data class CommandInfo(
val name: String,
val description: String,
val args: List<CommandArgType>,
val type: CommandType
)
+7 -1
View File
@@ -7,9 +7,15 @@ import kotlinx.serialization.Serializable
* *
* @property success true — команда выполнена, false — ошибка * @property success true — команда выполнена, false — ошибка
* @property message текст для вывода пользователю * @property message текст для вывода пользователю
*
* -- только при синхронизации --
* @property knownCommands - общий список известных команд
* @property humanBeingCommands - команды, требующие ввода коллекции [models.HumanBeing]
* @property commandsWArgs - команды, требующие ввода аргументов
*/ */
@Serializable @Serializable
data class Response( data class Response(
val success: Boolean, val success: Boolean,
val message: String val message: String,
val commands: List<CommandInfo>? = null,
) )
@@ -1,4 +1,4 @@
package lab6.prog.network.utils package network.utils
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.* import kotlinx.serialization.descriptors.*
@@ -1,4 +1,4 @@
package lab6.prog.network.utils package network.utils
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.* import kotlinx.serialization.descriptors.*
+3
View File
@@ -25,6 +25,9 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0' implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0'
implementation 'io.github.microutils:kotlin-logging-jvm:3.0.5' implementation 'io.github.microutils:kotlin-logging-jvm:3.0.5'
implementation "io.ktor:ktor-network:3.4.2" implementation "io.ktor:ktor-network:3.4.2"
implementation "io.github.oshai:kotlin-logging-jvm:7.0.3"
implementation "org.slf4j:slf4j-api:2.0.13"
implementation "ch.qos.logback:logback-classic:1.5.6"
implementation project(':common') implementation project(':common')
} }
+15 -8
View File
@@ -1,14 +1,20 @@
import collection.CollectionManager import collection.CollectionManager
import commands.* import commands.*
import file.FileManager import file.FileManager
import mu.KotlinLogging
import network.NetworkManager import network.NetworkManager
import runner.CommandInvoker import runner.CommandInvoker
import kotlin.system.exitProcess import kotlin.system.exitProcess
private val logger = KotlinLogging.logger {}
fun main(args: Array<String>) { fun main(args: Array<String>) {
System.setProperty("slf4j.internal.verbosity", "WARN") System.setProperty("slf4j.internal.verbosity", "WARN")
val filePath = if (args.isNotEmpty()) args[0] else "data.csv" val filePath = if (args.isNotEmpty()) args[0] else "data.csv"
val port = if (args.isNotEmpty() && args.size > 1) args[1].toInt() else 8080
logger.info{ "[startup] Starting server on port $port with $args args" }
val fileManager = FileManager(filePath) val fileManager = FileManager(filePath)
val manager = CollectionManager() val manager = CollectionManager()
@@ -21,9 +27,9 @@ fun main(args: Array<String>) {
registerServerCommands(invoker, manager, fileManager) registerServerCommands(invoker, manager, fileManager)
// Сохранение при завершении (Ctrl+C или kill) // Сохранение при завершении (Ctrl+C или kill)
val server = NetworkManager(port = 8080, invoker = invoker) val server = NetworkManager(port = port, invoker = invoker)
Runtime.getRuntime().addShutdownHook(Thread { Runtime.getRuntime().addShutdownHook(Thread {
println("[Server] Сохранение коллекции перед выходом...") logger.info{ "Saving collection before exit..." }
fileManager.write(manager.getAll()) fileManager.write(manager.getAll())
server.stop() server.stop()
}) })
@@ -31,18 +37,18 @@ fun main(args: Array<String>) {
// Консоль на сервере // Консоль на сервере
Thread { Thread {
val scanner = java.util.Scanner(System.`in`) val scanner = java.util.Scanner(System.`in`)
println("[Server] Серверные команды: save, exit") logger.info{ "[Server-console] Server-side commands: save, exit" }
while (scanner.hasNextLine()) { while (scanner.hasNextLine()) {
when (scanner.nextLine().trim().lowercase()) { when (scanner.nextLine().trim().lowercase()) {
"save" -> { "save" -> {
fileManager.write(manager.getAll()) fileManager.write(manager.getAll())
println("[Server] Коллекция сохранена.") logger.info{ "[Server-console] Collection is saved" }
} }
"exit" -> { "exit" -> {
println("[Server] Завершение работы...") logger.info{ "[Server-console] Shutting down..." }
exitProcess(0) exitProcess(0)
} }
else -> println("[Server] Доступные команды: save, exit") else -> logger.info{ "[Server-console] Server-side commands: save, exit" }
} }
} }
}.also { it.isDaemon = true }.start() }.also { it.isDaemon = true }.start()
@@ -56,7 +62,7 @@ fun registerServerCommands(
fileManager: FileManager fileManager: FileManager
) { ) {
listOf( listOf(
HelpCommand(invoker), // HelpCommand(invoker),
InfoCommand(manager), InfoCommand(manager),
ShowCommand(manager), ShowCommand(manager),
AddCommand(manager), AddCommand(manager),
@@ -67,10 +73,11 @@ fun registerServerCommands(
AddIfMaxCommand(manager), AddIfMaxCommand(manager),
AddIfMinCommand(manager), AddIfMinCommand(manager),
HistoryCommand(invoker), HistoryCommand(invoker),
GetCommandsCommand(invoker), SyncCommand(invoker),
SumOfMinutesCommand(manager), SumOfMinutesCommand(manager),
MinByNameCommand(manager), MinByNameCommand(manager),
PrintDescendingMinutesCommand(manager), PrintDescendingMinutesCommand(manager),
ExitCommand() ExitCommand()
).forEach { invoker.register(it)} ).forEach { invoker.register(it)}
logger.info{ " [startup] Registered commands: ${invoker.getCommands().keys}" }
} }
@@ -8,13 +8,14 @@ import java.util.UUID
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
class CollectionManager { class CollectionManager {
private val collection: TreeSet<HumanBeing> = TreeSet() private val collection: TreeSet<HumanBeing> = TreeSet()
val initDate: LocalDate = LocalDate.now() val initDate: LocalDate = LocalDate.now()
fun add(humanBeing: HumanBeing): Boolean { fun add(humanBeing: HumanBeing): Boolean {
logger.info { "[ADD] Создан элемент ${humanBeing.id}" } logger.info { "[ADD] Element created ${humanBeing.id}" }
return collection.add(humanBeing) return collection.add(humanBeing)
} }
@@ -22,19 +23,19 @@ class CollectionManager {
val old = getById(id) ?: return false val old = getById(id) ?: return false
collection.remove(old) collection.remove(old)
val replaced = updated.copy(id = old.id, creationDate = old.creationDate) val replaced = updated.copy(id = old.id, creationDate = old.creationDate)
logger.info { "[UPDATE] Обновлён элемент ${old.id}" } logger.info { "[UPDATE] Element updated ${old.id}" }
return collection.add(replaced) return collection.add(replaced)
} }
fun removeById(id: UUID): Boolean { fun removeById(id: UUID): Boolean {
val target = getById(id) ?: return false val target = getById(id) ?: return false
logger.info { "[REMOVE] Удалён элемент ${target.id}" } logger.info { "[REMOVE] Element deleted ${target.id}" }
return collection.remove(target) return collection.remove(target)
} }
fun clear() { fun clear() {
collection.clear() collection.clear()
logger.info { "[CLEAR] Коллекция очищена" } logger.info { "[CLEAR] Collection cleared" }
} }
fun getAll(): TreeSet<HumanBeing> = TreeSet(collection) fun getAll(): TreeSet<HumanBeing> = TreeSet(collection)
@@ -78,7 +79,7 @@ class CollectionManager {
fun loadFromFile(items: List<HumanBeing>) { fun loadFromFile(items: List<HumanBeing>) {
collection.clear() collection.clear()
items.stream().forEach { collection.add(it) } items.stream().forEach { collection.add(it) }
logger.info { "[LOAD] Загружено ${items.size} элементов" } logger.info { "[LOAD] loaded ${items.size} elements" }
} }
fun getInfo(): String = fun getInfo(): String =
+11 -5
View File
@@ -1,17 +1,23 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
class AddCommand(private val manager: CollectionManager) : Command { @Serializable
class AddCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "add" override val name = "add"
override val description = "добавить новый элемент в коллекцию" override val description = "добавить новый элемент в коллекцию"
override val args = listOf(CommandArgType.NONE)
override val type = CommandType.MODEL
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
humanBeing ?: return "[Ошибка] Объект HumanBeing не передан." humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
return if (manager.add(humanBeing)) return if (manager.add(humanBeing))
"Элемент добавлен: ${humanBeing.name} [${humanBeing.id}]" Response(true, "Элемент добавлен: ${humanBeing.name} [${humanBeing.id}]")
else else
"Элемент уже существует в коллекции." Response(false, "Элемент уже существует в коллекции.")
} }
} }
@@ -2,19 +2,22 @@ package commands
import collection.CollectionManager import collection.CollectionManager
import models.HumanBeing import models.HumanBeing
import network.Response
class AddIfMaxCommand(private val manager: CollectionManager) : Command { class AddIfMaxCommand(private val manager: CollectionManager) : Command {
override val name = "add_if_max" override val name = "add_if_max"
override val description = "добавить элемент, если он больше максимального" override val description = "добавить элемент, если он больше максимального"
override val type = CommandType.MODEL
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
humanBeing ?: return "[Ошибка] Объект HumanBeing не передан." humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
val max = manager.getMax() val max = manager.getMax()
return if (max == null || humanBeing > max) { return if (max == null || humanBeing > max) {
manager.add(humanBeing) manager.add(humanBeing)
"Элемент добавлен (превышает максимум)." Response(true, "Элемент добавлен (превышает максимум).")
} else { } else {
"Элемент не добавлен: не превышает текущий максимум (${max.name})." Response(false, "Элемент не добавлен: не превышает текущий максимум (${max.name}).")
} }
} }
} }
+11 -5
View File
@@ -2,19 +2,25 @@ package commands
import collection.CollectionManager import collection.CollectionManager
import models.HumanBeing import models.HumanBeing
import kotlinx.serialization.Serializable
import kotlinx.serialization.Contextual
import network.Response
class AddIfMinCommand(private val manager: CollectionManager) : Command { @Serializable
class AddIfMinCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "add_if_min" override val name = "add_if_min"
override val description = "добавить элемент, если он меньше минимального" override val description = "добавить элемент, если он меньше минимального"
override val type = CommandType.MODEL
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
humanBeing ?: return "[Ошибка] Объект HumanBeing не передан." humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
val min = manager.getMin() val min = manager.getMin()
return if (min == null || humanBeing < min) { return if (min == null || humanBeing < min) {
manager.add(humanBeing) manager.add(humanBeing)
"Элемент добавлен (меньше минимума)." Response(true, "Элемент добавлен (меньше минимума).")
} else { } else {
"Элемент не добавлен: не меньше текущего минимума (${min.name})." Response(false, "Элемент не добавлен: не меньше текущего минимума (${min.name}).")
} }
} }
} }
@@ -1,14 +1,20 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
class ClearCommand(private val manager: CollectionManager) : Command { @Serializable
class ClearCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "clear" override val name = "clear"
override val description = "очистить коллекцию" override val description = "очистить коллекцию"
override val args = listOf(CommandArgType.NONE)
override val type = CommandType.SIMPLE
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
manager.clear() manager.clear()
return "Коллекция очищена." return Response(true, "Коллекция очищена.")
} }
} }
+4 -1
View File
@@ -1,6 +1,7 @@
package commands package commands
import models.HumanBeing import models.HumanBeing
import network.Response
/** /**
* Базовый интерфейс команды. * Базовый интерфейс команды.
@@ -11,11 +12,13 @@ import models.HumanBeing
interface Command { interface Command {
val name: String val name: String
val description: String val description: String
val type : CommandType
val args : List<CommandArgType>
/** /**
* @param args строковые аргументы (UUID и т.п.) * @param args строковые аргументы (UUID и т.п.)
* @param humanBeing объект из запроса — только для add/update/add_if_max/add_if_min * @param humanBeing объект из запроса — только для add/update/add_if_max/add_if_min
* @return строка-результат для клиента * @return строка-результат для клиента
*/ */
fun execute(args: List<String>, humanBeing: HumanBeing? = null): String fun execute(args: List<String>, humanBeing: HumanBeing? = null): Response
} }
@@ -3,11 +3,16 @@ package commands
/** /**
* Завершает программу без сохранения. * Завершает программу без сохранения.
*/ */
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
@Serializable
class ExitCommand() : Command { class ExitCommand() : Command {
override val name = "exit" override val name = "exit"
override val description = "завершить программу" override val description = "завершить программу"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String = "" override fun execute(args: List<String>, humanBeing: HumanBeing?): Response = Response(true, "")
} }
@@ -1,15 +0,0 @@
package commands
import models.HumanBeing
import runner.CommandInvoker
class GetCommandsCommand (
private val invoker: CommandInvoker
): Command {
override val name: String = "GetCommands"
override val description: String = "Получает все доступные с сервера команды"
override fun execute(args: List<String>, humanBeing: HumanBeing?): String {
return invoker.getCommands().toString()
}
}
@@ -1,15 +1,21 @@
package commands package commands
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import runner.CommandInvoker import runner.CommandInvoker
import models.HumanBeing import models.HumanBeing
import network.Response
class HistoryCommand(private val invoker: CommandInvoker) : Command { @Serializable
class HistoryCommand(@Contextual private val invoker: CommandInvoker) : Command {
override val name = "history" override val name = "history"
override val description = "вывести последние 12 команд" override val description = "вывести последние 12 команд"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
val history = invoker.getHistory() val history = invoker.getHistory()
if (history.isEmpty()) return "История пуста." if (history.isEmpty()) return Response(true, "История пуста.")
return history.mapIndexed { i, cmd -> " ${i + 1}. $cmd" }.joinToString("\n") return Response(true, history.mapIndexed { i, cmd -> " ${i + 1}. $cmd" }.joinToString("\n"))
} }
} }
@@ -1,12 +1,18 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
class InfoCommand(private val manager: CollectionManager) : Command { @Serializable
class InfoCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "info" override val name = "info"
override val description = "вывести информацию о коллекции" override val description = "вывести информацию о коллекции"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String = override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
manager.getInfo() Response(true, manager.getInfo())
} }
@@ -1,15 +1,21 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
/** /**
* Выводит любой элемент с минимальным значением поля [HumanBeing.name]. * Выводит любой элемент с минимальным значением поля [HumanBeing.name].
*/ */
class MinByNameCommand(private val manager: CollectionManager) : Command { @Serializable
class MinByNameCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "min_by_name" override val name = "min_by_name"
override val description = "вывести элемент с минимальным именем" override val description = "вывести элемент с минимальным именем"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String = override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
manager.minByName()?.toString() ?: "Коллекция пуста." Response(true, manager.minByName()?.toString() ?: "Коллекция пуста.")
} }
@@ -1,18 +1,24 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
/** /**
* Выводит значения поля [models.HumanBeing.minutesOfWaiting] в порядке убывания. * Выводит значения поля [models.HumanBeing.minutesOfWaiting] в порядке убывания.
*/ */
class PrintDescendingMinutesCommand(private val manager: CollectionManager) : Command { @Serializable
class PrintDescendingMinutesCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "print_field_descending_minutes_of_waiting" override val name = "print_field_descending_minutes_of_waiting"
override val description = "вывести minutesOfWaiting в порядке убывания" override val description = "вывести minutesOfWaiting в порядке убывания"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
val values = manager.getMinutesOfWaitingDescending() val values = manager.getMinutesOfWaitingDescending()
return if (values.isEmpty()) "Коллекция пуста." return if (values.isEmpty()) Response(true, "Коллекция пуста.")
else values.joinToString("\n") else Response(true, values.joinToString("\n"))
} }
} }
@@ -1,23 +1,29 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.util.UUID import java.util.UUID
import models.HumanBeing import models.HumanBeing
import network.Response
class RemoveByIdCommand(private val manager: CollectionManager) : Command { @Serializable
class RemoveByIdCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "remove_by_id" override val name = "remove_by_id"
override val description = "удалить элемент по id: remove_by_id <uuid>" override val description = "удалить элемент по id: remove_by_id <uuid>"
override val type = CommandType.MODEL
override val args = listOf(CommandArgType.STRING)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
if (args.isEmpty()) return "[Ошибка] Укажите id. Пример: remove_by_id <uuid>" if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: remove_by_id <uuid>")
val id = try { val id = try {
UUID.fromString(args[0]) UUID.fromString(args[0])
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
return "[Ошибка] Некорректный UUID: '${args[0]}'" return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
} }
return if (manager.removeById(id)) "Элемент удалён." return if (manager.removeById(id)) Response(true, "Элемент удалён.")
else "[Ошибка] Элемент с id '$id' не найден." else Response(false, "[Ошибка] Элемент с id '$id' не найден.")
} }
} }
+11 -5
View File
@@ -2,17 +2,23 @@ package commands
import collection.CollectionManager import collection.CollectionManager
import file.FileManager import file.FileManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
@Serializable
class SaveCommand( class SaveCommand(
private val manager: CollectionManager, @Contextual private val manager: CollectionManager,
private val fileManager: FileManager @Contextual private val fileManager: FileManager
) : Command { ) : Command {
override val name = "save" override val name = "save"
override val description = "[SERVER] сохранить коллекцию в файл" override val description = "[SERVER] сохранить коллекцию в файл"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
return if (fileManager.write(manager.getAll())) "Коллекция сохранена." return if (fileManager.write(manager.getAll())) Response(true, "Коллекция сохранена.")
else "[Ошибка] Не удалось сохранить коллекцию." else Response(false, "[Ошибка] Не удалось сохранить коллекцию.")
} }
} }
+10 -4
View File
@@ -1,14 +1,20 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
class ShowCommand(private val manager: CollectionManager) : Command { @Serializable
class ShowCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "show" override val name = "show"
override val description = "вывести все элементы коллекции" override val description = "вывести все элементы коллекции"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
if (manager.isEmpty()) return "Коллекция пуста." if (manager.isEmpty()) return Response(true, "Коллекция пуста.")
return manager.getAll().joinToString("\n") { it.toString() } return Response(true, manager.getAll().joinToString("\n") { it.toString() })
} }
} }
@@ -1,15 +1,21 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
/** /**
* Выводит сумму значений поля [models.HumanBeing.minutesOfWaiting]. * Выводит сумму значений поля [models.HumanBeing.minutesOfWaiting].
*/ */
class SumOfMinutesCommand(private val manager: CollectionManager) : Command { @Serializable
class SumOfMinutesCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "sum_of_minutes_of_waiting" override val name = "sum_of_minutes_of_waiting"
override val description = "вывести сумму minutesOfWaiting всех элементов" override val description = "вывести сумму minutesOfWaiting всех элементов"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String = override fun execute(args: List<String>, humanBeing: HumanBeing?): Response =
"Сумма minutesOfWaiting: ${manager.sumOfMinutesOfWaiting()}" Response(true, "Сумма minutesOfWaiting: ${manager.sumOfMinutesOfWaiting()}")
} }
@@ -0,0 +1,45 @@
package commands
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing
import network.CommandInfo
import network.Response
import runner.CommandInvoker
@Serializable
class SyncCommand(
@Contextual private val invoker: CommandInvoker
) : Command {
override val name: String = "sync"
override val description: String = "Получает все доступные с сервера команды"
override val type = CommandType.SIMPLE
override val args = listOf(CommandArgType.NONE)
override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
val commands = mutableMapOf<String, Command>()
for (command in invoker.getCommands().values) {
if (command.name == "sync" || command.name == "exit") {
continue
}
commands[command.name] = command
}
val commandInfos = commands.values.map { command ->
CommandInfo(
name = command.name,
description = command.description,
type = command.type,
args = command.args
)
}
return Response(
success = true,
message = "хуй",
commands = commandInfos
)
}
}
@@ -1,24 +1,30 @@
package commands package commands
import collection.CollectionManager import collection.CollectionManager
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import models.HumanBeing import models.HumanBeing
import network.Response
import java.util.UUID import java.util.UUID
class UpdateCommand(private val manager: CollectionManager) : Command { @Serializable
class UpdateCommand(@Contextual private val manager: CollectionManager) : Command {
override val name = "update" override val name = "update"
override val description = "обновить элемент по id: update <uuid>" override val description = "обновить элемент по id: update <uuid>"
override val type = CommandType.MODEL
override val args = listOf(CommandArgType.STRING)
override fun execute(args: List<String>, humanBeing: HumanBeing?): String { override fun execute(args: List<String>, humanBeing: HumanBeing?): Response {
if (args.isEmpty()) return "[Ошибка] Укажите id. Пример: update <uuid>" if (args.isEmpty()) return Response(false, "[Ошибка] Укажите id. Пример: update <uuid>")
humanBeing ?: return "[Ошибка] Объект HumanBeing не передан." humanBeing ?: return Response(false, "[Ошибка] Объект HumanBeing не передан.")
val id = try { val id = try {
UUID.fromString(args[0]) UUID.fromString(args[0])
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
return "[Ошибка] Некорректный UUID: '${args[0]}'" return Response(false, "[Ошибка] Некорректный UUID: '${args[0]}'")
} }
return if (manager.update(id, humanBeing)) "Элемент обновлён." return if (manager.update(id, humanBeing)) Response(true, "Элемент обновлён.")
else "[Ошибка] Элемент с id '$id' не найден." else Response(false, "[Ошибка] Элемент с id '$id' не найден.")
} }
} }
+20 -22
View File
@@ -4,6 +4,7 @@ import models.Car
import models.Coordinates import models.Coordinates
import models.HumanBeing import models.HumanBeing
import models.WeaponType import models.WeaponType
import mu.KotlinLogging
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
@@ -12,6 +13,8 @@ import java.time.format.DateTimeParseException
import java.util.Scanner import java.util.Scanner
import java.util.UUID import java.util.UUID
private val logger = KotlinLogging.logger {}
/** /**
* Менеджер файлового хранилища коллекции. * Менеджер файлового хранилища коллекции.
* *
@@ -45,12 +48,12 @@ class FileManager(private val filePath: String) {
val file = File(filePath) val file = File(filePath)
if (!file.exists()) { if (!file.exists()) {
System.err.println("Файл '$filePath' не найден. Коллекция будет пустой.") logger.warn{"There's no '$filePath'. Collection will be empty."}
return emptyList() return emptyList()
} }
if (!file.canRead()) { if (!file.canRead()) {
System.err.println("Нет прав на чтение файла '$filePath'. Коллекция будет пустой.") logger.warn{"Permission denied: '$filePath'. Collection will be empty."}
return emptyList() return emptyList()
} }
@@ -63,20 +66,23 @@ class FileManager(private val filePath: String) {
val line = scanner.nextLine().trim() val line = scanner.nextLine().trim()
lineNumber++ lineNumber++
// Пропускаем заголовок и пустые строки // skip header and empty lines
if (lineNumber == 1 && line.startsWith("id")) continue if (lineNumber == 1 && line.startsWith("id")) continue
if (line.isBlank()) continue if (line.isBlank()) continue
parseLine(line, lineNumber) val parsedLine = parseLine(line, lineNumber)
?.let { result.add(it) } if (parsedLine != null) {
?: System.err.println("Строка $lineNumber пропущена: некорректные данные → '$line'") result.add(parsedLine)
} else {
logger.warn { "Line $lineNumber of $filePath is skipped: invalid data → '$line'" }
}
} }
} }
} catch (e: FileNotFoundException) { } catch (e: FileNotFoundException) {
System.err.println("Файл не найден во время чтения: ${e.message}") logger.warn(e) {"File not found: ${e.message}"}
} }
println("Загружено элементов: ${result.size} из файла '$filePath'") logger.info {"${result.size} elements loaded from'$filePath'"}
return result return result
} }
@@ -91,7 +97,7 @@ class FileManager(private val filePath: String) {
return try { return try {
val parts = line.split(DELIMITER, limit = 12) val parts = line.split(DELIMITER, limit = 12)
if (parts.size < 12) { if (parts.size < 12) {
System.err.println("Строка $lineNumber: ожидалось 12 полей, найдено ${parts.size}") logger.warn {"Line $lineNumber: expected 12 fields, found ${parts.size}"}
return null return null
} }
@@ -111,24 +117,16 @@ class FileManager(private val filePath: String) {
} }
val carCool = parts[11].trim().toBoolean() val carCool = parts[11].trim().toBoolean()
// самая лучшая валидация (100%)
require(name.isNotBlank()) { "name не может быть пустым" } require(name.isNotBlank()) { "name не может быть пустым" }
// от такого может быть диабет
HumanBeing( HumanBeing(
id = id, name = name, coordinates = Coordinates(coordX, coordY), id = id, name = name, coordinates = Coordinates(coordX, coordY),
creationDate = creationDate, realHero = realHero, hasToothpick = hasToothpick, creationDate = creationDate, realHero = realHero, hasToothpick = hasToothpick,
impactSpeed = impactSpeed, soundtrackName = soundtrackName, impactSpeed = impactSpeed, soundtrackName = soundtrackName,
minutesOfWaiting = minutesOfWaiting, weaponType = weaponType, car = Car(carCool) minutesOfWaiting = minutesOfWaiting, weaponType = weaponType, car = Car(carCool)
) )
} catch (e: IllegalArgumentException) {
System.err.println("Строка $lineNumber — ошибка данных: ${e.message}")
null
} catch (e: DateTimeParseException) {
System.err.println("Строка $lineNumber — неверный формат даты: ${e.message}")
null
} catch (e: Exception) { } catch (e: Exception) {
System.err.println("Строка $lineNumber — неожиданная ошибка: ${e.message}") logger.warn(e) {"Line $lineNumber: ${e.message}"}
null null
} }
} }
@@ -143,7 +141,7 @@ class FileManager(private val filePath: String) {
val file = File(filePath) val file = File(filePath)
if (file.exists() && !file.canWrite()) { if (file.exists() && !file.canWrite()) {
System.err.println("Нет прав на запись в файл '$filePath'.") logger.warn{"Permission denied: '$filePath'."}
return false return false
} }
@@ -156,13 +154,13 @@ class FileManager(private val filePath: String) {
writer.write("\n") writer.write("\n")
} }
} }
println("Коллекция сохранена в '$filePath' (${items.size} элементов).") logger.info {"Collection saved to'$filePath' (${items.size} elements)."}
true true
} catch (e: SecurityException) { } catch (e: SecurityException) {
println("Отказано в доступе к файлу '$filePath': ${e.message}") logger.warn(e) {"Permission denied: '$filePath'."}
false false
} catch (e: Exception) { } catch (e: Exception) {
println("Ошибка записи в файл: ${e.message}") logger.warn(e) {"An error occurred: '$filePath'."}
false false
} }
} }
@@ -1,13 +1,13 @@
package network package network
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import lab6.prog.network.AppJson import mu.KotlinLogging
import runner.CommandInvoker import runner.CommandInvoker
import java.net.DatagramPacket import java.net.DatagramPacket
import java.net.DatagramSocket import java.net.DatagramSocket
import java.net.InetAddress import java.net.InetAddress
private const val BUFFER_SIZE = 65507 private val logger = KotlinLogging.logger {}
/** /**
* UDP-сервер на основе DatagramSocket (датаграммы). * UDP-сервер на основе DatagramSocket (датаграммы).
@@ -24,37 +24,30 @@ class NetworkManager(
private val socket = DatagramSocket(port) private val socket = DatagramSocket(port)
fun start() { fun start() {
println("[Server] Запущен на порту $port. Ожидание клиентов...") logger.info{"Server started on port $port. Waiting for connection..."}
val buf = ByteArray(BUFFER_SIZE) val buf = ByteArray(65507)
while (!socket.isClosed) { while (!socket.isClosed) {
try { try {
// 1. Принять датаграмму
val packet = DatagramPacket(buf, buf.size) val packet = DatagramPacket(buf, buf.size)
socket.receive(packet) socket.receive(packet)
val json = String(packet.data, 0, packet.length, Charsets.UTF_8) val json = String(packet.data, 0, packet.length, Charsets.UTF_8)
// 2. Десериализовать запрос
val request = try { val request = try {
AppJson.decodeFromString<Request>(json) AppJson.decodeFromString<Request>(json)
} catch (e: SerializationException) { } catch (e: SerializationException) {
System.err.println("[Server] Некорректный JSON: ${e.message}") logger.warn(e) { "Invalid request: ${e.message}" }
sendResponse(Response(false, "Некорректный формат запроса"), packet.address, packet.port) sendResponse(Response(false, "Некорректный формат запроса"), packet.address, packet.port)
continue continue
} }
logger.info{"'${request.commandName}' from ${packet.address}:${packet.port}."}
println("[Server] '${request.commandName}' от ${packet.address}:${packet.port}")
// 3. Выполнить и получить ответ
val response = invoker.execute(request) val response = invoker.execute(request)
// 4. Отправить ответ
sendResponse(response, packet.address, packet.port) sendResponse(response, packet.address, packet.port)
} catch (e: Exception) { } catch (e: Exception) {
if (!socket.isClosed) if (!socket.isClosed)
System.err.println("[Server] Ошибка: ${e.message}") logger.warn(e) { "Error: ${e.message}" }
} }
} }
} }
@@ -65,7 +58,7 @@ class NetworkManager(
} }
fun stop() { fun stop() {
println("[Server] Остановка...") logger.info{ "Shutting down..." }
socket.close() socket.close()
} }
} }
@@ -12,7 +12,7 @@ import network.Response
*/ */
class CommandInvoker { class CommandInvoker {
private val serverOnlyCommands = setOf("save") private val serverOnlyCommands = setOf("save", "sync-server")
private val commands = mutableMapOf<String, Command>() private val commands = mutableMapOf<String, Command>()
private val history = ArrayDeque<String>() private val history = ArrayDeque<String>()
@@ -31,8 +31,7 @@ class CommandInvoker {
addToHistory(request.commandName) addToHistory(request.commandName)
return try { return try {
val result = command.execute(request.args, request.humanBeing) command.execute(request.args, request.humanBeing)
Response(true, result)
} catch (e: Exception) { } catch (e: Exception) {
Response(false, "[Ошибка выполнения] ${e.message}") Response(false, "[Ошибка выполнения] ${e.message}")
} }
+7 -31
View File
@@ -1,44 +1,20 @@
<configuration debug="false" statusListenerClass="ch.qos.logback.core.status.NopStatusListener"> <configuration>
<appender name="server_log" class="ch.qos.logback.core.FileAppender">
<appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/server.log</file>
<file>logs/all.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/archived/all-%d{yyyy-MM-dd_HH-mm}.%i.log</fileNamePattern>
<maxFileSize>5MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder> <encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<file>logs/errors.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/archived/errors-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>2MB</maxFileSize>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder> <encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="DEBUG"> <root level="TRACE">
<appender-ref ref="ALL_FILE" /> <appender-ref ref="server_log" />
<appender-ref ref="ERROR_FILE" /> <appender-ref ref="console" />
</root> </root>
</configuration> </configuration>