mirror of
https://github.com/wgh136/nysoure.git
synced 2025-12-15 07:31:14 +00:00
895 lines
21 KiB
Go
895 lines
21 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"nysoure/server/config"
|
|
"nysoure/server/dao"
|
|
"nysoure/server/model"
|
|
"nysoure/server/search"
|
|
"nysoure/server/utils"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/gofiber/fiber/v3/log"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
maxSearchQueryLength = 100
|
|
)
|
|
|
|
type ResourceParams struct {
|
|
Title string `json:"title" binding:"required"`
|
|
AlternativeTitles []string `json:"alternative_titles"`
|
|
Links []model.Link `json:"links"`
|
|
Tags []uint `json:"tags"`
|
|
Article string `json:"article"`
|
|
Images []uint `json:"images"`
|
|
Gallery []uint `json:"gallery"`
|
|
GalleryNsfw []uint `json:"gallery_nsfw"`
|
|
Characters []CharacterParams `json:"characters"`
|
|
}
|
|
|
|
type CharacterParams struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Alias []string `json:"alias"`
|
|
CV string `json:"cv"`
|
|
Role string `json:"role"`
|
|
Image uint `json:"image"`
|
|
}
|
|
|
|
func CreateResource(uid uint, params *ResourceParams) (uint, error) {
|
|
canUpload, err := checkUserCanUpload(uid)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if !canUpload {
|
|
return 0, model.NewUnAuthorizedError("You have not permission to upload resources")
|
|
}
|
|
|
|
images := make([]model.Image, len(params.Images))
|
|
for i, id := range params.Images {
|
|
images[i] = model.Image{
|
|
Model: gorm.Model{
|
|
ID: id,
|
|
},
|
|
}
|
|
}
|
|
tags := make([]model.Tag, len(params.Tags))
|
|
for i, id := range params.Tags {
|
|
tags[i] = model.Tag{
|
|
Model: gorm.Model{
|
|
ID: id,
|
|
},
|
|
}
|
|
}
|
|
gallery := make([]uint, 0, len(params.Gallery))
|
|
for _, id := range params.Gallery {
|
|
if slices.Contains(params.Images, id) {
|
|
gallery = append(gallery, id)
|
|
}
|
|
}
|
|
nsfw := make([]uint, 0, len(params.GalleryNsfw))
|
|
for _, id := range params.GalleryNsfw {
|
|
if slices.Contains(gallery, id) {
|
|
nsfw = append(nsfw, id)
|
|
}
|
|
}
|
|
characters := make([]model.Character, len(params.Characters))
|
|
for i, c := range params.Characters {
|
|
role := c.Role
|
|
if role == "" {
|
|
role = "primary"
|
|
}
|
|
var imageID *uint
|
|
if c.Image != 0 {
|
|
imageID = &c.Image
|
|
}
|
|
characters[i] = model.Character{
|
|
Name: c.Name,
|
|
Alias: c.Alias,
|
|
CV: c.CV,
|
|
Role: role,
|
|
ImageID: imageID,
|
|
}
|
|
}
|
|
r := model.Resource{
|
|
Title: params.Title,
|
|
AlternativeTitles: params.AlternativeTitles,
|
|
Article: params.Article,
|
|
Links: params.Links,
|
|
Images: images,
|
|
Tags: tags,
|
|
UserID: uid,
|
|
Gallery: gallery,
|
|
GalleryNsfw: nsfw,
|
|
Characters: characters,
|
|
}
|
|
if r, err = dao.CreateResource(r); err != nil {
|
|
return 0, err
|
|
}
|
|
err = updateCachedTagList()
|
|
if err != nil {
|
|
log.Error("Error updating cached tag list:", err)
|
|
}
|
|
err = dao.AddNewResourceActivity(uid, r.ID)
|
|
if err != nil {
|
|
log.Error("AddNewResourceActivity error: ", err)
|
|
}
|
|
if err := search.AddResourceToIndex(r); err != nil {
|
|
log.Error("AddResourceToIndex error: ", err)
|
|
}
|
|
return r.ID, nil
|
|
}
|
|
|
|
func findRelatedResources(r model.Resource, host string) []model.ResourceView {
|
|
lines := strings.Split(r.Article, "\n")
|
|
var relatedResources []model.ResourceView
|
|
for _, line := range lines {
|
|
r := parseResourceIfPresent(line, host)
|
|
if r != nil {
|
|
relatedResources = append(relatedResources, *r)
|
|
}
|
|
}
|
|
return relatedResources
|
|
}
|
|
|
|
func parseResourceIfPresent(line string, host string) *model.ResourceView {
|
|
if len(line) < 4 {
|
|
return nil
|
|
}
|
|
if !strings.HasPrefix(line, "[") || !strings.HasSuffix(line, ")") {
|
|
return nil
|
|
}
|
|
if !strings.Contains(line, "](") {
|
|
return nil
|
|
}
|
|
splites := strings.Split(line, "(")
|
|
if len(splites) != 2 {
|
|
return nil
|
|
}
|
|
u := strings.TrimSuffix(splites[1], ")")
|
|
u = strings.TrimSpace(u)
|
|
parsed, err := url.Parse(u)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if parsed.IsAbs() && parsed.Hostname() != host {
|
|
return nil
|
|
}
|
|
path := parsed.Path
|
|
if !strings.HasPrefix(path, "/resources/") {
|
|
return nil
|
|
}
|
|
idStr := strings.TrimPrefix(path, "/resources/")
|
|
id, err := strconv.Atoi(idStr)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
r, err := dao.GetResourceByID(uint(id))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
v := r.ToView()
|
|
return &v
|
|
}
|
|
|
|
func GetResource(id uint, host string, ctx fiber.Ctx) (*model.ResourceDetailView, error) {
|
|
r, err := dao.GetResourceByID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ctx != nil && ctx.Locals("real_user") == true {
|
|
err = dao.AddResourceViewCount(id)
|
|
if err != nil {
|
|
log.Error("AddResourceViewCount error: ", err)
|
|
}
|
|
}
|
|
v := r.ToDetailView()
|
|
if host != "" {
|
|
related := findRelatedResources(r, host)
|
|
v.Related = related
|
|
}
|
|
return &v, nil
|
|
}
|
|
|
|
func GetResourceList(page int, sort model.RSort) ([]model.ResourceView, int, error) {
|
|
resources, totalPages, err := dao.GetResourceList(page, pageSize, sort)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
var views []model.ResourceView
|
|
for _, r := range resources {
|
|
views = append(views, r.ToView())
|
|
}
|
|
return views, totalPages, nil
|
|
}
|
|
|
|
// splitQuery splits the input query string into keywords, treating quoted substrings (single or double quotes)
|
|
// as single keywords and supporting escape characters for quotes. Spaces outside quotes are used as separators.
|
|
func splitQuery(query string) []string {
|
|
var keywords []string
|
|
|
|
query = strings.TrimSpace(query)
|
|
if query == "" {
|
|
return keywords
|
|
}
|
|
|
|
l, r := 0, 0
|
|
inQuote := false
|
|
quoteChar := byte(0)
|
|
|
|
for r < len(query) {
|
|
if (query[r] == '"' || query[r] == '\'') && (r == 0 || query[r-1] != '\\') {
|
|
if !inQuote {
|
|
inQuote = true
|
|
quoteChar = query[r]
|
|
l = r + 1
|
|
} else if query[r] == quoteChar {
|
|
if r > l {
|
|
keywords = append(keywords, strings.TrimSpace(query[l:r]))
|
|
}
|
|
inQuote = false
|
|
r++
|
|
l = r
|
|
continue
|
|
}
|
|
} else if !inQuote && query[r] == ' ' {
|
|
if r > l {
|
|
keywords = append(keywords, strings.TrimSpace(query[l:r]))
|
|
}
|
|
for r < len(query) && query[r] == ' ' {
|
|
r++
|
|
}
|
|
l = r
|
|
continue
|
|
}
|
|
|
|
r++
|
|
}
|
|
|
|
if l < len(query) {
|
|
keywords = append(keywords, strings.TrimSpace(query[l:r]))
|
|
}
|
|
|
|
return keywords
|
|
}
|
|
|
|
func searchWithKeyword(keyword string) ([]uint, error) {
|
|
resources := make([]uint, 0)
|
|
|
|
if len([]rune(keyword)) <= maxTagLength {
|
|
exists, err := dao.ExistsTag(keyword)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if exists {
|
|
t, err := dao.GetTagByName(keyword)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res, err := dao.GetResourcesIdWithTag(t.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resources = append(resources, res...)
|
|
}
|
|
}
|
|
|
|
searchResult, err := search.SearchResource(keyword)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resources = append(resources, searchResult...)
|
|
|
|
return resources, nil
|
|
}
|
|
|
|
func SearchResource(query string, page int) ([]model.ResourceView, int, error) {
|
|
if len([]rune(query)) > maxSearchQueryLength {
|
|
return nil, 0, model.NewRequestError("Search query is too long")
|
|
}
|
|
|
|
start := (page - 1) * pageSize
|
|
end := start + pageSize
|
|
resources := make([]uint, 0)
|
|
|
|
checkTag := func(tag string) error {
|
|
if len([]rune(tag)) > maxTagLength {
|
|
return nil
|
|
}
|
|
exists, err := dao.ExistsTag(tag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists {
|
|
t, err := dao.GetTagByName(tag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res, err := dao.GetResourcesIdWithTag(t.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resources = append(resources, res...)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// check tag
|
|
if err := checkTag(query); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// check tag after removing spaces
|
|
trimmed := utils.RemoveSpaces(query)
|
|
if trimmed != query {
|
|
if err := checkTag(trimmed); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
}
|
|
|
|
// split query to search
|
|
keywords := splitQuery(query)
|
|
var temp []uint
|
|
haveTag := false
|
|
for _, keyword := range keywords {
|
|
if len([]rune(keyword)) <= maxTagLength {
|
|
exists, err := dao.ExistsTag(keyword)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if exists {
|
|
haveTag = true
|
|
}
|
|
}
|
|
}
|
|
if haveTag {
|
|
first := true
|
|
for _, keyword := range keywords {
|
|
if keyword == "" {
|
|
continue
|
|
}
|
|
if utils.OnlyPunctuation(keyword) {
|
|
continue
|
|
}
|
|
|
|
res, err := searchWithKeyword(keyword)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
if len(res) == 0 && search.IsStopWord(keyword) {
|
|
continue
|
|
}
|
|
if first {
|
|
temp = utils.RemoveDuplicate(res)
|
|
first = false
|
|
} else {
|
|
temp1 := make([]uint, 0)
|
|
for _, id := range temp {
|
|
for _, id2 := range res {
|
|
if id == id2 {
|
|
temp1 = append(temp1, id)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
temp = temp1
|
|
}
|
|
}
|
|
} else {
|
|
res, err := searchWithKeyword(query)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
temp = res
|
|
}
|
|
resources = append(resources, temp...)
|
|
resources = utils.RemoveDuplicate(resources)
|
|
|
|
if start >= len(resources) {
|
|
return []model.ResourceView{}, 0, nil
|
|
}
|
|
|
|
total := len(resources)
|
|
totalPages := (total + pageSize - 1) / pageSize
|
|
if start >= total {
|
|
return []model.ResourceView{}, totalPages, nil
|
|
}
|
|
if end > total {
|
|
end = total
|
|
}
|
|
idsPage := resources[start:end]
|
|
|
|
resourcesPage, err := dao.BatchGetResources(idsPage)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
var views []model.ResourceView
|
|
for _, r := range resourcesPage {
|
|
views = append(views, r.ToView())
|
|
}
|
|
return views, totalPages, nil
|
|
}
|
|
|
|
func DeleteResource(uid, id uint) error {
|
|
isAdmin, err := CheckUserIsAdmin(uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isAdmin {
|
|
r, err := dao.GetResourceByID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if r.UserID != uid {
|
|
return model.NewUnAuthorizedError("You have not permission to delete this resource")
|
|
}
|
|
}
|
|
r, err := GetResource(id, "", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(r.Files) > 0 {
|
|
return model.NewRequestError("This resource has files, please delete them first")
|
|
}
|
|
if err := dao.DeleteResource(id); err != nil {
|
|
return err
|
|
}
|
|
err = updateCachedTagList()
|
|
if err != nil {
|
|
log.Error("Error updating cached tag list:", err)
|
|
}
|
|
if err := search.RemoveResourceFromIndex(id); err != nil {
|
|
log.Error("RemoveResourceFromIndex error: ", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetResourcesWithTag(tag string, page int) ([]model.ResourceView, int, error) {
|
|
t, err := dao.GetTagByName(tag)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
tagID := t.ID
|
|
resources, totalPages, err := dao.GetResourceByTag(tagID, page, pageSize)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
var views []model.ResourceView
|
|
for _, r := range resources {
|
|
views = append(views, r.ToView())
|
|
}
|
|
return views, totalPages, nil
|
|
}
|
|
|
|
func GetResourcesWithUser(username string, page int) ([]model.ResourceView, int, error) {
|
|
resources, totalPages, err := dao.GetResourcesByUsername(username, page, pageSize)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
var views []model.ResourceView
|
|
for _, r := range resources {
|
|
views = append(views, r.ToView())
|
|
}
|
|
return views, totalPages, nil
|
|
}
|
|
|
|
func UpdateResource(uid, rid uint, params *ResourceParams) error {
|
|
isAdmin, err := checkUserCanUpload(uid)
|
|
if err != nil {
|
|
log.Error("checkUserCanUpload error: ", err)
|
|
return model.NewInternalServerError("Failed to check user permission")
|
|
}
|
|
r, err := dao.GetResourceByID(rid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if r.UserID != uid && !isAdmin {
|
|
return model.NewUnAuthorizedError("You have not permission to edit this resource")
|
|
}
|
|
|
|
gallery := make([]uint, 0, len(params.Gallery))
|
|
for _, id := range params.Gallery {
|
|
if slices.Contains(params.Images, id) {
|
|
gallery = append(gallery, id)
|
|
}
|
|
}
|
|
nsfw := make([]uint, 0, len(params.GalleryNsfw))
|
|
for _, id := range params.GalleryNsfw {
|
|
if slices.Contains(gallery, id) {
|
|
nsfw = append(nsfw, id)
|
|
}
|
|
}
|
|
characters := make([]model.Character, len(params.Characters))
|
|
for i, c := range params.Characters {
|
|
role := c.Role
|
|
if role == "" {
|
|
role = "primary"
|
|
}
|
|
var imageID *uint
|
|
if c.Image != 0 {
|
|
imageID = &c.Image
|
|
}
|
|
characters[i] = model.Character{
|
|
Name: c.Name,
|
|
Alias: c.Alias,
|
|
CV: c.CV,
|
|
Role: role,
|
|
ImageID: imageID,
|
|
}
|
|
}
|
|
|
|
r.Title = params.Title
|
|
r.AlternativeTitles = params.AlternativeTitles
|
|
r.Article = params.Article
|
|
r.Links = params.Links
|
|
r.Gallery = gallery
|
|
r.GalleryNsfw = nsfw
|
|
r.Characters = characters
|
|
|
|
images := make([]model.Image, len(params.Images))
|
|
for i, id := range params.Images {
|
|
images[i] = model.Image{
|
|
Model: gorm.Model{
|
|
ID: id,
|
|
},
|
|
}
|
|
}
|
|
tags := make([]model.Tag, len(params.Tags))
|
|
for i, id := range params.Tags {
|
|
tags[i] = model.Tag{
|
|
Model: gorm.Model{
|
|
ID: id,
|
|
},
|
|
}
|
|
}
|
|
r.Images = images
|
|
r.Tags = tags
|
|
if err := dao.UpdateResource(r); err != nil {
|
|
log.Error("UpdateResource error: ", err)
|
|
return model.NewInternalServerError("Failed to update resource")
|
|
}
|
|
err = updateCachedTagList()
|
|
if err != nil {
|
|
log.Error("Error updating cached tag list:", err)
|
|
}
|
|
err = dao.AddUpdateResourceActivity(uid, r.ID)
|
|
if err != nil {
|
|
log.Error("AddUpdateResourceActivity error: ", err)
|
|
}
|
|
if err := search.AddResourceToIndex(r); err != nil {
|
|
log.Error("AddResourceToIndex error: ", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func RandomResource(host string) (*model.ResourceDetailView, error) {
|
|
r, err := dao.RandomResource()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v := r.ToDetailView()
|
|
if host != "" {
|
|
related := findRelatedResources(r, host)
|
|
v.Related = related
|
|
}
|
|
return &v, nil
|
|
}
|
|
|
|
var lastSuccessCover uint
|
|
|
|
func RandomCover() (uint, error) {
|
|
for retries := 0; retries < 5; retries++ {
|
|
v, err := dao.RandomResource()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(v.Images) > 0 {
|
|
lastSuccessCover = v.Images[0].ID
|
|
return v.Images[0].ID, nil
|
|
}
|
|
}
|
|
return lastSuccessCover, nil
|
|
}
|
|
|
|
func GetPinnedResources() ([]model.ResourceView, error) {
|
|
ids := config.PinnedResources()
|
|
var views []model.ResourceView
|
|
for _, id := range ids {
|
|
r, err := dao.GetResourceByID(id)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
views = append(views, r.ToView())
|
|
}
|
|
return views, nil
|
|
}
|
|
|
|
func GetCharactersFromVndb(vnID string, uid uint) ([]CharacterParams, error) {
|
|
canUpload, err := checkUserCanUpload(uid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !canUpload {
|
|
return nil, model.NewUnAuthorizedError("You have not permission to fetch characters from VNDB")
|
|
}
|
|
|
|
client := http.Client{}
|
|
jsonStr := fmt.Sprintf(`
|
|
{
|
|
"filters": ["id", "=", "%s"],
|
|
"fields": "va.character.name, va.staff.name, va.staff.original, va.character.original, va.character.image.url, va.character.vns.role"
|
|
}
|
|
`, vnID)
|
|
jsonStr = strings.TrimSpace(jsonStr)
|
|
reader := strings.NewReader(jsonStr)
|
|
resp, err := client.Post("https://api.vndb.org/kana/vn", "application/json", reader)
|
|
if err != nil {
|
|
return nil, model.NewInternalServerError("Failed to fetch data from VNDB")
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, model.NewInternalServerError("Failed to fetch data from VNDB")
|
|
}
|
|
// 定义 VNDB API 响应结构
|
|
type VndbResponse struct {
|
|
Results []struct {
|
|
ID string `json:"id"`
|
|
VA []struct {
|
|
Character struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Original string `json:"original"`
|
|
Image struct {
|
|
URL string `json:"url"`
|
|
} `json:"image"`
|
|
VNS []struct {
|
|
ID string `json:"id"`
|
|
Role string `json:"role"`
|
|
} `json:"vns"`
|
|
} `json:"character"`
|
|
Staff struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Original string `json:"original"`
|
|
} `json:"staff"`
|
|
} `json:"va"`
|
|
} `json:"results"`
|
|
}
|
|
|
|
// 解析响应
|
|
var vndbResp VndbResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&vndbResp); err != nil {
|
|
return nil, model.NewInternalServerError("Failed to parse VNDB response")
|
|
}
|
|
|
|
if len(vndbResp.Results) == 0 {
|
|
return []CharacterParams{}, nil
|
|
}
|
|
|
|
result := vndbResp.Results[0]
|
|
var characters []CharacterParams
|
|
processedCharacters := make(map[string]bool) // 避免重复角色
|
|
|
|
// 遍历声优信息
|
|
for _, va := range result.VA {
|
|
role := "Unknown"
|
|
for _, vn := range va.Character.VNS {
|
|
if vn.ID == vnID {
|
|
role = vn.Role
|
|
break
|
|
}
|
|
}
|
|
|
|
if role != "primary" && role != "side" && role != "main" {
|
|
continue
|
|
}
|
|
|
|
// 避免重复角色
|
|
if processedCharacters[va.Character.ID] {
|
|
continue
|
|
}
|
|
processedCharacters[va.Character.ID] = true
|
|
|
|
// 优先使用 original 字段作为角色名,如果没有则使用 name
|
|
characterName := strings.ReplaceAll(va.Character.Original, " ", "")
|
|
if characterName == "" {
|
|
characterName = va.Character.Name
|
|
}
|
|
if characterName == "" {
|
|
continue // 跳过没有名字的角色
|
|
}
|
|
|
|
// 使用 original 字段作为声优名,如果没有则使用 name
|
|
cvName := strings.ReplaceAll(va.Staff.Original, " ", "")
|
|
if cvName == "" {
|
|
cvName = va.Staff.Name
|
|
}
|
|
|
|
character := CharacterParams{
|
|
Name: characterName,
|
|
Alias: []string{},
|
|
CV: cvName,
|
|
Role: role,
|
|
Image: 0, // 默认值,下面会下载图片
|
|
}
|
|
|
|
// 下载并保存角色图片
|
|
if va.Character.Image.URL != "" {
|
|
imageID, err := downloadAndCreateImage(va.Character.Image.URL)
|
|
if err != nil {
|
|
log.Error("Failed to download character image:", err)
|
|
// 继续处理,即使图片下载失败
|
|
} else {
|
|
character.Image = imageID
|
|
}
|
|
}
|
|
|
|
characters = append(characters, character)
|
|
}
|
|
|
|
return characters, nil
|
|
}
|
|
|
|
// downloadAndCreateImage 下载图片并使用 CreateImage 保存
|
|
func downloadAndCreateImage(imageURL string) (uint, error) {
|
|
// 创建 HTTP 客户端
|
|
client := &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
}
|
|
|
|
// 下载图片
|
|
resp, err := client.Get(imageURL)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to download image: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return 0, fmt.Errorf("failed to download image: HTTP %d", resp.StatusCode)
|
|
}
|
|
|
|
// 读取图片数据
|
|
imageData, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to read image data: %w", err)
|
|
}
|
|
|
|
// 限制图片大小,防止内存溢出
|
|
if len(imageData) > 8*1024*1024 { // 8MB 限制
|
|
return 0, fmt.Errorf("image too large")
|
|
}
|
|
|
|
// 使用系统用户ID (假设为1) 创建图片
|
|
// 注意:这里使用系统账户,实际使用时可能需要调整
|
|
imageID, err := CreateImage(1, "127.0.0.1", imageData)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to create image: %w", err)
|
|
}
|
|
|
|
return imageID, nil
|
|
}
|
|
|
|
// UpdateCharacterImage 更新角色的图片ID
|
|
func UpdateCharacterImage(uid, resourceID, characterID, imageID uint) error {
|
|
// 检查资源是否存在并且用户有权限修改
|
|
resource, err := dao.GetResourceByID(resourceID)
|
|
if err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
return model.NewNotFoundError("Resource not found")
|
|
}
|
|
return err
|
|
}
|
|
|
|
isAdmin, err := CheckUserIsAdmin(uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 检查用户是否有权限修改这个资源
|
|
if resource.UserID != uid && !isAdmin {
|
|
return model.NewUnAuthorizedError("You don't have permission to modify this resource")
|
|
}
|
|
|
|
// 更新角色图片
|
|
err = dao.UpdateCharacterImage(characterID, imageID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetLowResolutionCharacters 获取低清晰度的角色图片
|
|
func GetLowResolutionCharacters(page int, pageSize int, maxWidth, maxHeight int) ([]model.LowResCharacterView, int, error) {
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
if pageSize <= 0 {
|
|
pageSize = 50 // 默认每页50个角色
|
|
}
|
|
if pageSize > 1000 {
|
|
pageSize = 1000 // 限制最大页面大小
|
|
}
|
|
|
|
offset := (page - 1) * pageSize
|
|
|
|
// 获取角色列表
|
|
characters, err := dao.GetLowResolutionCharacters(maxWidth, maxHeight, pageSize, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// 获取总数
|
|
totalCount, err := dao.GetLowResolutionCharactersCount(maxWidth, maxHeight)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
totalPages := int((totalCount + int64(pageSize) - 1) / int64(pageSize))
|
|
|
|
return characters, totalPages, nil
|
|
}
|
|
|
|
// GetLowResolutionResourceImages 获取低清晰度的资源图片
|
|
func GetLowResolutionResourceImages(page int, pageSize int, maxWidth, maxHeight int) ([]model.LowResResourceImageView, int, error) {
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
if pageSize <= 0 {
|
|
pageSize = 50 // 默认每页50个图片
|
|
}
|
|
if pageSize > 1000 {
|
|
pageSize = 1000 // 限制最大页面大小
|
|
}
|
|
|
|
offset := (page - 1) * pageSize
|
|
|
|
// 获取资源图片列表
|
|
images, err := dao.GetLowResolutionResourceImages(maxWidth, maxHeight, pageSize, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// 获取总数
|
|
totalCount, err := dao.GetLowResolutionResourceImagesCount(maxWidth, maxHeight)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
totalPages := int((totalCount + int64(pageSize) - 1) / int64(pageSize))
|
|
|
|
return images, totalPages, nil
|
|
}
|
|
|
|
// UpdateResourceImage 更新资源图片
|
|
func UpdateResourceImage(uid, resourceID, oldImageID, newImageID uint) error {
|
|
// 首先检查用户权限 - 确保用户是资源的所有者或管理员
|
|
resource, err := dao.GetResourceByID(resourceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
isAdmin, err := CheckUserIsAdmin(uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if resource.UserID != uid && !isAdmin {
|
|
return model.NewUnAuthorizedError("You don't have permission to update this resource")
|
|
}
|
|
|
|
// 更新资源图片
|
|
return dao.UpdateResourceImage(resourceID, oldImageID, newImageID)
|
|
}
|