schema.go 15 KB

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