实现category service基础接口
This commit is contained in:
@ -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:
|
||||
|
||||
**接口定义**
|
||||
|
||||
- 用户注册
|
||||
- 用户登录
|
||||
- 获取用户信息
|
||||
- 用户注册
|
||||
- 用户注销
|
||||
|
||||
## 文件服务
|
||||
|
||||
138
api/gateway.api
138
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)
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
@ -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
|
||||
167
category/internal/logic/getsystemcategorieslogic.go
Normal file
167
category/internal/logic/getsystemcategorieslogic.go
Normal file
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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, // 赋值
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,4 +10,10 @@ UserRpc:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- 127.0.0.1:2379
|
||||
Key: user.rpc
|
||||
Key: user.rpc
|
||||
|
||||
CategoryRpc:
|
||||
Etcd:
|
||||
Hosts:
|
||||
- 127.0.0.1:2379
|
||||
Key: category.rpc
|
||||
@ -11,5 +11,6 @@ type Config struct {
|
||||
AccessSecret string
|
||||
AccessExpire int64
|
||||
}
|
||||
UserRpc zrpc.RpcClientConf
|
||||
UserRpc zrpc.RpcClientConf
|
||||
CategoryRpc zrpc.RpcClientConf
|
||||
}
|
||||
|
||||
50
gateway/internal/handler/createcategoryhandler.go
Normal file
50
gateway/internal/handler/createcategoryhandler.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
gateway/internal/handler/deletecategoryhandler.go
Normal file
28
gateway/internal/handler/deletecategoryhandler.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
gateway/internal/handler/getcategoryhandler.go
Normal file
28
gateway/internal/handler/getcategoryhandler.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
gateway/internal/handler/getcategorytreehandler.go
Normal file
28
gateway/internal/handler/getcategorytreehandler.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
gateway/internal/handler/getsystemcategorieshandler.go
Normal file
28
gateway/internal/handler/getsystemcategorieshandler.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
28
gateway/internal/handler/listcategorieshandler.go
Normal file
28
gateway/internal/handler/listcategorieshandler.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"),
|
||||
)
|
||||
}
|
||||
|
||||
28
gateway/internal/handler/updatecategoryhandler.go
Normal file
28
gateway/internal/handler/updatecategoryhandler.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
103
gateway/internal/logic/createcategorylogic.go
Normal file
103
gateway/internal/logic/createcategorylogic.go
Normal file
@ -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
|
||||
}
|
||||
30
gateway/internal/logic/deletecategorylogic.go
Normal file
30
gateway/internal/logic/deletecategorylogic.go
Normal file
@ -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
|
||||
}
|
||||
59
gateway/internal/logic/getcategorylogic.go
Normal file
59
gateway/internal/logic/getcategorylogic.go
Normal file
@ -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
|
||||
}
|
||||
30
gateway/internal/logic/getcategorytreelogic.go
Normal file
30
gateway/internal/logic/getcategorytreelogic.go
Normal file
@ -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
|
||||
}
|
||||
97
gateway/internal/logic/getsystemcategorieslogic.go
Normal file
97
gateway/internal/logic/getsystemcategorieslogic.go
Normal file
@ -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
|
||||
}
|
||||
30
gateway/internal/logic/listcategorieslogic.go
Normal file
30
gateway/internal/logic/listcategorieslogic.go
Normal file
@ -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
|
||||
}
|
||||
30
gateway/internal/logic/updatecategorylogic.go
Normal file
30
gateway/internal/logic/updatecategorylogic.go
Normal file
@ -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
|
||||
}
|
||||
@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"` // 新描述
|
||||
}
|
||||
|
||||
5
go.mod
5
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
|
||||
|
||||
11
go.sum
11
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=
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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分钟
|
||||
|
||||
Reference in New Issue
Block a user