package domain import ( "git.sxidc.com/go-framework/baize/framework/core/infrastructure/logger" "git.sxidc.com/go-framework/baize/framework/core/tag/check" "git.sxidc.com/go-tools/utils/reflectutils" "git.sxidc.com/go-tools/utils/strutils" "git.sxidc.com/go-tools/utils/template" "github.com/iancoleman/strcase" "github.com/pkg/errors" "reflect" ) const ( WhenCreate = "create" WhenDelete = "delete" WhenUpdate = "update" ) // Object 领域对象接口 type Object interface { // DomainCNName 返回领域的中文名称 DomainCNName() string // DomainCamelName 返回领域的大写驼峰式名称 DomainCamelName() string // 获取字段解释map GetFieldMap() map[string]string } // HasField 校验领域对象中是否有某一个字段 // 参数: // - object: 领域对象 // - fieldName: 字段名 // 返回值: // - 是否存在 func HasField(object Object, fieldName string) bool { return hasField(object, fieldName) } // CheckFieldsForCreate 为创建校验领域对象字段 // 参数: // - object: 领域对象 // - fieldMap: 字段说明map // 返回值: // - 错误 func CheckFieldsForCreate(object Object, fieldMap map[string]string) error { return CheckWhen(object, fieldMap, WhenCreate) } // CheckFieldsForDelete 为删除校验领域对象字段 // // 参数: // - object: 领域对象 // - fieldMap: 字段说明map // 返回值: // - 错误 func CheckFieldsForDelete(object Object, fieldMap map[string]string) error { return CheckWhen(object, fieldMap, WhenDelete) } // CheckFieldsForUpdate 为更新校验领域对象字段 // // 参数: // - object: 领域对象 // - fieldMap: 字段说明map // 返回值: // - 错误 func CheckFieldsForUpdate(object Object, fieldMap map[string]string) error { return CheckFieldsWhen(object, fieldMap, WhenUpdate, func(fieldName string) bool { fieldValue, err := getFieldValue(object, fieldName) if err != nil { logger.GetInstance().Error(err) return false } if !fieldValue.IsValid() { return false } if fieldValue.IsZero() { return false } if fieldValue.Kind() == reflect.Pointer && fieldValue.IsNil() { return false } return true }) } // Check 校验领域对象 // 参数: // - object: 领域对象 // - fieldMap: 字段说明map // - when: 何时校验 // 返回值: // - 错误 func Check(object Object, fieldMap map[string]string) error { err := check.Struct(object, fieldMap).CheckWhen("") if err != nil { return errors.New(object.DomainCNName() + ": " + err.Error()) } return nil } // CheckField 校验领域对象字段 // 参数: // - object: 领域对象 // - fieldName: 字段名 // - fieldMap: 字段说明map // 返回值: // - 错误 func CheckField(object Object, fieldName string, fieldMap map[string]string) error { err := check.Struct(object, fieldMap).CheckFieldWhen("", func(fn string) bool { if fieldName == fn { return true } return false }) if err != nil { return errors.New(object.DomainCNName() + ": " + err.Error()) } return nil } // CheckFields 校验领域对象字段 // 参数: // - object: 领域对象 // - fieldMap: 字段说明map // - checkFunc: 检查函数,返回true检查该字段,返回false不检查该字段 // 返回值: // - 错误 func CheckFields(object Object, fieldMap map[string]string, checkFunc func(fieldName string) bool) error { err := check.Struct(object, fieldMap).CheckFieldWhen("", checkFunc) if err != nil { return errors.New(object.DomainCNName() + ": " + err.Error()) } return nil } // CheckWhen 校验领域对象 // 参数: // - object: 领域对象 // - fieldMap: 字段说明map // - when: 何时校验 // 返回值: // - 错误 func CheckWhen(object Object, fieldMap map[string]string, when string) error { err := check.Struct(object, fieldMap).CheckWhen(when) if err != nil { return errors.New(object.DomainCNName() + ": " + err.Error()) } return nil } // CheckFieldWhen 校验领域对象字段 // 参数: // - object: 领域对象 // - fieldName: 字段名 // - fieldMap: 字段说明map // - when: 何时校验 // 返回值: // - 错误 func CheckFieldWhen(object Object, fieldName string, fieldMap map[string]string, when string) error { err := check.Struct(object, fieldMap).CheckFieldWhen(when, func(fn string) bool { if fieldName == fn { return true } return false }) if err != nil { return errors.New(object.DomainCNName() + ":\n" + err.Error()) } return nil } // CheckFieldsWhen 校验领域对象字段 // 参数: // - object: 领域对象 // - fieldMap: 字段说明map // - when: 何时校验 // - checkFunc: 检查函数,返回true检查该字段,返回false不检查该字段 // 返回值: // - 错误 func CheckFieldsWhen(object Object, fieldMap map[string]string, when string, checkFunc func(fieldName string) bool) error { err := check.Struct(object, fieldMap).CheckFieldWhen(when, checkFunc) if err != nil { return errors.New(object.DomainCNName() + ": " + err.Error()) } return nil } // SetField 设置领域对象对应字段的值 // 类型参数: // - T: 字段值的类型 // 参数: // - object: 领域对象 // - fieldName: 要设置值的字段名 // - value: 设置的值 // 返回值: // - 错误 func SetField[T any](object Object, fieldName string, value T) error { if object == nil { return errors.New("领域对象为nil") } fieldValue, err := getFieldValue(object, fieldName) if err != nil { return err } if !fieldValue.IsValid() || !fieldValue.CanSet() { return errors.New("领域对象" + fieldValue.Type().String() + "的字段" + fieldName + "无法赋值") } fieldValue.Set(reflect.ValueOf(value)) return nil } // Field 获取领域对象对应字段的值 // 类型参数: // - T: 字段值的类型 // 参数: // - object: 领域对象 // - fieldName: 要获取值的字段名 // 返回值: // - 错误 func Field[T any](object Object, fieldName string) (T, error) { zero := reflectutils.Zero[T]() if object == nil { return zero, errors.New("领域对象为nil") } fieldValue, err := getFieldValue(object, fieldName) if err != nil { return zero, err } if !fieldValue.IsValid() { return zero, errors.New("领域对象" + fieldValue.Type().String() + "的字段" + fieldName + "无法赋值") } retValue, ok := fieldValue.Interface().(T) if !ok { return zero, errors.New("转换字段类型失败") } return retValue, nil } // ToConcrete 将领域对象转换为具体类型 // 类型参数: // - T: 要转换到的类型 // 参数: // - object: 领域对象 // 返回值: // - 转换出的类型 // - 错误 func ToConcrete[T Object](object Object) (T, error) { zero := reflectutils.Zero[T]() if object == nil { return zero, errors.New("领域对象为nil") } concrete, ok := object.(T) if !ok { return zero, errors.New("领域对象转化失败") } return concrete, nil } // TableName 基于领域对象生成表名,实际是将领域对象的驼峰式名称转换为蛇形复数形式,如classes // 参数: // - schema: 数据库的schema // - object: 领域对象 // 返回值: // - 表名 func TableName(schema string, object Object) string { if strutils.IsStringEmpty(schema) { return template.Plural(strcase.ToSnake(template.Id(object.DomainCamelName()))) } else { return schema + "." + template.Plural(strcase.ToSnake(template.Id(object.DomainCamelName()))) } } // RelationTableName 生成两个领域对象的关联表名,实际是将两个领域对象的驼峰式名称转换为蛇形并使用and连接,如:class_and_student // 参数: // - schema: 数据库的schema // - left: 左领域对象 // - right: 右领域对象 // 返回值: // - 关联表名 func RelationTableName(schema string, left Object, right Object) string { if strutils.IsStringEmpty(schema) { return strcase.ToSnake(template.Id(left.DomainCamelName())) + "_and_" + strcase.ToSnake(template.Id(right.DomainCamelName())) } else { return schema + "." + strcase.ToSnake(template.Id(left.DomainCamelName())) + "_and_" + strcase.ToSnake(template.Id(right.DomainCamelName())) } } // ColumnName 生成对应字段的列名,实际为字段名转换为蛇形,如StudentNum会转化为student_num // 参数: // - fieldName: 字段名 // 返回值: // - 列名 func ColumnName(fieldName string) string { return strcase.ToSnake(template.Id(fieldName)) } // RelationColumnName 基于领域对象生成关联列名,实际为字段名转换为蛇形后加_id,如Student生成的关联列名为student_id // 参数: // - object: 领域对象 // 返回值: // - 关联列名 func RelationColumnName(object Object) string { return strcase.ToSnake(template.Id(object.DomainCamelName())) + "_id" } // RelativeDomainPath 基于领域对象生成领域URL路径,实际为字段名转换为左小写驼峰式前面加/,如Student生成的领域URL路径为/student // 参数: // - object: 领域对象 // 返回值: // - 领域URL路径 func RelativeDomainPath(object Object) string { return "/" + strcase.ToLowerCamel(template.Id(object.DomainCamelName())) } // SnakeDomainName 基于领域对象生成蛇形领域名称,如Student生成的蛇形领域名称为/student // 参数: // - object: 领域对象 // 返回值: // - 蛇形领域名称 func SnakeDomainName(object Object) string { return strcase.ToSnake(template.Id(object.DomainCamelName())) } func hasField(object Object, fieldName string) bool { if object == nil { return false } objectValue := reflect.ValueOf(object) if !reflectutils.IsValueStructOrStructPointer(objectValue) { return false } fieldValue := reflectutils.PointerValueElem(objectValue).FieldByName(fieldName) if !fieldValue.IsValid() { return false } return true } func getFieldValue(object Object, fieldName string) (*reflect.Value, error) { if object == nil { return nil, errors.New("领域对象为nil") } objectValue := reflect.ValueOf(object) if !reflectutils.IsValueStructOrStructPointer(objectValue) { return nil, errors.New("领域对象必须是结构或结构指针") } fieldValue := reflectutils.PointerValueElem(objectValue).FieldByName(fieldName) return &fieldValue, nil }