// package serv_http -- http-сервис для архитектуры package serv_http import ( "bytes" _ "embed" "encoding/json" "fmt" "log" "time" svg "github.com/ajstarks/svgo" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/compress" "gitp78su.ipnodns.ru/svi/test_arch/lev0/alias" "gitp78su.ipnodns.ru/svi/test_arch/lev0/cons" "gitp78su.ipnodns.ru/svi/test_arch/lev0/types" "gitp78su.ipnodns.ru/svi/test_arch/pkg/elems/canvas" "gitp78su.ipnodns.ru/svi/test_arch/pkg/views/view_actor" "gitp78su.ipnodns.ru/svi/test_arch/pkg/views/view_link" "gitp78su.ipnodns.ru/svi/test_arch/pkg/views/view_use_case" "gitp78su.ipnodns.ru/svi/test_arch/pkg/views/view_use_group" ) //go:embed index.html var indexHtml []byte // ServHttp -- http-сервис для архитектуры type ServHttp struct { fibApp *fiber.App diaMode string // Режим диаграммы oldBinArch []byte // Кеш старой архитектуры } // NewServHttp -- возвращает новый сервис func NewServHttp() (*ServHttp, error) { fibConf := fiber.Config{ ServerHeader: "Server: test_arch", UnescapePath: true, Network: fiber.NetworkTCP4, EnablePrintRoutes: true, } fibApp := fiber.New(fibConf) sf := &ServHttp{ fibApp: fibApp, diaMode: cons.ModeUseCase, } sf.fibApp.Use(compress.New(compress.Config{ Level: compress.LevelBestSpeed, // 1 })) sf.fibApp.Static("/static", "./static", fiber.Static{ Compress: true, Browse: true, CacheDuration: time.Second * 10, }) sf.fibApp.Get("/", sf.indexGet) sf.fibApp.Get("/use_case", sf.setModeUseCase) sf.fibApp.Post("/arch", sf.archPost) return sf, nil } // Run -- запуск http-сервиса func (sf *ServHttp) Run() error { if err := sf.fibApp.Listen(":8100"); err != nil { return fmt.Errorf("ServHttp.Run(): in listen, err=\n\t%w", err) } return nil } // FormArch -- извлекает данные из текстового поля для обновления type FormArch struct { StrData string `form:"text"` } // Возвращает новый холст func (sf *ServHttp) canvas(wr *bytes.Buffer, lstElem []map[string]interface{}) (*svg.SVG, error) { var ( elem map[string]interface{} isFind, isOk bool _section interface{} ) for _, elem = range lstElem { _section, isOk = elem["type"] if !isOk { continue } section, isOk := _section.(string) if !isOk { return nil, fmt.Errorf("ServHttp.canvas(): in mapElem, type(%#+v) not string
%#+v", _section, elem) } if section == cons.SectionConfig { isFind = true break } } if !isFind { return nil, fmt.Errorf("ServHttp.canvas(): in mapElem, config not found") } confCanvas := canvas.NewCanvas(sf.diaMode, elem) sizeX, sizeY := confCanvas.Size() canvas := svg.New(wr) canvas.Start(int(sizeX), int(sizeY)) confCanvas.Draw(canvas) return canvas, nil } // По запросы парсит, что получилось -- обновляет архитектуру func (sf *ServHttp) archPost(ctx *fiber.Ctx) error { form := &FormArch{} if err := ctx.BodyParser(form); err != nil { return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in BodyParser, err=
%v", err)) } binData := []byte(form.StrData) if len(binData) == len(sf.oldBinArch) { return ctx.Send(sf.oldBinArch) } mapElem := []map[string]interface{}{} err := json.Unmarshal(binData, &mapElem) if err != nil { return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in Decode JSON, err=
%v", err)) } wr := bytes.NewBuffer(nil) canvas, err := sf.canvas(wr, mapElem) if err != nil { return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in create canvas, err=
%v", err)) } switch sf.diaMode { case cons.ModeUseCase: // режим вариантов использования if err := sf.useCase(canvas, mapElem); err != nil { return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in useCase, err=
%v", err)) } default: // неизвестный режим return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): unknown mode=%q", sf.diaMode)) } canvas.End() binData = wr.Bytes() return ctx.Send(binData) } // Формирует варианты использования func (sf *ServHttp) useCase(canvas *svg.SVG, lstElem []map[string]interface{}) error { mapDraw := make(map[alias.Id]types.IElemDrawer) for _, elem := range lstElem { _id, isOk := elem["id"] if !isOk { return fmt.Errorf("ServHttp.useCase(): field 'id' not found
%#+v", elem) } id0, isOk := _id.(string) if !isOk { return fmt.Errorf("ServHttp.useCase(): field 'id'(%T, %+v) not alias.Id", _id, _id) } id := alias.Id(id0) if id == "" { return fmt.Errorf("ServHttp.useCase(): id=%q, elem is empty
%#+v", id, elem) } _, isOk = mapDraw[id] if isOk { return fmt.Errorf("ServHttp.useCase(): id=%q already exists", id) } binElem, _ := json.MarshalIndent(elem, "", " ") _type, isOk := elem["type"] if !isOk { return fmt.Errorf("ServHttp.useCase(): id=%q, field 'type' not found
%+v", id, string(binElem)) } strType, isOk := _type.(string) if !isOk { return fmt.Errorf("ServHttp.useCase(): id=%q, field 'type'(%+v) not string
%+v", id, _type, string(binElem)) } var ( drawer types.IElemDrawer err error ) switch strType { case cons.TypeUseActor: // Нарисовать актора drawer, err = view_actor.NewViewActor(elem) if err != nil { return fmt.Errorf("ServHttp.useCase(): id=%q, in create Actor, err=
%v", id, err) } case cons.TypeUseCase: // Нарисовать вариант использования drawer, err = view_use_case.NewViewUseCase(elem) if err != nil { return fmt.Errorf("ServHttp.useCase(): id=%q, err=%w
%+v", id, err, string(binElem)) } case cons.TypeUseLink: // Нарисовать связь drawer, err = view_link.NewViewLink(elem) if err != nil { return fmt.Errorf("ServHttp.useCase(): id=%q, in create Link, err=%w
%+v", id, err, string(binElem)) } case cons.TypeUseGroup: // Нарисовать группу вариантов использования drawer, err = view_use_group.NewViewUseGroup(elem) if err != nil { return fmt.Errorf("ServHttp.useCase(): id=%q, in create UseGroup, err=%w
%+v", id, err, string(binElem)) } } if drawer != nil { mapDraw[id] = drawer log.Printf("ServHttp.useCase(): id=%q, elem=\n%v\n\n", id, elem) } } for _, drawer := range mapDraw { switch drawer.(type) { case types.ILinker: } drawer.Draw(canvas, mapDraw) } return nil } // Возвращает картинку архитектуры func (sf *ServHttp) setModeUseCase(ctx *fiber.Ctx) error { sf.diaMode = cons.ModeUseCase return ctx.SendString("[mode='" + sf.diaMode + "']") } // Возвращает главную страницу сервиса func (sf *ServHttp) indexGet(ctx *fiber.Ctx) error { ctx.Response().Header.Set("Content-Type", "text/html") return ctx.Send(indexHtml) }