schema.go 15 KB

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