Node.js,Apollo Server v4,GraphQL,Mongoose - Category Tree

9ceoxa92  于 2024-01-08  发布在  Go
关注(0)|答案(1)|浏览(206)

我正在尝试使用Node.js,Apollo Server v4,GraphQL,Mongoose技术创建类别树。
但是当我想获取创建的类别或子类别数据时,输出不是我想要的。类别既作为引用出现在子类别中,也作为普通类别出现。我还添加了具有此id 655 b105543598 f2 ef 0 ba 0 f69的类别数据,作为具有此id 655 b104 a43598 f2 ef 0 ba 0 f64值的类别的子类别数组中的第2个子类别。我不明白。但是虽然名字不出现空。
GraphQL查询请求;

  1. query Query {
  2. getCategories {
  3. success
  4. response_code
  5. message
  6. categories {
  7. _id
  8. name
  9. attributes {
  10. name
  11. values {
  12. value
  13. }
  14. }
  15. products {
  16. name
  17. }
  18. subCategories {
  19. _id
  20. name
  21. attributes {
  22. name
  23. values {
  24. value
  25. }
  26. }
  27. products {
  28. name
  29. }
  30. subCategories {
  31. _id
  32. name
  33. attributes {
  34. name
  35. values {
  36. value
  37. }
  38. }
  39. products {
  40. name
  41. }
  42. subCategories {
  43. _id
  44. name
  45. attributes {
  46. name
  47. values {
  48. value
  49. }
  50. }
  51. products {
  52. name
  53. }
  54. subCategories {
  55. _id
  56. name
  57. attributes {
  58. name
  59. values {
  60. value
  61. }
  62. }
  63. products {
  64. name
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. }
  72. }

字符串
产出;

  1. {
  2. "data": {
  3. "getCategories": {
  4. "success": true,
  5. "response_code": "categories-successfully-retrieved",
  6. "message": "Categories Successfully Retrieved!",
  7. "categories": [
  8. {
  9. "_id": "655b103b43598f2ef0ba0f5e",
  10. "name": "K 2",
  11. "attributes": {
  12. "name": null,
  13. "values": {
  14. "value": null
  15. }
  16. },
  17. "products": [],
  18. "subCategories": []
  19. },
  20. {
  21. "_id": "655b103e43598f2ef0ba0f61",
  22. "name": "K 1",
  23. "attributes": {
  24. "name": null,
  25. "values": {
  26. "value": null
  27. }
  28. },
  29. "products": [],
  30. "subCategories": [
  31. {
  32. "_id": "655b104a43598f2ef0ba0f64",
  33. "name": "AK 1",
  34. "attributes": {
  35. "name": null,
  36. "values": {
  37. "value": null
  38. }
  39. },
  40. "products": [],
  41. "subCategories": [
  42. {
  43. "_id": "655b105543598f2ef0ba0f69",
  44. "name": null,
  45. "attributes": null,
  46. "products": null,
  47. "subCategories": null
  48. }
  49. ]
  50. },
  51. {
  52. "_id": "655b106643598f2ef0ba0f6f",
  53. "name": "AK 2",
  54. "attributes": {
  55. "name": null,
  56. "values": {
  57. "value": null
  58. }
  59. },
  60. "products": [],
  61. "subCategories": []
  62. }
  63. ]
  64. },
  65. {
  66. "_id": "655b104a43598f2ef0ba0f64",
  67. "name": "AK 1",
  68. "attributes": {
  69. "name": null,
  70. "values": {
  71. "value": null
  72. }
  73. },
  74. "products": [],
  75. "subCategories": [
  76. {
  77. "_id": "655b105543598f2ef0ba0f69",
  78. "name": "AAK 1",
  79. "attributes": {
  80. "name": null,
  81. "values": {
  82. "value": null
  83. }
  84. },
  85. "products": [],
  86. "subCategories": []
  87. }
  88. ]
  89. },
  90. {
  91. "_id": "655b105543598f2ef0ba0f69",
  92. "name": "AAK 1",
  93. "attributes": {
  94. "name": null,
  95. "values": {
  96. "value": null
  97. }
  98. },
  99. "products": [],
  100. "subCategories": []
  101. },
  102. {
  103. "_id": "655b106643598f2ef0ba0f6f",
  104. "name": "AK 2",
  105. "attributes": {
  106. "name": null,
  107. "values": {
  108. "value": null
  109. }
  110. },
  111. "products": [],
  112. "subCategories": []
  113. }
  114. ]
  115. }
  116. }
  117. }


预期产出;

  1. [
  2. {
  3. "name": "Category 1",
  4. "attributes": {
  5. "name": "Attribute Name",
  6. "values": [{
  7. "value": "Attribute Value",
  8. "products": ["Product Ref", "Product Ref"]
  9. }]
  10. },
  11. "products": ["Product Ref", "Product Ref"],
  12. "subCategories": [
  13. {
  14. "name": "Sub Category 1",
  15. "attributes": {
  16. "name": "Attribute Name",
  17. "values": [{
  18. "value": "Attribute Value",
  19. "products": ["Product Ref", "Product Ref"]
  20. }]
  21. },
  22. "products": ["Product Ref", "Product Ref"],
  23. "subCategories": [
  24. {
  25. "name": "Sub Category 2",
  26. "attributes": {
  27. "name": "Attribute Name",
  28. "values": [{
  29. "value": "Attribute Value",
  30. "products": ["Product Ref", "Product Ref"]
  31. }]
  32. },
  33. "products": ["Product Ref", "Product Ref"],
  34. "subCategories": []
  35. }
  36. ]
  37. }
  38. ]
  39. },
  40. {
  41. "name": "Category 2",
  42. "attributes": {
  43. "name": "Attribute Name",
  44. "values": [{
  45. "value": "Attribute Value",
  46. "products": ["Product Ref", "Product Ref"]
  47. }]
  48. },
  49. "products": ["Product Ref", "Product Ref"],
  50. "subCategories": [
  51. {
  52. "name": "Sub Category 1",
  53. "attributes": {
  54. "name": "Attribute Name",
  55. "values": [{
  56. "value": "Attribute Value",
  57. "products": ["Product Ref", "Product Ref"]
  58. }]
  59. },
  60. "products": ["Product Ref", "Product Ref"],
  61. "subCategories": [
  62. {
  63. "name": "Sub Category 2",
  64. "attributes": {
  65. "name": "Attribute Name",
  66. "values": [{
  67. "value": "Attribute Value",
  68. "products": ["Product Ref", "Product Ref"]
  69. }]
  70. },
  71. "products": ["Product Ref", "Product Ref"],
  72. "subCategories": []
  73. }
  74. ]
  75. }
  76. ]
  77. }
  78. ]


范畴 Mongoose 图式;

  1. import mongoose from "mongoose";
  2. const attributeSchema = new mongoose.Schema({
  3. name: { type: String, required: true, trim: true, unique: true, default: "Default Name" },
  4. values: [
  5. {
  6. value: { type: String, required: true, trim: true, unique: true },
  7. products: [{ type: mongoose.Schema.Types.ObjectId, ref: "Product" }],
  8. },
  9. ],
  10. });
  11. const categorySchema = new mongoose.Schema({
  12. name: { type: String, required: true, trim: true },
  13. subCategories: [{ type: mongoose.Schema.Types.ObjectId, ref: "Category" }],
  14. attributes: [attributeSchema],
  15. products: [{ type: mongoose.Schema.Types.ObjectId, ref: "Product" }],
  16. });
  17. export const Category = mongoose.model("Category", categorySchema);


类别GraphQL模式;

  1. export const categorySchema = `#graphql
  2. type Category {
  3. _id: ID
  4. name: String
  5. subCategories: [Category]
  6. attributes: Attributes
  7. products: [Product]
  8. }
  9. input CategoryInput {
  10. name: String!
  11. isMainCategory: Boolean!
  12. parentCategoryID: ID
  13. }
  14. type Attributes {
  15. name: String
  16. values: AttributesValue
  17. }
  18. type AttributesValue {
  19. value: String
  20. products: [Product]
  21. }
  22. input AttributesInput {
  23. name: String!
  24. categoryID: ID!
  25. }
  26. input AttributesValueInput {
  27. value: String!
  28. attributesID: ID!
  29. }
  30. type QueryCategoryResponse {
  31. success: Boolean!
  32. response_code: String!
  33. message: String!
  34. category: Category
  35. categories: [Category]
  36. }
  37. type Query {
  38. getCategories: QueryCategoryResponse
  39. getCategoryByID(id: ID!): QueryCategoryResponse
  40. getCategoriesByID(ids: [ID]!): QueryCategoryResponse
  41. }
  42. type Mutation {
  43. createCategory(input: CategoryInput!): Response
  44. }
  45. `;


类别解析器;

  1. import { Category } from "./model.js";
  2. export const categoryResolver = {
  3. Query: {
  4. getCategories: async () => {
  5. try {
  6. const categories = await Category.find().populate("subCategories attributes products");
  7. if (categories) {
  8. return {
  9. success: true,
  10. response_code: "categories-successfully-retrieved",
  11. message: "Categories Successfully Retrieved!",
  12. categories: categories,
  13. };
  14. } else {
  15. return { success: false, response_code: "categories-not-found", message: "Categories Not Found!" };
  16. }
  17. } catch (error) {
  18. return { success: false, response_code: "server-error", message: "Server Error!" };
  19. }
  20. },
  21. getCategoryByID: async (_, { id }) => {
  22. try {
  23. const category = await Category.findById({ _id: id }).populate("subCategories attributes products");
  24. if (category) {
  25. return {
  26. success: true,
  27. response_code: "category-successfully-retrieved",
  28. message: "Category Successfully Retrieved!",
  29. category: category,
  30. };
  31. } else {
  32. return { success: false, response_code: "category-not-found", message: "Category Not Found!" };
  33. }
  34. } catch (error) {
  35. return { success: false, response_code: "server-error", message: "Server Error!" };
  36. }
  37. },
  38. getCategoriesByID: async (_, { ids }) => {
  39. try {
  40. const categories = await Category.find({ _id: { $in: ids } }).populate("subCategories attributes products");
  41. if (categories.length !== 0) {
  42. return {
  43. success: true,
  44. response_code: "categories-successfully-retrieved",
  45. message: "Categories Successfully Retrieved!",
  46. categories: categories,
  47. };
  48. } else {
  49. return { success: false, response_code: "categories-not-found", message: "Categories Not Found!" };
  50. }
  51. } catch (error) {
  52. return { success: false, response_code: "server-error", message: "Server Error!" };
  53. }
  54. },
  55. },
  56. Mutation: {
  57. createCategory: async (_, { input }) => {
  58. try {
  59. if (input.isMainCategory) {
  60. const category = await Category.findOne({ name: input.name }).collation({ locale: "en", strength: 2 });
  61. if (category) {
  62. return { success: false, response_code: "category-name-exist", message: "Category Name Exist!" };
  63. } else {
  64. const newCategory = new Category({
  65. name: input.name,
  66. });
  67. await newCategory.save();
  68. return {
  69. success: true,
  70. response_code: "category-created-successfully",
  71. message: "Category Created Successfully!",
  72. };
  73. }
  74. } else {
  75. const category = await Category.findById(input.parentCategoryID).populate("subCategories");
  76. if (!category) {
  77. return { success: false, response_code: "sub-category-not-found", message: "Sub Category Not Found!" };
  78. }
  79. const categoryNames = category.subCategories.map((category) => category.name.toLowerCase());
  80. if (categoryNames.includes(input.name.toLowerCase())) {
  81. return { success: false, response_code: "category-name-exist", message: "Category Name Exists!" };
  82. }
  83. const newCategory = new Category({
  84. name: input.name,
  85. });
  86. await newCategory.save();
  87. category.subCategories.push(newCategory);
  88. await category.save();
  89. return {
  90. success: true,
  91. response_code: "sub-category-created-successfully",
  92. message: "Sub Category Created Successfully!",
  93. };
  94. }
  95. } catch (error) {
  96. console.log(error);
  97. return { success: false, response_code: "server-error", message: "Server Error!" };
  98. }
  99. },
  100. },
  101. };


我如何改进这段代码并解决我的getCategories输出问题?
显然,我也尝试过mongoose ref,我也尝试过根据categorySchema直接将数据发送到子类别中。我在第二个方法中提到的添加子类别的方法对我来说是一个头痛的问题。

e5njpo68

e5njpo681#

你的模型中似乎缺少了parent类别的概念。当定义一个层次模型时,通常包括对父类别的引用(类似于parentId)。
然后返回所有类别,首先搜索所有具有nullparentId的类别-这些是根节点。
你还需要一个子类别的字段解析器来查找父类别是当前类别的类别。这允许你递归地遍历树。
GraphQL的一个限制是你必须明确你想在你的查询中带回多少层,你不能定义一个递归树,它会向下钻取到每一个可能的叶子。你上面的查询向下钻取5层。
首先,在GraphQL类型中,添加一个parent字段。

  1. type Category {
  2. _id: ID
  3. name: String
  4. subCategories: [Category]
  5. parent: Category
  6. attributes: Attributes
  7. products: [Product]
  8. }

字符串
在Mongoose模型中:

  1. const categorySchema = new mongoose.Schema({
  2. name: { type: String, required: true, trim: true },
  3. subCategories: [{ type: mongoose.Schema.Types.ObjectId, ref: "Category" }],
  4. parentId: { type: mongoose.Schema.Types.ObjectId, required: false },
  5. attributes: [attributeSchema],
  6. products: [{ type: mongoose.Schema.Types.ObjectId, ref: "Product" }],
  7. });


然后修改getCategories解析器以搜索parentId为空的类别
然后向Category类型添加一个字段解析器来解析基于parentId的子类别,并添加一个字段解析器来解析父类别。
现在,相对于您的values结构-您定义它的方式,它严格地位于Mongoose模型的内部;如果你想通过value嵌套结果,那么你需要在Category类型中添加一个value字段,并定义一个包含valueproductsValue类型,即

  1. type Value {
  2. attributes: Attributes
  3. products: [Product]
  4. }
  5. type Category {
  6. _id: ID
  7. name: String
  8. subCategories: [Category]
  9. parent: Category
  10. value: Value
  11. }


定义一个 pluraltype(例如:Attributes)是不常见的。类型通常是单数的,因为如果你想要几个类型,你只需要把它们组成一个数组。你想在一个 field 名称中使用复数形式,而不是一个 type 名称。

展开查看全部

相关问题