|
@@ -0,0 +1,699 @@
|
|
|
+package rest
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "encoding/csv"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "git.nspix.com/golang/micro/gateway/http"
|
|
|
+ errpkg "git.nspix.com/golang/rest/v3/error"
|
|
|
+ "git.nspix.com/golang/rest/v3/inflector"
|
|
|
+ "gorm.io/gorm"
|
|
|
+ "gorm.io/gorm/clause"
|
|
|
+ "path"
|
|
|
+ "reflect"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ HttpAccessDenied = 8004 //拒绝访问
|
|
|
+ HttpInvalidPayload = 8002 //请求内容无效
|
|
|
+ HttpRequestCallbackFailed = 8003 //执行回调失败
|
|
|
+ HttpValidateFailed = 8008 //数据校验失败
|
|
|
+ HttpDatabaseQueryFailed = 8010 //查询失败
|
|
|
+ HttpDatabaseFindFailed = 8011 //查找失败
|
|
|
+ HttpDatabaseCreateFailed = 8012 //创建失败
|
|
|
+ HttpDatabaseUpdateFailed = 8013 //更新失败
|
|
|
+ HttpDatabaseDeleteFailed = 8014 //删除失败
|
|
|
+ HttpDatabaseExportFailed = 8015 //数据导出失败
|
|
|
+ HTTPUnknownFailed = 9001 //未知错误
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ ErrorAccessDeniedMessage = "access denied"
|
|
|
+)
|
|
|
+
|
|
|
+type (
|
|
|
+ DiffAttr struct {
|
|
|
+ Column string `json:"column"`
|
|
|
+ Label string `json:"label"`
|
|
|
+ OldValue interface{} `json:"old_value"`
|
|
|
+ NewValue interface{} `json:"new_value"`
|
|
|
+ }
|
|
|
+
|
|
|
+ Restful struct {
|
|
|
+ model Model
|
|
|
+ opts *Options
|
|
|
+ reflectType reflect.Type
|
|
|
+ reflectValue reflect.Value
|
|
|
+ singularName string
|
|
|
+ pluralizeName string
|
|
|
+ statement *gorm.Statement
|
|
|
+ primaryKey string
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+// getScenarioUrl 获取某个场景下HTTP请求的URL
|
|
|
+func (r *Restful) getScenarioUrl(scenario string) string {
|
|
|
+ var uri string
|
|
|
+ switch scenario {
|
|
|
+ case ScenarioList:
|
|
|
+ uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.pluralizeName
|
|
|
+ case ScenarioView:
|
|
|
+ uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName + "/:id"
|
|
|
+ case ScenarioCreate:
|
|
|
+ uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName
|
|
|
+ case ScenarioUpdate:
|
|
|
+ uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName + "/:id"
|
|
|
+ case ScenarioDelete:
|
|
|
+ uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName + "/:id"
|
|
|
+ case ScenarioExport:
|
|
|
+ uri = r.opts.ApiPrefix + "/" + r.model.ModuleName() + "/" + r.singularName + "-export"
|
|
|
+ }
|
|
|
+ return path.Clean(uri)
|
|
|
+}
|
|
|
+
|
|
|
+// getScenarioHandle 获取某个场景下HTTP请求的处理回调
|
|
|
+func (r *Restful) getScenarioHandle(scenario string) http.HandleFunc {
|
|
|
+ var handleFunc http.HandleFunc
|
|
|
+ switch scenario {
|
|
|
+ case ScenarioList:
|
|
|
+ handleFunc = r.actionIndex
|
|
|
+ case ScenarioView:
|
|
|
+ handleFunc = r.actionView
|
|
|
+ case ScenarioCreate:
|
|
|
+ handleFunc = r.actionCreate
|
|
|
+ case ScenarioUpdate:
|
|
|
+ handleFunc = r.actionUpdate
|
|
|
+ case ScenarioDelete:
|
|
|
+ handleFunc = r.actionDelete
|
|
|
+ case ScenarioExport:
|
|
|
+ handleFunc = r.actionExport
|
|
|
+ }
|
|
|
+ return handleFunc
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) setFieldValue(model reflect.Value, column string, value interface{}) {
|
|
|
+ var (
|
|
|
+ name string
|
|
|
+ )
|
|
|
+ refVal := reflect.Indirect(model)
|
|
|
+ for _, field := range r.statement.Schema.Fields {
|
|
|
+ if field.DBName == column {
|
|
|
+ name = field.Name
|
|
|
+ break
|
|
|
+ } else if field.Name == column {
|
|
|
+ name = column
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if name == "" {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fieldVal := refVal.FieldByName(name)
|
|
|
+ if fieldVal.CanSet() {
|
|
|
+ fieldVal.Set(reflect.ValueOf(value))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//getFieldValue get field value from reflect value
|
|
|
+func (r *Restful) getFieldValue(model reflect.Value, column string) interface{} {
|
|
|
+ var (
|
|
|
+ name string
|
|
|
+ )
|
|
|
+ refVal := reflect.Indirect(model)
|
|
|
+ for _, field := range r.statement.Schema.Fields {
|
|
|
+ if field.DBName == column {
|
|
|
+ name = field.Name
|
|
|
+ break
|
|
|
+ } else if field.Name == column {
|
|
|
+ name = column
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if name == "" {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ fieldVal := refVal.FieldByName(name)
|
|
|
+ return fieldVal.Interface()
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) hasScenario(s string) bool {
|
|
|
+ if v, ok := r.model.(FlexibleModel); ok {
|
|
|
+ for _, n := range v.Scenarios() {
|
|
|
+ if s == n {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) prepareConditions(ctx context.Context, requestCtx *http.Context, query *Query, schemas []*Schema) (err error) {
|
|
|
+ var (
|
|
|
+ ok bool
|
|
|
+ skip bool
|
|
|
+ formValue string
|
|
|
+ model interface{}
|
|
|
+ activeModel ActiveModel
|
|
|
+ )
|
|
|
+ model = reflect.New(r.reflectType).Interface()
|
|
|
+ if r.opts.Delegate != nil {
|
|
|
+ if err = r.opts.Delegate.BeforeQuery(ctx, query); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if activeModel, ok = model.(ActiveModel); ok {
|
|
|
+ if err = activeModel.BeforeQuery(ctx, query); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //处理默认的搜索
|
|
|
+ for _, schema := range schemas {
|
|
|
+ skip = false
|
|
|
+ if skip {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if schema.Native == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ formValue = requestCtx.FormValue(schema.Column)
|
|
|
+ switch schema.Format {
|
|
|
+ case "string", "text", "textarea":
|
|
|
+ if schema.Attribute.Match == MatchExactly {
|
|
|
+ query.AndFilterWhere(NewCond(schema.Column, formValue))
|
|
|
+ } else {
|
|
|
+ query.AndFilterWhere(NewCond(schema.Column, formValue).WithExpr("LIKE"))
|
|
|
+ }
|
|
|
+ case "date", "time", "datetime":
|
|
|
+ var sep string
|
|
|
+ seps := []byte{',', '/'}
|
|
|
+ for _, s := range seps {
|
|
|
+ if strings.IndexByte(formValue, s) > -1 {
|
|
|
+ sep = string(s)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ss := strings.Split(formValue, sep); len(ss) == 2 {
|
|
|
+ query.AndFilterWhere(
|
|
|
+ NewCond(schema.Column, strings.TrimSpace(ss[0])).WithExpr(">="),
|
|
|
+ NewCond(schema.Column, strings.TrimSpace(ss[1])).WithExpr("<="),
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ query.AndFilterWhere(NewCond(schema.Column, formValue))
|
|
|
+ }
|
|
|
+ case "duration", "number", "integer", "decimal":
|
|
|
+ query.AndFilterWhere(NewCond(schema.Column, formValue))
|
|
|
+ default:
|
|
|
+ if schema.Type == "string" {
|
|
|
+ if schema.Attribute.Match == MatchExactly {
|
|
|
+ query.AndFilterWhere(NewCond(schema.Column, formValue))
|
|
|
+ } else {
|
|
|
+ query.AndFilterWhere(NewCond(schema.Column, formValue).WithExpr("LIKE"))
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ query.AndFilterWhere(NewCond(schema.Column, formValue))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //处理排序
|
|
|
+ sortPar := requestCtx.FormValue("sort")
|
|
|
+ if sortPar != "" {
|
|
|
+ sorts := strings.Split(sortPar, ",")
|
|
|
+ for _, s := range sorts {
|
|
|
+ if s[0] == '-' {
|
|
|
+ query.OrderBy(s[1:], "DESC")
|
|
|
+ } else {
|
|
|
+ if s[0] == '+' {
|
|
|
+ query.OrderBy(s[1:], "ASC")
|
|
|
+ } else {
|
|
|
+ query.OrderBy(s, "ASC")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if activeModel, ok = model.(ActiveModel); ok {
|
|
|
+ err = activeModel.AfterQuery(ctx, query)
|
|
|
+ }
|
|
|
+ if r.opts.Delegate != nil {
|
|
|
+ err = r.opts.Delegate.AfterQuery(ctx, query)
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) actionIndex(httpCtx *http.Context) (err error) {
|
|
|
+ var (
|
|
|
+ page int
|
|
|
+ pageSize int
|
|
|
+ pageIndex int
|
|
|
+ query *Query
|
|
|
+ namespace string
|
|
|
+ )
|
|
|
+ if !r.hasScenario(ScenarioList) {
|
|
|
+ return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage)
|
|
|
+ }
|
|
|
+ ctx := httpCtx.Request().Context()
|
|
|
+ if ctx == nil {
|
|
|
+ ctx = context.Background()
|
|
|
+ }
|
|
|
+ namespace = httpCtx.ParamValue(NamespaceVariable)
|
|
|
+ ctx = context.WithValue(ctx, NamespaceField, namespace)
|
|
|
+ ctx = context.WithValue(ctx, "request", httpCtx.Request())
|
|
|
+ page, _ = strconv.Atoi(httpCtx.FormValue("page"))
|
|
|
+ pageSize, _ = strconv.Atoi(httpCtx.FormValue("pagesize"))
|
|
|
+ if pageSize <= 0 {
|
|
|
+ pageSize = 15
|
|
|
+ }
|
|
|
+ pageIndex = page
|
|
|
+ if pageIndex > 0 {
|
|
|
+ pageIndex--
|
|
|
+ }
|
|
|
+ sliceValue := reflect.MakeSlice(reflect.SliceOf(r.reflectType), 0, 0)
|
|
|
+ models := reflect.New(sliceValue.Type())
|
|
|
+ models.Elem().Set(sliceValue)
|
|
|
+ query = NewQuery(r.opts.DB)
|
|
|
+ searchSchemas := r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioSearch)
|
|
|
+ indexSchemas := r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioList)
|
|
|
+ if err = r.prepareConditions(ctx, httpCtx, query, searchSchemas); err != nil {
|
|
|
+ return httpCtx.Error(HttpDatabaseQueryFailed, err.Error())
|
|
|
+ }
|
|
|
+ if r.opts.EnableNamespace {
|
|
|
+ query.AndFilterWhere(NewQueryCondition(NamespaceField, namespace))
|
|
|
+ }
|
|
|
+ query.Offset(pageIndex * pageSize).Limit(pageSize)
|
|
|
+ if err = query.All(models.Interface()); err != nil {
|
|
|
+ return httpCtx.Error(HttpDatabaseQueryFailed, err.Error())
|
|
|
+ }
|
|
|
+ resp := &ListResponse{
|
|
|
+ Page: page,
|
|
|
+ PageSize: pageSize,
|
|
|
+ TotalCount: query.Limit(0).Offset(0).Count(r.model),
|
|
|
+ }
|
|
|
+ if resp.TotalCount > 0 {
|
|
|
+ resp.Data = r.opts.Formatter.formatModels(ctx, models.Interface(), indexSchemas, r.statement)
|
|
|
+ } else {
|
|
|
+ resp.Data = make([]string, 0)
|
|
|
+ }
|
|
|
+ return httpCtx.Success(resp)
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) actionCreate(httpCtx *http.Context) (err error) {
|
|
|
+ var (
|
|
|
+ errTx error
|
|
|
+ namespace string
|
|
|
+ model interface{}
|
|
|
+ schemas []*Schema
|
|
|
+ refModel reflect.Value
|
|
|
+ diffAttrs []*DiffAttr
|
|
|
+ )
|
|
|
+ ctx := httpCtx.Request().Context()
|
|
|
+ if ctx == nil {
|
|
|
+ ctx = context.Background()
|
|
|
+ }
|
|
|
+ diffAttrs = make([]*DiffAttr, 0, 10)
|
|
|
+ if !r.hasScenario(ScenarioCreate) {
|
|
|
+ return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage)
|
|
|
+ }
|
|
|
+ namespace = httpCtx.ParamValue(NamespaceVariable)
|
|
|
+ refModel = reflect.New(r.reflectType)
|
|
|
+ model = refModel.Interface()
|
|
|
+ if err = httpCtx.Bind(model); err != nil {
|
|
|
+ return httpCtx.Error(HttpInvalidPayload, err.Error())
|
|
|
+ }
|
|
|
+ schemas = r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioCreate)
|
|
|
+
|
|
|
+ //global set field value
|
|
|
+ r.setFieldValue(refModel, NamespaceField, namespace)
|
|
|
+ r.setFieldValue(refModel, CreatedByField, httpCtx.ParamValue(UserVariable))
|
|
|
+ r.setFieldValue(refModel, CreatedDeptField, httpCtx.ParamValue(DepartmentVariable))
|
|
|
+ r.setFieldValue(refModel, UpdatedByField, httpCtx.ParamValue(UserVariable))
|
|
|
+ r.setFieldValue(refModel, UpdatedDeptField, httpCtx.ParamValue(DepartmentVariable))
|
|
|
+
|
|
|
+ if err = r.opts.DB.Transaction(func(tx *gorm.DB) error {
|
|
|
+ if r.opts.Delegate != nil {
|
|
|
+ if errTx = r.opts.Delegate.BeforeCreate(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ if errTx = r.opts.Delegate.BeforeSave(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if activeModel, ok := model.(ActiveModel); ok {
|
|
|
+ if errTx = activeModel.BeforeCreate(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ if errTx = activeModel.BeforeSave(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //创建数据
|
|
|
+ if errTx = tx.Create(model).Error; errTx != nil {
|
|
|
+ return errTx
|
|
|
+ }
|
|
|
+ //对比差异数据
|
|
|
+ for _, scm := range schemas {
|
|
|
+ diffAttrs = append(diffAttrs, &DiffAttr{
|
|
|
+ Column: scm.Column,
|
|
|
+ Label: scm.Label,
|
|
|
+ OldValue: nil,
|
|
|
+ NewValue: r.getFieldValue(refModel, scm.Column),
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if activeModel, ok := model.(ActiveModel); ok {
|
|
|
+ if errTx = activeModel.AfterCreate(ctx, model, diffAttrs); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, err.Error())
|
|
|
+ }
|
|
|
+ if errTx = activeModel.AfterSave(ctx, model, diffAttrs); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, err.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if r.opts.Delegate != nil {
|
|
|
+ if errTx = r.opts.Delegate.AfterCreate(ctx, model, diffAttrs); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, err.Error())
|
|
|
+ }
|
|
|
+ if errTx = r.opts.Delegate.AfterSave(ctx, model, diffAttrs); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, err.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return errTx
|
|
|
+ }); err == nil {
|
|
|
+ pkVal := r.getFieldValue(refModel, r.primaryKey)
|
|
|
+ return httpCtx.Success(&CreateResponse{
|
|
|
+ ID: pkVal,
|
|
|
+ Topic: r.model.TableName(),
|
|
|
+ State: "created",
|
|
|
+ })
|
|
|
+ }
|
|
|
+ //form validation
|
|
|
+ if validateError, ok := err.(*errpkg.StructError); ok {
|
|
|
+ httpCtx.Response().Header().Set("Content-Type", "application/json")
|
|
|
+ return json.NewEncoder(httpCtx.Response()).Encode(map[string]interface{}{
|
|
|
+ "errno": HttpValidateFailed,
|
|
|
+ "result": validateError,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return httpCtx.Error(HttpDatabaseCreateFailed, err.Error())
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) actionUpdate(httpCtx *http.Context) (err error) {
|
|
|
+ var (
|
|
|
+ errTx error
|
|
|
+ namespace string
|
|
|
+ model interface{}
|
|
|
+ schemas []*Schema
|
|
|
+ refModel reflect.Value
|
|
|
+ oldValues = make(map[string]interface{})
|
|
|
+ diffs = make(map[string]interface{})
|
|
|
+ diffAttrs = make([]*DiffAttr, 0)
|
|
|
+ )
|
|
|
+ ctx := httpCtx.Request().Context()
|
|
|
+ if ctx == nil {
|
|
|
+ ctx = context.Background()
|
|
|
+ }
|
|
|
+ if !r.hasScenario(ScenarioUpdate) {
|
|
|
+ return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage)
|
|
|
+ }
|
|
|
+ namespace = httpCtx.ParamValue(NamespaceVariable)
|
|
|
+ idStr := httpCtx.ParamValue("id")
|
|
|
+ refModel = reflect.New(r.reflectType)
|
|
|
+ model = refModel.Interface()
|
|
|
+ //默认设置更新用户
|
|
|
+ r.setFieldValue(refModel, UpdatedByField, httpCtx.ParamValue(UserVariable))
|
|
|
+ r.setFieldValue(refModel, UpdatedDeptField, httpCtx.ParamValue(DepartmentVariable))
|
|
|
+ conditions := map[string]interface{}{
|
|
|
+ r.primaryKey: idStr,
|
|
|
+ }
|
|
|
+ if r.opts.EnableNamespace {
|
|
|
+ conditions[NamespaceField] = namespace
|
|
|
+ }
|
|
|
+ if err = r.opts.DB.Where(conditions).First(model).Error; err != nil {
|
|
|
+ return httpCtx.Error(HttpDatabaseFindFailed, err.Error())
|
|
|
+ }
|
|
|
+ schemas = r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioUpdate)
|
|
|
+ for _, scm := range schemas {
|
|
|
+ oldValues[scm.Column] = r.getFieldValue(refModel, scm.Column)
|
|
|
+ }
|
|
|
+ if err = httpCtx.Bind(model); err != nil {
|
|
|
+ return httpCtx.Error(HttpInvalidPayload, err.Error())
|
|
|
+ }
|
|
|
+ if err = r.opts.DB.Transaction(func(tx *gorm.DB) error {
|
|
|
+ if r.opts.Delegate != nil {
|
|
|
+ if errTx = r.opts.Delegate.BeforeUpdate(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ if errTx = r.opts.Delegate.BeforeSave(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if activeModel, ok := model.(ActiveModel); ok {
|
|
|
+ if errTx = activeModel.BeforeUpdate(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ if errTx = activeModel.BeforeSave(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //对比差异数据
|
|
|
+ for _, scm := range schemas {
|
|
|
+ v := r.getFieldValue(refModel, scm.Column)
|
|
|
+ if oldValues[scm.Column] != v {
|
|
|
+ diffs[scm.Column] = v
|
|
|
+ diffAttrs = append(diffAttrs, &DiffAttr{
|
|
|
+ Column: scm.Column,
|
|
|
+ Label: scm.Label,
|
|
|
+ OldValue: oldValues[scm.Column],
|
|
|
+ NewValue: v,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //进行局部数据更新
|
|
|
+ if len(diffs) > 0 {
|
|
|
+ if errTx = tx.Model(model).Updates(diffs).Error; errTx != nil {
|
|
|
+ return errTx
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if activeModel, ok := model.(ActiveModel); ok {
|
|
|
+ if errTx = activeModel.AfterUpdate(ctx, model, diffAttrs); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ if errTx = activeModel.AfterSave(ctx, model, diffAttrs); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if r.opts.Delegate != nil {
|
|
|
+ if errTx = r.opts.Delegate.AfterUpdate(ctx, model, diffAttrs); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ if errTx = r.opts.Delegate.AfterSave(ctx, model, diffAttrs); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return errTx
|
|
|
+ }); err == nil {
|
|
|
+ pkVal := r.getFieldValue(refModel, r.primaryKey)
|
|
|
+ return httpCtx.Success(&UpdateResponse{
|
|
|
+ ID: pkVal,
|
|
|
+ Topic: r.model.TableName(),
|
|
|
+ State: "updated",
|
|
|
+ })
|
|
|
+ }
|
|
|
+ //form validation
|
|
|
+ if validateError, ok := err.(*errpkg.StructError); ok {
|
|
|
+ httpCtx.Response().Header().Set("Content-Type", "application/json")
|
|
|
+ return json.NewEncoder(httpCtx.Response()).Encode(map[string]interface{}{
|
|
|
+ "errno": HttpValidateFailed,
|
|
|
+ "result": validateError,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return httpCtx.Error(HttpDatabaseUpdateFailed, err.Error())
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) actionDelete(httpCtx *http.Context) (err error) {
|
|
|
+ var (
|
|
|
+ errTx error
|
|
|
+ model interface{}
|
|
|
+ namespace string
|
|
|
+ )
|
|
|
+ if !r.hasScenario(ScenarioDelete) {
|
|
|
+ return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage)
|
|
|
+ }
|
|
|
+ ctx := httpCtx.Request().Context()
|
|
|
+ if ctx == nil {
|
|
|
+ ctx = context.Background()
|
|
|
+ }
|
|
|
+ idStr := httpCtx.ParamValue("id")
|
|
|
+ namespace = httpCtx.ParamValue(NamespaceVariable)
|
|
|
+ model = reflect.New(r.reflectType).Interface()
|
|
|
+ conditions := map[string]interface{}{
|
|
|
+ r.primaryKey: idStr,
|
|
|
+ }
|
|
|
+ if r.opts.EnableNamespace {
|
|
|
+ conditions[NamespaceField] = namespace
|
|
|
+ }
|
|
|
+ if err = r.opts.DB.Where(conditions).First(model).Error; err != nil {
|
|
|
+ return httpCtx.Error(HttpDatabaseFindFailed, err.Error())
|
|
|
+ }
|
|
|
+ if err = r.opts.DB.Transaction(func(tx *gorm.DB) error {
|
|
|
+ if r.opts.Delegate != nil {
|
|
|
+ if errTx = r.opts.Delegate.BeforeDelete(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if activeModel, ok := model.(ActiveModel); ok {
|
|
|
+ if errTx = activeModel.BeforeDelete(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if errTx = tx.Delete(model).Error; errTx != nil {
|
|
|
+ return errTx
|
|
|
+ }
|
|
|
+ if activeModel, ok := model.(ActiveModel); ok {
|
|
|
+ if errTx = activeModel.AfterDelete(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if r.opts.Delegate != nil {
|
|
|
+ if errTx = r.opts.Delegate.AfterDelete(ctx, model); errTx != nil {
|
|
|
+ return httpCtx.Error(HttpRequestCallbackFailed, errTx.Error())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return errTx
|
|
|
+ }); err == nil {
|
|
|
+ return httpCtx.Success(&DeleteResponse{
|
|
|
+ ID: r.getFieldValue(reflect.ValueOf(model), r.primaryKey),
|
|
|
+ Topic: r.model.TableName(),
|
|
|
+ State: "updated",
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ return httpCtx.Error(HttpDatabaseDeleteFailed, err.Error())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) actionExport(httpCtx *http.Context) (err error) {
|
|
|
+ var (
|
|
|
+ query *Query
|
|
|
+ namespace string
|
|
|
+ )
|
|
|
+ if !r.hasScenario(ScenarioExport) {
|
|
|
+ return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage)
|
|
|
+ }
|
|
|
+ ctx := httpCtx.Request().Context()
|
|
|
+ if ctx == nil {
|
|
|
+ ctx = context.Background()
|
|
|
+ }
|
|
|
+ ctx = context.WithValue(ctx, NamespaceVariable, namespace)
|
|
|
+ namespace = httpCtx.ParamValue(NamespaceVariable)
|
|
|
+ sliceValue := reflect.MakeSlice(reflect.SliceOf(r.reflectType), 0, 0)
|
|
|
+ models := reflect.New(sliceValue.Type())
|
|
|
+ models.Elem().Set(sliceValue)
|
|
|
+ query = NewQuery(r.opts.DB)
|
|
|
+ searchSchemas := r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioSearch)
|
|
|
+ exportSchemas := r.opts.LookupFunc(ctx, namespace, r.model.ModuleName(), r.model.TableName(), ScenarioList)
|
|
|
+ if err = r.prepareConditions(ctx, httpCtx, query, searchSchemas); err != nil {
|
|
|
+ return httpCtx.Error(HttpDatabaseQueryFailed, err.Error())
|
|
|
+ }
|
|
|
+ if r.opts.EnableNamespace {
|
|
|
+ query.AndFilterWhere(NewQueryCondition(NamespaceVariable, namespace))
|
|
|
+ }
|
|
|
+ if err = query.All(models.Interface()); err != nil {
|
|
|
+ return httpCtx.Error(HttpDatabaseExportFailed, err.Error())
|
|
|
+ }
|
|
|
+ httpCtx.Response().Header().Set("Content-Type", "text/csv")
|
|
|
+ httpCtx.Response().Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
|
|
|
+ httpCtx.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment;filename=%s.csv", r.singularName))
|
|
|
+ value := r.opts.Formatter.formatModels(ctx, models.Interface(), exportSchemas, r.statement)
|
|
|
+ writer := csv.NewWriter(httpCtx.Response())
|
|
|
+ ss := make([]string, len(exportSchemas))
|
|
|
+ for i, field := range exportSchemas {
|
|
|
+ ss[i] = field.Label
|
|
|
+ }
|
|
|
+ _ = writer.Write(ss)
|
|
|
+ if values, ok := value.([]interface{}); ok {
|
|
|
+ for _, val := range values {
|
|
|
+ row, ok2 := val.(map[string]interface{})
|
|
|
+ if !ok2 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ for i, field := range exportSchemas {
|
|
|
+ if v, ok := row[field.Column]; ok {
|
|
|
+ ss[i] = fmt.Sprint(v)
|
|
|
+ } else {
|
|
|
+ ss[i] = ""
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _ = writer.Write(ss)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ writer.Flush()
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func (r *Restful) actionView(httpCtx *http.Context) (err error) {
|
|
|
+ var (
|
|
|
+ model interface{}
|
|
|
+ namespace string
|
|
|
+ )
|
|
|
+ if !r.hasScenario(ScenarioView) {
|
|
|
+ return httpCtx.Error(HttpAccessDenied, ErrorAccessDeniedMessage)
|
|
|
+ }
|
|
|
+ namespace = httpCtx.ParamValue(NamespaceVariable)
|
|
|
+ scenario := httpCtx.FormValue("scenario")
|
|
|
+ idStr := httpCtx.ParamValue("id")
|
|
|
+ model = reflect.New(r.reflectType).Interface()
|
|
|
+ conditions := map[string]interface{}{
|
|
|
+ r.primaryKey: idStr,
|
|
|
+ }
|
|
|
+ if r.opts.EnableNamespace {
|
|
|
+ conditions[NamespaceField] = namespace
|
|
|
+ }
|
|
|
+ if err = r.opts.DB.Where(conditions).First(model).Error; err != nil {
|
|
|
+ return httpCtx.Error(HttpDatabaseFindFailed, err.Error())
|
|
|
+ }
|
|
|
+ if httpCtx.FormValue("format") != "" {
|
|
|
+ //获取指定场景下面的字段进行渲染显示
|
|
|
+ var schemas []*Schema
|
|
|
+ if scenario == "" {
|
|
|
+ schemas = r.opts.LookupFunc(httpCtx.Request().Context(), namespace, r.model.ModuleName(), r.model.TableName(), ScenarioView)
|
|
|
+ } else {
|
|
|
+ schemas = r.opts.LookupFunc(httpCtx.Request().Context(), namespace, r.model.ModuleName(), r.model.TableName(), scenario)
|
|
|
+ }
|
|
|
+ requestCtx := httpCtx.Request().Context()
|
|
|
+ if requestCtx == nil {
|
|
|
+ requestCtx = context.Background()
|
|
|
+ }
|
|
|
+ requestCtx = context.WithValue(requestCtx, NamespaceVariable, namespace)
|
|
|
+ return httpCtx.Success(r.opts.Formatter.formatModel(requestCtx, model, schemas, r.statement))
|
|
|
+ }
|
|
|
+ return httpCtx.Success(model)
|
|
|
+}
|
|
|
+
|
|
|
+func newRestful(model Model, opts *Options) *Restful {
|
|
|
+ var (
|
|
|
+ err error
|
|
|
+ tableName string
|
|
|
+ )
|
|
|
+ r := &Restful{
|
|
|
+ opts: opts,
|
|
|
+ model: model,
|
|
|
+ reflectValue: reflect.Indirect(reflect.ValueOf(model)),
|
|
|
+ }
|
|
|
+ r.reflectType = r.reflectValue.Type()
|
|
|
+ tableName = model.TableName()
|
|
|
+ r.singularName = inflector.Singularize(tableName)
|
|
|
+ r.pluralizeName = inflector.Pluralize(tableName)
|
|
|
+
|
|
|
+ r.statement = &gorm.Statement{
|
|
|
+ DB: r.opts.DB,
|
|
|
+ ConnPool: r.opts.DB.ConnPool,
|
|
|
+ Clauses: map[string]clause.Clause{},
|
|
|
+ }
|
|
|
+ if err = r.statement.Parse(model); err == nil {
|
|
|
+ if r.statement.Schema != nil {
|
|
|
+ if r.statement.Schema.PrimaryFieldDBNames != nil && len(r.statement.Schema.PrimaryFieldDBNames) > 0 {
|
|
|
+ r.primaryKey = r.statement.Schema.PrimaryFieldDBNames[0]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return r
|
|
|
+}
|