serv_http.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // package serv_http -- http-сервис для архитектуры
  2. package serv_http
  3. import (
  4. "bytes"
  5. _ "embed"
  6. "encoding/json"
  7. "fmt"
  8. "log"
  9. "time"
  10. svg "github.com/ajstarks/svgo"
  11. "github.com/gofiber/fiber/v2"
  12. "github.com/gofiber/fiber/v2/middleware/compress"
  13. "gitp78su.ipnodns.ru/svi/test_arch/lev0/alias"
  14. "gitp78su.ipnodns.ru/svi/test_arch/lev0/cons"
  15. "gitp78su.ipnodns.ru/svi/test_arch/lev0/types"
  16. "gitp78su.ipnodns.ru/svi/test_arch/pkg/elems/canvas"
  17. "gitp78su.ipnodns.ru/svi/test_arch/pkg/views/view_actor"
  18. "gitp78su.ipnodns.ru/svi/test_arch/pkg/views/view_link"
  19. "gitp78su.ipnodns.ru/svi/test_arch/pkg/views/view_use_case"
  20. "gitp78su.ipnodns.ru/svi/test_arch/pkg/views/view_use_group"
  21. )
  22. //go:embed index.html
  23. var indexHtml []byte
  24. // ServHttp -- http-сервис для архитектуры
  25. type ServHttp struct {
  26. fibApp *fiber.App
  27. diaMode string // Режим диаграммы
  28. oldBinArch []byte // Кеш старой архитектуры
  29. }
  30. // NewServHttp -- возвращает новый сервис
  31. func NewServHttp() (*ServHttp, error) {
  32. fibConf := fiber.Config{
  33. ServerHeader: "Server: test_arch",
  34. UnescapePath: true,
  35. Network: fiber.NetworkTCP4,
  36. EnablePrintRoutes: true,
  37. }
  38. fibApp := fiber.New(fibConf)
  39. sf := &ServHttp{
  40. fibApp: fibApp,
  41. diaMode: cons.ModeUseCase,
  42. }
  43. sf.fibApp.Use(compress.New(compress.Config{
  44. Level: compress.LevelBestSpeed, // 1
  45. }))
  46. sf.fibApp.Static("/static", "./static", fiber.Static{
  47. Compress: true,
  48. Browse: true,
  49. CacheDuration: time.Second * 10,
  50. })
  51. sf.fibApp.Get("/", sf.indexGet)
  52. sf.fibApp.Get("/use_case", sf.setModeUseCase)
  53. sf.fibApp.Post("/arch", sf.archPost)
  54. return sf, nil
  55. }
  56. // Run -- запуск http-сервиса
  57. func (sf *ServHttp) Run() error {
  58. if err := sf.fibApp.Listen(":8100"); err != nil {
  59. return fmt.Errorf("ServHttp.Run(): in listen, err=\n\t%w", err)
  60. }
  61. return nil
  62. }
  63. // FormArch -- извлекает данные из текстового поля для обновления
  64. type FormArch struct {
  65. StrData string `form:"text"`
  66. }
  67. // Возвращает новый холст
  68. func (sf *ServHttp) canvas(wr *bytes.Buffer, lstElem []map[string]interface{}) (*svg.SVG, error) {
  69. var (
  70. elem map[string]interface{}
  71. isFind, isOk bool
  72. _section interface{}
  73. )
  74. for _, elem = range lstElem {
  75. _section, isOk = elem["type"]
  76. if !isOk {
  77. continue
  78. }
  79. section, isOk := _section.(string)
  80. if !isOk {
  81. return nil, fmt.Errorf("ServHttp.canvas(): in mapElem, type(%#+v) not string<br>%#+v", _section, elem)
  82. }
  83. if section == cons.SectionConfig {
  84. isFind = true
  85. break
  86. }
  87. }
  88. if !isFind {
  89. return nil, fmt.Errorf("ServHttp.canvas(): in mapElem, config not found")
  90. }
  91. confCanvas := canvas.NewCanvas(sf.diaMode, elem)
  92. sizeX, sizeY := confCanvas.Size()
  93. canvas := svg.New(wr)
  94. canvas.Start(int(sizeX), int(sizeY))
  95. confCanvas.Draw(canvas)
  96. return canvas, nil
  97. }
  98. // По запросы парсит, что получилось -- обновляет архитектуру
  99. func (sf *ServHttp) archPost(ctx *fiber.Ctx) error {
  100. form := &FormArch{}
  101. if err := ctx.BodyParser(form); err != nil {
  102. return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in BodyParser, err=<br>%v", err))
  103. }
  104. binData := []byte(form.StrData)
  105. if len(binData) == len(sf.oldBinArch) {
  106. return ctx.Send(sf.oldBinArch)
  107. }
  108. mapElem := []map[string]interface{}{}
  109. err := json.Unmarshal(binData, &mapElem)
  110. if err != nil {
  111. return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in Decode JSON, err=<br>%v", err))
  112. }
  113. wr := bytes.NewBuffer(nil)
  114. canvas, err := sf.canvas(wr, mapElem)
  115. if err != nil {
  116. return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in create canvas, err=<br>%v", err))
  117. }
  118. switch sf.diaMode {
  119. case cons.ModeUseCase: // режим вариантов использования
  120. if err := sf.useCase(canvas, mapElem); err != nil {
  121. return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): in useCase, err=<br>%v", err))
  122. }
  123. default: // неизвестный режим
  124. return ctx.SendString(fmt.Sprintf("ServHttp.archPost(): unknown mode=%q", sf.diaMode))
  125. }
  126. canvas.End()
  127. binData = wr.Bytes()
  128. return ctx.Send(binData)
  129. }
  130. // Формирует варианты использования
  131. func (sf *ServHttp) useCase(canvas *svg.SVG, lstElem []map[string]interface{}) error {
  132. mapDraw := make(map[alias.Id]types.IElemDrawer)
  133. for _, elem := range lstElem {
  134. _id, isOk := elem["id"]
  135. if !isOk {
  136. return fmt.Errorf("ServHttp.useCase(): field 'id' not found<br>%#+v", elem)
  137. }
  138. id0, isOk := _id.(string)
  139. if !isOk {
  140. return fmt.Errorf("ServHttp.useCase(): field 'id'(%T, %+v) not alias.Id", _id, _id)
  141. }
  142. id := alias.Id(id0)
  143. if id == "" {
  144. return fmt.Errorf("ServHttp.useCase(): id=%q, elem is empty<br>%#+v", id, elem)
  145. }
  146. _, isOk = mapDraw[id]
  147. if isOk {
  148. return fmt.Errorf("ServHttp.useCase(): id=%q already exists", id)
  149. }
  150. binElem, _ := json.MarshalIndent(elem, "", " ")
  151. _type, isOk := elem["type"]
  152. if !isOk {
  153. return fmt.Errorf("ServHttp.useCase(): id=%q, field 'type' not found<br>%+v", id, string(binElem))
  154. }
  155. strType, isOk := _type.(string)
  156. if !isOk {
  157. return fmt.Errorf("ServHttp.useCase(): id=%q, field 'type'(%+v) not string<br>%+v", id, _type, string(binElem))
  158. }
  159. var (
  160. drawer types.IElemDrawer
  161. err error
  162. )
  163. switch strType {
  164. case cons.TypeUseActor: // Нарисовать актора
  165. drawer, err = view_actor.NewViewActor(elem)
  166. if err != nil {
  167. return fmt.Errorf("ServHttp.useCase(): id=%q, in create Actor, err=<br>%v", id, err)
  168. }
  169. case cons.TypeUseCase: // Нарисовать вариант использования
  170. drawer, err = view_use_case.NewViewUseCase(elem)
  171. if err != nil {
  172. return fmt.Errorf("ServHttp.useCase(): id=%q, err=%w<br>%+v", id, err, string(binElem))
  173. }
  174. case cons.TypeUseLink: // Нарисовать связь
  175. drawer, err = view_link.NewViewLink(elem)
  176. if err != nil {
  177. return fmt.Errorf("ServHttp.useCase(): id=%q, in create Link, err=%w<br>%+v", id, err, string(binElem))
  178. }
  179. case cons.TypeUseGroup: // Нарисовать группу вариантов использования
  180. drawer, err = view_use_group.NewViewUseGroup(elem)
  181. if err != nil {
  182. return fmt.Errorf("ServHttp.useCase(): id=%q, in create UseGroup, err=%w<br>%+v", id, err, string(binElem))
  183. }
  184. }
  185. if drawer != nil {
  186. mapDraw[id] = drawer
  187. log.Printf("ServHttp.useCase(): id=%q, elem=\n%v\n\n", id, elem)
  188. }
  189. }
  190. for _, drawer := range mapDraw {
  191. switch drawer.(type) {
  192. case types.ILinker:
  193. }
  194. drawer.Draw(canvas, mapDraw)
  195. }
  196. return nil
  197. }
  198. // Возвращает картинку архитектуры
  199. func (sf *ServHttp) setModeUseCase(ctx *fiber.Ctx) error {
  200. sf.diaMode = cons.ModeUseCase
  201. return ctx.SendString("[mode='" + sf.diaMode + "']")
  202. }
  203. // Возвращает главную страницу сервиса
  204. func (sf *ServHttp) indexGet(ctx *fiber.Ctx) error {
  205. ctx.Response().Header.Set("Content-Type", "text/html")
  206. return ctx.Send(indexHtml)
  207. }