restructure

This commit is contained in:
yanghao05
2023-04-20 12:11:34 +08:00
parent 6757e00d73
commit 5b8eca5d87
120 changed files with 546 additions and 7303 deletions

View File

@@ -1,20 +1,16 @@
package captcha
import (
"atom/container"
"atom/providers/config"
"errors"
"log"
"time"
"github.com/mojocn/base64Captcha"
"github.com/rogeecn/atom/container"
"github.com/spf13/viper"
"go.uber.org/dig"
)
func init() {
if err := container.Container.Provide(NewCaptcha); err != nil {
log.Fatal(err)
}
}
type CaptchaResponse struct {
CaptchaId string `json:"captcha_id,omitempty"`
PicPath string `json:"pic_path,omitempty"`
@@ -23,16 +19,32 @@ type CaptchaResponse struct {
}
type Captcha struct {
conf *config.Config
captcha *base64Captcha.Captcha
}
func NewCaptcha(conf *config.Config, driver base64Captcha.Driver) (*Captcha, error) {
var store = base64Captcha.DefaultMemStore
return &Captcha{
conf: conf,
captcha: base64Captcha.NewCaptcha(driver, store),
}, nil
func Provide(conf *Config, opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*Captcha, error) {
driver := base64Captcha.NewDriverDigit(
int(conf.Width),
int(conf.Height),
int(conf.Long),
conf.MaxScrew,
conf.DotCount,
)
store := base64Captcha.DefaultMemStore
return &Captcha{
captcha: base64Captcha.NewCaptcha(driver, store),
}, nil
})
}
func (c *Captcha) OpenCaptchaTimeOutDuration() time.Duration {
d, err := time.ParseDuration(viper.GetString("CAPTCHA_IMG_OPEN_TIMEOUT"))
if err != nil {
log.Panic(err)
}
return d
}
func (c *Captcha) Generate() (*CaptchaResponse, error) {
@@ -44,8 +56,8 @@ func (c *Captcha) Generate() (*CaptchaResponse, error) {
return &CaptchaResponse{
CaptchaId: id,
PicPath: b64s,
CaptchaLength: c.conf.Captcha.KeyLong,
OpenCaptcha: c.conf.Captcha.OpenCaptcha,
CaptchaLength: viper.GetUint("CAPTCHA_IMG_KEY_LONG"),
OpenCaptcha: viper.GetUint("CAPTCHA_IMG_OPEN"),
}, nil
}

View File

@@ -0,0 +1,25 @@
package captcha
import (
"log"
"time"
)
type Config struct {
Long uint // 验证码长度
Width uint // 验证码宽度
Height uint // 验证码高度
Open uint // 防爆破验证码开启此数0代表每次登录都需要验证码其他数字代表错误密码此数如3代表错误三次后出现验证码
OpenTimeOut string // 防爆破验证码超时时间单位s(秒)
MaxScrew float64 // MaxSkew max absolute skew factor of a single digit.
DotCount int // Number of background circles.
}
func (c *Config) OpenCaptchaTimeOutDuration() time.Duration {
d, err := time.ParseDuration(c.OpenTimeOut)
if err != nil {
log.Panic(err)
}
return d
}

View File

@@ -1,26 +0,0 @@
package storage
import (
"atom/container"
"atom/providers/config"
"log"
"github.com/mojocn/base64Captcha"
)
func init() {
if err := container.Container.Provide(NewCaptchaDriverDigit); err != nil {
log.Fatal(err)
}
}
func NewCaptchaDriverDigit(conf *config.Config) (base64Captcha.Driver, error) {
// 字符,公式,验证码配置
// 生成默认数字的driver
return base64Captcha.NewDriverDigit(
int(conf.Captcha.ImgHeight),
int(conf.Captcha.ImgWidth),
int(conf.Captcha.KeyLong),
0.7,
80), nil
}

View File

@@ -1,56 +0,0 @@
package config
import (
"atom/container"
"atom/utils"
"atom/utils/fs"
"log"
"github.com/pkg/errors"
"github.com/rogeecn/fabfile"
"github.com/spf13/viper"
)
type Config struct {
App App
Captcha Captcha
Http Http
Log Log
Database Database
Storage Storage
}
func init() {
if err := container.Container.Provide(Load); err != nil {
log.Fatal(err)
}
}
func Load() (*Config, error) {
var err error
confFile := utils.ShareConfigFile
if confFile == "" {
confFile, err = fabfile.Find("config.toml")
if err != nil {
return nil, err
}
}
path, name, _ := fs.FilePathInfo(confFile)
viper.SetConfigName(name) // name of config file (without extension)
viper.SetConfigType("toml") // REQUIRED if the config file does not have the extension in the name
viper.AddConfigPath("$HOME/") // call multiple times to add many search paths
viper.AddConfigPath(path) // optionally look for config in the working directory
viper.AddConfigPath(".") // optionally look for config in the working directory
// Find and read the config file
if err := viper.ReadInConfig(); err != nil { // Handle errors reading the config file
return nil, errors.Wrapf(err, "read config failed, %s", confFile)
}
config := &Config{}
if err := viper.Unmarshal(&config); err != nil {
return nil, errors.Wrapf(err, "unmarshal data failed, %s", confFile)
}
return config, nil
}

View File

@@ -1,18 +0,0 @@
package config
import "atom/utils"
type App struct {
Mode string
}
func (a App) IsDebug() bool {
return a.Mode == "debug"
}
func (a App) IsRelease() bool {
return a.Mode == "release"
}
func (a App) IsTesting() bool {
return a.Mode == "testing" || utils.IsInTesting()
}

View File

@@ -1,22 +0,0 @@
package config
import (
"log"
"time"
)
type Captcha struct {
KeyLong uint // 验证码长度
ImgWidth uint // 验证码宽度
ImgHeight uint // 验证码高度
OpenCaptcha uint // 防爆破验证码开启此数0代表每次登录都需要验证码其他数字代表错误密码此数如3代表错误三次后出现验证码
OpenCaptchaTimeOut string // 防爆破验证码超时时间单位s(秒)
}
func (c *Captcha) OpenCaptchaTimeOutDuration() time.Duration {
d, err := time.ParseDuration(c.OpenCaptchaTimeOut)
if err != nil {
log.Panic(err)
}
return d
}

View File

@@ -1,109 +0,0 @@
package config
import (
"fmt"
)
// Database database config
type Database struct {
Driver string
MySQL *MySQL
SQLite *SQLite
Redis *Redis
PostgreSQL *PostgreSQL
}
// MySQL database config
type MySQL struct {
Host string
Port uint
Database string
Username string
Password string
Prefix string // 表前缀
Singular bool // 是否开启全局禁用复数true表示开启
MaxIdleConns int // 空闲中的最大连接数
MaxOpenConns int // 打开到数据库的最大连接数
Engine string // 数据库引擎默认InnoDB
}
func (m *MySQL) CreateDatabaseSql() string {
return fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", m.Database)
}
func (m *MySQL) EmptyDsn() string {
dsnTpl := "%s@tcp(%s:%d)/"
authString := func() string {
if len(m.Password) > 0 {
return m.Username + ":" + m.Password
}
return m.Username
}
return fmt.Sprintf(dsnTpl, authString(), m.Host, m.Port)
}
// DSN connection dsn
func (m *MySQL) DSN() string {
dsnTpl := "%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local"
authString := func() string {
if len(m.Password) > 0 {
return m.Username + ":" + m.Password
}
return m.Username
}
return fmt.Sprintf(dsnTpl, authString(), m.Host, m.Port, m.Database)
}
type PostgreSQL struct {
Username string
Password string
Database string
Host string
Port uint
SslMode string
TimeZone string
Prefix string // 表前缀
Singular bool // 是否开启全局禁用复数true表示开启
MaxIdleConns int // 空闲中的最大连接数
MaxOpenConns int // 打开到数据库的最大连接数
}
func (m *PostgreSQL) EmptyDsn() string {
dsnTpl := "host=%s user=%s password=%s port=%d dbname=postgres sslmode=disable TimeZone=Asia/Shanghai"
return fmt.Sprintf(dsnTpl, m.Host, m.Username, m.Password, m.Port)
}
// DSN connection dsn
func (m *PostgreSQL) DSN() string {
dsnTpl := "host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s"
return fmt.Sprintf(dsnTpl, m.Host, m.Username, m.Password, m.Database, m.Port, m.SslMode, m.TimeZone)
}
type Redis struct {
Host string
Port uint
Database uint
Username string
Password string
}
// DSN connection dsn
func (m *Redis) DSN() string {
dsnTpl := "%s:%d"
return fmt.Sprintf(dsnTpl, m.Host, m.Port)
}
type SQLite struct {
File string
}
func (m *SQLite) CreateDatabaseSql() string {
return ""
}
func (m *SQLite) EmptyDsn() string {
return m.File
}

View File

@@ -1,5 +0,0 @@
package config
type Log struct {
Level string
}

View File

@@ -1,29 +0,0 @@
package config
type Storage struct {
Driver string
AliYunOSS AliYunOSS
AwsS3 AwsS3
}
type AliYunOSS struct {
Bucket string
Region string
Endpoint string
AccessKeyID string
AccessKeySecret string
BaseURL string
Path string
}
type AwsS3 struct {
Bucket string
Region string
Endpoint string
DisableSSL bool
SecretID string
SecretKey string
BaseURL string
Path string
S3ForcePathStyle bool
}

View File

@@ -1,35 +0,0 @@
package database
import (
"atom/container"
"atom/providers/config"
"errors"
"log"
"gorm.io/gorm"
)
const (
DriverMySQL = "mysql"
DriverSQLite = "sqlite"
DriverPostgres = "postgres"
DriverSQLServer = "sqlserver"
)
func init() {
if err := container.Container.Provide(NewDatabase); err != nil {
log.Fatal(err)
}
}
func NewDatabase(config *config.Config) (*gorm.DB, error) {
switch config.Database.Driver {
case DriverMySQL:
return NewMySQL(config.Database.MySQL)
case DriverSQLite:
return NewSQLite(config.Database.SQLite)
case DriverPostgres:
return NewPostgres(config.Database.PostgreSQL)
}
return nil, errors.New("failed to connect to db")
}

View File

@@ -1,73 +0,0 @@
package database
import (
"atom/providers/config"
"atom/providers/log"
"database/sql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
func NewMySQL(conf *config.MySQL) (*gorm.DB, error) {
if err := createMySQLDatabase(conf.EmptyDsn(), "mysql", conf.CreateDatabaseSql()); err != nil {
return nil, err
}
mysqlConfig := mysql.Config{
DSN: conf.DSN(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
gormConfig := gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: conf.Prefix,
SingularTable: conf.Singular,
},
DisableForeignKeyConstraintWhenMigrating: true,
}
// TODO: config logger
// _default := logger.New(NewWriter(log.New(os.Stdout, "\r\n", log.LstdFlags)), logger.Config{
// SlowThreshold: 200 * time.Millisecond,
// LogLevel: logger.Warn,
// Colorful: true,
// })
// conf.Logger = _default.LogMode(logger.Warn)
db, err := gorm.Open(mysql.New(mysqlConfig), &gormConfig)
if err != nil {
return nil, err
}
// config instance
db.InstanceSet("gorm:table_options", "ENGINE="+conf.Engine)
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
return db, err
}
// createDatabase 创建数据库
func createMySQLDatabase(dsn, driver, createSql string) error {
db, err := sql.Open(driver, dsn)
if err != nil {
return err
}
defer func(db *sql.DB) {
err = db.Close()
if err != nil {
log.Error(err)
}
}(db)
err = db.Ping()
if err != nil {
return err
}
_, err = db.Exec(createSql)
return err
}

View File

@@ -0,0 +1,50 @@
package mysql
import (
"fmt"
)
// MySQL database config
type Config struct {
Host string
Port uint
Database string
Username string
Password string
Prefix string // 表前缀
Singular bool // 是否开启全局禁用复数true表示开启
MaxIdleConns int // 空闲中的最大连接数
MaxOpenConns int // 打开到数据库的最大连接数
Engine string // 数据库引擎默认InnoDB
}
func (m *Config) CreateDatabaseSql() string {
return fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", m.Database)
}
func (m *Config) EmptyDsn() string {
dsnTpl := "%s@tcp(%s:%d)/"
authString := func() string {
if len(m.Password) > 0 {
return m.Username + ":" + m.Password
}
return m.Username
}
return fmt.Sprintf(dsnTpl, authString(), m.Host, m.Port)
}
// DSN connection dsn
func (m *Config) DSN() string {
dsnTpl := "%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local"
authString := func() string {
if len(m.Password) > 0 {
return m.Username + ":" + m.Password
}
return m.Username
}
return fmt.Sprintf(dsnTpl, authString(), m.Host, m.Port, m.Database)
}

View File

@@ -0,0 +1,77 @@
package mysql
import (
"database/sql"
"github.com/rogeecn/atom/container"
"github.com/rogeecn/atom/providers/log"
"go.uber.org/dig"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
func Provide(conf *Config, opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*gorm.DB, error) {
if err := createMySQLDatabase(conf.EmptyDsn(), "mysql", conf.CreateDatabaseSql()); err != nil {
return nil, err
}
mysqlConfig := mysql.Config{
DSN: conf.DSN(), // DSN data source name
DefaultStringSize: 191, // string 类型字段的默认长度
SkipInitializeWithVersion: false, // 根据版本自动配置
}
gormConfig := gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: conf.Prefix,
SingularTable: conf.Singular,
},
DisableForeignKeyConstraintWhenMigrating: true,
}
// TODO: config logger
// _default := logger.New(NewWriter(log.New(os.Stdout, "\r\n", log.LstdFlags)), logger.Config{
// SlowThreshold: 200 * time.Millisecond,
// LogLevel: logger.Warn,
// Colorful: true,
// })
// conf.Logger = _default.LogMode(logger.Warn)
db, err := gorm.Open(mysql.New(mysqlConfig), &gormConfig)
if err != nil {
return nil, err
}
// config instance
db.InstanceSet("gorm:table_options", "ENGINE="+conf.Engine)
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
return db, err
}, opts...)
}
// createDatabase 创建数据库
func createMySQLDatabase(dsn, driver, createSql string) error {
db, err := sql.Open(driver, dsn)
if err != nil {
return err
}
defer func(db *sql.DB) {
err = db.Close()
if err != nil {
log.Error(err)
}
}(db)
err = db.Ping()
if err != nil {
return err
}
_, err = db.Exec(createSql)
return err
}

View File

@@ -1,36 +0,0 @@
package database
import (
"atom/providers/config"
"log"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
func NewPostgres(conf *config.PostgreSQL) (*gorm.DB, error) {
dbConfig := postgres.Config{
DSN: conf.DSN(), // DSN data source name
}
log.Println("PostgreSQL DSN: ", dbConfig.DSN)
gormConfig := gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: conf.Prefix,
SingularTable: conf.Singular,
},
DisableForeignKeyConstraintWhenMigrating: true,
}
db, err := gorm.Open(postgres.New(dbConfig), &gormConfig)
if err != nil {
return nil, err
}
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
return db, err
}

View File

@@ -0,0 +1,31 @@
package postgres
import (
"fmt"
)
type Config struct {
Username string
Password string
Database string
Host string
Port uint
SslMode string
TimeZone string
Prefix string // 表前缀
Singular bool // 是否开启全局禁用复数true表示开启
MaxIdleConns int // 空闲中的最大连接数
MaxOpenConns int // 打开到数据库的最大连接数
}
func (m *Config) EmptyDsn() string {
dsnTpl := "host=%s user=%s password=%s port=%d dbname=postgres sslmode=disable TimeZone=Asia/Shanghai"
return fmt.Sprintf(dsnTpl, m.Host, m.Username, m.Password, m.Port)
}
// DSN connection dsn
func (m *Config) DSN() string {
dsnTpl := "host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s"
return fmt.Sprintf(dsnTpl, m.Host, m.Username, m.Password, m.Database, m.Port, m.SslMode, m.TimeZone)
}

View File

@@ -0,0 +1,40 @@
package postgres
import (
"log"
"github.com/rogeecn/atom/container"
"go.uber.org/dig"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
func Provide(conf *Config, opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*gorm.DB, error) {
dbConfig := postgres.Config{
DSN: conf.DSN(), // DSN data source name
}
log.Println("PostgreSQL DSN: ", dbConfig.DSN)
gormConfig := gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: conf.Prefix,
SingularTable: conf.Singular,
},
DisableForeignKeyConstraintWhenMigrating: true,
}
db, err := gorm.Open(postgres.New(dbConfig), &gormConfig)
if err != nil {
return nil, err
}
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
return db, err
}, opts...)
}

View File

@@ -0,0 +1 @@
package redis

View File

@@ -1,18 +0,0 @@
package database
import (
"atom/providers/config"
// "gorm.io/driver/sqlite"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
func NewSQLite(conf *config.SQLite) (*gorm.DB, error) {
db, err := gorm.Open(sqlite.Open(conf.File), &gorm.Config{})
if err != nil {
return nil, err
}
return db, err
}

View File

@@ -0,0 +1,13 @@
package sqlite
type Config struct {
File string
}
func (m *Config) CreateDatabaseSql() string {
return ""
}
func (m *Config) EmptyDsn() string {
return m.File
}

View File

@@ -0,0 +1,21 @@
package sqlite
import (
"github.com/rogeecn/atom/container"
"go.uber.org/dig"
// "gorm.io/driver/sqlite"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
)
func Provide(conf *Config, opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*gorm.DB, error) {
db, err := gorm.Open(sqlite.Open(conf.File), &gorm.Config{})
if err != nil {
return nil, err
}
return db, err
}, opts...)
}

View File

@@ -1,116 +0,0 @@
package dictionary
import (
"atom/container"
"atom/database/query"
"context"
"errors"
"log"
)
type Dict struct {
dict map[uint64]*DictInfo
dictItems []*DictInfo
mapAlias map[string]uint64
query *query.Query
}
func init() {
if err := container.Container.Provide(NewDictionary); err != nil {
log.Fatal(err)
}
}
func NewDictionary(query *query.Query) (*Dict, error) {
dict := &Dict{
query: query,
dict: make(map[uint64]*DictInfo),
dictItems: []*DictInfo{},
mapAlias: make(map[string]uint64),
}
if err := dict.Load(); err != nil {
return nil, err
}
return dict, nil
}
func (dict *Dict) Load() error {
dictTable := dict.query.SysDictionary
items, err := dictTable.WithContext(context.Background()).Where(dictTable.Status.Is(true)).Find()
if err != nil {
return err
}
ids := []uint64{}
for _, item := range items {
ids = append(ids, item.ID)
dict.mapAlias[item.Alias_] = item.ID
}
dictDetailTable := dict.query.SysDictionaryDetail
dictItems, err := dictDetailTable.WithContext(context.Background()).
Where(dictDetailTable.Status.Is(true)).
Where(dictDetailTable.ID.In(ids...)).
Order(dictDetailTable.Weight.Desc()).
Find()
if err != nil {
return err
}
idItems := make(map[uint64][]*DictItem)
for _, dictItem := range dictItems {
id := uint64(dictItem.SysDictionaryID)
idItems[id] = append(idItems[id], &DictItem{
Label: dictItem.Label,
Value: dictItem.Value,
})
}
for _, item := range items {
info := &DictInfo{
ID: item.ID,
Name: item.Name,
Alias: item.Alias_,
Description: item.Description,
Items: idItems[item.ID],
}
dict.dictItems = append(dict.dictItems, info)
dict.dict[item.ID] = info
}
return nil
}
// GetLabelByValue
func (dict *Dict) GetLabelByValue(alias, value string) (string, error) {
items, err := dict.GetItems(alias)
if err != nil {
return "", err
}
for _, item := range items {
if item.Value == value {
return item.Label, nil
}
}
return "", errors.New("dict item not exists")
}
// GetLabelByValue
func (dict *Dict) GetItems(alias string) ([]*DictItem, error) {
dictID, ok := dict.mapAlias[alias]
if !ok {
return nil, errors.New("dict not exists")
}
dictItem, ok := dict.dict[dictID]
if !ok {
return nil, errors.New("dict not exists")
}
return dictItem.Items, nil
}
func (dict *Dict) All(alias string) []*DictInfo {
return dict.dictItems
}

View File

@@ -1,14 +0,0 @@
package dictionary
type DictInfo struct {
ID uint64
Name string
Alias string
Description string
Items []*DictItem
}
type DictItem struct {
Label string
Value string
}

View File

@@ -1,22 +1,19 @@
package faker
import (
"atom/container"
"log"
"time"
"github.com/rogeecn/atom/container"
"go.uber.org/dig"
"github.com/brianvoe/gofakeit/v6"
)
func init() {
if err := container.Container.Provide(NewFaker); err != nil {
log.Fatal(err)
}
}
func Provide(opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*gofakeit.Faker, error) {
faker := gofakeit.New(time.Now().UnixNano())
gofakeit.SetGlobalFaker(faker)
func NewFaker() *gofakeit.Faker {
faker := gofakeit.New(time.Now().UnixNano())
gofakeit.SetGlobalFaker(faker)
return faker
return faker, nil
}, opts...)
}

View File

@@ -1,12 +1,10 @@
package config
package http
import (
"fmt"
"log"
"time"
)
type Http struct {
type Config struct {
Static string
Host string
Port uint
@@ -17,21 +15,6 @@ type Http struct {
Mode string
Whitelist []Whitelist
}
JWT JWT
}
type JWT struct {
SigningKey string // jwt签名
ExpiresTime string // 过期时间
Issuer string // 签发者
}
func (j JWT) ExpiresTimeDuration() time.Duration {
d, err := time.ParseDuration(j.ExpiresTime)
if err != nil {
log.Fatal(err)
}
return d
}
type Whitelist struct {
@@ -42,10 +25,10 @@ type Whitelist struct {
AllowCredentials bool
}
func (h *Http) Address() string {
func (h *Config) Address() string {
return fmt.Sprintf("%s:%d", h.Host, h.Port)
}
func (h *Http) PortString() string {
func (h *Config) PortString() string {
return fmt.Sprintf(":%d", h.Port)
}

View File

@@ -0,0 +1,9 @@
package http
type Route interface {
Register()
}
type Service interface {
Serve() error
}

View File

@@ -1,61 +0,0 @@
package http
import (
"atom/container"
"atom/providers/config"
"atom/providers/log"
"fmt"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func init() {
if err := container.Container.Provide(NewService); err != nil {
log.Fatal(err)
}
}
type Service struct {
Engine *gin.Engine
conf *config.Config
}
func (e *Service) Serve() error {
if e.conf.Http.Https {
return e.Engine.RunTLS(e.conf.Http.PortString(), e.conf.Http.HttpsCert, e.conf.Http.HttpKey)
}
return e.Engine.Run(e.conf.Http.PortString())
}
func NewService(cfg *config.Config, logger *log.Logger) *Service {
gin.DefaultWriter = logger.LevelWriter(zap.InfoLevel)
gin.DefaultErrorWriter = logger.LevelWriter(zap.ErrorLevel)
if cfg.App.IsDebug() {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
engine := gin.New()
engine.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf(`%s - [%s] "%s %s %s %d %s '%q' %s"\n`,
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
engine.Use(gin.Recovery())
return &Service{Engine: engine, conf: cfg}
}

View File

@@ -0,0 +1,54 @@
package gin
import (
"fmt"
"time"
"github.com/rogeecn/atom/container"
"github.com/rogeecn/atom/providers/http"
"github.com/rogeecn/atom/providers/log"
"go.uber.org/dig"
"github.com/gin-gonic/gin"
)
type Service struct {
conf *http.Config
Engine *gin.Engine
}
func (e *Service) Use(middleware ...gin.HandlerFunc) gin.IRoutes {
return e.Engine.Use(middleware...)
}
func (e *Service) Serve() error {
if e.conf.Https {
return e.Engine.RunTLS(e.conf.PortString(), e.conf.HttpsCert, e.conf.HttpKey)
}
return e.Engine.Run(e.conf.PortString())
}
func Provide(cfg *http.Config, opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (http.Service, error) {
gin.DefaultWriter = log.LevelWriter{Level: log.InfoLevel}
gin.DefaultErrorWriter = log.LevelWriter{Level: log.ErrorLevel}
engine := gin.New()
engine.Use(gin.Recovery())
engine.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf(`%s - [%s] "%s %s %s %d %s '%q' %s"\n`,
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
return &Service{Engine: engine, conf: cfg}, nil
}, opts...)
}

21
providers/jwt/config.go Normal file
View File

@@ -0,0 +1,21 @@
package jwt
import (
"time"
"github.com/rogeecn/atom/providers/log"
)
type Config struct {
SigningKey string // jwt签名
ExpiresTime string // 过期时间
Issuer string // 签发者
}
func (c *Config) ExpiresTimeDuration() time.Duration {
d, err := time.ParseDuration(c.ExpiresTime)
if err != nil {
log.Fatal(err)
}
return d
}

View File

@@ -1,13 +1,13 @@
package jwt
import (
"atom/container"
"atom/providers/config"
"atom/providers/log"
"errors"
"strings"
"time"
"github.com/rogeecn/atom/container"
"go.uber.org/dig"
jwt "github.com/golang-jwt/jwt/v4"
"golang.org/x/sync/singleflight"
)
@@ -17,12 +17,6 @@ const (
HttpHeader = "Authorization"
)
func init() {
if err := container.Container.Provide(NewJWT); err != nil {
log.Fatal(err)
}
}
type BaseClaims struct {
UID uint64 `json:"uid,omitempty"`
Role uint64 `json:"role,omitempty"`
@@ -37,8 +31,8 @@ type Claims struct {
const TOKEN_PREFIX = "Bearer "
type JWT struct {
config *config.Config
singleflight *singleflight.Group
config *Config
SigningKey []byte
}
@@ -49,21 +43,23 @@ var (
TokenInvalid = errors.New("Couldn't handle this token:")
)
func NewJWT(config *config.Config) (*JWT, error) {
return &JWT{
config: config,
SigningKey: []byte(config.Http.JWT.SigningKey),
}, nil
func Provide(config *Config, opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*JWT, error) {
return &JWT{
config: config,
SigningKey: []byte(config.SigningKey),
}, nil
}, opts...)
}
func (j *JWT) CreateClaims(baseClaims BaseClaims) *Claims {
ep, _ := time.ParseDuration(j.config.Http.JWT.ExpiresTime)
ep, _ := time.ParseDuration(j.config.ExpiresTime)
claims := Claims{
BaseClaims: baseClaims,
RegisteredClaims: jwt.RegisteredClaims{
NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Second * 10)), // 签名生效时间
ExpiresAt: jwt.NewNumericDate(time.Now().Add(ep)), // 过期时间 7天 配置文件
Issuer: j.config.Http.JWT.Issuer, // 签名的发行者
Issuer: j.config.Issuer, // 签名的发行者
},
}
return &claims

28
providers/log/config.go Normal file
View File

@@ -0,0 +1,28 @@
package log
type Config struct {
Level Level
}
type Level int8
const (
// DebugLevel logs are typically voluminous, and are usually disabled in
// production.
DebugLevel Level = iota - 1
// InfoLevel is the default logging priority.
InfoLevel
// WarnLevel logs are more important than Info, but don't need individual
// human review.
WarnLevel
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ErrorLevel
// DPanicLevel logs are particularly important errors. In development the
// logger panics after writing the message.
DPanicLevel
// PanicLevel logs a message, then panics.
PanicLevel
// FatalLevel logs a message, then calls os.Exit(1).
FatalLevel
)

View File

@@ -2,20 +2,18 @@ package log
import (
"strings"
"go.uber.org/zap/zapcore"
)
type LevelWriter struct {
Level zapcore.Level
Level Level
}
func (w LevelWriter) Write(p []byte) (n int, err error) {
str := strings.TrimSpace(string(p))
switch w.Level {
case zapcore.InfoLevel:
case InfoLevel:
Info(str)
case zapcore.ErrorLevel:
case ErrorLevel:
Error(str)
}
return len(p), nil

View File

@@ -1,99 +1,103 @@
package log
import (
"atom/container"
"log"
"github.com/rogeecn/atom/container"
"go.uber.org/dig"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func init() {
if err := container.Container.Provide(NewZapLogger); err != nil {
log.Fatal(err)
}
func Provide(config *Config, opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*Logger, error) {
logger, err := newZapLogger(config)
if err != nil {
return nil, err
}
defaultLogger = logger
return logger, nil
}, opts...)
}
var DefaultLogger *Logger
var defaultLogger *Logger
type Logger struct {
logger *zap.SugaredLogger
}
func (l *Logger) LevelWriter(level zapcore.Level) *LevelWriter {
func (l *Logger) LevelWriter(level Level) *LevelWriter {
return &LevelWriter{Level: level}
}
// Debug uses fmt.Sprint to construct and log a message.
func Debug(args ...interface{}) {
DefaultLogger.logger.Debug(args...)
defaultLogger.logger.Debug(args...)
}
// Info uses fmt.Sprint to construct and log a message.
func Info(args ...interface{}) {
DefaultLogger.logger.Info(args...)
defaultLogger.logger.Info(args...)
}
// Warn uses fmt.Sprint to construct and log a message.
func Warn(args ...interface{}) {
DefaultLogger.logger.Warn(args...)
defaultLogger.logger.Warn(args...)
}
// Error uses fmt.Sprint to construct and log a message.
func Error(args ...interface{}) {
DefaultLogger.logger.Error(args...)
defaultLogger.logger.Error(args...)
}
// DPanic uses fmt.Sprint to construct and log a message. In development, the
// logger then panics. (See DPanicLevel for details.)
func DPanic(args ...interface{}) {
DefaultLogger.logger.DPanic(args...)
defaultLogger.logger.DPanic(args...)
}
// Panic uses fmt.Sprint to construct and log a message, then panics.
func Panic(args ...interface{}) {
DefaultLogger.logger.Panic(args...)
defaultLogger.logger.Panic(args...)
}
// Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit.
func Fatal(args ...interface{}) {
DefaultLogger.logger.Fatal(args...)
defaultLogger.logger.Fatal(args...)
}
// Debugf uses fmt.Sprintf to log a templated message.
func Debugf(template string, args ...interface{}) {
DefaultLogger.logger.Debugf(template, args...)
defaultLogger.logger.Debugf(template, args...)
}
// Infof uses fmt.Sprintf to log a templated message.
func Infof(template string, args ...interface{}) {
DefaultLogger.logger.Infof(template, args...)
defaultLogger.logger.Infof(template, args...)
}
// Warnf uses fmt.Sprintf to log a templated message.
func Warnf(template string, args ...interface{}) {
DefaultLogger.logger.Warnf(template, args...)
defaultLogger.logger.Warnf(template, args...)
}
// Errorf uses fmt.Sprintf to log a templated message.
func Errorf(template string, args ...interface{}) {
DefaultLogger.logger.Errorf(template, args...)
defaultLogger.logger.Errorf(template, args...)
}
// DPanicf uses fmt.Sprintf to log a templated message. In development, the
// logger then panics. (See DPanicLevel for details.)
func DPanicf(template string, args ...interface{}) {
DefaultLogger.logger.DPanicf(template, args...)
defaultLogger.logger.DPanicf(template, args...)
}
// Panicf uses fmt.Sprintf to log a templated message, then panics.
func Panicf(template string, args ...interface{}) {
DefaultLogger.logger.Panicf(template, args...)
defaultLogger.logger.Panicf(template, args...)
}
// Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit.
func Fatalf(template string, args ...interface{}) {
DefaultLogger.logger.Fatalf(template, args...)
defaultLogger.logger.Fatalf(template, args...)
}
// Debugw logs a message with some additional context. The variadic key-value
@@ -103,47 +107,47 @@ func Fatalf(template string, args ...interface{}) {
//
// s.With(keysAndValues).Debug(msg)
func Debugw(msg string, keysAndValues ...interface{}) {
DefaultLogger.logger.Debugw(msg, keysAndValues...)
defaultLogger.logger.Debugw(msg, keysAndValues...)
}
// Infow logs a message with some additional context. The variadic key-value
// pairs are treated as they are in With.
func Infow(msg string, keysAndValues ...interface{}) {
DefaultLogger.logger.Infow(msg, keysAndValues...)
defaultLogger.logger.Infow(msg, keysAndValues...)
}
// Warnw logs a message with some additional context. The variadic key-value
// pairs are treated as they are in With.
func Warnw(msg string, keysAndValues ...interface{}) {
DefaultLogger.logger.Warnw(msg, keysAndValues...)
defaultLogger.logger.Warnw(msg, keysAndValues...)
}
// Errorw logs a message with some additional context. The variadic key-value
// pairs are treated as they are in With.
func Errorw(msg string, keysAndValues ...interface{}) {
DefaultLogger.logger.Errorw(msg, keysAndValues...)
defaultLogger.logger.Errorw(msg, keysAndValues...)
}
// DPanicw logs a message with some additional context. In development, the
// logger then panics. (See DPanicLevel for details.) The variadic key-value
// pairs are treated as they are in With.
func DPanicw(msg string, keysAndValues ...interface{}) {
DefaultLogger.logger.DPanicw(msg, keysAndValues...)
defaultLogger.logger.DPanicw(msg, keysAndValues...)
}
// Panicw logs a message with some additional context, then panics. The
// variadic key-value pairs are treated as they are in With.
func Panicw(msg string, keysAndValues ...interface{}) {
DefaultLogger.logger.Panicw(msg, keysAndValues...)
defaultLogger.logger.Panicw(msg, keysAndValues...)
}
// Fatalw logs a message with some additional context, then calls os.Exit. The
// variadic key-value pairs are treated as they are in With.
func Fatalw(msg string, keysAndValues ...interface{}) {
DefaultLogger.logger.Fatalw(msg, keysAndValues...)
defaultLogger.logger.Fatalw(msg, keysAndValues...)
}
// Sync flushes any buffered log entries.
func Sync() error {
return DefaultLogger.logger.Sync()
return defaultLogger.logger.Sync()
}

View File

@@ -1,16 +1,13 @@
package log
import (
"atom/providers/config"
"go.uber.org/zap"
)
func NewZapLogger(conf *config.Config) (*Logger, error) {
func newZapLogger(conf *Config) (*Logger, error) {
logger, err := zap.NewDevelopment()
if err != nil {
return nil, err
}
DefaultLogger = &Logger{logger: logger.Sugar()}
return DefaultLogger, nil
return &Logger{logger: logger.Sugar()}, nil
}

View File

@@ -1,17 +0,0 @@
package providers
import (
_ "atom/providers/captcha"
_ "atom/providers/captcha/driver"
_ "atom/providers/config"
_ "atom/providers/database"
_ "atom/providers/dictionary"
_ "atom/providers/faker"
_ "atom/providers/http"
_ "atom/providers/jwt"
_ "atom/providers/log"
_ "atom/providers/query"
_ "atom/providers/rbac"
_ "atom/providers/single_flight"
_ "atom/providers/uuid"
)

View File

@@ -1,19 +0,0 @@
package query
import (
"atom/container"
"atom/database/query"
"log"
"gorm.io/gorm"
)
func init() {
if err := container.Container.Provide(NewQuery); err != nil {
log.Fatal(err)
}
}
func NewQuery(db *gorm.DB) *query.Query {
return query.Use(db)
}

View File

@@ -1,134 +0,0 @@
package rbac
import (
"atom/container"
"atom/database/query"
"atom/providers/log"
"context"
"errors"
"strconv"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
gormadapter "github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"
"gorm.io/gorm"
)
func init() {
if err := container.Container.Provide(NewCasbin); err != nil {
log.Fatal(err)
}
}
type Casbin struct {
query *query.Query
enforcer *casbin.CachedEnforcer
}
type CasbinInfo struct {
Path string `json:"path"` // 路径
Method string `json:"method"` // 方法
}
func NewCasbin(query *query.Query, db *gorm.DB) (IRbac, error) {
cb := &Casbin{query: query}
a, _ := gormadapter.NewAdapterByDB(db)
text := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch2(r.obj,p.obj) && r.act == p.act
`
m, err := model.NewModelFromString(text)
if err != nil {
log.Error(err, "字符串加载模型失败!")
return nil, err
}
cb.enforcer, _ = casbin.NewCachedEnforcer(m, a)
cb.enforcer.SetExpireTime(60 * 60)
_ = cb.enforcer.LoadPolicy()
return cb, nil
}
func (cb *Casbin) Can(role, method, path string) bool {
return false
}
func (cb *Casbin) Reload() error {
return nil
}
func (cb *Casbin) JsonPermissionsForUser(username string) (string, error) {
return casbin.CasbinJsGetPermissionForUser(cb.enforcer, username)
}
func (cb *Casbin) Update(roleID uint, infos []CasbinInfo) error {
roleIdStr := strconv.Itoa(int(roleID))
cb.Clear(0, roleIdStr)
rules := [][]string{}
for _, v := range infos {
rules = append(rules, []string{roleIdStr, v.Path, v.Method})
}
success, _ := cb.enforcer.AddPolicies(rules)
if !success {
return errors.New("存在相同api,添加失败,请联系管理员")
}
err := cb.enforcer.InvalidateCache()
if err != nil {
return err
}
return nil
}
func (cb *Casbin) UpdateApi(before, after CasbinInfo) error {
rule := cb.query.CasbinRule
_, err := rule.WithContext(context.Background()).
Where(rule.V1.Eq(before.Path)).
Where(rule.V1.Eq(before.Method)).
UpdateSimple(
rule.V1.Value(after.Path),
rule.V2.Value(after.Method),
)
if err != nil {
return err
}
return cb.enforcer.InvalidateCache()
}
// 获取权限列表
func (cb *Casbin) GetPolicyPathByRoleID(roleID uint) (pathMaps []CasbinInfo) {
roleIdStr := strconv.Itoa(int(roleID))
list := cb.enforcer.GetFilteredPolicy(0, roleIdStr)
for _, v := range list {
pathMaps = append(pathMaps, CasbinInfo{
Path: v[1],
Method: v[2],
})
}
return pathMaps
}
// 清除匹配的权限
func (cb *Casbin) Clear(v int, p ...string) bool {
success, _ := cb.enforcer.RemoveFilteredPolicy(v, p...)
return success
}

View File

@@ -1,7 +0,0 @@
package rbac
type IRbac interface {
Can(role, method, path string) bool
JsonPermissionsForUser(string) (string, error)
Reload() error
}

View File

@@ -1,18 +1,13 @@
package single_flight
import (
"atom/container"
"log"
"github.com/rogeecn/atom/container"
"go.uber.org/dig"
"golang.org/x/sync/singleflight"
)
func init() {
if err := container.Container.Provide(NewSingleFlight); err != nil {
log.Fatal(err)
}
}
func NewSingleFlight() (*singleflight.Group, error) {
return &singleflight.Group{}, nil
func Provide(opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*singleflight.Group, error) {
return &singleflight.Group{}, nil
}, opts...)
}

View File

@@ -1,26 +1,22 @@
package uuid
import (
"atom/container"
"log"
"github.com/rogeecn/atom/container"
"go.uber.org/dig"
"github.com/gofrs/uuid"
)
func init() {
if err := container.Container.Provide(NewUUID); err != nil {
log.Fatal(err)
}
}
type Generator struct {
generator uuid.Generator
}
func NewUUID() (*Generator, error) {
return &Generator{
generator: uuid.DefaultGenerator,
}, nil
func Provide(opts ...dig.ProvideOption) error {
return container.Container.Provide(func() (*Generator, error) {
return &Generator{
generator: uuid.DefaultGenerator,
}, nil
})
}
func (u *Generator) MustGenerate() string {