diff --git a/README.md b/README.md index 38a30e9..14eb357 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ godemo/ # 项目根目录 ├── go.sum # 依赖校验 ├── docker/ # Docker文件目录 └── docker-gateway/ # gateway服务的Docker文件目录 + ├── etc/config.yaml # gateway服务配置文件(用于配置启动服务)(Optional) ├── Dockerfile # gateway服务的Dockerfile文件 └── docker-compose.yml # gateway服务的docker-compose文件 ├── sql/ # 数据库SQL定义 @@ -144,7 +145,7 @@ networks: ```bash # 在对应服务的docker目录执行(以file服务为例) -> docker/docker-file> docker compose up -d +docker/docker-file> docker compose up -d ``` ## RPC服务测试/调试 @@ -166,9 +167,9 @@ networks: **接口定义** +- 用户注册 - 用户登录 - 获取用户信息 -- 用户注册 - 用户注销 ## 文件服务 diff --git a/api/gateway.api b/api/gateway.api index 1e8b7ce..b1e21fd 100644 --- a/api/gateway.api +++ b/api/gateway.api @@ -1,5 +1,12 @@ syntax = "v1" +// ================== 通用类型定义 ================== +type BaseResp { + Code int `json:"code"` // 状态码 (0=成功) + Message string `json:"message"` // 消息 +} + +// ================== 用户服务类型 ================== type RegisterReq { Username string `json:"username"` Password string `json:"password"` @@ -40,22 +47,139 @@ type GetUserInfoResp { Roles []string `json:"roles"` } -service user-api { +// ================== 分类服务类型 ================== +type BaseCategory { + ID string `json:"id"` // 分类ID (UUID) + SystemID string `json:"systemId"` // 所属系统ID + Name string `json:"name"` // 分类名称 + Alias string `json:"alias"` // URL别名 + ParentID string `json:"parentId"` // 父分类ID (可为空) + Description string `json:"description"` // 分类描述 + CreatedAt string `json:"createdAt"` // 创建时间 (ISO8601) + UpdatedAt string `json:"updatedAt"` // 更新时间 (ISO8601) +} + +type CategoryDetail { + BaseCategory + Parent *BaseCategory `json:"parent"` // 父分类信息 +} + +type TreeNode { + BaseCategory + Children []*TreeNode `json:"children"` // 子分类列表 +} + +type CreateCategoryReq { + SystemID string `json:"systemId" validate:"required"` // 所属系统ID + Name string `json:"name" validate:"required"` // 分类名称 + Alias string `json:"alias,optional"` // URL别名 + ParentID string `json:"parentId,optional"` // 父分类ID + Description string `json:"description,optional"` // 分类描述 +} + +type CreateCategoryResp { + ID string `json:"id"` // 新创建的分类ID +} + +type ListCategoriesReq { + SystemID string `form:"systemId,optional"` // 按系统ID过滤 + ParentID string `form:"parentId,optional"` // 按父分类ID过滤 + Name string `form:"name,optional"` // 按名称模糊搜索 + Page int `form:"page,default=1"` // 页码 + PageSize int `form:"pageSize,default=20"` // 每页数量 +} + +type ListCategoriesResp { + Total int64 `json:"total"` // 总数 + List []BaseCategory `json:"list"` // 分类列表 +} + +type GetCategoryReq { + ID string `path:"id"` // 分类ID +} + +type CategoryDetailResp { + CategoryDetail +} + +type UpdateCategoryReq { + ID string `path:"id"` // 分类ID + Name string `json:"name,optional"` // 新名称 + Alias string `json:"alias,optional"` // 新别名 + ParentID string `json:"parentId,optional"` // 新父分类ID + Description string `json:"description,optional"` // 新描述 +} + +type GetCategoryTreeReq { + ID string `path:"id"` // 起始分类ID +} + +type CategoryTreeResp { + Root TreeNode `json:"root"` // 分类树根节点 +} + +type GetSystemCategoriesReq { + SystemID string `path:"system_id"` // 系统ID + ParentID string `form:"parentId,optional"` // 父分类ID (可选) + IncludeDescendants bool `form:"includeDescendants,default=true"` // 是否包含子分类 +} + +type DeleteCategoryReq { + ID string `path:"id"` // 分类ID +} + +// ================== 网关服务定义 ================== +@server ( + prefix: /api +) +service gateway-api { + // ===== 用户服务 ===== + // 公共接口(无需认证) @handler registerHandler - post /api/user/register (RegisterReq) returns (RegisterResp) + post /user/register (RegisterReq) returns (RegisterResp) @handler loginHandler - post /api/user/login (LoginReq) returns (LoginResp) + post /user/login (LoginReq) returns (LoginResp) } @server ( - jwt: JwtAuth + prefix: /api + jwt: JwtAuth ) -service user-api { +service gateway-api { @handler logoutHandler - post /api/user/logout (LogoutReq) returns (LogoutResp) + post /user/logout (LogoutReq) returns (LogoutResp) @handler getUserInfoHandler - get /api/user/:user_id (GetUserInfoReq) returns (GetUserInfoResp) + get /user/:user_id (GetUserInfoReq) returns (GetUserInfoResp) + + // ===== 分类服务 ===== + // 创建分类 + @handler createCategoryHandler + post /category/v1 (CreateCategoryReq) returns (CreateCategoryResp) + + // 批量获取分类(带分页和过滤) + @handler listCategoriesHandler + get /category/v1 (ListCategoriesReq) returns (ListCategoriesResp) + + // 获取单个分类详情 + @handler getCategoryHandler + get /category/v1/:id (GetCategoryReq) returns (CategoryDetailResp) + + // 更新分类 + @handler updateCategoryHandler + put /category/v1/:id (UpdateCategoryReq) returns (BaseResp) + + // 删除分类 + @handler deleteCategoryHandler + delete /category/v1/:id (DeleteCategoryReq) returns (BaseResp) + + // 获取子分类树 + @handler getCategoryTreeHandler + get /category/v1/:id/tree (GetCategoryTreeReq) returns (CategoryTreeResp) + + // 根据系统ID获取分类 + @handler getSystemCategoriesHandler + get /category/v1/system/:system_id (GetSystemCategoriesReq) returns (ListCategoriesResp) } diff --git a/category/category/category.pb.go b/category/category/category.pb.go index eb0cc4f..a199714 100644 --- a/category/category/category.pb.go +++ b/category/category/category.pb.go @@ -1301,6 +1301,150 @@ func (x *CheckAliasResponse) GetExistingId() string { return "" } +type GetSystemCategoriesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SystemId string `protobuf:"bytes,1,opt,name=system_id,json=systemId,proto3" json:"system_id,omitempty"` // 系统标识 (必需) + ParentId string `protobuf:"bytes,2,opt,name=parent_id,json=parentId,proto3" json:"parent_id,omitempty"` // 父分类ID (可选) + Page int32 `protobuf:"varint,3,opt,name=page,proto3" json:"page,omitempty"` // 分页参数 + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // 分页参数 + IncludeDescendants bool `protobuf:"varint,5,opt,name=include_descendants,json=includeDescendants,proto3" json:"include_descendants,omitempty"` // 是否包含后代 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSystemCategoriesRequest) Reset() { + *x = GetSystemCategoriesRequest{} + mi := &file_rpc_category_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSystemCategoriesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSystemCategoriesRequest) ProtoMessage() {} + +func (x *GetSystemCategoriesRequest) ProtoReflect() protoreflect.Message { + mi := &file_rpc_category_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSystemCategoriesRequest.ProtoReflect.Descriptor instead. +func (*GetSystemCategoriesRequest) Descriptor() ([]byte, []int) { + return file_rpc_category_proto_rawDescGZIP(), []int{23} +} + +func (x *GetSystemCategoriesRequest) GetSystemId() string { + if x != nil { + return x.SystemId + } + return "" +} + +func (x *GetSystemCategoriesRequest) GetParentId() string { + if x != nil { + return x.ParentId + } + return "" +} + +func (x *GetSystemCategoriesRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *GetSystemCategoriesRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *GetSystemCategoriesRequest) GetIncludeDescendants() bool { + if x != nil { + return x.IncludeDescendants + } + return false +} + +type GetSystemCategoriesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Categories []*CategoryInfo `protobuf:"bytes,1,rep,name=categories,proto3" json:"categories,omitempty"` + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` + CurrentPage int32 `protobuf:"varint,3,opt,name=current_page,json=currentPage,proto3" json:"current_page,omitempty"` + PageSize int32 `protobuf:"varint,4,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSystemCategoriesResponse) Reset() { + *x = GetSystemCategoriesResponse{} + mi := &file_rpc_category_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSystemCategoriesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSystemCategoriesResponse) ProtoMessage() {} + +func (x *GetSystemCategoriesResponse) ProtoReflect() protoreflect.Message { + mi := &file_rpc_category_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSystemCategoriesResponse.ProtoReflect.Descriptor instead. +func (*GetSystemCategoriesResponse) Descriptor() ([]byte, []int) { + return file_rpc_category_proto_rawDescGZIP(), []int{24} +} + +func (x *GetSystemCategoriesResponse) GetCategories() []*CategoryInfo { + if x != nil { + return x.Categories + } + return nil +} + +func (x *GetSystemCategoriesResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *GetSystemCategoriesResponse) GetCurrentPage() int32 { + if x != nil { + return x.CurrentPage + } + return 0 +} + +func (x *GetSystemCategoriesResponse) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + type CategoryTreeResponse_TreeNode struct { state protoimpl.MessageState `protogen:"open.v1"` Category *CategoryInfo `protobuf:"bytes,1,opt,name=category,proto3" json:"category,omitempty"` @@ -1311,7 +1455,7 @@ type CategoryTreeResponse_TreeNode struct { func (x *CategoryTreeResponse_TreeNode) Reset() { *x = CategoryTreeResponse_TreeNode{} - mi := &file_rpc_category_proto_msgTypes[23] + mi := &file_rpc_category_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1323,7 +1467,7 @@ func (x *CategoryTreeResponse_TreeNode) String() string { func (*CategoryTreeResponse_TreeNode) ProtoMessage() {} func (x *CategoryTreeResponse_TreeNode) ProtoReflect() protoreflect.Message { - mi := &file_rpc_category_proto_msgTypes[23] + mi := &file_rpc_category_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1448,7 +1592,20 @@ const file_rpc_category_proto_rawDesc = "" + "\x12CheckAliasResponse\x12!\n" + "\fis_available\x18\x01 \x01(\bR\visAvailable\x12\x1f\n" + "\vexisting_id\x18\x02 \x01(\tR\n" + - "existingId2\xfd\a\n" + + "existingId\"\xb8\x01\n" + + "\x1aGetSystemCategoriesRequest\x12\x1b\n" + + "\tsystem_id\x18\x01 \x01(\tR\bsystemId\x12\x1b\n" + + "\tparent_id\x18\x02 \x01(\tR\bparentId\x12\x12\n" + + "\x04page\x18\x03 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\x12/\n" + + "\x13include_descendants\x18\x05 \x01(\bR\x12includeDescendants\"\xab\x01\n" + + "\x1bGetSystemCategoriesResponse\x126\n" + + "\n" + + "categories\x18\x01 \x03(\v2\x16.category.CategoryInfoR\n" + + "categories\x12\x14\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\x12!\n" + + "\fcurrent_page\x18\x03 \x01(\x05R\vcurrentPage\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize2\xe1\b\n" + "\bCategory\x125\n" + "\x04Ping\x12\x15.category.PingRequest\x1a\x16.category.PingResponse\x12Q\n" + "\x0eCreateCategory\x12\x1f.category.CreateCategoryRequest\x1a\x1e.category.CategoryInfoResponse\x12Q\n" + @@ -1463,7 +1620,8 @@ const file_rpc_category_proto_rawDesc = "" + "\x15BatchUpdateCategories\x12\x1c.category.BatchUpdateRequest\x1a\x1d.category.BatchUpdateResponse\x12O\n" + "\x0eListCategories\x12\x1d.category.ListCategoryRequest\x1a\x1e.category.CategoryListResponse\x12G\n" + "\n" + - "CheckAlias\x12\x1b.category.CheckAliasRequest\x1a\x1c.category.CheckAliasResponseB\fZ\n" + + "CheckAlias\x12\x1b.category.CheckAliasRequest\x1a\x1c.category.CheckAliasResponse\x12b\n" + + "\x13GetSystemCategories\x12$.category.GetSystemCategoriesRequest\x1a%.category.GetSystemCategoriesResponseB\fZ\n" + "./categoryb\x06proto3" var ( @@ -1478,7 +1636,7 @@ func file_rpc_category_proto_rawDescGZIP() []byte { return file_rpc_category_proto_rawDescData } -var file_rpc_category_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_rpc_category_proto_msgTypes = make([]protoimpl.MessageInfo, 26) var file_rpc_category_proto_goTypes = []any{ (*PingRequest)(nil), // 0: category.PingRequest (*PingResponse)(nil), // 1: category.PingResponse @@ -1503,7 +1661,9 @@ var file_rpc_category_proto_goTypes = []any{ (*CategoryTreeResponse)(nil), // 20: category.CategoryTreeResponse (*CheckAliasRequest)(nil), // 21: category.CheckAliasRequest (*CheckAliasResponse)(nil), // 22: category.CheckAliasResponse - (*CategoryTreeResponse_TreeNode)(nil), // 23: category.CategoryTreeResponse.TreeNode + (*GetSystemCategoriesRequest)(nil), // 23: category.GetSystemCategoriesRequest + (*GetSystemCategoriesResponse)(nil), // 24: category.GetSystemCategoriesResponse + (*CategoryTreeResponse_TreeNode)(nil), // 25: category.CategoryTreeResponse.TreeNode } var file_rpc_category_proto_depIdxs = []int32{ 2, // 0: category.CategoryInfoResponse.category:type_name -> category.CategoryInfo @@ -1513,40 +1673,43 @@ var file_rpc_category_proto_depIdxs = []int32{ 4, // 4: category.BatchUpdateRequest.categories:type_name -> category.UpdateCategoryRequest 2, // 5: category.BatchUpdateResponse.updated_categories:type_name -> category.CategoryInfo 2, // 6: category.CategoryListResponse.categories:type_name -> category.CategoryInfo - 23, // 7: category.CategoryTreeResponse.root:type_name -> category.CategoryTreeResponse.TreeNode - 2, // 8: category.CategoryTreeResponse.TreeNode.category:type_name -> category.CategoryInfo - 23, // 9: category.CategoryTreeResponse.TreeNode.children:type_name -> category.CategoryTreeResponse.TreeNode - 0, // 10: category.Category.Ping:input_type -> category.PingRequest - 3, // 11: category.Category.CreateCategory:input_type -> category.CreateCategoryRequest - 4, // 12: category.Category.UpdateCategory:input_type -> category.UpdateCategoryRequest - 5, // 13: category.Category.DeleteCategory:input_type -> category.DeleteCategoryRequest - 7, // 14: category.Category.GetCategory:input_type -> category.GetCategoryRequest - 9, // 15: category.Category.GetChildren:input_type -> category.GetChildrenRequest - 10, // 16: category.Category.GetTree:input_type -> category.GetTreeRequest - 11, // 17: category.Category.MoveCategory:input_type -> category.MoveCategoryRequest - 12, // 18: category.Category.GetAncestorPath:input_type -> category.GetAncestorPathRequest - 14, // 19: category.Category.BatchCreateCategories:input_type -> category.BatchCreateRequest - 16, // 20: category.Category.BatchUpdateCategories:input_type -> category.BatchUpdateRequest - 18, // 21: category.Category.ListCategories:input_type -> category.ListCategoryRequest - 21, // 22: category.Category.CheckAlias:input_type -> category.CheckAliasRequest - 1, // 23: category.Category.Ping:output_type -> category.PingResponse - 8, // 24: category.Category.CreateCategory:output_type -> category.CategoryInfoResponse - 8, // 25: category.Category.UpdateCategory:output_type -> category.CategoryInfoResponse - 6, // 26: category.Category.DeleteCategory:output_type -> category.DeleteResponse - 8, // 27: category.Category.GetCategory:output_type -> category.CategoryInfoResponse - 19, // 28: category.Category.GetChildren:output_type -> category.CategoryListResponse - 20, // 29: category.Category.GetTree:output_type -> category.CategoryTreeResponse - 8, // 30: category.Category.MoveCategory:output_type -> category.CategoryInfoResponse - 13, // 31: category.Category.GetAncestorPath:output_type -> category.CategoryPathResponse - 15, // 32: category.Category.BatchCreateCategories:output_type -> category.BatchCreateResponse - 17, // 33: category.Category.BatchUpdateCategories:output_type -> category.BatchUpdateResponse - 19, // 34: category.Category.ListCategories:output_type -> category.CategoryListResponse - 22, // 35: category.Category.CheckAlias:output_type -> category.CheckAliasResponse - 23, // [23:36] is the sub-list for method output_type - 10, // [10:23] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 25, // 7: category.CategoryTreeResponse.root:type_name -> category.CategoryTreeResponse.TreeNode + 2, // 8: category.GetSystemCategoriesResponse.categories:type_name -> category.CategoryInfo + 2, // 9: category.CategoryTreeResponse.TreeNode.category:type_name -> category.CategoryInfo + 25, // 10: category.CategoryTreeResponse.TreeNode.children:type_name -> category.CategoryTreeResponse.TreeNode + 0, // 11: category.Category.Ping:input_type -> category.PingRequest + 3, // 12: category.Category.CreateCategory:input_type -> category.CreateCategoryRequest + 4, // 13: category.Category.UpdateCategory:input_type -> category.UpdateCategoryRequest + 5, // 14: category.Category.DeleteCategory:input_type -> category.DeleteCategoryRequest + 7, // 15: category.Category.GetCategory:input_type -> category.GetCategoryRequest + 9, // 16: category.Category.GetChildren:input_type -> category.GetChildrenRequest + 10, // 17: category.Category.GetTree:input_type -> category.GetTreeRequest + 11, // 18: category.Category.MoveCategory:input_type -> category.MoveCategoryRequest + 12, // 19: category.Category.GetAncestorPath:input_type -> category.GetAncestorPathRequest + 14, // 20: category.Category.BatchCreateCategories:input_type -> category.BatchCreateRequest + 16, // 21: category.Category.BatchUpdateCategories:input_type -> category.BatchUpdateRequest + 18, // 22: category.Category.ListCategories:input_type -> category.ListCategoryRequest + 21, // 23: category.Category.CheckAlias:input_type -> category.CheckAliasRequest + 23, // 24: category.Category.GetSystemCategories:input_type -> category.GetSystemCategoriesRequest + 1, // 25: category.Category.Ping:output_type -> category.PingResponse + 8, // 26: category.Category.CreateCategory:output_type -> category.CategoryInfoResponse + 8, // 27: category.Category.UpdateCategory:output_type -> category.CategoryInfoResponse + 6, // 28: category.Category.DeleteCategory:output_type -> category.DeleteResponse + 8, // 29: category.Category.GetCategory:output_type -> category.CategoryInfoResponse + 19, // 30: category.Category.GetChildren:output_type -> category.CategoryListResponse + 20, // 31: category.Category.GetTree:output_type -> category.CategoryTreeResponse + 8, // 32: category.Category.MoveCategory:output_type -> category.CategoryInfoResponse + 13, // 33: category.Category.GetAncestorPath:output_type -> category.CategoryPathResponse + 15, // 34: category.Category.BatchCreateCategories:output_type -> category.BatchCreateResponse + 17, // 35: category.Category.BatchUpdateCategories:output_type -> category.BatchUpdateResponse + 19, // 36: category.Category.ListCategories:output_type -> category.CategoryListResponse + 22, // 37: category.Category.CheckAlias:output_type -> category.CheckAliasResponse + 24, // 38: category.Category.GetSystemCategories:output_type -> category.GetSystemCategoriesResponse + 25, // [25:39] is the sub-list for method output_type + 11, // [11:25] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_rpc_category_proto_init() } @@ -1560,7 +1723,7 @@ func file_rpc_category_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_rpc_category_proto_rawDesc), len(file_rpc_category_proto_rawDesc)), NumEnums: 0, - NumMessages: 24, + NumMessages: 26, NumExtensions: 0, NumServices: 1, }, diff --git a/category/category/category_grpc.pb.go b/category/category/category_grpc.pb.go index d68b451..53e3303 100644 --- a/category/category/category_grpc.pb.go +++ b/category/category/category_grpc.pb.go @@ -32,6 +32,7 @@ const ( Category_BatchUpdateCategories_FullMethodName = "/category.Category/BatchUpdateCategories" Category_ListCategories_FullMethodName = "/category.Category/ListCategories" Category_CheckAlias_FullMethodName = "/category.Category/CheckAlias" + Category_GetSystemCategories_FullMethodName = "/category.Category/GetSystemCategories" ) // CategoryClient is the client API for Category service. @@ -58,6 +59,8 @@ type CategoryClient interface { // 查询过滤 ListCategories(ctx context.Context, in *ListCategoryRequest, opts ...grpc.CallOption) (*CategoryListResponse, error) CheckAlias(ctx context.Context, in *CheckAliasRequest, opts ...grpc.CallOption) (*CheckAliasResponse, error) + // 根据系统ID获取分类 + GetSystemCategories(ctx context.Context, in *GetSystemCategoriesRequest, opts ...grpc.CallOption) (*GetSystemCategoriesResponse, error) } type categoryClient struct { @@ -198,6 +201,16 @@ func (c *categoryClient) CheckAlias(ctx context.Context, in *CheckAliasRequest, return out, nil } +func (c *categoryClient) GetSystemCategories(ctx context.Context, in *GetSystemCategoriesRequest, opts ...grpc.CallOption) (*GetSystemCategoriesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSystemCategoriesResponse) + err := c.cc.Invoke(ctx, Category_GetSystemCategories_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // CategoryServer is the server API for Category service. // All implementations must embed UnimplementedCategoryServer // for forward compatibility. @@ -222,6 +235,8 @@ type CategoryServer interface { // 查询过滤 ListCategories(context.Context, *ListCategoryRequest) (*CategoryListResponse, error) CheckAlias(context.Context, *CheckAliasRequest) (*CheckAliasResponse, error) + // 根据系统ID获取分类 + GetSystemCategories(context.Context, *GetSystemCategoriesRequest) (*GetSystemCategoriesResponse, error) mustEmbedUnimplementedCategoryServer() } @@ -271,6 +286,9 @@ func (UnimplementedCategoryServer) ListCategories(context.Context, *ListCategory func (UnimplementedCategoryServer) CheckAlias(context.Context, *CheckAliasRequest) (*CheckAliasResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CheckAlias not implemented") } +func (UnimplementedCategoryServer) GetSystemCategories(context.Context, *GetSystemCategoriesRequest) (*GetSystemCategoriesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetSystemCategories not implemented") +} func (UnimplementedCategoryServer) mustEmbedUnimplementedCategoryServer() {} func (UnimplementedCategoryServer) testEmbeddedByValue() {} @@ -526,6 +544,24 @@ func _Category_CheckAlias_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _Category_GetSystemCategories_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSystemCategoriesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CategoryServer).GetSystemCategories(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Category_GetSystemCategories_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CategoryServer).GetSystemCategories(ctx, req.(*GetSystemCategoriesRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Category_ServiceDesc is the grpc.ServiceDesc for Category service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -585,6 +621,10 @@ var Category_ServiceDesc = grpc.ServiceDesc{ MethodName: "CheckAlias", Handler: _Category_CheckAlias_Handler, }, + { + MethodName: "GetSystemCategories", + Handler: _Category_GetSystemCategories_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "rpc/category.proto", diff --git a/category/categoryclient/category.go b/category/categoryclient/category.go index 86a89d4..d7aef74 100644 --- a/category/categoryclient/category.go +++ b/category/categoryclient/category.go @@ -32,6 +32,8 @@ type ( GetAncestorPathRequest = category.GetAncestorPathRequest GetCategoryRequest = category.GetCategoryRequest GetChildrenRequest = category.GetChildrenRequest + GetSystemCategoriesRequest = category.GetSystemCategoriesRequest + GetSystemCategoriesResponse = category.GetSystemCategoriesResponse GetTreeRequest = category.GetTreeRequest ListCategoryRequest = category.ListCategoryRequest MoveCategoryRequest = category.MoveCategoryRequest @@ -58,6 +60,8 @@ type ( // 查询过滤 ListCategories(ctx context.Context, in *ListCategoryRequest, opts ...grpc.CallOption) (*CategoryListResponse, error) CheckAlias(ctx context.Context, in *CheckAliasRequest, opts ...grpc.CallOption) (*CheckAliasResponse, error) + // 根据系统ID获取分类 + GetSystemCategories(ctx context.Context, in *GetSystemCategoriesRequest, opts ...grpc.CallOption) (*GetSystemCategoriesResponse, error) } defaultCategory struct { @@ -140,3 +144,9 @@ func (m *defaultCategory) CheckAlias(ctx context.Context, in *CheckAliasRequest, client := category.NewCategoryClient(m.cli.Conn()) return client.CheckAlias(ctx, in, opts...) } + +// 根据系统ID获取分类 +func (m *defaultCategory) GetSystemCategories(ctx context.Context, in *GetSystemCategoriesRequest, opts ...grpc.CallOption) (*GetSystemCategoriesResponse, error) { + client := category.NewCategoryClient(m.cli.Conn()) + return client.GetSystemCategories(ctx, in, opts...) +} diff --git a/category/etc/category.yaml b/category/etc/category.yaml index 2ffc926..86e9960 100644 --- a/category/etc/category.yaml +++ b/category/etc/category.yaml @@ -7,7 +7,7 @@ Etcd: Key: category.rpc DB: - DataSource: postgres://postgres:postgres@localhost:5432/godemo?sslmode=disable + DataSource: postgres://postgres:postgres@localhost:19732/godemo?sslmode=disable MaxOpenConns: 100 MaxIdleConns: 20 ConnMaxLifetime: 3600 \ No newline at end of file diff --git a/category/internal/logic/getsystemcategorieslogic.go b/category/internal/logic/getsystemcategorieslogic.go new file mode 100644 index 0000000..6b4b763 --- /dev/null +++ b/category/internal/logic/getsystemcategorieslogic.go @@ -0,0 +1,167 @@ +package logic + +import ( + "context" + + "godemo/category/category" + "godemo/category/internal/model" + "godemo/category/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type GetSystemCategoriesLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewGetSystemCategoriesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSystemCategoriesLogic { + return &GetSystemCategoriesLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// 根据系统ID获取分类 +func (l *GetSystemCategoriesLogic) GetSystemCategories(in *category.GetSystemCategoriesRequest) (*category.GetSystemCategoriesResponse, error) { + // 1. 参数验证 + if in.SystemId == "" { + return nil, status.Error(codes.InvalidArgument, "system_id is required") + } + + // 2. 设置默认分页值 + page, pageSize := normalizePagination(in.Page, in.PageSize) + + // 3. 根据是否包含后代选择查询方式 + var categories []*model.Categories + var total int64 + var err error + + if in.IncludeDescendants { + // 递归查询所有后代 + categories, err = l.getDescendantsRecursively(in.SystemId, in.ParentId) + if err != nil { + return nil, status.Error(codes.Internal, "failed to get descendant categories") + } + total = int64(len(categories)) + } else { + // 直接查询子分类 + categories, total, err = l.getDirectChildren(in.SystemId, in.ParentId, page, pageSize) + if err != nil { + return nil, status.Error(codes.Internal, "failed to get categories") + } + } + + // 4. 转换模型到protobuf + pbCategories := make([]*category.CategoryInfo, 0, len(categories)) + for _, cat := range categories { + pbCategories = append(pbCategories, convertToPbCategory(cat)) + } + + // 5. 构建响应 + return &category.GetSystemCategoriesResponse{ + Categories: pbCategories, + Total: total, + CurrentPage: int32(page), + PageSize: int32(pageSize), + }, nil +} + +// 获取直接子分类(非递归) +func (l *GetSystemCategoriesLogic) getDirectChildren(systemID, parentID string, page, pageSize int) ([]*model.Categories, int64, error) { + // 构建基础查询 + query := l.svcCtx.CategoryModel.RowBuilder(). + Where("system_id = $1", systemID) + + // 处理父分类ID + if parentID == "" { + query = query.Where("(parent_id IS NULL OR parent_id = '')") + } else { + query = query.Where("parent_id = $2", parentID) + } + + // 获取总数 + total, err := l.svcCtx.CategoryModel.FindCount(l.ctx, query.RemoveColumns()) + if err != nil { + return nil, 0, err + } + + // 分页查询 + query = query.OrderBy("created_at DESC"). + Offset(uint64((page - 1) * pageSize)). + Limit(uint64(pageSize)) + + categories, err := l.svcCtx.CategoryModel.FindAll(l.ctx, query) + if err != nil { + return nil, 0, err + } + + return categories, total, nil +} + +// 递归获取所有后代分类 +func (l *GetSystemCategoriesLogic) getDescendantsRecursively(systemID, parentID string) ([]*model.Categories, error) { + // 使用递归CTE查询 + query := ` + WITH RECURSIVE category_tree AS ( + SELECT id, system_id, name, alias, parent_id, description, created_at, updated_at + FROM categories + WHERE system_id = $1 + AND ( + CASE + WHEN $2 = '' THEN parent_id IS NULL + ELSE parent_id = $2 + END + ) + UNION ALL + SELECT c.id, c.system_id, c.name, c.alias, c.parent_id, c.description, c.created_at, c.updated_at + FROM categories c + INNER JOIN category_tree ct ON c.parent_id = ct.id + ) + SELECT * FROM category_tree + ` + + var categories []*model.Categories + err := l.svcCtx.SqlConn.QueryRowsCtx(l.ctx, &categories, query, systemID, parentID) + if err != nil && err != sqlx.ErrNotFound { + return nil, err + } + + return categories, nil +} + +// 转换模型到protobuf +func convertToPbCategory(cat *model.Categories) *category.CategoryInfo { + return &category.CategoryInfo{ + Id: cat.Id, + SystemId: cat.SystemId, + Name: cat.Name, + Alias: cat.Alias.String, + ParentId: cat.ParentId.String, + Description: cat.Description.String, + CreatedAt: cat.CreatedAt.Unix(), + UpdatedAt: cat.UpdatedAt.Time.Unix(), + } +} + +// 规范化分页参数 +func normalizePagination(page, pageSize int32) (int, int) { + p := int(page) + ps := int(pageSize) + + if p <= 0 { + p = 1 + } + if ps <= 0 { + ps = 20 + } else if ps > 100 { + ps = 100 + } + + return p, ps +} diff --git a/category/internal/model/categoriesmodel.go b/category/internal/model/categoriesmodel.go index 5a64e25..61ace81 100644 --- a/category/internal/model/categoriesmodel.go +++ b/category/internal/model/categoriesmodel.go @@ -3,6 +3,7 @@ package model import ( "context" + "github.com/Masterminds/squirrel" "github.com/zeromicro/go-zero/core/stores/sqlx" ) @@ -17,6 +18,11 @@ type ( Transact(ctx context.Context, fn func(ctx context.Context, session sqlx.Session) error) error FindBySystemParentName(ctx context.Context, systemID string, parentID string, name string) (*Categories, error) FindBySystemParentAlias(ctx context.Context, systemID string, parentID string, alias string) (*Categories, error) + + // 新增方法 + RowBuilder() squirrel.SelectBuilder + FindCount(ctx context.Context, builder squirrel.SelectBuilder) (int64, error) + FindAll(ctx context.Context, builder squirrel.SelectBuilder) ([]*Categories, error) } customCategoriesModel struct { @@ -72,3 +78,81 @@ func (m *customCategoriesModel) FindBySystemParentAlias(ctx context.Context, sys } return &resp, nil } + +// === 新增方法实现 === + +// RowBuilder 创建一个基本的SELECT查询构建器 +func (m *customCategoriesModel) RowBuilder() squirrel.SelectBuilder { + return squirrel.Select(categoriesRows).From(m.table) +} + +// FindCount 执行COUNT查询 +func (m *customCategoriesModel) FindCount(ctx context.Context, builder squirrel.SelectBuilder) (int64, error) { + // 将SELECT转换为COUNT + builder = builder.Columns("COUNT(1) AS count") + + query, args, err := builder.ToSql() + if err != nil { + return 0, err + } + + var count int64 + err = m.conn.QueryRowCtx(ctx, &count, query, args...) + if err != nil { + return 0, err + } + + return count, nil +} + +// FindAll 执行查询并返回所有结果 +func (m *customCategoriesModel) FindAll(ctx context.Context, builder squirrel.SelectBuilder) ([]*Categories, error) { + query, args, err := builder.ToSql() + if err != nil { + return nil, err + } + + var categories []*Categories + err = m.conn.QueryRowsCtx(ctx, &categories, query, args...) + if err != nil && err != sqlx.ErrNotFound { + return nil, err + } + + return categories, nil +} + +// === 辅助函数 === + +// 添加分页支持 +func (m *customCategoriesModel) FindAllWithPagination( + ctx context.Context, + builder squirrel.SelectBuilder, + orderBy string, + page int, + pageSize int, +) ([]*Categories, int64, error) { + // 获取总数 + total, err := m.FindCount(ctx, builder) + if err != nil { + return nil, 0, err + } + + // 添加分页 + if page > 0 && pageSize > 0 { + offset := (page - 1) * pageSize + builder = builder.Offset(uint64(offset)).Limit(uint64(pageSize)) + } + + // 添加排序 + if orderBy != "" { + builder = builder.OrderBy(orderBy) + } + + // 执行查询 + categories, err := m.FindAll(ctx, builder) + if err != nil { + return nil, 0, err + } + + return categories, total, nil +} diff --git a/category/internal/server/categoryserver.go b/category/internal/server/categoryserver.go index 702b6ec..8600280 100644 --- a/category/internal/server/categoryserver.go +++ b/category/internal/server/categoryserver.go @@ -92,3 +92,9 @@ func (s *CategoryServer) CheckAlias(ctx context.Context, in *category.CheckAlias l := logic.NewCheckAliasLogic(ctx, s.svcCtx) return l.CheckAlias(in) } + +// 根据系统ID获取分类 +func (s *CategoryServer) GetSystemCategories(ctx context.Context, in *category.GetSystemCategoriesRequest) (*category.GetSystemCategoriesResponse, error) { + l := logic.NewGetSystemCategoriesLogic(ctx, s.svcCtx) + return l.GetSystemCategories(in) +} diff --git a/category/internal/svc/servicecontext.go b/category/internal/svc/servicecontext.go index 2416487..297e723 100644 --- a/category/internal/svc/servicecontext.go +++ b/category/internal/svc/servicecontext.go @@ -11,6 +11,7 @@ import ( type ServiceContext struct { Config config.Config CategoryModel model.CategoriesModel + SqlConn sqlx.SqlConn // 添加这个字段 } func NewServiceContext(c config.Config) *ServiceContext { @@ -18,5 +19,6 @@ func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, CategoryModel: model.NewCategoriesModel(conn), + SqlConn: conn, // 赋值 } } diff --git a/gateway/etc/gateway-api.yaml b/gateway/etc/gateway-api.yaml index f67dd25..f26ccb2 100644 --- a/gateway/etc/gateway-api.yaml +++ b/gateway/etc/gateway-api.yaml @@ -10,4 +10,10 @@ UserRpc: Etcd: Hosts: - 127.0.0.1:2379 - Key: user.rpc \ No newline at end of file + Key: user.rpc + +CategoryRpc: + Etcd: + Hosts: + - 127.0.0.1:2379 + Key: category.rpc \ No newline at end of file diff --git a/gateway/internal/config/config.go b/gateway/internal/config/config.go index 325da65..9980687 100644 --- a/gateway/internal/config/config.go +++ b/gateway/internal/config/config.go @@ -11,5 +11,6 @@ type Config struct { AccessSecret string AccessExpire int64 } - UserRpc zrpc.RpcClientConf + UserRpc zrpc.RpcClientConf + CategoryRpc zrpc.RpcClientConf } diff --git a/gateway/internal/handler/createcategoryhandler.go b/gateway/internal/handler/createcategoryhandler.go new file mode 100644 index 0000000..219f300 --- /dev/null +++ b/gateway/internal/handler/createcategoryhandler.go @@ -0,0 +1,50 @@ +package handler + +import ( + "net/http" + + "godemo/gateway/internal/logic" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" + + "github.com/zeromicro/go-zero/rest/httpx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func createCategoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.CreateCategoryReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewCreateCategoryLogic(r.Context(), svcCtx) + resp, err := l.CreateCategory(&req) + if err != nil { + if s, ok := status.FromError(err); ok { + // 判断 gRPC 错误码,并自定义 HTTP 返回 + var code int + switch s.Code() { + case codes.NotFound: + code = http.StatusNotFound + case codes.InvalidArgument: + code = http.StatusBadRequest + default: + code = http.StatusInternalServerError + } + + // 自定义 JSON 错误格式 + httpx.WriteJson(w, code, map[string]interface{}{ + "code": code, + "message": s.Message(), + }) + } else { + httpx.ErrorCtx(r.Context(), w, err) + } + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/gateway/internal/handler/deletecategoryhandler.go b/gateway/internal/handler/deletecategoryhandler.go new file mode 100644 index 0000000..a877adb --- /dev/null +++ b/gateway/internal/handler/deletecategoryhandler.go @@ -0,0 +1,28 @@ +package handler + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "godemo/gateway/internal/logic" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" +) + +func deleteCategoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DeleteCategoryReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewDeleteCategoryLogic(r.Context(), svcCtx) + resp, err := l.DeleteCategory(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/gateway/internal/handler/getcategoryhandler.go b/gateway/internal/handler/getcategoryhandler.go new file mode 100644 index 0000000..e9e7ede --- /dev/null +++ b/gateway/internal/handler/getcategoryhandler.go @@ -0,0 +1,28 @@ +package handler + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "godemo/gateway/internal/logic" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" +) + +func getCategoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetCategoryReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewGetCategoryLogic(r.Context(), svcCtx) + resp, err := l.GetCategory(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/gateway/internal/handler/getcategorytreehandler.go b/gateway/internal/handler/getcategorytreehandler.go new file mode 100644 index 0000000..8673b25 --- /dev/null +++ b/gateway/internal/handler/getcategorytreehandler.go @@ -0,0 +1,28 @@ +package handler + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "godemo/gateway/internal/logic" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" +) + +func getCategoryTreeHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetCategoryTreeReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewGetCategoryTreeLogic(r.Context(), svcCtx) + resp, err := l.GetCategoryTree(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/gateway/internal/handler/getsystemcategorieshandler.go b/gateway/internal/handler/getsystemcategorieshandler.go new file mode 100644 index 0000000..706eb30 --- /dev/null +++ b/gateway/internal/handler/getsystemcategorieshandler.go @@ -0,0 +1,28 @@ +package handler + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "godemo/gateway/internal/logic" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" +) + +func getSystemCategoriesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.GetSystemCategoriesReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewGetSystemCategoriesLogic(r.Context(), svcCtx) + resp, err := l.GetSystemCategories(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/gateway/internal/handler/listcategorieshandler.go b/gateway/internal/handler/listcategorieshandler.go new file mode 100644 index 0000000..d78899d --- /dev/null +++ b/gateway/internal/handler/listcategorieshandler.go @@ -0,0 +1,28 @@ +package handler + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "godemo/gateway/internal/logic" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" +) + +func listCategoriesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.ListCategoriesReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewListCategoriesLogic(r.Context(), svcCtx) + resp, err := l.ListCategories(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/gateway/internal/handler/routes.go b/gateway/internal/handler/routes.go index cf880f1..a417451 100644 --- a/gateway/internal/handler/routes.go +++ b/gateway/internal/handler/routes.go @@ -16,30 +16,67 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { []rest.Route{ { Method: http.MethodPost, - Path: "/api/user/login", + Path: "/user/login", Handler: loginHandler(serverCtx), }, { Method: http.MethodPost, - Path: "/api/user/register", + Path: "/user/register", Handler: registerHandler(serverCtx), }, }, + rest.WithPrefix("/api"), ) server.AddRoutes( []rest.Route{ + { + Method: http.MethodPost, + Path: "/category/v1", + Handler: createCategoryHandler(serverCtx), + }, { Method: http.MethodGet, - Path: "/api/user/:user_id", + Path: "/category/v1", + Handler: listCategoriesHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/category/v1/:id", + Handler: getCategoryHandler(serverCtx), + }, + { + Method: http.MethodPut, + Path: "/category/v1/:id", + Handler: updateCategoryHandler(serverCtx), + }, + { + Method: http.MethodDelete, + Path: "/category/v1/:id", + Handler: deleteCategoryHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/category/v1/:id/tree", + Handler: getCategoryTreeHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/category/v1/system/:system_id", + Handler: getSystemCategoriesHandler(serverCtx), + }, + { + Method: http.MethodGet, + Path: "/user/:user_id", Handler: getUserInfoHandler(serverCtx), }, { Method: http.MethodPost, - Path: "/api/user/logout", + Path: "/user/logout", Handler: logoutHandler(serverCtx), }, }, rest.WithJwt(serverCtx.Config.JwtAuth.AccessSecret), + rest.WithPrefix("/api"), ) } diff --git a/gateway/internal/handler/updatecategoryhandler.go b/gateway/internal/handler/updatecategoryhandler.go new file mode 100644 index 0000000..23ab8d8 --- /dev/null +++ b/gateway/internal/handler/updatecategoryhandler.go @@ -0,0 +1,28 @@ +package handler + +import ( + "net/http" + + "github.com/zeromicro/go-zero/rest/httpx" + "godemo/gateway/internal/logic" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" +) + +func updateCategoryHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.UpdateCategoryReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + + l := logic.NewUpdateCategoryLogic(r.Context(), svcCtx) + resp, err := l.UpdateCategory(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + } else { + httpx.OkJsonCtx(r.Context(), w, resp) + } + } +} diff --git a/gateway/internal/logic/createcategorylogic.go b/gateway/internal/logic/createcategorylogic.go new file mode 100644 index 0000000..34bb5c2 --- /dev/null +++ b/gateway/internal/logic/createcategorylogic.go @@ -0,0 +1,103 @@ +package logic + +import ( + "context" + "strings" + + "godemo/category/category" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" + + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type CreateCategoryLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCreateCategoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateCategoryLogic { + return &CreateCategoryLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *CreateCategoryLogic) CreateCategory(req *types.CreateCategoryReq) (resp *types.CreateCategoryResp, err error) { + // 1. 参数验证 + if err := validateCreateRequest(req); err != nil { + return nil, err + } + + // 2. 准备 RPC 请求 + rpcReq := &category.CreateCategoryRequest{ + SystemId: req.SystemID, + Name: req.Name, + Description: req.Description, + } + + // 可选参数处理 + if req.Alias != "" { + rpcReq.Alias = req.Alias + } + + if req.ParentID != "" { + rpcReq.ParentId = req.ParentID + } + + // 3. 调用 RPC 服务 + rpcResp, rpcErr := l.svcCtx.CategoryRpc.CreateCategory(l.ctx, rpcReq) + if rpcErr != nil { + return nil, rpcErr + } + + // 4. 构建响应 + resp = &types.CreateCategoryResp{ + ID: rpcResp.Category.Id, + } + + l.Logger.Infof("Category created successfully: ID=%s, Name=%s", resp.ID, req.Name) + return resp, nil +} + +// validateCreateRequest 验证创建分类请求 +func validateCreateRequest(req *types.CreateCategoryReq) error { + // 必需字段检查 + if strings.TrimSpace(req.SystemID) == "" { + return status.Error(codes.InvalidArgument, "systemId不能为空") + } + if strings.TrimSpace(req.Name) == "" { + return status.Error(codes.InvalidArgument, "name不能为空") + } + + // 名称长度限制 + if len(req.Name) > 50 { + return status.Error(codes.InvalidArgument, "alias cannot exceed 50 characters") + } + + // 别名格式验证 (只允许字母、数字、连字符和下划线) + // if req.Alias != "" { + // if len(req.Alias) > 50 { + // return errors.New("alias cannot exceed 50 characters") + // } + // for _, ch := range req.Alias { + // if !(ch >= 'a' && ch <= 'z') && + // !(ch >= 'A' && ch <= 'Z') && + // !(ch >= '0' && ch <= '9') && + // ch != '-' && ch != '_' { + // return errors.New("alias can only contain letters, numbers, hyphens and underscores") + // } + // } + // } + + // 描述长度限制 + if len(req.Description) > 500 { + return status.Error(codes.InvalidArgument, "description cannot exceed 500 characters") + } + + return nil +} diff --git a/gateway/internal/logic/deletecategorylogic.go b/gateway/internal/logic/deletecategorylogic.go new file mode 100644 index 0000000..ad3711e --- /dev/null +++ b/gateway/internal/logic/deletecategorylogic.go @@ -0,0 +1,30 @@ +package logic + +import ( + "context" + + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DeleteCategoryLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDeleteCategoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteCategoryLogic { + return &DeleteCategoryLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DeleteCategoryLogic) DeleteCategory(req *types.DeleteCategoryReq) (resp *types.BaseResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/gateway/internal/logic/getcategorylogic.go b/gateway/internal/logic/getcategorylogic.go new file mode 100644 index 0000000..c3ae346 --- /dev/null +++ b/gateway/internal/logic/getcategorylogic.go @@ -0,0 +1,59 @@ +package logic + +import ( + "context" + "time" + + "godemo/category/category" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetCategoryLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetCategoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCategoryLogic { + return &GetCategoryLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetCategoryLogic) GetCategory(req *types.GetCategoryReq) (resp *types.CategoryDetailResp, err error) { + rpcResp, err := l.svcCtx.CategoryRpc.GetCategory(l.ctx, &category.GetCategoryRequest{ + Id: req.ID, + }) + if err != nil { + return nil, err + } + // 转换主分类的时间 + createdAtStr := time.Unix(rpcResp.Category.CreatedAt, 0).Format(time.RFC3339) + updatedAtStr := "" + if rpcResp.Category.UpdatedAt != 0 { + updatedAtStr = time.Unix(rpcResp.Category.UpdatedAt, 0).Format(time.RFC3339) + } + + resp = &types.CategoryDetailResp{ + CategoryDetail: types.CategoryDetail{ + BaseCategory: types.BaseCategory{ + ID: rpcResp.Category.Id, + Name: rpcResp.Category.Name, + Alias: rpcResp.Category.Alias, + Description: rpcResp.Category.Description, + ParentID: rpcResp.Category.ParentId, + SystemID: rpcResp.Category.SystemId, + CreatedAt: createdAtStr, + UpdatedAt: updatedAtStr, + }, + Parent: nil, + }, + } + + return +} diff --git a/gateway/internal/logic/getcategorytreelogic.go b/gateway/internal/logic/getcategorytreelogic.go new file mode 100644 index 0000000..e1605ee --- /dev/null +++ b/gateway/internal/logic/getcategorytreelogic.go @@ -0,0 +1,30 @@ +package logic + +import ( + "context" + + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetCategoryTreeLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetCategoryTreeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetCategoryTreeLogic { + return &GetCategoryTreeLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetCategoryTreeLogic) GetCategoryTree(req *types.GetCategoryTreeReq) (resp *types.CategoryTreeResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/gateway/internal/logic/getsystemcategorieslogic.go b/gateway/internal/logic/getsystemcategorieslogic.go new file mode 100644 index 0000000..4537f3b --- /dev/null +++ b/gateway/internal/logic/getsystemcategorieslogic.go @@ -0,0 +1,97 @@ +package logic + +import ( + "context" + "strings" + "time" + + "godemo/category/category" + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" + + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type GetSystemCategoriesLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetSystemCategoriesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetSystemCategoriesLogic { + return &GetSystemCategoriesLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetSystemCategoriesLogic) GetSystemCategories(req *types.GetSystemCategoriesReq) (resp *types.ListCategoriesResp, err error) { + // 1. 验证系统ID + if strings.TrimSpace(req.SystemID) == "" { + return nil, status.Error(codes.InvalidArgument, "system_id is required") + } + + // 2. 准备 RPC 请求 + rpcReq := &category.GetSystemCategoriesRequest{ + SystemId: req.SystemID, + IncludeDescendants: req.IncludeDescendants, + } + + // 添加父分类ID过滤(如果提供) + if req.ParentID != "" { + rpcReq.ParentId = req.ParentID + } + + // 3. 调用 RPC 服务 + rpcResp, rpcErr := l.svcCtx.CategoryRpc.GetSystemCategories(l.ctx, rpcReq) + if rpcErr != nil { + st, ok := status.FromError(rpcErr) + if ok && st.Code() == codes.NotFound { + // 返回空列表而不是错误 + return &types.ListCategoriesResp{ + Total: 0, + List: []types.BaseCategory{}, + }, nil + } + l.Logger.Errorf("RPC error: %v", rpcErr) + return nil, rpcErr + } + + // 4. 转换响应数据 + categories := make([]types.BaseCategory, 0, len(rpcResp.Categories)) + for _, cat := range rpcResp.Categories { + // 转换时间格式 + createdAt := "" + if cat.CreatedAt > 0 { + createdAt = time.Unix(cat.CreatedAt, 0).Format(time.RFC3339) + } + + updatedAt := "" + if cat.UpdatedAt > 0 { + updatedAt = time.Unix(cat.UpdatedAt, 0).Format(time.RFC3339) + } + + categories = append(categories, types.BaseCategory{ + ID: cat.Id, + SystemID: cat.SystemId, + Name: cat.Name, + Alias: cat.Alias, + ParentID: cat.ParentId, + Description: cat.Description, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }) + } + + // 5. 构建响应 + resp = &types.ListCategoriesResp{ + Total: rpcResp.Total, + List: categories, + } + + l.Logger.Infof("Retrieved %d categories for system %s (parent: %s)", len(categories), req.SystemID, req.ParentID) + return resp, nil +} diff --git a/gateway/internal/logic/listcategorieslogic.go b/gateway/internal/logic/listcategorieslogic.go new file mode 100644 index 0000000..3072c43 --- /dev/null +++ b/gateway/internal/logic/listcategorieslogic.go @@ -0,0 +1,30 @@ +package logic + +import ( + "context" + + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListCategoriesLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewListCategoriesLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListCategoriesLogic { + return &ListCategoriesLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *ListCategoriesLogic) ListCategories(req *types.ListCategoriesReq) (resp *types.ListCategoriesResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/gateway/internal/logic/updatecategorylogic.go b/gateway/internal/logic/updatecategorylogic.go new file mode 100644 index 0000000..accd5c7 --- /dev/null +++ b/gateway/internal/logic/updatecategorylogic.go @@ -0,0 +1,30 @@ +package logic + +import ( + "context" + + "godemo/gateway/internal/svc" + "godemo/gateway/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateCategoryLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewUpdateCategoryLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateCategoryLogic { + return &UpdateCategoryLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateCategoryLogic) UpdateCategory(req *types.UpdateCategoryReq) (resp *types.BaseResp, err error) { + // todo: add your logic here and delete this line + + return +} diff --git a/gateway/internal/svc/servicecontext.go b/gateway/internal/svc/servicecontext.go index e02c900..3afd716 100644 --- a/gateway/internal/svc/servicecontext.go +++ b/gateway/internal/svc/servicecontext.go @@ -1,6 +1,7 @@ package svc import ( + "godemo/category/category" "godemo/gateway/internal/config" "godemo/user/user" @@ -8,14 +9,15 @@ import ( ) type ServiceContext struct { - Config config.Config - UserRpc user.UserClient + Config config.Config + UserRpc user.UserClient + CategoryRpc category.CategoryClient } func NewServiceContext(c config.Config) *ServiceContext { - conn := zrpc.MustNewClient(c.UserRpc).Conn() return &ServiceContext{ - Config: c, - UserRpc: user.NewUserClient(conn), + Config: c, + UserRpc: user.NewUserClient(zrpc.MustNewClient(c.UserRpc).Conn()), + CategoryRpc: category.NewCategoryClient(zrpc.MustNewClient(c.CategoryRpc).Conn()), } } diff --git a/gateway/internal/types/types.go b/gateway/internal/types/types.go index de9d39c..3b7fc05 100644 --- a/gateway/internal/types/types.go +++ b/gateway/internal/types/types.go @@ -3,6 +3,65 @@ package types +type BaseCategory struct { + ID string `json:"id"` // 分类ID (UUID) + SystemID string `json:"systemId"` // 所属系统ID + Name string `json:"name"` // 分类名称 + Alias string `json:"alias"` // URL别名 + ParentID string `json:"parentId"` // 父分类ID (可为空) + Description string `json:"description"` // 分类描述 + CreatedAt string `json:"createdAt"` // 创建时间 (ISO8601) + UpdatedAt string `json:"updatedAt"` // 更新时间 (ISO8601) +} + +type BaseResp struct { + Code int `json:"code"` // 状态码 (0=成功) + Message string `json:"message"` // 消息 +} + +type CategoryDetail struct { + BaseCategory + Parent *BaseCategory `json:"parent"` // 父分类信息 +} + +type CategoryDetailResp struct { + CategoryDetail +} + +type CategoryTreeResp struct { + Root TreeNode `json:"root"` // 分类树根节点 +} + +type CreateCategoryReq struct { + SystemID string `json:"systemId" validate:"required"` // 所属系统ID + Name string `json:"name" validate:"required"` // 分类名称 + Alias string `json:"alias,optional"` // URL别名 + ParentID string `json:"parentId,optional"` // 父分类ID + Description string `json:"description,optional"` // 分类描述 +} + +type CreateCategoryResp struct { + ID string `json:"id"` // 新创建的分类ID +} + +type DeleteCategoryReq struct { + ID string `path:"id"` // 分类ID +} + +type GetCategoryReq struct { + ID string `path:"id"` // 分类ID +} + +type GetCategoryTreeReq struct { + ID string `path:"id"` // 起始分类ID +} + +type GetSystemCategoriesReq struct { + SystemID string `path:"system_id"` // 系统ID + ParentID string `form:"parentId,optional"` // 父分类ID (可选) + IncludeDescendants bool `form:"includeDescendants,default=true"` // 是否包含子分类 +} + type GetUserInfoReq struct { UserId string `path:"user_id"` } @@ -15,6 +74,19 @@ type GetUserInfoResp struct { Roles []string `json:"roles"` } +type ListCategoriesReq struct { + SystemID string `form:"systemId,optional"` // 按系统ID过滤 + ParentID string `form:"parentId,optional"` // 按父分类ID过滤 + Name string `form:"name,optional"` // 按名称模糊搜索 + Page int `form:"page,default=1"` // 页码 + PageSize int `form:"pageSize,default=20"` // 每页数量 +} + +type ListCategoriesResp struct { + Total int64 `json:"total"` // 总数 + List []BaseCategory `json:"list"` // 分类列表 +} + type LoginReq struct { Username string `json:"username"` Password string `json:"password"` @@ -42,3 +114,16 @@ type RegisterReq struct { type RegisterResp struct { UserId string `json:"user_id"` } + +type TreeNode struct { + BaseCategory + Children []*TreeNode `json:"children"` // 子分类列表 +} + +type UpdateCategoryReq struct { + ID string `path:"id"` // 分类ID + Name string `json:"name,optional"` // 新名称 + Alias string `json:"alias,optional"` // 新别名 + ParentID string `json:"parentId,optional"` // 新父分类ID + Description string `json:"description,optional"` // 新描述 +} diff --git a/go.mod b/go.mod index f739017..99cb79d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/lib/pq v1.10.9 github.com/zeromicro/go-zero v1.8.3 golang.org/x/crypto v0.36.0 - google.golang.org/grpc v1.72.1 + google.golang.org/grpc v1.72.2 google.golang.org/protobuf v1.36.6 ) @@ -19,12 +19,15 @@ require ( github.com/go-sql-driver/mysql v1.9.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/minio/crc64nvme v1.0.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/rs/xid v1.6.0 // indirect ) require ( + github.com/Masterminds/squirrel v1.5.4 github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum index 167291b..1c5f673 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0= @@ -103,6 +105,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -164,6 +170,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= @@ -276,8 +283,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1: google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/rpc/category.proto b/rpc/category.proto index 57ea223..d140c87 100644 --- a/rpc/category.proto +++ b/rpc/category.proto @@ -28,6 +28,9 @@ service Category { // 查询过滤 rpc ListCategories(ListCategoryRequest) returns (CategoryListResponse); rpc CheckAlias(CheckAliasRequest) returns (CheckAliasResponse); + + // 根据系统ID获取分类 + rpc GetSystemCategories(GetSystemCategoriesRequest) returns (GetSystemCategoriesResponse); } // 健康检查请求 @@ -175,4 +178,19 @@ message CheckAliasRequest { message CheckAliasResponse { bool is_available = 1; string existing_id = 2; // 冲突时返回已存在的分类ID +} + +message GetSystemCategoriesRequest { + string system_id = 1; // 系统标识 (必需) + string parent_id = 2; // 父分类ID (可选) + int32 page = 3; // 分页参数 + int32 page_size = 4; // 分页参数 + bool include_descendants = 5; // 是否包含后代 +} + +message GetSystemCategoriesResponse { + repeated CategoryInfo categories = 1; + int64 total = 2; + int32 current_page = 3; + int32 page_size = 4; } \ No newline at end of file diff --git a/user/etc/user.yaml b/user/etc/user.yaml index 0e9be9a..b9a19b7 100644 --- a/user/etc/user.yaml +++ b/user/etc/user.yaml @@ -6,8 +6,8 @@ Etcd: - localhost:2379 Key: user.rpc -DataSource: "postgres://postgres:postgres@localhost:5432/godemo?sslmode=disable" +DataSource: "postgres://postgres:postgres@localhost:19732/godemo?sslmode=disable" JwtAuth: AccessSecret: "your-secure-secret" - AccessExpire: 900 # 可选,单位秒,15分钟 + AccessExpire: 3600 # 可选,单位秒,15分钟