feat: wrap message by fluent ui toast

This commit is contained in:
zhaoyupeng
2024-09-27 14:52:10 +08:00
parent 77dff6649d
commit b2c13508f4
27 changed files with 677 additions and 102 deletions

32
internal/api/api.go Normal file
View File

@ -0,0 +1,32 @@
package api
import (
"context"
"github.com/loveuer/nf-disk/internal/handler"
"github.com/loveuer/nf-disk/ndh"
"github.com/loveuer/nf/nft/log"
"reflect"
)
var (
apis = make(map[string]ndh.Handler)
)
func register(path string, h ndh.Handler) {
name := reflect.ValueOf(h).String()
log.Info("app register: path = %s, name = %s", path, name)
apis[path] = h
}
func Resolve(path string) (ndh.Handler, bool) {
h, ok := apis[path]
return h, ok
}
func Init(ctx context.Context) error {
register("/api/connection/test", handler.ConnectionTest)
register("/api/connection/create", handler.ConnectionCreate)
register("/api/connection/list", handler.ConnectionList)
return nil
}

View File

@ -1,18 +0,0 @@
package controller
import (
"github.com/loveuer/nf/nft/log"
"nf-disk/internal/handler"
"nf-disk/internal/ndh"
"reflect"
)
func (a *App) register(path string, handler ndh.Handler) {
name := reflect.ValueOf(handler).String()
log.Info("app register: path = %s, name = %s", path, name)
a.handlers[path] = handler
}
func initApi(a *App) {
a.register("/api/connection/test", handler.ConnectionTest)
}

View File

@ -1,13 +1,13 @@
package controller
import (
"bytes"
"context"
"encoding/json"
"github.com/loveuer/nf-disk/internal/api"
"github.com/loveuer/nf-disk/internal/db"
"github.com/loveuer/nf-disk/internal/model"
"github.com/loveuer/nf-disk/internal/tool"
"github.com/loveuer/nf-disk/ndh"
"github.com/loveuer/nf/nft/log"
"nf-disk/internal/ndh"
"nf-disk/internal/tool"
"strings"
)
type App struct {
@ -22,24 +22,11 @@ func NewApp() *App {
}
func (a *App) Startup(ctx context.Context) {
a.ctx = ctx
log.Info("app startup!!!")
initApi(a)
}
func (a *App) Invoke(path string, req string) (res string) {
log.Info("app invoke: path = %s, req = %s", path, req)
handler, ok := a.handlers[path]
if !ok {
return `{"err": "handler not found", "status": 404}`
}
var buf bytes.Buffer
ctx := ndh.NewCtx(tool.Timeout(), json.NewDecoder(strings.NewReader(req)), &buf)
if err := handler(ctx); err != nil {
return err.Error()
}
return buf.String()
a.ctx = ctx
tool.Must(db.Init(ctx, "sqlite::memory", db.OptSqliteByMem(nil)))
tool.Must(model.Init(db.Default.Session()))
tool.Must(api.Init(ctx))
}

View File

@ -0,0 +1,51 @@
package controller
import (
"bytes"
"encoding/json"
"fmt"
"github.com/loveuer/nf-disk/internal/api"
"github.com/loveuer/nf-disk/internal/opt"
"github.com/loveuer/nf-disk/internal/tool"
"github.com/loveuer/nf-disk/ndh"
"github.com/loveuer/nf/nft/log"
"strings"
)
func handleError(err error) string {
bs, _ := json.Marshal(map[string]any{
"err": err.Error(),
"msg": opt.Msg500,
"status": 500,
})
return string(bs)
}
func handleNotFound(path string) string {
bs, _ := json.Marshal(map[string]any{
"err": fmt.Sprintf("path not found, path: %s", path),
"msg": opt.Msg500,
"status": 404,
})
return string(bs)
}
func (a *App) Invoke(path string, req string) (res string) {
log.Info("app invoke: path = %s, req = %s", path, req)
handler, ok := api.Resolve(path)
if !ok {
log.Warn("app invoke: path not found, path = %s", path)
return handleNotFound(path)
}
var buf bytes.Buffer
ctx := ndh.NewCtx(tool.TimeoutCtx(a.ctx), strings.NewReader(req), &buf)
if err := handler(ctx); err != nil {
return handleError(err)
}
return buf.String()
}

61
internal/db/client.go Normal file
View File

@ -0,0 +1,61 @@
package db
import (
"context"
"github.com/loveuer/nf-disk/internal/opt"
"github.com/loveuer/nf-disk/internal/tool"
"io"
"gorm.io/gorm"
)
var (
Default *Client
)
type Client struct {
ctx context.Context
cli *gorm.DB
ttype string
cfgSqlite *cfgSqlite
}
func (c *Client) Type() string {
return c.ttype
}
func (c *Client) Session(ctxs ...context.Context) *gorm.DB {
var ctx context.Context
if len(ctxs) > 0 && ctxs[0] != nil {
ctx = ctxs[0]
} else {
ctx = tool.Timeout(30)
}
session := c.cli.Session(&gorm.Session{Context: ctx})
if opt.Debug {
session = session.Debug()
}
return session
}
func (c *Client) Close() {
d, _ := c.cli.DB()
d.Close()
}
// Dump
// Only for sqlite with mem mode to dump data to bytes(io.Reader)
func (c *Client) Dump() (reader io.ReadSeekCloser, ok bool) {
if c.ttype != "sqlite" {
return nil, false
}
if c.cfgSqlite.fsType != "mem" {
return nil, false
}
return c.cfgSqlite.memDump.Dump(), true
}

44
internal/db/db_test.go Normal file
View File

@ -0,0 +1,44 @@
package db
import (
"context"
"io"
"os"
"testing"
)
func TestOpen(t *testing.T) {
myClient, err := New(context.TODO(), "sqlite::", OptSqliteByMem())
if err != nil {
t.Fatalf("TestOpen: New err = %v", err)
}
type Start struct {
Id int `json:"id" gorm:"column:id;primaryKey"`
Name string `json:"name" gorm:"column:name"`
Dis float64 `json:"dis" gorm:"column:dis"`
}
if err = myClient.Session().AutoMigrate(&Start{}); err != nil {
t.Fatalf("TestOpen: AutoMigrate err = %v", err)
}
if err = myClient.Session().Create(&Start{Name: "sun", Dis: 6631.76}).Error; err != nil {
t.Fatalf("TestOpen: Create err = %v", err)
}
if err = myClient.Session().Create(&Start{Name: "mar", Dis: 786.35}).Error; err != nil {
t.Fatalf("TestOpen: Create err = %v", err)
}
if reader, ok := myClient.Dump(); ok {
bs, err := io.ReadAll(reader)
if err != nil {
t.Fatalf("TestOpen: ReadAll err = %v", err)
}
os.WriteFile("dump.db", bs, 0644)
}
myClient.Close()
}

54
internal/db/init.go Normal file
View File

@ -0,0 +1,54 @@
package db
import (
"context"
"fmt"
"strings"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func New(ctx context.Context, uri string, opts ...Option) (*Client, error) {
strs := strings.Split(uri, "::")
if len(strs) != 2 {
return nil, fmt.Errorf("db.Init: opt db uri invalid: %s", uri)
}
c := &Client{ttype: strs[0], cfgSqlite: &cfgSqlite{fsType: "file"}}
for _, f := range opts {
f(c)
}
var (
err error
dsn = strs[1]
)
switch strs[0] {
case "sqlite":
err = openSqlite(c, dsn)
case "mysql":
c.cli, err = gorm.Open(mysql.Open(dsn))
case "postgres":
c.cli, err = gorm.Open(postgres.Open(dsn))
default:
return nil, fmt.Errorf("db type only support: [sqlite, mysql, postgres], unsupported db type: %s", strs[0])
}
if err != nil {
return nil, fmt.Errorf("db.Init: open %s with dsn:%s, err: %w", strs[0], dsn, err)
}
return c, nil
}
func Init(ctx context.Context, uri string, opts ...Option) (err error) {
if Default, err = New(ctx, uri, opts...); err != nil {
return err
}
return nil
}

27
internal/db/option.go Normal file
View File

@ -0,0 +1,27 @@
package db
import (
_ "github.com/loveuer/go-sqlite3/embed"
"io"
)
type Option func(c *Client)
func OptSqliteByUrl(address string) Option {
return func(c *Client) {
c.cfgSqlite.fsType = "url"
}
}
type SqliteMemDumper interface {
Dump() io.ReadSeekCloser
}
// 如果传 nil 则表示新生成一个 mem 的 sqlite
// 如果传了一个合法的 reader 则会从这个 reader 初始化 database
func OptSqliteByMem(reader io.ReadCloser) Option {
return func(c *Client) {
c.cfgSqlite.memReader = reader
c.cfgSqlite.fsType = "mem"
}
}

63
internal/db/sqlite.go Normal file
View File

@ -0,0 +1,63 @@
package db
import (
"fmt"
"io"
"time"
_ "github.com/loveuer/go-sqlite3/embed"
"github.com/loveuer/go-sqlite3/vfs/memdb"
"github.com/loveuer/go-sqlite3/vfs/readervfs"
"github.com/ncruces/go-sqlite3/gormlite"
"github.com/psanford/httpreadat"
"gorm.io/gorm"
)
type cfgSqlite struct {
fsType string // file, mem(bytes), url
memDump *memdb.MemDB
memReader io.ReadCloser
}
func openSqlite(c *Client, dsn string) error {
var (
db gorm.Dialector
err error
)
switch c.cfgSqlite.fsType {
case "file":
db = gormlite.Open("file:" + dsn)
case "url":
name := fmt.Sprintf("%d.db", time.Now().UnixNano())
readervfs.Create(name, httpreadat.New(dsn))
uri := fmt.Sprintf("file:%s?vfs=reader", name)
db = gormlite.Open(uri)
case "mem":
var (
bs []byte
name = fmt.Sprintf("%d.db", time.Now().UnixNano())
)
if c.cfgSqlite.memReader == nil {
bs = make([]byte, 0)
} else {
if bs, err = io.ReadAll(c.cfgSqlite.memReader); err != nil {
return err
}
}
memDump := memdb.Create(name, bs)
c.cfgSqlite.memDump = memDump
uri := fmt.Sprintf("file:/%s?vfs=memdb", name)
db = gormlite.Open(uri)
default:
return fmt.Errorf("unsupported sqlite fs type: %s", c.cfgSqlite.fsType)
}
if c.cli, err = gorm.Open(db); err != nil {
return err
}
return nil
}

View File

@ -1,8 +1,11 @@
package handler
import (
"nf-disk/internal/ndh"
"nf-disk/internal/s3"
"github.com/loveuer/nf-disk/internal/db"
"github.com/loveuer/nf-disk/internal/manager"
"github.com/loveuer/nf-disk/internal/model"
"github.com/loveuer/nf-disk/internal/s3"
"github.com/loveuer/nf-disk/ndh"
)
func ConnectionTest(c *ndh.Ctx) error {
@ -30,5 +33,69 @@ func ConnectionTest(c *ndh.Ctx) error {
return c.Send500(err.Error(), "连接失败")
}
return c.Send200("test success")
return c.Send200("连接测试成功")
}
func ConnectionCreate(c *ndh.Ctx) error {
type Req struct {
Name string `json:"name"`
Endpoint string `json:"endpoint"`
Access string `json:"access"`
Key string `json:"key"`
Force bool `json:"force"`
}
var (
err error
req = new(Req)
client *s3.Client
)
if err = c.ReqParse(req); err != nil {
return err
}
if req.Endpoint == "" || req.Access == "" || req.Key == "" {
return c.Send400(nil, "endpoint, secret_access, secret_key 是必填项")
}
if client, err = s3.New(c.Context(), req.Endpoint, req.Access, req.Key); err != nil {
return c.Send500(err.Error(), "连接失败")
}
if req.Name == "" {
req.Name = req.Endpoint
}
connection := &model.Connection{
Name: req.Name,
Endpoint: req.Endpoint,
Access: req.Access,
Key: req.Key,
}
if err = connection.Create(db.Default.Session()); err != nil {
return c.Send500(err.Error(), "创建连接失败(1)")
}
if err = manager.Register(connection, client); err != nil {
return c.Send500(err.Error(), "创建连接失败(2)")
}
return c.Send200(connection, "创建连接成功")
}
func ConnectionList(c *ndh.Ctx) error {
var (
err error
list = make([]*model.Connection, 0)
)
if err = db.Default.Session().Model(&model.Connection{}).
Find(&list).
Error; err != nil {
return err
}
return c.Send200(map[string]any{"list": list})
}

View File

@ -0,0 +1,18 @@
package manager
import (
"context"
"github.com/loveuer/nf-disk/internal/model"
"github.com/loveuer/nf-disk/internal/s3"
"github.com/loveuer/nf/nft/log"
)
func Init(ctx context.Context) error {
return nil
}
func Register(m *model.Connection, c *s3.Client) error {
log.Debug("manager: register connection-client: id = %d, name = %s", m.Id, m.Name)
return nil
}

9
internal/model/init.go Normal file
View File

@ -0,0 +1,9 @@
package model
import "gorm.io/gorm"
func Init(tx *gorm.DB) error {
return tx.AutoMigrate(
&Connection{},
)
}

18
internal/model/s3.go Normal file
View File

@ -0,0 +1,18 @@
package model
import "gorm.io/gorm"
type Connection struct {
Id uint64 `json:"id" gorm:"primaryKey;column:id"`
CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"`
UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"`
DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"`
Name string `json:"name" gorm:"unique;column:name"`
Endpoint string `json:"endpoint" gorm:"column:endpoint"`
Access string `json:"access" gorm:"column:access"`
Key string `json:"key" gorm:"column:key"`
}
func (c *Connection) Create(tx *gorm.DB) error {
return tx.Create(c).Error
}

View File

@ -1,72 +0,0 @@
package ndh
import (
"context"
"encoding/json"
"io"
)
type Ctx struct {
ctx context.Context
req *json.Decoder
res io.Writer
}
func NewCtx(ctx context.Context, req *json.Decoder, res io.Writer) *Ctx {
return &Ctx{
ctx: ctx,
req: req,
res: res,
}
}
func (c *Ctx) Context() context.Context {
return c.ctx
}
func (c *Ctx) Write(bs []byte) (int, error) {
return c.res.Write(bs)
}
func (c *Ctx) ReqParse(req any) error {
return c.req.Decode(req)
}
func (c *Ctx) Send200(data any, msg ...string) error {
m := "操作成功"
if len(msg) > 0 && msg[0] != "" {
m = msg[0]
}
return c.Send(200, m, "", data)
}
func (c *Ctx) Send400(data any, msg ...string) error {
m := "参数错误"
if len(msg) > 0 && msg[0] != "" {
m = msg[0]
}
return c.Send(400, m, "", data)
}
func (c *Ctx) Send500(data any, msg ...string) error {
m := "系统错误"
if len(msg) > 0 && msg[0] != "" {
m = msg[0]
}
return c.Send(500, m, "", data)
}
func (c *Ctx) Send(status uint32, msg, error string, data any) error {
value := map[string]any{"status": status, "msg": msg, "err": error, "data": data}
bs, err := json.Marshal(value)
if err != nil {
return err
}
_, err = c.Write(bs)
return err
}

View File

@ -1,3 +0,0 @@
package ndh
type Handler func(c *Ctx) error

View File

@ -1,5 +1,11 @@
package opt
const (
Msg200 = "操作成功"
Msg400 = "输入不正确"
Msg500 = "发生错误"
)
var (
Debug bool
)

View File

@ -7,9 +7,9 @@ import (
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
smithyendpoints "github.com/aws/smithy-go/endpoints"
"github.com/loveuer/nf-disk/internal/tool"
"github.com/loveuer/nf/nft/log"
"net/url"
"nf-disk/internal/tool"
)
type resolverV2 struct{}

11
internal/tool/must.go Normal file
View File

@ -0,0 +1,11 @@
package tool
import "github.com/loveuer/nf/nft/log"
func Must(errs ...error) {
for _, err := range errs {
if err != nil {
log.Panic(err.Error())
}
}
}