From d1a040617f570f168bf17a514e804dab959877d2 Mon Sep 17 00:00:00 2001 From: Snowykami Date: Tue, 22 Jul 2025 06:18:23 +0800 Subject: [PATCH] :zap: implement user authentication and database initialization, add models for user, comment, label, and OIDC configuration --- .gitignore | 5 +- cmd/server/main.go | 12 ++- go.mod | 28 ++++++- go.sum | 65 +++++++++++++++- internal/controller/v1/user.go | 8 +- internal/dto/dto.go | 7 ++ internal/dto/user.go | 33 ++++++++ internal/model/comment.go | 14 ++++ internal/model/file.go | 1 + internal/model/label.go | 10 +++ internal/model/oidc_config.go | 89 ++++++++++++++++++++++ internal/model/page.go | 1 + internal/model/post.go | 12 +++ internal/model/user.go | 15 ++++ internal/repo/init.go | 133 +++++++++++++++++++++++++++++++++ internal/repo/user.go | 45 +++++++++++ internal/router/router.go | 6 +- internal/service/user.go | 32 ++++++++ pkg/constant/constant.go | 8 +- pkg/resps/resps.go | 38 ++++++++++ pkg/resps/texts.go | 8 ++ pkg/utils/env.go | 10 ++- pkg/utils/password.go | 41 ++++++++++ 23 files changed, 602 insertions(+), 19 deletions(-) create mode 100644 internal/dto/dto.go create mode 100644 internal/dto/user.go create mode 100644 internal/model/comment.go create mode 100644 internal/model/file.go create mode 100644 internal/model/label.go create mode 100644 internal/model/oidc_config.go create mode 100644 internal/model/page.go create mode 100644 internal/model/post.go create mode 100644 internal/model/user.go create mode 100644 internal/repo/init.go create mode 100644 internal/repo/user.go create mode 100644 internal/service/user.go create mode 100644 pkg/resps/resps.go create mode 100644 pkg/resps/texts.go create mode 100644 pkg/utils/password.go diff --git a/.gitignore b/.gitignore index 94dcb33..1801170 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ # config ./configs -.env \ No newline at end of file +.env + +# data +./data \ No newline at end of file diff --git a/cmd/server/main.go b/cmd/server/main.go index 26519b5..e13de39 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,9 +1,17 @@ package main -import "github.com/snowykami/neo-blog/internal/router" +import ( + "github.com/snowykami/neo-blog/internal/repo" + "github.com/snowykami/neo-blog/internal/router" +) func main() { - err := router.Run() + err := repo.InitDatabase() + if err != nil { + panic(err) + } + + err = router.Run() if err != nil { panic(err) } diff --git a/go.mod b/go.mod index 87bfc9b..04d38eb 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,11 @@ go 1.23.3 require ( github.com/cloudwego/hertz v0.10.1 + github.com/glebarez/sqlite v1.11.0 github.com/joho/godotenv v1.5.1 + github.com/sirupsen/logrus v1.9.3 + gorm.io/driver/postgres v1.6.0 + gorm.io/gorm v1.30.0 ) require ( @@ -14,15 +18,35 @@ require ( github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/gopkg v0.1.4 // indirect github.com/cloudwego/netpoll v0.7.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/golang/protobuf v1.5.0 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/klauspost/cpuid/v2 v2.2.3 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/nyaruka/phonenumbers v1.0.55 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect - golang.org/x/sys v0.24.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.34.1 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.23.1 // indirect + resty.dev/v3 v3.0.0-beta.3 // indirect ) diff --git a/go.sum b/go.sum index fa1c2ac..951b4e8 100644 --- a/go.sum +++ b/go.sum @@ -18,30 +18,61 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 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/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/nyaruka/phonenumbers v1.0.55 h1:bj0nTO88Y68KeUQ/n3Lo2KgK7lM1hF7L9NFuwcCl3yg= github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= 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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 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= @@ -64,6 +95,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -73,22 +106,29 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -101,13 +141,16 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -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= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= @@ -116,4 +159,18 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 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/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +resty.dev/v3 v3.0.0-beta.3 h1:3kEwzEgCnnS6Ob4Emlk94t+I/gClyoah7SnNi67lt+E= +resty.dev/v3 v3.0.0-beta.3/go.mod h1:OgkqiPvTDtOuV4MGZuUDhwOpkY8enjOsjjMzeOHefy4= diff --git a/internal/controller/v1/user.go b/internal/controller/v1/user.go index 343042f..42bb509 100644 --- a/internal/controller/v1/user.go +++ b/internal/controller/v1/user.go @@ -3,6 +3,8 @@ package v1 import ( "context" "github.com/cloudwego/hertz/pkg/app" + "github.com/snowykami/neo-blog/internal/dto" + "github.com/snowykami/neo-blog/pkg/resps" ) type userType struct{} @@ -10,11 +12,13 @@ type userType struct{} var User = new(userType) func (u *userType) Login(ctx context.Context, c *app.RequestContext) { - // TODO: Impl + var userLoginReq dto.UserLoginReq + if err := c.BindAndValidate(&userLoginReq); err != nil { + resps.BadRequest(c, resps.ErrParamInvalid) + } } func (u *userType) Register(ctx context.Context, c *app.RequestContext) { - // TODO: Impl } func (u *userType) Logout(ctx context.Context, c *app.RequestContext) { diff --git a/internal/dto/dto.go b/internal/dto/dto.go new file mode 100644 index 0000000..a74ed41 --- /dev/null +++ b/internal/dto/dto.go @@ -0,0 +1,7 @@ +package dto + +type BaseResp struct { + Code int `json:"code"` + Message string `json:"message"` + Data any `json:"data"` +} diff --git a/internal/dto/user.go b/internal/dto/user.go new file mode 100644 index 0000000..4671a21 --- /dev/null +++ b/internal/dto/user.go @@ -0,0 +1,33 @@ +package dto + +type UserDto struct { + Username string `json:"username"` // 用户名 + Nickname string `json:"nickname"` + AvatarUrl string `json:"avatar_url"` // 头像URL + Email string `json:"email"` // 邮箱 + Gender string `json:"gender"` + Role string `json:"role"` +} +type UserLoginReq struct { + Username string `json:"username"` // username or email + Password string `json:"password"` +} + +type UserLoginResp struct { + Token string `json:"token"` + RefreshToken string `json:"refresh_token"` + User UserDto `json:"user"` +} + +type UserRegisterReq struct { + Username string `json:"username"` // 用户名 + Nickname string `json:"nickname"` // 昵称 + Password string `json:"password"` // 密码 + Email string `json:"email"` // 邮箱 +} + +type UserRegisterResp struct { + Token string `json:"token"` // 访问令牌 + RefreshToken string `json:"refresh_token"` // 刷新令牌 + User UserDto `json:"user"` // 用户信息 +} diff --git a/internal/model/comment.go b/internal/model/comment.go new file mode 100644 index 0000000..2c63adc --- /dev/null +++ b/internal/model/comment.go @@ -0,0 +1,14 @@ +package model + +import "gorm.io/gorm" + +type Comment struct { + gorm.Model + UserID uint `gorm:"index"` // 评论的用户ID + User User `gorm:"foreignKey:UserID;references:ID"` // 关联的用户 + TargetID uint `gorm:"index"` // 目标ID + TargetType string `gorm:"index"` // 目标类型,如 "post", "page" + ReplyID uint `gorm:"index"` // 回复的评论ID + Content string `gorm:"type:text"` // 评论内容 + Depth int `gorm:"default:0"` // 评论的层级深度 +} diff --git a/internal/model/file.go b/internal/model/file.go new file mode 100644 index 0000000..8b53790 --- /dev/null +++ b/internal/model/file.go @@ -0,0 +1 @@ +package model diff --git a/internal/model/label.go b/internal/model/label.go new file mode 100644 index 0000000..a0831c4 --- /dev/null +++ b/internal/model/label.go @@ -0,0 +1,10 @@ +package model + +import "gorm.io/gorm" + +type Label struct { + gorm.Model + Key string `gorm:"uniqueIndex"` // 标签键,唯一标识 + Value string `gorm:"type:text"` // 标签值,描述标签的内容 + Color string `gorm:"type:text"` // 前端可用颜色代码 +} diff --git a/internal/model/oidc_config.go b/internal/model/oidc_config.go new file mode 100644 index 0000000..f372ebe --- /dev/null +++ b/internal/model/oidc_config.go @@ -0,0 +1,89 @@ +package model + +import ( + "fmt" + "gorm.io/gorm" + "resty.dev/v3" + "time" +) + +type OidcConfig struct { + gorm.Model + Name string `gorm:"uniqueIndex"` + ClientID string `gorm:"column:client_id"` // 客户端ID + ClientSecret string `gorm:"column:client_secret"` // 客户端密钥 + DisplayName string `gorm:"column:display_name"` // 显示名称,例如:轻雪通行证 + GroupsClaim *string `gorm:"default:groups"` // 组声明,默认为:"groups" + Icon *string `gorm:"column:icon"` // 图标url,为空则使用内置默认图标 + OidcDiscoveryUrl string `gorm:"column:oidc_discovery_url"` // OpenID自动发现URL,例如 :https://pass.liteyuki.icu/.well-known/openid-configuration + Enabled bool `gorm:"column:enabled;default:true"` // 是否启用 + // 以下字段为自动获取字段,每次更新配置时自动填充 + Issuer string + AuthorizationEndpoint string + TokenEndpoint string + UserInfoEndpoint string + JwksUri string +} + +type oidcDiscoveryResp struct { + Issuer string `json:"issuer" validate:"required"` + AuthorizationEndpoint string `json:"authorization_endpoint" validate:"required"` + TokenEndpoint string `json:"token_endpoint" validate:"required"` + UserInfoEndpoint string `json:"userinfo_endpoint" validate:"required"` + JwksUri string `json:"jwks_uri" validate:"required"` + // 可选字段 + RegistrationEndpoint string `json:"registration_endpoint,omitempty"` + ScopesSupported []string `json:"scopes_supported,omitempty"` + ResponseTypesSupported []string `json:"response_types_supported,omitempty"` + GrantTypesSupported []string `json:"grant_types_supported,omitempty"` + SubjectTypesSupported []string `json:"subject_types_supported,omitempty"` + IdTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"` + ClaimsSupported []string `json:"claims_supported,omitempty"` + EndSessionEndpoint string `json:"end_session_endpoint,omitempty"` +} + +func updateOidcConfigFromUrl(url string) (*oidcDiscoveryResp, error) { + client := resty.New() + client.SetTimeout(10 * time.Second) // 设置超时时间 + var discovery oidcDiscoveryResp + resp, err := client.R(). + SetHeader("Accept", "application/json"). + SetResult(&discovery). + Get(url) + if err != nil { + return nil, fmt.Errorf("请求OIDC发现端点失败: %w", err) + } + if resp.StatusCode() != 200 { + return nil, fmt.Errorf("请求OIDC发现端点失败,状态码: %d", resp.StatusCode()) + } + // 验证必要字段 + if discovery.Issuer == "" || + discovery.AuthorizationEndpoint == "" || + discovery.TokenEndpoint == "" || + discovery.UserInfoEndpoint == "" || + discovery.JwksUri == "" { + return nil, fmt.Errorf("OIDC发现端点响应缺少必要字段") + } + return &discovery, nil +} + +func (o *OidcConfig) BeforeSave(tx *gorm.DB) (err error) { + // 设置默认值 + if o.GroupsClaim == nil { + defaultGroupsClaim := "groups" + o.GroupsClaim = &defaultGroupsClaim + } + // 只有在创建新记录或更新 OidcDiscoveryUrl 字段时才更新端点信息 + if tx.Statement.Changed("OidcDiscoveryUrl") { + discoveryResp, err := updateOidcConfigFromUrl(o.OidcDiscoveryUrl) + if err != nil { + return fmt.Errorf("更新OIDC配置失败: %w", err) + } + o.Issuer = discoveryResp.Issuer + o.AuthorizationEndpoint = discoveryResp.AuthorizationEndpoint + o.TokenEndpoint = discoveryResp.TokenEndpoint + o.UserInfoEndpoint = discoveryResp.UserInfoEndpoint + o.JwksUri = discoveryResp.JwksUri + } + return nil +} diff --git a/internal/model/page.go b/internal/model/page.go new file mode 100644 index 0000000..8b53790 --- /dev/null +++ b/internal/model/page.go @@ -0,0 +1 @@ +package model diff --git a/internal/model/post.go b/internal/model/post.go new file mode 100644 index 0000000..6b4f224 --- /dev/null +++ b/internal/model/post.go @@ -0,0 +1,12 @@ +package model + +import "gorm.io/gorm" + +type Post struct { + gorm.Model + UserID uint `gorm:"index"` // 发布者的用户ID + User User `gorm:"foreignKey:UserID;references:ID"` // 关联的用户 + Title string `gorm:"type:text;not null"` // 帖子标题 + Content string `gorm:"type:text;not null"` // 帖子内容 + Labels []Label `gorm:"many2many:post_labels;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` // 关联的标签 +} diff --git a/internal/model/user.go b/internal/model/user.go new file mode 100644 index 0000000..ff531bb --- /dev/null +++ b/internal/model/user.go @@ -0,0 +1,15 @@ +package model + +import "gorm.io/gorm" + +type User struct { + gorm.Model + Username string `gorm:"unique;index"` // 用户名,唯一 + Nickname string + AvatarUrl string + Email string `gorm:"unique;index"` + Gender string + Role string `gorm:"default:'user'"` + + Password string // 密码,存储加密后的值 +} diff --git a/internal/repo/init.go b/internal/repo/init.go new file mode 100644 index 0000000..d82cdda --- /dev/null +++ b/internal/repo/init.go @@ -0,0 +1,133 @@ +package repo + +import ( + "errors" + "fmt" + "github.com/glebarez/sqlite" + "github.com/sirupsen/logrus" + "github.com/snowykami/neo-blog/internal/model" + "github.com/snowykami/neo-blog/pkg/utils" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "os" + "path/filepath" +) + +var db *gorm.DB + +func GetDB() *gorm.DB { + return db +} + +// DBConfig 数据库配置结构体 +type DBConfig struct { + Driver string // 数据库驱动类型,例如 "sqlite" 或 "postgres" Database driver type, e.g., "sqlite" or "postgres" + Path string // SQLite 路径 SQLite path + Host string // PostgreSQL 主机名 PostgreSQL hostname + Port int // PostgreSQL 端口 PostgreSQL port + User string // PostgreSQL 用户名 PostgreSQL username + Password string // PostgreSQL 密码 PostgreSQL password + DBName string // PostgreSQL 数据库名 PostgreSQL database name + SSLMode string // PostgreSQL SSL 模式 PostgreSQL SSL mode +} + +// loadDBConfig 从配置文件加载数据库配置 +func loadDBConfig() DBConfig { + return DBConfig{ + Driver: utils.Env.Get("database.driver", "sqlite"), + Path: utils.Env.Get("database.path", "./data/data.db"), + Host: utils.Env.Get("database.host", "postgres"), + Port: utils.Env.GetenvAsInt("database.port", 5432), + User: utils.Env.Get("database.user", "spage"), + Password: utils.Env.Get("database.password", "spage"), + DBName: utils.Env.Get("database.dbname", "spage"), + SSLMode: utils.Env.Get("database.sslmode", "disable"), + } +} + +// InitDatabase 手动初始化数据库连接 +func InitDatabase() error { + dbConfig := loadDBConfig() + // 创建通用的 GORM 配置 + gormConfig := &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + } + + var err error + + switch dbConfig.Driver { + case "postgres": + if db, err = initPostgres(dbConfig, gormConfig); err != nil { + return fmt.Errorf("postgres initialization failed: %w", err) + } + logrus.Infoln("postgres initialization succeeded", dbConfig) + case "sqlite": + if db, err = initSQLite(dbConfig.Path, gormConfig); err != nil { + return fmt.Errorf("sqlite initialization failed: %w", err) + } + logrus.Infoln("sqlite initialization succeeded", dbConfig) + default: + return errors.New("unsupported database driver, only sqlite and postgres are supported") + } + + return nil + // TODO: impl + + //// 迁移模型 + //if err = models.Migrate(db); err != nil { + // logrus.Error("Failed to migrate models:", err) + // return err + //} + //// 执行初始化数据 + //// 创建管理员账户 + //hashedPassword, err := utils.Password.HashPassword(config.AdminPassword, config.JwtSecret) + //if err != nil { + // logrus.Error("Failed to hash password:", err) + // return err + //} + //user := &models.User{ + // Name: config.AdminUsername, + // Password: &hashedPassword, + // Role: constants.GlobalRoleAdmin, + //} + //if err = User.UpdateSystemAdmin(user); err != nil { + // logrus.Error("Failed to update admin user:", err) + // return err + //} + //return nil +} + +// initPostgres 初始化PostgreSQL连接 +func initPostgres(config DBConfig, gormConfig *gorm.Config) (db *gorm.DB, err error) { + if config.Host == "" || config.User == "" || config.Password == "" || config.DBName == "" { + err = errors.New("PostgreSQL configuration is incomplete: host, user, password, and dbname are required") + } + + dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", + config.Host, config.Port, config.User, config.Password, config.DBName, config.SSLMode) + + db, err = gorm.Open(postgres.Open(dsn), gormConfig) + return +} + +// initSQLite 初始化 SQLite 连接 +func initSQLite(path string, gormConfig *gorm.Config) (*gorm.DB, error) { + if path == "" { + path = "./data/data.db" + } + // 创建 SQLite 数据库文件的目录 + if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { + return nil, fmt.Errorf("failed to create directory for SQLite database: %w", err) + } + db, err := gorm.Open(sqlite.Open(path), gormConfig) + return db, err +} + +func migrate() error { + return GetDB().AutoMigrate( + &model.Comment{}, + &model.Label{}, + &model.Post{}, + &model.User{}) +} diff --git a/internal/repo/user.go b/internal/repo/user.go new file mode 100644 index 0000000..4128d78 --- /dev/null +++ b/internal/repo/user.go @@ -0,0 +1,45 @@ +package repo + +import "github.com/snowykami/neo-blog/internal/model" + +type userRepo struct{} + +var User = &userRepo{} + +func (user *userRepo) GetByUsername(username string) (*model.User, error) { + var userModel model.User + if err := GetDB().Where("username = ?", username).First(&userModel).Error; err != nil { + return nil, err + } + return &userModel, nil +} + +func (user *userRepo) GetByEmail(email string) (*model.User, error) { + var userModel model.User + if err := GetDB().Where("email = ?", email).First(&userModel).Error; err != nil { + return nil, err + } + return &userModel, nil +} + +func (user *userRepo) GetByUsernameOrEmail(usernameOrEmail string) (*model.User, error) { + var userModel model.User + if err := GetDB().Where("username = ? OR email = ?", usernameOrEmail, usernameOrEmail).First(&userModel).Error; err != nil { + return nil, err + } + return &userModel, nil +} + +func (user *userRepo) Create(userModel *model.User) error { + if err := GetDB().Create(userModel).Error; err != nil { + return err + } + return nil +} + +func (user *userRepo) Update(userModel *model.User) error { + if err := GetDB().Updates(userModel).Error; err != nil { + return err + } + return nil +} diff --git a/internal/router/router.go b/internal/router/router.go index f2c4f9a..12d71df 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -11,7 +11,7 @@ import ( var h *server.Hertz func Run() error { - mode := utils.Getenv("MODE", constant.ModeProd) // dev | prod + mode := utils.Env.Get("MODE", constant.ModeProd) // dev | prod switch mode { case constant.ModeProd: h.Spin() @@ -25,8 +25,8 @@ func Run() error { func init() { h = server.New( - server.WithHostPorts(":"+utils.Getenv("PORT", "8888")), - server.WithMaxRequestBodySize(utils.GetenvAsInt("MAX_REQUEST_BODY_SIZE", 1048576000)), // 1000MiB + server.WithHostPorts(":"+utils.Env.Get("PORT", "8888")), + server.WithMaxRequestBodySize(utils.Env.GetenvAsInt("MAX_REQUEST_BODY_SIZE", 1048576000)), // 1000MiB ) apiv1.RegisterRoutes(h) } diff --git a/internal/service/user.go b/internal/service/user.go new file mode 100644 index 0000000..6993664 --- /dev/null +++ b/internal/service/user.go @@ -0,0 +1,32 @@ +package service + +import ( + "errors" + "github.com/snowykami/neo-blog/internal/dto" + "github.com/snowykami/neo-blog/internal/repo" + "github.com/snowykami/neo-blog/pkg/constant" + "github.com/snowykami/neo-blog/pkg/resps" + "github.com/snowykami/neo-blog/pkg/utils" +) + +type UserService interface { + UserLogin(dto *dto.UserLoginReq) (*dto.UserLoginResp, error) + UserRegister(dto *dto.UserRegisterReq) (*dto.UserRegisterResp, error) +} + +type userService struct{} + +func NewUserService() UserService { + return &userService{} +} + +func (s *userService) UserLogin(dto *dto.UserLoginReq) (*dto.UserLoginResp, error) { + user, err := repo.User.GetByUsernameOrEmail(dto.Username) + if err != nil { + return nil, err + } + if user == nil { + return nil, errors.New(resps.ErrNotFound) + } + utils.Password.VerifyPassword(dto.Password, user.Password, utils.Env.Get(constant.EnvVarPasswordSalt, "default_salt")) +} diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index f79af6b..e163972 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -1,6 +1,10 @@ package constant const ( - ModeDev = "dev" - ModeProd = "prod" + ModeDev = "dev" + ModeProd = "prod" + RoleUser = "user" + RoleAdmin = "admin" + + EnvVarPasswordSalt = "PASSWORD_SALT" // 环境变量:密码盐 ) diff --git a/pkg/resps/resps.go b/pkg/resps/resps.go new file mode 100644 index 0000000..409c168 --- /dev/null +++ b/pkg/resps/resps.go @@ -0,0 +1,38 @@ +package resps + +import ( + "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/common/utils" +) + +func Custom(c *app.RequestContext, status int, message string, data any) { + c.JSON(status, utils.H{ + "status": status, + "message": message, + "data": data, + }) +} + +func Ok(c *app.RequestContext, message string, data any) { + Custom(c, 200, message, data) +} + +func BadRequest(c *app.RequestContext, message string) { + Custom(c, 400, message, nil) +} + +func UnAuthorized(c *app.RequestContext, message string) { + Custom(c, 401, message, nil) +} + +func Forbidden(c *app.RequestContext, message string) { + Custom(c, 403, message, nil) +} + +func NotFound(c *app.RequestContext, message string) { + Custom(c, 404, message, nil) +} + +func InternalServerError(c *app.RequestContext, message string) { + Custom(c, 500, message, nil) +} diff --git a/pkg/resps/texts.go b/pkg/resps/texts.go new file mode 100644 index 0000000..9db607d --- /dev/null +++ b/pkg/resps/texts.go @@ -0,0 +1,8 @@ +package resps + +const ( + ErrParamInvalid = "invalid request parameters" + ErrUnauthorized = "unauthorized access" + ErrForbidden = "access forbidden" + ErrNotFound = "resource not found" +) diff --git a/pkg/utils/env.go b/pkg/utils/env.go index dc89866..a081e27 100644 --- a/pkg/utils/env.go +++ b/pkg/utils/env.go @@ -10,7 +10,11 @@ func init() { _ = godotenv.Load() } -func Getenv(key string, defaultValue ...string) string { +type envType struct{} + +var Env envType + +func (e *envType) Get(key string, defaultValue ...string) string { value := os.Getenv(key) if value == "" && len(defaultValue) > 0 { return defaultValue[0] @@ -18,7 +22,7 @@ func Getenv(key string, defaultValue ...string) string { return value } -func GetenvAsInt(key string, defaultValue ...int) int { +func (e *envType) GetenvAsInt(key string, defaultValue ...int) int { value := os.Getenv(key) if value == "" && len(defaultValue) > 0 { return defaultValue[0] @@ -30,7 +34,7 @@ func GetenvAsInt(key string, defaultValue ...int) int { return intValue } -func GetenvAsBool(key string, defaultValue ...bool) bool { +func (e *envType) GetenvAsBool(key string, defaultValue ...bool) bool { value := os.Getenv(key) if value == "" && len(defaultValue) > 0 { return defaultValue[0] diff --git a/pkg/utils/password.go b/pkg/utils/password.go new file mode 100644 index 0000000..8800b26 --- /dev/null +++ b/pkg/utils/password.go @@ -0,0 +1,41 @@ +package utils + +import ( + "crypto/sha256" + "encoding/hex" + "golang.org/x/crypto/bcrypt" +) + +type PasswordType struct { +} + +var Password = PasswordType{} + +// HashPassword 密码哈希函数 +func (u *PasswordType) HashPassword(password string, salt string) (string, error) { + saltedPassword := Password.addSalt(password, salt) + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(saltedPassword), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hashedPassword), nil +} + +// VerifyPassword 验证密码 +func (u *PasswordType) VerifyPassword(password, hashedPassword string, salt string) bool { + if len(hashedPassword) == 0 || len(salt) == 0 { + // 防止oidc空密码出问题 + return false + } + saltedPassword := Password.addSalt(password, salt) + err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(saltedPassword)) + return err == nil +} + +// addSalt 加盐函数 +func (u *PasswordType) addSalt(password string, salt string) string { + combined := password + salt + hash := sha256.New() + hash.Write([]byte(combined)) + return hex.EncodeToString(hash.Sum(nil)) +}