This commit is contained in:
oneao committed 2026-04-01 21:51:18 +08:00
1 parent 4ef00e1d5d
commit 9e00adf2fe
27 files changed
+1104

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/allapp-go-v2.iml" filepath="$PROJECT_DIR$/.idea/allapp-go-v2.iml" />
</modules>
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
</component>
</project>
+19
View File
@@ -0,0 +1,19 @@
package main
import (
"allapp-go-v2/internal/bootstrap"
"allapp-go-v2/internal/server"
"log"
)
func main() {
app, err := bootstrap.NewApp()
if err != nil {
log.Fatal(err)
}
if err := server.Start(app); err != nil {
log.Fatal(err)
}
}
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,14 @@
app:
name: allapp
server:
port: 8086
postgres:
host: 117.72.182.135
port: 5432
user: postgres
password: zhang520..
dbname: app
timezone: Asia/Shanghai
sll_mode: false
+44
View File
@@ -0,0 +1,44 @@
module allapp-go-v2
go 1.25.0
require (
github.com/gofiber/fiber/v3 v3.1.0
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/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/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
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
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/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
)
+105
View File
@@ -0,0 +1,105 @@
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=
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
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/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=
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.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=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/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=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
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=
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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,38 @@
package bootstrap
import (
"allapp-go-v2/internal/config"
"allapp-go-v2/internal/pkg/logger"
"github.com/jackc/pgx/v5/pgxpool"
)
type App struct {
Config *config.Config
Db *pgxpool.Pool
}
func NewApp() (*App, error) {
cfg, err := config.Load()
if err != nil {
return nil, err
}
// 初始化日志
if err := logger.Init(cfg.App.Env); err != nil {
return nil, err
}
InitUniqueId(cfg)
db, err := InitDB(cfg)
if err != nil {
return nil, err
}
return &App{
Config: cfg,
Db: db,
}, nil
}
@@ -0,0 +1,53 @@
package bootstrap
import (
"allapp-go-v2/internal/config"
"allapp-go-v2/internal/pkg/logger"
"context"
"fmt"
"net/url"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
func InitDB(cfg *config.Config) (*pgxpool.Pool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
pg := cfg.Postgres
// ssl mode
sslMode := "disable"
if pg.SslMode {
sslMode = "require"
}
// 转义密码(防止特殊字符问题)
password := url.QueryEscape(pg.Password)
// 构建 DSN
dsn := fmt.Sprintf(
"postgres://%s:%s@%s:%d/%s?sslmode=%s&timezone=%s",
pg.User,
password,
pg.Host,
pg.Port,
pg.Dbname,
sslMode,
pg.TimeZone,
)
db, err := pgxpool.New(ctx, dsn)
if err != nil {
return nil, err
}
if err := db.Ping(ctx); err != nil {
return nil, err
}
logger.Log.Info("√ 数据库连接成功")
return db, nil
}
@@ -0,0 +1,11 @@
package bootstrap
import (
"allapp-go-v2/internal/config"
"allapp-go-v2/internal/pkg/uniqueid"
)
func InitUniqueId(cfg *config.Config) {
options := uniqueid.NewIdGeneratorOptions(cfg.UniqueID.WorkerID)
uniqueid.SetIdGenerator(options)
}
@@ -0,0 +1,103 @@
package config
import (
"fmt"
"os"
"strings"
"github.com/spf13/viper"
)
func Load() (*Config, error) {
env := getEnv("APP_ENV", "dev")
v := viper.New()
v.SetConfigType("yaml")
// 配置路径
v.AddConfigPath("configs")
// ======================
// 1️⃣ 基础配置
// ======================
if err := loadBase(v); err != nil {
return nil, err
}
// ======================
// 2️⃣ 环境配置(支持多环境)
// ======================
loadByEnv(v, env)
// ======================
// 3️⃣ 环境变量覆盖
// ======================
bindEnv(v)
// ======================
// 4️⃣ 默认值
// ======================
setDefaults(v, env)
// ======================
// 5️⃣ 解析
// ======================
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("decode config failed: %w", err)
}
return &cfg, nil
}
func loadBase(v *viper.Viper) error {
v.SetConfigName("config")
if err := v.ReadInConfig(); err != nil {
return fmt.Errorf("read base config failed: %w", err)
}
return nil
}
func loadByEnv(v *viper.Viper, env string) {
envs := strings.Split(env, ",")
for _, e := range envs {
e = strings.TrimSpace(e)
if e == "" {
continue
}
v.SetConfigName("config." + e)
if err := v.MergeInConfig(); err != nil {
fmt.Printf("⚠️ config.%s.yaml not found\n", e)
}
}
}
func bindEnv(v *viper.Viper) {
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
}
func setDefaults(v *viper.Viper, env string) {
v.SetDefault("app.env", env)
v.SetDefault("app.name", "golang")
v.SetDefault("server.port", "8080")
v.SetDefault("unique_id.datacenter_id", 1)
v.SetDefault("unique_id.worker_id", 1)
v.SetDefault("jwt.secret", "abcdedhaldkasdlkasd")
v.SetDefault("jwt.access_expiry", -1)
}
func getEnv(key, def string) string {
if val := os.Getenv(key); val != "" {
return val
}
return def
}
@@ -0,0 +1,54 @@
package config
import "time"
type Config struct {
App AppConfig `mapstructure:"app"`
Server ServerConfig `mapstructure:"server"`
JWT JwtConfig `mapstructure:"jwt"`
UniqueID UniqueIDConfig `mapstructure:"unique_id"`
Postgres PostgresConfig `mapstructure:"postgres"`
Wechat WechatConfig `mapstructure:"wechat"`
Redis RedisConfig `mapstructure:"redis"`
}
type AppConfig struct {
Name string `mapstructure:"name"`
Env string `mapstructure:"env"`
}
type ServerConfig struct {
Port string `mapstructure:"port"`
}
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"`
}
type WechatConfig struct {
AppId string `mapstructure:"app_id"`
AppSecret string `mapstructure:"app_secret"`
}
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"`
}
type RedisConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Db int `mapstructure:"db"`
Password string `mapstructure:"password"`
}
@@ -0,0 +1,147 @@
package logger
import (
"os"
"path/filepath"
"sync"
"time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var Log *zap.Logger
var (
currentDate string
mu sync.Mutex
envMode string
)
// ======================
// 初始化
// ======================
func Init(env string) error {
envMode = env
logger := buildLogger()
Log = logger
return nil
}
// ======================
// 构建 logger
// ======================
func buildLogger() *zap.Logger {
currentDate = getToday()
level := zap.InfoLevel
if envMode == "dev" {
level = zap.DebugLevel
}
encoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
MessageKey: "msg",
CallerKey: "caller",
EncodeLevel: zapcore.CapitalLevelEncoder,
EncodeTime: timeEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
encoder := zapcore.NewJSONEncoder(encoderConfig)
consoleWriter := zapcore.AddSync(os.Stdout)
infoWriter := zapcore.AddSync(&dailyWriter{
level: "info",
})
errorWriter := zapcore.AddSync(&dailyWriter{
level: "error",
})
infoCore := zapcore.NewCore(
encoder,
zapcore.NewMultiWriteSyncer(consoleWriter, infoWriter),
zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl < zapcore.ErrorLevel && lvl >= level
}),
)
errorCore := zapcore.NewCore(
encoder,
zapcore.NewMultiWriteSyncer(consoleWriter, errorWriter),
zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
}),
)
core := zapcore.NewTee(infoCore, errorCore)
return zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
}
// ======================
// 自定义 Writer(核心)
// ======================
type dailyWriter struct {
level string
file *os.File
}
func (w *dailyWriter) Write(p []byte) (n int, err error) {
mu.Lock()
defer mu.Unlock()
today := getToday()
// 跨天切换
if w.file == nil || today != currentDate {
if w.file != nil {
_ = w.file.Close()
}
currentDate = today
filename := getLogFilePath(w.level, today)
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return 0, err
}
w.file = file
}
return w.file.Write(p)
}
func (w *dailyWriter) Sync() error {
return nil
}
// ======================
// 工具函数
// ======================
// 获取当天
func getToday() string {
return time.Now().Format("2006-01-02")
}
// 获取日志路径
func getLogFilePath(level, date string) string {
dir := filepath.Join("logs", level)
// 自动创建目录
_ = os.MkdirAll(dir, os.ModePerm)
return filepath.Join(dir, date+".log")
}
// 时间格式
func timeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05"))
}
@@ -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
}
@@ -0,0 +1,26 @@
package server
import (
"allapp-go-v2/internal/bootstrap"
"fmt"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/requestid"
)
func Start(appCtx *bootstrap.App) error {
// 1️⃣ 创建 fiber 实例
app := fiber.New()
app.Use(requestid.New())
// 2️⃣ 注册路由
//registerRoutes(app, appCtx)
// 3️⃣ 启动服务
addr := fmt.Sprintf(":%s", appCtx.Config.Server.Port)
fmt.Println("🚀 server running at", addr)
return app.Listen(addr)
}
@@ -0,0 +1 @@
{"level":"INFO","time":"2026-04-01 21:29:06","caller":"bootstrap/app.go:28","msg":"√ 数据库连接成功"}