package table import ( "errors" "fmt" "io" "os" "path/filepath" "strings" "github.com/gin-gonic/gin" "github.com/xuri/excelize/v2" "github.com/yaoapp/gou" "github.com/yaoapp/gou/helper" "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/log" "github.com/yaoapp/kun/maps" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/share" "github.com/yaoapp/yao/xfs" ) // Tables 已载入模型 var Tables = map[string]*Table{} // apiNames mapping var apiNames = map[string]string{ "/:name/search": "search", "/:name/find/:id": "find", "/:name/save": "save", "/:name/delete/:id": "delete", "/:name/insert": "insert", "/:name/delete/in": "delete-in", "/:name/delete/where": "delete-where", "/:name/update/in": "update-in", "/:name/update/where": "update-where", "/:name/quicksave": "quicksave", "/:name/select": "select", } // Guard Table guard func Guard(c *gin.Context) { if !strings.HasPrefix(c.FullPath(), "/api/xiang/table") { c.Next() return } log.Trace("Table Guard FullPath: %s", c.FullPath()) routes := strings.Split(c.FullPath(), "/") if len(routes) < 4 { log.Trace("Table Guard Routes: %v", routes) c.Next() return } path := "/" + strings.Join(routes[4:], "/") apiName, has := apiNames[path] if !has { log.Trace("Table Guard API Name: %v", path) c.Next() return } name, has := c.Params.Get("name") if !has { log.Trace("Table Guard Name: %v", c.Params) c.Next() return } table, has := Tables[name] if !has { log.Trace("Table Guard Table: %s", name) c.Next() return } api, has := table.APIs[apiName] if !has { log.Trace("Table Guard API: %s", apiName) c.Next() return } log.Trace("Table Guard: %s %s %s %s", name, api.Guard, path, apiName) if api.Guard == "-" { c.Next() return } if api.Guard == "" { api.Guard = "bearer-jwt" } guards := strings.Split(api.Guard, ",") for _, guard := range guards { guard = strings.TrimSpace(guard) log.Trace("Table Guard: %s %s", name, guard) if guard != "" { if middleware, has := gou.HTTPGuards[guard]; has { middleware(c) continue } gou.ProcessGuard(guard)(c) } } } // Load 加载数据表格 func Load(cfg config.Config) error { if share.BUILDIN { return LoadBuildIn("tables", "") } return LoadFrom(filepath.Join(cfg.Root, "tables"), "") } // LoadFrom 从特定目录加载 func LoadFrom(dir string, prefix string) error { if share.DirNotExists(dir) { return fmt.Errorf("%s does not exists", dir) } err := share.Walk(dir, ".json", func(root, filename string) { name := share.SpecName(root, filename) content := share.ReadFile(filename) _, err := LoadTable(string(content), name) if err != nil { log.With(log.F{"root": root, "file": filename}).Error(err.Error()) } }) return err } // LoadBuildIn 从制品中读取 func LoadBuildIn(dir string, prefix string) error { return nil } // LoadTable 载入数据表格 func LoadTable(source string, name string) (*Table, error) { var input io.Reader = nil if strings.HasPrefix(source, "file://") || strings.HasPrefix(source, "fs://") { filename := strings.TrimPrefix(source, "file://") filename = strings.TrimPrefix(filename, "fs://") file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() input = file } else { input = strings.NewReader(source) } table := Table{ Source: source, Table: name, } err := helper.UnmarshalFile(input, &table) if err != nil { // exception.Err(err, 400).Ctx(maps.Map{"name": name}).Throw() return &table, err } table.loadColumns() table.loadFilters() table.loadAPIs() Tables[name] = &table return Tables[name], nil } // Select 读取已加载表格配置 func Select(name string) *Table { tab, has := Tables[name] if !has { exception.New( fmt.Sprintf("Table:%s; 尚未加载", name), 400, ).Throw() } return tab } // Reload 更新数据表格配置 func (table *Table) Reload() (*Table, error) { new, err := LoadTable(table.Source, table.Name) if err != nil { return nil, err } *table = *new return table, nil } // loadAPIs 加载数据管理 API func (table *Table) loadAPIs() { if table.Bind.Model == "" { return } defaults := getDefaultAPIs(table.Bind) defaults["setting"] = apiDefaultSetting() for name := range table.APIs { if _, has := defaults[name]; !has { delete(table.APIs, name) continue } api := defaults[name] api.Name = name if table.APIs[name].Process != "" { api.Process = table.APIs[name].Process } api.Guard = "bearer-jwt" if table.APIs[name].Guard != "" { api.Guard = table.APIs[name].Guard } if table.APIs[name].Default != nil { // fmt.Printf("\n%s.APIs[%s].Default: entry\n", table.Table, name) if len(table.APIs[name].Default) == len(api.Default) { for i := range table.APIs[name].Default { // fmt.Printf("%s.APIs[%s].Default[%d]:%v\n", table.Table, name, i, table.APIs[name].Default[i]) if table.APIs[name].Default[i] != nil { api.Default[i] = table.APIs[name].Default[i] } } } } defaults[name] = api } table.APIs = defaults } // getDefaultAPIs 读取数据模型绑定的APIs func getDefaultAPIs(bind Bind) map[string]share.API { name := bind.Model model := gou.Select(name) apis := map[string]share.API{ "search": apiSearchDefault(model, bind.Withs), "find": apiFindDefault(model, bind.Withs), "save": apiDefault(model, "save", "Save"), "delete": apiDefault(model, "delete", "Delete"), "insert": apiDefault(model, "insert", "Insert"), "delete-in": apiDefault(model, "delete-in", "DeleteWhere"), "delete-where": apiDefaultWhere(model, bind.Withs, "delete-where", "DeleteWhere"), "update-in": apiDefault(model, "update-in", "UpdateWhere"), "update-where": apiDefaultWhere(model, bind.Withs, "update-where", "UpdateWhere"), "quicksave": apiDefault(model, "quicksave", "EachSaveAfterDelete"), // 批量保存 "select": apiDefault(model, "select", "SelectOption"), // 选择 } return apis } // Before 运行 Before hook func (table *Table) Before(process string, processArgs []interface{}, sid string) []interface{} { if process == "" { return processArgs } args := []interface{}{} res := []interface{}{} if len(processArgs) > 0 { args = processArgs[1:] res = append(res, processArgs[0]) } response, err := gou.NewProcess(process, args...).WithSID(sid).Exec() if err != nil { log.With(log.F{"process": process, "args": args}).Warn("Hook执行失败: %s", err.Error()) return processArgs } if fixedArgs, ok := response.([]interface{}); ok { res = append(res, fixedArgs...) return res } log.With(log.F{"process": process, "args": args}).Warn("Hook执行失败: 无效的处理器") return processArgs } // After 运行 After hook func (table *Table) After(process string, data interface{}, args []interface{}, sid string) interface{} { if process == "" { return data } args = append([]interface{}{data}, args...) response, err := gou.NewProcess(process, args...).WithSID(sid).Exec() if err != nil { log.With(log.F{"process": process, "args": args}).Warn("Hook执行失败: %s", err.Error()) return data } return response } // loadFilters 加载查询过滤器 func (table *Table) loadFilters() { if table.Bind.Model == "" { return } defaults := share.GetDefaultFilters(table.Bind.Model) for name, filter := range table.Filters { defaults[name] = filter } table.Filters = defaults } // loadColumns 加载字段呈现方式 func (table *Table) loadColumns() { if table.Bind.Model == "" { return } defaults := share.GetDefaultColumns(table.Bind.Model) for name, column := range table.Columns { defaults[name] = column } table.Columns = defaults } // Export Export query result to Excel func (table *Table) Export(filename string, data interface{}, page int, chunkSize int) error { rows := []maps.MapStr{} if values, ok := data.([]maps.MapStrAny); ok { for _, row := range values { rows = append(rows, row.Dot()) } } else if values, ok := data.([]map[string]interface{}); ok { for _, row := range values { rows = append(rows, maps.Of(row).Dot()) } } else if values, ok := data.([]interface{}); ok { for _, row := range values { rows = append(rows, any.Of(row).MapStr().Dot()) } } columns, err := table.exportSetting() if err != nil { return err } if len(columns) == 0 { return fmt.Errorf("the table does not support export") } filename = filepath.Join(xfs.Stor.Root, filename) if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) { f := excelize.NewFile() index := f.GetActiveSheetIndex() name := f.GetSheetName(index) f.SetSheetName(name, table.Name) for i, column := range columns { axis, err := excelize.CoordinatesToCellName(i+1, 1) if err != nil { return err } f.SetCellValue(table.Name, axis, column["name"]) } if err := f.SaveAs(filename); err != nil { fmt.Println(err) return err } } f, err := excelize.OpenFile(filename) if err != nil { return err } defer f.Close() offset := (page-1)*chunkSize + 2 for line, row := range rows { for i, column := range columns { v := row.Get(column["field"]) if v != nil { axis, err := excelize.CoordinatesToCellName(i+1, line+offset) if err != nil { return err } f.SetCellValue(table.Name, axis, v) } } // fmt.Println("--", line, page, offset, "--") } return f.Save() } func (table *Table) exportSetting() ([]map[string]string, error) { // Validate params if table.List.Layout == nil { return nil, fmt.Errorf("the table layout does not found") } columns, has := table.List.Layout["columns"] if !has { return nil, fmt.Errorf("the columns table layout does not found") } fields, ok := columns.([]interface{}) if !ok { return nil, fmt.Errorf("Table Layout columns format error") } setting := []map[string]string{} for _, field := range fields { f, ok := field.(map[string]interface{}) if !ok { continue } n, has := f["name"] if !has { continue } name, ok := n.(string) if !ok { continue } column, has := table.Columns[name] if !has { continue } field := column.Export if field == "" { if value, has := column.View.Props["value"]; has { if valueStr, ok := value.(string); ok { field = strings.TrimPrefix(valueStr, ":") } } } if field != "" && name != "" { setting = append(setting, map[string]string{"name": name, "field": field}) } } return setting, nil }