Merge pull request #1 from yv1ing/dev

合并开发分支初代版本
This commit is contained in:
2025-10-09 11:10:45 +08:00
committed by GitHub
23 changed files with 832 additions and 16 deletions

21
.gitignore vendored
View File

@@ -1,32 +1,21 @@
# 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
*.exe~ *.exe~
*.dll *.dll
*.so *.so
*.dylib *.dylib
# Test binary, built with `go test -c`
*.test *.test
# Code coverage profiles and other test artifacts
*.out *.out
coverage.*
*.coverprofile *.coverprofile
coverage.*
profile.cov profile.cov
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work go.work
go.work.sum go.work.sum
# env file
.env .env
.idea/
.vscode/
# Editor/IDE *.log
# .idea/
# .vscode/

13
configs/config.toml Normal file
View File

@@ -0,0 +1,13 @@
# 系统运行设置
ListenHost = "127.0.0.1"
ListenPort = 8888
SecretKey = ""
RootName = "root"
RootPass = "123456"
[Mysql]
Host = ""
Port = 3306
Username = ""
Password = ""
Database = "ga"

48
go.mod Normal file
View File

@@ -0,0 +1,48 @@
module gin-admin
go 1.25.0
require (
github.com/BurntSushi/toml v1.5.0
github.com/gin-gonic/gin v1.10.1
github.com/golang-jwt/jwt/v5 v5.3.0
go.uber.org/zap v1.27.0
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.30.2
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/gin-contrib/sse v1.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.27.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/pretty v0.3.1 // 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.4 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

111
go.sum Normal file
View File

@@ -0,0 +1,111 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.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.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/gorm v1.30.2 h1:f7bevlVoVe4Byu3pmbWPVHnPsLoWaMjEb7/clyr9Ivs=
gorm.io/gorm v1.30.2/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=

View File

@@ -0,0 +1,58 @@
package system
import (
"github.com/gin-gonic/gin"
"net/http"
systemmodel "gin-admin/internal/model/system"
systemservice "gin-admin/internal/service/system"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 16:29
// @Desc: 系统用户服务接口
type SysUserLoginReq struct {
Username string `json:"username"`
Password string `json:"password"`
}
func SysUserLoginHandler(ctx *gin.Context) {
var req SysUserLoginReq
err := ctx.ShouldBindJSON(&req)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, systemmodel.Response{
Code: http.StatusBadRequest,
Info: "请求参数非法",
})
return
}
jwtToken, err := systemservice.SysUserLogin(req.Username, req.Password)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, systemmodel.Response{
Code: http.StatusInternalServerError,
Info: "登录请求失败:" + err.Error(),
})
return
}
if jwtToken == "" {
ctx.AbortWithStatusJSON(http.StatusForbidden, systemmodel.Response{
Code: http.StatusForbidden,
Info: "登录请求失败:账号或密码错误",
})
return
} else {
ctx.AbortWithStatusJSON(http.StatusOK, systemmodel.Response{
Code: http.StatusOK,
Info: "登录请求成功",
Data: gin.H{
"token": jwtToken,
},
})
return
}
}

View File

@@ -0,0 +1,49 @@
package core
import (
"fmt"
"gin-admin/internal/core/config"
"gin-admin/internal/core/initialize"
"gin-admin/pkg/logger"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 13:58
// @Desc: API服务入口
func Start() {
var err error
// 加载配置
err = initialize.InitConfig()
if err != nil {
logger.Error("加载配置失败:", err)
return
}
// 初始化数据库
err = initialize.InitDatabase()
if err != nil {
logger.Error("初始化数据库失败:", err)
return
}
// 初始化系统用户
err = initialize.InitSysUser()
if err != nil {
logger.Error("初始化系统用户失败:", err)
return
}
// 初始化引擎
eng := initialize.InitEngine()
addr := fmt.Sprintf("%s:%d", config.Config.ListenHost, config.Config.ListenPort)
// 启动引擎
logger.Info("正在启动引擎,监听在:", addr)
err = eng.Run(addr)
if err != nil {
logger.Error("启动引擎失败:", err)
}
}

View File

@@ -0,0 +1,18 @@
package config
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 14:01
// @Desc: 系统全局配置
type globalConfig struct {
ListenHost string
ListenPort uint
SecretKey string
RootName string
RootPass string
Mysql mysqlConfig
}
var Config *globalConfig

View File

@@ -0,0 +1,14 @@
package config
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 14:24
// @Desc: MySQL配置
type mysqlConfig struct {
Host string
Port uint
Username string
Password string
Database string
}

View File

@@ -0,0 +1,20 @@
package initialize
import (
"gin-admin/internal/core/config"
"github.com/BurntSushi/toml"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 14:06
// @Desc: 初始化系统配置
func InitConfig() error {
_, err := toml.DecodeFile("configs/config.toml", &config.Config)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,50 @@
package initialize
import (
"fmt"
"gin-admin/internal/core/config"
"gin-admin/internal/repository"
"gorm.io/driver/mysql"
"gorm.io/gorm"
systemmodel "gin-admin/internal/model/system"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 14:22
// @Desc: 初始化mysql数据库
func dsn() string {
return fmt.Sprintf(
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
config.Config.Mysql.Username,
config.Config.Mysql.Password,
config.Config.Mysql.Host,
config.Config.Mysql.Port,
config.Config.Mysql.Database,
)
}
func recreateTables(db *gorm.DB, models ...interface{}) error {
err := db.Migrator().DropTable(models...)
if err != nil {
return err
}
return db.AutoMigrate(models...)
}
func InitDatabase() error {
db, err := gorm.Open(mysql.Open(dsn()), &gorm.Config{})
if err != nil {
return err
}
err = recreateTables(
db,
&systemmodel.User{},
)
repository.SetupRepository(db)
return nil
}

View File

@@ -0,0 +1,20 @@
package initialize
import (
"gin-admin/internal/router"
"github.com/gin-gonic/gin"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 13:59
// @Desc: 初始化gin引擎
func InitEngine() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
eng := gin.New()
router.SetupRoutes(eng)
return eng
}

View File

@@ -0,0 +1,26 @@
package initialize
import (
"gin-admin/internal/core/config"
"gin-admin/internal/repository"
"gin-admin/pkg/encrypt"
systemmodel "gin-admin/internal/model/system"
systemrepository "gin-admin/internal/repository/system"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 17:05
// @Desc: 初始化系统用户
func InitSysUser() error {
return systemrepository.CreateUser(repository.Repo.DB, &systemmodel.User{
IsActive: true,
Username: config.Config.RootName,
Password: encrypt.Sha256String(config.Config.RootPass, config.Config.SecretKey),
Avatar: "/logo.png",
Email: "root@gin-admin.cn",
Phone: "18888888888",
})
}

View File

@@ -0,0 +1,81 @@
package middleware
import (
"errors"
"gin-admin/internal/core/config"
"gin-admin/pkg/auth"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"net/http"
"regexp"
"strings"
systemmodel "gin-admin/internal/model/system"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 17:31
// @Desc: 鉴权中间件
func extractBearerToken(c *gin.Context) string {
authorization := c.GetHeader("Authorization")
if authorization == "" {
return ""
}
parts := strings.SplitN(authorization, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") || parts[1] == "" {
return ""
}
return parts[1]
}
func JwtMiddleware(whitelist []string) gin.HandlerFunc {
var whitelistRegex []*regexp.Regexp
for _, pattern := range whitelist {
re, err := regexp.Compile(pattern)
if err == nil {
whitelistRegex = append(whitelistRegex, re)
}
}
return func(c *gin.Context) {
path := c.Request.URL.Path
for _, re := range whitelistRegex {
if re.MatchString(path) {
c.Next()
return
}
}
tokenStr := extractBearerToken(c)
if tokenStr == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, systemmodel.Response{
Code: http.StatusUnauthorized,
Info: "请求头Authorization非法或缺失",
})
return
}
claims, err := auth.ParseAccessToken(tokenStr, config.Config.SecretKey)
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
c.AbortWithStatusJSON(http.StatusUnauthorized, systemmodel.Response{
Code: http.StatusUnauthorized,
Info: "Token已过期",
})
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, systemmodel.Response{
Code: http.StatusUnauthorized,
Info: "Token不合法",
})
}
return
}
c.Set("UID", claims.ID)
c.Next()
}
}

View File

@@ -0,0 +1,12 @@
package system
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 16:00
// @Desc: 系统基础数据模型
type Response struct {
Code int `json:"code"`
Info string `json:"info"`
Data any `json:"data"`
}

View File

@@ -0,0 +1,19 @@
package system
import "gorm.io/gorm"
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 15:46
// @Desc: 系统用户角色模型
type User struct {
gorm.Model
IsActive bool `gorm:"default:true"`
Username string `gorm:"uniqueIndex;size:255"`
Password string
Avatar string
Email string
Phone string
}

View File

@@ -0,0 +1,22 @@
package repository
import (
"gorm.io/gorm"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 14:34
// @Desc: 数据操作的抽象封装
type repository struct {
DB *gorm.DB
}
var Repo *repository
func SetupRepository(db *gorm.DB) {
Repo = &repository{
DB: db,
}
}

View File

@@ -0,0 +1,42 @@
package system
import (
"gorm.io/gorm"
systemmodel "gin-admin/internal/model/system"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 16:33
// @Desc: 系统用户数据库操作实现
func CreateUser(db *gorm.DB, user *systemmodel.User) error {
return db.Create(user).Error
}
func DeleteUser(db *gorm.DB, id uint) error {
return db.Delete(&systemmodel.User{}, id).Error
}
func UpdateUser(db *gorm.DB, user *systemmodel.User) error {
return db.Save(user).Error
}
func GetUserByID(db *gorm.DB, id uint) (*systemmodel.User, error) {
var user systemmodel.User
err := db.First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
func GetUserByUsername(db *gorm.DB, username string) (*systemmodel.User, error) {
var user systemmodel.User
err := db.Where("username = ?", username).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}

26
internal/router/router.go Normal file
View File

@@ -0,0 +1,26 @@
package router
import (
"gin-admin/internal/middleware"
"github.com/gin-gonic/gin"
systemapi "gin-admin/internal/api/system"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 16:17
// @Desc: 路由注册入口
var whitelist = []string{
`^/api/sys/login$`,
}
func SetupRoutes(eng *gin.Engine) {
eng.Use(middleware.JwtMiddleware(whitelist))
api := eng.Group("/api")
/* 系统内置接口 */
api.POST("/sys/login", systemapi.SysUserLoginHandler)
}

View File

@@ -0,0 +1,40 @@
package system
import (
"errors"
"gin-admin/internal/core/config"
"gin-admin/internal/repository"
"gin-admin/pkg/auth"
"gin-admin/pkg/encrypt"
"gorm.io/gorm"
systemrepository "gin-admin/internal/repository/system"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 16:30
// @Desc: 系统用户服务实现
// SysUserLogin 系统用户登录
func SysUserLogin(username, password string) (string, error) {
user, err := systemrepository.GetUserByUsername(repository.Repo.DB, username)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return "", errors.New("系统内部错误")
}
if errors.Is(err, gorm.ErrRecordNotFound) || encrypt.Sha256String(password, config.Config.SecretKey) != user.Password {
return "", nil
}
if !user.IsActive {
return "", errors.New("用户已被禁用")
}
jwtToken, err := auth.CreateAccessToken(user.ID, user.Username, config.Config.SecretKey)
if err != nil {
return "", errors.New("系统内部错误")
}
return jwtToken, nil
}

16
main.go Normal file
View File

@@ -0,0 +1,16 @@
package main
import (
"gin-admin/internal/core"
"gin-admin/pkg/logger"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 14:20
// @Desc: 程序主入口
func main() {
logger.SetupLogger("app.log")
core.Start()
}

54
pkg/auth/jwt.go Normal file
View File

@@ -0,0 +1,54 @@
package auth
import (
"errors"
"fmt"
"github.com/golang-jwt/jwt/v5"
"time"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 15:54
// @Desc: jwt鉴权方法
type AccessClaims struct {
ID uint `json:"id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
func CreateAccessToken(ID uint, username string, secretKey string) (string, error) {
jwtSecret := []byte(secretKey)
claims := AccessClaims{
ID: ID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
Subject: "gin-admin",
IssuedAt: jwt.NewNumericDate(time.Now()),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(12 * time.Hour)),
ID: fmt.Sprintf("%d-%d", ID, time.Now().UnixNano()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
func ParseAccessToken(tokenStr, secretKey string) (*AccessClaims, error) {
jwtSecret := []byte(secretKey)
token, err := jwt.ParseWithClaims(tokenStr, &AccessClaims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*AccessClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("token非法")
}

22
pkg/encrypt/sha256.go Normal file
View File

@@ -0,0 +1,22 @@
package encrypt
import (
"crypto/sha256"
"encoding/hex"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 17:15
// @Desc: 计算Sha256哈希值
func Sha256String(text, salt string) string {
textBytes := []byte(text)
saltBytes := []byte(salt)
hash := sha256.New()
hash.Write(textBytes)
hash.Write(saltBytes)
return hex.EncodeToString(hash.Sum(nil))
}

66
pkg/logger/logger.go Normal file
View File

@@ -0,0 +1,66 @@
package logger
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"sync"
)
// @Author: yv1ing
// @Author: me@yvling.cn
// @Date: 2025/8/28 14:53
// @Desc: 日志器封装
var (
sugar *zap.SugaredLogger
baseLogger *zap.Logger
initOnce sync.Once
)
func SetupLogger(filename string) {
initOnce.Do(func() {
var syncers []zapcore.WriteSyncer
syncers = append(syncers, zapcore.AddSync(os.Stdout))
if filename != "" {
f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
syncers = append(syncers, zapcore.AddSync(f))
}
}
multi := zapcore.NewMultiWriteSyncer(syncers...)
encCfg := zap.NewProductionEncoderConfig()
encCfg.TimeKey = "time"
encCfg.LevelKey = "level"
encCfg.CallerKey = "caller"
encCfg.EncodeTime = zapcore.ISO8601TimeEncoder
encCfg.EncodeLevel = zapcore.CapitalLevelEncoder
encCfg.EncodeCaller = zapcore.ShortCallerEncoder
encoder := zapcore.NewConsoleEncoder(encCfg)
core := zapcore.NewCore(encoder, multi, zapcore.DebugLevel)
baseLogger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
sugar = baseLogger.Sugar()
})
}
func Close() {
if baseLogger != nil {
_ = baseLogger.Sync()
}
}
func Debug(args ...any) { sugar.Debug(args...) }
func Info(args ...any) { sugar.Info(args...) }
func Warn(args ...any) { sugar.Warn(args...) }
func Error(args ...any) { sugar.Error(args...) }
func Debugf(template string, args ...any) { sugar.Debugf(template, args...) }
func Infof(template string, args ...any) { sugar.Infof(template, args...) }
func Warnf(template string, args ...any) { sugar.Warnf(template, args...) }
func Errorf(template string, args ...any) { sugar.Errorf(template, args...) }