sys_export_template.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. package system
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "mime/multipart"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "github.com/flipped-aurora/gin-vue-admin/server/global"
  13. "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
  14. "github.com/flipped-aurora/gin-vue-admin/server/model/system"
  15. systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
  16. "github.com/flipped-aurora/gin-vue-admin/server/utils"
  17. "github.com/xuri/excelize/v2"
  18. "gorm.io/gorm"
  19. )
  20. type SysExportTemplateService struct {
  21. }
  22. var SysExportTemplateServiceApp = new(SysExportTemplateService)
  23. // CreateSysExportTemplate 创建导出模板记录
  24. // Author [piexlmax](https://github.com/piexlmax)
  25. func (sysExportTemplateService *SysExportTemplateService) CreateSysExportTemplate(sysExportTemplate *system.SysExportTemplate) (err error) {
  26. err = global.GVA_DB.Create(sysExportTemplate).Error
  27. return err
  28. }
  29. // DeleteSysExportTemplate 删除导出模板记录
  30. // Author [piexlmax](https://github.com/piexlmax)
  31. func (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) {
  32. err = global.GVA_DB.Delete(&sysExportTemplate).Error
  33. return err
  34. }
  35. // DeleteSysExportTemplateByIds 批量删除导出模板记录
  36. // Author [piexlmax](https://github.com/piexlmax)
  37. func (sysExportTemplateService *SysExportTemplateService) DeleteSysExportTemplateByIds(ids request.IdsReq) (err error) {
  38. err = global.GVA_DB.Delete(&[]system.SysExportTemplate{}, "id in ?", ids.Ids).Error
  39. return err
  40. }
  41. // UpdateSysExportTemplate 更新导出模板记录
  42. // Author [piexlmax](https://github.com/piexlmax)
  43. func (sysExportTemplateService *SysExportTemplateService) UpdateSysExportTemplate(sysExportTemplate system.SysExportTemplate) (err error) {
  44. return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
  45. conditions := sysExportTemplate.Conditions
  46. e := tx.Delete(&[]system.Condition{}, "template_id = ?", sysExportTemplate.TemplateID).Error
  47. if e != nil {
  48. return e
  49. }
  50. sysExportTemplate.Conditions = nil
  51. joins := sysExportTemplate.JoinTemplate
  52. e = tx.Delete(&[]system.JoinTemplate{}, "template_id = ?", sysExportTemplate.TemplateID).Error
  53. if e != nil {
  54. return e
  55. }
  56. sysExportTemplate.JoinTemplate = nil
  57. e = tx.Updates(&sysExportTemplate).Error
  58. if e != nil {
  59. return e
  60. }
  61. if len(conditions) > 0 {
  62. for i := range conditions {
  63. conditions[i].ID = 0
  64. }
  65. e = tx.Create(&conditions).Error
  66. }
  67. if len(joins) > 0 {
  68. for i := range joins {
  69. joins[i].ID = 0
  70. }
  71. e = tx.Create(&joins).Error
  72. }
  73. return e
  74. })
  75. }
  76. // GetSysExportTemplate 根据id获取导出模板记录
  77. // Author [piexlmax](https://github.com/piexlmax)
  78. func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplate(id uint) (sysExportTemplate system.SysExportTemplate, err error) {
  79. err = global.GVA_DB.Where("id = ?", id).Preload("JoinTemplate").Preload("Conditions").First(&sysExportTemplate).Error
  80. return
  81. }
  82. // GetSysExportTemplateInfoList 分页获取导出模板记录
  83. // Author [piexlmax](https://github.com/piexlmax)
  84. func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplateInfoList(info systemReq.SysExportTemplateSearch) (list []system.SysExportTemplate, total int64, err error) {
  85. limit := info.PageSize
  86. offset := info.PageSize * (info.Page - 1)
  87. // 创建db
  88. db := global.GVA_DB.Model(&system.SysExportTemplate{})
  89. var sysExportTemplates []system.SysExportTemplate
  90. // 如果有条件搜索 下方会自动创建搜索语句
  91. if info.StartCreatedAt != nil && info.EndCreatedAt != nil {
  92. db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt)
  93. }
  94. if info.Name != "" {
  95. db = db.Where("name LIKE ?", "%"+info.Name+"%")
  96. }
  97. if info.TableName != "" {
  98. db = db.Where("table_name = ?", info.TableName)
  99. }
  100. if info.TemplateID != "" {
  101. db = db.Where("template_id = ?", info.TemplateID)
  102. }
  103. err = db.Count(&total).Error
  104. if err != nil {
  105. return
  106. }
  107. if limit != 0 {
  108. db = db.Limit(limit).Offset(offset)
  109. }
  110. err = db.Find(&sysExportTemplates).Error
  111. return sysExportTemplates, total, err
  112. }
  113. // ExportExcel 导出Excel
  114. // Author [piexlmax](https://github.com/piexlmax)
  115. func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID string, values url.Values) (file *bytes.Buffer, name string, err error) {
  116. var params = values.Get("params")
  117. paramsValues, err := url.ParseQuery(params)
  118. if err != nil {
  119. return nil, "", fmt.Errorf("解析 params 参数失败: %v", err)
  120. }
  121. var template system.SysExportTemplate
  122. err = global.GVA_DB.Preload("Conditions").Preload("JoinTemplate").First(&template, "template_id = ?", templateID).Error
  123. if err != nil {
  124. return nil, "", err
  125. }
  126. f := excelize.NewFile()
  127. defer func() {
  128. if err := f.Close(); err != nil {
  129. fmt.Println(err)
  130. }
  131. }()
  132. // Create a new sheet.
  133. index, err := f.NewSheet("Sheet1")
  134. if err != nil {
  135. fmt.Println(err)
  136. return
  137. }
  138. var templateInfoMap = make(map[string]string)
  139. columns, err := utils.GetJSONKeys(template.TemplateInfo)
  140. if err != nil {
  141. return nil, "", err
  142. }
  143. err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
  144. if err != nil {
  145. return nil, "", err
  146. }
  147. var tableTitle []string
  148. var selectKeyFmt []string
  149. for _, key := range columns {
  150. selectKeyFmt = append(selectKeyFmt, key)
  151. tableTitle = append(tableTitle, templateInfoMap[key])
  152. }
  153. selects := strings.Join(selectKeyFmt, ", ")
  154. var tableMap []map[string]interface{}
  155. db := global.GVA_DB
  156. if template.DBName != "" {
  157. db = global.MustGetGlobalDBByDBName(template.DBName)
  158. }
  159. if len(template.JoinTemplate) > 0 {
  160. for _, join := range template.JoinTemplate {
  161. db = db.Joins(join.JOINS + " " + join.Table + " ON " + join.ON)
  162. }
  163. }
  164. db = db.Select(selects).Table(template.TableName)
  165. filterDeleted := false
  166. filterParam := paramsValues.Get("filterDeleted")
  167. if filterParam == "true" {
  168. filterDeleted = true
  169. }
  170. if filterDeleted {
  171. // 自动过滤主表的软删除
  172. db = db.Where(fmt.Sprintf("%s.deleted_at IS NULL", template.TableName))
  173. // 过滤关联表的软删除(如果有)
  174. if len(template.JoinTemplate) > 0 {
  175. for _, join := range template.JoinTemplate {
  176. // 检查关联表是否有deleted_at字段
  177. hasDeletedAt := sysExportTemplateService.hasDeletedAtColumn(join.Table)
  178. if hasDeletedAt {
  179. db = db.Where(fmt.Sprintf("%s.deleted_at IS NULL", join.Table))
  180. }
  181. }
  182. }
  183. }
  184. if len(template.Conditions) > 0 {
  185. for _, condition := range template.Conditions {
  186. sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator)
  187. value := paramsValues.Get(condition.From)
  188. if condition.Operator == "IN" || condition.Operator == "NOT IN" {
  189. sql = fmt.Sprintf("%s %s (?)", condition.Column, condition.Operator)
  190. }
  191. if value != "" {
  192. if condition.Operator == "LIKE" {
  193. value = "%" + value + "%"
  194. }
  195. db = db.Where(sql, value)
  196. }
  197. }
  198. }
  199. // 通过参数传入limit
  200. limit := paramsValues.Get("limit")
  201. if limit != "" {
  202. l, e := strconv.Atoi(limit)
  203. if e == nil {
  204. db = db.Limit(l)
  205. }
  206. }
  207. // 模板的默认limit
  208. if limit == "" && template.Limit != nil && *template.Limit != 0 {
  209. db = db.Limit(*template.Limit)
  210. }
  211. // 通过参数传入offset
  212. offset := paramsValues.Get("offset")
  213. if offset != "" {
  214. o, e := strconv.Atoi(offset)
  215. if e == nil {
  216. db = db.Offset(o)
  217. }
  218. }
  219. // 获取当前表的所有字段
  220. table := template.TableName
  221. orderColumns, err := db.Migrator().ColumnTypes(table)
  222. if err != nil {
  223. return nil, "", err
  224. }
  225. // 创建一个 map 来存储字段名
  226. fields := make(map[string]bool)
  227. for _, column := range orderColumns {
  228. fields[column.Name()] = true
  229. }
  230. // 通过参数传入order
  231. order := paramsValues.Get("order")
  232. if order == "" && template.Order != "" {
  233. // 如果没有order入参,这里会使用模板的默认排序
  234. order = template.Order
  235. }
  236. if order != "" {
  237. checkOrderArr := strings.Split(order, " ")
  238. orderStr := ""
  239. // 检查请求的排序字段是否在字段列表中
  240. if _, ok := fields[checkOrderArr[0]]; !ok {
  241. return nil, "", fmt.Errorf("order by %s is not in the fields", order)
  242. }
  243. orderStr = checkOrderArr[0]
  244. if len(checkOrderArr) > 1 {
  245. if checkOrderArr[1] != "asc" && checkOrderArr[1] != "desc" {
  246. return nil, "", fmt.Errorf("order by %s is not secure", order)
  247. }
  248. orderStr = orderStr + " " + checkOrderArr[1]
  249. }
  250. db = db.Order(orderStr)
  251. }
  252. err = db.Debug().Find(&tableMap).Error
  253. if err != nil {
  254. return nil, "", err
  255. }
  256. var rows [][]string
  257. rows = append(rows, tableTitle)
  258. for _, exTable := range tableMap {
  259. var row []string
  260. for _, column := range columns {
  261. column = strings.ReplaceAll(column, "\"", "")
  262. column = strings.ReplaceAll(column, "`", "")
  263. if len(template.JoinTemplate) > 0 {
  264. columnAs := strings.Split(column, " as ")
  265. if len(columnAs) > 1 {
  266. column = strings.TrimSpace(strings.Split(column, " as ")[1])
  267. } else {
  268. columnArr := strings.Split(column, ".")
  269. if len(columnArr) > 1 {
  270. column = strings.Split(column, ".")[1]
  271. }
  272. }
  273. }
  274. // 需要对时间类型特殊处理
  275. if t, ok := exTable[column].(time.Time); ok {
  276. row = append(row, t.Format("2006-01-02 15:04:05"))
  277. } else {
  278. row = append(row, fmt.Sprintf("%v", exTable[column]))
  279. }
  280. }
  281. rows = append(rows, row)
  282. }
  283. for i, row := range rows {
  284. for j, colCell := range row {
  285. cell := fmt.Sprintf("%s%d", getColumnName(j+1), i+1)
  286. var sErr error
  287. if v, err := strconv.ParseFloat(colCell, 64); err == nil {
  288. sErr = f.SetCellValue("Sheet1", cell, v)
  289. } else if v, err := strconv.ParseInt(colCell, 10, 64); err == nil {
  290. sErr = f.SetCellValue("Sheet1", cell, v)
  291. } else {
  292. sErr = f.SetCellValue("Sheet1", cell, colCell)
  293. }
  294. if sErr != nil {
  295. return nil, "", sErr
  296. }
  297. }
  298. }
  299. f.SetActiveSheet(index)
  300. file, err = f.WriteToBuffer()
  301. if err != nil {
  302. return nil, "", err
  303. }
  304. return file, template.Name, nil
  305. }
  306. // ExportTemplate 导出Excel模板
  307. // Author [piexlmax](https://github.com/piexlmax)
  308. func (sysExportTemplateService *SysExportTemplateService) ExportTemplate(templateID string) (file *bytes.Buffer, name string, err error) {
  309. var template system.SysExportTemplate
  310. err = global.GVA_DB.First(&template, "template_id = ?", templateID).Error
  311. if err != nil {
  312. return nil, "", err
  313. }
  314. f := excelize.NewFile()
  315. defer func() {
  316. if err := f.Close(); err != nil {
  317. fmt.Println(err)
  318. }
  319. }()
  320. // Create a new sheet.
  321. index, err := f.NewSheet("Sheet1")
  322. if err != nil {
  323. fmt.Println(err)
  324. return
  325. }
  326. var templateInfoMap = make(map[string]string)
  327. columns, err := utils.GetJSONKeys(template.TemplateInfo)
  328. err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
  329. if err != nil {
  330. return nil, "", err
  331. }
  332. var tableTitle []string
  333. for _, key := range columns {
  334. tableTitle = append(tableTitle, templateInfoMap[key])
  335. }
  336. for i := range tableTitle {
  337. fErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(i+1), 1), tableTitle[i])
  338. if fErr != nil {
  339. return nil, "", fErr
  340. }
  341. }
  342. f.SetActiveSheet(index)
  343. file, err = f.WriteToBuffer()
  344. if err != nil {
  345. return nil, "", err
  346. }
  347. return file, template.Name, nil
  348. }
  349. // 辅助函数:检查表是否有deleted_at列
  350. func (s *SysExportTemplateService) hasDeletedAtColumn(tableName string) bool {
  351. var count int64
  352. global.GVA_DB.Raw("SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = 'deleted_at'", tableName).Count(&count)
  353. return count > 0
  354. }
  355. // ImportExcel 导入Excel
  356. // Author [piexlmax](https://github.com/piexlmax)
  357. func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID string, file *multipart.FileHeader) (err error) {
  358. var template system.SysExportTemplate
  359. err = global.GVA_DB.First(&template, "template_id = ?", templateID).Error
  360. if err != nil {
  361. return err
  362. }
  363. src, err := file.Open()
  364. if err != nil {
  365. return err
  366. }
  367. defer src.Close()
  368. f, err := excelize.OpenReader(src)
  369. if err != nil {
  370. return err
  371. }
  372. rows, err := f.GetRows("Sheet1")
  373. if err != nil {
  374. return err
  375. }
  376. if len(rows) < 2 {
  377. return errors.New("Excel data is not enough.\nIt should contain title row and data")
  378. }
  379. var templateInfoMap = make(map[string]string)
  380. err = json.Unmarshal([]byte(template.TemplateInfo), &templateInfoMap)
  381. if err != nil {
  382. return err
  383. }
  384. var titleKeyMap = make(map[string]string)
  385. for key, title := range templateInfoMap {
  386. titleKeyMap[title] = key
  387. }
  388. db := global.GVA_DB
  389. if template.DBName != "" {
  390. db = global.MustGetGlobalDBByDBName(template.DBName)
  391. }
  392. return db.Transaction(func(tx *gorm.DB) error {
  393. excelTitle := rows[0]
  394. for i, str := range excelTitle {
  395. excelTitle[i] = strings.TrimSpace(str)
  396. }
  397. values := rows[1:]
  398. items := make([]map[string]interface{}, 0, len(values))
  399. for _, row := range values {
  400. var item = make(map[string]interface{})
  401. for ii, value := range row {
  402. if _, ok := titleKeyMap[excelTitle[ii]]; !ok {
  403. continue // excel中多余的标题,在模板信息中没有对应的字段,因此key为空,必须跳过
  404. }
  405. key := titleKeyMap[excelTitle[ii]]
  406. item[key] = value
  407. }
  408. needCreated := tx.Migrator().HasColumn(template.TableName, "created_at")
  409. needUpdated := tx.Migrator().HasColumn(template.TableName, "updated_at")
  410. if item["created_at"] == nil && needCreated {
  411. item["created_at"] = time.Now()
  412. }
  413. if item["updated_at"] == nil && needUpdated {
  414. item["updated_at"] = time.Now()
  415. }
  416. items = append(items, item)
  417. }
  418. cErr := tx.Table(template.TableName).CreateInBatches(&items, 1000).Error
  419. return cErr
  420. })
  421. }
  422. func getColumnName(n int) string {
  423. columnName := ""
  424. for n > 0 {
  425. n--
  426. columnName = string(rune('A'+n%26)) + columnName
  427. n /= 26
  428. }
  429. return columnName
  430. }