已完成用户APP首期功能开发

交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
czl231
2026-09-13 00:57:32 +08:00
parent a0a4bb1218
commit 3ef33b531d
793 changed files with 40959 additions and 1204 deletions

View File

@@ -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)