u
This commit is contained in:
1 parent
ae0d245605
commit
4725ee75ca
26 files changed
+1089
-67
No files matched your search
@@ -5,5 +5,14 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
_ = bootstrap.BootService()
|
||||
app, err := bootstrap.NewApp()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer app.Close()
|
||||
|
||||
if err := app.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,13 @@
|
||||
app:
|
||||
port: 8087
|
||||
postgres:
|
||||
host: 117.72.182.135
|
||||
port: 5432
|
||||
user: postgres
|
||||
password: zhang520..
|
||||
dbname: app
|
||||
timezone: Asia/Shanghai
|
||||
max_open_conns: 10
|
||||
max_idle_conns: 5
|
||||
conn_max_lifetime: 30m
|
||||
sll_mode: false
|
||||
@@ -2,17 +2,23 @@ module allapp-go
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/gofiber/fiber/v3 v3.1.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/jackc/pgx/v5 v5.9.1
|
||||
github.com/spf13/viper v1.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gofiber/fiber/v3 v3.1.0 // indirect
|
||||
github.com/gofiber/schema v1.7.0 // indirect
|
||||
github.com/gofiber/utils/v2 v2.0.2 // indirect
|
||||
github.com/google/uuid v1.6.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.9.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@@ -23,14 +29,17 @@ require (
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.3 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
)
|
||||
@@ -1,8 +1,14 @@
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
|
||||
@@ -11,6 +17,10 @@ github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg=
|
||||
github.com/gofiber/schema v1.7.0/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
|
||||
github.com/gofiber/utils/v2 v2.0.2 h1:ShRRssz0F3AhTlAQcuEj54OEDtWF7+HJDwEi/aa6QLI=
|
||||
github.com/gofiber/utils/v2 v2.0.2/go.mod h1:+9Ub4NqQ+IaJoTliq5LfdmOJAA/Hzwf4pXOxOa3RrJ0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
@@ -19,8 +29,14 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
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/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -29,9 +45,14 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
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 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/shamaton/msgpack/v3 v3.1.0 h1:jsk0vEAqVvvS9+fTZ5/EcQ9tz860c9pWxJ4Iwecz8gU=
|
||||
github.com/shamaton/msgpack/v3 v3.1.0/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
@@ -45,6 +66,8 @@ github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjb
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
|
||||
@@ -53,20 +76,32 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
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=
|
||||
@@ -0,0 +1,74 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"allapp-go/internal/bootstrap/boots"
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/pkg/logger"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Config *config.Config
|
||||
PgSql *pgxpool.Pool
|
||||
}
|
||||
|
||||
// NewApp 初始化(不阻塞)
|
||||
func NewApp() (*App, error) {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 日志
|
||||
boots.InitLogger(cfg)
|
||||
|
||||
pgsql, err := boots.InitPgsql(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 其他组件
|
||||
boots.InitUniqueId(cfg)
|
||||
boots.InitJwt(cfg)
|
||||
|
||||
return &App{
|
||||
Config: cfg,
|
||||
PgSql: pgsql,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run 启动服务(阻塞)
|
||||
func (a *App) Run() error {
|
||||
app, err := boots.RunServer(a.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 监听退出信号
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
<-quit
|
||||
|
||||
logger.Log.Info("shutting down server...")
|
||||
|
||||
if err := app.Shutdown(); err != nil {
|
||||
logger.Log.Error("shutdown failed")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close 释放资源
|
||||
func (a *App) Close() {
|
||||
if a.PgSql != nil {
|
||||
a.PgSql.Close()
|
||||
}
|
||||
|
||||
logger.Sync()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/pkg/logger"
|
||||
)
|
||||
|
||||
func InitLogger(cfg *config.Config) {
|
||||
logger.Init(logger.Config{
|
||||
Dir: "logs",
|
||||
Level: "info",
|
||||
MaxSize: 100, // MB
|
||||
MaxBackups: 10,
|
||||
MaxAge: 7, // 天
|
||||
Compress: true,
|
||||
})
|
||||
defer logger.Sync()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/pkg/jwtx"
|
||||
)
|
||||
|
||||
// InitJwt 初始化Jwt
|
||||
func InitJwt(cfg *config.Config) {
|
||||
jwtx.Init(cfg.JWT.Secret, cfg.JWT.AccessExpiry)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func InitPgsql(cfg *config.Config) (*pgxpool.Pool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pg := cfg.Postgres
|
||||
|
||||
// ssl
|
||||
sslMode := "disable"
|
||||
if pg.SslMode {
|
||||
sslMode = "require"
|
||||
}
|
||||
|
||||
// 密码转义
|
||||
password := url.QueryEscape(pg.Password)
|
||||
|
||||
// DSN
|
||||
dsn := fmt.Sprintf(
|
||||
"postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
||||
pg.User,
|
||||
password,
|
||||
pg.Host,
|
||||
pg.Port,
|
||||
pg.Dbname,
|
||||
sslMode,
|
||||
)
|
||||
|
||||
// 解析配置
|
||||
conf, err := pgxpool.ParseConfig(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 连接池配置
|
||||
// ======================
|
||||
|
||||
// 最大连接数
|
||||
if pg.MaxOpenConns > 0 {
|
||||
conf.MaxConns = int32(pg.MaxOpenConns)
|
||||
}
|
||||
|
||||
// 最小连接数(近似 idle)
|
||||
if pg.MaxIdleConns > 0 {
|
||||
conf.MinConns = int32(pg.MaxIdleConns)
|
||||
}
|
||||
|
||||
// 连接最大生命周期
|
||||
if pg.ConnMaxLifetime > 0 {
|
||||
conf.MaxConnLifetime = pg.ConnMaxLifetime
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 时区设置(正确方式 ⭐)
|
||||
// ======================
|
||||
if pg.TimeZone != "" {
|
||||
conf.ConnConfig.RuntimeParams["TimeZone"] = pg.TimeZone
|
||||
}
|
||||
|
||||
// 创建连接池
|
||||
db, err := pgxpool.NewWithConfig(ctx, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
if err := db.Ping(ctx); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/internal/router"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/requestid"
|
||||
)
|
||||
|
||||
func RunServer(cfg *config.Config) (*fiber.App, error) {
|
||||
app := fiber.New()
|
||||
|
||||
// ===== middleware =====
|
||||
app.Use(requestid.New())
|
||||
|
||||
// TODO: 你可以加 logger middleware / recover
|
||||
|
||||
// ===== router =====
|
||||
router.Setup(app)
|
||||
|
||||
addr := fmt.Sprintf(":%d", cfg.App.Port)
|
||||
|
||||
go func() {
|
||||
if err := app.Listen(addr); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return app, nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/pkg/uniqueid"
|
||||
)
|
||||
|
||||
// InitUniqueId 初始化雪花id
|
||||
func InitUniqueId(cfg *config.Config) {
|
||||
options := uniqueid.NewIdGeneratorOptions(cfg.UniqueID.WorkerID)
|
||||
uniqueid.SetIdGenerator(options)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"log"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
func BootService() *App {
|
||||
// 1️⃣ 加载配置
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("❌ load config failed: %v", err)
|
||||
}
|
||||
|
||||
RunServer(cfg)
|
||||
|
||||
return &App{
|
||||
Config: cfg,
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/internal/router"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func RunServer(cfg *config.Config) {
|
||||
app := fiber.New()
|
||||
|
||||
router.Setup(app)
|
||||
|
||||
addr := fmt.Sprintf(":%d", cfg.App.Port)
|
||||
|
||||
log.Printf("🚀 Server running on %s", addr)
|
||||
|
||||
if err := app.Listen(addr); err != nil {
|
||||
log.Fatalf("❌ start server failed: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
type Config struct {
|
||||
App AppConfig `mapstructure:"app"`
|
||||
JWT JwtConfig `mapstructure:"jwt"`
|
||||
UniqueID UniqueIDConfig `mapstructure:"unique_id"`
|
||||
Postgres PostgresConfig `mapstructure:"postgres"`
|
||||
}
|
||||
|
||||
@@ -11,11 +15,24 @@ type AppConfig struct {
|
||||
}
|
||||
|
||||
type PostgresConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Dbname string `mapstructure:"dbname"`
|
||||
SslMode bool `mapstructure:"ssl_mode"`
|
||||
TimeZone string `mapstructure:"timezone"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Dbname string `mapstructure:"dbname"`
|
||||
SslMode bool `mapstructure:"ssl_mode"`
|
||||
TimeZone string `mapstructure:"timezone"`
|
||||
MaxOpenConns int32 `mapstructure:"max_open_conns"`
|
||||
MaxIdleConns int32 `mapstructure:"max_idle_conns"`
|
||||
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
|
||||
}
|
||||
|
||||
type JwtConfig struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
AccessExpiry time.Duration `mapstructure:"access_expiry"`
|
||||
}
|
||||
|
||||
type UniqueIDConfig struct {
|
||||
DataCenterID uint16 `mapstructure:"datacenter_id"`
|
||||
WorkerID uint16 `mapstructure:"worker_id"`
|
||||
}
|
||||
@@ -14,12 +14,19 @@ func Load() (*Config, error) {
|
||||
v := viper.New()
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
// ✅ 默认值
|
||||
setDefaults(v)
|
||||
|
||||
// ✅ 支持环境变量(如 APP_PORT → app.port)
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
// 基础配置
|
||||
v.AddConfigPath("configs")
|
||||
v.SetConfigName("config")
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return nil, fmt.Errorf("read base config failed: %w", err)
|
||||
return nil, fmt.Errorf("读取基础配置失败: %w", err)
|
||||
}
|
||||
|
||||
// 环境配置(覆盖)
|
||||
@@ -27,20 +34,30 @@ func Load() (*Config, error) {
|
||||
v.SetConfigName(envConfigName)
|
||||
|
||||
if err := v.MergeInConfig(); err != nil {
|
||||
// dev 允许没有文件,prod 建议必须存在
|
||||
// dev 可以没有,prod 必须有
|
||||
if env == "prod" {
|
||||
return nil, fmt.Errorf("read env config failed: %w", err)
|
||||
return nil, fmt.Errorf("读取环境配置失败(%s): %w", env, err)
|
||||
}
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal config failed: %w", err)
|
||||
return nil, fmt.Errorf("解析配置失败: %w", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// ✅ 默认值统一在这里管理
|
||||
func setDefaults(v *viper.Viper) {
|
||||
v.SetDefault("app.name", "allapp-go")
|
||||
v.SetDefault("app.port", 8080)
|
||||
|
||||
// 可以继续扩展
|
||||
v.SetDefault("unique_id.datacenter_id", 1)
|
||||
v.SetDefault("unique_id.worker_id", 1)
|
||||
}
|
||||
|
||||
func getEnv(key, defaultVal string) string {
|
||||
val := os.Getenv(key)
|
||||
if val == "" {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"level":"INFO","time":"2026-04-02T22:07:05.693+0800","caller":"app/main.go:15","msg":"shutting down server..."}
|
||||
@@ -0,0 +1,112 @@
|
||||
package jwtx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// ==================
|
||||
// 内部管理器
|
||||
// ==================
|
||||
|
||||
type jwtManager struct {
|
||||
secret string
|
||||
tokenExpiry time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
manager *jwtManager
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// ==================
|
||||
// 初始化
|
||||
// ==================
|
||||
|
||||
// Init 初始化(只执行一次)
|
||||
// expiry = -1 表示不过期
|
||||
func Init(secret string, expiry time.Duration) {
|
||||
once.Do(func() {
|
||||
manager = &jwtManager{
|
||||
secret: secret,
|
||||
tokenExpiry: expiry,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getManager() *jwtManager {
|
||||
if manager == nil {
|
||||
panic("jwt未初始化,请先调用Init")
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
// ==================
|
||||
// Claims(通用结构)
|
||||
// ==================
|
||||
|
||||
type CustomClaims struct {
|
||||
Data map[string]any `json:"data"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// ==================
|
||||
// 对外方法
|
||||
// ==================
|
||||
|
||||
// CreateToken 生成 JWT
|
||||
func CreateToken(ctx context.Context, data map[string]any) (string, error) {
|
||||
m := getManager()
|
||||
return m.createToken(data)
|
||||
}
|
||||
|
||||
// VerifyToken 校验 token 并返回 claims
|
||||
func VerifyToken(ctx context.Context, tokenString string) (*CustomClaims, bool) {
|
||||
if tokenString == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
m := getManager()
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(m.secret), nil
|
||||
})
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*CustomClaims)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return claims, true
|
||||
}
|
||||
|
||||
// ==================
|
||||
// 内部实现
|
||||
// ==================
|
||||
|
||||
func (j *jwtManager) createToken(data map[string]any) (string, error) {
|
||||
now := time.Now()
|
||||
|
||||
claims := &CustomClaims{
|
||||
Data: data,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
},
|
||||
}
|
||||
|
||||
// ✅ 只有 >0 才设置过期时间
|
||||
// -1 / 0 都表示不过期
|
||||
if j.tokenExpiry > 0 {
|
||||
claims.ExpiresAt = jwt.NewNumericDate(now.Add(j.tokenExpiry))
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(j.secret))
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var Log *zap.Logger
|
||||
|
||||
const TraceIDKey = "request_id"
|
||||
|
||||
// ======================
|
||||
// 配置结构
|
||||
// ======================
|
||||
|
||||
type Config struct {
|
||||
Dir string // 日志目录
|
||||
Level string // debug info warn error
|
||||
MaxSize int // MB
|
||||
MaxBackups int
|
||||
MaxAge int // 天
|
||||
Compress bool // 是否压缩
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 初始化
|
||||
// ======================
|
||||
|
||||
func Init(cfg Config) {
|
||||
if cfg.Dir == "" {
|
||||
cfg.Dir = "logs"
|
||||
}
|
||||
|
||||
if cfg.Level == "" {
|
||||
cfg.Level = "info"
|
||||
}
|
||||
|
||||
level := parseLevel(cfg.Level)
|
||||
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
MessageKey: "msg",
|
||||
CallerKey: "caller",
|
||||
EncodeLevel: zapcore.CapitalLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
|
||||
encoder := zapcore.NewJSONEncoder(encoderConfig)
|
||||
|
||||
// 控制台
|
||||
consoleWriter := zapcore.AddSync(os.Stdout)
|
||||
|
||||
// 文件 writer(info)
|
||||
infoWriter := getWriter(cfg, "info")
|
||||
|
||||
// 文件 writer(error)
|
||||
errorWriter := getWriter(cfg, "error")
|
||||
|
||||
// info core
|
||||
infoCore := zapcore.NewCore(
|
||||
encoder,
|
||||
zapcore.NewMultiWriteSyncer(consoleWriter, infoWriter),
|
||||
zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl < zapcore.ErrorLevel && lvl >= level
|
||||
}),
|
||||
)
|
||||
|
||||
// error core
|
||||
errorCore := zapcore.NewCore(
|
||||
encoder,
|
||||
zapcore.NewMultiWriteSyncer(consoleWriter, errorWriter),
|
||||
zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zapcore.ErrorLevel
|
||||
}),
|
||||
)
|
||||
|
||||
core := zapcore.NewTee(infoCore, errorCore)
|
||||
|
||||
Log = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
|
||||
}
|
||||
|
||||
// ======================
|
||||
// lumberjack writer
|
||||
// ======================
|
||||
|
||||
func getWriter(cfg Config, level string) zapcore.WriteSyncer {
|
||||
dir := filepath.Join(cfg.Dir, level)
|
||||
_ = os.MkdirAll(dir, os.ModePerm)
|
||||
|
||||
return zapcore.AddSync(&lumberjack.Logger{
|
||||
Filename: filepath.Join(dir, level+".log"),
|
||||
MaxSize: cfg.MaxSize,
|
||||
MaxBackups: cfg.MaxBackups,
|
||||
MaxAge: cfg.MaxAge,
|
||||
Compress: cfg.Compress,
|
||||
})
|
||||
}
|
||||
|
||||
// ======================
|
||||
// level 解析
|
||||
// ======================
|
||||
|
||||
func parseLevel(lvl string) zapcore.Level {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "debug":
|
||||
return zap.DebugLevel
|
||||
case "warn":
|
||||
return zap.WarnLevel
|
||||
case "error":
|
||||
return zap.ErrorLevel
|
||||
default:
|
||||
return zap.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
// ======================
|
||||
// trace 支持(核心)
|
||||
// ======================
|
||||
|
||||
// Fiber 中使用
|
||||
func Ctx(c fiber.Ctx) *zap.Logger {
|
||||
if c == nil {
|
||||
return Log
|
||||
}
|
||||
|
||||
traceID, _ := c.Locals(TraceIDKey).(string)
|
||||
if traceID == "" {
|
||||
return Log
|
||||
}
|
||||
|
||||
return Log.With(zap.String("trace_id", traceID))
|
||||
}
|
||||
|
||||
// Service / Repo 使用
|
||||
func FromCtx(ctx context.Context) *zap.Logger {
|
||||
if ctx == nil {
|
||||
return Log
|
||||
}
|
||||
|
||||
traceID, _ := ctx.Value(TraceIDKey).(string)
|
||||
if traceID == "" {
|
||||
return Log
|
||||
}
|
||||
|
||||
return Log.With(zap.String("trace_id", traceID))
|
||||
}
|
||||
|
||||
// 注入 trace
|
||||
func WithTrace(ctx context.Context, traceID string) context.Context {
|
||||
return context.WithValue(ctx, TraceIDKey, traceID)
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 优雅退出
|
||||
// ======================
|
||||
|
||||
func Sync() {
|
||||
if Log != nil {
|
||||
_ = Log.Sync()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package uniqueid
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DefaultIdGenerator struct {
|
||||
Options *IdGeneratorOptions
|
||||
SnowWorker ISnowWorker
|
||||
IdGeneratorException IdGeneratorException
|
||||
}
|
||||
|
||||
func NewDefaultIdGenerator(options *IdGeneratorOptions) *DefaultIdGenerator {
|
||||
if options == nil {
|
||||
panic("dig.Options error.")
|
||||
}
|
||||
|
||||
// 1.BaseTime
|
||||
minTime := int64(631123200000) // time.Now().AddDate(-30, 0, 0).UnixNano() / 1e6
|
||||
if options.BaseTime < minTime || options.BaseTime > time.Now().UnixNano()/1e6 {
|
||||
panic("BaseTime error.")
|
||||
}
|
||||
|
||||
// 2.WorkerIdBitLength
|
||||
if options.WorkerIdBitLength <= 0 {
|
||||
panic("WorkerIdBitLength error.(range:[1, 21])")
|
||||
}
|
||||
if options.WorkerIdBitLength+options.SeqBitLength > 22 {
|
||||
panic("error:WorkerIdBitLength + SeqBitLength <= 22")
|
||||
}
|
||||
|
||||
// 3.WorkerId
|
||||
maxWorkerIdNumber := uint16(1<<options.WorkerIdBitLength) - 1
|
||||
if maxWorkerIdNumber == 0 {
|
||||
maxWorkerIdNumber = 63
|
||||
}
|
||||
if options.WorkerId < 0 || options.WorkerId > maxWorkerIdNumber {
|
||||
panic("WorkerId error. (range:[0, " + strconv.FormatUint(uint64(maxWorkerIdNumber), 10) + "]")
|
||||
}
|
||||
|
||||
// 4.SeqBitLength
|
||||
if options.SeqBitLength < 2 || options.SeqBitLength > 21 {
|
||||
panic("SeqBitLength error. (range:[2, 21])")
|
||||
}
|
||||
|
||||
// 5.MaxSeqNumber
|
||||
maxSeqNumber := uint32(1<<options.SeqBitLength) - 1
|
||||
if maxSeqNumber == 0 {
|
||||
maxSeqNumber = 63
|
||||
}
|
||||
if options.MaxSeqNumber < 0 || options.MaxSeqNumber > maxSeqNumber {
|
||||
panic("MaxSeqNumber error. (range:[1, " + strconv.FormatUint(uint64(maxSeqNumber), 10) + "]")
|
||||
}
|
||||
|
||||
// 6.MinSeqNumber
|
||||
if options.MinSeqNumber < 5 || options.MinSeqNumber > maxSeqNumber {
|
||||
panic("MinSeqNumber error. (range:[5, " + strconv.FormatUint(uint64(maxSeqNumber), 10) + "]")
|
||||
}
|
||||
|
||||
var snowWorker ISnowWorker
|
||||
switch options.Method {
|
||||
case 1:
|
||||
snowWorker = NewSnowWorkerM1(options)
|
||||
case 2:
|
||||
snowWorker = NewSnowWorkerM2(options)
|
||||
default:
|
||||
snowWorker = NewSnowWorkerM1(options)
|
||||
}
|
||||
|
||||
if options.Method == 1 {
|
||||
time.Sleep(time.Duration(500) * time.Microsecond)
|
||||
}
|
||||
|
||||
return &DefaultIdGenerator{
|
||||
Options: options,
|
||||
SnowWorker: snowWorker,
|
||||
}
|
||||
}
|
||||
|
||||
func (dig DefaultIdGenerator) NewLong() int64 {
|
||||
return dig.SnowWorker.NextId()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package uniqueid
|
||||
|
||||
type IIdGenerator interface {
|
||||
NewLong() uint64
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package uniqueid
|
||||
|
||||
type ISnowWorker interface {
|
||||
NextId() int64
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package uniqueid
|
||||
|
||||
import "fmt"
|
||||
|
||||
type IdGeneratorException struct {
|
||||
message string
|
||||
error error
|
||||
}
|
||||
|
||||
func (e IdGeneratorException) IdGeneratorException(message ...interface{}) {
|
||||
fmt.Println(message)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package uniqueid
|
||||
|
||||
type IdGeneratorOptions struct {
|
||||
Method uint16 // 雪花计算方法,(1-漂移算法|2-传统算法),默认1
|
||||
BaseTime int64 // 基础时间(ms单位),不能超过当前系统时间
|
||||
WorkerId uint16 // 机器码,必须由外部设定,最大值 2^WorkerIdBitLength-1
|
||||
WorkerIdBitLength byte // 机器码位长,默认值6,取值范围 [1, 15](要求:序列数位长+机器码位长不超过22)
|
||||
SeqBitLength byte // 序列数位长,默认值6,取值范围 [3, 21](要求:序列数位长+机器码位长不超过22)
|
||||
MaxSeqNumber uint32 // 最大序列数(含),设置范围 [MinSeqNumber, 2^SeqBitLength-1],默认值0,表示最大序列数取最大值(2^SeqBitLength-1])
|
||||
MinSeqNumber uint32 // 最小序列数(含),默认值5,取值范围 [5, MaxSeqNumber],每毫秒的前5个序列数对应编号0-4是保留位,其中1-4是时间回拨相应预留位,0是手工新值预留位
|
||||
TopOverCostCount uint32 // 最大漂移次数(含),默认2000,推荐范围500-10000(与计算能力有关)
|
||||
}
|
||||
|
||||
func NewIdGeneratorOptions(workerId uint16) *IdGeneratorOptions {
|
||||
return &IdGeneratorOptions{
|
||||
Method: 1,
|
||||
WorkerId: workerId,
|
||||
BaseTime: 1582136402000,
|
||||
WorkerIdBitLength: 6,
|
||||
SeqBitLength: 6,
|
||||
MaxSeqNumber: 0,
|
||||
MinSeqNumber: 5,
|
||||
TopOverCostCount: 2000,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package uniqueid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var singletonMutex sync.Mutex
|
||||
var idGenerator *DefaultIdGenerator
|
||||
|
||||
// SetIdGenerator .
|
||||
func SetIdGenerator(options *IdGeneratorOptions) {
|
||||
singletonMutex.Lock()
|
||||
idGenerator = NewDefaultIdGenerator(options)
|
||||
singletonMutex.Unlock()
|
||||
}
|
||||
|
||||
// NextId .
|
||||
func NextId() int64 {
|
||||
if idGenerator == nil {
|
||||
singletonMutex.Lock()
|
||||
defer singletonMutex.Unlock()
|
||||
if idGenerator == nil {
|
||||
options := NewIdGeneratorOptions(1)
|
||||
idGenerator = NewDefaultIdGenerator(options)
|
||||
}
|
||||
}
|
||||
|
||||
return idGenerator.NewLong()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package uniqueid
|
||||
|
||||
type OverCostActionArg struct {
|
||||
ActionType int32
|
||||
TimeTick int64
|
||||
WorkerId uint16
|
||||
OverCostCountInOneTerm int32
|
||||
GenCountInOneTerm int32
|
||||
TermIndex int32
|
||||
}
|
||||
|
||||
func (ocaa OverCostActionArg) OverCostActionArg(workerId uint16, timeTick int64, actionType int32, overCostCountInOneTerm int32, genCountWhenOverCost int32, index int32) {
|
||||
ocaa.ActionType = actionType
|
||||
ocaa.TimeTick = timeTick
|
||||
ocaa.WorkerId = workerId
|
||||
ocaa.OverCostCountInOneTerm = overCostCountInOneTerm
|
||||
ocaa.GenCountInOneTerm = genCountWhenOverCost
|
||||
ocaa.TermIndex = index
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package uniqueid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SnowWorkerM1 .
|
||||
type SnowWorkerM1 struct {
|
||||
BaseTime int64 //基础时间
|
||||
WorkerId uint16 //机器码
|
||||
WorkerIdBitLength byte //机器码位长
|
||||
SeqBitLength byte //自增序列数位长
|
||||
MaxSeqNumber uint32 //最大序列数(含)
|
||||
MinSeqNumber uint32 //最小序列数(含)
|
||||
TopOverCostCount uint32 //最大漂移次数
|
||||
_TimestampShift byte
|
||||
_CurrentSeqNumber uint32
|
||||
|
||||
_LastTimeTick int64
|
||||
_TurnBackTimeTick int64
|
||||
_TurnBackIndex byte
|
||||
_IsOverCost bool
|
||||
_OverCostCountInOneTerm uint32
|
||||
_GenCountInOneTerm uint32
|
||||
_TermIndex uint32
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewSnowWorkerM1 .
|
||||
func NewSnowWorkerM1(options *IdGeneratorOptions) ISnowWorker {
|
||||
var workerIdBitLength byte
|
||||
var seqBitLength byte
|
||||
var maxSeqNumber uint32
|
||||
|
||||
// 1.BaseTime
|
||||
var baseTime int64
|
||||
if options.BaseTime != 0 {
|
||||
baseTime = options.BaseTime
|
||||
} else {
|
||||
baseTime = 1582136402000
|
||||
}
|
||||
|
||||
// 2.WorkerIdBitLength
|
||||
if options.WorkerIdBitLength == 0 {
|
||||
workerIdBitLength = 6
|
||||
} else {
|
||||
workerIdBitLength = options.WorkerIdBitLength
|
||||
}
|
||||
|
||||
// 3.WorkerId
|
||||
var workerId = options.WorkerId
|
||||
|
||||
// 4.SeqBitLength
|
||||
if options.SeqBitLength == 0 {
|
||||
seqBitLength = 6
|
||||
} else {
|
||||
seqBitLength = options.SeqBitLength
|
||||
}
|
||||
|
||||
// 5.MaxSeqNumber
|
||||
if options.MaxSeqNumber <= 0 {
|
||||
maxSeqNumber = (1 << seqBitLength) - 1
|
||||
} else {
|
||||
maxSeqNumber = options.MaxSeqNumber
|
||||
}
|
||||
|
||||
// 6.MinSeqNumber
|
||||
var minSeqNumber = options.MinSeqNumber
|
||||
|
||||
// 7.Others
|
||||
var topOverCostCount = options.TopOverCostCount
|
||||
if topOverCostCount == 0 {
|
||||
topOverCostCount = 2000
|
||||
}
|
||||
|
||||
timestampShift := (byte)(workerIdBitLength + seqBitLength)
|
||||
currentSeqNumber := minSeqNumber
|
||||
|
||||
return &SnowWorkerM1{
|
||||
BaseTime: baseTime,
|
||||
WorkerIdBitLength: workerIdBitLength,
|
||||
WorkerId: workerId,
|
||||
SeqBitLength: seqBitLength,
|
||||
MaxSeqNumber: maxSeqNumber,
|
||||
MinSeqNumber: minSeqNumber,
|
||||
TopOverCostCount: topOverCostCount,
|
||||
_TimestampShift: timestampShift,
|
||||
_CurrentSeqNumber: currentSeqNumber,
|
||||
|
||||
_LastTimeTick: 0,
|
||||
_TurnBackTimeTick: 0,
|
||||
_TurnBackIndex: 0,
|
||||
_IsOverCost: false,
|
||||
_OverCostCountInOneTerm: 0,
|
||||
_GenCountInOneTerm: 0,
|
||||
_TermIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// DoGenIDAction .
|
||||
func (m1 *SnowWorkerM1) DoGenIdAction(arg *OverCostActionArg) {
|
||||
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) BeginOverCostAction(useTimeTick int64) {
|
||||
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) EndOverCostAction(useTimeTick int64) {
|
||||
if m1._TermIndex > 10000 {
|
||||
m1._TermIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) BeginTurnBackAction(useTimeTick int64) {
|
||||
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) EndTurnBackAction(useTimeTick int64) {
|
||||
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) NextOverCostId() int64 {
|
||||
currentTimeTick := m1.GetCurrentTimeTick()
|
||||
if currentTimeTick > m1._LastTimeTick {
|
||||
m1.EndOverCostAction(currentTimeTick)
|
||||
m1._LastTimeTick = currentTimeTick
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
m1._IsOverCost = false
|
||||
m1._OverCostCountInOneTerm = 0
|
||||
m1._GenCountInOneTerm = 0
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
if m1._OverCostCountInOneTerm >= m1.TopOverCostCount {
|
||||
m1.EndOverCostAction(currentTimeTick)
|
||||
m1._LastTimeTick = m1.GetNextTimeTick()
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
m1._IsOverCost = false
|
||||
m1._OverCostCountInOneTerm = 0
|
||||
m1._GenCountInOneTerm = 0
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
if m1._CurrentSeqNumber > m1.MaxSeqNumber {
|
||||
m1._LastTimeTick++
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
m1._IsOverCost = true
|
||||
m1._OverCostCountInOneTerm++
|
||||
m1._GenCountInOneTerm++
|
||||
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
m1._GenCountInOneTerm++
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
// NextNormalID .
|
||||
func (m1 *SnowWorkerM1) NextNormalId() int64 {
|
||||
currentTimeTick := m1.GetCurrentTimeTick()
|
||||
if currentTimeTick < m1._LastTimeTick {
|
||||
if m1._TurnBackTimeTick < 1 {
|
||||
m1._TurnBackTimeTick = m1._LastTimeTick - 1
|
||||
m1._TurnBackIndex++
|
||||
// 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序
|
||||
// 最多4次回拨(防止回拨重叠)
|
||||
if m1._TurnBackIndex > 4 {
|
||||
m1._TurnBackIndex = 1
|
||||
}
|
||||
m1.BeginTurnBackAction(m1._TurnBackTimeTick)
|
||||
}
|
||||
|
||||
// time.Sleep(time.Duration(1) * time.Millisecond)
|
||||
return m1.CalcTurnBackId(m1._TurnBackTimeTick)
|
||||
}
|
||||
|
||||
// 时间追平时,_TurnBackTimeTick清零
|
||||
if m1._TurnBackTimeTick > 0 {
|
||||
m1.EndTurnBackAction(m1._TurnBackTimeTick)
|
||||
m1._TurnBackTimeTick = 0
|
||||
}
|
||||
|
||||
if currentTimeTick > m1._LastTimeTick {
|
||||
m1._LastTimeTick = currentTimeTick
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
if m1._CurrentSeqNumber > m1.MaxSeqNumber {
|
||||
m1.BeginOverCostAction(currentTimeTick)
|
||||
m1._TermIndex++
|
||||
m1._LastTimeTick++
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
m1._IsOverCost = true
|
||||
m1._OverCostCountInOneTerm = 1
|
||||
m1._GenCountInOneTerm = 1
|
||||
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
// CalcID .
|
||||
func (m1 *SnowWorkerM1) CalcId(useTimeTick int64) int64 {
|
||||
result := int64(useTimeTick<<m1._TimestampShift) + int64(m1.WorkerId<<m1.SeqBitLength) + int64(m1._CurrentSeqNumber)
|
||||
m1._CurrentSeqNumber++
|
||||
return result
|
||||
}
|
||||
|
||||
// CalcTurnBackID .
|
||||
func (m1 *SnowWorkerM1) CalcTurnBackId(useTimeTick int64) int64 {
|
||||
result := int64(useTimeTick<<m1._TimestampShift) + int64(m1.WorkerId<<m1.SeqBitLength) + int64(m1._TurnBackIndex)
|
||||
m1._TurnBackTimeTick--
|
||||
return result
|
||||
}
|
||||
|
||||
// GetCurrentTimeTick .
|
||||
func (m1 *SnowWorkerM1) GetCurrentTimeTick() int64 {
|
||||
var millis = time.Now().UnixNano() / 1e6
|
||||
return millis - m1.BaseTime
|
||||
}
|
||||
|
||||
// GetNextTimeTick .
|
||||
func (m1 *SnowWorkerM1) GetNextTimeTick() int64 {
|
||||
tempTimeTicker := m1.GetCurrentTimeTick()
|
||||
for tempTimeTicker <= m1._LastTimeTick {
|
||||
tempTimeTicker = m1.GetCurrentTimeTick()
|
||||
}
|
||||
return tempTimeTicker
|
||||
}
|
||||
|
||||
// NextId .
|
||||
func (m1 *SnowWorkerM1) NextId() int64 {
|
||||
m1.Lock()
|
||||
defer m1.Unlock()
|
||||
if m1._IsOverCost {
|
||||
return m1.NextOverCostId()
|
||||
} else {
|
||||
return m1.NextNormalId()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package uniqueid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type SnowWorkerM2 struct {
|
||||
*SnowWorkerM1
|
||||
}
|
||||
|
||||
func NewSnowWorkerM2(options *IdGeneratorOptions) ISnowWorker {
|
||||
return &SnowWorkerM2{
|
||||
NewSnowWorkerM1(options).(*SnowWorkerM1),
|
||||
}
|
||||
}
|
||||
|
||||
func (m2 SnowWorkerM2) NextId() int64 {
|
||||
m2.Lock()
|
||||
defer m2.Unlock()
|
||||
currentTimeTick := m2.GetCurrentTimeTick()
|
||||
if m2._LastTimeTick == currentTimeTick {
|
||||
m2._CurrentSeqNumber++
|
||||
if m2._CurrentSeqNumber > m2.MaxSeqNumber {
|
||||
m2._CurrentSeqNumber = m2.MinSeqNumber
|
||||
currentTimeTick = m2.GetNextTimeTick()
|
||||
}
|
||||
} else {
|
||||
m2._CurrentSeqNumber = m2.MinSeqNumber
|
||||
}
|
||||
if currentTimeTick < m2._LastTimeTick {
|
||||
fmt.Println("Time error for {0} milliseconds", strconv.FormatInt(m2._LastTimeTick-currentTimeTick, 10))
|
||||
}
|
||||
m2._LastTimeTick = currentTimeTick
|
||||
result := int64(currentTimeTick<<m2._TimestampShift) + int64(m2.WorkerId<<m2.SeqBitLength) + int64(m2._CurrentSeqNumber)
|
||||
return result
|
||||
}
|
||||
Reference in new issue
Block a user