schema.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. package rest
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "reflect"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "git.nspix.com/golang/micro/helper/utils"
  10. lru "github.com/hashicorp/golang-lru"
  11. "gorm.io/gorm"
  12. "gorm.io/gorm/clause"
  13. "gorm.io/gorm/schema"
  14. )
  15. var (
  16. ErrInvalidModelInstance = errors.New("invalid model instance")
  17. timeKind = reflect.TypeOf(time.Time{}).Kind()
  18. timePtrKind = reflect.TypeOf(&time.Time{}).Kind()
  19. schemaCache, _ = lru.New(512)
  20. DefaultNamespace = "default"
  21. schemaDB *gorm.DB
  22. )
  23. const (
  24. ScenarioCreate = "create"
  25. ScenarioUpdate = "update"
  26. ScenarioDelete = "delete"
  27. ScenarioSearch = "search"
  28. ScenarioExport = "export"
  29. ScenarioList = "list"
  30. ScenarioView = "view"
  31. ScenarioMapping = "mapping"
  32. Basic = "basic"
  33. Advanced = "advanced"
  34. MatchExactly = "exactly" //精确匹配
  35. MatchFuzzy = "fuzzy" //模糊匹配
  36. )
  37. type (
  38. Rule struct { //规则配置
  39. Min int `json:"min"`
  40. Max int `json:"max"`
  41. Type string `json:"type"`
  42. Unique bool `json:"unique"`
  43. Required []string `json:"required"`
  44. Regular string `json:"regular"`
  45. }
  46. FieldValue struct {
  47. Label string `json:"label"`
  48. Value interface{} `json:"value"`
  49. Color string `json:"color"`
  50. }
  51. visibleCond struct {
  52. Column string `json:"column"`
  53. Values []interface{} `json:"values"`
  54. }
  55. liveAttribute struct { //值容器
  56. Enable bool `json:"enable"`
  57. Type string `json:"type"`
  58. Url string `json:"url"`
  59. Columns []string `json:"columns"`
  60. }
  61. Properties struct { //属性配置
  62. Match string `json:"match"` //匹配模式
  63. PrimaryKey bool `json:"primary_key"` //是否为主键
  64. DefaultValue string `json:"default_value"` //默认值
  65. Readonly []string `json:"readonly"` //只读场景
  66. Disable []string `json:"disable"` //禁用场景
  67. Visible []visibleCond `json:"visible"` //可见条件
  68. Values []FieldValue `json:"values"` //值
  69. Live liveAttribute `json:"live"` //延时加载配置
  70. Icon string `json:"icon"` //显示图标
  71. Suffix string `json:"suffix"` //追加内容
  72. Tooltip string `json:"tooltip"` //字段提示信息
  73. Description string `json:"description"` //字段说明信息
  74. }
  75. Schema struct { //Schema的表
  76. Id uint64 `json:"id" gorm:"primary_key"`
  77. CreatedAt int64 `json:"created_at" gorm:"autoCreateTime"` //创建时间
  78. UpdatedAt int64 `json:"updated_at" gorm:"autoUpdateTime"` //更新时间
  79. Namespace string `json:"namespace" gorm:"column:namespace;type:char(60);index"` //域
  80. Module string `json:"module" gorm:"column:module_name;type:varchar(60);index"` //模块名称
  81. Table string `json:"table" gorm:"column:table_name;type:varchar(120);index"` //表名称
  82. Enable uint8 `json:"enable" gorm:"column:enable;type:int(1)"` //是否启用
  83. Tag string `json:"tag" gorm:"column:tag;type:varchar(60)"` //字段归类
  84. Column string `json:"column" gorm:"type:varchar(120)"` //字段名称
  85. Label string `json:"label" gorm:"type:varchar(120)"` //显示名称
  86. Type string `json:"type" gorm:"type:varchar(120)"` //字段类型
  87. Format string `json:"format" gorm:"type:varchar(120)"` //字段格式
  88. Native uint8 `json:"native" gorm:"type:int(1)"` //是否为原生字段
  89. PrimaryKey uint8 `json:"primary_key" gorm:"type:int(1)"` //是否为主键
  90. Expression string `json:"expression" gorm:"type:varchar(526)"` //计算规则
  91. Rules string `json:"rules" gorm:"type:varchar(2048)"` //字段规则
  92. Scenarios string `json:"scenarios" gorm:"type:varchar(120)"` //场景
  93. Properties string `json:"properties" gorm:"type:varchar(4096)"` //字段属性
  94. Position int `json:"position"` //字段排序位置
  95. properties *Properties
  96. }
  97. Field struct {
  98. Column string `json:"column" gorm:"type:varchar(120)"`
  99. Label string `json:"label" gorm:"type:varchar(120)"`
  100. Type string `json:"type" gorm:"type:varchar(120)"`
  101. Format string `json:"format" gorm:"type:varchar(120)"`
  102. Scenario string `json:"scenario" gorm:"type:varchar(120)"`
  103. Rules string `json:"rules" gorm:"type:varchar(2048)"`
  104. Options string `json:"options" gorm:"type:varchar(4096)"`
  105. }
  106. )
  107. func (r *Rule) String() string {
  108. buf, _ := json.Marshal(r)
  109. return string(buf)
  110. }
  111. // getProperties 获取属性
  112. func (schema *Schema) getProperties() *Properties {
  113. if schema.properties == nil {
  114. schema.properties = &Properties{}
  115. _ = json.Unmarshal([]byte(schema.Properties), schema.properties)
  116. }
  117. return schema.properties
  118. }
  119. func dataTypeOf(field *schema.Field) string {
  120. var dataType string
  121. reflectType := field.FieldType
  122. for reflectType.Kind() == reflect.Ptr {
  123. reflectType = reflectType.Elem()
  124. }
  125. dataValue := reflect.Indirect(reflect.New(reflectType))
  126. switch dataValue.Kind() {
  127. case reflect.Bool:
  128. dataType = "boolean"
  129. case reflect.Int8, reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  130. dataType = "integer"
  131. case reflect.Float32, reflect.Float64:
  132. dataType = "double"
  133. case reflect.Struct:
  134. if _, ok := dataValue.Interface().(time.Time); ok {
  135. dataType = "string"
  136. }
  137. default:
  138. dataType = "string"
  139. }
  140. return dataType
  141. }
  142. //获取字段格式
  143. func dataFormatOf(field *schema.Field) string {
  144. var dataType string
  145. dataType = field.Tag.Get("format")
  146. if dataType != "" {
  147. return dataType
  148. }
  149. //如果有枚举值,直接设置为下拉类型
  150. enum := field.Tag.Get("enum")
  151. if enum != "" {
  152. return "dropdown"
  153. }
  154. reflectType := field.FieldType
  155. for reflectType.Kind() == reflect.Ptr {
  156. reflectType = reflectType.Elem()
  157. }
  158. //时间处理
  159. if utils.InArray(field.Name, []string{"CreatedAt", "UpdatedAt", "DeletedAt"}) {
  160. return "datetime"
  161. }
  162. dataValue := reflect.Indirect(reflect.New(reflectType))
  163. switch dataValue.Kind() {
  164. case timeKind, timePtrKind:
  165. dataType = "datetime"
  166. case reflect.Bool:
  167. dataType = "boolean"
  168. case reflect.Int8, reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  169. dataType = "integer"
  170. case reflect.Float32, reflect.Float64:
  171. dataType = "decimal"
  172. case reflect.Struct:
  173. if _, ok := dataValue.Interface().(time.Time); ok {
  174. dataType = "datetime"
  175. }
  176. default:
  177. dataType = "string"
  178. }
  179. return dataType
  180. }
  181. func createStatement(db *gorm.DB) *gorm.Statement {
  182. return &gorm.Statement{
  183. DB: db,
  184. ConnPool: db.Statement.ConnPool,
  185. Context: db.Statement.Context,
  186. Clauses: map[string]clause.Clause{},
  187. }
  188. }
  189. // visibleSchemas 获取某个场景下面的字段
  190. func visibleSchemas(namespace, modelName, tableName, scenario string) (schemas []*Schema) {
  191. schemas, _ = getSchemas(namespace, modelName, tableName)
  192. values := make([]*Schema, 0)
  193. for _, scm := range schemas {
  194. if scm.Enable != 1 {
  195. continue
  196. }
  197. if scm.PrimaryKey == 1 {
  198. values = append(values, scm)
  199. } else {
  200. if strings.Contains(scm.Scenarios, scenario) {
  201. values = append(values, scm)
  202. }
  203. }
  204. }
  205. return values
  206. }
  207. // getSchemas 获取某个模型下面所有的字段配置
  208. func getSchemas(namespace, moduleName, tableName string) (schemas []*Schema, err error) {
  209. schemas = make([]*Schema, 0)
  210. cacheKey := namespace + ":" + tableName + "@" + moduleName
  211. if v, ok := schemaCache.Get(cacheKey); ok {
  212. return v.([]*Schema), nil
  213. }
  214. if len(namespace) == 0 {
  215. namespace = DefaultNamespace
  216. }
  217. if err = schemaDB.Where("`namespace`=? AND `module_name`=? AND `table_name`=?", namespace, moduleName, tableName).Order("position,id ASC").Find(&schemas).Error; err == nil {
  218. //修改表结构缓存
  219. if len(schemas) > 0 {
  220. schemaCache.Add(cacheKey, schemas)
  221. }
  222. }
  223. return
  224. }
  225. func getSchemasNoCache(db *gorm.DB, namespace, moduleName, tableName string) (schemas []*Schema, err error) {
  226. tx := db.Session(&gorm.Session{NewDB: true, SkipHooks: true})
  227. if len(namespace) == 0 {
  228. namespace = DefaultNamespace
  229. }
  230. err = tx.Where("`namespace`=? AND `module_name`=? AND `table_name`=?", namespace, moduleName, tableName).Order("position,id ASC").Find(&schemas).Error
  231. return
  232. }
  233. // invalidCache 删除表结构缓存
  234. func invalidCache(namespace, moduleName, tableName string) {
  235. cacheKey := namespace + ":" + tableName + "@" + moduleName
  236. schemaCache.Remove(cacheKey)
  237. }
  238. // extraSize 提取模型定义的长度大小
  239. func extraSize(str string) int {
  240. var (
  241. sb strings.Builder
  242. aflag = -1
  243. )
  244. b := make([]byte, 0)
  245. b = append(b, str...)
  246. for _, s := range b {
  247. if s >= '0' && s <= '9' {
  248. if aflag == -1 {
  249. aflag = 1
  250. }
  251. sb.WriteByte(s)
  252. } else if aflag == 1 {
  253. break
  254. }
  255. }
  256. v, _ := strconv.Atoi(sb.String())
  257. return v
  258. }
  259. // generateFieldRule 生成数据校验规则
  260. func generateFieldRule(field *schema.Field) string {
  261. r := &Rule{
  262. Required: []string{},
  263. }
  264. if field.GORMDataType == schema.String {
  265. r.Max = field.Size
  266. }
  267. if field.GORMDataType == schema.Int || field.GORMDataType == schema.Float || field.GORMDataType == schema.Uint {
  268. r.Max = field.Scale
  269. }
  270. if field.NotNull {
  271. r.Required = []string{ScenarioCreate, ScenarioUpdate}
  272. }
  273. if field.PrimaryKey {
  274. r.Unique = true
  275. }
  276. return r.String()
  277. }
  278. // generateFieldProperties 生成数据属性
  279. func generateFieldProperties(field *schema.Field) string {
  280. attr := &Properties{
  281. Match: MatchFuzzy,
  282. PrimaryKey: field.PrimaryKey,
  283. DefaultValue: field.DefaultValue,
  284. Readonly: []string{},
  285. Disable: []string{},
  286. Visible: nil,
  287. Values: make([]FieldValue, 0),
  288. Live: liveAttribute{},
  289. Description: field.DBName,
  290. }
  291. //赋值属性
  292. props := field.Tag.Get("props")
  293. if props != "" {
  294. vs := strings.Split(props, ";")
  295. for _, str := range vs {
  296. kv := strings.SplitN(str, ":", 2)
  297. if len(kv) != 2 {
  298. continue
  299. }
  300. switch strings.ToLower(kv[0]) {
  301. case "icon":
  302. attr.Icon = kv[1]
  303. case "suffix":
  304. attr.Suffix = kv[1]
  305. case "tooltip":
  306. attr.Tooltip = kv[1]
  307. case "description":
  308. attr.Description = kv[1]
  309. case "live":
  310. //在线数据解析
  311. ss := strings.Split(kv[1], ",")
  312. for _, s := range ss {
  313. if len(s) == 0 {
  314. continue
  315. }
  316. attr.Live.Enable = true
  317. if s == "dropdown" || s == "cascader" {
  318. attr.Live.Type = s
  319. } else if s[0] == '/' || strings.HasPrefix(s, "http") {
  320. attr.Live.Url = s
  321. } else {
  322. if strings.IndexByte(s, '.') > -1 {
  323. attr.Live.Columns = strings.Split(s, ".")
  324. }
  325. }
  326. }
  327. }
  328. }
  329. }
  330. //赋值枚举值
  331. enumns := field.Tag.Get("enum")
  332. if enumns != "" {
  333. vs := strings.Split(enumns, ";")
  334. for _, str := range vs {
  335. kv := strings.SplitN(str, ":", 2)
  336. if len(kv) != 2 {
  337. continue
  338. }
  339. fv := FieldValue{Value: kv[0]}
  340. //颜色分隔符
  341. if pos := strings.IndexByte(kv[1], '#'); pos > -1 {
  342. fv.Label = kv[1][:pos]
  343. fv.Color = kv[1][pos:]
  344. } else {
  345. fv.Label = kv[1]
  346. }
  347. attr.Values = append(attr.Values, fv)
  348. }
  349. }
  350. if !field.Creatable {
  351. attr.Disable = append(attr.Disable, ScenarioCreate)
  352. }
  353. if !field.Updatable {
  354. attr.Disable = append(attr.Disable, ScenarioUpdate)
  355. }
  356. attr.Tooltip = field.Comment
  357. if buf, err := json.Marshal(attr); err == nil {
  358. return string(buf)
  359. }
  360. return ""
  361. }
  362. // generateFieldScenario 生成字段应用场景
  363. func generateFieldScenario(field *schema.Field) string {
  364. var ss []string
  365. if v, ok := field.Tag.Lookup("scenarios"); ok {
  366. v = strings.TrimSpace(v)
  367. if v != "" {
  368. ss = strings.Split(v, ";")
  369. }
  370. } else {
  371. if field.PrimaryKey {
  372. ss = []string{ScenarioList, ScenarioView, ScenarioExport}
  373. } else if field.Name == "CreatedAt" || field.Name == "UpdatedAt" {
  374. ss = []string{ScenarioList}
  375. } else if field.Name == "DeletedAt" || field.Name == "Namespace" {
  376. //不添加任何显示场景
  377. ss = []string{}
  378. } else {
  379. //高级字段只配置一些简单的场景
  380. if field.Tag.Get("tag") == Advanced {
  381. ss = []string{ScenarioCreate, ScenarioUpdate, ScenarioView, ScenarioExport}
  382. } else {
  383. ss = []string{ScenarioSearch, ScenarioList, ScenarioCreate, ScenarioUpdate, ScenarioView, ScenarioExport}
  384. }
  385. }
  386. }
  387. if buf, err := json.Marshal(ss); err == nil {
  388. return string(buf)
  389. }
  390. return ""
  391. }
  392. // migrate 合并数据表结构
  393. func migrateUp(namespace string, value interface{}) (err error) {
  394. var (
  395. pos int
  396. ok bool
  397. model Model
  398. moduleName string
  399. tableName string
  400. stmt *gorm.Statement
  401. scm *Schema
  402. schemas []*Schema
  403. values []*Schema
  404. field *schema.Field
  405. columnIsExists bool
  406. columnName string
  407. columnLabel string
  408. )
  409. if schemaDB == nil {
  410. return errors.New("call initSchema first")
  411. }
  412. if len(namespace) == 0 {
  413. namespace = DefaultNamespace
  414. }
  415. stmt = createStatement(schemaDB)
  416. if value == nil {
  417. return ErrInvalidModelInstance
  418. }
  419. if model, ok = value.(Model); !ok {
  420. return ErrInvalidModelInstance
  421. }
  422. moduleName = model.ModuleName()
  423. tableName = model.TableName()
  424. if err = stmt.Parse(value); err != nil {
  425. return err
  426. }
  427. if schemas, err = getSchemas(namespace, moduleName, tableName); err != nil {
  428. schemas = make([]*Schema, 0)
  429. }
  430. totalCount := len(stmt.Schema.Fields)
  431. //遍历所有字段
  432. for _, field = range stmt.Schema.Fields {
  433. columnName = field.DBName
  434. if columnName == "-" {
  435. continue
  436. }
  437. if columnName == "" {
  438. columnName = field.Name
  439. }
  440. columnIsExists = false
  441. for _, scm = range schemas {
  442. if scm.Column == columnName {
  443. columnIsExists = true
  444. break
  445. }
  446. }
  447. if columnIsExists {
  448. continue
  449. }
  450. columnLabel = field.Tag.Get("comment")
  451. if columnLabel == "" {
  452. columnLabel = strings.Join(utils.BreakUp(field.DBName), " ")
  453. }
  454. isPrimaryKey := uint8(0)
  455. if field.PrimaryKey {
  456. isPrimaryKey = 1
  457. }
  458. pv := pos
  459. //默认把创建时间和更新时间放到最后
  460. if field.Name == "CreatedAt" || field.Name == "UpdatedAt" || field.Name == "DeletedAt" {
  461. pv = totalCount
  462. }
  463. tag := field.Tag.Get("tag")
  464. if tag == "" {
  465. tag = Basic
  466. }
  467. values = append(values, &Schema{
  468. Namespace: namespace,
  469. Module: moduleName,
  470. Table: tableName,
  471. Enable: 1,
  472. Tag: tag,
  473. Column: columnName,
  474. Label: columnLabel,
  475. Type: strings.ToLower(dataTypeOf(field)),
  476. Format: strings.ToLower(dataFormatOf(field)),
  477. Native: 1,
  478. PrimaryKey: isPrimaryKey,
  479. Rules: generateFieldRule(field),
  480. Scenarios: generateFieldScenario(field),
  481. Properties: generateFieldProperties(field),
  482. Position: pv,
  483. })
  484. pos++
  485. }
  486. if len(values) > 0 {
  487. //batch save
  488. err = schemaDB.Create(values).Error
  489. }
  490. return
  491. }
  492. func initSchema(db *gorm.DB) (err error) {
  493. schemaDB = db.Session(&gorm.Session{NewDB: true})
  494. err = schemaDB.AutoMigrate(&Schema{})
  495. return
  496. }