commit e5e02d24309e3d3e87549ccae2b25d4030d49369 Author: lc-official Date: Wed Jul 9 03:03:01 2025 +0800 初始化提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b8227f --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ +### Go template +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +### GoLand template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +build/ +build/** \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/coreapp.iml b/.idea/coreapp.iml new file mode 100644 index 0000000..2d5c520 --- /dev/null +++ b/.idea/coreapp.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..64effed --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..640485b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5bfe15b --- /dev/null +++ b/go.mod @@ -0,0 +1,37 @@ +module coreapp + +go 1.24 + +require ( + github.com/gin-gonic/gin v1.10.1 + github.com/gorilla/websocket v1.5.3 + google.golang.org/protobuf v1.36.6 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..24201c2 --- /dev/null +++ b/go.sum @@ -0,0 +1,93 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..7594171 --- /dev/null +++ b/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "coreapp/router" + "flag" + "fmt" + "github.com/gin-gonic/gin" + "os" + "os/signal" + "syscall" +) + +func main() { + fPort := flag.Int("port", 8080, "http server port") + + flag.Parse() + + server := gin.Default() + router.Route(server) + go func() { + err := server.Run(fmt.Sprintf(":%d", *fPort)) + if err != nil { + panic(err) + } + }() + + defer exit() + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) + <-signalChan +} + +func exit() { + +} diff --git a/proto/server.proto b/proto/server.proto new file mode 100644 index 0000000..73b0721 --- /dev/null +++ b/proto/server.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; +option go_package = "router/minecraft"; + +enum ServerPacketType { + PING = 0; + PONG = 1; + NEW = 2; + DELETE = 3; + CONNECT = 4; +} + +message ServerPacket { + ServerPacketType typ = 1; + optional bytes payload = 2; +} + +message ServerNewPacket { + string serverId = 1; + string name = 2; + string host = 3; + int32 port = 4; + string motd = 5; + string group = 6; +} + +message ServerDeletePacket { + string serverId = 1; +} + +message ServerConnectPacket { + string serverId = 1; + string player = 2; +} \ No newline at end of file diff --git a/router/minecraft/server.pb.go b/router/minecraft/server.pb.go new file mode 100644 index 0000000..5bd90b1 --- /dev/null +++ b/router/minecraft/server.pb.go @@ -0,0 +1,395 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v5.29.3 +// source: server.proto + +package minecraft + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ServerPacketType int32 + +const ( + ServerPacketType_PING ServerPacketType = 0 + ServerPacketType_PONG ServerPacketType = 1 + ServerPacketType_NEW ServerPacketType = 2 + ServerPacketType_DELETE ServerPacketType = 3 + ServerPacketType_CONNECT ServerPacketType = 4 +) + +// Enum value maps for ServerPacketType. +var ( + ServerPacketType_name = map[int32]string{ + 0: "PING", + 1: "PONG", + 2: "NEW", + 3: "DELETE", + 4: "CONNECT", + } + ServerPacketType_value = map[string]int32{ + "PING": 0, + "PONG": 1, + "NEW": 2, + "DELETE": 3, + "CONNECT": 4, + } +) + +func (x ServerPacketType) Enum() *ServerPacketType { + p := new(ServerPacketType) + *p = x + return p +} + +func (x ServerPacketType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ServerPacketType) Descriptor() protoreflect.EnumDescriptor { + return file_server_proto_enumTypes[0].Descriptor() +} + +func (ServerPacketType) Type() protoreflect.EnumType { + return &file_server_proto_enumTypes[0] +} + +func (x ServerPacketType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ServerPacketType.Descriptor instead. +func (ServerPacketType) EnumDescriptor() ([]byte, []int) { + return file_server_proto_rawDescGZIP(), []int{0} +} + +type ServerPacket struct { + state protoimpl.MessageState `protogen:"open.v1"` + Typ ServerPacketType `protobuf:"varint,1,opt,name=typ,proto3,enum=ServerPacketType" json:"typ,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3,oneof" json:"payload,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServerPacket) Reset() { + *x = ServerPacket{} + mi := &file_server_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServerPacket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerPacket) ProtoMessage() {} + +func (x *ServerPacket) ProtoReflect() protoreflect.Message { + mi := &file_server_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerPacket.ProtoReflect.Descriptor instead. +func (*ServerPacket) Descriptor() ([]byte, []int) { + return file_server_proto_rawDescGZIP(), []int{0} +} + +func (x *ServerPacket) GetTyp() ServerPacketType { + if x != nil { + return x.Typ + } + return ServerPacketType_PING +} + +func (x *ServerPacket) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +type ServerNewPacket struct { + state protoimpl.MessageState `protogen:"open.v1"` + ServerId string `protobuf:"bytes,1,opt,name=serverId,proto3" json:"serverId,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"` + Port int32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"` + Motd string `protobuf:"bytes,5,opt,name=motd,proto3" json:"motd,omitempty"` + Group string `protobuf:"bytes,6,opt,name=group,proto3" json:"group,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServerNewPacket) Reset() { + *x = ServerNewPacket{} + mi := &file_server_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServerNewPacket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerNewPacket) ProtoMessage() {} + +func (x *ServerNewPacket) ProtoReflect() protoreflect.Message { + mi := &file_server_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerNewPacket.ProtoReflect.Descriptor instead. +func (*ServerNewPacket) Descriptor() ([]byte, []int) { + return file_server_proto_rawDescGZIP(), []int{1} +} + +func (x *ServerNewPacket) GetServerId() string { + if x != nil { + return x.ServerId + } + return "" +} + +func (x *ServerNewPacket) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ServerNewPacket) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *ServerNewPacket) GetPort() int32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *ServerNewPacket) GetMotd() string { + if x != nil { + return x.Motd + } + return "" +} + +func (x *ServerNewPacket) GetGroup() string { + if x != nil { + return x.Group + } + return "" +} + +type ServerDeletePacket struct { + state protoimpl.MessageState `protogen:"open.v1"` + ServerId string `protobuf:"bytes,1,opt,name=serverId,proto3" json:"serverId,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServerDeletePacket) Reset() { + *x = ServerDeletePacket{} + mi := &file_server_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServerDeletePacket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerDeletePacket) ProtoMessage() {} + +func (x *ServerDeletePacket) ProtoReflect() protoreflect.Message { + mi := &file_server_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerDeletePacket.ProtoReflect.Descriptor instead. +func (*ServerDeletePacket) Descriptor() ([]byte, []int) { + return file_server_proto_rawDescGZIP(), []int{2} +} + +func (x *ServerDeletePacket) GetServerId() string { + if x != nil { + return x.ServerId + } + return "" +} + +type ServerConnectPacket struct { + state protoimpl.MessageState `protogen:"open.v1"` + ServerId string `protobuf:"bytes,1,opt,name=serverId,proto3" json:"serverId,omitempty"` + Player string `protobuf:"bytes,2,opt,name=player,proto3" json:"player,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ServerConnectPacket) Reset() { + *x = ServerConnectPacket{} + mi := &file_server_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ServerConnectPacket) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServerConnectPacket) ProtoMessage() {} + +func (x *ServerConnectPacket) ProtoReflect() protoreflect.Message { + mi := &file_server_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ServerConnectPacket.ProtoReflect.Descriptor instead. +func (*ServerConnectPacket) Descriptor() ([]byte, []int) { + return file_server_proto_rawDescGZIP(), []int{3} +} + +func (x *ServerConnectPacket) GetServerId() string { + if x != nil { + return x.ServerId + } + return "" +} + +func (x *ServerConnectPacket) GetPlayer() string { + if x != nil { + return x.Player + } + return "" +} + +var File_server_proto protoreflect.FileDescriptor + +const file_server_proto_rawDesc = "" + + "\n" + + "\fserver.proto\"^\n" + + "\fServerPacket\x12#\n" + + "\x03typ\x18\x01 \x01(\x0e2\x11.ServerPacketTypeR\x03typ\x12\x1d\n" + + "\apayload\x18\x02 \x01(\fH\x00R\apayload\x88\x01\x01B\n" + + "\n" + + "\b_payload\"\x93\x01\n" + + "\x0fServerNewPacket\x12\x1a\n" + + "\bserverId\x18\x01 \x01(\tR\bserverId\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + + "\x04host\x18\x03 \x01(\tR\x04host\x12\x12\n" + + "\x04port\x18\x04 \x01(\x05R\x04port\x12\x12\n" + + "\x04motd\x18\x05 \x01(\tR\x04motd\x12\x14\n" + + "\x05group\x18\x06 \x01(\tR\x05group\"0\n" + + "\x12ServerDeletePacket\x12\x1a\n" + + "\bserverId\x18\x01 \x01(\tR\bserverId\"I\n" + + "\x13ServerConnectPacket\x12\x1a\n" + + "\bserverId\x18\x01 \x01(\tR\bserverId\x12\x16\n" + + "\x06player\x18\x02 \x01(\tR\x06player*H\n" + + "\x10ServerPacketType\x12\b\n" + + "\x04PING\x10\x00\x12\b\n" + + "\x04PONG\x10\x01\x12\a\n" + + "\x03NEW\x10\x02\x12\n" + + "\n" + + "\x06DELETE\x10\x03\x12\v\n" + + "\aCONNECT\x10\x04B\x12Z\x10router/minecraftb\x06proto3" + +var ( + file_server_proto_rawDescOnce sync.Once + file_server_proto_rawDescData []byte +) + +func file_server_proto_rawDescGZIP() []byte { + file_server_proto_rawDescOnce.Do(func() { + file_server_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_server_proto_rawDesc), len(file_server_proto_rawDesc))) + }) + return file_server_proto_rawDescData +} + +var file_server_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_server_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_server_proto_goTypes = []any{ + (ServerPacketType)(0), // 0: ServerPacketType + (*ServerPacket)(nil), // 1: ServerPacket + (*ServerNewPacket)(nil), // 2: ServerNewPacket + (*ServerDeletePacket)(nil), // 3: ServerDeletePacket + (*ServerConnectPacket)(nil), // 4: ServerConnectPacket +} +var file_server_proto_depIdxs = []int32{ + 0, // 0: ServerPacket.typ:type_name -> ServerPacketType + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_server_proto_init() } +func file_server_proto_init() { + if File_server_proto != nil { + return + } + file_server_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_server_proto_rawDesc), len(file_server_proto_rawDesc)), + NumEnums: 1, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_server_proto_goTypes, + DependencyIndexes: file_server_proto_depIdxs, + EnumInfos: file_server_proto_enumTypes, + MessageInfos: file_server_proto_msgTypes, + }.Build() + File_server_proto = out.File + file_server_proto_goTypes = nil + file_server_proto_depIdxs = nil +} diff --git a/router/minecraft/servers.go b/router/minecraft/servers.go new file mode 100644 index 0000000..820ad83 --- /dev/null +++ b/router/minecraft/servers.go @@ -0,0 +1,365 @@ +package minecraft + +import ( + "coreapp/util" + "coreapp/util/ws" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "google.golang.org/protobuf/proto" + "math/rand" + "net/http" + "strings" + "sync" + "time" +) + +type ServerType string + +const ( + ServerTypeUnknown ServerType = "" + ServerTypeLobby ServerType = "LOBBY" + ServerTypeRoom ServerType = "ROOM" + ServerTypeLimbo ServerType = "LIMBO" + ServerTypeSystem ServerType = "SYSTEM" +) + +func (typ ServerType) Prefix() string { + switch typ { + case ServerTypeLobby: + return "L" + case ServerTypeRoom: + return "mini" + case ServerTypeLimbo: + return "" + case ServerTypeSystem: + return "S" + case ServerTypeUnknown: + default: + return "U" + } + return "U" +} + +type Server struct { + Id string `json:"id"` + Name string `json:"name"` + Group string `json:"group"` + Host string `json:"host"` + Port int `json:"port"` + Type ServerType `json:"type"` + Motd string `json:"motd"` + LastHB time.Time +} + +var servers = sync.Map{} +var serverProxyConn *ws.Conn +var serverProxyLastHB time.Time + +func ServerMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + if strings.HasSuffix(c.Request.URL.Path, "ws") || (c.Request.Method == "POST" && !strings.HasSuffix(c.Request.URL.Path, "/register")) { + if c.GetHeader("Authorization") != "NEXTCRAFT" { + c.AbortWithStatusJSON(401, gin.H{ + "code": 1000, + "message": "Who are you?", + }) + return + } + serverId := c.Query("id") + if serverId == "" { + c.AbortWithStatusJSON(400, gin.H{ + "code": -1, + "message": "请提供服务器ID", + }) + return + } + if serverId != "PROXY" { + if _, ok := servers.Load(serverId); !ok { + c.AbortWithStatusJSON(404, gin.H{ + "code": -1, + "message": "服务器不存在", + }) + return + } + } + c.Set("serverId", serverId) + } + } +} + +func ServerList(c *gin.Context) { + serverList := make(map[string]*Server) + + // 遍历 sync.Map + servers.Range(func(key, value interface{}) bool { + serverList[key.(string)] = value.(*Server) + return true // 继续遍历 + }) + + c.JSON(200, gin.H{ + "code": 0, + "message": "success", + "data": serverList, + }) +} + +type ServerRegisterData struct { + Name string `json:"name"` + Host string `json:"host"` + Port int `json:"port"` + Type ServerType `json:"type"` + Group string `json:"group"` + Motd string `json:"motd"` +} + +func ServerRegister(c *gin.Context) { + data := &ServerRegisterData{} + if err := c.ShouldBindJSON(data); err != nil { + c.JSON(400, gin.H{ + "code": -1, + "message": "请提供正确的注册信息", + }) + return + } + + if data.Name == "" { + data.Name = "AUTO" + } + + if data.Type == "" { + c.JSON(400, gin.H{ + "code": -1, + "message": "请提供正确的服务器类型{LOBBY, ROOM, SYSTEM, LIMBO, UNKNOWN}", + }) + return + } + + if data.Host == "" || data.Port == 0 { + c.JSON(400, gin.H{ + "code": -1, + "message": "请提供正确的服务器IP和端口", + }) + return + } + + if data.Group == "" { + data.Group = "default" + } + + server := &Server{ + Id: generateServerId(data.Type.Prefix()), + Host: data.Host, + Port: data.Port, + Type: data.Type, + Group: data.Group, + LastHB: time.Now(), + } + + if data.Name == "AUTO" { + server.Name = server.Id + } else { + server.Name = data.Name + } + + servers.Store(server.Id, server) // 使用 Store 替代原来的 map 写入 + + newPkt := &ServerNewPacket{ + ServerId: server.Id, + Name: server.Name, + Host: server.Host, + Port: int32(server.Port), + Group: server.Group, + Motd: server.Motd, + } + + if serverProxyConn != nil { + buf, err := serverWrapPacket(ServerPacketType_NEW, newPkt) + if err != nil { + panic(err) + } + serverProxyConn.WriteChan <- ws.Binary(buf) + } + + c.JSON(200, gin.H{ + "code": 0, + "message": "success", + "data": server, + }) +} + +func ServerUnregister(c *gin.Context) { + serverId := c.GetString("serverId") + deletePkt := &ServerDeletePacket{ + ServerId: serverId, + } + if serverProxyConn != nil { + buf, err := serverWrapPacket(ServerPacketType_DELETE, deletePkt) + if err != nil { + panic(err) + } + serverProxyConn.WriteChan <- ws.Binary(buf) + } + servers.Delete(serverId) // 直接删除 + c.JSON(200, gin.H{ + "code": 0, + "message": "success", + }) +} + +// ServerMessage 函数用于处理服务器消息 +func ServerMessage(c *gin.Context) { + conn, err := serverUpgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": -666, + "message": "WebSocket升级失败", + }) + panic(c.Error(err)) + return + } + wrapped := ws.Wrap(conn) + go wrapped.ReadLoop() + go wrapped.WriteLoop() + if c.GetString("serverId") == "PROXY" { + serverProxyConn = wrapped + serverProxyLastHB = time.Now() + go serverProxyReadLoop(wrapped) + } else { + go serverReadLoop(wrapped, c.GetString("serverId")) + } +} + +func serverReadLoop(conn *ws.Conn, serverId string) { + for { + // 使用 Load 读取,避免并发问题 + serverVal, ok := servers.Load(serverId) + if !ok { + conn.Close() + return + } + server := serverVal.(*Server) + + select { + case msg := <-conn.ReadChan: + if msg == nil { + return + } + pkt := &ServerPacket{} + err := proto.Unmarshal(msg, pkt) + if err != nil { + panic(err) + } + switch pkt.GetTyp() { + case ServerPacketType_PING: + // 更新 LastHB(由于 Server 本身是结构体指针,可以直接修改) + server.LastHB = time.Now() + + resp := &ServerPacket{ + Typ: ServerPacketType_PONG, + } + buf, err := proto.Marshal(resp) + if err != nil { + panic(err) + } + conn.WriteChan <- ws.Binary(buf) + case ServerPacketType_CONNECT: + if serverProxyConn != nil { + serverProxyConn.WriteChan <- ws.Binary(msg) + } + } + default: + if conn.IsClosed() { + return + } + } + } +} + +func serverProxyReadLoop(conn *ws.Conn) { + defer func() { + serverProxyConn = nil + }() + for { + select { + case msg := <-conn.ReadChan: + if msg == nil { + return + } + pkt := &ServerPacket{} + err := proto.Unmarshal(msg, pkt) + if err != nil { + panic(err) + } + switch pkt.GetTyp() { + case ServerPacketType_PING: + serverProxyLastHB = time.Now() + resp := &ServerPacket{ + Typ: ServerPacketType_PONG, + } + buf, err := proto.Marshal(resp) + if err != nil { + panic(err) + } + conn.WriteChan <- ws.Binary(buf) + } + } + } +} + +var serverUpgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +func generateServerId(prefix string) string { + return string(prefix) + util.RandStringNumber(rand.Intn(2)+1) + strings.ToUpper(util.RandStringAlphabet(rand.Intn(2)+1)) +} + +var serverHBTicker = time.NewTicker(time.Second * 5) + +func init() { + go func() { + for range serverHBTicker.C { + // 遍历所有服务器,检查心跳 + servers.Range(func(key, value interface{}) bool { + server := value.(*Server) + if time.Since(server.LastHB) > time.Second*10 { + deletePkt := &ServerDeletePacket{ + ServerId: server.Id, + } + if serverProxyConn != nil { + buf, err := serverWrapPacket(ServerPacketType_DELETE, deletePkt) + if err != nil { + panic(err) + } + serverProxyConn.WriteChan <- ws.Binary(buf) + } + servers.Delete(key) // 超时则删除 + } + return true // 继续遍历 + }) + if time.Since(serverProxyLastHB) > time.Second*10 && serverProxyConn != nil { + serverProxyConn.Close() + } + } + }() +} + +func serverWrapPacket(typ ServerPacketType, payload proto.Message) ([]byte, error) { + pkt := &ServerPacket{ + Typ: typ, + Payload: nil, + } + if payload != nil { + buf, err := proto.Marshal(payload) + if err != nil { + return nil, err + } + pkt.Payload = buf + } + buf, err := proto.Marshal(pkt) + if err != nil { + return nil, err + } + return buf, nil +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..c8e213c --- /dev/null +++ b/router/router.go @@ -0,0 +1,20 @@ +package router + +import ( + "coreapp/router/minecraft" + "github.com/gin-gonic/gin" +) + +func Route(server *gin.Engine) { + rootGroup := server.Group("/") + + minecraftGroup := rootGroup.Group("/minecraft") + { + servers := minecraftGroup.Group("/servers") + servers.Use(minecraft.ServerMiddleware()) + servers.GET("/list", minecraft.ServerList) + servers.POST("/register", minecraft.ServerRegister) + servers.POST("/unregister", minecraft.ServerUnregister) + servers.GET("/ws", minecraft.ServerMessage) + } +} diff --git a/util/strings.go b/util/strings.go new file mode 100644 index 0000000..89014ae --- /dev/null +++ b/util/strings.go @@ -0,0 +1,30 @@ +package util + +import "math/rand" + +const ( + alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + number = "0123456789" + alphanumeric = alphabet + number +) + +func RandStringAlphaNumeric(length int) (str string) { + for i := 0; i < length; i++ { + str += string(alphanumeric[rand.Intn(len(alphanumeric))]) + } + return +} + +func RandStringAlphabet(length int) (str string) { + for i := 0; i < length; i++ { + str += string(alphabet[rand.Intn(len(alphabet))]) + } + return +} + +func RandStringNumber(length int) (str string) { + for i := 0; i < length; i++ { + str += string(number[rand.Intn(len(number))]) + } + return +} diff --git a/util/ws/websocket.go b/util/ws/websocket.go new file mode 100644 index 0000000..7d35d6a --- /dev/null +++ b/util/ws/websocket.go @@ -0,0 +1,76 @@ +package ws + +import "github.com/gorilla/websocket" + +type Packet struct { + isBinary bool + data []byte +} + +func Text(buf []byte) *Packet { + return &Packet{ + isBinary: false, + data: buf, + } +} + +func Binary(buf []byte) *Packet { + return &Packet{ + isBinary: true, + data: buf, + } +} + +type Conn struct { + WriteChan chan *Packet + ReadChan chan []byte + conn *websocket.Conn + close bool +} + +func Wrap(conn *websocket.Conn) *Conn { + return &Conn{ + WriteChan: make(chan *Packet), + ReadChan: make(chan []byte), + conn: conn, + } +} + +func (conn *Conn) ReadLoop() { + for !conn.close { + _, message, err := conn.conn.ReadMessage() + if err != nil { + break + } + conn.ReadChan <- message + } +} + +func (conn *Conn) WriteLoop() { + for { + select { + case message := <-conn.WriteChan: + typ := websocket.TextMessage + if message.isBinary { + typ = websocket.BinaryMessage + } + err := conn.conn.WriteMessage(typ, message.data) + if err != nil { + break + } + default: + if conn.close { + return + } + } + } +} + +func (conn *Conn) Close() error { + conn.close = true + return conn.conn.Close() +} + +func (conn *Conn) IsClosed() bool { + return conn.close +}