1312 lines
53 KiB
Go
1312 lines
53 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"image"
|
|
"log"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.satori.love/gameui/webp/models"
|
|
"github.com/doug-martin/goqu/v9"
|
|
_ "github.com/doug-martin/goqu/v9/dialect/mysql"
|
|
"github.com/graphql-go/graphql"
|
|
"github.com/graphql-go/graphql/language/ast"
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/mitchellh/mapstructure"
|
|
"github.com/thoas/go-funk"
|
|
"github.com/zhenghaoz/gorse/client"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var db *gorm.DB
|
|
var err error
|
|
|
|
func LoadItem(requestedFields []ast.Selection) (data []string) {
|
|
var items = []string{"user", "article"}
|
|
for _, field := range requestedFields {
|
|
fieldAST, _ := field.(*ast.Field)
|
|
if funk.Contains(items, fieldAST.Name.Value) {
|
|
name := fieldAST.Name.Value
|
|
name = strings.ToUpper(string(name[0])) + name[1:]
|
|
data = append(data, name)
|
|
for _, str := range LoadItem(fieldAST.SelectionSet.Selections) {
|
|
data = append(data, name+"."+str)
|
|
}
|
|
}
|
|
if fieldAST.Name.Value == "list" {
|
|
for _, str := range LoadItem(fieldAST.SelectionSet.Selections) {
|
|
data = append(data, str)
|
|
}
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
// 自动生成 GraphQL 类型的函数
|
|
func generateGraphQLType(model interface{}) (*graphql.Object, error) {
|
|
modelType := reflect.TypeOf(model)
|
|
if modelType.Kind() != reflect.Struct {
|
|
return nil, fmt.Errorf("model must be a struct")
|
|
}
|
|
|
|
fields := graphql.Fields{}
|
|
for i := 0; i < modelType.NumField(); i++ {
|
|
field := modelType.Field(i)
|
|
fieldType := graphql.String // 默认使用字符串类型
|
|
|
|
// 这里可以根据需要添加更多类型映射
|
|
switch field.Type.Kind() {
|
|
case reflect.String:
|
|
fieldType = graphql.String
|
|
case reflect.Int:
|
|
fieldType = graphql.Int
|
|
case reflect.Bool:
|
|
fieldType = graphql.Boolean
|
|
}
|
|
|
|
fields[field.Name] = &graphql.Field{
|
|
Type: fieldType,
|
|
}
|
|
}
|
|
|
|
return graphql.NewObject(graphql.ObjectConfig{
|
|
Name: modelType.Name(),
|
|
Fields: fields,
|
|
}), nil
|
|
}
|
|
|
|
func NewSchema(config Config) (graphql.Schema, error) {
|
|
|
|
if db, err = gorm.Open(mysql.Open(fmt.Sprintf(
|
|
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
|
config.Mysql.UserName,
|
|
config.Mysql.Password,
|
|
config.Mysql.Host,
|
|
config.Mysql.Port,
|
|
config.Mysql.Database,
|
|
)), &gorm.Config{}); err != nil {
|
|
log.Fatal("failed to connect to database:", err)
|
|
}
|
|
|
|
// 打开数据库连接
|
|
connection, err := sqlx.Connect("mysql", fmt.Sprintf(
|
|
"%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
|
config.Mysql.UserName,
|
|
config.Mysql.Password,
|
|
config.Mysql.Host,
|
|
config.Mysql.Port,
|
|
config.Mysql.Database,
|
|
))
|
|
if err != nil {
|
|
log.Fatalln("连接数据库失败", err)
|
|
}
|
|
|
|
// 定时检查补全颜色字段
|
|
checkColorNullRows := func() {
|
|
// 跳过的行数
|
|
offset := 0
|
|
|
|
for {
|
|
time.Sleep(1 * time.Second)
|
|
var list []struct {
|
|
ID int
|
|
Content string
|
|
}
|
|
|
|
fmt.Println("跳过的行数:", offset)
|
|
if err := db.Table("web_images").Select("id", "content").Where("article_category_top_id = 22").Where("color_0_r IS NULL").Offset(offset).Limit(100).Scan(&list).Error; err != nil {
|
|
fmt.Println("定时检查补全颜色字段查询失败", err)
|
|
continue
|
|
}
|
|
for index, item := range list {
|
|
matches := regexp.MustCompile(`^https?://image\.gameuiux\.cn/(.*)`).FindStringSubmatch(item.Content)
|
|
if len(matches) < 2 {
|
|
fmt.Println("转换路径失败", index, item.ID, item.Content)
|
|
continue
|
|
}
|
|
// 打开图像文件
|
|
filepath := "oss/" + matches[1]
|
|
file, err := os.Open(filepath)
|
|
if err != nil {
|
|
fmt.Println("打开文件失败", index, item.ID, item.Content, err)
|
|
offset++
|
|
continue
|
|
}
|
|
defer file.Close()
|
|
|
|
// 解码 JPEG 图像
|
|
img, _, err := image.Decode(file)
|
|
if err != nil {
|
|
fmt.Println("解码图像失败", index, item.ID, item.Content, err)
|
|
continue
|
|
}
|
|
|
|
k := 8
|
|
centers, labels := KMeans(extractColors(img), k)
|
|
|
|
// 将聚类中心和颜色数量结合,并按颜色数量降序排序
|
|
type cluster struct {
|
|
center RGB
|
|
count int
|
|
}
|
|
clusters := make([]cluster, k)
|
|
for i := 0; i < k; i++ {
|
|
clusters[i] = cluster{center: centers[i], count: 0}
|
|
}
|
|
// 统计每个聚类的颜色数量
|
|
for _, label := range labels {
|
|
clusters[label].count++
|
|
}
|
|
// 按颜色数量降序排序
|
|
sort.Slice(clusters, func(i, j int) bool {
|
|
return clusters[i].count > clusters[j].count
|
|
})
|
|
// 返回排序后的聚类中心
|
|
sortedCenters := make([]RGB, k)
|
|
for i, c := range clusters {
|
|
sortedCenters[i] = c.center
|
|
}
|
|
|
|
//fmt.Println("聚类后的颜色数量:", clusters)
|
|
|
|
if err := db.Table("web_images").Where("id = ?", item.ID).Updates(map[string]interface{}{
|
|
"color_0_r": sortedCenters[0].R,
|
|
"color_0_g": sortedCenters[0].G,
|
|
"color_0_b": sortedCenters[0].B,
|
|
"color_1_r": sortedCenters[1].R,
|
|
"color_1_g": sortedCenters[1].G,
|
|
"color_1_b": sortedCenters[1].B,
|
|
}).Error; err != nil {
|
|
fmt.Println("更新颜色字段失败", index, item.ID, item.Content, err)
|
|
continue
|
|
}
|
|
fmt.Println("更新颜色索引:", item.ID, filepath)
|
|
}
|
|
}
|
|
}
|
|
|
|
if config.Oss.Local {
|
|
fmt.Println("开启图像色调计算")
|
|
go checkColorNullRows()
|
|
}
|
|
|
|
// 定时检查点赞收藏记录
|
|
if config.Gorse.Open {
|
|
fmt.Println("开启用户偏好收集")
|
|
gorseInit(config.Gorse.Host, config.Gorse.Port)
|
|
}
|
|
|
|
// 用户的可选字段
|
|
user := 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: "用户更新时间"},
|
|
},
|
|
})
|
|
|
|
// 文章的可选字段
|
|
article := 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: user, 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
|
|
}},
|
|
},
|
|
})
|
|
|
|
// 分类的可选字段
|
|
category := graphql.NewObject(graphql.ObjectConfig{
|
|
Name: "Category",
|
|
Description: "分类",
|
|
Fields: graphql.Fields{
|
|
"id": &graphql.Field{Type: graphql.Int, Description: "分类ID"},
|
|
"title": &graphql.Field{Type: graphql.String, Description: "分类标题"},
|
|
"keyword": &graphql.Field{Type: graphql.String, Description: "分类关键词"},
|
|
"parent_id": &graphql.Field{Type: graphql.Int, Description: "分类父级ID"},
|
|
"create_time": &graphql.Field{Type: graphql.DateTime, Description: "分类创建时间"},
|
|
"update_time": &graphql.Field{Type: graphql.DateTime, Description: "分类更新时间"},
|
|
"status": &graphql.Field{Type: graphql.Int, Description: "分类状态"},
|
|
"content": &graphql.Field{Type: graphql.String, Description: "分类内容"},
|
|
"sort": &graphql.Field{Type: graphql.Int, Description: "分类排序"},
|
|
"image": &graphql.Field{Type: graphql.String, Description: "分类图片"},
|
|
"image_num": &graphql.Field{Type: graphql.Int, Description: "分类图片数量"},
|
|
"article_num": &graphql.Field{Type: graphql.Int, Description: "分类文章数量"},
|
|
},
|
|
})
|
|
|
|
// 图像的可选字段
|
|
image := 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: article, 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
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
game := 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: "游戏更新时间"},
|
|
},
|
|
})
|
|
|
|
work := 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: "作品更新时间"},
|
|
},
|
|
})
|
|
|
|
image.AddFieldConfig("user", &graphql.Field{Type: user, Description: "图像所属用户"})
|
|
image.AddFieldConfig("similars", &graphql.Field{Type: graphql.NewList(image), Description: "相似的图像", Resolve: func(p graphql.ResolveParams) (interface{}, error) {
|
|
return []Image{}, nil
|
|
}})
|
|
|
|
// 将 list 中的字段提取出来用于查询
|
|
get_fields := func(requestedFields []ast.Selection) (fields []string) {
|
|
for _, field := range requestedFields {
|
|
fieldAST, _ := field.(*ast.Field)
|
|
if fieldAST.Name.Value == "list" {
|
|
for _, field := range fieldAST.SelectionSet.Selections {
|
|
fieldAST, _ := field.(*ast.Field)
|
|
if fieldAST.Name.Value == "text_count" {
|
|
continue
|
|
}
|
|
fields = append(fields, fieldAST.Name.Value)
|
|
}
|
|
}
|
|
}
|
|
return fields
|
|
}
|
|
|
|
orderType := graphql.NewEnum(graphql.EnumConfig{
|
|
Name: "OrderType",
|
|
Description: "排序类型",
|
|
Values: graphql.EnumValueConfigMap{
|
|
"ASC": &graphql.EnumValueConfig{
|
|
Value: "ASC",
|
|
Description: "升序",
|
|
},
|
|
"DESC": &graphql.EnumValueConfig{
|
|
Value: "DESC",
|
|
Description: "降序",
|
|
},
|
|
},
|
|
})
|
|
|
|
schema, err := graphql.NewSchema(graphql.SchemaConfig{Query: graphql.NewObject(graphql.ObjectConfig{Name: "RootQuery", Fields: graphql.Fields{
|
|
"categorys": &graphql.Field{
|
|
Name: "categorys",
|
|
Description: "分类列表",
|
|
Type: graphql.NewObject(graphql.ObjectConfig{
|
|
Name: "CategoryConnection",
|
|
Description: "条件筛选分类列表",
|
|
Fields: graphql.Fields{
|
|
"list": &graphql.Field{Type: graphql.NewList(category), 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: "筛选分类中含有指定标题的"},
|
|
"keyword": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选分类中含有指定关键词的"},
|
|
"parent_id": &graphql.ArgumentConfig{Type: graphql.Int, Description: "筛选分类中含有指定父级ID的"},
|
|
"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 categorys []Category
|
|
var total int
|
|
var err error
|
|
|
|
// 获取筛选条件
|
|
var arg struct {
|
|
ID int
|
|
Title string
|
|
Keyword string
|
|
ParentID int
|
|
First int
|
|
Last int
|
|
After string
|
|
Before string
|
|
}
|
|
mapstructure.Decode(p.Args, &arg)
|
|
|
|
var limit int = 10
|
|
if arg.First != 0 {
|
|
limit = arg.First
|
|
} else if arg.Last != 0 {
|
|
limit = arg.Last
|
|
}
|
|
|
|
if err := db.Limit(limit).Where("id > 0").Find(&categorys).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"list": categorys,
|
|
"total": total,
|
|
}, err
|
|
},
|
|
},
|
|
"users": &graphql.Field{
|
|
Name: "users",
|
|
Description: "用户列表",
|
|
Type: graphql.NewObject(graphql.ObjectConfig{
|
|
Name: "UserConnection",
|
|
Description: "条件筛选用户列表",
|
|
Fields: graphql.Fields{
|
|
"list": &graphql.Field{Type: graphql.NewList(user), 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
|
|
},
|
|
},
|
|
"games": &graphql.Field{
|
|
Name: "games",
|
|
Description: "游戏列表",
|
|
Type: graphql.NewObject(graphql.ObjectConfig{
|
|
Name: "GameConnection",
|
|
Description: "条件筛选游戏列表",
|
|
Fields: graphql.Fields{
|
|
"list": &graphql.Field{Type: graphql.NewList(game), 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")
|
|
|
|
// 筛选条件
|
|
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": 22}).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 = 22").Find(&games).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return map[string]interface{}{
|
|
"list": games,
|
|
"total": total,
|
|
}, err
|
|
},
|
|
},
|
|
"works": &graphql.Field{
|
|
Name: "works",
|
|
Description: "作品列表",
|
|
Type: graphql.NewObject(graphql.ObjectConfig{
|
|
Name: "WorkConnection",
|
|
Description: "条件筛选作品列表",
|
|
Fields: graphql.Fields{
|
|
"list": &graphql.Field{Type: graphql.NewList(work), 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
|
|
},
|
|
},
|
|
"articles": &graphql.Field{
|
|
Name: "articles",
|
|
Description: "文章列表",
|
|
Type: graphql.NewObject(graphql.ObjectConfig{
|
|
Name: "ArticleConnection",
|
|
Description: "条件筛选文章列表",
|
|
Fields: graphql.Fields{
|
|
"list": &graphql.Field{Type: graphql.NewList(article), 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
|
|
},
|
|
},
|
|
"images": &graphql.Field{
|
|
Name: "images",
|
|
Description: "图像列表",
|
|
Type: graphql.NewObject(graphql.ObjectConfig{
|
|
Name: "ImageConnection",
|
|
Description: "条件筛选图像列表",
|
|
Fields: graphql.Fields{
|
|
"list": &graphql.Field{Type: graphql.NewList(image), 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.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 args.Text != "" {
|
|
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": 100000,
|
|
})
|
|
|
|
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}}).Select("web_images.id", goqu.L(
|
|
fmt.Sprintf("ROW_NUMBER() OVER(ORDER BY %s %s)", "web_images.id", "DESC"),
|
|
).As("row_num"))
|
|
}
|
|
|
|
// 筛选:相似图像
|
|
if args.Similar != 0 {
|
|
var item []int
|
|
for _, id := range models.GetSimilarImagesIdList(args.Similar, 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"))
|
|
|
|
// 收集阅读行为
|
|
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"))
|
|
}
|
|
|
|
// 筛选:时间段
|
|
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 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["text"] == nil && p.Args["similar"] == nil && p.Args["interest"] == nil {
|
|
sort := regexp.MustCompile(`[^a-zA-Z0-9_]`).ReplaceAllString(p.Args["sort"].(string), "")
|
|
order := regexp.MustCompile(`[^a-zA-Z0-9_]`).ReplaceAllString(p.Args["order"].(string), "")
|
|
query = query.Select("web_images.id", goqu.L(
|
|
fmt.Sprintf("ROW_NUMBER() OVER(ORDER BY web_images.%s %s)", sort, order),
|
|
).As("row_num"))
|
|
}
|
|
|
|
// 取所有数据的前N条
|
|
sql, _, _ := query.Where(goqu.Ex{"article_category_top_id": 22}).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 args.First != 0 {
|
|
limit = args.First
|
|
} else if args.Last != 0 {
|
|
limit = args.Last
|
|
}
|
|
|
|
//fmt.Println("SQL:", sql)
|
|
|
|
// 優化查詢: 如果是第一頁(未傳入遊標), 可以在分頁前(窗口函數)立即截取長度以大幅降低排序選取耗時
|
|
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.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)
|
|
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
|
|
},
|
|
},
|
|
"tags": &graphql.Field{
|
|
Name: "tags",
|
|
Description: "标签列表",
|
|
Type: graphql.NewObject(graphql.ObjectConfig{
|
|
Name: "TagConnection",
|
|
Description: "条件筛选标签列表",
|
|
Fields: graphql.Fields{
|
|
"list": &graphql.Field{Type: graphql.NewList(graphql.String), Description: "标签列表"},
|
|
"total": &graphql.Field{Type: graphql.Int, Description: "标签总数"},
|
|
},
|
|
}),
|
|
Args: graphql.FieldConfigArgument{
|
|
"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 tags []string
|
|
if err := connection.Select(&tags, "SELECT DISTINCT tags FROM web_images LIMIT 10"); err != nil {
|
|
fmt.Println("获取标签列表失败", err)
|
|
return nil, err
|
|
}
|
|
return map[string]interface{}{
|
|
"list": tags,
|
|
"total": 0,
|
|
}, nil
|
|
},
|
|
},
|
|
"image": &graphql.Field{
|
|
Name: "image",
|
|
Description: "单张图片",
|
|
Type: image,
|
|
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
|
|
},
|
|
},
|
|
"article": &graphql.Field{
|
|
Name: "article",
|
|
Description: "单篇文章",
|
|
Type: article,
|
|
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
|
|
},
|
|
},
|
|
}})})
|
|
if err != nil {
|
|
return schema, err
|
|
}
|
|
return schema, nil
|
|
}
|