已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
@@ -4,7 +4,9 @@ package upload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
@@ -30,6 +32,11 @@ const (
|
||||
|
||||
// UploadAvatar 接收 JPG/PNG 头像,验证真实图片内容后写入受控目录。
|
||||
func UploadAvatar(ctx *gin.Context) {
|
||||
claims, parseErr := sdkmiddleware.ParseAuth(ctx)
|
||||
if parseErr != nil || claims.Identity == "" || claims.Client == "" {
|
||||
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
|
||||
return
|
||||
}
|
||||
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, maxAvatarSize+(256<<10))
|
||||
fileHeader, err := ctx.FormFile("file")
|
||||
if err != nil || fileHeader == nil || fileHeader.Size <= 0 || fileHeader.Size > maxAvatarSize {
|
||||
@@ -55,7 +62,8 @@ func UploadAvatar(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
datePath := time.Now().Format("2006/01/02")
|
||||
// 将上传文件绑定到登录主体,后续资料更新不能引用其他账户的头像。
|
||||
datePath := avatarOwnerDirectory(claims.Client, claims.Identity) + "/" + time.Now().Format("2006/01/02")
|
||||
filename := models.NewIdentity() + extension
|
||||
directory := filepath.Join(uploadRoot(), "avatars", filepath.FromSlash(datePath))
|
||||
if err := os.MkdirAll(directory, 0o750); err != nil {
|
||||
@@ -92,6 +100,25 @@ func UploadAvatar(ctx *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// avatarOwnerDirectory 对客户端和账户组合取摘要,避免身份内容成为文件系统路径。
|
||||
func avatarOwnerDirectory(client, identity string) string {
|
||||
return fmt.Sprintf("owners/%x", sha256.Sum256([]byte(client+"\x00"+identity)))
|
||||
}
|
||||
|
||||
// OwnsAvatar 验证规范 URI 属于当前账户且指向已上传的普通文件。
|
||||
func OwnsAvatar(client, identity, uri string) bool {
|
||||
prefix := "/uploads/avatars/" + avatarOwnerDirectory(client, identity) + "/"
|
||||
if !strings.HasPrefix(uri, prefix) || strings.Contains(uri, "\\") || strings.Contains(uri, "..") {
|
||||
return false
|
||||
}
|
||||
path, err := avatarPathFromURI(uri)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
info, err := os.Lstat(path)
|
||||
return err == nil && info.Mode().IsRegular()
|
||||
}
|
||||
|
||||
// ServeAvatar 仅从头像受控目录读取文件,拒绝外部 URL 与目录穿越路径。
|
||||
func ServeAvatar(ctx *gin.Context, uri string) {
|
||||
path, err := avatarPathFromURI(uri)
|
||||
|
||||
Reference in New Issue
Block a user