42 changed files with 1976 additions and 0 deletions
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"gofaster/internal/config" |
||||
"gofaster/internal/controller" |
||||
"gofaster/internal/middleware" |
||||
"gofaster/internal/model" |
||||
"gofaster/internal/repository" |
||||
"gofaster/internal/service" |
||||
"gofaster/pkg/database" |
||||
"gofaster/pkg/logger" |
||||
"gofaster/routes" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
func main() { |
||||
// 加载配置
|
||||
cfg := config.LoadConfig() |
||||
|
||||
// 初始化日志
|
||||
log := logger.NewLogger(cfg.Log.Level, cfg.Log.Path) |
||||
defer log.Sync() |
||||
|
||||
// 初始化数据库
|
||||
db, err := database.NewDB(&cfg.DB) |
||||
if err != nil { |
||||
log.Fatal("Failed to connect database", zap.Error(err)) |
||||
} |
||||
|
||||
// 自动迁移
|
||||
if err := autoMigrate(db); err != nil { |
||||
log.Fatal("Failed to migrate database", zap.Error(err)) |
||||
} |
||||
|
||||
// 初始化Redis
|
||||
redisClient := database.NewRedisClient(&cfg.Redis) |
||||
|
||||
// 初始化服务层
|
||||
userRepo := repository.NewUserRepo(db) |
||||
userService := service.NewUserService(userRepo) |
||||
|
||||
// 初始化控制器
|
||||
userCtrl := controller.NewUserController(userService) |
||||
|
||||
// 初始化Gin
|
||||
app := gin.New() |
||||
|
||||
// 中间件
|
||||
app.Use(middleware.LoggerMiddleware(log, db)) |
||||
app.Use(middleware.RecoveryMiddleware(log)) |
||||
app.Use(middleware.CORSMiddleware()) |
||||
|
||||
// 路由
|
||||
api := app.Group("/api") |
||||
routes.RegisterUserRoutes(api, userCtrl) |
||||
|
||||
// 认证路由
|
||||
authApi := api.Group("") |
||||
authApi.Use(middleware.AuthMiddleware(cfg.JWT.Secret)) |
||||
{ |
||||
// 需要认证的路由
|
||||
} |
||||
|
||||
// 启动服务器
|
||||
log.Info("Starting server", zap.String("port", cfg.Server.Port)) |
||||
if err := app.Run(":" + cfg.Server.Port); err != nil { |
||||
log.Fatal("Failed to start server", zap.Error(err)) |
||||
} |
||||
} |
||||
|
||||
func autoMigrate(db *gorm.DB) error { |
||||
return db.AutoMigrate( |
||||
&model.User{}, |
||||
&model.Role{}, |
||||
&model.Permission{}, |
||||
&model.ActionLog{}, |
||||
&model.Workflow{}, |
||||
&model.WorkflowNode{}, |
||||
&model.WorkflowInstance{}, |
||||
&model.WorkflowTask{}, |
||||
) |
||||
} |
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
module gofaster |
||||
|
||||
go 1.24.3 |
||||
|
||||
require github.com/spf13/viper v1.20.1 |
||||
|
||||
require ( |
||||
github.com/KyleBanks/depth v1.2.1 // indirect |
||||
github.com/PuerkitoBio/purell v1.2.1 // indirect |
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect |
||||
github.com/bytedance/sonic v1.14.0 // indirect |
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect |
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect |
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect |
||||
github.com/cloudwego/base64x v0.1.5 // indirect |
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect |
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect |
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect |
||||
github.com/gin-contrib/sse v1.1.0 // indirect |
||||
github.com/gin-gonic/gin v1.10.1 // indirect |
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect |
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect |
||||
github.com/go-openapi/spec v0.21.0 // indirect |
||||
github.com/go-openapi/swag v0.23.1 // indirect |
||||
github.com/go-playground/locales v0.14.1 // indirect |
||||
github.com/go-playground/universal-translator v0.18.1 // indirect |
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect |
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect |
||||
github.com/goccy/go-json v0.10.5 // indirect |
||||
github.com/golang-jwt/jwt/v5 v5.2.3 |
||||
github.com/jackc/pgpassfile v1.0.0 // indirect |
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect |
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect |
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect |
||||
github.com/jinzhu/inflection v1.0.0 // indirect |
||||
github.com/jinzhu/now v1.1.5 // indirect |
||||
github.com/josharian/intern v1.0.0 // indirect |
||||
github.com/json-iterator/go v1.1.12 // indirect |
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect |
||||
github.com/leodido/go-urn v1.4.0 // indirect |
||||
github.com/mailru/easyjson v0.9.0 // indirect |
||||
github.com/mattn/go-isatty v0.0.20 // indirect |
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect |
||||
github.com/modern-go/reflect2 v1.0.2 // indirect |
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect |
||||
github.com/redis/go-redis/v9 v9.11.0 // indirect |
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect |
||||
github.com/sourcegraph/conc v0.3.0 // indirect |
||||
github.com/spf13/afero v1.12.0 // indirect |
||||
github.com/spf13/cast v1.7.1 // indirect |
||||
github.com/spf13/pflag v1.0.6 // indirect |
||||
github.com/subosito/gotenv v1.6.0 // indirect |
||||
github.com/swaggo/gin-swagger v1.6.0 // indirect |
||||
github.com/swaggo/swag v1.16.5 // indirect |
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect |
||||
github.com/ugorji/go/codec v1.3.0 // indirect |
||||
go.uber.org/multierr v1.11.0 // indirect |
||||
go.uber.org/zap v1.27.0 // indirect |
||||
golang.org/x/arch v0.19.0 // indirect |
||||
golang.org/x/crypto v0.40.0 // indirect |
||||
golang.org/x/mod v0.26.0 // indirect |
||||
golang.org/x/net v0.42.0 // indirect |
||||
golang.org/x/sync v0.16.0 // indirect |
||||
golang.org/x/sys v0.34.0 // indirect |
||||
golang.org/x/text v0.27.0 // indirect |
||||
golang.org/x/tools v0.35.0 // indirect |
||||
google.golang.org/protobuf v1.36.6 // indirect |
||||
gopkg.in/yaml.v2 v2.4.0 // indirect |
||||
gopkg.in/yaml.v3 v3.0.1 // indirect |
||||
gorm.io/driver/postgres v1.6.0 // indirect |
||||
gorm.io/gorm v1.30.1 // indirect |
||||
) |
@ -0,0 +1,161 @@
@@ -0,0 +1,161 @@
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= |
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= |
||||
github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= |
||||
github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= |
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= |
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= |
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= |
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= |
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= |
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= |
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= |
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= |
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= |
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= |
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= |
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= |
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= |
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= |
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= |
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= |
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= |
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= |
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= |
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
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.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= |
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= |
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= |
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= |
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= |
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= |
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= |
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= |
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= |
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= |
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= |
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= |
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= |
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= |
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= |
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= |
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= |
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= |
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= |
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= |
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= |
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= |
||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= |
||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= |
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= |
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= |
||||
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= |
||||
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= |
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= |
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= |
||||
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.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= |
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= |
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= |
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= |
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= |
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= |
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= |
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= |
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= |
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= |
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= |
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= |
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= |
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= |
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= |
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= |
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= |
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= |
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= |
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= |
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= |
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= |
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= |
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= |
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= |
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= |
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= |
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs= |
||||
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= |
||||
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= |
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= |
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= |
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= |
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= |
||||
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= |
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= |
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= |
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= |
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= |
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= |
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= |
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= |
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= |
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= |
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= |
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= |
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= |
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= |
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= |
||||
github.com/swaggo/swag v1.16.5 h1:nMf2fEV1TetMTJb4XzD0Lz7jFfKJmJKGTygEey8NSxM= |
||||
github.com/swaggo/swag v1.16.5/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= |
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= |
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= |
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= |
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= |
||||
go.uber.org/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 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= |
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= |
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= |
||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU= |
||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= |
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= |
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= |
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= |
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= |
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= |
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= |
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= |
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= |
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= |
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= |
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= |
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= |
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= |
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= |
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= |
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= |
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= |
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= |
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= |
||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4= |
||||
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= |
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= |
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
package config |
||||
|
||||
import ( |
||||
"log" |
||||
|
||||
"github.com/spf13/viper" |
||||
) |
||||
|
||||
type Config struct { |
||||
DB DBConfig |
||||
Server ServerConfig |
||||
Redis RedisConfig |
||||
JWT JWTConfig |
||||
} |
||||
|
||||
type DBConfig struct { |
||||
Host string |
||||
Port string |
||||
User string |
||||
Password string |
||||
Name string |
||||
} |
||||
|
||||
type ServerConfig struct { |
||||
Port string |
||||
} |
||||
|
||||
type RedisConfig struct { |
||||
Host string |
||||
Port string |
||||
Password string |
||||
DB int |
||||
} |
||||
|
||||
type JWTConfig struct { |
||||
Secret string |
||||
Expire int // 小时
|
||||
} |
||||
|
||||
func LoadConfig() *Config { |
||||
viper.SetConfigName("config") |
||||
viper.SetConfigType("yaml") |
||||
viper.AddConfigPath(".") |
||||
viper.AutomaticEnv() |
||||
|
||||
if err := viper.ReadInConfig(); err != nil { |
||||
log.Fatalf("Error reading config file: %v", err) |
||||
} |
||||
|
||||
var cfg Config |
||||
if err := viper.Unmarshal(&cfg); err != nil { |
||||
log.Fatalf("Unable to decode into struct: %v", err) |
||||
} |
||||
|
||||
return &cfg |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
package controller |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"gofaster/internal/model" |
||||
"gofaster/internal/service" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
) |
||||
|
||||
type UserController struct { |
||||
userService *service.UserService |
||||
} |
||||
|
||||
func NewUserController(userService *service.UserService) *UserController { |
||||
return &UserController{userService: userService} |
||||
} |
||||
|
||||
func (c *UserController) RegisterRoutes(r *gin.RouterGroup) { |
||||
r.GET("/users", c.ListUsers) |
||||
r.POST("/users", c.CreateUser) |
||||
r.GET("/users/:id", c.GetUser) |
||||
r.PUT("/users/:id", c.UpdateUser) |
||||
r.DELETE("/users/:id", c.DeleteUser) |
||||
} |
||||
|
||||
func (c *UserController) ListUsers(ctx *gin.Context) { |
||||
// 实现分页查询
|
||||
} |
||||
|
||||
func (c *UserController) CreateUser(ctx *gin.Context) { |
||||
var user model.User |
||||
if err := ctx.ShouldBindJSON(&user); err != nil { |
||||
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
||||
return |
||||
} |
||||
|
||||
if err := c.userService.CreateUser(&user); err != nil { |
||||
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
||||
return |
||||
} |
||||
|
||||
ctx.JSON(http.StatusCreated, user) |
||||
} |
||||
|
||||
// 其他方法实现...
|
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package middleware |
||||
|
||||
import ( |
||||
"gofaster/pkg/auth" |
||||
"net/http" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
) |
||||
|
||||
func AuthMiddleware(jwtSecret string) gin.HandlerFunc { |
||||
return func(c *gin.Context) { |
||||
token := c.GetHeader("Authorization") |
||||
if token == "" { |
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未提供认证令牌"}) |
||||
return |
||||
} |
||||
|
||||
claims, err := auth.ParseToken(token, jwtSecret) |
||||
if err != nil { |
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "无效的认证令牌"}) |
||||
return |
||||
} |
||||
|
||||
c.Set("user_id", claims.UserID) |
||||
c.Next() |
||||
} |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
package middleware |
||||
|
||||
import ( |
||||
"bytes" |
||||
"gofaster/internal/model" |
||||
"io" |
||||
"time" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
"go.uber.org/zap" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
func LoggerMiddleware(logger *zap.Logger, db *gorm.DB) gin.HandlerFunc { |
||||
return func(c *gin.Context) { |
||||
start := time.Now() |
||||
path := c.Request.URL.Path |
||||
query := c.Request.URL.RawQuery |
||||
|
||||
// 记录请求体
|
||||
var requestBody string |
||||
if c.Request.Body != nil { |
||||
bodyBytes, _ := io.ReadAll(c.Request.Body) |
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) |
||||
requestBody = string(bodyBytes) |
||||
} |
||||
|
||||
// 处理请求
|
||||
c.Next() |
||||
|
||||
// 记录日志
|
||||
latency := time.Since(start).Milliseconds() |
||||
actionLog := model.ActionLog{ |
||||
UserID: getUserIdFromContext(c), |
||||
Action: getActionFromPath(path), |
||||
IP: c.ClientIP(), |
||||
UserAgent: c.Request.UserAgent(), |
||||
Path: path, |
||||
Method: c.Request.Method, |
||||
Request: requestBody, |
||||
Status: c.Writer.Status(), |
||||
Latency: latency, |
||||
} |
||||
|
||||
// 异步保存日志
|
||||
go func() { |
||||
if err := db.Create(&actionLog).Error; err != nil { |
||||
logger.Error("保存操作日志失败", zap.Error(err)) |
||||
} |
||||
}() |
||||
} |
||||
} |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
package middleware |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
) |
||||
|
||||
func PermissionMiddleware(permission string) gin.HandlerFunc { |
||||
return func(c *gin.Context) { |
||||
userID, exists := c.Get("user_id") |
||||
if !exists { |
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未认证"}) |
||||
return |
||||
} |
||||
|
||||
// 检查用户是否有该权限
|
||||
hasPerm := checkPermission(userID.(uint), permission) |
||||
if !hasPerm { |
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "没有权限"}) |
||||
return |
||||
} |
||||
|
||||
c.Next() |
||||
} |
||||
} |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
package model |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type BaseModel struct { |
||||
ID uint `gorm:"primarykey" json:"id"` |
||||
CreatedAt time.Time `json:"created_at"` |
||||
UpdatedAt time.Time `json:"updated_at"` |
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
package model |
||||
|
||||
type ActionLog struct { |
||||
BaseModel |
||||
UserID uint `json:"user_id"` |
||||
Username string `json:"username"` |
||||
Action string `json:"action"` |
||||
IP string `json:"ip"` |
||||
UserAgent string `json:"user_agent"` |
||||
Path string `json:"path"` |
||||
Method string `json:"method"` |
||||
Request string `json:"request" gorm:"type:text"` |
||||
Response string `json:"response" gorm:"type:text"` |
||||
Status int `json:"status"` |
||||
Latency int64 `json:"latency"` // 毫秒
|
||||
} |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
package model |
||||
|
||||
type Permission struct { |
||||
BaseModel |
||||
Name string `gorm:"uniqueIndex;size:50" json:"name"` |
||||
Description string `gorm:"size:200" json:"description"` |
||||
Resource string `gorm:"size:100" json:"resource"` |
||||
Action string `gorm:"size:50" json:"action"` // create, read, update, delete等
|
||||
} |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
package model |
||||
|
||||
type Role struct { |
||||
BaseModel |
||||
Name string `gorm:"uniqueIndex;size:50" json:"name"` |
||||
Description string `gorm:"size:200" json:"description"` |
||||
Permissions []Permission `gorm:"many2many:role_permissions;" json:"permissions"` |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
package model |
||||
|
||||
type User struct { |
||||
BaseModel |
||||
Username string `gorm:"uniqueIndex;size:50" json:"username"` |
||||
Password string `gorm:"size:100" json:"-"` |
||||
Email string `gorm:"size:100" json:"email"` |
||||
Phone string `gorm:"size:20" json:"phone"` |
||||
Status int `gorm:"default:1" json:"status"` // 1-正常 2-禁用
|
||||
Roles []Role `gorm:"many2many:user_roles;" json:"roles"` |
||||
} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
package model |
||||
|
||||
import "time" |
||||
|
||||
type Workflow struct { |
||||
BaseModel |
||||
Name string `json:"name"` |
||||
Description string `json:"description"` |
||||
Status int `json:"status"` // 1-启用 2-禁用
|
||||
Nodes []WorkflowNode `json:"nodes"` |
||||
} |
||||
|
||||
type WorkflowNode struct { |
||||
BaseModel |
||||
WorkflowID uint `json:"workflow_id"` |
||||
Name string `json:"name"` |
||||
Type string `json:"type"` // start, end, task, approval等
|
||||
Handler string `json:"handler"` // 处理函数
|
||||
NextNodes string `json:"next_nodes"` // 逗号分隔的下个节点ID
|
||||
Position int `json:"position"` |
||||
} |
||||
|
||||
type WorkflowInstance struct { |
||||
BaseModel |
||||
WorkflowID uint `json:"workflow_id"` |
||||
Status string `json:"status"` // running, completed, canceled
|
||||
CurrentNodeID uint `json:"current_node_id"` |
||||
Data string `json:"data" gorm:"type:text"` // JSON格式的业务数据
|
||||
Tasks []WorkflowTask `json:"tasks"` |
||||
} |
||||
|
||||
type WorkflowTask struct { |
||||
BaseModel |
||||
InstanceID uint `json:"instance_id"` |
||||
NodeID uint `json:"node_id"` |
||||
Status string `json:"status"` // pending, processing, completed, canceled
|
||||
Result string `json:"result" gorm:"type:text"` // JSON格式的处理结果
|
||||
Handler string `json:"handler"` |
||||
Assignee uint `json:"assignee"` // 处理人
|
||||
StartTime time.Time `json:"start_time"` |
||||
EndTime time.Time `json:"end_time"` |
||||
} |
@ -0,0 +1,72 @@
@@ -0,0 +1,72 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type ActionLogRepo struct { |
||||
BaseRepo |
||||
} |
||||
|
||||
func NewActionLogRepo(db *gorm.DB) *ActionLogRepo { |
||||
return &ActionLogRepo{BaseRepo{db: db}} |
||||
} |
||||
|
||||
func (r *ActionLogRepo) Create(log *model.ActionLog) error { |
||||
return r.db.Create(log).Error |
||||
} |
||||
|
||||
func (r *ActionLogRepo) GetByID(id uint) (*model.ActionLog, error) { |
||||
var log model.ActionLog |
||||
err := r.db.First(&log, id).Error |
||||
return &log, err |
||||
} |
||||
|
||||
func (r *ActionLogRepo) ListByUser(userID uint, page, pageSize int) ([]model.ActionLog, int64, error) { |
||||
var logs []model.ActionLog |
||||
var count int64 |
||||
|
||||
err := r.db.Model(&model.ActionLog{}).Where("user_id = ?", userID).Count(&count).Error |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
offset := (page - 1) * pageSize |
||||
err = r.db.Where("user_id = ?", userID).Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&logs).Error |
||||
return logs, count, err |
||||
} |
||||
|
||||
func (r *ActionLogRepo) List(page, pageSize int) ([]model.ActionLog, int64, error) { |
||||
var logs []model.ActionLog |
||||
var count int64 |
||||
|
||||
err := r.db.Model(&model.ActionLog{}).Count(&count).Error |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
offset := (page - 1) * pageSize |
||||
err = r.db.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&logs).Error |
||||
return logs, count, err |
||||
} |
||||
|
||||
func (r *ActionLogRepo) Search(query string, page, pageSize int) ([]model.ActionLog, int64, error) { |
||||
var logs []model.ActionLog |
||||
var count int64 |
||||
|
||||
searchQuery := "%" + query + "%" |
||||
countQuery := r.db.Model(&model.ActionLog{}). |
||||
Where("username LIKE ? OR action LIKE ? OR path LIKE ? OR method LIKE ?", |
||||
searchQuery, searchQuery, searchQuery, searchQuery) |
||||
|
||||
err := countQuery.Count(&count).Error |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
offset := (page - 1) * pageSize |
||||
err = countQuery.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&logs).Error |
||||
return logs, count, err |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type BaseRepo struct { |
||||
db *gorm.DB |
||||
} |
||||
|
||||
func NewBaseRepo(db *gorm.DB) *BaseRepo { |
||||
return &BaseRepo{db: db} |
||||
} |
||||
|
||||
func (r *BaseRepo) DB() *gorm.DB { |
||||
return r.db |
||||
} |
||||
|
||||
func (r *BaseRepo) Begin() *gorm.DB { |
||||
return r.db.Begin() |
||||
} |
||||
|
||||
func (r *BaseRepo) Commit(tx *gorm.DB) error { |
||||
return tx.Commit().Error |
||||
} |
||||
|
||||
func (r *BaseRepo) Rollback(tx *gorm.DB) error { |
||||
return tx.Rollback().Error |
||||
} |
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type PermissionRepo struct { |
||||
BaseRepo |
||||
} |
||||
|
||||
func NewPermissionRepo(db *gorm.DB) *PermissionRepo { |
||||
return &PermissionRepo{BaseRepo{db: db}} |
||||
} |
||||
|
||||
func (r *PermissionRepo) Create(permission *model.Permission) error { |
||||
return r.db.Create(permission).Error |
||||
} |
||||
|
||||
func (r *PermissionRepo) GetByID(id uint) (*model.Permission, error) { |
||||
var permission model.Permission |
||||
err := r.db.First(&permission, id).Error |
||||
return &permission, err |
||||
} |
||||
|
||||
func (r *PermissionRepo) GetByName(name string) (*model.Permission, error) { |
||||
var permission model.Permission |
||||
err := r.db.Where("name = ?", name).First(&permission).Error |
||||
return &permission, err |
||||
} |
||||
|
||||
func (r *PermissionRepo) Update(permission *model.Permission) error { |
||||
return r.db.Save(permission).Error |
||||
} |
||||
|
||||
func (r *PermissionRepo) Delete(id uint) error { |
||||
return r.db.Delete(&model.Permission{}, id).Error |
||||
} |
||||
|
||||
func (r *PermissionRepo) List(page, pageSize int) ([]model.Permission, int64, error) { |
||||
var permissions []model.Permission |
||||
var count int64 |
||||
|
||||
err := r.db.Model(&model.Permission{}).Count(&count).Error |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
offset := (page - 1) * pageSize |
||||
err = r.db.Offset(offset).Limit(pageSize).Find(&permissions).Error |
||||
return permissions, count, err |
||||
} |
||||
|
||||
func (r *PermissionRepo) GetByResourceAction(resource, action string) (*model.Permission, error) { |
||||
var permission model.Permission |
||||
err := r.db.Where("resource = ? AND action = ?", resource, action).First(&permission).Error |
||||
return &permission, err |
||||
} |
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type RoleRepo struct { |
||||
BaseRepo |
||||
} |
||||
|
||||
func NewRoleRepo(db *gorm.DB) *RoleRepo { |
||||
return &RoleRepo{BaseRepo{db: db}} |
||||
} |
||||
|
||||
func (r *RoleRepo) Create(role *model.Role) error { |
||||
return r.db.Create(role).Error |
||||
} |
||||
|
||||
func (r *RoleRepo) GetByID(id uint) (*model.Role, error) { |
||||
var role model.Role |
||||
err := r.db.Preload("Permissions").First(&role, id).Error |
||||
return &role, err |
||||
} |
||||
|
||||
func (r *RoleRepo) GetByName(name string) (*model.Role, error) { |
||||
var role model.Role |
||||
err := r.db.Preload("Permissions").Where("name = ?", name).First(&role).Error |
||||
return &role, err |
||||
} |
||||
|
||||
func (r *RoleRepo) Update(role *model.Role) error { |
||||
return r.db.Save(role).Error |
||||
} |
||||
|
||||
func (r *RoleRepo) Delete(id uint) error { |
||||
return r.db.Delete(&model.Role{}, id).Error |
||||
} |
||||
|
||||
func (r *RoleRepo) List(page, pageSize int) ([]model.Role, int64, error) { |
||||
var roles []model.Role |
||||
var count int64 |
||||
|
||||
err := r.db.Model(&model.Role{}).Count(&count).Error |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
offset := (page - 1) * pageSize |
||||
err = r.db.Preload("Permissions").Offset(offset).Limit(pageSize).Find(&roles).Error |
||||
return roles, count, err |
||||
} |
||||
|
||||
func (r *RoleRepo) AssignPermissions(roleID uint, permissionIDs []uint) error { |
||||
var permissions []model.Permission |
||||
if err := r.db.Find(&permissions, permissionIDs).Error; err != nil { |
||||
return err |
||||
} |
||||
return r.db.Model(&model.Role{BaseModel: model.BaseModel{ID: roleID}}).Association("Permissions").Replace(permissions) |
||||
} |
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type UserRepo struct { |
||||
BaseRepo |
||||
} |
||||
|
||||
func NewUserRepo(db *gorm.DB) *UserRepo { |
||||
return &UserRepo{BaseRepo{db: db}} |
||||
} |
||||
|
||||
func (r *UserRepo) Create(user *model.User) error { |
||||
return r.db.Create(user).Error |
||||
} |
||||
|
||||
func (r *UserRepo) GetByID(id uint) (*model.User, error) { |
||||
var user model.User |
||||
err := r.db.Preload("Roles.Permissions").First(&user, id).Error |
||||
return &user, err |
||||
} |
||||
|
||||
func (r *UserRepo) GetByUsername(username string) (*model.User, error) { |
||||
var user model.User |
||||
err := r.db.Preload("Roles.Permissions").Where("username = ?", username).First(&user).Error |
||||
return &user, err |
||||
} |
||||
|
||||
func (r *UserRepo) Update(user *model.User) error { |
||||
return r.db.Save(user).Error |
||||
} |
||||
|
||||
func (r *UserRepo) Delete(id uint) error { |
||||
return r.db.Delete(&model.User{}, id).Error |
||||
} |
||||
|
||||
func (r *UserRepo) List(page, pageSize int) ([]model.User, int64, error) { |
||||
var users []model.User |
||||
var count int64 |
||||
|
||||
err := r.db.Model(&model.User{}).Count(&count).Error |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
offset := (page - 1) * pageSize |
||||
err = r.db.Preload("Roles").Offset(offset).Limit(pageSize).Find(&users).Error |
||||
return users, count, err |
||||
} |
||||
|
||||
func (r *UserRepo) AssignRole(userID uint, roleIDs []uint) error { |
||||
var roles []model.Role |
||||
if err := r.db.Find(&roles, roleIDs).Error; err != nil { |
||||
return err |
||||
} |
||||
return r.db.Model(&model.User{BaseModel: model.BaseModel{ID: userID}}).Association("Roles").Replace(roles) |
||||
} |
@ -0,0 +1,128 @@
@@ -0,0 +1,128 @@
|
||||
package repository |
||||
|
||||
import ( |
||||
"gofaster/internal/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type WorkflowRepo struct { |
||||
BaseRepo |
||||
} |
||||
|
||||
func NewWorkflowRepo(db *gorm.DB) *WorkflowRepo { |
||||
return &WorkflowRepo{BaseRepo{db: db}} |
||||
} |
||||
|
||||
// Workflow CRUD
|
||||
func (r *WorkflowRepo) CreateWorkflow(workflow *model.Workflow) error { |
||||
return r.db.Create(workflow).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) GetWorkflowByID(id uint) (*model.Workflow, error) { |
||||
var workflow model.Workflow |
||||
err := r.db.Preload("Nodes").First(&workflow, id).Error |
||||
return &workflow, err |
||||
} |
||||
|
||||
func (r *WorkflowRepo) UpdateWorkflow(workflow *model.Workflow) error { |
||||
return r.db.Save(workflow).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) DeleteWorkflow(id uint) error { |
||||
return r.db.Delete(&model.Workflow{}, id).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) ListWorkflows(page, pageSize int) ([]model.Workflow, int64, error) { |
||||
var workflows []model.Workflow |
||||
var count int64 |
||||
|
||||
err := r.db.Model(&model.Workflow{}).Count(&count).Error |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
offset := (page - 1) * pageSize |
||||
err = r.db.Preload("Nodes").Offset(offset).Limit(pageSize).Find(&workflows).Error |
||||
return workflows, count, err |
||||
} |
||||
|
||||
// WorkflowNode operations
|
||||
func (r *WorkflowRepo) AddNode(node *model.WorkflowNode) error { |
||||
return r.db.Create(node).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) UpdateNode(node *model.WorkflowNode) error { |
||||
return r.db.Save(node).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) DeleteNode(id uint) error { |
||||
return r.db.Delete(&model.WorkflowNode{}, id).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) GetNodeByID(id uint) (*model.WorkflowNode, error) { |
||||
var node model.WorkflowNode |
||||
err := r.db.First(&node, id).Error |
||||
return &node, err |
||||
} |
||||
|
||||
// WorkflowInstance operations
|
||||
func (r *WorkflowRepo) CreateInstance(instance *model.WorkflowInstance) error { |
||||
return r.db.Create(instance).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) GetInstanceByID(id uint) (*model.WorkflowInstance, error) { |
||||
var instance model.WorkflowInstance |
||||
err := r.db.Preload("Tasks").First(&instance, id).Error |
||||
return &instance, err |
||||
} |
||||
|
||||
func (r *WorkflowRepo) UpdateInstance(instance *model.WorkflowInstance) error { |
||||
return r.db.Save(instance).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) ListInstances(workflowID uint, page, pageSize int) ([]model.WorkflowInstance, int64, error) { |
||||
var instances []model.WorkflowInstance |
||||
var count int64 |
||||
|
||||
query := r.db.Model(&model.WorkflowInstance{}) |
||||
if workflowID != 0 { |
||||
query = query.Where("workflow_id = ?", workflowID) |
||||
} |
||||
|
||||
err := query.Count(&count).Error |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
offset := (page - 1) * pageSize |
||||
err = query.Preload("Tasks").Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&instances).Error |
||||
return instances, count, err |
||||
} |
||||
|
||||
// WorkflowTask operations
|
||||
func (r *WorkflowRepo) CreateTask(task *model.WorkflowTask) error { |
||||
return r.db.Create(task).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) GetTaskByID(id uint) (*model.WorkflowTask, error) { |
||||
var task model.WorkflowTask |
||||
err := r.db.First(&task, id).Error |
||||
return &task, err |
||||
} |
||||
|
||||
func (r *WorkflowRepo) UpdateTask(task *model.WorkflowTask) error { |
||||
return r.db.Save(task).Error |
||||
} |
||||
|
||||
func (r *WorkflowRepo) ListTasksByInstance(instanceID uint) ([]model.WorkflowTask, error) { |
||||
var tasks []model.WorkflowTask |
||||
err := r.db.Where("instance_id = ?", instanceID).Order("created_at").Find(&tasks).Error |
||||
return tasks, err |
||||
} |
||||
|
||||
func (r *WorkflowRepo) ListPendingTasks() ([]model.WorkflowTask, error) { |
||||
var tasks []model.WorkflowTask |
||||
err := r.db.Where("status = ?", "pending").Order("created_at").Find(&tasks).Error |
||||
return tasks, err |
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"gofaster/internal/model" |
||||
"gofaster/internal/repository" |
||||
) |
||||
|
||||
type UserService struct { |
||||
repo *repository.UserRepo |
||||
} |
||||
|
||||
func NewUserService(repo *repository.UserRepo) *UserService { |
||||
return &UserService{repo: repo} |
||||
} |
||||
|
||||
func (s *UserService) CreateUser(user *model.User) error { |
||||
return s.repo.Create(user) |
||||
} |
||||
|
||||
func (s *UserService) GetUserByID(id uint) (*model.User, error) { |
||||
return s.repo.GetByID(id) |
||||
} |
||||
|
||||
func (s *UserService) UpdateUser(user *model.User) error { |
||||
return s.repo.Update(user) |
||||
} |
||||
|
||||
func (s *UserService) DeleteUser(id uint) error { |
||||
return s.repo.Delete(id) |
||||
} |
||||
|
||||
func (s *UserService) ListUsers(page, pageSize int) ([]*model.User, int64, error) { |
||||
return s.repo.List(page, pageSize) |
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
package service |
||||
|
||||
import ( |
||||
"gofaster/internal/model" |
||||
|
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type WorkflowEngine struct { |
||||
db *gorm.DB |
||||
} |
||||
|
||||
func NewWorkflowEngine(db *gorm.DB) *WorkflowEngine { |
||||
return &WorkflowEngine{db: db} |
||||
} |
||||
|
||||
func (e *WorkflowEngine) StartWorkflow(workflowID uint, data map[string]interface{}) (*model.WorkflowInstance, error) { |
||||
// 创建工作流实例
|
||||
// 找到开始节点
|
||||
// 创建第一个任务
|
||||
// 返回实例
|
||||
} |
||||
|
||||
func (e *WorkflowEngine) ProcessTask(taskID uint, userID uint, result map[string]interface{}) error { |
||||
// 处理任务
|
||||
// 根据节点类型执行不同逻辑
|
||||
// 更新任务状态
|
||||
// 根据结果决定下一个节点
|
||||
// 创建下一个任务
|
||||
} |
||||
|
||||
func (e *WorkflowEngine) GetUserTasks(userID uint, status string) ([]*model.WorkflowTask, error) { |
||||
// 获取用户的任务列表
|
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
package auth |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"github.com/golang-jwt/jwt/v5" |
||||
) |
||||
|
||||
type Claims struct { |
||||
UserID uint `json:"user_id"` |
||||
jwt.RegisteredClaims |
||||
} |
||||
|
||||
func GenerateToken(userID uint, secret string, expireHours int) (string, error) { |
||||
expireTime := time.Now().Add(time.Hour * time.Duration(expireHours)) |
||||
|
||||
claims := Claims{ |
||||
UserID: userID, |
||||
RegisteredClaims: jwt.RegisteredClaims{ |
||||
ExpiresAt: jwt.NewNumericDate(expireTime), |
||||
IssuedAt: jwt.NewNumericDate(time.Now()), |
||||
Issuer: "go-admin-platform", |
||||
}, |
||||
} |
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
||||
return token.SignedString([]byte(secret)) |
||||
} |
||||
|
||||
func ParseToken(tokenString, secret string) (*Claims, error) { |
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { |
||||
return []byte(secret), nil |
||||
}) |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if claims, ok := token.Claims.(*Claims); ok && token.Valid { |
||||
return claims, nil |
||||
} |
||||
|
||||
return nil, jwt.ErrInvalidKey |
||||
} |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
package database |
||||
|
||||
import ( |
||||
"fmt" |
||||
"gofaster/internal/config" |
||||
|
||||
"gorm.io/driver/postgres" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
func NewDB(cfg *config.DBConfig) (*gorm.DB, error) { |
||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Shanghai", |
||||
cfg.Host, cfg.User, cfg.Password, cfg.Name, cfg.Port) |
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// 自动迁移将在业务模块中单独处理
|
||||
return db, nil |
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous |
||||
*.class |
||||
*.log |
||||
*.pyc |
||||
*.swp |
||||
.DS_Store |
||||
.atom/ |
||||
.build/ |
||||
.buildlog/ |
||||
.history |
||||
.svn/ |
||||
.swiftpm/ |
||||
migrate_working_dir/ |
||||
|
||||
# IntelliJ related |
||||
*.iml |
||||
*.ipr |
||||
*.iws |
||||
.idea/ |
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in |
||||
# VS Code which you may wish to be included in version control, so this line |
||||
# is commented out by default. |
||||
#.vscode/ |
||||
|
||||
# Flutter/Dart/Pub related |
||||
**/doc/api/ |
||||
**/ios/Flutter/.last_build_id |
||||
.dart_tool/ |
||||
.flutter-plugins |
||||
.flutter-plugins-dependencies |
||||
.pub-cache/ |
||||
.pub/ |
||||
/build/ |
||||
|
||||
# Symbolication related |
||||
app.*.symbols |
||||
|
||||
# Obfuscation related |
||||
app.*.map.json |
||||
|
||||
# Android Studio will place build artifacts here |
||||
/android/app/debug |
||||
/android/app/profile |
||||
/android/app/release |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project. |
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc. |
||||
# |
||||
# This file should be version controlled and should not be manually edited. |
||||
|
||||
version: |
||||
revision: "ea121f8859e4b13e47a8f845e4586164519588bc" |
||||
channel: "stable" |
||||
|
||||
project_type: app |
||||
|
||||
# Tracks metadata for the flutter migrate command |
||||
migration: |
||||
platforms: |
||||
- platform: root |
||||
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc |
||||
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc |
||||
- platform: web |
||||
create_revision: ea121f8859e4b13e47a8f845e4586164519588bc |
||||
base_revision: ea121f8859e4b13e47a8f845e4586164519588bc |
||||
|
||||
# User provided section |
||||
|
||||
# List of Local paths (relative to this file) that should be |
||||
# ignored by the migrate tool. |
||||
# |
||||
# Files that are not part of the templates will be ignored by default. |
||||
unmanaged_files: |
||||
- 'lib/main.dart' |
||||
- 'ios/Runner.xcodeproj/project.pbxproj' |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
# frontend |
||||
|
||||
A new Flutter project. |
||||
|
||||
## Getting Started |
||||
|
||||
This project is a starting point for a Flutter application. |
||||
|
||||
A few resources to get you started if this is your first Flutter project: |
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) |
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) |
||||
|
||||
For help getting started with Flutter development, view the |
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials, |
||||
samples, guidance on mobile development, and a full API reference. |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to |
||||
# check for errors, warnings, and lints. |
||||
# |
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled |
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be |
||||
# invoked from the command line by running `flutter analyze`. |
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps, |
||||
# packages, and plugins designed to encourage good coding practices. |
||||
include: package:flutter_lints/flutter.yaml |
||||
|
||||
linter: |
||||
# The lint rules applied to this project can be customized in the |
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml` |
||||
# included above or to enable additional rules. A list of all available lints |
||||
# and their documentation is published at https://dart.dev/lints. |
||||
# |
||||
# Instead of disabling a lint rule for the entire project in the |
||||
# section below, it can also be suppressed for a single line of code |
||||
# or a specific dart file by using the `// ignore: name_of_lint` and |
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file |
||||
# producing the lint. |
||||
rules: |
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule |
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule |
||||
|
||||
# Additional information about this file can be found at |
||||
# https://dart.dev/guides/language/analysis-options |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
import 'package:dio/dio.dart'; |
||||
|
||||
class ApiClient { |
||||
final Dio _dio; |
||||
final String baseUrl; |
||||
|
||||
ApiClient({required this.baseUrl}) : _dio = Dio(BaseOptions(baseUrl: baseUrl)) { |
||||
_dio.interceptors.add(InterceptorsWrapper( |
||||
onRequest: (options, handler) { |
||||
// 添加认证token等 |
||||
return handler.next(options); |
||||
}, |
||||
onError: (error, handler) { |
||||
// 统一错误处理 |
||||
return handler.next(error); |
||||
}, |
||||
)); |
||||
} |
||||
|
||||
Future<Response> get(String path, {Map<String, dynamic>? params}) async { |
||||
return _dio.get(path, queryParameters: params); |
||||
} |
||||
|
||||
Future<Response> post(String path, dynamic data) async { |
||||
return _dio.post(path, data: data); |
||||
} |
||||
|
||||
// 其他HTTP方法... |
||||
} |
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
import 'package:flutter/material.dart'; |
||||
|
||||
void main() { |
||||
runApp(const MyApp()); |
||||
} |
||||
|
||||
class MyApp extends StatelessWidget { |
||||
const MyApp({super.key}); |
||||
|
||||
// This widget is the root of your application. |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return MaterialApp( |
||||
title: 'Flutter Demo', |
||||
theme: ThemeData( |
||||
// This is the theme of your application. |
||||
// |
||||
// TRY THIS: Try running your application with "flutter run". You'll see |
||||
// the application has a purple toolbar. Then, without quitting the app, |
||||
// try changing the seedColor in the colorScheme below to Colors.green |
||||
// and then invoke "hot reload" (save your changes or press the "hot |
||||
// reload" button in a Flutter-supported IDE, or press "r" if you used |
||||
// the command line to start the app). |
||||
// |
||||
// Notice that the counter didn't reset back to zero; the application |
||||
// state is not lost during the reload. To reset the state, use hot |
||||
// restart instead. |
||||
// |
||||
// This works for code too, not just values: Most code changes can be |
||||
// tested with just a hot reload. |
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), |
||||
), |
||||
home: const MyHomePage(title: 'Flutter Demo Home Page'), |
||||
); |
||||
} |
||||
} |
||||
|
||||
class MyHomePage extends StatefulWidget { |
||||
const MyHomePage({super.key, required this.title}); |
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning |
||||
// that it has a State object (defined below) that contains fields that affect |
||||
// how it looks. |
||||
|
||||
// This class is the configuration for the state. It holds the values (in this |
||||
// case the title) provided by the parent (in this case the App widget) and |
||||
// used by the build method of the State. Fields in a Widget subclass are |
||||
// always marked "final". |
||||
|
||||
final String title; |
||||
|
||||
@override |
||||
State<MyHomePage> createState() => _MyHomePageState(); |
||||
} |
||||
|
||||
class _MyHomePageState extends State<MyHomePage> { |
||||
int _counter = 0; |
||||
|
||||
void _incrementCounter() { |
||||
setState(() { |
||||
// This call to setState tells the Flutter framework that something has |
||||
// changed in this State, which causes it to rerun the build method below |
||||
// so that the display can reflect the updated values. If we changed |
||||
// _counter without calling setState(), then the build method would not be |
||||
// called again, and so nothing would appear to happen. |
||||
_counter++; |
||||
}); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
// This method is rerun every time setState is called, for instance as done |
||||
// by the _incrementCounter method above. |
||||
// |
||||
// The Flutter framework has been optimized to make rerunning build methods |
||||
// fast, so that you can just rebuild anything that needs updating rather |
||||
// than having to individually change instances of widgets. |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
// TRY THIS: Try changing the color here to a specific color (to |
||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar |
||||
// change color while the other colors stay the same. |
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary, |
||||
// Here we take the value from the MyHomePage object that was created by |
||||
// the App.build method, and use it to set our appbar title. |
||||
title: Text(widget.title), |
||||
), |
||||
body: Center( |
||||
// Center is a layout widget. It takes a single child and positions it |
||||
// in the middle of the parent. |
||||
child: Column( |
||||
// Column is also a layout widget. It takes a list of children and |
||||
// arranges them vertically. By default, it sizes itself to fit its |
||||
// children horizontally, and tries to be as tall as its parent. |
||||
// |
||||
// Column has various properties to control how it sizes itself and |
||||
// how it positions its children. Here we use mainAxisAlignment to |
||||
// center the children vertically; the main axis here is the vertical |
||||
// axis because Columns are vertical (the cross axis would be |
||||
// horizontal). |
||||
// |
||||
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" |
||||
// action in the IDE, or press "p" in the console), to see the |
||||
// wireframe for each widget. |
||||
mainAxisAlignment: MainAxisAlignment.center, |
||||
children: <Widget>[ |
||||
const Text('You have pushed the button this many times:'), |
||||
Text( |
||||
'$_counter', |
||||
style: Theme.of(context).textTheme.headlineMedium, |
||||
), |
||||
], |
||||
), |
||||
), |
||||
floatingActionButton: FloatingActionButton( |
||||
onPressed: _incrementCounter, |
||||
tooltip: 'Increment', |
||||
child: const Icon(Icons.add), |
||||
), // This trailing comma makes auto-formatting nicer for build methods. |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
||||
|
||||
class UserListPage extends ConsumerWidget { |
||||
const UserListPage({super.key}); |
||||
|
||||
@override |
||||
Widget build(BuildContext context, WidgetRef ref) { |
||||
final usersAsync = ref.watch(usersProvider); |
||||
|
||||
return Scaffold( |
||||
appBar: AppBar(title: const Text('用户管理')), |
||||
body: usersAsync.when( |
||||
loading: () => const Center(child: CircularProgressIndicator()), |
||||
error: (error, stack) => Center(child: Text('Error: $error')), |
||||
data: (users) => ListView.builder( |
||||
itemCount: users.length, |
||||
itemBuilder: (context, index) { |
||||
final user = users[index]; |
||||
return ListTile( |
||||
title: Text(user['username']), |
||||
subtitle: Text(user['email']), |
||||
trailing: IconButton( |
||||
icon: const Icon(Icons.edit), |
||||
onPressed: () { |
||||
// 编辑用户 |
||||
}, |
||||
), |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
floatingActionButton: FloatingActionButton( |
||||
onPressed: () { |
||||
// 添加用户 |
||||
}, |
||||
child: const Icon(Icons.add), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart'; |
||||
import '../api/client.dart'; |
||||
|
||||
class UserService { |
||||
final ApiClient _client; |
||||
|
||||
UserService(this._client); |
||||
|
||||
Future<Map<String, dynamic>> listUsers({int page = 1, int pageSize = 10}) async { |
||||
final response = await _client.get('/users', params: { |
||||
'page': page, |
||||
'pageSize': pageSize, |
||||
}); |
||||
return response.data; |
||||
} |
||||
|
||||
// 其他用户相关方法... |
||||
} |
||||
|
||||
final userServiceProvider = Provider<UserService>((ref) { |
||||
final client = ref.read(apiClientProvider); |
||||
return UserService(client); |
||||
}); |
@ -0,0 +1,277 @@
@@ -0,0 +1,277 @@
|
||||
# Generated by pub |
||||
# See https://dart.dev/tools/pub/glossary#lockfile |
||||
packages: |
||||
async: |
||||
dependency: transitive |
||||
description: |
||||
name: async |
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "2.12.0" |
||||
boolean_selector: |
||||
dependency: transitive |
||||
description: |
||||
name: boolean_selector |
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "2.1.2" |
||||
characters: |
||||
dependency: transitive |
||||
description: |
||||
name: characters |
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.4.0" |
||||
clock: |
||||
dependency: transitive |
||||
description: |
||||
name: clock |
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.1.2" |
||||
collection: |
||||
dependency: transitive |
||||
description: |
||||
name: collection |
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.19.1" |
||||
cupertino_icons: |
||||
dependency: "direct main" |
||||
description: |
||||
name: cupertino_icons |
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.0.8" |
||||
dio: |
||||
dependency: "direct main" |
||||
description: |
||||
name: dio |
||||
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "5.8.0+1" |
||||
dio_web_adapter: |
||||
dependency: transitive |
||||
description: |
||||
name: dio_web_adapter |
||||
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "2.1.1" |
||||
fake_async: |
||||
dependency: transitive |
||||
description: |
||||
name: fake_async |
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.3.2" |
||||
flutter: |
||||
dependency: "direct main" |
||||
description: flutter |
||||
source: sdk |
||||
version: "0.0.0" |
||||
flutter_lints: |
||||
dependency: "direct dev" |
||||
description: |
||||
name: flutter_lints |
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "5.0.0" |
||||
flutter_riverpod: |
||||
dependency: "direct main" |
||||
description: |
||||
name: flutter_riverpod |
||||
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "2.6.1" |
||||
flutter_test: |
||||
dependency: "direct dev" |
||||
description: flutter |
||||
source: sdk |
||||
version: "0.0.0" |
||||
http_parser: |
||||
dependency: transitive |
||||
description: |
||||
name: http_parser |
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "4.1.2" |
||||
leak_tracker: |
||||
dependency: transitive |
||||
description: |
||||
name: leak_tracker |
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "10.0.8" |
||||
leak_tracker_flutter_testing: |
||||
dependency: transitive |
||||
description: |
||||
name: leak_tracker_flutter_testing |
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "3.0.9" |
||||
leak_tracker_testing: |
||||
dependency: transitive |
||||
description: |
||||
name: leak_tracker_testing |
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "3.0.1" |
||||
lints: |
||||
dependency: transitive |
||||
description: |
||||
name: lints |
||||
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "5.1.1" |
||||
matcher: |
||||
dependency: transitive |
||||
description: |
||||
name: matcher |
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "0.12.17" |
||||
material_color_utilities: |
||||
dependency: transitive |
||||
description: |
||||
name: material_color_utilities |
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "0.11.1" |
||||
meta: |
||||
dependency: transitive |
||||
description: |
||||
name: meta |
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.16.0" |
||||
path: |
||||
dependency: transitive |
||||
description: |
||||
name: path |
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.9.1" |
||||
riverpod: |
||||
dependency: "direct main" |
||||
description: |
||||
name: riverpod |
||||
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "2.6.1" |
||||
sky_engine: |
||||
dependency: transitive |
||||
description: flutter |
||||
source: sdk |
||||
version: "0.0.0" |
||||
source_span: |
||||
dependency: transitive |
||||
description: |
||||
name: source_span |
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.10.1" |
||||
stack_trace: |
||||
dependency: transitive |
||||
description: |
||||
name: stack_trace |
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.12.1" |
||||
state_notifier: |
||||
dependency: transitive |
||||
description: |
||||
name: state_notifier |
||||
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.0.0" |
||||
stream_channel: |
||||
dependency: transitive |
||||
description: |
||||
name: stream_channel |
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "2.1.4" |
||||
string_scanner: |
||||
dependency: transitive |
||||
description: |
||||
name: string_scanner |
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.4.1" |
||||
term_glyph: |
||||
dependency: transitive |
||||
description: |
||||
name: term_glyph |
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.2.2" |
||||
test_api: |
||||
dependency: transitive |
||||
description: |
||||
name: test_api |
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "0.7.4" |
||||
typed_data: |
||||
dependency: transitive |
||||
description: |
||||
name: typed_data |
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.4.0" |
||||
vector_math: |
||||
dependency: transitive |
||||
description: |
||||
name: vector_math |
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "2.1.4" |
||||
vm_service: |
||||
dependency: transitive |
||||
description: |
||||
name: vm_service |
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "14.3.1" |
||||
web: |
||||
dependency: transitive |
||||
description: |
||||
name: web |
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" |
||||
url: "https://pub.flutter-io.cn" |
||||
source: hosted |
||||
version: "1.1.1" |
||||
sdks: |
||||
dart: ">=3.7.2 <4.0.0" |
||||
flutter: ">=3.18.0-18.0.pre.54" |
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
name: frontend |
||||
description: "A new Flutter project." |
||||
# The following line prevents the package from being accidentally published to |
||||
# pub.dev using `flutter pub publish`. This is preferred for private packages. |
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev |
||||
|
||||
# The following defines the version and build number for your application. |
||||
# A version number is three numbers separated by dots, like 1.2.43 |
||||
# followed by an optional build number separated by a +. |
||||
# Both the version and the builder number may be overridden in flutter |
||||
# build by specifying --build-name and --build-number, respectively. |
||||
# In Android, build-name is used as versionName while build-number used as versionCode. |
||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning |
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. |
||||
# Read more about iOS versioning at |
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html |
||||
# In Windows, build-name is used as the major, minor, and patch parts |
||||
# of the product and file versions while build-number is used as the build suffix. |
||||
version: 1.0.0+1 |
||||
|
||||
environment: |
||||
sdk: ^3.7.2 |
||||
|
||||
# Dependencies specify other packages that your package needs in order to work. |
||||
# To automatically upgrade your package dependencies to the latest versions |
||||
# consider running `flutter pub upgrade --major-versions`. Alternatively, |
||||
# dependencies can be manually updated by changing the version numbers below to |
||||
# the latest version available on pub.dev. To see which dependencies have newer |
||||
# versions available, run `flutter pub outdated`. |
||||
dependencies: |
||||
flutter: |
||||
sdk: flutter |
||||
|
||||
# The following adds the Cupertino Icons font to your application. |
||||
# Use with the CupertinoIcons class for iOS style icons. |
||||
cupertino_icons: ^1.0.8 |
||||
dio: ^5.8.0+1 |
||||
riverpod: ^2.6.1 |
||||
flutter_riverpod: ^2.6.1 |
||||
|
||||
dev_dependencies: |
||||
flutter_test: |
||||
sdk: flutter |
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to |
||||
# encourage good coding practices. The lint set provided by the package is |
||||
# activated in the `analysis_options.yaml` file located at the root of your |
||||
# package. See that file for information about deactivating specific lint |
||||
# rules and activating additional ones. |
||||
flutter_lints: ^5.0.0 |
||||
|
||||
# For information on the generic Dart part of this file, see the |
||||
# following page: https://dart.dev/tools/pub/pubspec |
||||
|
||||
# The following section is specific to Flutter packages. |
||||
flutter: |
||||
|
||||
# The following line ensures that the Material Icons font is |
||||
# included with your application, so that you can use the icons in |
||||
# the material Icons class. |
||||
uses-material-design: true |
||||
|
||||
# To add assets to your application, add an assets section, like this: |
||||
# assets: |
||||
# - images/a_dot_burr.jpeg |
||||
# - images/a_dot_ham.jpeg |
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see |
||||
# https://flutter.dev/to/resolution-aware-images |
||||
|
||||
# For details regarding adding assets from package dependencies, see |
||||
# https://flutter.dev/to/asset-from-package |
||||
|
||||
# To add custom fonts to your application, add a fonts section here, |
||||
# in this "flutter" section. Each entry in this list should have a |
||||
# "family" key with the font family name, and a "fonts" key with a |
||||
# list giving the asset and other descriptors for the font. For |
||||
# example: |
||||
# fonts: |
||||
# - family: Schyler |
||||
# fonts: |
||||
# - asset: fonts/Schyler-Regular.ttf |
||||
# - asset: fonts/Schyler-Italic.ttf |
||||
# style: italic |
||||
# - family: Trajan Pro |
||||
# fonts: |
||||
# - asset: fonts/TrajanPro.ttf |
||||
# - asset: fonts/TrajanPro_Bold.ttf |
||||
# weight: 700 |
||||
# |
||||
# For details regarding fonts from package dependencies, |
||||
# see https://flutter.dev/to/font-from-package |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
// This is a basic Flutter widget test. |
||||
// |
||||
// To perform an interaction with a widget in your test, use the WidgetTester |
||||
// utility in the flutter_test package. For example, you can send tap and scroll |
||||
// gestures. You can also use WidgetTester to find child widgets in the widget |
||||
// tree, read text, and verify that the values of widget properties are correct. |
||||
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_test/flutter_test.dart'; |
||||
|
||||
import 'package:frontend/main.dart'; |
||||
|
||||
void main() { |
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async { |
||||
// Build our app and trigger a frame. |
||||
await tester.pumpWidget(const MyApp()); |
||||
|
||||
// Verify that our counter starts at 0. |
||||
expect(find.text('0'), findsOneWidget); |
||||
expect(find.text('1'), findsNothing); |
||||
|
||||
// Tap the '+' icon and trigger a frame. |
||||
await tester.tap(find.byIcon(Icons.add)); |
||||
await tester.pump(); |
||||
|
||||
// Verify that our counter has incremented. |
||||
expect(find.text('0'), findsNothing); |
||||
expect(find.text('1'), findsOneWidget); |
||||
}); |
||||
} |
After Width: | Height: | Size: 917 B |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<!-- |
||||
If you are serving your web app in a path other than the root, change the |
||||
href value below to reflect the base path you are serving from. |
||||
|
||||
The path provided below has to start and end with a slash "/" in order for |
||||
it to work correctly. |
||||
|
||||
For more details: |
||||
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base |
||||
|
||||
This is a placeholder for base href that will be replaced by the value of |
||||
the `--base-href` argument provided to `flutter build`. |
||||
--> |
||||
<base href="$FLUTTER_BASE_HREF"> |
||||
|
||||
<meta charset="UTF-8"> |
||||
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> |
||||
<meta name="description" content="A new Flutter project."> |
||||
|
||||
<!-- iOS meta tags & icons --> |
||||
<meta name="mobile-web-app-capable" content="yes"> |
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black"> |
||||
<meta name="apple-mobile-web-app-title" content="frontend"> |
||||
<link rel="apple-touch-icon" href="icons/Icon-192.png"> |
||||
|
||||
<!-- Favicon --> |
||||
<link rel="icon" type="image/png" href="favicon.png"/> |
||||
|
||||
<title>frontend</title> |
||||
<link rel="manifest" href="manifest.json"> |
||||
</head> |
||||
<body> |
||||
<script src="flutter_bootstrap.js" async></script> |
||||
</body> |
||||
</html> |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
{ |
||||
"name": "frontend", |
||||
"short_name": "frontend", |
||||
"start_url": ".", |
||||
"display": "standalone", |
||||
"background_color": "#0175C2", |
||||
"theme_color": "#0175C2", |
||||
"description": "A new Flutter project.", |
||||
"orientation": "portrait-primary", |
||||
"prefer_related_applications": false, |
||||
"icons": [ |
||||
{ |
||||
"src": "icons/Icon-192.png", |
||||
"sizes": "192x192", |
||||
"type": "image/png" |
||||
}, |
||||
{ |
||||
"src": "icons/Icon-512.png", |
||||
"sizes": "512x512", |
||||
"type": "image/png" |
||||
}, |
||||
{ |
||||
"src": "icons/Icon-maskable-192.png", |
||||
"sizes": "192x192", |
||||
"type": "image/png", |
||||
"purpose": "maskable" |
||||
}, |
||||
{ |
||||
"src": "icons/Icon-maskable-512.png", |
||||
"sizes": "512x512", |
||||
"type": "image/png", |
||||
"purpose": "maskable" |
||||
} |
||||
] |
||||
} |
Loading…
Reference in new issue