Skip to content

Commit ab75bc7

Browse files
pixelmaxQmpiexlMax(奇淼task
authored
public: 发布2.8.0版本 (#1998)
* feat:修改token获取优先级,优先从header获取 * feat: 增加业务ctx的传递 * fix文件选择问题, 有公共函数onDownloadFile,去掉downloadFile * 没问题 * feat: 增加context引入 * fix: 修复图片多选的情况下点击确定抽屉无法收起的bug * feat: 提高header的token优先级,优化导出表格逻辑,不再依赖于cookie鉴权 --------- Co-authored-by: piexlMax(奇淼 <[email protected]> Co-authored-by: task <[email protected]>
1 parent f776a06 commit ab75bc7

File tree

29 files changed

+416
-100
lines changed

29 files changed

+416
-100
lines changed

server/api/v1/system/sys_export_template.go

+175-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ package system
33
import (
44
"fmt"
55
"net/http"
6+
"net/url"
7+
"sync"
8+
"time"
69

710
"github.com/flipped-aurora/gin-vue-admin/server/global"
811
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
@@ -15,6 +18,33 @@ import (
1518
"go.uber.org/zap"
1619
)
1720

21+
// 用于token一次性存储
22+
var (
23+
exportTokenCache = make(map[string]interface{})
24+
exportTokenExpiration = make(map[string]time.Time)
25+
tokenMutex sync.RWMutex
26+
)
27+
28+
// 五分钟检测窗口过期
29+
func cleanupExpiredTokens() {
30+
for {
31+
time.Sleep(5 * time.Minute)
32+
tokenMutex.Lock()
33+
now := time.Now()
34+
for token, expiry := range exportTokenExpiration {
35+
if now.After(expiry) {
36+
delete(exportTokenCache, token)
37+
delete(exportTokenExpiration, token)
38+
}
39+
}
40+
tokenMutex.Unlock()
41+
}
42+
}
43+
44+
func init() {
45+
go cleanupExpiredTokens()
46+
}
47+
1848
type SysExportTemplateApi struct {
1949
}
2050

@@ -183,7 +213,7 @@ func (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gi
183213
}
184214
}
185215

186-
// ExportExcel 导出表格
216+
// ExportExcel 导出表格token
187217
// @Tags SysExportTemplate
188218
// @Summary 导出表格
189219
// @Security ApiKeyAuth
@@ -192,16 +222,83 @@ func (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gi
192222
// @Router /sysExportTemplate/exportExcel [get]
193223
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) {
194224
templateID := c.Query("templateID")
195-
queryParams := c.Request.URL.Query()
196225
if templateID == "" {
197226
response.FailWithMessage("模板ID不能为空", c)
198227
return
199228
}
229+
230+
queryParams := c.Request.URL.Query()
231+
232+
//创造一次性token
233+
token := utils.RandomString(32) // 随机32位
234+
235+
// 记录本次请求参数
236+
exportParams := map[string]interface{}{
237+
"templateID": templateID,
238+
"queryParams": queryParams,
239+
}
240+
241+
// 参数保留记录完成鉴权
242+
tokenMutex.Lock()
243+
exportTokenCache[token] = exportParams
244+
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
245+
tokenMutex.Unlock()
246+
247+
// 生成一次性链接
248+
exportUrl := fmt.Sprintf("/sysExportTemplate/exportExcelByToken?token=%s", token)
249+
response.OkWithData(exportUrl, c)
250+
}
251+
252+
// ExportExcelByToken 导出表格
253+
// @Tags ExportExcelByToken
254+
// @Summary 导出表格
255+
// @Security ApiKeyAuth
256+
// @accept application/json
257+
// @Produce application/json
258+
// @Router /sysExportTemplate/exportExcelByToken [get]
259+
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcelByToken(c *gin.Context) {
260+
token := c.Query("token")
261+
if token == "" {
262+
response.FailWithMessage("导出token不能为空", c)
263+
return
264+
}
265+
266+
// 获取token并且从缓存中剔除
267+
tokenMutex.RLock()
268+
exportParamsRaw, exists := exportTokenCache[token]
269+
expiry, _ := exportTokenExpiration[token]
270+
tokenMutex.RUnlock()
271+
272+
if !exists || time.Now().After(expiry) {
273+
global.GVA_LOG.Error("导出token无效或已过期!")
274+
response.FailWithMessage("导出token无效或已过期", c)
275+
return
276+
}
277+
278+
// 从token获取参数
279+
exportParams, ok := exportParamsRaw.(map[string]interface{})
280+
if !ok {
281+
global.GVA_LOG.Error("解析导出参数失败!")
282+
response.FailWithMessage("解析导出参数失败", c)
283+
return
284+
}
285+
286+
// 获取导出参数
287+
templateID := exportParams["templateID"].(string)
288+
queryParams := exportParams["queryParams"].(url.Values)
289+
290+
// 清理一次性token
291+
tokenMutex.Lock()
292+
delete(exportTokenCache, token)
293+
delete(exportTokenExpiration, token)
294+
tokenMutex.Unlock()
295+
296+
// 导出
200297
if file, name, err := sysExportTemplateService.ExportExcel(templateID, queryParams); err != nil {
201298
global.GVA_LOG.Error("获取失败!", zap.Error(err))
202299
response.FailWithMessage("获取失败", c)
203300
} else {
204-
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+utils.RandomString(6)+".xlsx")) // 对下载的文件重命名
301+
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+utils.RandomString(6)+".xlsx"))
205302
c.Header("success", "true")
206303
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes())
207304
}
@@ -213,18 +310,91 @@ func (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) {
213310
// @Security ApiKeyAuth
214311
// @accept application/json
215312
// @Produce application/json
216-
// @Router /sysExportTemplate/ExportTemplate [get]
313+
// @Router /sysExportTemplate/exportTemplate [get]
217314
func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplate(c *gin.Context) {
218315
templateID := c.Query("templateID")
219316
if templateID == "" {
220317
response.FailWithMessage("模板ID不能为空", c)
221318
return
222319
}
320+
321+
// 创造一次性token
322+
token := utils.RandomString(32) // 随机32位
323+
324+
// 记录本次请求参数
325+
exportParams := map[string]interface{}{
326+
"templateID": templateID,
327+
"isTemplate": true,
328+
}
329+
330+
// 参数保留记录完成鉴权
331+
tokenMutex.Lock()
332+
exportTokenCache[token] = exportParams
333+
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
334+
tokenMutex.Unlock()
335+
336+
// 生成一次性链接
337+
exportUrl := fmt.Sprintf("/sysExportTemplate/exportTemplateByToken?token=%s", token)
338+
response.OkWithData(exportUrl, c)
339+
}
340+
341+
// ExportTemplateByToken 通过token导出表格模板
342+
// @Tags ExportTemplateByToken
343+
// @Summary 通过token导出表格模板
344+
// @Security ApiKeyAuth
345+
// @accept application/json
346+
// @Produce application/json
347+
// @Router /sysExportTemplate/exportTemplateByToken [get]
348+
func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplateByToken(c *gin.Context) {
349+
token := c.Query("token")
350+
if token == "" {
351+
response.FailWithMessage("导出token不能为空", c)
352+
return
353+
}
354+
355+
// 获取token并且从缓存中剔除
356+
tokenMutex.RLock()
357+
exportParamsRaw, exists := exportTokenCache[token]
358+
expiry, _ := exportTokenExpiration[token]
359+
tokenMutex.RUnlock()
360+
361+
if !exists || time.Now().After(expiry) {
362+
global.GVA_LOG.Error("导出token无效或已过期!")
363+
response.FailWithMessage("导出token无效或已过期", c)
364+
return
365+
}
366+
367+
// 从token获取参数
368+
exportParams, ok := exportParamsRaw.(map[string]interface{})
369+
if !ok {
370+
global.GVA_LOG.Error("解析导出参数失败!")
371+
response.FailWithMessage("解析导出参数失败", c)
372+
return
373+
}
374+
375+
// 检查是否为模板导出
376+
isTemplate, _ := exportParams["isTemplate"].(bool)
377+
if !isTemplate {
378+
global.GVA_LOG.Error("token类型错误!")
379+
response.FailWithMessage("token类型错误", c)
380+
return
381+
}
382+
383+
// 获取导出参数
384+
templateID := exportParams["templateID"].(string)
385+
386+
// 清理一次性token
387+
tokenMutex.Lock()
388+
delete(exportTokenCache, token)
389+
delete(exportTokenExpiration, token)
390+
tokenMutex.Unlock()
391+
392+
// 导出模板
223393
if file, name, err := sysExportTemplateService.ExportTemplate(templateID); err != nil {
224394
global.GVA_LOG.Error("获取失败!", zap.Error(err))
225395
response.FailWithMessage("获取失败", c)
226396
} else {
227-
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+"模板.xlsx")) // 对下载的文件重命名
397+
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+"模板.xlsx"))
228398
c.Header("success", "true")
229399
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes())
230400
}

server/core/server.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func RunWindowsServer() {
3939

4040
fmt.Printf(`
4141
欢迎使用 gin-vue-admin
42-
当前版本:v2.7.9
42+
当前版本:v2.8.0
4343
加群方式:微信号:shouzi_1994 QQ群:470239250
4444
项目地址:https://github.com/flipped-aurora/gin-vue-admin
4545
插件市场:https://plugin.gin-vue-admin.com

server/docs/docs.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/initialize/router.go

+18-18
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,24 @@ func Routers() *gin.Engine {
7777
}
7878

7979
{
80-
systemRouter.InitApiRouter(PrivateGroup, PublicGroup) // 注册功能api路由
81-
systemRouter.InitJwtRouter(PrivateGroup) // jwt相关路由
82-
systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由
83-
systemRouter.InitMenuRouter(PrivateGroup) // 注册menu路由
84-
systemRouter.InitSystemRouter(PrivateGroup) // system相关路由
85-
systemRouter.InitCasbinRouter(PrivateGroup) // 权限相关路由
86-
systemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup) // 创建自动化代码
87-
systemRouter.InitAuthorityRouter(PrivateGroup) // 注册角色路由
88-
systemRouter.InitSysDictionaryRouter(PrivateGroup) // 字典管理
89-
systemRouter.InitAutoCodeHistoryRouter(PrivateGroup) // 自动化代码历史
90-
systemRouter.InitSysOperationRecordRouter(PrivateGroup) // 操作记录
91-
systemRouter.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理
92-
systemRouter.InitAuthorityBtnRouterRouter(PrivateGroup) // 按钮权限管理
93-
systemRouter.InitSysExportTemplateRouter(PrivateGroup) // 导出模板
94-
systemRouter.InitSysParamsRouter(PrivateGroup, PublicGroup) // 参数管理
95-
exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由
96-
exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由
97-
exampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup) // 文件上传下载分类
80+
systemRouter.InitApiRouter(PrivateGroup, PublicGroup) // 注册功能api路由
81+
systemRouter.InitJwtRouter(PrivateGroup) // jwt相关路由
82+
systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由
83+
systemRouter.InitMenuRouter(PrivateGroup) // 注册menu路由
84+
systemRouter.InitSystemRouter(PrivateGroup) // system相关路由
85+
systemRouter.InitCasbinRouter(PrivateGroup) // 权限相关路由
86+
systemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup) // 创建自动化代码
87+
systemRouter.InitAuthorityRouter(PrivateGroup) // 注册角色路由
88+
systemRouter.InitSysDictionaryRouter(PrivateGroup) // 字典管理
89+
systemRouter.InitAutoCodeHistoryRouter(PrivateGroup) // 自动化代码历史
90+
systemRouter.InitSysOperationRecordRouter(PrivateGroup) // 操作记录
91+
systemRouter.InitSysDictionaryDetailRouter(PrivateGroup) // 字典详情管理
92+
systemRouter.InitAuthorityBtnRouterRouter(PrivateGroup) // 按钮权限管理
93+
systemRouter.InitSysExportTemplateRouter(PrivateGroup, PublicGroup) // 导出模板
94+
systemRouter.InitSysParamsRouter(PrivateGroup, PublicGroup) // 参数管理
95+
exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由
96+
exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由
97+
exampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup) // 文件上传下载分类
9898

9999
}
100100

server/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
// @Tag.Description 用户
2222

2323
// @title Gin-Vue-Admin Swagger API接口文档
24-
// @version v2.7.9
24+
// @version v2.8.0
2525
// @description 使用gin+vue进行极速开发的全栈开发基础平台
2626
// @securityDefinitions.apikey ApiKeyAuth
2727
// @in header

server/resource/function/api.go.tpl

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
88
// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}]
99
func (a *{{.Abbreviation}}) {{.FuncName}}(c *gin.Context) {
10+
// 创建业务用Context
11+
ctx := c.Request.Context()
1012
// 请添加自己的业务逻辑
11-
err := service{{ .StructName }}.{{.FuncName}}()
13+
err := service{{ .StructName }}.{{.FuncName}}(ctx)
1214
if err != nil {
1315
global.GVA_LOG.Error("失败!", zap.Error(err))
1416
response.FailWithMessage("失败", c)
@@ -28,8 +30,10 @@ func (a *{{.Abbreviation}}) {{.FuncName}}(c *gin.Context) {
2830
// @Success 200 {object} response.Response{data=object,msg=string} "成功"
2931
// @Router /{{.Abbreviation}}/{{.Router}} [{{.Method}}]
3032
func ({{.Abbreviation}}Api *{{.StructName}}Api){{.FuncName}}(c *gin.Context) {
33+
// 创建业务用Context
34+
ctx := c.Request.Context()
3135
// 请添加自己的业务逻辑
32-
err := {{.Abbreviation}}Service.{{.FuncName}}()
36+
err := {{.Abbreviation}}Service.{{.FuncName}}(ctx)
3337
if err != nil {
3438
global.GVA_LOG.Error("失败!", zap.Error(err))
3539
response.FailWithMessage("失败", c)

server/resource/function/server.go.tpl

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
// {{.FuncName}} {{.FuncDesc}}
1010
// Author [yourname](https://github.com/yourname)
11-
func (s *{{.Abbreviation}}) {{.FuncName}}() (err error) {
11+
func (s *{{.Abbreviation}}) {{.FuncName}}(ctx context.Context) (err error) {
1212
db := {{$db}}.Model(&model.{{.StructName}}{})
1313
return db.Error
1414
}
@@ -17,9 +17,9 @@ func (s *{{.Abbreviation}}) {{.FuncName}}() (err error) {
1717

1818
// {{.FuncName}} {{.FuncDesc}}
1919
// Author [yourname](https://github.com/yourname)
20-
func ({{.Abbreviation}}Service *{{.StructName}}Service){{.FuncName}}() (err error) {
20+
func ({{.Abbreviation}}Service *{{.StructName}}Service){{.FuncName}}(ctx context.Context) (err error) {
2121
// 请在这里实现自己的业务逻辑
2222
db := {{$db}}.Model(&{{.Package}}.{{.StructName}}{})
2323
return db.Error
2424
}
25-
{{end}}
25+
{{end}}

0 commit comments

Comments
 (0)