API分页

This commit is contained in:
2024-12-02 06:53:52 +08:00
parent 520e3bade6
commit f4bec98b7a
10 changed files with 1385 additions and 1446 deletions

158
api/article.go Normal file
View File

@@ -0,0 +1,158 @@
package api
import (
"fmt"
"log"
"regexp"
"time"
"github.com/doug-martin/goqu/v9"
"github.com/graphql-go/graphql"
)
type Article struct {
ID int `json:"id" db:"id" gorm:"primaryKey"`
Title string `json:"title" db:"title"`
Orientation string `json:"orientation" db:"orientation"`
Device string `json:"device" db:"device"`
Era string `json:"era" db:"era"`
Tags string `json:"tags" db:"tags"`
UserId int `json:"user_id" db:"user_id"`
User User `json:"user" gorm:"foreignKey:UserId"`
CreateTime time.Time `json:"create_time" db:"create_time"`
UpdateTime time.Time `json:"update_time" db:"update_time"`
}
func (Article) TableName() string {
return "web_article"
}
var articleType = graphql.NewObject(graphql.ObjectConfig{
Name: "Article",
Description: "文章",
Fields: graphql.Fields{
"id": &graphql.Field{Type: graphql.Int, Description: "ID"},
"title": &graphql.Field{Type: graphql.String, Description: "标题"},
"orientation": &graphql.Field{Type: graphql.String, Description: "方向"},
"device": &graphql.Field{Type: graphql.String, Description: "设备"},
"era": &graphql.Field{Type: graphql.String, Description: "游戏上线年份"},
"tags": &graphql.Field{Type: graphql.String, Description: "标签"},
"user": &graphql.Field{Type: userType, Description: "所属用户"},
"create_time": &graphql.Field{Type: graphql.DateTime, Description: "创建时间"},
"update_time": &graphql.Field{Type: graphql.DateTime, Description: "更新时间"},
"text_count": &graphql.Field{Type: graphql.Int, Description: "文字数量", Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var count int64
err := db.Table("web_images").Where("article_id = ?", p.Source.(Article).ID).Where("text != ''").Count(&count).Error
return int(count), err
}},
},
})
var ArticleItem = &graphql.Field{
Name: "article",
Description: "单篇文章",
Type: articleType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "根据ID获取文章"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
article := Article{ID: p.Args["id"].(int)}
if err := db.First(&article).Error; err != nil {
log.Println("获取文章失败", err)
return nil, err
}
return article, nil
},
}
var ArticleItems = &graphql.Field{
Name: "articles",
Description: "文章列表",
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "ArticleConnection",
Description: "条件筛选文章列表",
Fields: graphql.Fields{
"list": &graphql.Field{Type: graphql.NewList(articleType), Description: "文章列表"},
"total": &graphql.Field{Type: graphql.Int, Description: "文章总数"},
},
}),
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选文章中指定ID的"},
"title": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选文章中含有指定标题的"},
"tags": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选文章中含有指定标签的"},
"create_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "筛选文章中创建时间等于指定值的"},
"update_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "筛选文章中更新时间等于指定值的"},
"sort": &graphql.ArgumentConfig{Type: graphql.String, Description: "按指定字段排序游戏", DefaultValue: "id"},
"order": &graphql.ArgumentConfig{Type: orderType, Description: "排序类型(升序或降序)", DefaultValue: "ASC"},
"first": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的前n個元素)"},
"last": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的最後n個元素)"},
"after": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之後的元素)"},
"before": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之前的元素)"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var articles []Article
var total int
var err error
var query = goqu.Dialect("mysql").From("web_article")
// 筛选条件
for _, format := range []string{"id", "style", "device", "orientation", "era", "category_id", "tags"} {
if p.Args[format] != nil {
query = query.Where(goqu.C(format).Eq(p.Args[format]))
}
}
// 排序条件
if p.Args["sort"] != nil {
if p.Args["order"].(string) == "ASC" {
query = query.Order(goqu.C(p.Args["sort"].(string)).Asc())
}
if p.Args["order"].(string) == "DESC" {
query = query.Order(goqu.C(p.Args["sort"].(string)).Desc())
}
}
// 如果没有外部排序则使用指定排序(正则sort只能是字母数字下划下)
if p.Args["text"] == nil && p.Args["similar"] == nil && p.Args["interest"] == nil {
sort := regexp.MustCompile(`[^a-zA-Z0-9_]`).ReplaceAllString(p.Args["sort"].(string), "")
query = query.Select("web_images.id", goqu.L(
fmt.Sprintf("ROW_NUMBER() OVER(ORDER BY web_images.%s %s)", sort, p.Args["order"]),
).As("row_num"))
}
// 取所有数据的前N条
sql, _, _ := query.Where(goqu.Ex{"article_category_top_id": 9}).ToSQL()
fmt.Println(sql)
// 遊標截取篩選結果集的前N条
var cursor string
if p.Args["after"] != nil {
cursor = fmt.Sprintf(`WHERE row_num > (SELECT row_num FROM RankedArticles WHERE RankedArticles.id = %d)`, p.Args["after"].(int))
}
var limit int = 10
if p.Args["first"] != nil {
limit = p.Args["first"].(int)
} else if p.Args["last"] != nil {
limit = p.Args["last"].(int)
}
sql = fmt.Sprintf(`
WITH RankedArticles AS (%s)
SELECT * FROM web_images INNER JOIN(
SELECT id, row_num FROM RankedArticles %s
) AS LimitedRanked ON LimitedRanked.id = web_images.id
ORDER BY LimitedRanked.row_num ASC LIMIT %d
`, sql, cursor, limit)
if err := db.Limit(limit).Where("category_top_id = 9").Find(&articles).Error; err != nil {
return nil, err
}
return map[string]interface{}{
"list": articles,
"total": total,
}, err
},
}

65
api/collect.go Normal file
View File

@@ -0,0 +1,65 @@
package api
import (
"time"
"github.com/graphql-go/graphql"
)
type Collect struct {
ID int `json:"id" gorm:"primaryKey"`
UserId int `json:"user_id"`
ArticleId int `json:"article_id"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
func (Collect) TableName() string {
return "web_collect"
}
var CollectItems = &graphql.Field{
Name: "collects",
Description: "收藏列表",
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "CollectConnection",
Description: "条件筛选收藏列表",
Fields: graphql.Fields{
"list": &graphql.Field{Type: graphql.NewList(graphql.NewObject(graphql.ObjectConfig{
Name: "Collect",
Description: "收藏",
Fields: graphql.Fields{
"id": &graphql.Field{Type: graphql.Int, Description: "收藏ID"},
"title": &graphql.Field{Type: graphql.String, Description: "收藏标题"},
"type": &graphql.Field{Type: graphql.Int, Description: "收藏类型"},
"create_time": &graphql.Field{Type: graphql.DateTime, Description: "收藏创建时间"},
"update_time": &graphql.Field{Type: graphql.DateTime, Description: "收藏更新时间"},
"praise_count": &graphql.Field{Type: graphql.Int, Description: "点赞数"},
"collect_count": &graphql.Field{Type: graphql.Int, Description: "收藏数"},
"praise": &graphql.Field{Type: graphql.Boolean, Description: "当前用户是否点赞"},
"collect": &graphql.Field{Type: graphql.Boolean, Description: "当前用户是否收藏"},
},
})), Description: "收藏列表"},
"total": &graphql.Field{Type: graphql.Int, Description: "收藏总数"},
},
}),
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选收藏中指定ID的"},
"title": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选收藏中含有指定标题的"},
"type": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选收藏中含有指定类型的"},
"create_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "筛选收藏中创建时间等于指定值的"},
"update_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "筛选收藏中更新时间等于指定值的"},
"first": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的前n個元素)"},
"last": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的最後n個元素)"},
"after": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之後的元素)"},
"before": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之前的元素)"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var collects []Collect
var total int
return map[string]interface{}{
"list": collects,
"total": total,
}, nil
},
}

156
api/game.go Normal file
View File

@@ -0,0 +1,156 @@
package api
import (
"fmt"
"regexp"
"strings"
"time"
"github.com/doug-martin/goqu/v9"
"github.com/graphql-go/graphql"
)
type Game struct {
ID int `json:"id" gorm:"primaryKey"`
Title string `json:"title"`
Orientation string `json:"orientation"`
Device string `json:"device"`
Era string `json:"era"`
Tags string `json:"tags"`
UserId int `json:"user_id"`
Content string `json:"content"`
Image string `json:"image"`
Images string `json:"images"`
User User `json:"user" gorm:"foreignKey:UserId"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
func (Game) TableName() string {
return "web_article"
}
var gameType = graphql.NewObject(graphql.ObjectConfig{
Name: "Game",
Description: "游戏",
Fields: graphql.Fields{
"id": &graphql.Field{Type: graphql.Int, Description: "游戏ID"},
"title": &graphql.Field{Type: graphql.String, Description: "游戏标题"},
"era": &graphql.Field{Type: graphql.String, Description: "游戏上线年份"},
"create_time": &graphql.Field{Type: graphql.DateTime, Description: "游戏创建时间"},
"update_time": &graphql.Field{Type: graphql.DateTime, Description: "游戏更新时间"},
"praise_count": &graphql.Field{Type: graphql.Int, Description: "点赞数"},
"collect_count": &graphql.Field{Type: graphql.Int, Description: "收藏数"},
"praise": &graphql.Field{Type: graphql.Boolean, Description: "当前用户是否点赞"},
"collect": &graphql.Field{Type: graphql.Boolean, Description: "当前用户是否收藏"},
},
})
var GameItems = &graphql.Field{
Name: "games",
Description: "游戏列表",
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "GameConnection",
Description: "条件筛选游戏列表",
Fields: graphql.Fields{
"list": &graphql.Field{Type: graphql.NewList(gameType), Description: "游戏列表"},
"total": &graphql.Field{Type: graphql.Int, Description: "游戏总数"},
},
}),
Args: graphql.FieldConfigArgument{
"style": &graphql.ArgumentConfig{Type: graphql.String, Description: "按风格筛选游戏"},
"device": &graphql.ArgumentConfig{Type: graphql.String, Description: "按平台筛选游戏"},
"orientation": &graphql.ArgumentConfig{Type: graphql.String, Description: "按屏幕方向筛选游戏"},
"era": &graphql.ArgumentConfig{Type: graphql.String, Description: "按年份筛选游戏"},
"category_id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "按分类ID筛选游戏"},
"tags": &graphql.ArgumentConfig{Type: graphql.String, Description: "按标签筛选游戏"},
"user_id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "按用户ID筛选游戏"},
"id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选游戏中指定ID的"},
"title": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选游戏中含有指定标题的"},
"rank": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选游戏中含有指定排名的"},
"create_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "按创建时间筛选游戏"},
"update_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "按修改时间筛选游戏"},
"sort": &graphql.ArgumentConfig{Type: graphql.String, Description: "按指定字段排序游戏", DefaultValue: "id"},
"order": &graphql.ArgumentConfig{Type: orderType, Description: "排序类型(升序或降序)", DefaultValue: "ASC"},
"first": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的前n個元素)"},
"last": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的最後n個元素)"},
"after": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之後的元素)"},
"before": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之前的元素)"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var games []Game
var total int
var err error
var query = goqu.Dialect("mysql").From("web_article").Where(goqu.Ex{"category_top_id": 22})
// 筛选条件
for _, format := range []string{"id", "title", "style", "device", "orientation", "era", "category_id", "tags"} {
if p.Args[format] != nil {
query = query.Where(goqu.C(format).Eq(p.Args[format]))
}
}
// 如果查询了 total 字段
if existField(p.Info.FieldASTs[0].SelectionSet.Selections, "total") {
sql, _, _ := query.ToSQL()
sql = strings.Replace(sql, "SELECT *", "SELECT COUNT(*)", 1)
if err := db.Raw(sql).Scan(&total).Error; err != nil {
return nil, err
}
}
// 如果没有外部排序则使用指定排序(正则sort只能是字母数字下划下)
if p.Args["text"] == nil && p.Args["similar"] == nil && p.Args["interest"] == nil {
sort := regexp.MustCompile(`[^a-zA-Z0-9_]`).ReplaceAllString(p.Args["sort"].(string), "")
query = query.Select("web_article.id", goqu.L(
fmt.Sprintf("ROW_NUMBER() OVER(ORDER BY web_article.%s %s)", sort, p.Args["order"]),
).As("row_num"))
} else {
// 排序条件
if p.Args["sort"] != nil {
if p.Args["order"].(string) == "ASC" {
query = query.Order(goqu.C(p.Args["sort"].(string)).Asc())
}
if p.Args["order"].(string) == "DESC" {
query = query.Order(goqu.C(p.Args["sort"].(string)).Desc())
}
}
}
// 取所有数据的前N条
sql, _, _ := query.ToSQL()
fmt.Println(sql)
// 遊標截取篩選結果集的前N条
var cursor string
if p.Args["after"] != nil {
cursor = fmt.Sprintf(`WHERE row_num > (SELECT row_num FROM RankedArticles WHERE RankedArticles.id = %d)`, p.Args["after"].(int))
}
var limit int = 10
if p.Args["first"] != nil {
limit = p.Args["first"].(int)
} else if p.Args["last"] != nil {
limit = p.Args["last"].(int)
}
sql = fmt.Sprintf(`
WITH RankedArticles AS (%s)
SELECT * FROM web_article INNER JOIN(
SELECT id, row_num FROM RankedArticles %s
) AS LimitedRanked ON LimitedRanked.id = web_article.id
ORDER BY LimitedRanked.row_num ASC LIMIT %d
`, sql, cursor, limit)
if err := db.Raw(sql).Scan(&games).Error; err != nil {
fmt.Println("获取游戏列表失败", err)
return nil, err
}
return map[string]interface{}{
"list": games,
"total": total,
}, err
},
}

View File

@@ -12,7 +12,7 @@ import (
var gorse *client.GorseClient var gorse *client.GorseClient
func gorseInit(host string, port int) { func GorseInit(host string, port int) {
gorse = client.NewGorseClient(fmt.Sprintf("http://%s:%d", host, port), "") gorse = client.NewGorseClient(fmt.Sprintf("http://%s:%d", host, port), "")
} }

File diff suppressed because it is too large Load Diff

626
api/image.go Normal file
View File

@@ -0,0 +1,626 @@
package api
import (
"context"
"database/sql/driver"
"encoding/json"
"fmt"
"log"
"regexp"
"strconv"
"strings"
"time"
"git.satori.love/gameui/webp/models"
"github.com/doug-martin/goqu/v9"
"github.com/graphql-go/graphql"
"github.com/mitchellh/mapstructure"
"github.com/zhenghaoz/gorse/client"
)
type Image struct {
ID int `json:"id" db:"id" gorm:"primaryKey"`
Width int `json:"width" db:"width"`
Height int `json:"height" db:"height"`
Content string `json:"content" db:"content"`
Remark string `json:"remark" db:"remark"`
Description string `json:"description" db:"description"`
Tags string `json:"tags" db:"tags"`
Rank string `json:"rank" db:"rank"`
CommentNum int `json:"comment_num" db:"comment_num"`
PraiseCount int `json:"praise_count" gorm:"column:praise"`
CollectCount int `json:"collect_count" db:"collect_count"`
ArticleID int `json:"article_id" db:"article_id"`
UserID int `json:"user_id" db:"user_id"`
CreateTime time.Time `json:"create_time" db:"create_time"`
UpdateTime time.Time `json:"update_time" db:"update_time"`
Text TextList `json:"text" db:"text" gorm:"type:json"`
User User `json:"user" gorm:"foreignKey:UserID"`
Article Article `json:"article" gorm:"foreignKey:ArticleID"`
Activity bool `json:"activity"`
}
func (Image) TableName() string {
return "web_images"
}
type TextList []struct {
Text string `json:"text"`
Confidence float64 `json:"confidence"`
Coordinate [][]float64 `json:"coordinate"`
}
func (a *TextList) Scan(value interface{}) error {
// 如果数据库中的值为NULL则返回nil
if value == nil || len(value.([]byte)) == 0 {
*a = TextList{}
return nil
}
return json.Unmarshal(value.([]byte), a)
}
func (a TextList) Value() (driver.Value, error) {
return json.Marshal(a)
}
// 图像的可选字段
var imageType = graphql.NewObject(graphql.ObjectConfig{
Name: "Image",
Description: "图像",
Fields: graphql.Fields{
"id": &graphql.Field{Type: graphql.Int, Description: "图像ID"},
"width": &graphql.Field{Type: graphql.Int, Description: "图像宽度"},
"height": &graphql.Field{Type: graphql.Int, Description: "图像高度"},
"content": &graphql.Field{Type: graphql.String, Description: "图像内容"},
"remark": &graphql.Field{Type: graphql.String, Description: "图像备注"},
"description": &graphql.Field{Type: graphql.String, Description: "图像描述"},
"tags": &graphql.Field{Type: graphql.String, Description: "图像标签"},
"rank": &graphql.Field{Type: graphql.String, Description: "图像等级"},
"comment_num": &graphql.Field{Type: graphql.Int, Description: "评论数"},
"article_category_top_id": &graphql.Field{Type: graphql.Int, Description: "文章分类顶级ID"},
"praise_count": &graphql.Field{Type: graphql.Int, Description: "点赞数"},
"collect_count": &graphql.Field{Type: graphql.Int, Description: "收藏数"},
"create_time": &graphql.Field{Type: graphql.DateTime, Description: "图像创建时间"},
"update_time": &graphql.Field{Type: graphql.DateTime, Description: "图像更新时间"},
"activity": &graphql.Field{Type: graphql.Boolean, Description: "图像活动"},
"article": &graphql.Field{Type: articleType, Description: "图像所属文章"},
"praise": &graphql.Field{Type: graphql.Boolean, Description: "当前用户是否点赞", Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var user_id = p.Context.Value("user_id").(int)
if user_id != 0 {
var praise int64
if err := db.Table("web_praise").Where("user_id = ?", user_id).Where("praise_id = ?", p.Source.(Image).ID).Where("type = ?", 4).Count(&praise); err != nil {
return false, nil
}
if praise > 0 {
return true, nil
}
}
return false, nil
}},
"collect": &graphql.Field{Type: graphql.Boolean, Description: "当前用户是否收藏", Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var user_id = p.Context.Value("user_id").(int)
if user_id != 0 {
var collect int64
if err := db.Table("web_collect").Where("user_id = ?", user_id).Where("image_id = ?", p.Source.(Image).ID).Count(&collect); err != nil {
return false, nil
}
if collect > 0 {
return true, nil
}
}
return false, nil
}},
"collect_id": &graphql.Field{Type: graphql.Int, Description: "当前用户收藏ID", Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var user_id = p.Context.Value("user_id").(int)
if user_id != 0 {
var collect_id int
if err := db.Table("web_collect").Where("user_id = ?", user_id).Where("image_id = ?", p.Source.(Image).ID).First(&collect_id); err != nil {
return nil, nil
}
return collect_id, nil
}
return nil, nil
}},
"text": &graphql.Field{
Type: graphql.NewList(graphql.NewObject(graphql.ObjectConfig{
Name: "Text",
Description: "图像中的文字提取",
Fields: graphql.Fields{
"text": &graphql.Field{Type: graphql.String, Description: "文字内容"},
"confidence": &graphql.Field{Type: graphql.Float, Description: "置信度"},
"coordinate": &graphql.Field{Type: &graphql.List{OfType: graphql.NewList(graphql.Float)}, Description: "文字坐标"},
},
})),
Description: "图像中的文字",
Args: graphql.FieldConfigArgument{
"text": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选含有指定文字的列"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
if p.Args["text"] != nil {
var texts TextList
for _, text := range p.Source.(Image).Text {
if strings.Contains(text.Text, p.Args["text"].(string)) {
texts = append(texts, text)
}
}
return texts, nil
}
return p.Source.(Image).Text, nil
},
},
//"user": &graphql.Field{Type: userType, Description: "图像所属用户"},
},
})
//image.AddFieldConfig("user", &graphql.Field{Type: userType, Description: "图像所属用户"})
//image.AddFieldConfig("similars", &graphql.Field{Type: graphql.NewList(image), Description: "相似的图像", Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// return []Image{}, nil
//}})
var ImageItem = &graphql.Field{
Name: "image",
Description: "单张图片",
Type: imageType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "根据ID获取图片"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
img := Image{ID: p.Args["id"].(int)}
query := db.Limit(1)
for index, item := range LoadItem(p.Info.FieldASTs[0].SelectionSet.Selections) {
fmt.Println(index, item)
query = query.Preload(item)
}
if err := query.First(&img).Error; err != nil {
log.Println("获取图片失败", err)
return nil, err
}
return img, nil
},
}
var ImageItems = &graphql.Field{
Name: "images",
Description: "图像列表",
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "ImageConnection",
Description: "条件筛选图像列表",
Fields: graphql.Fields{
"list": &graphql.Field{Type: graphql.NewList(imageType), Description: "图像列表"},
"total": &graphql.Field{Type: graphql.Int, Description: "图像总数"},
},
}),
Args: graphql.FieldConfigArgument{
"activity": &graphql.ArgumentConfig{Type: graphql.Boolean, Description: "按是否活动筛选图像"},
"style": &graphql.ArgumentConfig{Type: graphql.String, Description: "按游戏风格筛选图像"},
"device": &graphql.ArgumentConfig{Type: graphql.String, Description: "按游戏平台筛选图像"},
"orientation": &graphql.ArgumentConfig{Type: graphql.String, Description: "按游戏版式筛选图像"},
"era": &graphql.ArgumentConfig{Type: graphql.String, Description: "按游戏年份筛选图像"},
"category_id": &graphql.ArgumentConfig{Type: graphql.String, Description: "按游戏类型筛选图像(支持多选)"},
"tags": &graphql.ArgumentConfig{Type: graphql.String, Description: "按游戏标签筛选图像(支持多选)"},
"images_desc": &graphql.ArgumentConfig{Type: graphql.String, Description: "按游戏归类筛选图像(支持多选)"},
"article_tags": &graphql.ArgumentConfig{Type: graphql.String, Description: "按文章标签筛选图像(支持多选)"},
"color": &graphql.ArgumentConfig{Type: graphql.String, Description: "按主色调筛选图像, 使用十六进制颜色代码(#FF1414)"},
"explorer_id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中指定收藏夹的"},
"collect_id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中指定收藏夹的"},
"collect": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中指定用户收藏过的"},
"praise": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中指定用户点赞过的"},
"follower": &graphql.ArgumentConfig{Type: graphql.Int, Description: "获取指定ID用户的关注列表发布的图像"},
"interest": &graphql.ArgumentConfig{Type: graphql.Int, Description: "获取指定ID用户的兴趣推荐图像"},
"similar": &graphql.ArgumentConfig{Type: graphql.Int, Description: "获取与指定ID图像相似的图像"},
"id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "获取指定ID的图像"},
"width": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中指定宽度的"},
"height": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中指定高度的"},
"comment_num": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中评论数等于指定值的"},
"praise_count": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中点赞数等于指定值的"},
"collect_count": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中收藏数等于指定值的"},
"article_id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中属于指定文章ID的"},
"user_id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选图像中属于指定用户ID的"},
"sort": &graphql.ArgumentConfig{Type: graphql.String, Description: "排序方法", DefaultValue: "id"},
"content": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定内容的"},
"remark": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定备注的"},
"description": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定描述的"},
"rank": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定排名的"},
"text": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定文字的"},
"create_time": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中创建时间等于指定值的"},
"update_time": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中更新时间等于指定值的"},
"first": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的前n個元素)"},
"last": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的最後n個元素)"},
"after": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中指定遊標之後的元素)"},
"before": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中指定遊標之前的元素)"},
"order": &graphql.ArgumentConfig{Type: orderType, Description: "排序方向", DefaultValue: "ASC"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
// 定义参数结构体
var args struct {
First int
Last int
After int
Before int
Text string
Interest int
Similar int
Sort string
Order string
}
mapstructure.Decode(p.Args, &args)
var total int
var images []Image
var query = goqu.Dialect("mysql").From("web_images")
// 参数映射
var argFormat = []string{"id", "width", "height", "content", "remark", "description", "rank", "comment_num", "praise_count", "collect_count", "article_id", "user_id"}
// 筛选条件
for _, format := range argFormat {
if p.Args[format] != nil {
fmt.Println(format, p.Args[format])
query = query.Where(goqu.Ex{fmt.Sprintf("web_images.%s", format): p.Args[format]})
}
}
// 筛选:提取文字
if p.Args["text"] != nil {
resp, err := models.ZincSearch(map[string]interface{}{
"query": map[string]interface{}{
"bool": map[string]interface{}{
"must": []map[string]interface{}{
{
"match_phrase": map[string]string{"text": args.Text},
},
},
},
},
"_source": false,
"sort": []string{"_score"},
"size": 200000,
})
if err != nil {
fmt.Println("ZincSearch 获取图像列表失败", err)
return nil, err
}
var item []int
for _, hit := range resp.Hits.Hits {
num, _ := strconv.Atoi(hit.ID)
item = append(item, num)
}
if len(item) == 0 {
return map[string]interface{}{"list": []Image{}, "total": 0}, nil
}
query = query.Where(goqu.Ex{"web_images.id": goqu.Op{"in": item}})
total = len(item)
}
// 筛选:相似图像
if p.Args["similar"] != nil {
var item []int
for _, id := range models.GetSimilarImagesIdList(p.Args["similar"].(int), 200) {
item = append(item, int(id))
}
if len(item) == 0 {
return map[string]interface{}{"list": []Image{}, "total": 0}, nil
}
query = query.Where(goqu.Ex{"web_images.id": goqu.Op{"in": item}}).Select("web_images.id", goqu.L(
fmt.Sprintf("ROW_NUMBER() OVER(ORDER BY FIELD(%s, %s))", "web_images.id", regexp.MustCompile(`[\[\]]`).ReplaceAllString(strings.Join(strings.Fields(fmt.Sprint(item)), ", "), "")),
).As("row_num"))
total = len(item)
// 收集阅读行为
if p.Context.Value("user_id") != nil && p.Args["after"] == nil {
ctx := context.Background()
user_id := p.Context.Value("user_id").(int)
feedbacks := []client.Feedback{{
FeedbackType: "read",
UserId: fmt.Sprintf("%s", user_id),
ItemId: fmt.Sprintf("%s", p.Args["Similar"]),
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
}}
if _, err := gorse.InsertFeedback(ctx, feedbacks); err != nil {
fmt.Println("写入阅读记录失败", err)
}
}
}
// 筛选:兴趣推荐
if p.Args["interest"] != nil {
fmt.Println("interest:", p.Args["interest"])
user_id := p.Args["interest"].(int)
fmt.Println("interest1:", user_id)
list, err := GetRecommend(user_id, []string{})
fmt.Println("interest2:", list, err)
if err != nil {
fmt.Println("GetRecommend 获取兴趣推荐失败", err)
return map[string]interface{}{"list": []Image{}, "total": 0}, nil
}
if len(list) == 0 {
return map[string]interface{}{"list": []Image{}, "total": 0}, nil
}
fmt.Println("Interest:", user_id, list)
query = query.Where(goqu.Ex{"web_images.id": goqu.Op{"in": list}}).Select("web_images.id", goqu.L(
fmt.Sprintf("ROW_NUMBER() OVER(ORDER BY FIELD(%s, %s))", "web_images.id", regexp.MustCompile(`[\[\]]`).ReplaceAllString(strings.Join(strings.Fields(fmt.Sprint(list)), ", "), "")),
).As("row_num"))
total = len(list)
}
// 筛选:时间段
applyTimeCondition := func(name string, str string) {
parts := strings.Split(str, "-")
query = query.Where(goqu.Ex{fmt.Sprintf("YEAR(%s)", name): parts[0]})
if len(parts) > 1 {
query = query.Where(goqu.Ex{fmt.Sprintf("MONTH(%s)", name): parts[1]})
}
if len(parts) > 2 {
query = query.Where(goqu.Ex{fmt.Sprintf("DAY(%s)", name): parts[2]})
}
}
// 数据库中筛选:创建时间段
if p.Args["create_time"] != nil {
applyTimeCondition("create_time", p.Args["create_time"].(string))
}
// 数据库中筛选:更新时间段
if p.Args["update_time"] != nil {
applyTimeCondition("update_time", p.Args["update_time"].(string))
}
// 数据库中筛选:按是否活动
if activity, ok := p.Args["activity"].(bool); ok {
query = query.Where(goqu.Ex{"web_images.activity": map[bool]string{true: "1", false: "0"}[activity]})
}
// 数据库中筛选:按游戏标签
if p.Args["tags"] != nil {
tags := strings.Split(strings.ReplaceAll(p.Args["tags"].(string), " ", ""), ",")
for _, tag := range tags {
query = query.Where(goqu.L("MATCH(web_images.tags) AGAINST (? IN NATURAL LANGUAGE MODE)", tag))
}
}
// 数据库中筛选:按游戏分类
if p.Args["images_desc"] != nil {
tags := strings.Split(strings.ReplaceAll(p.Args["images_desc"].(string), " ", ""), ",")
for _, tag := range tags {
query = query.Where(goqu.L("MATCH(web_images.images_desc) AGAINST (? IN NATURAL LANGUAGE MODE)", tag))
}
}
// 数据库中筛选:喜欢的截图
if p.Args["praise"] != nil {
query = query.Join(goqu.T("web_praise"), goqu.On(
goqu.I("web_images.id").Eq(goqu.I("web_praise.praise_id")),
goqu.I("web_praise.user_id").Eq(p.Args["praise"]),
goqu.I("web_praise.type").Eq(4),
))
}
var collect_params []goqu.Expression = []goqu.Expression{
goqu.I("web_images.id").Eq(goqu.I("web_collect.image_id")),
}
// 数据库中筛选:用户收藏的截图
if p.Args["collect"] != nil {
collect_params = append(collect_params, goqu.I("web_collect.user_id").Eq(p.Args["collect"]))
}
// 数据库中筛选:收藏夹中的截图
if p.Args["collect_id"] != nil {
collect_params = append(collect_params, goqu.I("web_collect.collect_id").Eq(p.Args["collect_id"]))
}
// 数据库中筛选:收藏夹中的截图
if p.Args["explorer_id"] != nil {
collect_params = append(collect_params, goqu.I("web_collect.explorer_id").Eq(p.Args["explorer_id"]))
}
if len(collect_params) > 1 {
query = query.Join(goqu.T("web_collect"), goqu.On(collect_params...))
}
var conditions []goqu.Expression = []goqu.Expression{
goqu.I("web_images.article_id").Eq(goqu.I("web_article.id")),
}
// 按游戏风格筛选图像
if p.Args["style"] != nil {
conditions = append(conditions, goqu.I("web_article.style").Eq(p.Args["style"]))
}
// 按游戏平台筛选图像
if p.Args["device"] != nil {
conditions = append(conditions, goqu.I("web_article.device").Eq(p.Args["device"]))
}
// 按游戏版式筛选图像
if p.Args["orientation"] != nil {
conditions = append(conditions, goqu.I("web_article.orientation").Eq(p.Args["orientation"]))
}
// 按游戏年份筛选图像
if p.Args["era"] != nil {
conditions = append(conditions, goqu.I("web_article.era").Eq(p.Args["era"]))
}
// 按游戏类型筛选图像(逗号分割且去除空格)
if p.Args["category_id"] != nil {
category_ids := strings.Split(strings.ReplaceAll(p.Args["category_id"].(string), " ", ""), ",")
conditions = append(conditions, goqu.I("web_article.category_id").In(category_ids))
}
// 按游戏标签筛选图像
if p.Args["article_tags"] != nil {
tags := strings.Split(strings.ReplaceAll(p.Args["article_tags"].(string), " ", ""), ", ")
for _, tag := range tags {
conditions = append(conditions, goqu.L("MATCH(web_article.tags) AGAINST (? IN NATURAL LANGUAGE MODE)", tag))
}
}
if len(conditions) > 1 {
query = query.Join(goqu.T("web_article"), goqu.On(conditions...))
}
// 数据库中筛选:按关注列表
if p.Args["follower"] != nil {
query = query.Join(goqu.T("web_fans"), goqu.On(
goqu.I("web_images.user_id").Eq(goqu.I("web_fans.blogger_id")),
goqu.I("web_fans.follower_id").Eq(p.Args["follower"]),
))
}
// 数据库中筛选: 按图像主色调颜色筛选
if p.Args["color"] != nil {
// hexToRGB 将十六进制颜色转换为 RGB
hexToRGB := func(hex string) (int, int, int, error) {
// 去掉 # 号
if strings.HasPrefix(hex, "#") {
hex = hex[1:]
}
// 检查是否是有效的十六进制颜色
if len(hex) != 6 {
return 0, 0, 0, fmt.Errorf("invalid hex color")
}
// 解析红色、绿色、蓝色通道的十六进制值
r, err := strconv.ParseInt(hex[:2], 16, 0)
if err != nil {
return 0, 0, 0, err
}
g, err := strconv.ParseInt(hex[2:4], 16, 0)
if err != nil {
return 0, 0, 0, err
}
b, err := strconv.ParseInt(hex[4:], 16, 0)
if err != nil {
return 0, 0, 0, err
}
return int(r), int(g), int(b), nil
}
// 逗号分割且去除空格
colors := strings.Split(strings.ReplaceAll(p.Args["color"].(string), " ", ""), ",")
for index, color := range colors {
var precision int = 10
if strings.Contains(color, ":") {
re := regexp.MustCompile(`^#([0-9a-fA-F]{6}):(\d+)$`)
matches := re.FindStringSubmatch(color)
num, err := strconv.Atoi(matches[2])
if err != nil {
fmt.Println("数字精度转换失败:", err)
return nil, err
}
color = "#" + matches[1]
precision = num
}
r, g, b, err := hexToRGB(color)
if err != nil {
fmt.Println("hexToRGB", index, err)
return nil, err
}
fmt.Println(color, r, g, b, precision)
query = query.Where(goqu.Or(
goqu.And(
goqu.L(fmt.Sprintf("web_images.color_%d_r", 0)).Gt(r-precision),
goqu.L(fmt.Sprintf("web_images.color_%d_r", 0)).Lt(r+precision),
goqu.L(fmt.Sprintf("web_images.color_%d_g", 0)).Gt(g-precision),
goqu.L(fmt.Sprintf("web_images.color_%d_g", 0)).Lt(g+precision),
goqu.L(fmt.Sprintf("web_images.color_%d_b", 0)).Gt(b-precision),
goqu.L(fmt.Sprintf("web_images.color_%d_b", 0)).Lt(b+precision),
),
goqu.And(
goqu.L(fmt.Sprintf("web_images.color_%d_r", 1)).Gt(r-precision),
goqu.L(fmt.Sprintf("web_images.color_%d_r", 1)).Lt(r+precision),
goqu.L(fmt.Sprintf("web_images.color_%d_g", 1)).Gt(g-precision),
goqu.L(fmt.Sprintf("web_images.color_%d_g", 1)).Lt(g+precision),
goqu.L(fmt.Sprintf("web_images.color_%d_b", 1)).Gt(b-precision),
goqu.L(fmt.Sprintf("web_images.color_%d_b", 1)).Lt(b+precision),
),
))
}
}
// 如果没有外部排序则使用指定排序(正则sort只能是字母数字下划线)
if p.Args["similar"] == nil && p.Args["interest"] == nil && p.Args["praise"] == nil {
// 如果查询了 total 字段
if existField(p.Info.FieldASTs[0].SelectionSet.Selections, "total") {
sql, _, _ := query.ToSQL()
sql = strings.Replace(sql, "SELECT *", "SELECT COUNT(*)", 1)
if err := db.Raw(sql).Scan(&total).Error; err != nil {
return nil, err
}
}
query = query.Select("web_images.id", goqu.L(fmt.Sprintf(
"ROW_NUMBER() OVER(ORDER BY web_images.%s %s)",
regexp.MustCompile(`[^a-zA-Z0-9_]`).ReplaceAllString(p.Args["sort"].(string), ""),
p.Args["order"].(string),
)).As("row_num"))
}
// 图像按点赞顺序排序 p.Args["sort"].(string) == "praise_time"
if p.Args["praise"] != nil {
query = query.Select("web_images.id", goqu.L(
fmt.Sprintf("ROW_NUMBER() OVER(ORDER BY web_praise.%s %s)", "id", p.Args["order"].(string)),
).As("row_num"))
}
// 取所有数据的前N条
sql, _, _ := query.Where(goqu.Ex{"article_category_top_id": 22}).ToSQL()
// 遊標截取篩選結果集的前N条
var cursor string
if p.Args["after"] != nil {
cursor = fmt.Sprintf(`WHERE row_num > (SELECT row_num FROM RankedArticles WHERE RankedArticles.id = %d)`, p.Args["after"].(int))
}
var limit int = 10
if args.First != 0 {
limit = args.First
} else if args.Last != 0 {
limit = args.Last
}
sql = fmt.Sprintf(`
WITH RankedArticles AS (%s)
SELECT web_images.* FROM web_images INNER JOIN(
SELECT id, row_num FROM RankedArticles %s
) AS LimitedRanked ON LimitedRanked.id = web_images.id
ORDER BY LimitedRanked.row_num ASC LIMIT %d
`, sql, cursor, limit)
fmt.Println(sql)
if err := db.Raw(sql).Scan(&images).Error; err != nil {
fmt.Println("获取图像列表失败", err)
return nil, err
}
var ids []int
for _, item := range images {
ids = append(ids, item.ID)
}
var find = db.Where("web_images.id IN ?", ids).Select("*", "CASE WHEN activity = '1' THEN TRUE ELSE FALSE END")
for _, item := range LoadItem(p.Info.FieldASTs[0].SelectionSet.Selections) {
find = find.Preload(item)
}
if len(ids) == 0 {
return map[string]interface{}{"list": []Image{}, "total": 0}, nil
}
orderClause := fmt.Sprintf("FIELD(id, %s)", regexp.MustCompile(`[\[\]]`).ReplaceAllString(strings.Join(strings.Fields(fmt.Sprint(ids)), ","), ""))
if err := find.Order(orderClause).Find(&images).Error; err != nil {
fmt.Println("获取图像列表失败", err)
return nil, err
}
return map[string]interface{}{
"list": images,
"total": total,
}, nil
},
}

View File

@@ -1,11 +1,9 @@
package api package api
import ( import (
"encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time"
) )
type IDS []int type IDS []int
@@ -54,160 +52,3 @@ func (images *ImageList) ToAllUserID() (uniqueIds IDS) {
} }
return uniqueIds return uniqueIds
} }
type Image struct {
ID int `json:"id" db:"id" gorm:"primaryKey"`
Width int `json:"width" db:"width"`
Height int `json:"height" db:"height"`
Content string `json:"content" db:"content"`
Remark string `json:"remark" db:"remark"`
Description string `json:"description" db:"description"`
Tags string `json:"tags" db:"tags"`
Rank string `json:"rank" db:"rank"`
CommentNum int `json:"comment_num" db:"comment_num"`
PraiseCount int `json:"praise_count" gorm:"column:praise"`
CollectCount int `json:"collect_count" db:"collect_count"`
ArticleID int `json:"article_id" db:"article_id"`
UserID int `json:"user_id" db:"user_id"`
CreateTime time.Time `json:"create_time" db:"create_time"`
UpdateTime time.Time `json:"update_time" db:"update_time"`
Text TextList `json:"text" db:"text" gorm:"type:json"`
User User `json:"user" gorm:"foreignKey:UserID"`
Article Article `json:"article" gorm:"foreignKey:ArticleID"`
Activity bool `json:"activity"`
}
func (Image) TableName() string {
return "web_images"
}
type TextList []struct {
Text string `json:"text"`
Confidence float64 `json:"confidence"`
Coordinate [][]float64 `json:"coordinate"`
}
func (a *TextList) Scan(value interface{}) error {
// 如果数据库中的值为NULL则返回nil
if value == nil || len(value.([]byte)) == 0 {
*a = TextList{}
return nil
}
return json.Unmarshal(value.([]byte), a)
}
type User struct {
ID int `json:"id" db:"id" gorm:"primaryKey"`
UserName string `json:"user_name" db:"user_name"`
Avatar string `json:"avatar" db:"avatar"`
Rank string `json:"rank" db:"rank"`
Price int `json:"price" db:"price"`
CreateTime time.Time `json:"create_time" db:"create_time"`
UpdateTime time.Time `json:"update_time" db:"update_time"`
}
func (User) TableName() string {
return "web_member"
}
type Work struct {
ID int `json:"id" gorm:"primaryKey"`
Title string `json:"title"`
Orientation string `json:"orientation"`
Device string `json:"device"`
Era string `json:"era"`
Tags string `json:"tags"`
UserId int `json:"user_id"`
Content string `json:"content"`
Image string `json:"image"`
Images string `json:"images"`
User User `json:"user" gorm:"foreignKey:UserId"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
func (Work) TableName() string {
return "web_article"
}
type Game struct {
ID int `json:"id" gorm:"primaryKey"`
Title string `json:"title"`
Orientation string `json:"orientation"`
Device string `json:"device"`
Era string `json:"era"`
Tags string `json:"tags"`
UserId int `json:"user_id"`
Content string `json:"content"`
Image string `json:"image"`
Images string `json:"images"`
User User `json:"user" gorm:"foreignKey:UserId"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
func (Game) TableName() string {
return "web_article"
}
type Article struct {
ID int `json:"id" db:"id" gorm:"primaryKey"`
Title string `json:"title" db:"title"`
Orientation string `json:"orientation" db:"orientation"`
Device string `json:"device" db:"device"`
Era string `json:"era" db:"era"`
Tags string `json:"tags" db:"tags"`
UserId int `json:"user_id" db:"user_id"`
User User `json:"user" gorm:"foreignKey:UserId"`
CreateTime time.Time `json:"create_time" db:"create_time"`
UpdateTime time.Time `json:"update_time" db:"update_time"`
}
func (Article) TableName() string {
return "web_article"
}
type Category struct {
ID int `json:"id"`
Title string `json:"title"`
Keyword string `json:"keyword"`
ParentID int `json:"parent_id"`
Status int `json:"status"`
Content string `json:"content"`
Sort int `json:"sort"`
Image string `json:"image"`
ImageNum int `json:"image_num"`
ArticleNum int `json:"article_num"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
// TableName 方法用于自定义表名
func (Category) TableName() string {
return "web_article_category"
}
// 输入配置
type MysqlConfig struct {
Host string
Port int
Database string
UserName string
Password string
}
type Oss struct {
Local bool
}
type GorseConfig struct {
Host string
Port int
Open bool
}
type Config struct {
Mysql MysqlConfig
Gorse GorseConfig
Oss
}

93
api/user.go Normal file
View File

@@ -0,0 +1,93 @@
package api
import (
"time"
"github.com/graphql-go/graphql"
)
type User struct {
ID int `json:"id" db:"id" gorm:"primaryKey"`
UserName string `json:"user_name" db:"user_name"`
Avatar string `json:"avatar" db:"avatar"`
Rank string `json:"rank" db:"rank"`
Price int `json:"price" db:"price"`
CreateTime time.Time `json:"create_time" db:"create_time"`
UpdateTime time.Time `json:"update_time" db:"update_time"`
}
func (User) TableName() string {
return "web_member"
}
var userType = graphql.NewObject(graphql.ObjectConfig{
Name: "User",
Description: "用户信息",
Fields: graphql.Fields{
"id": &graphql.Field{Type: graphql.Int, Description: "用户ID"},
"user_name": &graphql.Field{Type: graphql.String, Description: "用户名"},
"avatar": &graphql.Field{Type: graphql.String, Description: "用户头像"},
"rank": &graphql.Field{Type: graphql.String, Description: "用户等级"},
"price": &graphql.Field{Type: graphql.Int, Description: "用户金币"},
"create_time": &graphql.Field{Type: graphql.DateTime, Description: "用户创建时间"},
"update_time": &graphql.Field{Type: graphql.DateTime, Description: "用户更新时间"},
},
})
var UserItems = &graphql.Field{
Name: "users",
Description: "用户列表",
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "UserConnection",
Description: "条件筛选用户列表",
Fields: graphql.Fields{
"list": &graphql.Field{Type: graphql.NewList(userType), Description: "用户列表"},
"total": &graphql.Field{Type: graphql.Int, Description: "用户总数"},
},
}),
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选用户中指定ID的"},
"user_name": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选用户中含有指定用户名的"},
"create_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "筛选用户中创建时间等于指定值的"},
"update_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "筛选用户中更新时间等于指定值的"},
"first": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的前n個元素)"},
"last": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的最後n個元素)"},
"after": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之後的元素)"},
"before": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之前的元素)"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
//var where []string
//if p.Args["id"] != nil {
// where = append(where, fmt.Sprintf("id=%d", p.Args["id"]))
//}
//if p.Args["user_name"] != nil {
// where = append(where, fmt.Sprintf("user_name='%s'", p.Args["user_name"]))
//}
//// 筛选条件
//where_str := strings.Join(where, " AND ")
//if where_str != "" {
// where_str = "WHERE " + where_str
//}
//var query strings.Builder
var users []User
var total int
//fields := strings.Join(get_fields(p.Info.FieldASTs[0].SelectionSet.Selections), ",")
//query.WriteString(fmt.Sprintf("SELECT %s FROM web_member %s LIMIT %d OFFSET %d", fields, where_str, 10, 0))
//if err := connection.Select(&users, query.String()); err != nil {
// fmt.Println("获取用户列表失败", err)
// return nil, err
//}
//if len(users) > 0 {
// query.Reset()
// query.WriteString(fmt.Sprintf("SELECT COUNT(*) FROM web_member %s", where_str))
// if err := connection.Get(&total, query.String()); err != nil {
// fmt.Println("获取用户总数失败", err)
// return nil, err
// }
//}
return map[string]interface{}{
"list": users,
"total": total,
}, nil
},
}

139
api/work.go Normal file
View File

@@ -0,0 +1,139 @@
package api
import (
"fmt"
"log"
"regexp"
"time"
"github.com/doug-martin/goqu/v9"
"github.com/graphql-go/graphql"
)
type Work struct {
ID int `json:"id" gorm:"primaryKey"`
Title string `json:"title"`
Orientation string `json:"orientation"`
Device string `json:"device"`
Era string `json:"era"`
Tags string `json:"tags"`
UserId int `json:"user_id"`
Content string `json:"content"`
Image string `json:"image"`
Images string `json:"images"`
User User `json:"user" gorm:"foreignKey:UserId"`
CreateTime time.Time `json:"create_time"`
UpdateTime time.Time `json:"update_time"`
}
func (Work) TableName() string {
return "web_article"
}
var workType = graphql.NewObject(graphql.ObjectConfig{
Name: "Work",
Description: "作品",
Fields: graphql.Fields{
"id": &graphql.Field{Type: graphql.Int, Description: "作品ID"},
"title": &graphql.Field{Type: graphql.String, Description: "作品标题"},
"create_time": &graphql.Field{Type: graphql.DateTime, Description: "作品创建时间"},
"update_time": &graphql.Field{Type: graphql.DateTime, Description: "作品更新时间"},
"praise_count": &graphql.Field{Type: graphql.Int, Description: "点赞数"},
"collect_count": &graphql.Field{Type: graphql.Int, Description: "收藏数"},
"praise": &graphql.Field{Type: graphql.Boolean, Description: "当前用户是否点赞"},
"collect": &graphql.Field{Type: graphql.Boolean, Description: "当前用户是否收藏"},
},
})
var WorkItems = &graphql.Field{
Name: "works",
Description: "作品列表",
Type: graphql.NewObject(graphql.ObjectConfig{
Name: "WorkConnection",
Description: "条件筛选作品列表",
Fields: graphql.Fields{
"list": &graphql.Field{Type: graphql.NewList(workType), Description: "作品列表"},
//"total": &graphql.Field{Type: graphql.Int, Description: "作品总数"},
},
}),
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选作品中指定ID的"},
"title": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选作品中含有指定标题的"},
"tags": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选作品中含有指定标签的"},
"create_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "筛选作品中创建时间等于指定值的"},
"update_time": &graphql.ArgumentConfig{Type: graphql.DateTime, Description: "筛选作品中更新时间等于指定值的"},
"sort": &graphql.ArgumentConfig{Type: graphql.String, Description: "按指定字段排序游戏", DefaultValue: "id"},
"order": &graphql.ArgumentConfig{Type: orderType, Description: "排序类型(升序或降序)", DefaultValue: "ASC"},
"first": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的前n個元素)"},
"last": &graphql.ArgumentConfig{Type: graphql.Int, Description: "翻页参数(傳回清單中的最後n個元素)"},
"after": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之後的元素)"},
"before": &graphql.ArgumentConfig{Type: graphql.String, Description: "翻页参数(傳回清單中指定遊標之前的元素)"},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var works []Work
var total int
var err error
var query = goqu.Dialect("mysql").From("web_article")
// 筛选条件
for _, format := range []string{"id", "tags"} {
if p.Args[format] != nil {
query = query.Where(goqu.C(format).Eq(p.Args[format]))
}
}
// 排序条件
if p.Args["sort"] != nil {
if p.Args["order"].(string) == "ASC" {
query = query.Order(goqu.C(p.Args["sort"].(string)).Asc())
}
if p.Args["order"].(string) == "DESC" {
query = query.Order(goqu.C(p.Args["sort"].(string)).Desc())
}
}
// 如果没有外部排序则使用指定排序(正则sort只能是字母数字下划下)
if p.Args["text"] == nil && p.Args["similar"] == nil && p.Args["interest"] == nil {
sort := regexp.MustCompile(`[^a-zA-Z0-9_]`).ReplaceAllString(p.Args["sort"].(string), "")
query = query.Select("web_images.id", goqu.L(
fmt.Sprintf("ROW_NUMBER() OVER(ORDER BY web_images.%s %s)", sort, p.Args["order"]),
).As("row_num"))
}
// 取所有数据的前N条
sql, _, _ := query.Where(goqu.Ex{"article_category_top_id": 1}).ToSQL()
fmt.Println(sql)
// 遊標截取篩選結果集的前N条
var cursor string
if p.Args["after"] != nil {
cursor = fmt.Sprintf(`WHERE row_num > (SELECT row_num FROM RankedArticles WHERE RankedArticles.id = %d)`, p.Args["after"].(int))
}
var limit int = 10
if p.Args["first"] != nil {
limit = p.Args["first"].(int)
} else if p.Args["last"] != nil {
limit = p.Args["last"].(int)
}
sql = fmt.Sprintf(`
WITH RankedArticles AS (%s)
SELECT * FROM web_images INNER JOIN(
SELECT id, row_num FROM RankedArticles %s
) AS LimitedRanked ON LimitedRanked.id = web_images.id
ORDER BY LimitedRanked.row_num ASC LIMIT %d
`, sql, cursor, limit)
if err := db.Limit(limit).Where("category_top_id = 1").Find(&works).Error; err != nil {
log.Println("获取作品列表失败", err)
return nil, err
}
return map[string]interface{}{
"list": works,
"total": total,
}, err
},
}

View File

@@ -19,6 +19,7 @@ import (
"git.satori.love/gameui/webp/api" "git.satori.love/gameui/webp/api"
"git.satori.love/gameui/webp/models" "git.satori.love/gameui/webp/models"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler" "github.com/graphql-go/handler"
"github.com/milvus-io/milvus-sdk-go/v2/entity" "github.com/milvus-io/milvus-sdk-go/v2/entity"
"github.com/spf13/viper" "github.com/spf13/viper"
@@ -229,6 +230,7 @@ func main() {
models.InitConfig(config) models.InitConfig(config)
models.ZincInit() models.ZincInit()
api.InitDefault(config)
mysqlConnection.Init() mysqlConnection.Init()
milvusConnection.Init() milvusConnection.Init()
@@ -238,33 +240,35 @@ func main() {
return return
} }
// graphql Schema
schema, err := api.NewSchema(api.Config{
Mysql: api.MysqlConfig{
Host: config.GetString("mysql.host"),
Port: config.GetInt("mysql.port"),
Database: config.GetString("mysql.database"),
UserName: config.GetString("mysql.user"),
Password: config.GetString("mysql.password"),
},
Oss: api.Oss{
Local: config.GetBool("oss.local"),
},
Gorse: api.GorseConfig{
Host: config.GetString("gorse.host"),
Port: config.GetInt("gorse.port"),
Open: config.GetBool("gorse.open"),
},
})
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志 defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志
w.Write([]byte("Hello World!")) w.Write([]byte("Hello World!"))
}) })
if config.GetBool("oss.local") {
fmt.Println("开启图像色调计算")
go api.CheckColorNullRows(0)
}
if config.GetBool("gorse.open") {
fmt.Println("开启用户偏好收集")
api.GorseInit(config.GetString("gorse.host"), config.GetInt("gorse.port"))
}
schema, err := graphql.NewSchema(graphql.SchemaConfig{Query: graphql.NewObject(graphql.ObjectConfig{Name: "RootQuery", Fields: graphql.Fields{
"users": api.UserItems,
"collects": api.CollectItems,
"games": api.GameItems,
"works": api.WorkItems,
"articles": api.ArticleItems,
"article": api.ArticleItem,
"images": api.ImageItems,
"image": api.ImageItem,
}})})
if err != nil {
log.Fatalf("failed to create new schema, error: %v", err)
}
http.Handle("/api", LogRequest(handler.New(&handler.Config{ http.Handle("/api", LogRequest(handler.New(&handler.Config{
Schema: &schema, Schema: &schema,
Playground: true, Playground: true,
@@ -275,19 +279,6 @@ func main() {
http.HandleFunc("/api/images", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api/images", func(w http.ResponseWriter, r *http.Request) {
defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志 defer LogComponent(time.Now().UnixNano(), r) // 最后打印日志
// 私域: (自己的图片, 自己的文章, 自己的精选集, 点赞收藏精选集)
// 条件查询(模糊搜索, 时间区间, 作者, 标签, 分类, 精选集, 状态, 置顶, 模糊权重)(权重规则:权重指数)
// 条件筛选(交集, 并集, 差集, 子集)
// 文字搜索支持翻页
// 文字搜索支持与按颜色筛选混合
// TODO 查找含有指定文字的图像
// TODO 查找含有指定标签的图像
// TODO 查找含有指定特征的图像
// TODO 查找含有指定颜色的图像(倒排索引)
// TODO 查找含有指定分类的图像
// 1,000,000
// 获取查询条件(忽略空值) // 获取查询条件(忽略空值)
QueryConditions := func(key string) (list []string) { QueryConditions := func(key string) (list []string) {
for _, item := range strings.Split(r.URL.Query().Get(key), ",") { for _, item := range strings.Split(r.URL.Query().Get(key), ",") {