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 }, }, }, }) 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: "筛选用户中含有指定用户名的"}, "avatar": &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: "筛选用户中更新时间等于指定值的"}, "text": &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.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 }, }, "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")) } // 如果没有外部排序则使用指定排序(正则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")) } // 筛选:时间段 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), ), )) } } // 取所有数据的前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 } //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 LIMIT %d `, sql, cursor, limit) //fmt.Println("cursor:", 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) { //fmt.Println(index, item) 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 }, }, "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: "筛选文章中更新时间等于指定值的"}, }, Resolve: func(p graphql.ResolveParams) (interface{}, error) { first := 10 // p.Args["first"] after := 0 // p.Args["after"] fields := strings.Join(get_fields(p.Info.FieldASTs[0].SelectionSet.Selections), ",") var where []string if p.Args["id"] != nil { where = append(where, fmt.Sprintf("id=%d", p.Args["id"])) } if p.Args["title"] != nil { where = append(where, fmt.Sprintf("title='%s'", p.Args["title"])) } // 筛选条件 where_str := strings.Join(where, " AND ") if where_str != "" { where_str = "WHERE " + where_str } var query strings.Builder query.WriteString(fmt.Sprintf("SELECT %s FROM web_article %s LIMIT %d OFFSET %d", fields, where_str, first, after)) // 返回翻页信息 var articles []Article if err := connection.Select(&articles, query.String()); err != nil { fmt.Println("获取文章列表失败", err) return nil, err } return map[string]interface{}{ "list": articles, "total": 0, }, 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 }