package api import ( "fmt" "log" "reflect" "strings" "git.satori.love/gameui/webp/models" "github.com/graphql-go/graphql" "github.com/graphql-go/graphql/language/ast" "github.com/jmoiron/sqlx" "github.com/mitchellh/mapstructure" ) // 自动生成 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) { // 打开数据库连接 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) } var user, article, text *graphql.Object // 用户的可选字段 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.Float, 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: "文章标题"}, "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: "文章更新时间"}, }, }) 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: "分类文章数量"}, }, }) fmt.Println(category) // 图像中的文字提取 text = 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: "文字坐标"}, }, }) // 图像的可选字段 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: "图像更新时间"}, "article": &graphql.Field{Type: article, Description: "图像所属文章"}, "text": &graphql.Field{ Type: graphql.NewList(text), 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 { fmt.Println("san", text.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, ok := field.(*ast.Field) if ok && fieldAST.Name.Value == "list" { for _, field := range fieldAST.SelectionSet.Selections { fieldAST, ok := field.(*ast.Field) if ok { if fieldAST.Name.Value == "user" { fields = append(fields, "user_id") continue } if fieldAST.Name.Value == "article" { fields = append(fields, "article_id") continue } if fieldAST.Name.Value == "similars" { // 跳过自定义字段 continue } fields = append(fields, fieldAST.Name.Value) } } } } return fields } 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 category Category mapstructure.Decode(p.Args, &category) // 获取翻页参数 var first, last int var after, before string if p.Args["first"] != nil { first = p.Args["first"].(int) } if p.Args["last"] != nil { last = p.Args["last"].(int) } if p.Args["after"] != nil { after = p.Args["after"].(string) } if p.Args["before"] != nil { before = p.Args["before"].(string) } fmt.Println(first, last, after, before) // 获取请求的字段 fields := strings.Join(get_fields(p.Info.FieldASTs[0].SelectionSet.Selections), ",") // 筛选条件 where_str := "" // 查询分类列表 var query strings.Builder query.WriteString(fmt.Sprintf("SELECT %s FROM web_article %s LIMIT %d OFFSET %d", fields, where_str, 10, 0)) if err := connection.Select(&categorys, query.String()); err != nil { fmt.Println("查询分类列表失败", err) 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{ "preference": &graphql.ArgumentConfig{Type: graphql.String, Description: "使用浏览记录获取的偏好推荐图像"}, "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: "筛选图像中指定高度的"}, "content": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定内容的"}, "remark": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定备注的"}, "description": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定描述的"}, "tags": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定标签的"}, "rank": &graphql.ArgumentConfig{Type: graphql.String, Description: "筛选图像中含有指定等级的"}, "text": &graphql.ArgumentConfig{Type: graphql.String, 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的"}, "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 args struct { First int Last int After string Before string Text string Preference string Similar int } mapstructure.Decode(p.Args, &args) // 参数到 SQL 格式字符串的映射 var argToSQLFormat = map[string]string{ "id": "id=%d", "width": "width=%d", "height": "height=%d", "content": "content='%s'", "remark": "remark='%s'", "description": "description='%s'", "tags": "tags='%s'", "rank": "rank='%s'", "comment_num": "comment_num=%d", "praise_count": "praise_count=%d", "collect_count": "collect_count=%d", "article_id": "article_id=%d", "user_id": "user_id=%d", "create_time": "create_time='%s'", "update_time": "update_time='%s'", } // 筛选条件 var where []string var order []string for arg, format := range argToSQLFormat { if p.Args[arg] != nil { where = append(where, fmt.Sprintf(format, p.Args[arg])) } } var id_list []string // 特殊处理 preference 参数 if args.Preference != "" { // 去除空格并拆分以逗号分割的ID id_list = strings.Split(strings.ReplaceAll(args.Preference, " ", ""), ",") // 使用这一组 id 推荐 fmt.Println("preference:", args.Preference) } // 特殊处理 similar 参数 if args.Similar != 0 { id_list := models.GetSimilarImagesIdList(args.Similar, 200) ids_str := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(id_list)), ","), "[]") if ids_str == "" { return map[string]interface{}{"list": []Image{}, "total": 0}, nil } where = append(where, fmt.Sprintf("id IN (%s)", ids_str)) order = append(order, fmt.Sprintf("ORDER BY FIELD(id,%s)", ids_str)) } // 特殊处理 text 参数 if args.Text != "" { resp, err := models.ZincSearch(map[string]interface{}{ "query": map[string]interface{}{ "bool": map[string]interface{}{ "must": []map[string]interface{}{ { "query_string": map[string]string{"query": "text:" + args.Text}, }, }, }, }, "sort": []string{ "_score", }, "from": 0, "size": 200, }) if err != nil { fmt.Println("ZincSearch 获取图像列表失败", err) return nil, err } id_list = resp.ToIDList(args.First, args.Last, args.After, args.Before) id_list_str := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(id_list)), ","), "[]") if id_list_str == "" { return map[string]interface{}{ "list": []Image{}, "total": 0, }, nil } where = append(where, fmt.Sprintf("id IN (%s)", id_list_str)) } where_str := strings.Join(where, " AND ") order_str := strings.Join(order, "") if where_str != "" { where_str = "WHERE " + where_str } // 处理翻页参数 var limit, offset int if args.First == 0 && args.Last == 0 { limit = 10 offset = 0 } if args.First != 0 { limit = args.First offset = 0 } if args.Last != 0 { limit = args.Last offset = len(id_list) - limit } // 执行查询 var query strings.Builder fields := strings.Join(get_fields(p.Info.FieldASTs[0].SelectionSet.Selections), ",") query.WriteString(fmt.Sprintf("SELECT %s FROM web_images %s %s LIMIT %d OFFSET %d", fields, where_str, order_str, limit, offset)) var images ImageList var q = query.String() if err := connection.Select(&images, q); err != nil { fmt.Println("获取图像列表失败", err) return nil, err } // 获取用户信息(如果图像列表不为空且请求字段中包含user) if len(images) > 0 && strings.Contains(fields, "user") { user_ids_str := images.ToAllUserID().ToString() var users []User if err := connection.Select(&users, fmt.Sprintf("SELECT id,user_name,avatar,rank,create_time,update_time FROM web_member WHERE id IN (%s)", user_ids_str)); err != nil { fmt.Println("获取用户列表失败", err) return nil, err } // 将用户信息与图像信息关联 images.SetUser(users) } // 获取文章信息(如果图像列表不为空且请求字段中包含article) if len(images) > 0 && strings.Contains(fields, "article") { article_ids_str := images.ToAllArticleID().ToString() var articles []Article if err := connection.Select(&articles, fmt.Sprintf("SELECT id,title,tags,create_time,update_time FROM web_article WHERE id IN (%s)", article_ids_str)); err != nil { fmt.Println("获取文章列表失败", err) return nil, err } // 将文章信息与图像信息关联 images.SetArticle(articles) } return map[string]interface{}{ "list": images, "total": 0, }, 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 }, }, }})}) if err != nil { return schema, err } return schema, nil }