123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- package crud
- import (
- "context"
- "git.nspix.com/golang/micro/gateway/http"
- "git.nspix.com/golang/rest/orm/query"
- "git.nspix.com/golang/rest/orm/schema"
- "github.com/jinzhu/inflection"
- "github.com/russross/blackfriday"
- "gorm.io/gorm"
- "sort"
- "strings"
- "sync"
- )
- var (
- Default = New()
- enable int32 = 1
- )
- type (
- EachQueryCallback func(val interface{}, field *schema.Schema, search *query.Query, ctx *http.Context) (err error)
- QueryCallback func(val interface{}, query *query.Query, ctx *http.Context) (err error)
- SaveCallback func(val interface{}, ctx *http.Context) (err error)
- DeleteCallback func(val interface{}, ctx *http.Context) (err error)
- Naming interface {
- DisplayName() string
- }
- CRUD struct {
- db *gorm.DB
- formatter *Formatter
- once sync.Once
- ctx context.Context
- entityLocker sync.RWMutex
- entities map[string]*Entity
- formatLocker sync.RWMutex
- httpSvr *http.Server
- formatCallbacks map[string]EachQueryCallback
- }
- )
- func (crud *CRUD) getCallbackByFormat(format string) EachQueryCallback {
- crud.formatLocker.RLock()
- v, ok := crud.formatCallbacks[format]
- crud.formatLocker.RUnlock()
- if ok {
- return v
- }
- return nil
- }
- func (crud *CRUD) Register(module string, value interface{}, opts ...Option) {
- o := &Options{ctx: crud.ctx, DB: crud.db}
- for _, f := range opts {
- f(o)
- }
- if crud.httpSvr == nil {
- return
- }
- e := newEntity(crud.ctx, value, o.DB)
- e.instance = crud
- e.Callbacks = o.Callbacks
- e.SetModule(module)
- if o.enable != nil {
- e.enable = o.enable
- } else {
- e.enable = &enable
- }
- if o.Formatter == nil {
- e.formatter = crud.formatter
- } else {
- e.formatter = o.Formatter
- }
- crud.entityLocker.Lock()
- crud.entities[e.stmt.Table+"@"+e.Module] = e
- crud.entityLocker.Unlock()
- e.naming.plural = inflection.Plural(e.stmt.Table)
- e.naming.singular = inflection.Singular(e.stmt.Table)
- if o.Scenarios == nil || len(o.Scenarios) == 0 {
- e.Scenarios = []string{ScenarioList, ScenarioView, ScenarioCreate, ScenarioUpdate, ScenarioDelete, ScenarioExport}
- } else {
- e.Scenarios = o.Scenarios
- }
- if v, ok := value.(Naming); ok {
- e.naming.label = v.DisplayName()
- } else {
- e.naming.label = strings.Title(e.naming.singular)
- }
- e.prefix = o.Prefix
- if e.hasScenarios(ScenarioList) {
- e.Urls[ScenarioList] = o.Prefix + "/" + e.naming.plural
- crud.httpSvr.Handle("GET", e.Urls[ScenarioList], e.actionIndex, o.Middleware...)
- }
- if e.hasScenarios(ScenarioView) {
- e.Urls[ScenarioView] = o.Prefix + "/" + e.naming.singular + "/:id"
- crud.httpSvr.Handle("GET", e.Urls[ScenarioView], e.actionView, o.Middleware...)
- }
- if e.hasScenarios(ScenarioCreate) {
- e.Urls[ScenarioCreate] = o.Prefix + "/" + e.naming.singular
- crud.httpSvr.Handle("POST", e.Urls[ScenarioCreate], e.actionCreate, o.Middleware...)
- }
- if e.hasScenarios(ScenarioUpdate) {
- e.Urls[ScenarioUpdate] = o.Prefix + "/" + e.naming.singular + "/:id"
- crud.httpSvr.Handle("PUT", e.Urls[ScenarioUpdate], e.actionUpdate, o.Middleware...)
- }
- if e.hasScenarios(ScenarioDelete) {
- e.Urls[ScenarioDelete] = o.Prefix + "/" + e.naming.singular + "/:id"
- crud.httpSvr.Handle("DELETE", e.Urls[ScenarioDelete], e.actionDelete, o.Middleware...)
- }
- if e.hasScenarios(ScenarioExport) {
- e.Urls[ScenarioExport] = o.Prefix + "/" + e.naming.singular + "-export"
- crud.httpSvr.Handle("GET", e.Urls[ScenarioExport], e.actionExport, o.Middleware...)
- }
- }
- func (crud *CRUD) AttachFormatQueryHook(format string, cb EachQueryCallback) {
- crud.formatLocker.Lock()
- crud.formatCallbacks[format] = cb
- crud.formatLocker.Unlock()
- }
- func (crud *CRUD) SetDB(db *gorm.DB) {
- crud.db = db
- }
- func (crud *CRUD) GetDB() *gorm.DB {
- return crud.db
- }
- func (crud *CRUD) SetHttpServer(svr *http.Server) {
- crud.httpSvr = svr
- crud.once.Do(func() {
- crud.bindDocAction()
- })
- }
- func (crud *CRUD) SetContext(ctx context.Context) {
- crud.ctx = ctx
- if crud.httpSvr == nil {
- crud.httpSvr = http.FromContext(ctx)
- }
- if crud.db == nil {
- crud.db = ctx.Value("db").(*gorm.DB)
- }
- crud.once.Do(func() {
- crud.bindDocAction()
- })
- }
- func (crud *CRUD) readSnapshot() Entities {
- crud.entityLocker.RLock()
- i := 0
- es := make(Entities, len(crud.entities))
- for _, e := range crud.entities {
- es[i] = e
- i++
- }
- crud.entityLocker.RUnlock()
- sort.Sort(es)
- return es
- }
- func (crud *CRUD) actionCatalog(c *http.Context) (err error) {
- var sb strings.Builder
- sb.WriteString("# 接口目录")
- sb.WriteByte('\n')
- sb.WriteByte('\n')
- es := crud.readSnapshot()
- for _, e := range es {
- sb.WriteString("* [" + e.naming.label + "接口](/crud/doc/" + e.stmt.Table + "@" + e.Module + ")")
- sb.WriteByte('\n')
- }
- c.Response().Header().Set("Content-Type", "text/html; charset=utf-8")
- _, _ = c.Response().Write([]byte(`<html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel="stylesheet" href="//rd-api.nspix.com/assets/docs/doc.css"></head><body><div class="markdown-body"><aside id="markdown-toc"></aside><article id="markdown-body">`))
- _, _ = c.Response().Write(blackfriday.Run([]byte(sb.String())))
- _, _ = c.Response().Write([]byte(`</article></div><script src="//rd-api.nspix.com/assets/docs/doc.js"></script></body></html>`))
- return
- }
- func (crud *CRUD) actionDocs(c *http.Context) (err error) {
- crud.entityLocker.RLock()
- entity, ok := crud.entities[c.ParamValue("id")]
- crud.entityLocker.RUnlock()
- if !ok {
- return c.Error(4004, "record not found")
- }
- c.Response().Header().Set("Content-Type", "text/html; charset=utf-8")
- _, _ = c.Response().Write([]byte(`<html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel="stylesheet" href="//rd-api.nspix.com/assets/docs/doc.css"><title>` + entity.naming.label + `</title></head><body><div class="markdown-body"><aside id="markdown-toc"></aside><article id="markdown-body">`))
- _, _ = c.Response().Write(blackfriday.Run(crud.generateDoc(entity)))
- _, _ = c.Response().Write([]byte(`</article></div><script src="//rd-api.nspix.com/assets/docs/doc.js"></script></body></html>`))
- return
- }
- func (crud *CRUD) actionEntities(c *http.Context) (err error) {
- es := crud.readSnapshot()
- names := make([]map[string]interface{}, 0)
- for _, e := range es {
- names = append(names, map[string]interface{}{
- "module": e.Module,
- "table": e.stmt.Table,
- "label": e.naming.label,
- "url": e.prefix + "/" + e.naming.singular,
- "urls": e.Urls,
- "scenarios": e.Scenarios,
- })
- }
- return c.Success(names)
- }
- func (crud *CRUD) bindDocAction() {
- if crud.httpSvr != nil {
- crud.httpSvr.Handle("GET", "/crud/entities", crud.actionEntities)
- crud.httpSvr.Handle("GET", "/crud/docs", crud.actionCatalog)
- crud.httpSvr.Handle("GET", "/crud/doc/:id", crud.actionDocs)
- }
- }
- func New() *CRUD {
- return &CRUD{
- formatter: DefaultFormat,
- ctx: context.Background(),
- formatCallbacks: make(map[string]EachQueryCallback),
- entities: make(map[string]*Entity),
- }
- }
|