fix Authorization

This commit is contained in:
2025-11-11 17:47:08 +08:00
parent 64c0a10193
commit c40be9daf8
10 changed files with 326 additions and 29 deletions

View File

@@ -47,10 +47,15 @@ type (
DocumentId int64 `json:"documentId" validate:"required"` // 授权书ID
}
// DownloadAuthorizationDocumentByNameReq 通过文件名下载授权书请求
DownloadAuthorizationDocumentByNameReq {
FileName string `path:"fileName" validate:"required"` // 授权书文件名
}
// DownloadAuthorizationDocumentResp 下载授权书响应
DownloadAuthorizationDocumentResp {
FileName string `json:"fileName"` // 文件名
FileUrl string `json:"fileUrl"` // 文件访问URL
FilePath string `json:"filePath"` // 文件存储路径
}
)
@@ -71,4 +76,8 @@ service main {
// 下载授权书文件
@handler DownloadAuthorizationDocument
get /authorization/download/:documentId (DownloadAuthorizationDocumentReq) returns (DownloadAuthorizationDocumentResp)
// 通过文件名下载授权书文件
@handler DownloadAuthorizationDocumentByName
get /authorization/download/file/:fileName (DownloadAuthorizationDocumentByNameReq) returns (DownloadAuthorizationDocumentResp)
}

View File

@@ -0,0 +1,67 @@
package authorization
import (
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"tydata-server/app/main/api/internal/logic/authorization"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/app/main/model"
"tydata-server/common/result"
"tydata-server/common/xerr"
"tydata-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/httpx"
)
func DownloadAuthorizationDocumentByNameHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.DownloadAuthorizationDocumentByNameReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := authorization.NewDownloadAuthorizationDocumentByNameLogic(r.Context(), svcCtx)
resp, err := l.DownloadAuthorizationDocumentByName(&req)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
httpx.WriteJson(w, http.StatusNotFound, result.Error(xerr.CUSTOM_ERROR, "授权书不存在"))
return
}
result.HttpResult(r, w, nil, err)
return
}
file, openErr := os.Open(resp.FilePath)
if openErr != nil {
logx.Errorf("打开授权书文件失败: fileName=%s, filePath=%s, error=%v", req.FileName, resp.FilePath, openErr)
result.HttpResult(r, w, nil, errors.New("授权书文件不存在"))
return
}
defer file.Close()
fileInfo, statErr := file.Stat()
if statErr != nil {
logx.Errorf("读取授权书文件信息失败: fileName=%s, filePath=%s, error=%v", req.FileName, resp.FilePath, statErr)
result.HttpResult(r, w, nil, errors.New("授权书文件不可用"))
return
}
escapedName := url.PathEscape(resp.FileName)
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", escapedName, escapedName))
w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
http.ServeContent(w, r, resp.FileName, fileInfo.ModTime(), file)
}
}

View File

@@ -1,14 +1,22 @@
package authorization
import (
"errors"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"tydata-server/app/main/api/internal/logic/authorization"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/app/main/model"
"tydata-server/common/result"
"tydata-server/common/xerr"
"tydata-server/pkg/lzkit/validator"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/httpx"
)
@@ -25,6 +33,35 @@ func DownloadAuthorizationDocumentHandler(svcCtx *svc.ServiceContext) http.Handl
}
l := authorization.NewDownloadAuthorizationDocumentLogic(r.Context(), svcCtx)
resp, err := l.DownloadAuthorizationDocument(&req)
result.HttpResult(r, w, resp, err)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
httpx.WriteJson(w, http.StatusNotFound, result.Error(xerr.CUSTOM_ERROR, "授权书不存在"))
return
}
result.HttpResult(r, w, nil, err)
return
}
file, openErr := os.Open(resp.FilePath)
if openErr != nil {
logx.Errorf("打开授权书文件失败: filePath=%s, error=%v", resp.FilePath, openErr)
result.HttpResult(r, w, nil, errors.New("授权书文件不存在"))
return
}
defer file.Close()
fileInfo, statErr := file.Stat()
if statErr != nil {
logx.Errorf("读取授权书文件信息失败: filePath=%s, error=%v", resp.FilePath, statErr)
result.HttpResult(r, w, nil, errors.New("授权书文件不可用"))
return
}
escapedName := url.PathEscape(resp.FileName)
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", escapedName, escapedName))
w.Header().Set("Content-Length", strconv.FormatInt(fileInfo.Size(), 10))
http.ServeContent(w, r, resp.FileName, fileInfo.ModTime(), file)
}
}

View File

@@ -841,6 +841,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/authorization/download/:documentId",
Handler: authorization.DownloadAuthorizationDocumentHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/authorization/download/file/:fileName",
Handler: authorization.DownloadAuthorizationDocumentByNameHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1"),
)

View File

@@ -0,0 +1,59 @@
package authorization
import (
"context"
"errors"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/app/main/model"
"tydata-server/common/globalkey"
"github.com/zeromicro/go-zero/core/logx"
)
type DownloadAuthorizationDocumentByNameLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDownloadAuthorizationDocumentByNameLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DownloadAuthorizationDocumentByNameLogic {
return &DownloadAuthorizationDocumentByNameLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *DownloadAuthorizationDocumentByNameLogic) DownloadAuthorizationDocumentByName(req *types.DownloadAuthorizationDocumentByNameReq) (resp *types.DownloadAuthorizationDocumentResp, err error) {
builder := l.svcCtx.AuthorizationDocumentModel.SelectBuilder().
Where("file_name = ?", req.FileName).
Where("del_state = ?", globalkey.DelStateNo).
Limit(1)
authDocs, err := l.svcCtx.AuthorizationDocumentModel.FindAll(l.ctx, builder, "")
if err != nil {
logx.Errorf("根据文件名查询授权书失败: fileName=%s, error=%v", req.FileName, err)
return nil, err
}
if len(authDocs) == 0 {
logx.Errorf("根据文件名未找到授权书: fileName=%s", req.FileName)
return nil, model.ErrNotFound
}
authDoc := authDocs[0]
if authDoc.Status != "active" {
logx.Errorf("授权书状态异常: fileName=%s, status=%s", req.FileName, authDoc.Status)
return nil, errors.New("授权书不可用")
}
filePath := l.svcCtx.AuthorizationService.ResolveFilePath(authDoc.FilePath, authDoc.FileUrl)
resp = &types.DownloadAuthorizationDocumentResp{
FileName: authDoc.FileName,
FilePath: filePath,
}
return resp, nil
}

View File

@@ -38,13 +38,12 @@ func (l *DownloadAuthorizationDocumentLogic) DownloadAuthorizationDocument(req *
return nil, errors.New("授权书不可用")
}
// 3. 构建完整文件URL
fullFileURL := l.svcCtx.AuthorizationService.GetFullFileURL(authDoc.FileUrl)
// 3. 构建响应
filePath := l.svcCtx.AuthorizationService.ResolveFilePath(authDoc.FilePath, authDoc.FileUrl)
// 4. 构建响应
resp = &types.DownloadAuthorizationDocumentResp{
FileName: authDoc.FileName,
FileUrl: fullFileURL,
FilePath: filePath,
}
return resp, nil

View File

@@ -2,17 +2,19 @@ package queue
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"net/url"
"os"
"path"
"regexp"
"strings"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/app/main/model"
"tydata-server/pkg/lzkit/crypto"
"tydata-server/pkg/lzkit/lzUtils"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
"github.com/hibiken/asynq"
"github.com/zeromicro/go-zero/core/logx"
@@ -112,9 +114,9 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
// 将授权书URL添加到解密数据中
if authDoc != nil {
// 生成完整的授权书访问URL
fullAuthDocURL := l.svcCtx.AuthorizationService.GetFullFileURL(authDoc.FileUrl)
userInfo["authorization_url"] = fullAuthDocURL
// 生成授权书下载接口URL
downloadURL := l.buildAuthorizationDownloadURL(authDoc.FileName)
userInfo["authorization_url"] = downloadURL
// 重新序列化用户信息
updatedDecryptData, marshalErr := json.Marshal(userInfo)
@@ -132,7 +134,7 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
if updateParamsErr != nil {
logx.Errorf("更新查询参数失败: %v", updateParamsErr)
} else {
logx.Infof("成功更新查询参数包含授权书URL: %s", fullAuthDocURL)
logx.Infof("成功更新查询参数包含授权书URL: %s", downloadURL)
}
}
decryptData = updatedDecryptData
@@ -177,6 +179,18 @@ func (l *PaySuccessNotifyUserHandler) ProcessTask(ctx context.Context, t *asynq.
return nil
}
func (l *PaySuccessNotifyUserHandler) buildAuthorizationDownloadURL(fileName string) string {
escapedFileName := url.PathEscape(fileName)
base := l.svcCtx.Config.Authorization.FileBaseURL
if parsed, err := url.Parse(base); err == nil && parsed.Scheme != "" && parsed.Host != "" {
parsed.Path = path.Join("/api/v1/authorization/download/file", escapedFileName)
parsed.RawQuery = ""
parsed.Fragment = ""
return parsed.String()
}
return fmt.Sprintf("/api/v1/authorization/download/file/%s", escapedFileName)
}
// 定义一个中间件函数
func (l *PaySuccessNotifyUserHandler) handleError(ctx context.Context, err error, order *model.Order, query *model.Query) error {
logx.Errorf("处理任务失败,原因: %v", err)

View File

@@ -5,8 +5,10 @@ import (
"context"
"database/sql"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"tydata-server/app/main/api/internal/config"
"tydata-server/app/main/model"
@@ -25,11 +27,13 @@ type AuthorizationService struct {
// NewAuthorizationService 创建授权书服务实例
func NewAuthorizationService(c config.Config, authDocModel model.AuthorizationDocumentModel) *AuthorizationService {
absStoragePath := determineStoragePath()
return &AuthorizationService{
config: c,
authDocModel: authDocModel,
fileStoragePath: "data/authorization_docs", // 使用相对路径,兼容开发环境
fileBaseURL: c.Authorization.FileBaseURL, // 从配置文件读取
fileStoragePath: absStoragePath,
fileBaseURL: c.Authorization.FileBaseURL, // 从配置文件读取
}
}
@@ -101,6 +105,109 @@ func (s *AuthorizationService) GetFullFileURL(relativePath string) string {
return fmt.Sprintf("%s/%s", s.fileBaseURL, relativePath)
}
// ResolveFilePath 根据存储的路径信息解析出本地文件的绝对路径
func (s *AuthorizationService) ResolveFilePath(filePath string, relativePath string) string {
candidates := []string{}
cleanRelative := func(p string) string {
p = strings.TrimPrefix(p, "/")
p = strings.TrimPrefix(p, "\\")
normalized := filepath.ToSlash(p)
if strings.HasPrefix(normalized, "http://") || strings.HasPrefix(normalized, "https://") {
if parsed, err := url.Parse(normalized); err == nil {
normalized = filepath.ToSlash(strings.TrimPrefix(parsed.Path, "/"))
}
}
normalized = strings.TrimPrefix(normalized, "data/authorization_docs/")
normalized = strings.TrimPrefix(normalized, "authorization_docs/")
normalized = strings.TrimPrefix(normalized, "./")
normalized = strings.TrimPrefix(normalized, "../")
return normalized
}
if filePath != "" {
candidates = append(candidates, filePath)
}
if relativePath != "" {
candidates = append(candidates, filepath.Join(s.fileStoragePath, filepath.FromSlash(cleanRelative(relativePath))))
}
if filePath != "" {
cleaned := filepath.Clean(filePath)
if strings.HasPrefix(cleaned, s.fileStoragePath) {
candidates = append(candidates, cleaned)
} else {
trimmed := strings.TrimPrefix(cleaned, "data"+string(os.PathSeparator)+"authorization_docs"+string(os.PathSeparator))
trimmed = strings.TrimPrefix(trimmed, "data/authorization_docs/")
if trimmed != cleaned {
candidates = append(candidates, filepath.Join(s.fileStoragePath, filepath.FromSlash(cleanRelative(trimmed))))
}
if !filepath.IsAbs(cleaned) {
candidates = append(candidates, filepath.Join(s.fileStoragePath, filepath.FromSlash(cleanRelative(cleaned))))
}
}
}
for _, candidate := range candidates {
if candidate == "" {
continue
}
pathCandidate := candidate
if !filepath.IsAbs(pathCandidate) {
if absPath, err := filepath.Abs(pathCandidate); err == nil {
pathCandidate = absPath
}
}
if statErr := checkFileExists(pathCandidate); statErr == nil {
return pathCandidate
}
}
logx.Errorf("授权书文件路径解析失败 filePath=%s fileUrl=%s candidates=%v", filePath, relativePath, candidates)
return ""
}
func determineStoragePath() string {
candidatePaths := []string{
"data/authorization_docs",
"../data/authorization_docs",
"../../data/authorization_docs",
"../../../data/authorization_docs",
}
for _, candidate := range candidatePaths {
absPath, err := filepath.Abs(candidate)
if err != nil {
logx.Errorf("解析授权书存储路径失败: %s, err=%v", candidate, err)
continue
}
if info, err := os.Stat(absPath); err == nil && info.IsDir() {
logx.Infof("授权书存储路径选择: %s", absPath)
return absPath
}
}
// 如果没有现成的目录,使用第一个候选的绝对路径
absPath, err := filepath.Abs(candidatePaths[0])
if err != nil {
logx.Errorf("解析默认授权书存储路径失败,使用相对路径: %s, err=%v", candidatePaths[0], err)
return candidatePaths[0]
}
logx.Infof("授权书存储路径创建: %s", absPath)
return absPath
}
func checkFileExists(path string) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if info.IsDir() {
return fmt.Errorf("path %s is directory", path)
}
return nil
}
// generatePDFContent 生成PDF内容
func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{}) ([]byte, error) {
// 创建PDF文档
@@ -113,7 +220,7 @@ func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{
"/app/static/SIMHEI.TTF", // Docker容器内的字体文件
"app/main/api/static/SIMHEI.TTF", // 开发环境备用路径
}
// 尝试添加字体
fontAdded := false
for _, fontPath := range fontPaths {
@@ -150,17 +257,17 @@ func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{
pdf.SetFont("Arial", "", 20)
}
pdf.CellFormat(0, 15, "授权书", "", 1, "C", false, 0, "")
// 添加空行
pdf.Ln(5)
// 设置正文样式 - 正常字体
if fontAdded {
pdf.SetFont("ChineseFont", "", 12)
} else {
pdf.SetFont("Arial", "", 12)
}
// 构建授权书内容(去掉标题部分)
content := fmt.Sprintf(`海南天远大数据科技有限公司:
本人%s拟向贵司申请大数据分析报告查询业务贵司需要了解本人相关状况用于查询大数据分析报告因此本人同意向贵司提供本人的姓名和手机号等个人信息并同意贵司向第三方传送上述信息。第三方将使用上述信息核实信息真实情况查询信用记录并生成报告。

View File

@@ -1231,13 +1231,17 @@ type DirectPushReport struct {
Last30D TimeRangeReport `json:"last30d"` // 近30天数据
}
type DownloadAuthorizationDocumentByNameReq struct {
FileName string `path:"fileName" validate:"required"` // 授权书文件名
}
type DownloadAuthorizationDocumentReq struct {
DocumentId int64 `json:"documentId" validate:"required"` // 授权书ID
}
type DownloadAuthorizationDocumentResp struct {
FileName string `json:"fileName"` // 文件名
FileUrl string `json:"fileUrl"` // 文件访问URL
FilePath string `json:"filePath"` // 文件存储路径
}
type Feature struct {

View File

@@ -32,12 +32,8 @@ func NewAuthorizationDocumentModel(conn sqlx.SqlConn, c cache.CacheConf) Authori
// FindByOrderId 根据订单ID查询授权书列表
func (m *customAuthorizationDocumentModel) FindByOrderId(ctx context.Context, orderId int64) ([]*AuthorizationDocument, error) {
query := `SELECT * FROM authorization_document WHERE order_id = ? AND del_state = 0 ORDER BY create_time DESC`
var authDocs []*AuthorizationDocument
err := m.QueryRowsNoCacheCtx(ctx, &authDocs, query, orderId)
if err != nil {
return nil, err
}
return authDocs, nil
return authDocs, err
}