add initial application structure with configuration, logging, and health check endpoints
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
/bin/
|
||||||
|
/dist/
|
||||||
|
/tmp/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Go workspace
|
||||||
|
/vendor/
|
||||||
|
/coverage/
|
||||||
|
/cover.out
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
# Local env/config overrides
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
application.local.yaml
|
||||||
|
application.*.local.yaml
|
||||||
|
|
||||||
|
# IDE/editor
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
20
application.yaml
Normal file
20
application.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
readTimeout: 5s
|
||||||
|
writeTimeout: 5s
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
dsn: "root:mysql_78GywN@tcp(111.228.49.52:3306)/db?parseTime=true&loc=Local"
|
||||||
|
|
||||||
|
redis:
|
||||||
|
addr: "111.228.49.52"
|
||||||
|
password: "redis_7aFAxY"
|
||||||
|
db: 0
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
format: json
|
||||||
|
|
||||||
|
cron:
|
||||||
|
enabled: true
|
||||||
|
timezone: "Asia/Shanghai"
|
||||||
142
cmd/gen/controllers/main.go
Normal file
142
cmd/gen/controllers/main.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
controllerDir, err := findControllerDir()
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctors, err := findControllerCtors(controllerDir)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := renderModule(ctors)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outPath := filepath.Join(controllerDir, "module.go")
|
||||||
|
if err := os.WriteFile(outPath, content, 0o644); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findControllerDir() (string, error) {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists(filepath.Join(wd, "module.go")) && exists(filepath.Join(wd, "routes.go")) {
|
||||||
|
return wd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate := filepath.Join(wd, "internal", "controller")
|
||||||
|
if exists(filepath.Join(candidate, "module.go")) {
|
||||||
|
return candidate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("could not locate internal/controller")
|
||||||
|
}
|
||||||
|
|
||||||
|
func findControllerCtors(dir string) ([]string, error) {
|
||||||
|
entries, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctorRe := regexp.MustCompile(`(?m)^func\s+(New[A-Za-z0-9_]*Controller)\s*\(`)
|
||||||
|
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := entry.Name()
|
||||||
|
if !hasSuffix(name, "_controller.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if name == "module.go" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(dir, name)
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := ctorRe.FindAllSubmatch(data, -1)
|
||||||
|
for _, m := range matches {
|
||||||
|
if len(m) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[string(m[1])] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctors []string
|
||||||
|
for k := range seen {
|
||||||
|
ctors = append(ctors, k)
|
||||||
|
}
|
||||||
|
sort.Strings(ctors)
|
||||||
|
|
||||||
|
return ctors, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderModule(ctors []string) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
buf.WriteString("// Code generated by cmd/gen/controllers; DO NOT EDIT.\n")
|
||||||
|
buf.WriteString("package controller\n\n")
|
||||||
|
buf.WriteString("import \"go.uber.org/fx\"\n\n")
|
||||||
|
buf.WriteString("var Module = fx.Options(\n")
|
||||||
|
|
||||||
|
if len(ctors) > 0 {
|
||||||
|
buf.WriteString("\tfx.Provide(\n")
|
||||||
|
for _, ctor := range ctors {
|
||||||
|
buf.WriteString("\t\t")
|
||||||
|
buf.WriteString(ctor)
|
||||||
|
buf.WriteString(",\n")
|
||||||
|
}
|
||||||
|
buf.WriteString("\t),\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString("\tfx.Invoke(\n\t\tRegisterRoutes,\n\t),\n")
|
||||||
|
buf.WriteString(")\n")
|
||||||
|
|
||||||
|
formatted, err := format.Source(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func exists(path string) bool {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !info.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasSuffix(s, suffix string) bool {
|
||||||
|
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
22
cmd/server/main.go
Normal file
22
cmd/server/main.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"epic-ent/internal/bootstrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := bootstrap.NewApp()
|
||||||
|
|
||||||
|
if err := app.Start(context.Background()); err != nil {
|
||||||
|
log.Fatalf("failed to start app: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-app.Done()
|
||||||
|
|
||||||
|
if err := app.Stop(context.Background()); err != nil {
|
||||||
|
log.Printf("failed to stop app: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
go.mod
Normal file
40
go.mod
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
module epic-ent
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3
|
||||||
|
github.com/labstack/echo/v4 v4.15.0
|
||||||
|
github.com/redis/go-redis/v9 v9.17.2
|
||||||
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
|
go.uber.org/fx v1.24.0
|
||||||
|
go.uber.org/zap v1.27.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // 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/sagikazarmark/locafero v0.12.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/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
|
go.uber.org/dig v1.19.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
|
golang.org/x/net v0.49.0 // indirect
|
||||||
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
|
golang.org/x/text v0.33.0 // indirect
|
||||||
|
)
|
||||||
70
go.sum
Normal file
70
go.sum
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
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/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
|
||||||
|
github.com/labstack/echo/v4 v4.15.0 h1:hoRTKWcnR5STXZFe9BmYun9AMTNeSbjHi2vtDuADJ24=
|
||||||
|
github.com/labstack/echo/v4 v4.15.0/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c=
|
||||||
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
|
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/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
|
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||||
|
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||||
|
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||||
|
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.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||||
|
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
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/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
|
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||||
|
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||||
|
go.uber.org/fx v1.22.2/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
|
||||||
|
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||||
|
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
25
internal/bootstrap/app.go
Normal file
25
internal/bootstrap/app.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"epic-ent/internal/config"
|
||||||
|
"epic-ent/internal/controller"
|
||||||
|
"epic-ent/internal/exception"
|
||||||
|
"epic-ent/internal/infra"
|
||||||
|
"epic-ent/internal/middleware"
|
||||||
|
"epic-ent/internal/repository"
|
||||||
|
"epic-ent/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewApp() *fx.App {
|
||||||
|
return fx.New(
|
||||||
|
config.Module,
|
||||||
|
infra.Module,
|
||||||
|
repository.Module,
|
||||||
|
service.Module,
|
||||||
|
controller.Module,
|
||||||
|
middleware.Module,
|
||||||
|
exception.Module,
|
||||||
|
)
|
||||||
|
}
|
||||||
62
internal/config/config.go
Normal file
62
internal/config/config.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server ServerConfig `mapstructure:"server"`
|
||||||
|
MySQL MySQLConfig `mapstructure:"mysql"`
|
||||||
|
Redis RedisConfig `mapstructure:"redis"`
|
||||||
|
Log LogConfig `mapstructure:"log"`
|
||||||
|
Cron CronConfig `mapstructure:"cron"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerConfig struct {
|
||||||
|
Port int `mapstructure:"port"`
|
||||||
|
ReadTimeout string `mapstructure:"readTimeout"`
|
||||||
|
WriteTimeout string `mapstructure:"writeTimeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MySQLConfig struct {
|
||||||
|
DSN string `mapstructure:"dsn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedisConfig struct {
|
||||||
|
Addr string `mapstructure:"addr"`
|
||||||
|
Password string `mapstructure:"password"`
|
||||||
|
DB int `mapstructure:"db"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogConfig struct {
|
||||||
|
Level string `mapstructure:"level"`
|
||||||
|
Format string `mapstructure:"format"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CronConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
Timezone string `mapstructure:"timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() (*Config, error) {
|
||||||
|
v := viper.New()
|
||||||
|
v.SetConfigName("application")
|
||||||
|
v.SetConfigType("yaml")
|
||||||
|
v.AddConfigPath(".")
|
||||||
|
|
||||||
|
v.SetEnvPrefix("APP")
|
||||||
|
v.AutomaticEnv()
|
||||||
|
|
||||||
|
if err := v.ReadInConfig(); err != nil {
|
||||||
|
return nil, fmt.Errorf("read config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
if err := v.Unmarshal(&cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
7
internal/config/module.go
Normal file
7
internal/config/module.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "go.uber.org/fx"
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(Load),
|
||||||
|
)
|
||||||
3
internal/controller/doc.go
Normal file
3
internal/controller/doc.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
//go:generate go run ../../cmd/gen/controllers
|
||||||
22
internal/controller/health_controller.go
Normal file
22
internal/controller/health_controller.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
|
"epic-ent/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthController struct {
|
||||||
|
svc *service.HealthService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHealthController(svc *service.HealthService) *HealthController {
|
||||||
|
return &HealthController{svc: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HealthController) Health(c echo.Context) error {
|
||||||
|
status := h.svc.Check()
|
||||||
|
return c.JSON(http.StatusOK, status)
|
||||||
|
}
|
||||||
13
internal/controller/module.go
Normal file
13
internal/controller/module.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Code generated by cmd/gen/controllers; DO NOT EDIT.
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import "go.uber.org/fx"
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(
|
||||||
|
NewHealthController,
|
||||||
|
),
|
||||||
|
fx.Invoke(
|
||||||
|
RegisterRoutes,
|
||||||
|
),
|
||||||
|
)
|
||||||
17
internal/controller/routes.go
Normal file
17
internal/controller/routes.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RouteParams struct {
|
||||||
|
fx.In
|
||||||
|
|
||||||
|
Echo *echo.Echo
|
||||||
|
Health *HealthController
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterRoutes(p RouteParams) {
|
||||||
|
p.Echo.GET("/health", p.Health.Health)
|
||||||
|
}
|
||||||
22
internal/controller/user_controller.go
Normal file
22
internal/controller/user_controller.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
|
"epic-ent/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserController struct {
|
||||||
|
svc *service.HealthService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserController(svc *service.HealthService) *HealthController {
|
||||||
|
return &HealthController{svc: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UserController) Health(c echo.Context) error {
|
||||||
|
//status := h.svc.Check()
|
||||||
|
return c.JSON(http.StatusOK, "你好")
|
||||||
|
}
|
||||||
5
internal/domain/dto/health.go
Normal file
5
internal/domain/dto/health.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type HealthRequest struct {
|
||||||
|
Ping string `json:"ping"`
|
||||||
|
}
|
||||||
5
internal/domain/vo/health.go
Normal file
5
internal/domain/vo/health.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package vo
|
||||||
|
|
||||||
|
type HealthStatus struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
14
internal/exception/handler.go
Normal file
14
internal/exception/handler.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package exception
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterErrorHandler(e *echo.Echo) {
|
||||||
|
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||||
|
_ = c.JSON(500, map[string]any{
|
||||||
|
"code": "INTERNAL_ERROR",
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
7
internal/exception/module.go
Normal file
7
internal/exception/module.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package exception
|
||||||
|
|
||||||
|
import "go.uber.org/fx"
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Invoke(RegisterErrorHandler),
|
||||||
|
)
|
||||||
17
internal/infra/cache/redis.go
vendored
Normal file
17
internal/infra/cache/redis.go
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
|
||||||
|
"epic-ent/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRedis(cfg *config.Config) (*redis.Client, error) {
|
||||||
|
rdb := redis.NewClient(&redis.Options{
|
||||||
|
Addr: cfg.Redis.Addr,
|
||||||
|
Password: cfg.Redis.Password,
|
||||||
|
DB: cfg.Redis.DB,
|
||||||
|
})
|
||||||
|
|
||||||
|
return rdb, nil
|
||||||
|
}
|
||||||
39
internal/infra/cron/cron.go
Normal file
39
internal/infra/cron/cron.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package cron
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"epic-ent/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterJobs(lc fx.Lifecycle, cfg *config.Config, logger *zap.Logger) {
|
||||||
|
if !cfg.Cron.Enabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loc, err := time.LoadLocation(cfg.Cron.Timezone)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("invalid timezone, fallback to Local", zap.Error(err))
|
||||||
|
loc = time.Local
|
||||||
|
}
|
||||||
|
|
||||||
|
c := cron.New(cron.WithLocation(loc))
|
||||||
|
|
||||||
|
lc.Append(fx.Hook{
|
||||||
|
OnStart: func(ctx context.Context) error {
|
||||||
|
c.Start()
|
||||||
|
logger.Info("cron started")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
OnStop: func(ctx context.Context) error {
|
||||||
|
c.Stop()
|
||||||
|
logger.Info("cron stopped")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
18
internal/infra/db/mysql.go
Normal file
18
internal/infra/db/mysql.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
|
||||||
|
"epic-ent/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewMySQL(cfg *config.Config) (*sql.DB, error) {
|
||||||
|
db, err := sql.Open("mysql", cfg.MySQL.DSN)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
49
internal/infra/http/http.go
Normal file
49
internal/infra/http/http.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"epic-ent/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewEcho() *echo.Echo {
|
||||||
|
return echo.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartServer(lc fx.Lifecycle, cfg *config.Config, e *echo.Echo, logger *zap.Logger) {
|
||||||
|
addr := fmt.Sprintf(":%d", cfg.Server.Port)
|
||||||
|
|
||||||
|
if cfg.Server.ReadTimeout != "" {
|
||||||
|
if d, err := time.ParseDuration(cfg.Server.ReadTimeout); err == nil {
|
||||||
|
e.Server.ReadTimeout = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.Server.WriteTimeout != "" {
|
||||||
|
if d, err := time.ParseDuration(cfg.Server.WriteTimeout); err == nil {
|
||||||
|
e.Server.WriteTimeout = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lc.Append(fx.Hook{
|
||||||
|
OnStart: func(ctx context.Context) error {
|
||||||
|
logger.Info("http server starting", zap.String("addr", addr))
|
||||||
|
go func() {
|
||||||
|
if err := e.Start(addr); err != nil && err != http.ErrServerClosed {
|
||||||
|
logger.Error("http server stopped", zap.Error(err))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
OnStop: func(ctx context.Context) error {
|
||||||
|
logger.Info("http server stopping")
|
||||||
|
return e.Shutdown(ctx)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
23
internal/infra/log/logger.go
Normal file
23
internal/infra/log/logger.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"epic-ent/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLogger(cfg *config.Config) (*zap.Logger, error) {
|
||||||
|
level := zapcore.InfoLevel
|
||||||
|
if err := level.Set(cfg.Log.Level); err != nil {
|
||||||
|
level = zapcore.InfoLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
zapCfg := zap.NewProductionConfig()
|
||||||
|
zapCfg.Level = zap.NewAtomicLevelAt(level)
|
||||||
|
if cfg.Log.Format == "console" {
|
||||||
|
zapCfg.Encoding = "console"
|
||||||
|
}
|
||||||
|
|
||||||
|
return zapCfg.Build()
|
||||||
|
}
|
||||||
24
internal/infra/module.go
Normal file
24
internal/infra/module.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package infra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"epic-ent/internal/infra/cache"
|
||||||
|
"epic-ent/internal/infra/cron"
|
||||||
|
"epic-ent/internal/infra/db"
|
||||||
|
"epic-ent/internal/infra/http"
|
||||||
|
infraLog "epic-ent/internal/infra/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(
|
||||||
|
infraLog.NewLogger,
|
||||||
|
db.NewMySQL,
|
||||||
|
cache.NewRedis,
|
||||||
|
http.NewEcho,
|
||||||
|
),
|
||||||
|
fx.Invoke(
|
||||||
|
cron.RegisterJobs,
|
||||||
|
http.StartServer,
|
||||||
|
),
|
||||||
|
)
|
||||||
9
internal/middleware/middleware.go
Normal file
9
internal/middleware/middleware.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Register(e *echo.Echo) {
|
||||||
|
e.HideBanner = true
|
||||||
|
}
|
||||||
7
internal/middleware/module.go
Normal file
7
internal/middleware/module.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import "go.uber.org/fx"
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Invoke(Register),
|
||||||
|
)
|
||||||
15
internal/repository/health_repo.go
Normal file
15
internal/repository/health_repo.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"epic-ent/internal/domain/vo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthRepository struct{}
|
||||||
|
|
||||||
|
func NewHealthRepository() *HealthRepository {
|
||||||
|
return &HealthRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *HealthRepository) Check() vo.HealthStatus {
|
||||||
|
return vo.HealthStatus{Status: "ok"}
|
||||||
|
}
|
||||||
9
internal/repository/module.go
Normal file
9
internal/repository/module.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import "go.uber.org/fx"
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(
|
||||||
|
NewHealthRepository,
|
||||||
|
),
|
||||||
|
)
|
||||||
18
internal/service/health_service.go
Normal file
18
internal/service/health_service.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"epic-ent/internal/domain/vo"
|
||||||
|
"epic-ent/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HealthService struct {
|
||||||
|
repo *repository.HealthRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHealthService(repo *repository.HealthRepository) *HealthService {
|
||||||
|
return &HealthService{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HealthService) Check() vo.HealthStatus {
|
||||||
|
return s.repo.Check()
|
||||||
|
}
|
||||||
9
internal/service/module.go
Normal file
9
internal/service/module.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import "go.uber.org/fx"
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(
|
||||||
|
NewHealthService,
|
||||||
|
),
|
||||||
|
)
|
||||||
3
internal/util/consts.go
Normal file
3
internal/util/consts.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
const AppName = "epic-ent"
|
||||||
Reference in New Issue
Block a user