feat: add ads/sponsor/captcha modules and polish home & chat UI
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"io"
|
||||
@@ -77,6 +78,9 @@ func (s *UploadService) EnsureDir() error {
|
||||
if err := os.MkdirAll(filepath.Join(s.dir, "backgrounds"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(s.dir, "ads"), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(filepath.Join(s.dir, "brand"), 0o755)
|
||||
}
|
||||
|
||||
@@ -166,7 +170,10 @@ func detectImageFormat(data []byte) (imageFormat, error) {
|
||||
if len(data) >= 12 && string(data[0:4]) == "RIFF" && string(data[8:12]) == "WEBP" {
|
||||
return imageFormat{ext: ".webp", mime: "image/webp"}, nil
|
||||
}
|
||||
return imageFormat{}, errors.New("仅支持 JPEG / PNG / WebP")
|
||||
if len(data) >= 6 && (string(data[0:6]) == "GIF87a" || string(data[0:6]) == "GIF89a") {
|
||||
return imageFormat{ext: ".gif", mime: "image/gif"}, nil
|
||||
}
|
||||
return imageFormat{}, errors.New("仅支持 JPEG / PNG / WebP / GIF")
|
||||
}
|
||||
|
||||
func decodeImageSizeReader(r io.Reader, mime string) (w, h int, err error) {
|
||||
@@ -403,6 +410,91 @@ func (s *UploadService) SaveBackground(src io.Reader) (string, error) {
|
||||
return "/uploads/backgrounds/" + filename, nil
|
||||
}
|
||||
|
||||
const AdAssetMaxBytes = 2 << 20 // 收款码 / 广告素材 2MB
|
||||
|
||||
// SaveAdAsset 保存广告相关图片(收款码等)到 uploads/ads,保留原格式含 GIF
|
||||
func (s *UploadService) SaveAdAsset(src io.Reader) (string, error) {
|
||||
if src == nil {
|
||||
return "", errors.New("文件为空")
|
||||
}
|
||||
tooLarge := func() error { return errors.New("图片不能超过 2MB") }
|
||||
|
||||
head := make([]byte, 12)
|
||||
n, err := io.ReadFull(src, head)
|
||||
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) && !errors.Is(err, io.EOF) {
|
||||
return "", errors.New("读取图片失败")
|
||||
}
|
||||
if n == 0 {
|
||||
return "", errors.New("文件为空")
|
||||
}
|
||||
format, err := detectImageFormat(head[:n])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(s.dir, "ads"), 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
nameBytes := make([]byte, 16)
|
||||
if _, err := rand.Read(nameBytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
filename := hex.EncodeToString(nameBytes) + format.ext
|
||||
fullPath := filepath.Join(s.dir, "ads", filename)
|
||||
tmp := fullPath + ".partial"
|
||||
if s.ops != nil {
|
||||
release, e := s.ops.BeginTemporary(tmp)
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
defer release()
|
||||
}
|
||||
f, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
remain := AdAssetMaxBytes + 1 - int64(n)
|
||||
if remain < 0 {
|
||||
_ = f.Close()
|
||||
_ = os.Remove(tmp)
|
||||
return "", tooLarge()
|
||||
}
|
||||
if _, err := f.Write(head[:n]); err != nil {
|
||||
_ = f.Close()
|
||||
_ = os.Remove(tmp)
|
||||
return "", err
|
||||
}
|
||||
writtenRest, copyErr := io.Copy(f, io.LimitReader(src, remain))
|
||||
_ = f.Close()
|
||||
if copyErr != nil {
|
||||
_ = os.Remove(tmp)
|
||||
return "", copyErr
|
||||
}
|
||||
if int64(n)+writtenRest > AdAssetMaxBytes {
|
||||
_ = os.Remove(tmp)
|
||||
return "", tooLarge()
|
||||
}
|
||||
rf, err := os.Open(tmp)
|
||||
if err != nil {
|
||||
_ = os.Remove(tmp)
|
||||
return "", err
|
||||
}
|
||||
w, h, err := decodeImageSizeReader(rf, format.mime)
|
||||
_ = rf.Close()
|
||||
if err != nil {
|
||||
_ = os.Remove(tmp)
|
||||
return "", err
|
||||
}
|
||||
if w < 1 || h < 1 || w > 4096 || h > 4096 {
|
||||
_ = os.Remove(tmp)
|
||||
return "", errors.New("图片尺寸无效")
|
||||
}
|
||||
if err := os.Rename(tmp, fullPath); err != nil {
|
||||
_ = os.Remove(tmp)
|
||||
return "", err
|
||||
}
|
||||
return "/uploads/ads/" + filename, nil
|
||||
}
|
||||
|
||||
// CopyBackgroundFromMedia 把当前用户媒体库里的一张图复制进 backgrounds,不写站点设置
|
||||
func (s *UploadService) CopyBackgroundFromMedia(userID, attachmentID uint) (string, error) {
|
||||
var att model.Attachment
|
||||
|
||||
Reference in New Issue
Block a user