Skip to content

Commit f5c213d

Browse files
committed
feat: 提高header的token优先级,优化导出表格逻辑,不再依赖于cookie鉴权
1 parent 17500d7 commit f5c213d

File tree

14 files changed

+302
-35
lines changed

14 files changed

+302
-35
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/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/router/system/sys_export_template.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ type SysExportTemplateRouter struct {
99
}
1010

1111
// InitSysExportTemplateRouter 初始化 导出模板 路由信息
12-
func (s *SysExportTemplateRouter) InitSysExportTemplateRouter(Router *gin.RouterGroup) {
12+
func (s *SysExportTemplateRouter) InitSysExportTemplateRouter(Router *gin.RouterGroup, pubRouter *gin.RouterGroup) {
1313
sysExportTemplateRouter := Router.Group("sysExportTemplate").Use(middleware.OperationRecord())
1414
sysExportTemplateRouterWithoutRecord := Router.Group("sysExportTemplate")
15+
sysExportTemplateRouterWithoutAuth := pubRouter.Group("sysExportTemplate")
16+
1517
{
1618
sysExportTemplateRouter.POST("createSysExportTemplate", exportTemplateApi.CreateSysExportTemplate) // 新建导出模板
1719
sysExportTemplateRouter.DELETE("deleteSysExportTemplate", exportTemplateApi.DeleteSysExportTemplate) // 删除导出模板
@@ -22,7 +24,11 @@ func (s *SysExportTemplateRouter) InitSysExportTemplateRouter(Router *gin.Router
2224
{
2325
sysExportTemplateRouterWithoutRecord.GET("findSysExportTemplate", exportTemplateApi.FindSysExportTemplate) // 根据ID获取导出模板
2426
sysExportTemplateRouterWithoutRecord.GET("getSysExportTemplateList", exportTemplateApi.GetSysExportTemplateList) // 获取导出模板列表
25-
sysExportTemplateRouterWithoutRecord.GET("exportExcel", exportTemplateApi.ExportExcel) // 导出表格
27+
sysExportTemplateRouterWithoutRecord.GET("exportExcel", exportTemplateApi.ExportExcel) // 获取导出token
2628
sysExportTemplateRouterWithoutRecord.GET("exportTemplate", exportTemplateApi.ExportTemplate) // 导出表格模板
2729
}
30+
{
31+
sysExportTemplateRouterWithoutAuth.GET("exportExcelByToken", exportTemplateApi.ExportExcelByToken) // 通过token导出表格
32+
sysExportTemplateRouterWithoutAuth.GET("exportTemplateByToken", exportTemplateApi.ExportTemplateByToken) // 通过token导出模板
33+
}
2834
}

web/src/api/exportTemplate.js

+31
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,34 @@ export const getSysExportTemplateList = (params) => {
9595
params
9696
})
9797
}
98+
99+
100+
// ExportExcel 导出表格token
101+
// @Tags SysExportTemplate
102+
// @Summary 导出表格
103+
// @Security ApiKeyAuth
104+
// @accept application/json
105+
// @Produce application/json
106+
// @Router /sysExportTemplate/exportExcel [get]
107+
export const exportExcel = (params) => {
108+
return service({
109+
url: '/sysExportTemplate/exportExcel',
110+
method: 'get',
111+
params
112+
})
113+
}
114+
115+
// ExportTemplate 导出表格模板
116+
// @Tags SysExportTemplate
117+
// @Summary 导出表格模板
118+
// @Security ApiKeyAuth
119+
// @accept application/json
120+
// @Produce application/json
121+
// @Router /sysExportTemplate/exportTemplate [get]
122+
export const exportTemplate = (params) => {
123+
return service({
124+
url: '/sysExportTemplate/exportTemplate',
125+
method: 'get',
126+
params
127+
})
128+
}

web/src/components/exportExcel/exportExcel.vue

+13-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
</template>
66

77
<script setup>
8-
import {getUrl} from "@/utils/image";
8+
9+
import { exportExcel } from '@/api/exportTemplate'
910
1011
const props = defineProps({
1112
templateId: {
@@ -58,8 +59,17 @@
5859
)
5960
.join('&')
6061
61-
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}${params ? '&' + params : ''}`
62+
const res = await exportExcel({
63+
templateID: props.templateId,
64+
params
65+
})
66+
67+
if(res.code === 0){
68+
ElMessage.success('创建导出任务成功,开始下载')
69+
const url = `${baseUrl}${res.data}`
70+
window.open(url, '_blank')
71+
}
72+
6273
63-
window.open(url, '_blank')
6474
}
6575
</script>

0 commit comments

Comments
 (0)