diff --git a/.gitignore b/.gitignore index 3de7fa2c7ce1a7890554d8b88a56c8458c501716..df72416fca15a294c042143f859cc4fb2ad05f66 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ Thumbs.db *.key *.log bin/ +cabinet +cmd/Cabinet/cabinet # Develop tools .vscode/ diff --git a/api/bill/v1/bill.pb.go b/api/bill/v1/bill.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..ab17eb7b17a251f63e8d905dd09e4b18e0ab5bf6 --- /dev/null +++ b/api/bill/v1/bill.pb.go @@ -0,0 +1,599 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc v3.19.4 +// source: api/bill/v1/bill.proto + +package v1 + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 查询账单详情请求 +type GetBillDetailRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + BillId string `protobuf:"bytes,1,opt,name=bill_id,json=billId,proto3" json:"bill_id,omitempty"` + TransactionNo string `protobuf:"bytes,2,opt,name=transaction_no,json=transactionNo,proto3" json:"transaction_no,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBillDetailRequest) Reset() { + *x = GetBillDetailRequest{} + mi := &file_api_bill_v1_bill_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBillDetailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBillDetailRequest) ProtoMessage() {} + +func (x *GetBillDetailRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_bill_v1_bill_proto_msgTypes[0] + 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 GetBillDetailRequest.ProtoReflect.Descriptor instead. +func (*GetBillDetailRequest) Descriptor() ([]byte, []int) { + return file_api_bill_v1_bill_proto_rawDescGZIP(), []int{0} +} + +func (x *GetBillDetailRequest) GetBillId() string { + if x != nil { + return x.BillId + } + return "" +} + +func (x *GetBillDetailRequest) GetTransactionNo() string { + if x != nil { + return x.TransactionNo + } + return "" +} + +// 查询账单详情响应 +type GetBillDetailReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + BillId string `protobuf:"bytes,2,opt,name=bill_id,json=billId,proto3" json:"bill_id,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 统一为 int64 + Amount float64 `protobuf:"fixed64,4,opt,name=amount,proto3" json:"amount,omitempty"` // 使用 double 保持精度 + AmountFormatted string `protobuf:"bytes,5,opt,name=amount_formatted,json=amountFormatted,proto3" json:"amount_formatted,omitempty"` + BillType int64 `protobuf:"varint,6,opt,name=bill_type,json=billType,proto3" json:"bill_type,omitempty"` + BillTypeLabel string `protobuf:"bytes,7,opt,name=bill_type_label,json=billTypeLabel,proto3" json:"bill_type_label,omitempty"` + PaymentMethod int64 `protobuf:"varint,8,opt,name=payment_method,json=paymentMethod,proto3" json:"payment_method,omitempty"` + PaymentLabel string `protobuf:"bytes,9,opt,name=payment_label,json=paymentLabel,proto3" json:"payment_label,omitempty"` + TransactionNo string `protobuf:"bytes,10,opt,name=transaction_no,json=transactionNo,proto3" json:"transaction_no,omitempty"` + TransactionTime *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=transaction_time,json=transactionTime,proto3" json:"transaction_time,omitempty"` + Status int64 `protobuf:"varint,12,opt,name=status,proto3" json:"status,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,14,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBillDetailReply) Reset() { + *x = GetBillDetailReply{} + mi := &file_api_bill_v1_bill_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBillDetailReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBillDetailReply) ProtoMessage() {} + +func (x *GetBillDetailReply) ProtoReflect() protoreflect.Message { + mi := &file_api_bill_v1_bill_proto_msgTypes[1] + 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 GetBillDetailReply.ProtoReflect.Descriptor instead. +func (*GetBillDetailReply) Descriptor() ([]byte, []int) { + return file_api_bill_v1_bill_proto_rawDescGZIP(), []int{1} +} + +func (x *GetBillDetailReply) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *GetBillDetailReply) GetBillId() string { + if x != nil { + return x.BillId + } + return "" +} + +func (x *GetBillDetailReply) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *GetBillDetailReply) GetAmount() float64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *GetBillDetailReply) GetAmountFormatted() string { + if x != nil { + return x.AmountFormatted + } + return "" +} + +func (x *GetBillDetailReply) GetBillType() int64 { + if x != nil { + return x.BillType + } + return 0 +} + +func (x *GetBillDetailReply) GetBillTypeLabel() string { + if x != nil { + return x.BillTypeLabel + } + return "" +} + +func (x *GetBillDetailReply) GetPaymentMethod() int64 { + if x != nil { + return x.PaymentMethod + } + return 0 +} + +func (x *GetBillDetailReply) GetPaymentLabel() string { + if x != nil { + return x.PaymentLabel + } + return "" +} + +func (x *GetBillDetailReply) GetTransactionNo() string { + if x != nil { + return x.TransactionNo + } + return "" +} + +func (x *GetBillDetailReply) GetTransactionTime() *timestamppb.Timestamp { + if x != nil { + return x.TransactionTime + } + return nil +} + +func (x *GetBillDetailReply) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *GetBillDetailReply) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *GetBillDetailReply) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +// 账单记录(统一字段命名) +type BillRecord struct { + state protoimpl.MessageState `protogen:"open.v1"` + BillId string `protobuf:"bytes,1,opt,name=bill_id,json=billId,proto3" json:"bill_id,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 统一为 int64 + Amount float64 `protobuf:"fixed64,3,opt,name=amount,proto3" json:"amount,omitempty"` // 统一为 double + AmountFormatted string `protobuf:"bytes,4,opt,name=amount_formatted,json=amountFormatted,proto3" json:"amount_formatted,omitempty"` + BillType int64 `protobuf:"varint,5,opt,name=bill_type,json=billType,proto3" json:"bill_type,omitempty"` // 统一为 int64 + BillTypeLabel string `protobuf:"bytes,6,opt,name=bill_type_label,json=billTypeLabel,proto3" json:"bill_type_label,omitempty"` + PaymentMethod int64 `protobuf:"varint,7,opt,name=payment_method,json=paymentMethod,proto3" json:"payment_method,omitempty"` + PaymentLabel string `protobuf:"bytes,8,opt,name=payment_label,json=paymentLabel,proto3" json:"payment_label,omitempty"` + TransactionNo string `protobuf:"bytes,9,opt,name=transaction_no,json=transactionNo,proto3" json:"transaction_no,omitempty"` + TransactionTime *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=transaction_time,json=transactionTime,proto3" json:"transaction_time,omitempty"` + Status int64 `protobuf:"varint,11,opt,name=status,proto3" json:"status,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BillRecord) Reset() { + *x = BillRecord{} + mi := &file_api_bill_v1_bill_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BillRecord) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BillRecord) ProtoMessage() {} + +func (x *BillRecord) ProtoReflect() protoreflect.Message { + mi := &file_api_bill_v1_bill_proto_msgTypes[2] + 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 BillRecord.ProtoReflect.Descriptor instead. +func (*BillRecord) Descriptor() ([]byte, []int) { + return file_api_bill_v1_bill_proto_rawDescGZIP(), []int{2} +} + +func (x *BillRecord) GetBillId() string { + if x != nil { + return x.BillId + } + return "" +} + +func (x *BillRecord) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *BillRecord) GetAmount() float64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *BillRecord) GetAmountFormatted() string { + if x != nil { + return x.AmountFormatted + } + return "" +} + +func (x *BillRecord) GetBillType() int64 { + if x != nil { + return x.BillType + } + return 0 +} + +func (x *BillRecord) GetBillTypeLabel() string { + if x != nil { + return x.BillTypeLabel + } + return "" +} + +func (x *BillRecord) GetPaymentMethod() int64 { + if x != nil { + return x.PaymentMethod + } + return 0 +} + +func (x *BillRecord) GetPaymentLabel() string { + if x != nil { + return x.PaymentLabel + } + return "" +} + +func (x *BillRecord) GetTransactionNo() string { + if x != nil { + return x.TransactionNo + } + return "" +} + +func (x *BillRecord) GetTransactionTime() *timestamppb.Timestamp { + if x != nil { + return x.TransactionTime + } + return nil +} + +func (x *BillRecord) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *BillRecord) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +// 查询账单列表请求 +type ListBillsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Page int32 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + BillType int64 `protobuf:"varint,4,opt,name=bill_type,json=billType,proto3" json:"bill_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListBillsRequest) Reset() { + *x = ListBillsRequest{} + mi := &file_api_bill_v1_bill_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListBillsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListBillsRequest) ProtoMessage() {} + +func (x *ListBillsRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_bill_v1_bill_proto_msgTypes[3] + 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 ListBillsRequest.ProtoReflect.Descriptor instead. +func (*ListBillsRequest) Descriptor() ([]byte, []int) { + return file_api_bill_v1_bill_proto_rawDescGZIP(), []int{3} +} + +func (x *ListBillsRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListBillsRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListBillsRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ListBillsRequest) GetBillType() int64 { + if x != nil { + return x.BillType + } + return 0 +} + +// 查询账单列表响应 +type ListBillsReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + List []*BillRecord `protobuf:"bytes,2,rep,name=list,proto3" json:"list,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListBillsReply) Reset() { + *x = ListBillsReply{} + mi := &file_api_bill_v1_bill_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListBillsReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListBillsReply) ProtoMessage() {} + +func (x *ListBillsReply) ProtoReflect() protoreflect.Message { + mi := &file_api_bill_v1_bill_proto_msgTypes[4] + 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 ListBillsReply.ProtoReflect.Descriptor instead. +func (*ListBillsReply) Descriptor() ([]byte, []int) { + return file_api_bill_v1_bill_proto_rawDescGZIP(), []int{4} +} + +func (x *ListBillsReply) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *ListBillsReply) GetList() []*BillRecord { + if x != nil { + return x.List + } + return nil +} + +var File_api_bill_v1_bill_proto protoreflect.FileDescriptor + +const file_api_bill_v1_bill_proto_rawDesc = "" + + "\n" + + "\x16api/bill/v1/bill.proto\x12\vapi.bill.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"V\n" + + "\x14GetBillDetailRequest\x12\x17\n" + + "\abill_id\x18\x01 \x01(\tR\x06billId\x12%\n" + + "\x0etransaction_no\x18\x02 \x01(\tR\rtransactionNo\"\xa6\x04\n" + + "\x12GetBillDetailReply\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x04R\x02id\x12\x17\n" + + "\abill_id\x18\x02 \x01(\tR\x06billId\x12\x17\n" + + "\auser_id\x18\x03 \x01(\x03R\x06userId\x12\x16\n" + + "\x06amount\x18\x04 \x01(\x01R\x06amount\x12)\n" + + "\x10amount_formatted\x18\x05 \x01(\tR\x0famountFormatted\x12\x1b\n" + + "\tbill_type\x18\x06 \x01(\x03R\bbillType\x12&\n" + + "\x0fbill_type_label\x18\a \x01(\tR\rbillTypeLabel\x12%\n" + + "\x0epayment_method\x18\b \x01(\x03R\rpaymentMethod\x12#\n" + + "\rpayment_label\x18\t \x01(\tR\fpaymentLabel\x12%\n" + + "\x0etransaction_no\x18\n" + + " \x01(\tR\rtransactionNo\x12E\n" + + "\x10transaction_time\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\x0ftransactionTime\x12\x16\n" + + "\x06status\x18\f \x01(\x03R\x06status\x129\n" + + "\n" + + "created_at\x18\r \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x0e \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\"\xd3\x03\n" + + "\n" + + "BillRecord\x12\x17\n" + + "\abill_id\x18\x01 \x01(\tR\x06billId\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x16\n" + + "\x06amount\x18\x03 \x01(\x01R\x06amount\x12)\n" + + "\x10amount_formatted\x18\x04 \x01(\tR\x0famountFormatted\x12\x1b\n" + + "\tbill_type\x18\x05 \x01(\x03R\bbillType\x12&\n" + + "\x0fbill_type_label\x18\x06 \x01(\tR\rbillTypeLabel\x12%\n" + + "\x0epayment_method\x18\a \x01(\x03R\rpaymentMethod\x12#\n" + + "\rpayment_label\x18\b \x01(\tR\fpaymentLabel\x12%\n" + + "\x0etransaction_no\x18\t \x01(\tR\rtransactionNo\x12E\n" + + "\x10transaction_time\x18\n" + + " \x01(\v2\x1a.google.protobuf.TimestampR\x0ftransactionTime\x12\x16\n" + + "\x06status\x18\v \x01(\x03R\x06status\x129\n" + + "\n" + + "created_at\x18\f \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\"y\n" + + "\x10ListBillsRequest\x12\x12\n" + + "\x04page\x18\x01 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x17\n" + + "\auser_id\x18\x03 \x01(\x03R\x06userId\x12\x1b\n" + + "\tbill_type\x18\x04 \x01(\x03R\bbillType\"S\n" + + "\x0eListBillsReply\x12\x14\n" + + "\x05total\x18\x01 \x01(\x03R\x05total\x12+\n" + + "\x04list\x18\x02 \x03(\v2\x17.api.bill.v1.BillRecordR\x04list2\xdd\x01\n" + + "\vBillService\x12o\n" + + "\rGetBillDetail\x12!.api.bill.v1.GetBillDetailRequest\x1a\x1f.api.bill.v1.GetBillDetailReply\"\x1a\x82\xd3\xe4\x93\x02\x14:\x01*\"\x0f/v1/bill/detail\x12]\n" + + "\tListBills\x12\x1d.api.bill.v1.ListBillsRequest\x1a\x1b.api.bill.v1.ListBillsReply\"\x14\x82\xd3\xe4\x93\x02\x0e:\x01*\"\t/v1/billsB'\n" + + "\vapi.bill.v1P\x01Z\x16Cabinet/api/bill/v1;v1b\x06proto3" + +var ( + file_api_bill_v1_bill_proto_rawDescOnce sync.Once + file_api_bill_v1_bill_proto_rawDescData []byte +) + +func file_api_bill_v1_bill_proto_rawDescGZIP() []byte { + file_api_bill_v1_bill_proto_rawDescOnce.Do(func() { + file_api_bill_v1_bill_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_bill_v1_bill_proto_rawDesc), len(file_api_bill_v1_bill_proto_rawDesc))) + }) + return file_api_bill_v1_bill_proto_rawDescData +} + +var file_api_bill_v1_bill_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_api_bill_v1_bill_proto_goTypes = []any{ + (*GetBillDetailRequest)(nil), // 0: api.bill.v1.GetBillDetailRequest + (*GetBillDetailReply)(nil), // 1: api.bill.v1.GetBillDetailReply + (*BillRecord)(nil), // 2: api.bill.v1.BillRecord + (*ListBillsRequest)(nil), // 3: api.bill.v1.ListBillsRequest + (*ListBillsReply)(nil), // 4: api.bill.v1.ListBillsReply + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_api_bill_v1_bill_proto_depIdxs = []int32{ + 5, // 0: api.bill.v1.GetBillDetailReply.transaction_time:type_name -> google.protobuf.Timestamp + 5, // 1: api.bill.v1.GetBillDetailReply.created_at:type_name -> google.protobuf.Timestamp + 5, // 2: api.bill.v1.GetBillDetailReply.updated_at:type_name -> google.protobuf.Timestamp + 5, // 3: api.bill.v1.BillRecord.transaction_time:type_name -> google.protobuf.Timestamp + 5, // 4: api.bill.v1.BillRecord.created_at:type_name -> google.protobuf.Timestamp + 2, // 5: api.bill.v1.ListBillsReply.list:type_name -> api.bill.v1.BillRecord + 0, // 6: api.bill.v1.BillService.GetBillDetail:input_type -> api.bill.v1.GetBillDetailRequest + 3, // 7: api.bill.v1.BillService.ListBills:input_type -> api.bill.v1.ListBillsRequest + 1, // 8: api.bill.v1.BillService.GetBillDetail:output_type -> api.bill.v1.GetBillDetailReply + 4, // 9: api.bill.v1.BillService.ListBills:output_type -> api.bill.v1.ListBillsReply + 8, // [8:10] is the sub-list for method output_type + 6, // [6:8] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_api_bill_v1_bill_proto_init() } +func file_api_bill_v1_bill_proto_init() { + if File_api_bill_v1_bill_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_bill_v1_bill_proto_rawDesc), len(file_api_bill_v1_bill_proto_rawDesc)), + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_bill_v1_bill_proto_goTypes, + DependencyIndexes: file_api_bill_v1_bill_proto_depIdxs, + MessageInfos: file_api_bill_v1_bill_proto_msgTypes, + }.Build() + File_api_bill_v1_bill_proto = out.File + file_api_bill_v1_bill_proto_goTypes = nil + file_api_bill_v1_bill_proto_depIdxs = nil +} diff --git a/api/bill/v1/bill.proto b/api/bill/v1/bill.proto new file mode 100644 index 0000000000000000000000000000000000000000..a546930ff2aff0dbe022008b5174829e29da4a48 --- /dev/null +++ b/api/bill/v1/bill.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; + +package api.bill.v1; + +import "google/api/annotations.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "Cabinet/api/bill/v1;v1"; +option java_multiple_files = true; +option java_package = "api.bill.v1"; + +service BillService { + // 根据账单ID或交易单号查询账单详情 + rpc GetBillDetail (GetBillDetailRequest) returns (GetBillDetailReply) { + option (google.api.http) = { + post: "/v1/bill/detail" + body: "*" // 添加这一行,表示使用整个请求消息作为body + }; + } + + // 分页查询账单列表,支持按时间和类型筛选 + rpc ListBills (ListBillsRequest) returns (ListBillsReply) { + option (google.api.http) = { + post: "/v1/bills" + body: "*" // 添加这一行,表示使用整个请求消息作为body + }; + } +} + +// 查询账单详情请求 +message GetBillDetailRequest { + string bill_id = 1; + string transaction_no = 2; +} + +// 查询账单详情响应 +message GetBillDetailReply { + uint64 id = 1; + string bill_id = 2; + int64 user_id = 3; // 统一为 int64 + double amount = 4; // 使用 double 保持精度 + string amount_formatted = 5; + int64 bill_type = 6; + string bill_type_label = 7; + int64 payment_method = 8; + string payment_label = 9; + string transaction_no = 10; + google.protobuf.Timestamp transaction_time = 11; + int64 status = 12; + google.protobuf.Timestamp created_at = 13; + google.protobuf.Timestamp updated_at = 14; +} + +// 账单记录(统一字段命名) +message BillRecord { + string bill_id = 1; + int64 user_id = 2; // 统一为 int64 + double amount = 3; // 统一为 double + string amount_formatted = 4; + int64 bill_type = 5; // 统一为 int64 + string bill_type_label = 6; + int64 payment_method = 7; + string payment_label = 8; + string transaction_no = 9; + google.protobuf.Timestamp transaction_time = 10; + int64 status = 11; + google.protobuf.Timestamp created_at = 12; +} + +// 查询账单列表请求 +message ListBillsRequest { + int32 page = 1; + int32 page_size = 2; + int64 user_id = 3; + int64 bill_type = 4; +} + +// 查询账单列表响应 +message ListBillsReply { + int64 total = 1; + repeated BillRecord list = 2; +} \ No newline at end of file diff --git a/api/bill/v1/bill_grpc.pb.go b/api/bill/v1/bill_grpc.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..a1c5d8550426cf17c253e934161d4e8134dbad09 --- /dev/null +++ b/api/bill/v1/bill_grpc.pb.go @@ -0,0 +1,163 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.19.4 +// source: api/bill/v1/bill.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + BillService_GetBillDetail_FullMethodName = "/api.bill.v1.BillService/GetBillDetail" + BillService_ListBills_FullMethodName = "/api.bill.v1.BillService/ListBills" +) + +// BillServiceClient is the client API for BillService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type BillServiceClient interface { + // 根据账单ID或交易单号查询账单详情 + GetBillDetail(ctx context.Context, in *GetBillDetailRequest, opts ...grpc.CallOption) (*GetBillDetailReply, error) + // 分页查询账单列表,支持按时间和类型筛选 + ListBills(ctx context.Context, in *ListBillsRequest, opts ...grpc.CallOption) (*ListBillsReply, error) +} + +type billServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewBillServiceClient(cc grpc.ClientConnInterface) BillServiceClient { + return &billServiceClient{cc} +} + +func (c *billServiceClient) GetBillDetail(ctx context.Context, in *GetBillDetailRequest, opts ...grpc.CallOption) (*GetBillDetailReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetBillDetailReply) + err := c.cc.Invoke(ctx, BillService_GetBillDetail_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *billServiceClient) ListBills(ctx context.Context, in *ListBillsRequest, opts ...grpc.CallOption) (*ListBillsReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListBillsReply) + err := c.cc.Invoke(ctx, BillService_ListBills_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BillServiceServer is the server API for BillService service. +// All implementations must embed UnimplementedBillServiceServer +// for forward compatibility. +type BillServiceServer interface { + // 根据账单ID或交易单号查询账单详情 + GetBillDetail(context.Context, *GetBillDetailRequest) (*GetBillDetailReply, error) + // 分页查询账单列表,支持按时间和类型筛选 + ListBills(context.Context, *ListBillsRequest) (*ListBillsReply, error) + mustEmbedUnimplementedBillServiceServer() +} + +// UnimplementedBillServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedBillServiceServer struct{} + +func (UnimplementedBillServiceServer) GetBillDetail(context.Context, *GetBillDetailRequest) (*GetBillDetailReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBillDetail not implemented") +} +func (UnimplementedBillServiceServer) ListBills(context.Context, *ListBillsRequest) (*ListBillsReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListBills not implemented") +} +func (UnimplementedBillServiceServer) mustEmbedUnimplementedBillServiceServer() {} +func (UnimplementedBillServiceServer) testEmbeddedByValue() {} + +// UnsafeBillServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to BillServiceServer will +// result in compilation errors. +type UnsafeBillServiceServer interface { + mustEmbedUnimplementedBillServiceServer() +} + +func RegisterBillServiceServer(s grpc.ServiceRegistrar, srv BillServiceServer) { + // If the following call pancis, it indicates UnimplementedBillServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&BillService_ServiceDesc, srv) +} + +func _BillService_GetBillDetail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBillDetailRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BillServiceServer).GetBillDetail(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: BillService_GetBillDetail_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BillServiceServer).GetBillDetail(ctx, req.(*GetBillDetailRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BillService_ListBills_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListBillsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BillServiceServer).ListBills(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: BillService_ListBills_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BillServiceServer).ListBills(ctx, req.(*ListBillsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// BillService_ServiceDesc is the grpc.ServiceDesc for BillService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var BillService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "api.bill.v1.BillService", + HandlerType: (*BillServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetBillDetail", + Handler: _BillService_GetBillDetail_Handler, + }, + { + MethodName: "ListBills", + Handler: _BillService_ListBills_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/bill/v1/bill.proto", +} diff --git a/api/bill/v1/bill_http.pb.go b/api/bill/v1/bill_http.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..442a4d265134c4a43a9c0fe0593fb78c331aeef0 --- /dev/null +++ b/api/bill/v1/bill_http.pb.go @@ -0,0 +1,123 @@ +// Code generated by protoc-gen-go-http. DO NOT EDIT. +// versions: +// - protoc-gen-go-http v2.9.0 +// - protoc v3.19.4 +// source: api/bill/v1/bill.proto + +package v1 + +import ( + context "context" + http "github.com/go-kratos/kratos/v2/transport/http" + binding "github.com/go-kratos/kratos/v2/transport/http/binding" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the kratos package it is being compiled against. +var _ = new(context.Context) +var _ = binding.EncodeURL + +const _ = http.SupportPackageIsVersion1 + +const OperationBillServiceGetBillDetail = "/api.bill.v1.BillService/GetBillDetail" +const OperationBillServiceListBills = "/api.bill.v1.BillService/ListBills" + +type BillServiceHTTPServer interface { + // GetBillDetail 根据账单ID或交易单号查询账单详情 + GetBillDetail(context.Context, *GetBillDetailRequest) (*GetBillDetailReply, error) + // ListBills 分页查询账单列表,支持按时间和类型筛选 + ListBills(context.Context, *ListBillsRequest) (*ListBillsReply, error) +} + +func RegisterBillServiceHTTPServer(s *http.Server, srv BillServiceHTTPServer) { + r := s.Route("/") + r.POST("/v1/bill/detail", _BillService_GetBillDetail0_HTTP_Handler(srv)) + r.POST("/v1/bills", _BillService_ListBills0_HTTP_Handler(srv)) +} + +func _BillService_GetBillDetail0_HTTP_Handler(srv BillServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in GetBillDetailRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationBillServiceGetBillDetail) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.GetBillDetail(ctx, req.(*GetBillDetailRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*GetBillDetailReply) + return ctx.Result(200, reply) + } +} + +func _BillService_ListBills0_HTTP_Handler(srv BillServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in ListBillsRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationBillServiceListBills) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.ListBills(ctx, req.(*ListBillsRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*ListBillsReply) + return ctx.Result(200, reply) + } +} + +type BillServiceHTTPClient interface { + // GetBillDetail 根据账单ID或交易单号查询账单详情 + GetBillDetail(ctx context.Context, req *GetBillDetailRequest, opts ...http.CallOption) (rsp *GetBillDetailReply, err error) + // ListBills 分页查询账单列表,支持按时间和类型筛选 + ListBills(ctx context.Context, req *ListBillsRequest, opts ...http.CallOption) (rsp *ListBillsReply, err error) +} + +type BillServiceHTTPClientImpl struct { + cc *http.Client +} + +func NewBillServiceHTTPClient(client *http.Client) BillServiceHTTPClient { + return &BillServiceHTTPClientImpl{client} +} + +// GetBillDetail 根据账单ID或交易单号查询账单详情 +func (c *BillServiceHTTPClientImpl) GetBillDetail(ctx context.Context, in *GetBillDetailRequest, opts ...http.CallOption) (*GetBillDetailReply, error) { + var out GetBillDetailReply + pattern := "/v1/bill/detail" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationBillServiceGetBillDetail)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// ListBills 分页查询账单列表,支持按时间和类型筛选 +func (c *BillServiceHTTPClientImpl) ListBills(ctx context.Context, in *ListBillsRequest, opts ...http.CallOption) (*ListBillsReply, error) { + var out ListBillsReply + pattern := "/v1/bills" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationBillServiceListBills)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/api/cabinet/v1/cabinet.pb.go b/api/cabinet/v1/cabinet.pb.go index 301252d1b59f4d51796e7077c082ea358c5ab980..f38471790a37b04b44eb5500bad016bf9bc88369 100644 --- a/api/cabinet/v1/cabinet.pb.go +++ b/api/cabinet/v1/cabinet.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.36.10 // protoc v3.19.4 // source: cabinet/v1/cabinet.proto @@ -12,6 +12,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -24,20 +25,17 @@ const ( // FindCabinetRequest 根据ID查找智能柜的请求参数 // 用于指定要查询的智能柜ID type FindCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // 智能柜ID,系统唯一标识 unknownFields protoimpl.UnknownFields - - Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // 智能柜ID,系统唯一标识 + sizeCache protoimpl.SizeCache } func (x *FindCabinetRequest) Reset() { *x = FindCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FindCabinetRequest) String() string { @@ -48,7 +46,7 @@ func (*FindCabinetRequest) ProtoMessage() {} func (x *FindCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -73,23 +71,20 @@ func (x *FindCabinetRequest) GetId() int32 { // FindCabinetReply 根据ID查找智能柜的响应结果 // 返回智能柜的基本状态和信息 type FindCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Status int32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"` // 智能柜状态(0-离线,1-在线,2-故障) - AvailableCompartments int32 `protobuf:"varint,2,opt,name=available_compartments,json=availableCompartments,proto3" json:"available_compartments,omitempty"` // 可用格口数量 - CabinetCode string `protobuf:"bytes,3,opt,name=cabinet_code,json=cabinetCode,proto3" json:"cabinet_code,omitempty"` // 智能柜编码,用于设备标识 - Location string `protobuf:"bytes,4,opt,name=location,proto3" json:"location,omitempty"` // 智能柜物理位置描述 + state protoimpl.MessageState `protogen:"open.v1"` + Status int32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"` // 智能柜状态(0-离线,1-在线,2-故障) + AvailableCompartments int32 `protobuf:"varint,2,opt,name=available_compartments,json=availableCompartments,proto3" json:"available_compartments,omitempty"` // 可用格口数量 + CabinetCode string `protobuf:"bytes,3,opt,name=cabinet_code,json=cabinetCode,proto3" json:"cabinet_code,omitempty"` // 智能柜编码,用于设备标识 + Location string `protobuf:"bytes,4,opt,name=location,proto3" json:"location,omitempty"` // 智能柜物理位置描述 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *FindCabinetReply) Reset() { *x = FindCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FindCabinetReply) String() string { @@ -100,7 +95,7 @@ func (*FindCabinetReply) ProtoMessage() {} func (x *FindCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -146,18 +141,16 @@ func (x *FindCabinetReply) GetLocation() string { // CreateCabinetRequest 创建智能柜的请求参数 // 用于提交新智能柜的详细信息 type CreateCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CreateCabinetRequest) Reset() { *x = CreateCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CreateCabinetRequest) String() string { @@ -168,7 +161,7 @@ func (*CreateCabinetRequest) ProtoMessage() {} func (x *CreateCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -186,18 +179,16 @@ func (*CreateCabinetRequest) Descriptor() ([]byte, []int) { // CreateCabinetReply 创建智能柜的响应结果 // 返回创建操作的状态和新智能柜的信息 type CreateCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CreateCabinetReply) Reset() { *x = CreateCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CreateCabinetReply) String() string { @@ -208,7 +199,7 @@ func (*CreateCabinetReply) ProtoMessage() {} func (x *CreateCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -226,18 +217,16 @@ func (*CreateCabinetReply) Descriptor() ([]byte, []int) { // UpdateCabinetRequest 更新智能柜信息的请求参数 // 用于修改现有智能柜的配置和状态 type UpdateCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpdateCabinetRequest) Reset() { *x = UpdateCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UpdateCabinetRequest) String() string { @@ -248,7 +237,7 @@ func (*UpdateCabinetRequest) ProtoMessage() {} func (x *UpdateCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -266,18 +255,16 @@ func (*UpdateCabinetRequest) Descriptor() ([]byte, []int) { // UpdateCabinetReply 更新智能柜信息的响应结果 // 返回更新操作的状态 type UpdateCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpdateCabinetReply) Reset() { *x = UpdateCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UpdateCabinetReply) String() string { @@ -288,7 +275,7 @@ func (*UpdateCabinetReply) ProtoMessage() {} func (x *UpdateCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -306,18 +293,16 @@ func (*UpdateCabinetReply) Descriptor() ([]byte, []int) { // DeleteCabinetRequest 删除智能柜的请求参数 // 用于指定要删除的智能柜 type DeleteCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DeleteCabinetRequest) Reset() { *x = DeleteCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DeleteCabinetRequest) String() string { @@ -328,7 +313,7 @@ func (*DeleteCabinetRequest) ProtoMessage() {} func (x *DeleteCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -346,18 +331,16 @@ func (*DeleteCabinetRequest) Descriptor() ([]byte, []int) { // DeleteCabinetReply 删除智能柜的响应结果 // 返回删除操作的状态 type DeleteCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DeleteCabinetReply) Reset() { *x = DeleteCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DeleteCabinetReply) String() string { @@ -368,7 +351,7 @@ func (*DeleteCabinetReply) ProtoMessage() {} func (x *DeleteCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -386,18 +369,16 @@ func (*DeleteCabinetReply) Descriptor() ([]byte, []int) { // GetCabinetRequest 获取智能柜详情的请求参数 // 用于指定要查询详情的智能柜 type GetCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetCabinetRequest) Reset() { *x = GetCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetCabinetRequest) String() string { @@ -408,7 +389,7 @@ func (*GetCabinetRequest) ProtoMessage() {} func (x *GetCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -426,18 +407,16 @@ func (*GetCabinetRequest) Descriptor() ([]byte, []int) { // GetCabinetReply 获取智能柜详情的响应结果 // 返回智能柜的完整详细信息 type GetCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *GetCabinetReply) Reset() { *x = GetCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetCabinetReply) String() string { @@ -448,7 +427,7 @@ func (*GetCabinetReply) ProtoMessage() {} func (x *GetCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -466,18 +445,16 @@ func (*GetCabinetReply) Descriptor() ([]byte, []int) { // ListCabinetRequest 获取智能柜列表的请求参数 // 用于分页查询智能柜列表 type ListCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListCabinetRequest) Reset() { *x = ListCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListCabinetRequest) String() string { @@ -488,7 +465,7 @@ func (*ListCabinetRequest) ProtoMessage() {} func (x *ListCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -506,18 +483,16 @@ func (*ListCabinetRequest) Descriptor() ([]byte, []int) { // ListCabinetReply 获取智能柜列表的响应结果 // 返回符合条件的智能柜列表和总数 type ListCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ListCabinetReply) Reset() { *x = ListCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListCabinetReply) String() string { @@ -528,7 +503,7 @@ func (*ListCabinetReply) ProtoMessage() {} func (x *ListCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -546,23 +521,20 @@ func (*ListCabinetReply) Descriptor() ([]byte, []int) { // NearbyCabinetRequest 查询附近智能柜的请求参数 // 基于地理位置查询周边智能柜,是用户端核心功能之一 type NearbyCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Latitude float32 `protobuf:"fixed32,1,opt,name=latitude,proto3" json:"latitude,omitempty"` // 纬度,用户当前位置的纬度坐标 + Longitude float32 `protobuf:"fixed32,2,opt,name=longitude,proto3" json:"longitude,omitempty"` // 经度,用户当前位置的经度坐标 + Radius float32 `protobuf:"fixed32,3,opt,name=radius,proto3" json:"radius,omitempty"` // 搜索半径(公里),默认5公里,用于限制搜索范围 + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` // 返回数量限制,默认10,用于控制响应数据量 unknownFields protoimpl.UnknownFields - - Latitude float32 `protobuf:"fixed32,1,opt,name=latitude,proto3" json:"latitude,omitempty"` // 纬度,用户当前位置的纬度坐标 - Longitude float32 `protobuf:"fixed32,2,opt,name=longitude,proto3" json:"longitude,omitempty"` // 经度,用户当前位置的经度坐标 - Radius float32 `protobuf:"fixed32,3,opt,name=radius,proto3" json:"radius,omitempty"` // 搜索半径(公里),默认5公里,用于限制搜索范围 - Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` // 返回数量限制,默认10,用于控制响应数据量 + sizeCache protoimpl.SizeCache } func (x *NearbyCabinetRequest) Reset() { *x = NearbyCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NearbyCabinetRequest) String() string { @@ -573,7 +545,7 @@ func (*NearbyCabinetRequest) ProtoMessage() {} func (x *NearbyCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -619,20 +591,17 @@ func (x *NearbyCabinetRequest) GetLimit() int32 { // NearbyCabinetReply 查询附近智能柜的响应结果 // 返回符合条件的附近智能柜列表 type NearbyCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Cabinets []*CabinetInfo `protobuf:"bytes,1,rep,name=cabinets,proto3" json:"cabinets,omitempty"` // 智能柜列表,按距离排序,包含每个柜机的详细信息和距离 unknownFields protoimpl.UnknownFields - - Cabinets []*CabinetInfo `protobuf:"bytes,1,rep,name=cabinets,proto3" json:"cabinets,omitempty"` // 智能柜列表,按距离排序,包含每个柜机的详细信息和距离 + sizeCache protoimpl.SizeCache } func (x *NearbyCabinetReply) Reset() { *x = NearbyCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NearbyCabinetReply) String() string { @@ -643,7 +612,7 @@ func (*NearbyCabinetReply) ProtoMessage() {} func (x *NearbyCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -669,30 +638,27 @@ func (x *NearbyCabinetReply) GetCabinets() []*CabinetInfo { // 用于在各种接口中返回智能柜的详细信息 // 这是系统中最核心的数据传输对象之一 type CabinetInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // 智能柜ID,系统唯一标识 - CabinetCode string `protobuf:"bytes,2,opt,name=cabinet_code,json=cabinetCode,proto3" json:"cabinet_code,omitempty"` // 智能柜编码,设备唯一标识 - CabinetName string `protobuf:"bytes,3,opt,name=cabinet_name,json=cabinetName,proto3" json:"cabinet_name,omitempty"` // 智能柜名称,如小区名称或地点名称 - CabinetType string `protobuf:"bytes,4,opt,name=cabinet_type,json=cabinetType,proto3" json:"cabinet_type,omitempty"` // 智能柜类型,如快递柜、生鲜柜、文件柜等 - Location string `protobuf:"bytes,5,opt,name=location,proto3" json:"location,omitempty"` // 智能柜详细位置描述 - Latitude float32 `protobuf:"fixed32,6,opt,name=latitude,proto3" json:"latitude,omitempty"` // 纬度坐标,用于地图定位 - Longitude float32 `protobuf:"fixed32,7,opt,name=longitude,proto3" json:"longitude,omitempty"` // 经度坐标,用于地图定位 - TotalCompartments int32 `protobuf:"varint,8,opt,name=total_compartments,json=totalCompartments,proto3" json:"total_compartments,omitempty"` // 总格口数量 - AvailableCompartments int32 `protobuf:"varint,9,opt,name=available_compartments,json=availableCompartments,proto3" json:"available_compartments,omitempty"` // 可用格口数量 - Status int32 `protobuf:"varint,10,opt,name=status,proto3" json:"status,omitempty"` // 智能柜状态(0-离线,1-在线,2-故障) - Distance float64 `protobuf:"fixed64,11,opt,name=distance,proto3" json:"distance,omitempty"` // 距离(公里),在附近查询时返回与用户的距离 + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // 智能柜ID,系统唯一标识 + CabinetCode string `protobuf:"bytes,2,opt,name=cabinet_code,json=cabinetCode,proto3" json:"cabinet_code,omitempty"` // 智能柜编码,设备唯一标识 + CabinetName string `protobuf:"bytes,3,opt,name=cabinet_name,json=cabinetName,proto3" json:"cabinet_name,omitempty"` // 智能柜名称,如小区名称或地点名称 + CabinetType string `protobuf:"bytes,4,opt,name=cabinet_type,json=cabinetType,proto3" json:"cabinet_type,omitempty"` // 智能柜类型,如快递柜、生鲜柜、文件柜等 + Location string `protobuf:"bytes,5,opt,name=location,proto3" json:"location,omitempty"` // 智能柜详细位置描述 + Latitude float32 `protobuf:"fixed32,6,opt,name=latitude,proto3" json:"latitude,omitempty"` // 纬度坐标,用于地图定位 + Longitude float32 `protobuf:"fixed32,7,opt,name=longitude,proto3" json:"longitude,omitempty"` // 经度坐标,用于地图定位 + TotalCompartments int32 `protobuf:"varint,8,opt,name=total_compartments,json=totalCompartments,proto3" json:"total_compartments,omitempty"` // 总格口数量 + AvailableCompartments int32 `protobuf:"varint,9,opt,name=available_compartments,json=availableCompartments,proto3" json:"available_compartments,omitempty"` // 可用格口数量 + Status int32 `protobuf:"varint,10,opt,name=status,proto3" json:"status,omitempty"` // 智能柜状态(0-离线,1-在线,2-故障) + Distance float64 `protobuf:"fixed64,11,opt,name=distance,proto3" json:"distance,omitempty"` // 距离(公里),在附近查询时返回与用户的距离 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CabinetInfo) Reset() { *x = CabinetInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CabinetInfo) String() string { @@ -703,7 +669,7 @@ func (*CabinetInfo) ProtoMessage() {} func (x *CabinetInfo) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -798,21 +764,18 @@ func (x *CabinetInfo) GetDistance() float64 { // FindFavoriteCabinetRequest 查询用户是否收藏指定柜机的请求参数 // 用于检查特定用户是否已收藏特定智能柜 type FindFavoriteCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID,系统用户唯一标识 + CabinetId int64 `protobuf:"varint,2,opt,name=cabinet_id,json=cabinetId,proto3" json:"cabinet_id,omitempty"` // 柜机ID,智能柜唯一标识 unknownFields protoimpl.UnknownFields - - UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID,系统用户唯一标识 - CabinetId int64 `protobuf:"varint,2,opt,name=cabinet_id,json=cabinetId,proto3" json:"cabinet_id,omitempty"` // 柜机ID,智能柜唯一标识 + sizeCache protoimpl.SizeCache } func (x *FindFavoriteCabinetRequest) Reset() { *x = FindFavoriteCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FindFavoriteCabinetRequest) String() string { @@ -823,7 +786,7 @@ func (*FindFavoriteCabinetRequest) ProtoMessage() {} func (x *FindFavoriteCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -855,21 +818,18 @@ func (x *FindFavoriteCabinetRequest) GetCabinetId() int64 { // FindFavoriteCabinetReply 查询用户收藏状态的响应结果 // 返回用户对指定柜机的收藏状态和柜机详情 type FindFavoriteCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + IsFavorite bool `protobuf:"varint,1,opt,name=is_favorite,json=isFavorite,proto3" json:"is_favorite,omitempty"` // 是否已收藏,true表示已收藏,false表示未收藏 + Cabinet *CabinetInfo `protobuf:"bytes,2,opt,name=cabinet,proto3" json:"cabinet,omitempty"` // 柜机信息,返回智能柜的详细数据 unknownFields protoimpl.UnknownFields - - IsFavorite bool `protobuf:"varint,1,opt,name=is_favorite,json=isFavorite,proto3" json:"is_favorite,omitempty"` // 是否已收藏,true表示已收藏,false表示未收藏 - Cabinet *CabinetInfo `protobuf:"bytes,2,opt,name=cabinet,proto3" json:"cabinet,omitempty"` // 柜机信息,返回智能柜的详细数据 + sizeCache protoimpl.SizeCache } func (x *FindFavoriteCabinetReply) Reset() { *x = FindFavoriteCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FindFavoriteCabinetReply) String() string { @@ -880,7 +840,7 @@ func (*FindFavoriteCabinetReply) ProtoMessage() {} func (x *FindFavoriteCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -912,22 +872,19 @@ func (x *FindFavoriteCabinetReply) GetCabinet() *CabinetInfo { // ListFavoriteCabinetRequest 获取用户收藏柜机列表的请求参数 // 用于分页查询用户收藏的所有智能柜 type ListFavoriteCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID,系统用户唯一标识 + Page int32 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"` // 页码,从1开始,默认1 + PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // 每页数量,默认10,用于控制返回数据量 unknownFields protoimpl.UnknownFields - - UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID,系统用户唯一标识 - Page int32 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"` // 页码,从1开始,默认1 - PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // 每页数量,默认10,用于控制返回数据量 + sizeCache protoimpl.SizeCache } func (x *ListFavoriteCabinetRequest) Reset() { *x = ListFavoriteCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListFavoriteCabinetRequest) String() string { @@ -938,7 +895,7 @@ func (*ListFavoriteCabinetRequest) ProtoMessage() {} func (x *ListFavoriteCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -977,21 +934,18 @@ func (x *ListFavoriteCabinetRequest) GetPageSize() int32 { // ListFavoriteCabinetReply 获取用户收藏柜机列表的响应结果 // 返回用户收藏的智能柜列表和总数 type ListFavoriteCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Cabinets []*CabinetInfo `protobuf:"bytes,1,rep,name=cabinets,proto3" json:"cabinets,omitempty"` // 收藏的柜机列表,包含智能柜详细信息 + Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` // 总数,用户收藏的智能柜总数,用于分页计算 unknownFields protoimpl.UnknownFields - - Cabinets []*CabinetInfo `protobuf:"bytes,1,rep,name=cabinets,proto3" json:"cabinets,omitempty"` // 收藏的柜机列表,包含智能柜详细信息 - Total int32 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` // 总数,用户收藏的智能柜总数,用于分页计算 + sizeCache protoimpl.SizeCache } func (x *ListFavoriteCabinetReply) Reset() { *x = ListFavoriteCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListFavoriteCabinetReply) String() string { @@ -1002,7 +956,7 @@ func (*ListFavoriteCabinetReply) ProtoMessage() {} func (x *ListFavoriteCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1034,21 +988,18 @@ func (x *ListFavoriteCabinetReply) GetTotal() int32 { // DeleteFavoriteCabinetRequest 取消收藏柜机的请求参数 // 用于指定要取消收藏的用户和智能柜 type DeleteFavoriteCabinetRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID,系统用户唯一标识 + CabinetId int64 `protobuf:"varint,2,opt,name=cabinet_id,json=cabinetId,proto3" json:"cabinet_id,omitempty"` // 柜机ID,智能柜唯一标识 unknownFields protoimpl.UnknownFields - - UserId int32 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 用户ID,系统用户唯一标识 - CabinetId int64 `protobuf:"varint,2,opt,name=cabinet_id,json=cabinetId,proto3" json:"cabinet_id,omitempty"` // 柜机ID,智能柜唯一标识 + sizeCache protoimpl.SizeCache } func (x *DeleteFavoriteCabinetRequest) Reset() { *x = DeleteFavoriteCabinetRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DeleteFavoriteCabinetRequest) String() string { @@ -1059,7 +1010,7 @@ func (*DeleteFavoriteCabinetRequest) ProtoMessage() {} func (x *DeleteFavoriteCabinetRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1091,21 +1042,18 @@ func (x *DeleteFavoriteCabinetRequest) GetCabinetId() int64 { // DeleteFavoriteCabinetReply 取消收藏柜机的响应结果 // 返回取消收藏操作的结果状态和消息 type DeleteFavoriteCabinetReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 是否成功,true表示取消收藏成功,false表示失败 + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 消息,操作结果的详细描述或错误信息 unknownFields protoimpl.UnknownFields - - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 是否成功,true表示取消收藏成功,false表示失败 - Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 消息,操作结果的详细描述或错误信息 + sizeCache protoimpl.SizeCache } func (x *DeleteFavoriteCabinetReply) Reset() { *x = DeleteFavoriteCabinetReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DeleteFavoriteCabinetReply) String() string { @@ -1116,7 +1064,7 @@ func (*DeleteFavoriteCabinetReply) ProtoMessage() {} func (x *DeleteFavoriteCabinetReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1148,20 +1096,17 @@ func (x *DeleteFavoriteCabinetReply) GetMessage() string { // FindCabinetByParcelRequest 根据包裹号查询柜机的请求参数 // 用于通过快递单号查找对应的智能柜信息 type FindCabinetByParcelRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ExpressNo string `protobuf:"bytes,1,opt,name=express_no,json=expressNo,proto3" json:"express_no,omitempty"` // 快递单号(包裹号),用于唯一标识一个包裹 unknownFields protoimpl.UnknownFields - - ExpressNo string `protobuf:"bytes,1,opt,name=express_no,json=expressNo,proto3" json:"express_no,omitempty"` // 快递单号(包裹号),用于唯一标识一个包裹 + sizeCache protoimpl.SizeCache } func (x *FindCabinetByParcelRequest) Reset() { *x = FindCabinetByParcelRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FindCabinetByParcelRequest) String() string { @@ -1172,7 +1117,7 @@ func (*FindCabinetByParcelRequest) ProtoMessage() {} func (x *FindCabinetByParcelRequest) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1197,21 +1142,18 @@ func (x *FindCabinetByParcelRequest) GetExpressNo() string { // FindCabinetByParcelReply 根据包裹号查询柜机的响应结果 // 返回包裹所在的智能柜信息和包裹详细信息 type FindCabinetByParcelReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Cabinet *CabinetInfo `protobuf:"bytes,1,opt,name=cabinet,proto3" json:"cabinet,omitempty"` // 目的地柜机信息,包裹所在的智能柜详情 + Parcel *ParcelInfo `protobuf:"bytes,2,opt,name=parcel,proto3" json:"parcel,omitempty"` // 包裹信息,包裹的详细状态和收件人信息 unknownFields protoimpl.UnknownFields - - Cabinet *CabinetInfo `protobuf:"bytes,1,opt,name=cabinet,proto3" json:"cabinet,omitempty"` // 目的地柜机信息,包裹所在的智能柜详情 - Parcel *ParcelInfo `protobuf:"bytes,2,opt,name=parcel,proto3" json:"parcel,omitempty"` // 包裹信息,包裹的详细状态和收件人信息 + sizeCache protoimpl.SizeCache } func (x *FindCabinetByParcelReply) Reset() { *x = FindCabinetByParcelReply{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *FindCabinetByParcelReply) String() string { @@ -1222,7 +1164,7 @@ func (*FindCabinetByParcelReply) ProtoMessage() {} func (x *FindCabinetByParcelReply) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1254,25 +1196,24 @@ func (x *FindCabinetByParcelReply) GetParcel() *ParcelInfo { // ParcelInfo 包裹信息数据结构 // 用于在各种接口中返回包裹的详细信息 type ParcelInfo struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ParcelId int32 `protobuf:"varint,1,opt,name=parcel_id,json=parcelId,proto3" json:"parcel_id,omitempty"` // 包裹ID,系统唯一标识 - ExpressNo string `protobuf:"bytes,2,opt,name=express_no,json=expressNo,proto3" json:"express_no,omitempty"` // 快递单号,物流系统唯一标识 - RecipientName string `protobuf:"bytes,3,opt,name=recipient_name,json=recipientName,proto3" json:"recipient_name,omitempty"` // 收件人姓名,包裹接收人姓名 - RecipientPhone string `protobuf:"bytes,4,opt,name=recipient_phone,json=recipientPhone,proto3" json:"recipient_phone,omitempty"` // 收件人电话,包裹接收人联系方式 - LockerId int32 `protobuf:"varint,5,opt,name=locker_id,json=lockerId,proto3" json:"locker_id,omitempty"` // 格口ID,包裹所在的智能柜格口编号 - ParcelStatus int32 `protobuf:"varint,6,opt,name=parcel_status,json=parcelStatus,proto3" json:"parcel_status,omitempty"` // 包裹状态(0-待取件,1-滞留件,2-已取件,3-已撤回,4-已完结) + state protoimpl.MessageState `protogen:"open.v1"` + ParcelId int32 `protobuf:"varint,1,opt,name=parcel_id,json=parcelId,proto3" json:"parcel_id,omitempty"` // 包裹ID,系统唯一标识 + ExpressNo string `protobuf:"bytes,2,opt,name=express_no,json=expressNo,proto3" json:"express_no,omitempty"` // 快递单号,物流系统唯一标识 + RecipientName string `protobuf:"bytes,3,opt,name=recipient_name,json=recipientName,proto3" json:"recipient_name,omitempty"` // 收件人姓名,包裹接收人姓名 + RecipientPhone string `protobuf:"bytes,4,opt,name=recipient_phone,json=recipientPhone,proto3" json:"recipient_phone,omitempty"` // 收件人电话,包裹接收人联系方式 + LockerId int32 `protobuf:"varint,5,opt,name=locker_id,json=lockerId,proto3" json:"locker_id,omitempty"` // 格口ID,包裹所在的智能柜格口编号 + ParcelStatus int32 `protobuf:"varint,6,opt,name=parcel_status,json=parcelStatus,proto3" json:"parcel_status,omitempty"` // 包裹状态(0-待取件,1-滞留件,2-已取件,3-已撤回,4-已完结) + DispatchTime string `protobuf:"bytes,7,opt,name=dispatch_time,json=dispatchTime,proto3" json:"dispatch_time,omitempty"` // 派件时间 + CourierId int32 `protobuf:"varint,8,opt,name=courier_id,json=courierId,proto3" json:"courier_id,omitempty"` // 派件员ID + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ParcelInfo) Reset() { *x = ParcelInfo{} - if protoimpl.UnsafeEnabled { - mi := &file_cabinet_v1_cabinet_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_cabinet_v1_cabinet_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ParcelInfo) String() string { @@ -1283,7 +1224,7 @@ func (*ParcelInfo) ProtoMessage() {} func (x *ParcelInfo) ProtoReflect() protoreflect.Message { mi := &file_cabinet_v1_cabinet_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1340,247 +1281,674 @@ func (x *ParcelInfo) GetParcelStatus() int32 { return 0 } -var File_cabinet_v1_cabinet_proto protoreflect.FileDescriptor +func (x *ParcelInfo) GetDispatchTime() string { + if x != nil { + return x.DispatchTime + } + return "" +} + +func (x *ParcelInfo) GetCourierId() int32 { + if x != nil { + return x.CourierId + } + return 0 +} + +// ListCourierParcelsRequest 获取快递员派件列表的请求参数 +// 用于查询快递员的派件情况 +type ListCourierParcelsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CourierId int32 `protobuf:"varint,1,opt,name=courier_id,json=courierId,proto3" json:"courier_id,omitempty"` // 快递员ID,必填 + ParcelStatus int32 `protobuf:"varint,2,opt,name=parcel_status,json=parcelStatus,proto3" json:"parcel_status,omitempty"` // 包裹状态(可选,-1表示查询所有状态,0-待取件,1-滞留件,2-已取件,3-已撤回,4-已完结) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListCourierParcelsRequest) Reset() { + *x = ListCourierParcelsRequest{} + mi := &file_cabinet_v1_cabinet_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCourierParcelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCourierParcelsRequest) ProtoMessage() {} + +func (x *ListCourierParcelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_cabinet_v1_cabinet_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 ListCourierParcelsRequest.ProtoReflect.Descriptor instead. +func (*ListCourierParcelsRequest) Descriptor() ([]byte, []int) { + return file_cabinet_v1_cabinet_proto_rawDescGZIP(), []int{24} +} + +func (x *ListCourierParcelsRequest) GetCourierId() int32 { + if x != nil { + return x.CourierId + } + return 0 +} + +func (x *ListCourierParcelsRequest) GetParcelStatus() int32 { + if x != nil { + return x.ParcelStatus + } + return 0 +} + +// ListCourierParcelsReply 获取快递员派件列表的响应结果 +// 返回按柜机分组的包裹列表,形成层级结构 +type ListCourierParcelsReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Groups []*CabinetParcelGroup `protobuf:"bytes,1,rep,name=groups,proto3" json:"groups,omitempty"` // 按柜机分组的包裹列表 + TotalParcels int32 `protobuf:"varint,2,opt,name=total_parcels,json=totalParcels,proto3" json:"total_parcels,omitempty"` // 包裹总数 + TotalCabinets int32 `protobuf:"varint,3,opt,name=total_cabinets,json=totalCabinets,proto3" json:"total_cabinets,omitempty"` // 涉及的柜机总数 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListCourierParcelsReply) Reset() { + *x = ListCourierParcelsReply{} + mi := &file_cabinet_v1_cabinet_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCourierParcelsReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCourierParcelsReply) ProtoMessage() {} + +func (x *ListCourierParcelsReply) ProtoReflect() protoreflect.Message { + mi := &file_cabinet_v1_cabinet_proto_msgTypes[25] + 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 ListCourierParcelsReply.ProtoReflect.Descriptor instead. +func (*ListCourierParcelsReply) Descriptor() ([]byte, []int) { + return file_cabinet_v1_cabinet_proto_rawDescGZIP(), []int{25} +} + +func (x *ListCourierParcelsReply) GetGroups() []*CabinetParcelGroup { + if x != nil { + return x.Groups + } + return nil +} + +func (x *ListCourierParcelsReply) GetTotalParcels() int32 { + if x != nil { + return x.TotalParcels + } + return 0 +} + +func (x *ListCourierParcelsReply) GetTotalCabinets() int32 { + if x != nil { + return x.TotalCabinets + } + return 0 +} + +// CabinetParcelGroup 柜机包裹分组 +// 表示一个柜机及其包含的包裹列表,用于实现层级结构 +type CabinetParcelGroup struct { + state protoimpl.MessageState `protogen:"open.v1"` + Cabinet *CabinetInfo `protobuf:"bytes,1,opt,name=cabinet,proto3" json:"cabinet,omitempty"` // 柜机信息(父级) + Parcels []*ParcelInfo `protobuf:"bytes,2,rep,name=parcels,proto3" json:"parcels,omitempty"` // 柜机内的包裹列表(子级) + ParcelCount int32 `protobuf:"varint,3,opt,name=parcel_count,json=parcelCount,proto3" json:"parcel_count,omitempty"` // 该柜机的包裹数量 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CabinetParcelGroup) Reset() { + *x = CabinetParcelGroup{} + mi := &file_cabinet_v1_cabinet_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CabinetParcelGroup) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CabinetParcelGroup) ProtoMessage() {} + +func (x *CabinetParcelGroup) ProtoReflect() protoreflect.Message { + mi := &file_cabinet_v1_cabinet_proto_msgTypes[26] + 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 CabinetParcelGroup.ProtoReflect.Descriptor instead. +func (*CabinetParcelGroup) Descriptor() ([]byte, []int) { + return file_cabinet_v1_cabinet_proto_rawDescGZIP(), []int{26} +} + +func (x *CabinetParcelGroup) GetCabinet() *CabinetInfo { + if x != nil { + return x.Cabinet + } + return nil +} + +func (x *CabinetParcelGroup) GetParcels() []*ParcelInfo { + if x != nil { + return x.Parcels + } + return nil +} + +func (x *CabinetParcelGroup) GetParcelCount() int32 { + if x != nil { + return x.ParcelCount + } + return 0 +} + +// SyncLockerStatusRequest 同步智能柜格口状态的请求参数 +// 用于指定要同步的智能柜ID +type SyncLockerStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CabinetId int64 `protobuf:"varint,1,opt,name=cabinet_id,json=cabinetId,proto3" json:"cabinet_id,omitempty"` // 智能柜ID,系统唯一标识 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncLockerStatusRequest) Reset() { + *x = SyncLockerStatusRequest{} + mi := &file_cabinet_v1_cabinet_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncLockerStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncLockerStatusRequest) ProtoMessage() {} + +func (x *SyncLockerStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_cabinet_v1_cabinet_proto_msgTypes[27] + 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 SyncLockerStatusRequest.ProtoReflect.Descriptor instead. +func (*SyncLockerStatusRequest) Descriptor() ([]byte, []int) { + return file_cabinet_v1_cabinet_proto_rawDescGZIP(), []int{27} +} + +func (x *SyncLockerStatusRequest) GetCabinetId() int64 { + if x != nil { + return x.CabinetId + } + return 0 +} + +// SyncLockerStatusReply 同步智能柜格口状态的响应结果 +// 返回同步结果和统计信息 +type SyncLockerStatusReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` // 是否同步成功 + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // 同步结果消息 + TotalCompartments int32 `protobuf:"varint,3,opt,name=total_compartments,json=totalCompartments,proto3" json:"total_compartments,omitempty"` // 总格口数量 + UsedCompartments int32 `protobuf:"varint,4,opt,name=used_compartments,json=usedCompartments,proto3" json:"used_compartments,omitempty"` // 使用中的格口数量 + AvailableCompartments int32 `protobuf:"varint,5,opt,name=available_compartments,json=availableCompartments,proto3" json:"available_compartments,omitempty"` // 可用格口数量 + UpdatedLockers int32 `protobuf:"varint,6,opt,name=updated_lockers,json=updatedLockers,proto3" json:"updated_lockers,omitempty"` // 更新的格口数量 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SyncLockerStatusReply) Reset() { + *x = SyncLockerStatusReply{} + mi := &file_cabinet_v1_cabinet_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncLockerStatusReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncLockerStatusReply) ProtoMessage() {} + +func (x *SyncLockerStatusReply) ProtoReflect() protoreflect.Message { + mi := &file_cabinet_v1_cabinet_proto_msgTypes[28] + 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 SyncLockerStatusReply.ProtoReflect.Descriptor instead. +func (*SyncLockerStatusReply) Descriptor() ([]byte, []int) { + return file_cabinet_v1_cabinet_proto_rawDescGZIP(), []int{28} +} + +func (x *SyncLockerStatusReply) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *SyncLockerStatusReply) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *SyncLockerStatusReply) GetTotalCompartments() int32 { + if x != nil { + return x.TotalCompartments + } + return 0 +} + +func (x *SyncLockerStatusReply) GetUsedCompartments() int32 { + if x != nil { + return x.UsedCompartments + } + return 0 +} + +func (x *SyncLockerStatusReply) GetAvailableCompartments() int32 { + if x != nil { + return x.AvailableCompartments + } + return 0 +} + +func (x *SyncLockerStatusReply) GetUpdatedLockers() int32 { + if x != nil { + return x.UpdatedLockers + } + return 0 +} + +// GetParcelRequest 查询包裹详情的请求参数 +// 支持两种查询方式:包裹ID或快递单号后5位 +type GetParcelRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ParcelId int32 `protobuf:"varint,1,opt,name=parcel_id,json=parcelId,proto3" json:"parcel_id,omitempty"` // 包裹ID(可选,如果提供则按ID查询) + ExpressNoSuffix string `protobuf:"bytes,2,opt,name=express_no_suffix,json=expressNoSuffix,proto3" json:"express_no_suffix,omitempty"` // 快递单号后5位(可选,如果提供则按后5位查询) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetParcelRequest) Reset() { + *x = GetParcelRequest{} + mi := &file_cabinet_v1_cabinet_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetParcelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetParcelRequest) ProtoMessage() {} + +func (x *GetParcelRequest) ProtoReflect() protoreflect.Message { + mi := &file_cabinet_v1_cabinet_proto_msgTypes[29] + 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 GetParcelRequest.ProtoReflect.Descriptor instead. +func (*GetParcelRequest) Descriptor() ([]byte, []int) { + return file_cabinet_v1_cabinet_proto_rawDescGZIP(), []int{29} +} -var file_cabinet_v1_cabinet_proto_rawDesc = []byte{ - 0x0a, 0x18, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, - 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x61, 0x70, 0x69, 0x2e, - 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x24, 0x0a, 0x12, 0x46, 0x69, 0x6e, 0x64, - 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x22, 0xa0, - 0x01, 0x0a, 0x10, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x16, 0x61, - 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x74, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x61, 0x76, 0x61, - 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x74, 0x6d, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, - 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, - 0x16, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x16, 0x0a, - 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x14, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, - 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x13, 0x0a, 0x11, 0x47, - 0x65, 0x74, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x11, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x22, 0x14, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x4c, 0x69, 0x73, - 0x74, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x7e, 0x0a, - 0x14, 0x4e, 0x65, 0x61, 0x72, 0x62, 0x79, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, - 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, - 0x06, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x22, 0x4d, 0x0a, - 0x12, 0x4e, 0x65, 0x61, 0x72, 0x62, 0x79, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x08, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x08, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x73, 0x22, 0xf6, 0x02, 0x0a, - 0x0b, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x21, 0x0a, 0x0c, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x02, - 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x74, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, - 0x6d, 0x70, 0x61, 0x72, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x35, 0x0a, 0x16, 0x61, 0x76, - 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x74, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x61, 0x76, 0x61, 0x69, - 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x74, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x64, 0x69, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x54, 0x0a, 0x1a, 0x46, 0x69, 0x6e, 0x64, 0x46, 0x61, 0x76, - 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x09, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x22, 0x72, 0x0a, 0x18, 0x46, - 0x69, 0x6e, 0x64, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x66, 0x61, - 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, - 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x07, 0x63, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x22, - 0x66, 0x0a, 0x1a, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, - 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, - 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, - 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x69, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x46, - 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, - 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x08, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x49, 0x6e, - 0x66, 0x6f, 0x52, 0x08, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x22, 0x56, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, - 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, - 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x49, 0x64, 0x22, 0x50, 0x0a, 0x1a, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x3b, 0x0a, 0x1a, - 0x46, 0x69, 0x6e, 0x64, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x42, 0x79, 0x50, 0x61, 0x72, - 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, - 0x70, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x4e, 0x6f, 0x22, 0x85, 0x01, 0x0a, 0x18, 0x46, 0x69, - 0x6e, 0x64, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x42, 0x79, 0x50, 0x61, 0x72, 0x63, 0x65, - 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x07, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, - 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x12, 0x32, 0x0a, - 0x06, 0x70, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, - 0x61, 0x72, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x70, 0x61, 0x72, 0x63, 0x65, - 0x6c, 0x22, 0xda, 0x01, 0x0a, 0x0a, 0x50, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, - 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x4e, 0x6f, 0x12, 0x25, 0x0a, 0x0e, - 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, - 0x5f, 0x70, 0x68, 0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, - 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x50, 0x68, 0x6f, 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x09, - 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x08, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x72, - 0x63, 0x65, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0c, 0x70, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x32, 0x89, - 0x0b, 0x0a, 0x07, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x12, 0x78, 0x0a, 0x0d, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, - 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x12, 0x78, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61, - 0x62, 0x69, 0x6e, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x61, 0x62, - 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, - 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x1a, 0x12, 0x2f, 0x76, 0x31, 0x2f, - 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x78, - 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x12, - 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x61, 0x62, - 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x17, 0x3a, 0x01, 0x2a, 0x2a, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, - 0x74, 0x2f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x69, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x43, - 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x12, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, - 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x61, - 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, - 0x67, 0x65, 0x74, 0x12, 0x6d, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x12, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, - 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, - 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x6c, 0x69, - 0x73, 0x74, 0x12, 0x75, 0x0a, 0x0d, 0x4e, 0x65, 0x61, 0x72, 0x62, 0x79, 0x43, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, - 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x61, 0x72, 0x62, 0x79, 0x43, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x61, 0x72, 0x62, - 0x79, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1a, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x2f, 0x6e, 0x65, 0x61, 0x72, 0x62, 0x79, 0x12, 0x6d, 0x0a, 0x0b, 0x46, 0x69, 0x6e, - 0x64, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, - 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x61, - 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, - 0x6e, 0x64, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x18, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x12, 0x8e, 0x01, 0x0a, 0x13, 0x46, 0x69, 0x6e, - 0x64, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, - 0x12, 0x2a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, - 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, - 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, - 0x6e, 0x64, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, - 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, 0x19, - 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x66, 0x61, 0x76, 0x6f, - 0x72, 0x69, 0x74, 0x65, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x12, 0x8e, 0x01, 0x0a, 0x13, 0x4c, 0x69, - 0x73, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, - 0x74, 0x12, 0x2a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, - 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x12, - 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x66, 0x61, 0x76, - 0x6f, 0x72, 0x69, 0x74, 0x65, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x96, 0x01, 0x0a, 0x15, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, - 0x69, 0x6e, 0x65, 0x74, 0x12, 0x2c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, - 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, - 0x72, 0x69, 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x61, 0x76, 0x6f, 0x72, 0x69, - 0x74, 0x65, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x23, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x2a, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x2f, 0x66, 0x61, 0x76, 0x6f, 0x72, 0x69, 0x74, 0x65, 0x2f, 0x64, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x94, 0x01, 0x0a, 0x13, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x42, 0x79, 0x50, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x12, 0x2a, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, - 0x64, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x42, 0x79, 0x50, 0x61, 0x72, 0x63, 0x65, 0x6c, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x63, 0x61, - 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x64, 0x43, 0x61, 0x62, - 0x69, 0x6e, 0x65, 0x74, 0x42, 0x79, 0x50, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x63, - 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x61, 0x72, 0x63, 0x65, 0x6c, 0x2f, 0x7b, 0x65, - 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x6e, 0x6f, 0x7d, 0x42, 0x2d, 0x0a, 0x0e, 0x61, 0x70, - 0x69, 0x2e, 0x63, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x50, 0x01, 0x5a, 0x19, - 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, +func (x *GetParcelRequest) GetParcelId() int32 { + if x != nil { + return x.ParcelId + } + return 0 +} + +func (x *GetParcelRequest) GetExpressNoSuffix() string { + if x != nil { + return x.ExpressNoSuffix + } + return "" +} + +// GetParcelReply 查询包裹详情的响应结果 +// 返回包裹的详细信息,包括所在智能柜信息 +type GetParcelReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Parcel *ParcelInfo `protobuf:"bytes,1,opt,name=parcel,proto3" json:"parcel,omitempty"` // 包裹信息 + Cabinet *CabinetInfo `protobuf:"bytes,2,opt,name=cabinet,proto3" json:"cabinet,omitempty"` // 所在智能柜信息(如果查询到) + Locker *LockerInfo `protobuf:"bytes,3,opt,name=locker,proto3" json:"locker,omitempty"` // 所在格口信息(如果查询到) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetParcelReply) Reset() { + *x = GetParcelReply{} + mi := &file_cabinet_v1_cabinet_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetParcelReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetParcelReply) ProtoMessage() {} + +func (x *GetParcelReply) ProtoReflect() protoreflect.Message { + mi := &file_cabinet_v1_cabinet_proto_msgTypes[30] + 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 GetParcelReply.ProtoReflect.Descriptor instead. +func (*GetParcelReply) Descriptor() ([]byte, []int) { + return file_cabinet_v1_cabinet_proto_rawDescGZIP(), []int{30} +} + +func (x *GetParcelReply) GetParcel() *ParcelInfo { + if x != nil { + return x.Parcel + } + return nil +} + +func (x *GetParcelReply) GetCabinet() *CabinetInfo { + if x != nil { + return x.Cabinet + } + return nil +} + +func (x *GetParcelReply) GetLocker() *LockerInfo { + if x != nil { + return x.Locker + } + return nil +} + +// LockerInfo 格口信息 +// 用于返回包裹所在的格口详细信息 +type LockerInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + LockerId int32 `protobuf:"varint,1,opt,name=locker_id,json=lockerId,proto3" json:"locker_id,omitempty"` // 格口ID + LockerNumber string `protobuf:"bytes,2,opt,name=locker_number,json=lockerNumber,proto3" json:"locker_number,omitempty"` // 格口编号(如A01、B02) + Size string `protobuf:"bytes,3,opt,name=size,proto3" json:"size,omitempty"` // 格口大小(small/medium/large) + Status int32 `protobuf:"varint,4,opt,name=status,proto3" json:"status,omitempty"` // 格口状态(0-空闲,1-占用,2-故障) + CabinetId int64 `protobuf:"varint,5,opt,name=cabinet_id,json=cabinetId,proto3" json:"cabinet_id,omitempty"` // 所属智能柜ID + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LockerInfo) Reset() { + *x = LockerInfo{} + mi := &file_cabinet_v1_cabinet_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LockerInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LockerInfo) ProtoMessage() {} + +func (x *LockerInfo) ProtoReflect() protoreflect.Message { + mi := &file_cabinet_v1_cabinet_proto_msgTypes[31] + 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 LockerInfo.ProtoReflect.Descriptor instead. +func (*LockerInfo) Descriptor() ([]byte, []int) { + return file_cabinet_v1_cabinet_proto_rawDescGZIP(), []int{31} +} + +func (x *LockerInfo) GetLockerId() int32 { + if x != nil { + return x.LockerId + } + return 0 +} + +func (x *LockerInfo) GetLockerNumber() string { + if x != nil { + return x.LockerNumber + } + return "" +} + +func (x *LockerInfo) GetSize() string { + if x != nil { + return x.Size + } + return "" +} + +func (x *LockerInfo) GetStatus() int32 { + if x != nil { + return x.Status + } + return 0 } +func (x *LockerInfo) GetCabinetId() int64 { + if x != nil { + return x.CabinetId + } + return 0 +} + +var File_cabinet_v1_cabinet_proto protoreflect.FileDescriptor + +const file_cabinet_v1_cabinet_proto_rawDesc = "" + + "\n" + + "\x18cabinet/v1/cabinet.proto\x12\x0eapi.cabinet.v1\x1a\x1cgoogle/api/annotations.proto\"$\n" + + "\x12FindCabinetRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x05R\x02id\"\xa0\x01\n" + + "\x10FindCabinetReply\x12\x16\n" + + "\x06status\x18\x01 \x01(\x05R\x06status\x125\n" + + "\x16available_compartments\x18\x02 \x01(\x05R\x15availableCompartments\x12!\n" + + "\fcabinet_code\x18\x03 \x01(\tR\vcabinetCode\x12\x1a\n" + + "\blocation\x18\x04 \x01(\tR\blocation\"\x16\n" + + "\x14CreateCabinetRequest\"\x14\n" + + "\x12CreateCabinetReply\"\x16\n" + + "\x14UpdateCabinetRequest\"\x14\n" + + "\x12UpdateCabinetReply\"\x16\n" + + "\x14DeleteCabinetRequest\"\x14\n" + + "\x12DeleteCabinetReply\"\x13\n" + + "\x11GetCabinetRequest\"\x11\n" + + "\x0fGetCabinetReply\"\x14\n" + + "\x12ListCabinetRequest\"\x12\n" + + "\x10ListCabinetReply\"~\n" + + "\x14NearbyCabinetRequest\x12\x1a\n" + + "\blatitude\x18\x01 \x01(\x02R\blatitude\x12\x1c\n" + + "\tlongitude\x18\x02 \x01(\x02R\tlongitude\x12\x16\n" + + "\x06radius\x18\x03 \x01(\x02R\x06radius\x12\x14\n" + + "\x05limit\x18\x04 \x01(\x05R\x05limit\"M\n" + + "\x12NearbyCabinetReply\x127\n" + + "\bcabinets\x18\x01 \x03(\v2\x1b.api.cabinet.v1.CabinetInfoR\bcabinets\"\xf6\x02\n" + + "\vCabinetInfo\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12!\n" + + "\fcabinet_code\x18\x02 \x01(\tR\vcabinetCode\x12!\n" + + "\fcabinet_name\x18\x03 \x01(\tR\vcabinetName\x12!\n" + + "\fcabinet_type\x18\x04 \x01(\tR\vcabinetType\x12\x1a\n" + + "\blocation\x18\x05 \x01(\tR\blocation\x12\x1a\n" + + "\blatitude\x18\x06 \x01(\x02R\blatitude\x12\x1c\n" + + "\tlongitude\x18\a \x01(\x02R\tlongitude\x12-\n" + + "\x12total_compartments\x18\b \x01(\x05R\x11totalCompartments\x125\n" + + "\x16available_compartments\x18\t \x01(\x05R\x15availableCompartments\x12\x16\n" + + "\x06status\x18\n" + + " \x01(\x05R\x06status\x12\x1a\n" + + "\bdistance\x18\v \x01(\x01R\bdistance\"T\n" + + "\x1aFindFavoriteCabinetRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x05R\x06userId\x12\x1d\n" + + "\n" + + "cabinet_id\x18\x02 \x01(\x03R\tcabinetId\"r\n" + + "\x18FindFavoriteCabinetReply\x12\x1f\n" + + "\vis_favorite\x18\x01 \x01(\bR\n" + + "isFavorite\x125\n" + + "\acabinet\x18\x02 \x01(\v2\x1b.api.cabinet.v1.CabinetInfoR\acabinet\"f\n" + + "\x1aListFavoriteCabinetRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x05R\x06userId\x12\x12\n" + + "\x04page\x18\x02 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x03 \x01(\x05R\bpageSize\"i\n" + + "\x18ListFavoriteCabinetReply\x127\n" + + "\bcabinets\x18\x01 \x03(\v2\x1b.api.cabinet.v1.CabinetInfoR\bcabinets\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\"V\n" + + "\x1cDeleteFavoriteCabinetRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x05R\x06userId\x12\x1d\n" + + "\n" + + "cabinet_id\x18\x02 \x01(\x03R\tcabinetId\"P\n" + + "\x1aDeleteFavoriteCabinetReply\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\";\n" + + "\x1aFindCabinetByParcelRequest\x12\x1d\n" + + "\n" + + "express_no\x18\x01 \x01(\tR\texpressNo\"\x85\x01\n" + + "\x18FindCabinetByParcelReply\x125\n" + + "\acabinet\x18\x01 \x01(\v2\x1b.api.cabinet.v1.CabinetInfoR\acabinet\x122\n" + + "\x06parcel\x18\x02 \x01(\v2\x1a.api.cabinet.v1.ParcelInfoR\x06parcel\"\x9e\x02\n" + + "\n" + + "ParcelInfo\x12\x1b\n" + + "\tparcel_id\x18\x01 \x01(\x05R\bparcelId\x12\x1d\n" + + "\n" + + "express_no\x18\x02 \x01(\tR\texpressNo\x12%\n" + + "\x0erecipient_name\x18\x03 \x01(\tR\rrecipientName\x12'\n" + + "\x0frecipient_phone\x18\x04 \x01(\tR\x0erecipientPhone\x12\x1b\n" + + "\tlocker_id\x18\x05 \x01(\x05R\blockerId\x12#\n" + + "\rparcel_status\x18\x06 \x01(\x05R\fparcelStatus\x12#\n" + + "\rdispatch_time\x18\a \x01(\tR\fdispatchTime\x12\x1d\n" + + "\n" + + "courier_id\x18\b \x01(\x05R\tcourierId\"_\n" + + "\x19ListCourierParcelsRequest\x12\x1d\n" + + "\n" + + "courier_id\x18\x01 \x01(\x05R\tcourierId\x12#\n" + + "\rparcel_status\x18\x02 \x01(\x05R\fparcelStatus\"\xa1\x01\n" + + "\x17ListCourierParcelsReply\x12:\n" + + "\x06groups\x18\x01 \x03(\v2\".api.cabinet.v1.CabinetParcelGroupR\x06groups\x12#\n" + + "\rtotal_parcels\x18\x02 \x01(\x05R\ftotalParcels\x12%\n" + + "\x0etotal_cabinets\x18\x03 \x01(\x05R\rtotalCabinets\"\xa4\x01\n" + + "\x12CabinetParcelGroup\x125\n" + + "\acabinet\x18\x01 \x01(\v2\x1b.api.cabinet.v1.CabinetInfoR\acabinet\x124\n" + + "\aparcels\x18\x02 \x03(\v2\x1a.api.cabinet.v1.ParcelInfoR\aparcels\x12!\n" + + "\fparcel_count\x18\x03 \x01(\x05R\vparcelCount\"8\n" + + "\x17SyncLockerStatusRequest\x12\x1d\n" + + "\n" + + "cabinet_id\x18\x01 \x01(\x03R\tcabinetId\"\x87\x02\n" + + "\x15SyncLockerStatusReply\x12\x18\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\x12-\n" + + "\x12total_compartments\x18\x03 \x01(\x05R\x11totalCompartments\x12+\n" + + "\x11used_compartments\x18\x04 \x01(\x05R\x10usedCompartments\x125\n" + + "\x16available_compartments\x18\x05 \x01(\x05R\x15availableCompartments\x12'\n" + + "\x0fupdated_lockers\x18\x06 \x01(\x05R\x0eupdatedLockers\"[\n" + + "\x10GetParcelRequest\x12\x1b\n" + + "\tparcel_id\x18\x01 \x01(\x05R\bparcelId\x12*\n" + + "\x11express_no_suffix\x18\x02 \x01(\tR\x0fexpressNoSuffix\"\xaf\x01\n" + + "\x0eGetParcelReply\x122\n" + + "\x06parcel\x18\x01 \x01(\v2\x1a.api.cabinet.v1.ParcelInfoR\x06parcel\x125\n" + + "\acabinet\x18\x02 \x01(\v2\x1b.api.cabinet.v1.CabinetInfoR\acabinet\x122\n" + + "\x06locker\x18\x03 \x01(\v2\x1a.api.cabinet.v1.LockerInfoR\x06locker\"\x99\x01\n" + + "\n" + + "LockerInfo\x12\x1b\n" + + "\tlocker_id\x18\x01 \x01(\x05R\blockerId\x12#\n" + + "\rlocker_number\x18\x02 \x01(\tR\flockerNumber\x12\x12\n" + + "\x04size\x18\x03 \x01(\tR\x04size\x12\x16\n" + + "\x06status\x18\x04 \x01(\x05R\x06status\x12\x1d\n" + + "\n" + + "cabinet_id\x18\x05 \x01(\x03R\tcabinetId2\x94\x0e\n" + + "\aCabinet\x12x\n" + + "\rCreateCabinet\x12$.api.cabinet.v1.CreateCabinetRequest\x1a\".api.cabinet.v1.CreateCabinetReply\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/v1/cabinet/create\x12x\n" + + "\rUpdateCabinet\x12$.api.cabinet.v1.UpdateCabinetRequest\x1a\".api.cabinet.v1.UpdateCabinetReply\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\x1a\x12/v1/cabinet/update\x12x\n" + + "\rDeleteCabinet\x12$.api.cabinet.v1.DeleteCabinetRequest\x1a\".api.cabinet.v1.DeleteCabinetReply\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01**\x12/v1/cabinet/delete\x12i\n" + + "\n" + + "GetCabinet\x12!.api.cabinet.v1.GetCabinetRequest\x1a\x1f.api.cabinet.v1.GetCabinetReply\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/cabinet/get\x12m\n" + + "\vListCabinet\x12\".api.cabinet.v1.ListCabinetRequest\x1a .api.cabinet.v1.ListCabinetReply\"\x18\x82\xd3\xe4\x93\x02\x12\x12\x10/v1/cabinet/list\x12u\n" + + "\rNearbyCabinet\x12$.api.cabinet.v1.NearbyCabinetRequest\x1a\".api.cabinet.v1.NearbyCabinetReply\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/cabinet/nearby\x12m\n" + + "\vFindCabinet\x12\".api.cabinet.v1.FindCabinetRequest\x1a .api.cabinet.v1.FindCabinetReply\"\x18\x82\xd3\xe4\x93\x02\x12\x12\x10/v1/cabinet/find\x12\x8e\x01\n" + + "\x13FindFavoriteCabinet\x12*.api.cabinet.v1.FindFavoriteCabinetRequest\x1a(.api.cabinet.v1.FindFavoriteCabinetReply\"!\x82\xd3\xe4\x93\x02\x1b\x12\x19/v1/cabinet/favorite/find\x12\x8e\x01\n" + + "\x13ListFavoriteCabinet\x12*.api.cabinet.v1.ListFavoriteCabinetRequest\x1a(.api.cabinet.v1.ListFavoriteCabinetReply\"!\x82\xd3\xe4\x93\x02\x1b\x12\x19/v1/cabinet/favorite/list\x12\x96\x01\n" + + "\x15DeleteFavoriteCabinet\x12,.api.cabinet.v1.DeleteFavoriteCabinetRequest\x1a*.api.cabinet.v1.DeleteFavoriteCabinetReply\"#\x82\xd3\xe4\x93\x02\x1d*\x1b/v1/cabinet/favorite/delete\x12\x94\x01\n" + + "\x13FindCabinetByParcel\x12*.api.cabinet.v1.FindCabinetByParcelRequest\x1a(.api.cabinet.v1.FindCabinetByParcelReply\"'\x82\xd3\xe4\x93\x02!\x12\x1f/v1/cabinet/parcel/{express_no}\x12\x8d\x01\n" + + "\x12ListCourierParcels\x12).api.cabinet.v1.ListCourierParcelsRequest\x1a'.api.cabinet.v1.ListCourierParcelsReply\"#\x82\xd3\xe4\x93\x02\x1d\x12\x1b/v1/cabinet/courier/parcels\x12\x8d\x01\n" + + "\x10SyncLockerStatus\x12'.api.cabinet.v1.SyncLockerStatusRequest\x1a%.api.cabinet.v1.SyncLockerStatusReply\")\x82\xd3\xe4\x93\x02#:\x01*\"\x1e/v1/cabinet/sync-locker-status\x12i\n" + + "\tGetParcel\x12 .api.cabinet.v1.GetParcelRequest\x1a\x1e.api.cabinet.v1.GetParcelReply\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/v1/cabinet/parcelB-\n" + + "\x0eapi.cabinet.v1P\x01Z\x19Cabinet/api/cabinet/v1;v1b\x06proto3" + var ( file_cabinet_v1_cabinet_proto_rawDescOnce sync.Once - file_cabinet_v1_cabinet_proto_rawDescData = file_cabinet_v1_cabinet_proto_rawDesc + file_cabinet_v1_cabinet_proto_rawDescData []byte ) func file_cabinet_v1_cabinet_proto_rawDescGZIP() []byte { file_cabinet_v1_cabinet_proto_rawDescOnce.Do(func() { - file_cabinet_v1_cabinet_proto_rawDescData = protoimpl.X.CompressGZIP(file_cabinet_v1_cabinet_proto_rawDescData) + file_cabinet_v1_cabinet_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cabinet_v1_cabinet_proto_rawDesc), len(file_cabinet_v1_cabinet_proto_rawDesc))) }) return file_cabinet_v1_cabinet_proto_rawDescData } -var file_cabinet_v1_cabinet_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_cabinet_v1_cabinet_proto_msgTypes = make([]protoimpl.MessageInfo, 32) var file_cabinet_v1_cabinet_proto_goTypes = []any{ (*FindCabinetRequest)(nil), // 0: api.cabinet.v1.FindCabinetRequest (*FindCabinetReply)(nil), // 1: api.cabinet.v1.FindCabinetReply @@ -1606,6 +1974,14 @@ var file_cabinet_v1_cabinet_proto_goTypes = []any{ (*FindCabinetByParcelRequest)(nil), // 21: api.cabinet.v1.FindCabinetByParcelRequest (*FindCabinetByParcelReply)(nil), // 22: api.cabinet.v1.FindCabinetByParcelReply (*ParcelInfo)(nil), // 23: api.cabinet.v1.ParcelInfo + (*ListCourierParcelsRequest)(nil), // 24: api.cabinet.v1.ListCourierParcelsRequest + (*ListCourierParcelsReply)(nil), // 25: api.cabinet.v1.ListCourierParcelsReply + (*CabinetParcelGroup)(nil), // 26: api.cabinet.v1.CabinetParcelGroup + (*SyncLockerStatusRequest)(nil), // 27: api.cabinet.v1.SyncLockerStatusRequest + (*SyncLockerStatusReply)(nil), // 28: api.cabinet.v1.SyncLockerStatusReply + (*GetParcelRequest)(nil), // 29: api.cabinet.v1.GetParcelRequest + (*GetParcelReply)(nil), // 30: api.cabinet.v1.GetParcelReply + (*LockerInfo)(nil), // 31: api.cabinet.v1.LockerInfo } var file_cabinet_v1_cabinet_proto_depIdxs = []int32{ 14, // 0: api.cabinet.v1.NearbyCabinetReply.cabinets:type_name -> api.cabinet.v1.CabinetInfo @@ -1613,33 +1989,45 @@ var file_cabinet_v1_cabinet_proto_depIdxs = []int32{ 14, // 2: api.cabinet.v1.ListFavoriteCabinetReply.cabinets:type_name -> api.cabinet.v1.CabinetInfo 14, // 3: api.cabinet.v1.FindCabinetByParcelReply.cabinet:type_name -> api.cabinet.v1.CabinetInfo 23, // 4: api.cabinet.v1.FindCabinetByParcelReply.parcel:type_name -> api.cabinet.v1.ParcelInfo - 2, // 5: api.cabinet.v1.Cabinet.CreateCabinet:input_type -> api.cabinet.v1.CreateCabinetRequest - 4, // 6: api.cabinet.v1.Cabinet.UpdateCabinet:input_type -> api.cabinet.v1.UpdateCabinetRequest - 6, // 7: api.cabinet.v1.Cabinet.DeleteCabinet:input_type -> api.cabinet.v1.DeleteCabinetRequest - 8, // 8: api.cabinet.v1.Cabinet.GetCabinet:input_type -> api.cabinet.v1.GetCabinetRequest - 10, // 9: api.cabinet.v1.Cabinet.ListCabinet:input_type -> api.cabinet.v1.ListCabinetRequest - 12, // 10: api.cabinet.v1.Cabinet.NearbyCabinet:input_type -> api.cabinet.v1.NearbyCabinetRequest - 0, // 11: api.cabinet.v1.Cabinet.FindCabinet:input_type -> api.cabinet.v1.FindCabinetRequest - 15, // 12: api.cabinet.v1.Cabinet.FindFavoriteCabinet:input_type -> api.cabinet.v1.FindFavoriteCabinetRequest - 17, // 13: api.cabinet.v1.Cabinet.ListFavoriteCabinet:input_type -> api.cabinet.v1.ListFavoriteCabinetRequest - 19, // 14: api.cabinet.v1.Cabinet.DeleteFavoriteCabinet:input_type -> api.cabinet.v1.DeleteFavoriteCabinetRequest - 21, // 15: api.cabinet.v1.Cabinet.FindCabinetByParcel:input_type -> api.cabinet.v1.FindCabinetByParcelRequest - 3, // 16: api.cabinet.v1.Cabinet.CreateCabinet:output_type -> api.cabinet.v1.CreateCabinetReply - 5, // 17: api.cabinet.v1.Cabinet.UpdateCabinet:output_type -> api.cabinet.v1.UpdateCabinetReply - 7, // 18: api.cabinet.v1.Cabinet.DeleteCabinet:output_type -> api.cabinet.v1.DeleteCabinetReply - 9, // 19: api.cabinet.v1.Cabinet.GetCabinet:output_type -> api.cabinet.v1.GetCabinetReply - 11, // 20: api.cabinet.v1.Cabinet.ListCabinet:output_type -> api.cabinet.v1.ListCabinetReply - 13, // 21: api.cabinet.v1.Cabinet.NearbyCabinet:output_type -> api.cabinet.v1.NearbyCabinetReply - 1, // 22: api.cabinet.v1.Cabinet.FindCabinet:output_type -> api.cabinet.v1.FindCabinetReply - 16, // 23: api.cabinet.v1.Cabinet.FindFavoriteCabinet:output_type -> api.cabinet.v1.FindFavoriteCabinetReply - 18, // 24: api.cabinet.v1.Cabinet.ListFavoriteCabinet:output_type -> api.cabinet.v1.ListFavoriteCabinetReply - 20, // 25: api.cabinet.v1.Cabinet.DeleteFavoriteCabinet:output_type -> api.cabinet.v1.DeleteFavoriteCabinetReply - 22, // 26: api.cabinet.v1.Cabinet.FindCabinetByParcel:output_type -> api.cabinet.v1.FindCabinetByParcelReply - 16, // [16:27] is the sub-list for method output_type - 5, // [5:16] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 26, // 5: api.cabinet.v1.ListCourierParcelsReply.groups:type_name -> api.cabinet.v1.CabinetParcelGroup + 14, // 6: api.cabinet.v1.CabinetParcelGroup.cabinet:type_name -> api.cabinet.v1.CabinetInfo + 23, // 7: api.cabinet.v1.CabinetParcelGroup.parcels:type_name -> api.cabinet.v1.ParcelInfo + 23, // 8: api.cabinet.v1.GetParcelReply.parcel:type_name -> api.cabinet.v1.ParcelInfo + 14, // 9: api.cabinet.v1.GetParcelReply.cabinet:type_name -> api.cabinet.v1.CabinetInfo + 31, // 10: api.cabinet.v1.GetParcelReply.locker:type_name -> api.cabinet.v1.LockerInfo + 2, // 11: api.cabinet.v1.Cabinet.CreateCabinet:input_type -> api.cabinet.v1.CreateCabinetRequest + 4, // 12: api.cabinet.v1.Cabinet.UpdateCabinet:input_type -> api.cabinet.v1.UpdateCabinetRequest + 6, // 13: api.cabinet.v1.Cabinet.DeleteCabinet:input_type -> api.cabinet.v1.DeleteCabinetRequest + 8, // 14: api.cabinet.v1.Cabinet.GetCabinet:input_type -> api.cabinet.v1.GetCabinetRequest + 10, // 15: api.cabinet.v1.Cabinet.ListCabinet:input_type -> api.cabinet.v1.ListCabinetRequest + 12, // 16: api.cabinet.v1.Cabinet.NearbyCabinet:input_type -> api.cabinet.v1.NearbyCabinetRequest + 0, // 17: api.cabinet.v1.Cabinet.FindCabinet:input_type -> api.cabinet.v1.FindCabinetRequest + 15, // 18: api.cabinet.v1.Cabinet.FindFavoriteCabinet:input_type -> api.cabinet.v1.FindFavoriteCabinetRequest + 17, // 19: api.cabinet.v1.Cabinet.ListFavoriteCabinet:input_type -> api.cabinet.v1.ListFavoriteCabinetRequest + 19, // 20: api.cabinet.v1.Cabinet.DeleteFavoriteCabinet:input_type -> api.cabinet.v1.DeleteFavoriteCabinetRequest + 21, // 21: api.cabinet.v1.Cabinet.FindCabinetByParcel:input_type -> api.cabinet.v1.FindCabinetByParcelRequest + 24, // 22: api.cabinet.v1.Cabinet.ListCourierParcels:input_type -> api.cabinet.v1.ListCourierParcelsRequest + 27, // 23: api.cabinet.v1.Cabinet.SyncLockerStatus:input_type -> api.cabinet.v1.SyncLockerStatusRequest + 29, // 24: api.cabinet.v1.Cabinet.GetParcel:input_type -> api.cabinet.v1.GetParcelRequest + 3, // 25: api.cabinet.v1.Cabinet.CreateCabinet:output_type -> api.cabinet.v1.CreateCabinetReply + 5, // 26: api.cabinet.v1.Cabinet.UpdateCabinet:output_type -> api.cabinet.v1.UpdateCabinetReply + 7, // 27: api.cabinet.v1.Cabinet.DeleteCabinet:output_type -> api.cabinet.v1.DeleteCabinetReply + 9, // 28: api.cabinet.v1.Cabinet.GetCabinet:output_type -> api.cabinet.v1.GetCabinetReply + 11, // 29: api.cabinet.v1.Cabinet.ListCabinet:output_type -> api.cabinet.v1.ListCabinetReply + 13, // 30: api.cabinet.v1.Cabinet.NearbyCabinet:output_type -> api.cabinet.v1.NearbyCabinetReply + 1, // 31: api.cabinet.v1.Cabinet.FindCabinet:output_type -> api.cabinet.v1.FindCabinetReply + 16, // 32: api.cabinet.v1.Cabinet.FindFavoriteCabinet:output_type -> api.cabinet.v1.FindFavoriteCabinetReply + 18, // 33: api.cabinet.v1.Cabinet.ListFavoriteCabinet:output_type -> api.cabinet.v1.ListFavoriteCabinetReply + 20, // 34: api.cabinet.v1.Cabinet.DeleteFavoriteCabinet:output_type -> api.cabinet.v1.DeleteFavoriteCabinetReply + 22, // 35: api.cabinet.v1.Cabinet.FindCabinetByParcel:output_type -> api.cabinet.v1.FindCabinetByParcelReply + 25, // 36: api.cabinet.v1.Cabinet.ListCourierParcels:output_type -> api.cabinet.v1.ListCourierParcelsReply + 28, // 37: api.cabinet.v1.Cabinet.SyncLockerStatus:output_type -> api.cabinet.v1.SyncLockerStatusReply + 30, // 38: api.cabinet.v1.Cabinet.GetParcel:output_type -> api.cabinet.v1.GetParcelReply + 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_cabinet_v1_cabinet_proto_init() } @@ -1647,303 +2035,13 @@ func file_cabinet_v1_cabinet_proto_init() { if File_cabinet_v1_cabinet_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_cabinet_v1_cabinet_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*FindCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*FindCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*CreateCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*CreateCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*UpdateCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*UpdateCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*DeleteCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*DeleteCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*GetCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*GetCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*ListCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*ListCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*NearbyCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[13].Exporter = func(v any, i int) any { - switch v := v.(*NearbyCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[14].Exporter = func(v any, i int) any { - switch v := v.(*CabinetInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[15].Exporter = func(v any, i int) any { - switch v := v.(*FindFavoriteCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[16].Exporter = func(v any, i int) any { - switch v := v.(*FindFavoriteCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[17].Exporter = func(v any, i int) any { - switch v := v.(*ListFavoriteCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[18].Exporter = func(v any, i int) any { - switch v := v.(*ListFavoriteCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[19].Exporter = func(v any, i int) any { - switch v := v.(*DeleteFavoriteCabinetRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[20].Exporter = func(v any, i int) any { - switch v := v.(*DeleteFavoriteCabinetReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[21].Exporter = func(v any, i int) any { - switch v := v.(*FindCabinetByParcelRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[22].Exporter = func(v any, i int) any { - switch v := v.(*FindCabinetByParcelReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cabinet_v1_cabinet_proto_msgTypes[23].Exporter = func(v any, i int) any { - switch v := v.(*ParcelInfo); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_cabinet_v1_cabinet_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_cabinet_v1_cabinet_proto_rawDesc), len(file_cabinet_v1_cabinet_proto_rawDesc)), NumEnums: 0, - NumMessages: 24, + NumMessages: 32, NumExtensions: 0, NumServices: 1, }, @@ -1952,7 +2050,6 @@ func file_cabinet_v1_cabinet_proto_init() { MessageInfos: file_cabinet_v1_cabinet_proto_msgTypes, }.Build() File_cabinet_v1_cabinet_proto = out.File - file_cabinet_v1_cabinet_proto_rawDesc = nil file_cabinet_v1_cabinet_proto_goTypes = nil file_cabinet_v1_cabinet_proto_depIdxs = nil } diff --git a/api/cabinet/v1/cabinet.proto b/api/cabinet/v1/cabinet.proto index 72d5c1236de775741f3817830bb543aa66fbff2e..46c0370fa797c2b8057bf930ae0f72dabc43a262 100644 --- a/api/cabinet/v1/cabinet.proto +++ b/api/cabinet/v1/cabinet.proto @@ -143,6 +143,49 @@ service Cabinet { get: "/v1/cabinet/parcel/{express_no}" }; }; + + // ListCourierParcels 获取快递员派件列表 + // 功能:根据快递员ID查询其派送的包裹列表,按柜机分组 + // 支持按派件状态筛选 + // HTTP路径:GET /v1/cabinet/courier/parcels + // 查询参数:courier_id(快递员ID)、parcel_status(可选,包裹状态) + // 响应:按柜机分组的包裹列表 + rpc ListCourierParcels (ListCourierParcelsRequest) returns (ListCourierParcelsReply) { + option (google.api.http) = { + get: "/v1/cabinet/courier/parcels" + }; + }; + + // SyncLockerStatus 同步智能柜格口状态和可用数量 + // 功能:根据包裹状态更新格口状态,确保格口使用数量与包裹状态一致 + // 逻辑: + // 1. 查询智能柜下所有格口 + // 2. 查询该智能柜下所有待取件(0)和滞留件(1)的包裹 + // 3. 根据包裹状态更新格口状态(有包裹且状态为待取件或滞留件 -> 占用,否则 -> 空闲) + // 4. 更新智能柜的可用格口数量 = 总格口数 - 使用中的格口数 + // HTTP路径:POST /v1/cabinet/sync-locker-status + // 请求体:SyncLockerStatusRequest + // 响应:SyncLockerStatusReply + rpc SyncLockerStatus (SyncLockerStatusRequest) returns (SyncLockerStatusReply) { + option (google.api.http) = { + post: "/v1/cabinet/sync-locker-status" + body: "*" + }; + }; + + // GetParcel 查询包裹详情 + // 功能:根据包裹ID或快递单号后5位查询包裹详情 + // 支持两种查询方式: + // 1. 根据包裹ID查询(parcel_id > 0) + // 2. 根据快递单号后5位查询(express_no_suffix 不为空) + // HTTP路径:GET /v1/cabinet/parcel + // 查询参数:parcel_id(可选)或 express_no_suffix(可选) + // 响应:包裹详细信息 + rpc GetParcel (GetParcelRequest) returns (GetParcelReply) { + option (google.api.http) = { + get: "/v1/cabinet/parcel" + }; + }; } // FindCabinetRequest 根据ID查找智能柜的请求参数 @@ -308,4 +351,71 @@ message ParcelInfo { string recipient_phone = 4; // 收件人电话,包裹接收人联系方式 int32 locker_id = 5; // 格口ID,包裹所在的智能柜格口编号 int32 parcel_status = 6; // 包裹状态(0-待取件,1-滞留件,2-已取件,3-已撤回,4-已完结) + string dispatch_time = 7; // 派件时间 + int32 courier_id = 8; // 派件员ID +} + +// ListCourierParcelsRequest 获取快递员派件列表的请求参数 +// 用于查询快递员的派件情况 +message ListCourierParcelsRequest { + int32 courier_id = 1; // 快递员ID,必填 + int32 parcel_status = 2; // 包裹状态(可选,-1表示查询所有状态,0-待取件,1-滞留件,2-已取件,3-已撤回,4-已完结) +} + +// ListCourierParcelsReply 获取快递员派件列表的响应结果 +// 返回按柜机分组的包裹列表,形成层级结构 +message ListCourierParcelsReply { + repeated CabinetParcelGroup groups = 1; // 按柜机分组的包裹列表 + int32 total_parcels = 2; // 包裹总数 + int32 total_cabinets = 3; // 涉及的柜机总数 +} + +// CabinetParcelGroup 柜机包裹分组 +// 表示一个柜机及其包含的包裹列表,用于实现层级结构 +message CabinetParcelGroup { + CabinetInfo cabinet = 1; // 柜机信息(父级) + repeated ParcelInfo parcels = 2; // 柜机内的包裹列表(子级) + int32 parcel_count = 3; // 该柜机的包裹数量 +} + +// SyncLockerStatusRequest 同步智能柜格口状态的请求参数 +// 用于指定要同步的智能柜ID +message SyncLockerStatusRequest { + int64 cabinet_id = 1; // 智能柜ID,系统唯一标识 +} + +// SyncLockerStatusReply 同步智能柜格口状态的响应结果 +// 返回同步结果和统计信息 +message SyncLockerStatusReply { + bool success = 1; // 是否同步成功 + string message = 2; // 同步结果消息 + int32 total_compartments = 3; // 总格口数量 + int32 used_compartments = 4; // 使用中的格口数量 + int32 available_compartments = 5; // 可用格口数量 + int32 updated_lockers = 6; // 更新的格口数量 +} + +// GetParcelRequest 查询包裹详情的请求参数 +// 支持两种查询方式:包裹ID或快递单号后5位 +message GetParcelRequest { + int32 parcel_id = 1; // 包裹ID(可选,如果提供则按ID查询) + string express_no_suffix = 2; // 快递单号后5位(可选,如果提供则按后5位查询) +} + +// GetParcelReply 查询包裹详情的响应结果 +// 返回包裹的详细信息,包括所在智能柜信息 +message GetParcelReply { + ParcelInfo parcel = 1; // 包裹信息 + CabinetInfo cabinet = 2; // 所在智能柜信息(如果查询到) + LockerInfo locker = 3; // 所在格口信息(如果查询到) +} + +// LockerInfo 格口信息 +// 用于返回包裹所在的格口详细信息 +message LockerInfo { + int32 locker_id = 1; // 格口ID + string locker_number = 2; // 格口编号(如A01、B02) + string size = 3; // 格口大小(small/medium/large) + int32 status = 4; // 格口状态(0-空闲,1-占用,2-故障) + int64 cabinet_id = 5; // 所属智能柜ID } \ No newline at end of file diff --git a/api/cabinet/v1/cabinet_grpc.pb.go b/api/cabinet/v1/cabinet_grpc.pb.go index d2afe170e0a7c7b95723cdfe933a7fe73b335f96..cebbfff036fdf148d3393dc0067041a2eb5aeb93 100644 --- a/api/cabinet/v1/cabinet_grpc.pb.go +++ b/api/cabinet/v1/cabinet_grpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.4.0 +// - protoc-gen-go-grpc v1.5.1 // - protoc v3.19.4 // source: cabinet/v1/cabinet.proto @@ -15,8 +15,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.62.0 or later. -const _ = grpc.SupportPackageIsVersion8 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( Cabinet_CreateCabinet_FullMethodName = "/api.cabinet.v1.Cabinet/CreateCabinet" @@ -30,6 +30,9 @@ const ( Cabinet_ListFavoriteCabinet_FullMethodName = "/api.cabinet.v1.Cabinet/ListFavoriteCabinet" Cabinet_DeleteFavoriteCabinet_FullMethodName = "/api.cabinet.v1.Cabinet/DeleteFavoriteCabinet" Cabinet_FindCabinetByParcel_FullMethodName = "/api.cabinet.v1.Cabinet/FindCabinetByParcel" + Cabinet_ListCourierParcels_FullMethodName = "/api.cabinet.v1.Cabinet/ListCourierParcels" + Cabinet_SyncLockerStatus_FullMethodName = "/api.cabinet.v1.Cabinet/SyncLockerStatus" + Cabinet_GetParcel_FullMethodName = "/api.cabinet.v1.Cabinet/GetParcel" ) // CabinetClient is the client API for Cabinet service. @@ -114,6 +117,33 @@ type CabinetClient interface { // 路径参数:express_no(快递单号) // 响应:柜机信息和包裹信息 FindCabinetByParcel(ctx context.Context, in *FindCabinetByParcelRequest, opts ...grpc.CallOption) (*FindCabinetByParcelReply, error) + // ListCourierParcels 获取快递员派件列表 + // 功能:根据快递员ID查询其派送的包裹列表,按柜机分组 + // 支持按派件状态筛选 + // HTTP路径:GET /v1/cabinet/courier/parcels + // 查询参数:courier_id(快递员ID)、parcel_status(可选,包裹状态) + // 响应:按柜机分组的包裹列表 + ListCourierParcels(ctx context.Context, in *ListCourierParcelsRequest, opts ...grpc.CallOption) (*ListCourierParcelsReply, error) + // SyncLockerStatus 同步智能柜格口状态和可用数量 + // 功能:根据包裹状态更新格口状态,确保格口使用数量与包裹状态一致 + // 逻辑: + // 1. 查询智能柜下所有格口 + // 2. 查询该智能柜下所有待取件(0)和滞留件(1)的包裹 + // 3. 根据包裹状态更新格口状态(有包裹且状态为待取件或滞留件 -> 占用,否则 -> 空闲) + // 4. 更新智能柜的可用格口数量 = 总格口数 - 使用中的格口数 + // HTTP路径:POST /v1/cabinet/sync-locker-status + // 请求体:SyncLockerStatusRequest + // 响应:SyncLockerStatusReply + SyncLockerStatus(ctx context.Context, in *SyncLockerStatusRequest, opts ...grpc.CallOption) (*SyncLockerStatusReply, error) + // GetParcel 查询包裹详情 + // 功能:根据包裹ID或快递单号后5位查询包裹详情 + // 支持两种查询方式: + // 1. 根据包裹ID查询(parcel_id > 0) + // 2. 根据快递单号后5位查询(express_no_suffix 不为空) + // HTTP路径:GET /v1/cabinet/parcel + // 查询参数:parcel_id(可选)或 express_no_suffix(可选) + // 响应:包裹详细信息 + GetParcel(ctx context.Context, in *GetParcelRequest, opts ...grpc.CallOption) (*GetParcelReply, error) } type cabinetClient struct { @@ -234,9 +264,39 @@ func (c *cabinetClient) FindCabinetByParcel(ctx context.Context, in *FindCabinet return out, nil } +func (c *cabinetClient) ListCourierParcels(ctx context.Context, in *ListCourierParcelsRequest, opts ...grpc.CallOption) (*ListCourierParcelsReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListCourierParcelsReply) + err := c.cc.Invoke(ctx, Cabinet_ListCourierParcels_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cabinetClient) SyncLockerStatus(ctx context.Context, in *SyncLockerStatusRequest, opts ...grpc.CallOption) (*SyncLockerStatusReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SyncLockerStatusReply) + err := c.cc.Invoke(ctx, Cabinet_SyncLockerStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cabinetClient) GetParcel(ctx context.Context, in *GetParcelRequest, opts ...grpc.CallOption) (*GetParcelReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetParcelReply) + err := c.cc.Invoke(ctx, Cabinet_GetParcel_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // CabinetServer is the server API for Cabinet service. // All implementations must embed UnimplementedCabinetServer -// for forward compatibility +// for forward compatibility. // // Cabinet 智能柜服务接口 // 该服务提供智能柜相关的所有核心功能,包括智能柜管理、位置查询、收藏管理等 @@ -316,12 +376,42 @@ type CabinetServer interface { // 路径参数:express_no(快递单号) // 响应:柜机信息和包裹信息 FindCabinetByParcel(context.Context, *FindCabinetByParcelRequest) (*FindCabinetByParcelReply, error) + // ListCourierParcels 获取快递员派件列表 + // 功能:根据快递员ID查询其派送的包裹列表,按柜机分组 + // 支持按派件状态筛选 + // HTTP路径:GET /v1/cabinet/courier/parcels + // 查询参数:courier_id(快递员ID)、parcel_status(可选,包裹状态) + // 响应:按柜机分组的包裹列表 + ListCourierParcels(context.Context, *ListCourierParcelsRequest) (*ListCourierParcelsReply, error) + // SyncLockerStatus 同步智能柜格口状态和可用数量 + // 功能:根据包裹状态更新格口状态,确保格口使用数量与包裹状态一致 + // 逻辑: + // 1. 查询智能柜下所有格口 + // 2. 查询该智能柜下所有待取件(0)和滞留件(1)的包裹 + // 3. 根据包裹状态更新格口状态(有包裹且状态为待取件或滞留件 -> 占用,否则 -> 空闲) + // 4. 更新智能柜的可用格口数量 = 总格口数 - 使用中的格口数 + // HTTP路径:POST /v1/cabinet/sync-locker-status + // 请求体:SyncLockerStatusRequest + // 响应:SyncLockerStatusReply + SyncLockerStatus(context.Context, *SyncLockerStatusRequest) (*SyncLockerStatusReply, error) + // GetParcel 查询包裹详情 + // 功能:根据包裹ID或快递单号后5位查询包裹详情 + // 支持两种查询方式: + // 1. 根据包裹ID查询(parcel_id > 0) + // 2. 根据快递单号后5位查询(express_no_suffix 不为空) + // HTTP路径:GET /v1/cabinet/parcel + // 查询参数:parcel_id(可选)或 express_no_suffix(可选) + // 响应:包裹详细信息 + GetParcel(context.Context, *GetParcelRequest) (*GetParcelReply, error) mustEmbedUnimplementedCabinetServer() } -// UnimplementedCabinetServer must be embedded to have forward compatible implementations. -type UnimplementedCabinetServer struct { -} +// UnimplementedCabinetServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCabinetServer struct{} func (UnimplementedCabinetServer) CreateCabinet(context.Context, *CreateCabinetRequest) (*CreateCabinetReply, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateCabinet not implemented") @@ -356,7 +446,17 @@ func (UnimplementedCabinetServer) DeleteFavoriteCabinet(context.Context, *Delete func (UnimplementedCabinetServer) FindCabinetByParcel(context.Context, *FindCabinetByParcelRequest) (*FindCabinetByParcelReply, error) { return nil, status.Errorf(codes.Unimplemented, "method FindCabinetByParcel not implemented") } +func (UnimplementedCabinetServer) ListCourierParcels(context.Context, *ListCourierParcelsRequest) (*ListCourierParcelsReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListCourierParcels not implemented") +} +func (UnimplementedCabinetServer) SyncLockerStatus(context.Context, *SyncLockerStatusRequest) (*SyncLockerStatusReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SyncLockerStatus not implemented") +} +func (UnimplementedCabinetServer) GetParcel(context.Context, *GetParcelRequest) (*GetParcelReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetParcel not implemented") +} func (UnimplementedCabinetServer) mustEmbedUnimplementedCabinetServer() {} +func (UnimplementedCabinetServer) testEmbeddedByValue() {} // UnsafeCabinetServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to CabinetServer will @@ -366,6 +466,13 @@ type UnsafeCabinetServer interface { } func RegisterCabinetServer(s grpc.ServiceRegistrar, srv CabinetServer) { + // If the following call pancis, it indicates UnimplementedCabinetServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&Cabinet_ServiceDesc, srv) } @@ -567,6 +674,60 @@ func _Cabinet_FindCabinetByParcel_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _Cabinet_ListCourierParcels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListCourierParcelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CabinetServer).ListCourierParcels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Cabinet_ListCourierParcels_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CabinetServer).ListCourierParcels(ctx, req.(*ListCourierParcelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Cabinet_SyncLockerStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SyncLockerStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CabinetServer).SyncLockerStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Cabinet_SyncLockerStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CabinetServer).SyncLockerStatus(ctx, req.(*SyncLockerStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Cabinet_GetParcel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetParcelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CabinetServer).GetParcel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Cabinet_GetParcel_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CabinetServer).GetParcel(ctx, req.(*GetParcelRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Cabinet_ServiceDesc is the grpc.ServiceDesc for Cabinet service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -618,6 +779,18 @@ var Cabinet_ServiceDesc = grpc.ServiceDesc{ MethodName: "FindCabinetByParcel", Handler: _Cabinet_FindCabinetByParcel_Handler, }, + { + MethodName: "ListCourierParcels", + Handler: _Cabinet_ListCourierParcels_Handler, + }, + { + MethodName: "SyncLockerStatus", + Handler: _Cabinet_SyncLockerStatus_Handler, + }, + { + MethodName: "GetParcel", + Handler: _Cabinet_GetParcel_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "cabinet/v1/cabinet.proto", diff --git a/api/cabinet/v1/cabinet_http.pb.go b/api/cabinet/v1/cabinet_http.pb.go index ab5ff79da887df5ed980c6711183eab4cb563e4d..c47d2ccbe2d6481de26fd951d5d6b642ad448853 100644 --- a/api/cabinet/v1/cabinet_http.pb.go +++ b/api/cabinet/v1/cabinet_http.pb.go @@ -26,9 +26,12 @@ const OperationCabinetFindCabinet = "/api.cabinet.v1.Cabinet/FindCabinet" const OperationCabinetFindCabinetByParcel = "/api.cabinet.v1.Cabinet/FindCabinetByParcel" const OperationCabinetFindFavoriteCabinet = "/api.cabinet.v1.Cabinet/FindFavoriteCabinet" const OperationCabinetGetCabinet = "/api.cabinet.v1.Cabinet/GetCabinet" +const OperationCabinetGetParcel = "/api.cabinet.v1.Cabinet/GetParcel" const OperationCabinetListCabinet = "/api.cabinet.v1.Cabinet/ListCabinet" +const OperationCabinetListCourierParcels = "/api.cabinet.v1.Cabinet/ListCourierParcels" const OperationCabinetListFavoriteCabinet = "/api.cabinet.v1.Cabinet/ListFavoriteCabinet" const OperationCabinetNearbyCabinet = "/api.cabinet.v1.Cabinet/NearbyCabinet" +const OperationCabinetSyncLockerStatus = "/api.cabinet.v1.Cabinet/SyncLockerStatus" const OperationCabinetUpdateCabinet = "/api.cabinet.v1.Cabinet/UpdateCabinet" type CabinetHTTPServer interface { @@ -77,12 +80,28 @@ type CabinetHTTPServer interface { // 查询参数:参考GetCabinetRequest // 响应:GetCabinetReply GetCabinet(context.Context, *GetCabinetRequest) (*GetCabinetReply, error) + // GetParcel GetParcel 查询包裹详情 + // 功能:根据包裹ID或快递单号后5位查询包裹详情 + // 支持两种查询方式: + // 1. 根据包裹ID查询(parcel_id > 0) + // 2. 根据快递单号后5位查询(express_no_suffix 不为空) + // HTTP路径:GET /v1/cabinet/parcel + // 查询参数:parcel_id(可选)或 express_no_suffix(可选) + // 响应:包裹详细信息 + GetParcel(context.Context, *GetParcelRequest) (*GetParcelReply, error) // ListCabinet ListCabinet 获取智能柜列表 // 功能:查询智能柜列表,支持分页和筛选 // HTTP路径:GET /v1/cabinet/list // 查询参数:参考ListCabinetRequest // 响应:ListCabinetReply ListCabinet(context.Context, *ListCabinetRequest) (*ListCabinetReply, error) + // ListCourierParcels ListCourierParcels 获取快递员派件列表 + // 功能:根据快递员ID查询其派送的包裹列表,按柜机分组 + // 支持按派件状态筛选 + // HTTP路径:GET /v1/cabinet/courier/parcels + // 查询参数:courier_id(快递员ID)、parcel_status(可选,包裹状态) + // 响应:按柜机分组的包裹列表 + ListCourierParcels(context.Context, *ListCourierParcelsRequest) (*ListCourierParcelsReply, error) // ListFavoriteCabinet ListFavoriteCabinet 获取用户收藏的柜机列表 // 功能:获取指定用户收藏的所有智能柜列表,支持分页 // 用于用户管理其收藏的柜机 @@ -97,6 +116,17 @@ type CabinetHTTPServer interface { // 查询参数:纬度、经度、搜索半径、结果数量限制 // 响应:附近的智能柜列表及距离信息 NearbyCabinet(context.Context, *NearbyCabinetRequest) (*NearbyCabinetReply, error) + // SyncLockerStatus SyncLockerStatus 同步智能柜格口状态和可用数量 + // 功能:根据包裹状态更新格口状态,确保格口使用数量与包裹状态一致 + // 逻辑: + // 1. 查询智能柜下所有格口 + // 2. 查询该智能柜下所有待取件(0)和滞留件(1)的包裹 + // 3. 根据包裹状态更新格口状态(有包裹且状态为待取件或滞留件 -> 占用,否则 -> 空闲) + // 4. 更新智能柜的可用格口数量 = 总格口数 - 使用中的格口数 + // HTTP路径:POST /v1/cabinet/sync-locker-status + // 请求体:SyncLockerStatusRequest + // 响应:SyncLockerStatusReply + SyncLockerStatus(context.Context, *SyncLockerStatusRequest) (*SyncLockerStatusReply, error) // UpdateCabinet UpdateCabinet 更新智能柜信息 // 功能:修改已存在的智能柜设备信息 // HTTP路径:PUT /v1/cabinet/update @@ -118,6 +148,9 @@ func RegisterCabinetHTTPServer(s *http.Server, srv CabinetHTTPServer) { r.GET("/v1/cabinet/favorite/list", _Cabinet_ListFavoriteCabinet0_HTTP_Handler(srv)) r.DELETE("/v1/cabinet/favorite/delete", _Cabinet_DeleteFavoriteCabinet0_HTTP_Handler(srv)) r.GET("/v1/cabinet/parcel/{express_no}", _Cabinet_FindCabinetByParcel0_HTTP_Handler(srv)) + r.GET("/v1/cabinet/courier/parcels", _Cabinet_ListCourierParcels0_HTTP_Handler(srv)) + r.POST("/v1/cabinet/sync-locker-status", _Cabinet_SyncLockerStatus0_HTTP_Handler(srv)) + r.GET("/v1/cabinet/parcel", _Cabinet_GetParcel0_HTTP_Handler(srv)) } func _Cabinet_CreateCabinet0_HTTP_Handler(srv CabinetHTTPServer) func(ctx http.Context) error { @@ -341,6 +374,66 @@ func _Cabinet_FindCabinetByParcel0_HTTP_Handler(srv CabinetHTTPServer) func(ctx } } +func _Cabinet_ListCourierParcels0_HTTP_Handler(srv CabinetHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in ListCourierParcelsRequest + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationCabinetListCourierParcels) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.ListCourierParcels(ctx, req.(*ListCourierParcelsRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*ListCourierParcelsReply) + return ctx.Result(200, reply) + } +} + +func _Cabinet_SyncLockerStatus0_HTTP_Handler(srv CabinetHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in SyncLockerStatusRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationCabinetSyncLockerStatus) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.SyncLockerStatus(ctx, req.(*SyncLockerStatusRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*SyncLockerStatusReply) + return ctx.Result(200, reply) + } +} + +func _Cabinet_GetParcel0_HTTP_Handler(srv CabinetHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in GetParcelRequest + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationCabinetGetParcel) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.GetParcel(ctx, req.(*GetParcelRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*GetParcelReply) + return ctx.Result(200, reply) + } +} + type CabinetHTTPClient interface { // CreateCabinet CreateCabinet 创建智能柜 // 功能:新增一个智能柜设备信息到系统 @@ -387,12 +480,28 @@ type CabinetHTTPClient interface { // 查询参数:参考GetCabinetRequest // 响应:GetCabinetReply GetCabinet(ctx context.Context, req *GetCabinetRequest, opts ...http.CallOption) (rsp *GetCabinetReply, err error) + // GetParcel GetParcel 查询包裹详情 + // 功能:根据包裹ID或快递单号后5位查询包裹详情 + // 支持两种查询方式: + // 1. 根据包裹ID查询(parcel_id > 0) + // 2. 根据快递单号后5位查询(express_no_suffix 不为空) + // HTTP路径:GET /v1/cabinet/parcel + // 查询参数:parcel_id(可选)或 express_no_suffix(可选) + // 响应:包裹详细信息 + GetParcel(ctx context.Context, req *GetParcelRequest, opts ...http.CallOption) (rsp *GetParcelReply, err error) // ListCabinet ListCabinet 获取智能柜列表 // 功能:查询智能柜列表,支持分页和筛选 // HTTP路径:GET /v1/cabinet/list // 查询参数:参考ListCabinetRequest // 响应:ListCabinetReply ListCabinet(ctx context.Context, req *ListCabinetRequest, opts ...http.CallOption) (rsp *ListCabinetReply, err error) + // ListCourierParcels ListCourierParcels 获取快递员派件列表 + // 功能:根据快递员ID查询其派送的包裹列表,按柜机分组 + // 支持按派件状态筛选 + // HTTP路径:GET /v1/cabinet/courier/parcels + // 查询参数:courier_id(快递员ID)、parcel_status(可选,包裹状态) + // 响应:按柜机分组的包裹列表 + ListCourierParcels(ctx context.Context, req *ListCourierParcelsRequest, opts ...http.CallOption) (rsp *ListCourierParcelsReply, err error) // ListFavoriteCabinet ListFavoriteCabinet 获取用户收藏的柜机列表 // 功能:获取指定用户收藏的所有智能柜列表,支持分页 // 用于用户管理其收藏的柜机 @@ -407,6 +516,17 @@ type CabinetHTTPClient interface { // 查询参数:纬度、经度、搜索半径、结果数量限制 // 响应:附近的智能柜列表及距离信息 NearbyCabinet(ctx context.Context, req *NearbyCabinetRequest, opts ...http.CallOption) (rsp *NearbyCabinetReply, err error) + // SyncLockerStatus SyncLockerStatus 同步智能柜格口状态和可用数量 + // 功能:根据包裹状态更新格口状态,确保格口使用数量与包裹状态一致 + // 逻辑: + // 1. 查询智能柜下所有格口 + // 2. 查询该智能柜下所有待取件(0)和滞留件(1)的包裹 + // 3. 根据包裹状态更新格口状态(有包裹且状态为待取件或滞留件 -> 占用,否则 -> 空闲) + // 4. 更新智能柜的可用格口数量 = 总格口数 - 使用中的格口数 + // HTTP路径:POST /v1/cabinet/sync-locker-status + // 请求体:SyncLockerStatusRequest + // 响应:SyncLockerStatusReply + SyncLockerStatus(ctx context.Context, req *SyncLockerStatusRequest, opts ...http.CallOption) (rsp *SyncLockerStatusReply, err error) // UpdateCabinet UpdateCabinet 更新智能柜信息 // 功能:修改已存在的智能柜设备信息 // HTTP路径:PUT /v1/cabinet/update @@ -552,6 +672,27 @@ func (c *CabinetHTTPClientImpl) GetCabinet(ctx context.Context, in *GetCabinetRe return &out, nil } +// GetParcel GetParcel 查询包裹详情 +// 功能:根据包裹ID或快递单号后5位查询包裹详情 +// 支持两种查询方式: +// 1. 根据包裹ID查询(parcel_id > 0) +// 2. 根据快递单号后5位查询(express_no_suffix 不为空) +// HTTP路径:GET /v1/cabinet/parcel +// 查询参数:parcel_id(可选)或 express_no_suffix(可选) +// 响应:包裹详细信息 +func (c *CabinetHTTPClientImpl) GetParcel(ctx context.Context, in *GetParcelRequest, opts ...http.CallOption) (*GetParcelReply, error) { + var out GetParcelReply + pattern := "/v1/cabinet/parcel" + path := binding.EncodeURL(pattern, in, true) + opts = append(opts, http.Operation(OperationCabinetGetParcel)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + // ListCabinet ListCabinet 获取智能柜列表 // 功能:查询智能柜列表,支持分页和筛选 // HTTP路径:GET /v1/cabinet/list @@ -570,6 +711,25 @@ func (c *CabinetHTTPClientImpl) ListCabinet(ctx context.Context, in *ListCabinet return &out, nil } +// ListCourierParcels ListCourierParcels 获取快递员派件列表 +// 功能:根据快递员ID查询其派送的包裹列表,按柜机分组 +// 支持按派件状态筛选 +// HTTP路径:GET /v1/cabinet/courier/parcels +// 查询参数:courier_id(快递员ID)、parcel_status(可选,包裹状态) +// 响应:按柜机分组的包裹列表 +func (c *CabinetHTTPClientImpl) ListCourierParcels(ctx context.Context, in *ListCourierParcelsRequest, opts ...http.CallOption) (*ListCourierParcelsReply, error) { + var out ListCourierParcelsReply + pattern := "/v1/cabinet/courier/parcels" + path := binding.EncodeURL(pattern, in, true) + opts = append(opts, http.Operation(OperationCabinetListCourierParcels)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + // ListFavoriteCabinet ListFavoriteCabinet 获取用户收藏的柜机列表 // 功能:获取指定用户收藏的所有智能柜列表,支持分页 // 用于用户管理其收藏的柜机 @@ -608,6 +768,29 @@ func (c *CabinetHTTPClientImpl) NearbyCabinet(ctx context.Context, in *NearbyCab return &out, nil } +// SyncLockerStatus SyncLockerStatus 同步智能柜格口状态和可用数量 +// 功能:根据包裹状态更新格口状态,确保格口使用数量与包裹状态一致 +// 逻辑: +// 1. 查询智能柜下所有格口 +// 2. 查询该智能柜下所有待取件(0)和滞留件(1)的包裹 +// 3. 根据包裹状态更新格口状态(有包裹且状态为待取件或滞留件 -> 占用,否则 -> 空闲) +// 4. 更新智能柜的可用格口数量 = 总格口数 - 使用中的格口数 +// HTTP路径:POST /v1/cabinet/sync-locker-status +// 请求体:SyncLockerStatusRequest +// 响应:SyncLockerStatusReply +func (c *CabinetHTTPClientImpl) SyncLockerStatus(ctx context.Context, in *SyncLockerStatusRequest, opts ...http.CallOption) (*SyncLockerStatusReply, error) { + var out SyncLockerStatusReply + pattern := "/v1/cabinet/sync-locker-status" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationCabinetSyncLockerStatus)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + // UpdateCabinet UpdateCabinet 更新智能柜信息 // 功能:修改已存在的智能柜设备信息 // HTTP路径:PUT /v1/cabinet/update diff --git a/api/helloworld/v1/greeter.pb.go b/api/helloworld/v1/greeter.pb.go index f95e53ceae868350bd442d91a607c32c00ae0d88..2aa44712d82b48c78487b6217470b03b3988b22d 100644 --- a/api/helloworld/v1/greeter.pb.go +++ b/api/helloworld/v1/greeter.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 -// protoc v3.19.3 -// source: helloworld/v1/greeter.proto +// protoc-gen-go v1.36.10 +// protoc v3.19.4 +// source: api/helloworld/v1/greeter.proto package v1 @@ -12,6 +12,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -23,20 +24,17 @@ const ( // The request message containing the user's name. type HelloRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + sizeCache protoimpl.SizeCache } func (x *HelloRequest) Reset() { *x = HelloRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_helloworld_v1_greeter_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_helloworld_v1_greeter_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HelloRequest) String() string { @@ -46,8 +44,8 @@ func (x *HelloRequest) String() string { func (*HelloRequest) ProtoMessage() {} func (x *HelloRequest) ProtoReflect() protoreflect.Message { - mi := &file_helloworld_v1_greeter_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_helloworld_v1_greeter_proto_msgTypes[0] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -59,7 +57,7 @@ func (x *HelloRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. func (*HelloRequest) Descriptor() ([]byte, []int) { - return file_helloworld_v1_greeter_proto_rawDescGZIP(), []int{0} + return file_api_helloworld_v1_greeter_proto_rawDescGZIP(), []int{0} } func (x *HelloRequest) GetName() string { @@ -71,20 +69,17 @@ func (x *HelloRequest) GetName() string { // The response message containing the greetings type HelloReply struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` unknownFields protoimpl.UnknownFields - - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + sizeCache protoimpl.SizeCache } func (x *HelloReply) Reset() { *x = HelloReply{} - if protoimpl.UnsafeEnabled { - mi := &file_helloworld_v1_greeter_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_helloworld_v1_greeter_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *HelloReply) String() string { @@ -94,8 +89,8 @@ func (x *HelloReply) String() string { func (*HelloReply) ProtoMessage() {} func (x *HelloReply) ProtoReflect() protoreflect.Message { - mi := &file_helloworld_v1_greeter_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_helloworld_v1_greeter_proto_msgTypes[1] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -107,7 +102,7 @@ func (x *HelloReply) ProtoReflect() protoreflect.Message { // Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. func (*HelloReply) Descriptor() ([]byte, []int) { - return file_helloworld_v1_greeter_proto_rawDescGZIP(), []int{1} + return file_api_helloworld_v1_greeter_proto_rawDescGZIP(), []int{1} } func (x *HelloReply) GetMessage() string { @@ -117,53 +112,38 @@ func (x *HelloReply) GetMessage() string { return "" } -var File_helloworld_v1_greeter_proto protoreflect.FileDescriptor - -var file_helloworld_v1_greeter_proto_rawDesc = []byte{ - 0x0a, 0x1b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x68, - 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, - 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, - 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x69, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, - 0x72, 0x12, 0x5e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x1b, 0x2e, - 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, - 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x68, 0x65, 0x6c, - 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x12, 0x12, 0x2f, - 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, - 0x7d, 0x42, 0x6c, 0x0a, 0x1c, 0x64, 0x65, 0x76, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x76, - 0x31, 0x42, 0x11, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x56, 0x31, 0x50, 0x01, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2f, 0x6b, 0x72, 0x61, - 0x74, 0x6f, 0x73, 0x2d, 0x6c, 0x61, 0x79, 0x6f, 0x75, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, - 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +var File_api_helloworld_v1_greeter_proto protoreflect.FileDescriptor + +const file_api_helloworld_v1_greeter_proto_rawDesc = "" + + "\n" + + "\x1fapi/helloworld/v1/greeter.proto\x12\rhelloworld.v1\x1a\x1cgoogle/api/annotations.proto\"\"\n" + + "\fHelloRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"&\n" + + "\n" + + "HelloReply\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage2i\n" + + "\aGreeter\x12^\n" + + "\bSayHello\x12\x1b.helloworld.v1.HelloRequest\x1a\x19.helloworld.v1.HelloReply\"\x1a\x82\xd3\xe4\x93\x02\x14\x12\x12/helloworld/{name}BQ\n" + + "\x1cdev.kratos.api.helloworld.v1B\x11HelloworldProtoV1P\x01Z\x1cCabinet/api/helloworld/v1;v1b\x06proto3" var ( - file_helloworld_v1_greeter_proto_rawDescOnce sync.Once - file_helloworld_v1_greeter_proto_rawDescData = file_helloworld_v1_greeter_proto_rawDesc + file_api_helloworld_v1_greeter_proto_rawDescOnce sync.Once + file_api_helloworld_v1_greeter_proto_rawDescData []byte ) -func file_helloworld_v1_greeter_proto_rawDescGZIP() []byte { - file_helloworld_v1_greeter_proto_rawDescOnce.Do(func() { - file_helloworld_v1_greeter_proto_rawDescData = protoimpl.X.CompressGZIP(file_helloworld_v1_greeter_proto_rawDescData) +func file_api_helloworld_v1_greeter_proto_rawDescGZIP() []byte { + file_api_helloworld_v1_greeter_proto_rawDescOnce.Do(func() { + file_api_helloworld_v1_greeter_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_helloworld_v1_greeter_proto_rawDesc), len(file_api_helloworld_v1_greeter_proto_rawDesc))) }) - return file_helloworld_v1_greeter_proto_rawDescData + return file_api_helloworld_v1_greeter_proto_rawDescData } -var file_helloworld_v1_greeter_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_helloworld_v1_greeter_proto_goTypes = []interface{}{ +var file_api_helloworld_v1_greeter_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_api_helloworld_v1_greeter_proto_goTypes = []any{ (*HelloRequest)(nil), // 0: helloworld.v1.HelloRequest (*HelloReply)(nil), // 1: helloworld.v1.HelloReply } -var file_helloworld_v1_greeter_proto_depIdxs = []int32{ +var file_api_helloworld_v1_greeter_proto_depIdxs = []int32{ 0, // 0: helloworld.v1.Greeter.SayHello:input_type -> helloworld.v1.HelloRequest 1, // 1: helloworld.v1.Greeter.SayHello:output_type -> helloworld.v1.HelloReply 1, // [1:2] is the sub-list for method output_type @@ -173,53 +153,26 @@ var file_helloworld_v1_greeter_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for field type_name } -func init() { file_helloworld_v1_greeter_proto_init() } -func file_helloworld_v1_greeter_proto_init() { - if File_helloworld_v1_greeter_proto != nil { +func init() { file_api_helloworld_v1_greeter_proto_init() } +func file_api_helloworld_v1_greeter_proto_init() { + if File_api_helloworld_v1_greeter_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_helloworld_v1_greeter_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HelloRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_helloworld_v1_greeter_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HelloReply); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_helloworld_v1_greeter_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_helloworld_v1_greeter_proto_rawDesc), len(file_api_helloworld_v1_greeter_proto_rawDesc)), NumEnums: 0, NumMessages: 2, NumExtensions: 0, NumServices: 1, }, - GoTypes: file_helloworld_v1_greeter_proto_goTypes, - DependencyIndexes: file_helloworld_v1_greeter_proto_depIdxs, - MessageInfos: file_helloworld_v1_greeter_proto_msgTypes, + GoTypes: file_api_helloworld_v1_greeter_proto_goTypes, + DependencyIndexes: file_api_helloworld_v1_greeter_proto_depIdxs, + MessageInfos: file_api_helloworld_v1_greeter_proto_msgTypes, }.Build() - File_helloworld_v1_greeter_proto = out.File - file_helloworld_v1_greeter_proto_rawDesc = nil - file_helloworld_v1_greeter_proto_goTypes = nil - file_helloworld_v1_greeter_proto_depIdxs = nil + File_api_helloworld_v1_greeter_proto = out.File + file_api_helloworld_v1_greeter_proto_goTypes = nil + file_api_helloworld_v1_greeter_proto_depIdxs = nil } diff --git a/api/helloworld/v1/greeter_grpc.pb.go b/api/helloworld/v1/greeter_grpc.pb.go index 99a9bda0deb103c6c81a8f165ac4698276a21e72..e40b2e7c5c43d9b8a014f0150654dd997638b93c 100644 --- a/api/helloworld/v1/greeter_grpc.pb.go +++ b/api/helloworld/v1/greeter_grpc.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.19.3 -// source: helloworld/v1/greeter.proto +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.19.4 +// source: api/helloworld/v1/greeter.proto package v1 @@ -15,12 +15,18 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Greeter_SayHello_FullMethodName = "/helloworld.v1.Greeter/SayHello" +) // GreeterClient is the client API for Greeter service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// The greeting service definition. type GreeterClient interface { // Sends a greeting SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) @@ -35,8 +41,9 @@ func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { } func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(HelloReply) - err := c.cc.Invoke(ctx, "/helloworld.v1.Greeter/SayHello", in, out, opts...) + err := c.cc.Invoke(ctx, Greeter_SayHello_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -45,21 +52,27 @@ func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ... // GreeterServer is the server API for Greeter service. // All implementations must embed UnimplementedGreeterServer -// for forward compatibility +// for forward compatibility. +// +// The greeting service definition. type GreeterServer interface { // Sends a greeting SayHello(context.Context, *HelloRequest) (*HelloReply, error) mustEmbedUnimplementedGreeterServer() } -// UnimplementedGreeterServer must be embedded to have forward compatible implementations. -type UnimplementedGreeterServer struct { -} +// UnimplementedGreeterServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedGreeterServer struct{} func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") } func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} +func (UnimplementedGreeterServer) testEmbeddedByValue() {} // UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to GreeterServer will @@ -69,6 +82,13 @@ type UnsafeGreeterServer interface { } func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { + // If the following call pancis, it indicates UnimplementedGreeterServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&Greeter_ServiceDesc, srv) } @@ -82,7 +102,7 @@ func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(in } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/helloworld.v1.Greeter/SayHello", + FullMethod: Greeter_SayHello_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) @@ -103,5 +123,5 @@ var Greeter_ServiceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: "helloworld/v1/greeter.proto", + Metadata: "api/helloworld/v1/greeter.proto", } diff --git a/api/helloworld/v1/greeter_http.pb.go b/api/helloworld/v1/greeter_http.pb.go index be38a7ec64a6e8aa71e8160018643b30a5dbb265..781cb5de523aafaadce530f78feb8307bd9804e4 100644 --- a/api/helloworld/v1/greeter_http.pb.go +++ b/api/helloworld/v1/greeter_http.pb.go @@ -1,6 +1,8 @@ // Code generated by protoc-gen-go-http. DO NOT EDIT. // versions: -// protoc-gen-go-http v2.1.3 +// - protoc-gen-go-http v2.9.0 +// - protoc v3.19.4 +// source: api/helloworld/v1/greeter.proto package v1 @@ -17,7 +19,10 @@ var _ = binding.EncodeURL const _ = http.SupportPackageIsVersion1 +const OperationGreeterSayHello = "/helloworld.v1.Greeter/SayHello" + type GreeterHTTPServer interface { + // SayHello Sends a greeting SayHello(context.Context, *HelloRequest) (*HelloReply, error) } @@ -35,7 +40,7 @@ func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Contex if err := ctx.BindVars(&in); err != nil { return err } - http.SetOperation(ctx, "/helloworld.v1.Greeter/SayHello") + http.SetOperation(ctx, OperationGreeterSayHello) h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { return srv.SayHello(ctx, req.(*HelloRequest)) }) @@ -49,6 +54,7 @@ func _Greeter_SayHello0_HTTP_Handler(srv GreeterHTTPServer) func(ctx http.Contex } type GreeterHTTPClient interface { + // SayHello Sends a greeting SayHello(ctx context.Context, req *HelloRequest, opts ...http.CallOption) (rsp *HelloReply, err error) } @@ -60,15 +66,16 @@ func NewGreeterHTTPClient(client *http.Client) GreeterHTTPClient { return &GreeterHTTPClientImpl{client} } +// SayHello Sends a greeting func (c *GreeterHTTPClientImpl) SayHello(ctx context.Context, in *HelloRequest, opts ...http.CallOption) (*HelloReply, error) { var out HelloReply pattern := "/helloworld/{name}" path := binding.EncodeURL(pattern, in, true) - opts = append(opts, http.Operation("/helloworld.v1.Greeter/SayHello")) + opts = append(opts, http.Operation(OperationGreeterSayHello)) opts = append(opts, http.PathTemplate(pattern)) err := c.cc.Invoke(ctx, "GET", path, nil, &out, opts...) if err != nil { return nil, err } - return &out, err + return &out, nil } diff --git a/api/help/v1/help.pb.go b/api/help/v1/help.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..a18f50f5c3b339ca792895dc3877001c14aa5112 --- /dev/null +++ b/api/help/v1/help.pb.go @@ -0,0 +1,752 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc v3.19.4 +// source: api/help/v1/help.proto + +package v1 + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 请求响应消息定义 +type GetCategoriesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCategoriesRequest) Reset() { + *x = GetCategoriesRequest{} + mi := &file_api_help_v1_help_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCategoriesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCategoriesRequest) ProtoMessage() {} + +func (x *GetCategoriesRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[0] + 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 GetCategoriesRequest.ProtoReflect.Descriptor instead. +func (*GetCategoriesRequest) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{0} +} + +type GetCategoriesReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Categories []*HelpCategory `protobuf:"bytes,1,rep,name=categories,proto3" json:"categories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCategoriesReply) Reset() { + *x = GetCategoriesReply{} + mi := &file_api_help_v1_help_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCategoriesReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCategoriesReply) ProtoMessage() {} + +func (x *GetCategoriesReply) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[1] + 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 GetCategoriesReply.ProtoReflect.Descriptor instead. +func (*GetCategoriesReply) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{1} +} + +func (x *GetCategoriesReply) GetCategories() []*HelpCategory { + if x != nil { + return x.Categories + } + return nil +} + +type GetArticlesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Category string `protobuf:"bytes,1,opt,name=category,proto3" json:"category,omitempty"` + SubCategory string `protobuf:"bytes,2,opt,name=sub_category,json=subCategory,proto3" json:"sub_category,omitempty"` + 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"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetArticlesRequest) Reset() { + *x = GetArticlesRequest{} + mi := &file_api_help_v1_help_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetArticlesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetArticlesRequest) ProtoMessage() {} + +func (x *GetArticlesRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[2] + 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 GetArticlesRequest.ProtoReflect.Descriptor instead. +func (*GetArticlesRequest) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{2} +} + +func (x *GetArticlesRequest) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *GetArticlesRequest) GetSubCategory() string { + if x != nil { + return x.SubCategory + } + return "" +} + +func (x *GetArticlesRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *GetArticlesRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type GetArticlesReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Articles []*HelpArticle `protobuf:"bytes,1,rep,name=articles,proto3" json:"articles,omitempty"` + Total int32 `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 *GetArticlesReply) Reset() { + *x = GetArticlesReply{} + mi := &file_api_help_v1_help_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetArticlesReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetArticlesReply) ProtoMessage() {} + +func (x *GetArticlesReply) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[3] + 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 GetArticlesReply.ProtoReflect.Descriptor instead. +func (*GetArticlesReply) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{3} +} + +func (x *GetArticlesReply) GetArticles() []*HelpArticle { + if x != nil { + return x.Articles + } + return nil +} + +func (x *GetArticlesReply) GetTotal() int32 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *GetArticlesReply) GetCurrentPage() int32 { + if x != nil { + return x.CurrentPage + } + return 0 +} + +func (x *GetArticlesReply) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type SearchArticlesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Keyword string `protobuf:"bytes,1,opt,name=keyword,proto3" json:"keyword,omitempty"` + Page int32 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"` + PageSize int32 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SearchArticlesRequest) Reset() { + *x = SearchArticlesRequest{} + mi := &file_api_help_v1_help_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SearchArticlesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchArticlesRequest) ProtoMessage() {} + +func (x *SearchArticlesRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[4] + 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 SearchArticlesRequest.ProtoReflect.Descriptor instead. +func (*SearchArticlesRequest) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{4} +} + +func (x *SearchArticlesRequest) GetKeyword() string { + if x != nil { + return x.Keyword + } + return "" +} + +func (x *SearchArticlesRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *SearchArticlesRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type SearchArticlesReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Articles []*HelpArticle `protobuf:"bytes,1,rep,name=articles,proto3" json:"articles,omitempty"` + Total int32 `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 *SearchArticlesReply) Reset() { + *x = SearchArticlesReply{} + mi := &file_api_help_v1_help_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SearchArticlesReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SearchArticlesReply) ProtoMessage() {} + +func (x *SearchArticlesReply) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[5] + 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 SearchArticlesReply.ProtoReflect.Descriptor instead. +func (*SearchArticlesReply) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{5} +} + +func (x *SearchArticlesReply) GetArticles() []*HelpArticle { + if x != nil { + return x.Articles + } + return nil +} + +func (x *SearchArticlesReply) GetTotal() int32 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *SearchArticlesReply) GetCurrentPage() int32 { + if x != nil { + return x.CurrentPage + } + return 0 +} + +func (x *SearchArticlesReply) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +type GetArticleDetailRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetArticleDetailRequest) Reset() { + *x = GetArticleDetailRequest{} + mi := &file_api_help_v1_help_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetArticleDetailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetArticleDetailRequest) ProtoMessage() {} + +func (x *GetArticleDetailRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[6] + 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 GetArticleDetailRequest.ProtoReflect.Descriptor instead. +func (*GetArticleDetailRequest) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{6} +} + +func (x *GetArticleDetailRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type GetArticleDetailReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Article *HelpArticle `protobuf:"bytes,1,opt,name=article,proto3" json:"article,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetArticleDetailReply) Reset() { + *x = GetArticleDetailReply{} + mi := &file_api_help_v1_help_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetArticleDetailReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetArticleDetailReply) ProtoMessage() {} + +func (x *GetArticleDetailReply) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[7] + 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 GetArticleDetailReply.ProtoReflect.Descriptor instead. +func (*GetArticleDetailReply) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{7} +} + +func (x *GetArticleDetailReply) GetArticle() *HelpArticle { + if x != nil { + return x.Article + } + return nil +} + +// 数据结构定义 +type HelpCategory struct { + state protoimpl.MessageState `protogen:"open.v1"` + Category string `protobuf:"bytes,1,opt,name=category,proto3" json:"category,omitempty"` + SubCategories []string `protobuf:"bytes,2,rep,name=sub_categories,json=subCategories,proto3" json:"sub_categories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HelpCategory) Reset() { + *x = HelpCategory{} + mi := &file_api_help_v1_help_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelpCategory) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelpCategory) ProtoMessage() {} + +func (x *HelpCategory) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[8] + 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 HelpCategory.ProtoReflect.Descriptor instead. +func (*HelpCategory) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{8} +} + +func (x *HelpCategory) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *HelpCategory) GetSubCategories() []string { + if x != nil { + return x.SubCategories + } + return nil +} + +type HelpArticle struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` + Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` + Category string `protobuf:"bytes,4,opt,name=category,proto3" json:"category,omitempty"` + SubCategory string `protobuf:"bytes,5,opt,name=sub_category,json=subCategory,proto3" json:"sub_category,omitempty"` + OrderWeight int32 `protobuf:"varint,6,opt,name=order_weight,json=orderWeight,proto3" json:"order_weight,omitempty"` + ViewCount int32 `protobuf:"varint,7,opt,name=view_count,json=viewCount,proto3" json:"view_count,omitempty"` + IsActive bool `protobuf:"varint,8,opt,name=is_active,json=isActive,proto3" json:"is_active,omitempty"` + CreatedAt string `protobuf:"bytes,9,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt string `protobuf:"bytes,10,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HelpArticle) Reset() { + *x = HelpArticle{} + mi := &file_api_help_v1_help_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HelpArticle) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HelpArticle) ProtoMessage() {} + +func (x *HelpArticle) ProtoReflect() protoreflect.Message { + mi := &file_api_help_v1_help_proto_msgTypes[9] + 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 HelpArticle.ProtoReflect.Descriptor instead. +func (*HelpArticle) Descriptor() ([]byte, []int) { + return file_api_help_v1_help_proto_rawDescGZIP(), []int{9} +} + +func (x *HelpArticle) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *HelpArticle) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *HelpArticle) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *HelpArticle) GetCategory() string { + if x != nil { + return x.Category + } + return "" +} + +func (x *HelpArticle) GetSubCategory() string { + if x != nil { + return x.SubCategory + } + return "" +} + +func (x *HelpArticle) GetOrderWeight() int32 { + if x != nil { + return x.OrderWeight + } + return 0 +} + +func (x *HelpArticle) GetViewCount() int32 { + if x != nil { + return x.ViewCount + } + return 0 +} + +func (x *HelpArticle) GetIsActive() bool { + if x != nil { + return x.IsActive + } + return false +} + +func (x *HelpArticle) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +func (x *HelpArticle) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + +var File_api_help_v1_help_proto protoreflect.FileDescriptor + +const file_api_help_v1_help_proto_rawDesc = "" + + "\n" + + "\x16api/help/v1/help.proto\x12\vapi.help.v1\x1a\x1cgoogle/api/annotations.proto\"\x16\n" + + "\x14GetCategoriesRequest\"O\n" + + "\x12GetCategoriesReply\x129\n" + + "\n" + + "categories\x18\x01 \x03(\v2\x19.api.help.v1.HelpCategoryR\n" + + "categories\"\x84\x01\n" + + "\x12GetArticlesRequest\x12\x1a\n" + + "\bcategory\x18\x01 \x01(\tR\bcategory\x12!\n" + + "\fsub_category\x18\x02 \x01(\tR\vsubCategory\x12\x12\n" + + "\x04page\x18\x03 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\"\x9e\x01\n" + + "\x10GetArticlesReply\x124\n" + + "\barticles\x18\x01 \x03(\v2\x18.api.help.v1.HelpArticleR\barticles\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\x12!\n" + + "\fcurrent_page\x18\x03 \x01(\x05R\vcurrentPage\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\"b\n" + + "\x15SearchArticlesRequest\x12\x18\n" + + "\akeyword\x18\x01 \x01(\tR\akeyword\x12\x12\n" + + "\x04page\x18\x02 \x01(\x05R\x04page\x12\x1b\n" + + "\tpage_size\x18\x03 \x01(\x05R\bpageSize\"\xa1\x01\n" + + "\x13SearchArticlesReply\x124\n" + + "\barticles\x18\x01 \x03(\v2\x18.api.help.v1.HelpArticleR\barticles\x12\x14\n" + + "\x05total\x18\x02 \x01(\x05R\x05total\x12!\n" + + "\fcurrent_page\x18\x03 \x01(\x05R\vcurrentPage\x12\x1b\n" + + "\tpage_size\x18\x04 \x01(\x05R\bpageSize\")\n" + + "\x17GetArticleDetailRequest\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"K\n" + + "\x15GetArticleDetailReply\x122\n" + + "\aarticle\x18\x01 \x01(\v2\x18.api.help.v1.HelpArticleR\aarticle\"Q\n" + + "\fHelpCategory\x12\x1a\n" + + "\bcategory\x18\x01 \x01(\tR\bcategory\x12%\n" + + "\x0esub_categories\x18\x02 \x03(\tR\rsubCategories\"\xa9\x02\n" + + "\vHelpArticle\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x14\n" + + "\x05title\x18\x02 \x01(\tR\x05title\x12\x18\n" + + "\acontent\x18\x03 \x01(\tR\acontent\x12\x1a\n" + + "\bcategory\x18\x04 \x01(\tR\bcategory\x12!\n" + + "\fsub_category\x18\x05 \x01(\tR\vsubCategory\x12!\n" + + "\forder_weight\x18\x06 \x01(\x05R\vorderWeight\x12\x1d\n" + + "\n" + + "view_count\x18\a \x01(\x05R\tviewCount\x12\x1b\n" + + "\tis_active\x18\b \x01(\bR\bisActive\x12\x1d\n" + + "\n" + + "created_at\x18\t \x01(\tR\tcreatedAt\x12\x1d\n" + + "\n" + + "updated_at\x18\n" + + " \x01(\tR\tupdatedAt2\xed\x03\n" + + "\vHelpService\x12s\n" + + "\rGetCategories\x12!.api.help.v1.GetCategoriesRequest\x1a\x1f.api.help.v1.GetCategoriesReply\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/v1/help/categories\x12k\n" + + "\vGetArticles\x12\x1f.api.help.v1.GetArticlesRequest\x1a\x1d.api.help.v1.GetArticlesReply\"\x1c\x82\xd3\xe4\x93\x02\x16:\x01*\"\x11/v1/help/articles\x12{\n" + + "\x0eSearchArticles\x12\".api.help.v1.SearchArticlesRequest\x1a .api.help.v1.SearchArticlesReply\"#\x82\xd3\xe4\x93\x02\x1d:\x01*\"\x18/v1/help/articles/search\x12\x7f\n" + + "\x10GetArticleDetail\x12$.api.help.v1.GetArticleDetailRequest\x1a\".api.help.v1.GetArticleDetailReply\"!\x82\xd3\xe4\x93\x02\x1b:\x01*\"\x16/v1/help/articleDetailB'\n" + + "\vapi.help.v1P\x01Z\x16Cabinet/api/help/v1;v1b\x06proto3" + +var ( + file_api_help_v1_help_proto_rawDescOnce sync.Once + file_api_help_v1_help_proto_rawDescData []byte +) + +func file_api_help_v1_help_proto_rawDescGZIP() []byte { + file_api_help_v1_help_proto_rawDescOnce.Do(func() { + file_api_help_v1_help_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_help_v1_help_proto_rawDesc), len(file_api_help_v1_help_proto_rawDesc))) + }) + return file_api_help_v1_help_proto_rawDescData +} + +var file_api_help_v1_help_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_api_help_v1_help_proto_goTypes = []any{ + (*GetCategoriesRequest)(nil), // 0: api.help.v1.GetCategoriesRequest + (*GetCategoriesReply)(nil), // 1: api.help.v1.GetCategoriesReply + (*GetArticlesRequest)(nil), // 2: api.help.v1.GetArticlesRequest + (*GetArticlesReply)(nil), // 3: api.help.v1.GetArticlesReply + (*SearchArticlesRequest)(nil), // 4: api.help.v1.SearchArticlesRequest + (*SearchArticlesReply)(nil), // 5: api.help.v1.SearchArticlesReply + (*GetArticleDetailRequest)(nil), // 6: api.help.v1.GetArticleDetailRequest + (*GetArticleDetailReply)(nil), // 7: api.help.v1.GetArticleDetailReply + (*HelpCategory)(nil), // 8: api.help.v1.HelpCategory + (*HelpArticle)(nil), // 9: api.help.v1.HelpArticle +} +var file_api_help_v1_help_proto_depIdxs = []int32{ + 8, // 0: api.help.v1.GetCategoriesReply.categories:type_name -> api.help.v1.HelpCategory + 9, // 1: api.help.v1.GetArticlesReply.articles:type_name -> api.help.v1.HelpArticle + 9, // 2: api.help.v1.SearchArticlesReply.articles:type_name -> api.help.v1.HelpArticle + 9, // 3: api.help.v1.GetArticleDetailReply.article:type_name -> api.help.v1.HelpArticle + 0, // 4: api.help.v1.HelpService.GetCategories:input_type -> api.help.v1.GetCategoriesRequest + 2, // 5: api.help.v1.HelpService.GetArticles:input_type -> api.help.v1.GetArticlesRequest + 4, // 6: api.help.v1.HelpService.SearchArticles:input_type -> api.help.v1.SearchArticlesRequest + 6, // 7: api.help.v1.HelpService.GetArticleDetail:input_type -> api.help.v1.GetArticleDetailRequest + 1, // 8: api.help.v1.HelpService.GetCategories:output_type -> api.help.v1.GetCategoriesReply + 3, // 9: api.help.v1.HelpService.GetArticles:output_type -> api.help.v1.GetArticlesReply + 5, // 10: api.help.v1.HelpService.SearchArticles:output_type -> api.help.v1.SearchArticlesReply + 7, // 11: api.help.v1.HelpService.GetArticleDetail:output_type -> api.help.v1.GetArticleDetailReply + 8, // [8:12] is the sub-list for method output_type + 4, // [4:8] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_api_help_v1_help_proto_init() } +func file_api_help_v1_help_proto_init() { + if File_api_help_v1_help_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_help_v1_help_proto_rawDesc), len(file_api_help_v1_help_proto_rawDesc)), + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_help_v1_help_proto_goTypes, + DependencyIndexes: file_api_help_v1_help_proto_depIdxs, + MessageInfos: file_api_help_v1_help_proto_msgTypes, + }.Build() + File_api_help_v1_help_proto = out.File + file_api_help_v1_help_proto_goTypes = nil + file_api_help_v1_help_proto_depIdxs = nil +} diff --git a/api/help/v1/help.proto b/api/help/v1/help.proto new file mode 100644 index 0000000000000000000000000000000000000000..9f74001875dfaa0d3249762a179ec7b0ae105374 --- /dev/null +++ b/api/help/v1/help.proto @@ -0,0 +1,102 @@ +syntax = "proto3"; + +package api.help.v1; +import "google/api/annotations.proto"; +option go_package = "Cabinet/api/help/v1;v1"; +option java_multiple_files = true; +option java_package = "api.help.v1"; + +service HelpService { + // 获取帮助中心分类结构 + rpc GetCategories (GetCategoriesRequest) returns (GetCategoriesReply) { + option (google.api.http) = { + post: "/v1/help/categories" + body: "*" // 添加这一行,表示使用整个请求消息作为body + }; + } + + // 根据分类获取文章列表 + rpc GetArticles (GetArticlesRequest) returns (GetArticlesReply) { + option (google.api.http) = { + post: "/v1/help/articles" + body: "*" // 添加这一行,表示使用整个请求消息作为body + }; + } + + // 搜索帮助文章 + rpc SearchArticles (SearchArticlesRequest) returns (SearchArticlesReply) { + option (google.api.http) = { + post: "/v1/help/articles/search" + body: "*" // 添加这一行,表示使用整个请求消息作为body + }; + } + + // 获取文章详情 + rpc GetArticleDetail (GetArticleDetailRequest) returns (GetArticleDetailReply) { + option (google.api.http) = { + post: "/v1/help/articleDetail" + body: "*" // 添加这一行,表示使用整个请求消息作为body + }; + } +} + +// 请求响应消息定义 +message GetCategoriesRequest {} + +message GetCategoriesReply { + repeated HelpCategory categories = 1; +} + +message GetArticlesRequest { + string category = 1; + string sub_category = 2; + int32 page = 3; + int32 page_size = 4; +} + +message GetArticlesReply { + repeated HelpArticle articles = 1; + int32 total = 2; + int32 current_page = 3; + int32 page_size = 4; +} + +message SearchArticlesRequest { + string keyword = 1; + int32 page = 2; + int32 page_size = 3; +} + +message SearchArticlesReply { + repeated HelpArticle articles = 1; + int32 total = 2; + int32 current_page = 3; + int32 page_size = 4; +} + +message GetArticleDetailRequest { + string id = 1; +} + +message GetArticleDetailReply { + HelpArticle article = 1; +} + +// 数据结构定义 +message HelpCategory { + string category = 1; + repeated string sub_categories = 2; +} + +message HelpArticle { + string id = 1; + string title = 2; + string content = 3; + string category = 4; + string sub_category = 5; + int32 order_weight = 6; + int32 view_count = 7; + bool is_active = 8; + string created_at = 9; + string updated_at = 10; +} \ No newline at end of file diff --git a/api/help/v1/help_grpc.pb.go b/api/help/v1/help_grpc.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..2e79616381ee3a10f2d117ed5c21dca9746809ee --- /dev/null +++ b/api/help/v1/help_grpc.pb.go @@ -0,0 +1,243 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.19.4 +// source: api/help/v1/help.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + HelpService_GetCategories_FullMethodName = "/api.help.v1.HelpService/GetCategories" + HelpService_GetArticles_FullMethodName = "/api.help.v1.HelpService/GetArticles" + HelpService_SearchArticles_FullMethodName = "/api.help.v1.HelpService/SearchArticles" + HelpService_GetArticleDetail_FullMethodName = "/api.help.v1.HelpService/GetArticleDetail" +) + +// HelpServiceClient is the client API for HelpService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type HelpServiceClient interface { + // 获取帮助中心分类结构 + GetCategories(ctx context.Context, in *GetCategoriesRequest, opts ...grpc.CallOption) (*GetCategoriesReply, error) + // 根据分类获取文章列表 + GetArticles(ctx context.Context, in *GetArticlesRequest, opts ...grpc.CallOption) (*GetArticlesReply, error) + // 搜索帮助文章 + SearchArticles(ctx context.Context, in *SearchArticlesRequest, opts ...grpc.CallOption) (*SearchArticlesReply, error) + // 获取文章详情 + GetArticleDetail(ctx context.Context, in *GetArticleDetailRequest, opts ...grpc.CallOption) (*GetArticleDetailReply, error) +} + +type helpServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewHelpServiceClient(cc grpc.ClientConnInterface) HelpServiceClient { + return &helpServiceClient{cc} +} + +func (c *helpServiceClient) GetCategories(ctx context.Context, in *GetCategoriesRequest, opts ...grpc.CallOption) (*GetCategoriesReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetCategoriesReply) + err := c.cc.Invoke(ctx, HelpService_GetCategories_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *helpServiceClient) GetArticles(ctx context.Context, in *GetArticlesRequest, opts ...grpc.CallOption) (*GetArticlesReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetArticlesReply) + err := c.cc.Invoke(ctx, HelpService_GetArticles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *helpServiceClient) SearchArticles(ctx context.Context, in *SearchArticlesRequest, opts ...grpc.CallOption) (*SearchArticlesReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SearchArticlesReply) + err := c.cc.Invoke(ctx, HelpService_SearchArticles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *helpServiceClient) GetArticleDetail(ctx context.Context, in *GetArticleDetailRequest, opts ...grpc.CallOption) (*GetArticleDetailReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetArticleDetailReply) + err := c.cc.Invoke(ctx, HelpService_GetArticleDetail_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// HelpServiceServer is the server API for HelpService service. +// All implementations must embed UnimplementedHelpServiceServer +// for forward compatibility. +type HelpServiceServer interface { + // 获取帮助中心分类结构 + GetCategories(context.Context, *GetCategoriesRequest) (*GetCategoriesReply, error) + // 根据分类获取文章列表 + GetArticles(context.Context, *GetArticlesRequest) (*GetArticlesReply, error) + // 搜索帮助文章 + SearchArticles(context.Context, *SearchArticlesRequest) (*SearchArticlesReply, error) + // 获取文章详情 + GetArticleDetail(context.Context, *GetArticleDetailRequest) (*GetArticleDetailReply, error) + mustEmbedUnimplementedHelpServiceServer() +} + +// UnimplementedHelpServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedHelpServiceServer struct{} + +func (UnimplementedHelpServiceServer) GetCategories(context.Context, *GetCategoriesRequest) (*GetCategoriesReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCategories not implemented") +} +func (UnimplementedHelpServiceServer) GetArticles(context.Context, *GetArticlesRequest) (*GetArticlesReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetArticles not implemented") +} +func (UnimplementedHelpServiceServer) SearchArticles(context.Context, *SearchArticlesRequest) (*SearchArticlesReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SearchArticles not implemented") +} +func (UnimplementedHelpServiceServer) GetArticleDetail(context.Context, *GetArticleDetailRequest) (*GetArticleDetailReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetArticleDetail not implemented") +} +func (UnimplementedHelpServiceServer) mustEmbedUnimplementedHelpServiceServer() {} +func (UnimplementedHelpServiceServer) testEmbeddedByValue() {} + +// UnsafeHelpServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to HelpServiceServer will +// result in compilation errors. +type UnsafeHelpServiceServer interface { + mustEmbedUnimplementedHelpServiceServer() +} + +func RegisterHelpServiceServer(s grpc.ServiceRegistrar, srv HelpServiceServer) { + // If the following call pancis, it indicates UnimplementedHelpServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&HelpService_ServiceDesc, srv) +} + +func _HelpService_GetCategories_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCategoriesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HelpServiceServer).GetCategories(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HelpService_GetCategories_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HelpServiceServer).GetCategories(ctx, req.(*GetCategoriesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HelpService_GetArticles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetArticlesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HelpServiceServer).GetArticles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HelpService_GetArticles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HelpServiceServer).GetArticles(ctx, req.(*GetArticlesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HelpService_SearchArticles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SearchArticlesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HelpServiceServer).SearchArticles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HelpService_SearchArticles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HelpServiceServer).SearchArticles(ctx, req.(*SearchArticlesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _HelpService_GetArticleDetail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetArticleDetailRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HelpServiceServer).GetArticleDetail(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: HelpService_GetArticleDetail_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HelpServiceServer).GetArticleDetail(ctx, req.(*GetArticleDetailRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// HelpService_ServiceDesc is the grpc.ServiceDesc for HelpService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var HelpService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "api.help.v1.HelpService", + HandlerType: (*HelpServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetCategories", + Handler: _HelpService_GetCategories_Handler, + }, + { + MethodName: "GetArticles", + Handler: _HelpService_GetArticles_Handler, + }, + { + MethodName: "SearchArticles", + Handler: _HelpService_SearchArticles_Handler, + }, + { + MethodName: "GetArticleDetail", + Handler: _HelpService_GetArticleDetail_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/help/v1/help.proto", +} diff --git a/api/help/v1/help_http.pb.go b/api/help/v1/help_http.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..3f482d60fa180c23a72950d7746357f49a6c9ee3 --- /dev/null +++ b/api/help/v1/help_http.pb.go @@ -0,0 +1,207 @@ +// Code generated by protoc-gen-go-http. DO NOT EDIT. +// versions: +// - protoc-gen-go-http v2.9.0 +// - protoc v3.19.4 +// source: api/help/v1/help.proto + +package v1 + +import ( + context "context" + http "github.com/go-kratos/kratos/v2/transport/http" + binding "github.com/go-kratos/kratos/v2/transport/http/binding" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the kratos package it is being compiled against. +var _ = new(context.Context) +var _ = binding.EncodeURL + +const _ = http.SupportPackageIsVersion1 + +const OperationHelpServiceGetArticleDetail = "/api.help.v1.HelpService/GetArticleDetail" +const OperationHelpServiceGetArticles = "/api.help.v1.HelpService/GetArticles" +const OperationHelpServiceGetCategories = "/api.help.v1.HelpService/GetCategories" +const OperationHelpServiceSearchArticles = "/api.help.v1.HelpService/SearchArticles" + +type HelpServiceHTTPServer interface { + // GetArticleDetail 获取文章详情 + GetArticleDetail(context.Context, *GetArticleDetailRequest) (*GetArticleDetailReply, error) + // GetArticles 根据分类获取文章列表 + GetArticles(context.Context, *GetArticlesRequest) (*GetArticlesReply, error) + // GetCategories 获取帮助中心分类结构 + GetCategories(context.Context, *GetCategoriesRequest) (*GetCategoriesReply, error) + // SearchArticles 搜索帮助文章 + SearchArticles(context.Context, *SearchArticlesRequest) (*SearchArticlesReply, error) +} + +func RegisterHelpServiceHTTPServer(s *http.Server, srv HelpServiceHTTPServer) { + r := s.Route("/") + r.POST("/v1/help/categories", _HelpService_GetCategories0_HTTP_Handler(srv)) + r.POST("/v1/help/articles", _HelpService_GetArticles0_HTTP_Handler(srv)) + r.POST("/v1/help/articles/search", _HelpService_SearchArticles0_HTTP_Handler(srv)) + r.POST("/v1/help/articleDetail", _HelpService_GetArticleDetail0_HTTP_Handler(srv)) +} + +func _HelpService_GetCategories0_HTTP_Handler(srv HelpServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in GetCategoriesRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationHelpServiceGetCategories) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.GetCategories(ctx, req.(*GetCategoriesRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*GetCategoriesReply) + return ctx.Result(200, reply) + } +} + +func _HelpService_GetArticles0_HTTP_Handler(srv HelpServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in GetArticlesRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationHelpServiceGetArticles) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.GetArticles(ctx, req.(*GetArticlesRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*GetArticlesReply) + return ctx.Result(200, reply) + } +} + +func _HelpService_SearchArticles0_HTTP_Handler(srv HelpServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in SearchArticlesRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationHelpServiceSearchArticles) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.SearchArticles(ctx, req.(*SearchArticlesRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*SearchArticlesReply) + return ctx.Result(200, reply) + } +} + +func _HelpService_GetArticleDetail0_HTTP_Handler(srv HelpServiceHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in GetArticleDetailRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationHelpServiceGetArticleDetail) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.GetArticleDetail(ctx, req.(*GetArticleDetailRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*GetArticleDetailReply) + return ctx.Result(200, reply) + } +} + +type HelpServiceHTTPClient interface { + // GetArticleDetail 获取文章详情 + GetArticleDetail(ctx context.Context, req *GetArticleDetailRequest, opts ...http.CallOption) (rsp *GetArticleDetailReply, err error) + // GetArticles 根据分类获取文章列表 + GetArticles(ctx context.Context, req *GetArticlesRequest, opts ...http.CallOption) (rsp *GetArticlesReply, err error) + // GetCategories 获取帮助中心分类结构 + GetCategories(ctx context.Context, req *GetCategoriesRequest, opts ...http.CallOption) (rsp *GetCategoriesReply, err error) + // SearchArticles 搜索帮助文章 + SearchArticles(ctx context.Context, req *SearchArticlesRequest, opts ...http.CallOption) (rsp *SearchArticlesReply, err error) +} + +type HelpServiceHTTPClientImpl struct { + cc *http.Client +} + +func NewHelpServiceHTTPClient(client *http.Client) HelpServiceHTTPClient { + return &HelpServiceHTTPClientImpl{client} +} + +// GetArticleDetail 获取文章详情 +func (c *HelpServiceHTTPClientImpl) GetArticleDetail(ctx context.Context, in *GetArticleDetailRequest, opts ...http.CallOption) (*GetArticleDetailReply, error) { + var out GetArticleDetailReply + pattern := "/v1/help/articleDetail" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationHelpServiceGetArticleDetail)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// GetArticles 根据分类获取文章列表 +func (c *HelpServiceHTTPClientImpl) GetArticles(ctx context.Context, in *GetArticlesRequest, opts ...http.CallOption) (*GetArticlesReply, error) { + var out GetArticlesReply + pattern := "/v1/help/articles" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationHelpServiceGetArticles)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// GetCategories 获取帮助中心分类结构 +func (c *HelpServiceHTTPClientImpl) GetCategories(ctx context.Context, in *GetCategoriesRequest, opts ...http.CallOption) (*GetCategoriesReply, error) { + var out GetCategoriesReply + pattern := "/v1/help/categories" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationHelpServiceGetCategories)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// SearchArticles 搜索帮助文章 +func (c *HelpServiceHTTPClientImpl) SearchArticles(ctx context.Context, in *SearchArticlesRequest, opts ...http.CallOption) (*SearchArticlesReply, error) { + var out SearchArticlesReply + pattern := "/v1/help/articles/search" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationHelpServiceSearchArticles)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/api/order/v1/order.pb.go b/api/order/v1/order.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..39a05fba57f2080f4d09f0d2d9eb587dead564a1 --- /dev/null +++ b/api/order/v1/order.pb.go @@ -0,0 +1,675 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc v3.19.4 +// source: api/order/v1/order.proto + +package v1 + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// 创建充值订单 +type CreateOrderRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` //用户Id + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` //充值金额 + Currency string `protobuf:"bytes,3,opt,name=currency,proto3" json:"currency,omitempty"` //币种 cny-人民币, //php 菲律宾币 + PayMethod string `protobuf:"bytes,4,opt,name=pay_method,json=payMethod,proto3" json:"pay_method,omitempty"` //支付方式 支付宝 微信 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateOrderRequest) Reset() { + *x = CreateOrderRequest{} + mi := &file_api_order_v1_order_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateOrderRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateOrderRequest) ProtoMessage() {} + +func (x *CreateOrderRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[0] + 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 CreateOrderRequest.ProtoReflect.Descriptor instead. +func (*CreateOrderRequest) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{0} +} + +func (x *CreateOrderRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *CreateOrderRequest) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *CreateOrderRequest) GetCurrency() string { + if x != nil { + return x.Currency + } + return "" +} + +func (x *CreateOrderRequest) GetPayMethod() string { + if x != nil { + return x.PayMethod + } + return "" +} + +type CreateOrderReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + OrderNo string `protobuf:"bytes,1,opt,name=Order_no,json=OrderNo,proto3" json:"Order_no,omitempty"` //订单号 + PayUrl string `protobuf:"bytes,2,opt,name=pay_url,json=payUrl,proto3" json:"pay_url,omitempty"` //支付跳转url 用户点击跳转支付页面 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateOrderReply) Reset() { + *x = CreateOrderReply{} + mi := &file_api_order_v1_order_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateOrderReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateOrderReply) ProtoMessage() {} + +func (x *CreateOrderReply) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[1] + 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 CreateOrderReply.ProtoReflect.Descriptor instead. +func (*CreateOrderReply) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateOrderReply) GetOrderNo() string { + if x != nil { + return x.OrderNo + } + return "" +} + +func (x *CreateOrderReply) GetPayUrl() string { + if x != nil { + return x.PayUrl + } + return "" +} + +// 获取充值记录列表 +type ListRecordsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` //用户Id + Page int64 `protobuf:"varint,2,opt,name=page,proto3" json:"page,omitempty"` //页码 + PageSize int64 `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` //每页数量 + Status int64 `protobuf:"varint,4,opt,name=status,proto3" json:"status,omitempty"` //状态筛选 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRecordsRequest) Reset() { + *x = ListRecordsRequest{} + mi := &file_api_order_v1_order_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRecordsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecordsRequest) ProtoMessage() {} + +func (x *ListRecordsRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[2] + 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 ListRecordsRequest.ProtoReflect.Descriptor instead. +func (*ListRecordsRequest) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{2} +} + +func (x *ListRecordsRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *ListRecordsRequest) GetPage() int64 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListRecordsRequest) GetPageSize() int64 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListRecordsRequest) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +type ListRecords struct { + state protoimpl.MessageState `protogen:"open.v1"` + OrderNo string `protobuf:"bytes,1,opt,name=order_no,json=orderNo,proto3" json:"order_no,omitempty"` //订单号 + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` //金额 + Currency string `protobuf:"bytes,3,opt,name=currency,proto3" json:"currency,omitempty"` //币种 + PayMethod string `protobuf:"bytes,4,opt,name=pay_method,json=payMethod,proto3" json:"pay_method,omitempty"` //支付方式 + Status int64 `protobuf:"varint,5,opt,name=status,proto3" json:"status,omitempty"` //状态文本 + CreatedAt string `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` //创建时间 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRecords) Reset() { + *x = ListRecords{} + mi := &file_api_order_v1_order_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRecords) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecords) ProtoMessage() {} + +func (x *ListRecords) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[3] + 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 ListRecords.ProtoReflect.Descriptor instead. +func (*ListRecords) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{3} +} + +func (x *ListRecords) GetOrderNo() string { + if x != nil { + return x.OrderNo + } + return "" +} + +func (x *ListRecords) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *ListRecords) GetCurrency() string { + if x != nil { + return x.Currency + } + return "" +} + +func (x *ListRecords) GetPayMethod() string { + if x != nil { + return x.PayMethod + } + return "" +} + +func (x *ListRecords) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *ListRecords) GetCreatedAt() string { + if x != nil { + return x.CreatedAt + } + return "" +} + +type ListRecordsReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + List []*ListRecords `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` // + Total int64 `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"` //总记录数 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRecordsReply) Reset() { + *x = ListRecordsReply{} + mi := &file_api_order_v1_order_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRecordsReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRecordsReply) ProtoMessage() {} + +func (x *ListRecordsReply) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[4] + 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 ListRecordsReply.ProtoReflect.Descriptor instead. +func (*ListRecordsReply) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{4} +} + +func (x *ListRecordsReply) GetList() []*ListRecords { + if x != nil { + return x.List + } + return nil +} + +func (x *ListRecordsReply) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +// 获取充值详情 +type GetDetailRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OrderNo string `protobuf:"bytes,1,opt,name=order_no,json=orderNo,proto3" json:"order_no,omitempty"` //订单号 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDetailRequest) Reset() { + *x = GetDetailRequest{} + mi := &file_api_order_v1_order_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDetailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDetailRequest) ProtoMessage() {} + +func (x *GetDetailRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[5] + 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 GetDetailRequest.ProtoReflect.Descriptor instead. +func (*GetDetailRequest) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{5} +} + +func (x *GetDetailRequest) GetOrderNo() string { + if x != nil { + return x.OrderNo + } + return "" +} + +type GetDetailReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + OrderNo string `protobuf:"bytes,1,opt,name=order_no,json=orderNo,proto3" json:"order_no,omitempty"` //订单号 + Amount int64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` //金额 + Currency string `protobuf:"bytes,3,opt,name=currency,proto3" json:"currency,omitempty"` //币种 + PayMethod string `protobuf:"bytes,4,opt,name=pay_method,json=payMethod,proto3" json:"pay_method,omitempty"` //支付方式 + Status int64 `protobuf:"varint,5,opt,name=status,proto3" json:"status,omitempty"` //创建时间 + TradeNo string `protobuf:"bytes,8,opt,name=trade_no,json=tradeNo,proto3" json:"trade_no,omitempty"` //交易单号 //第三方支付平台返回 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetDetailReply) Reset() { + *x = GetDetailReply{} + mi := &file_api_order_v1_order_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetDetailReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetDetailReply) ProtoMessage() {} + +func (x *GetDetailReply) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[6] + 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 GetDetailReply.ProtoReflect.Descriptor instead. +func (*GetDetailReply) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{6} +} + +func (x *GetDetailReply) GetOrderNo() string { + if x != nil { + return x.OrderNo + } + return "" +} + +func (x *GetDetailReply) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *GetDetailReply) GetCurrency() string { + if x != nil { + return x.Currency + } + return "" +} + +func (x *GetDetailReply) GetPayMethod() string { + if x != nil { + return x.PayMethod + } + return "" +} + +func (x *GetDetailReply) GetStatus() int64 { + if x != nil { + return x.Status + } + return 0 +} + +func (x *GetDetailReply) GetTradeNo() string { + if x != nil { + return x.TradeNo + } + return "" +} + +// 支付宝回调 +type NotifyRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NotifyRequest) Reset() { + *x = NotifyRequest{} + mi := &file_api_order_v1_order_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NotifyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotifyRequest) ProtoMessage() {} + +func (x *NotifyRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[7] + 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 NotifyRequest.ProtoReflect.Descriptor instead. +func (*NotifyRequest) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{7} +} + +type NotifyReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` //返回支付平台结果 成功或者失败 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NotifyReply) Reset() { + *x = NotifyReply{} + mi := &file_api_order_v1_order_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NotifyReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotifyReply) ProtoMessage() {} + +func (x *NotifyReply) ProtoReflect() protoreflect.Message { + mi := &file_api_order_v1_order_proto_msgTypes[8] + 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 NotifyReply.ProtoReflect.Descriptor instead. +func (*NotifyReply) Descriptor() ([]byte, []int) { + return file_api_order_v1_order_proto_rawDescGZIP(), []int{8} +} + +func (x *NotifyReply) GetResult() string { + if x != nil { + return x.Result + } + return "" +} + +var File_api_order_v1_order_proto protoreflect.FileDescriptor + +const file_api_order_v1_order_proto_rawDesc = "" + + "\n" + + "\x18api/order/v1/order.proto\x12\fapi.order.v1\x1a\x1cgoogle/api/annotations.proto\"\x80\x01\n" + + "\x12CreateOrderRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x16\n" + + "\x06amount\x18\x02 \x01(\x03R\x06amount\x12\x1a\n" + + "\bcurrency\x18\x03 \x01(\tR\bcurrency\x12\x1d\n" + + "\n" + + "pay_method\x18\x04 \x01(\tR\tpayMethod\"F\n" + + "\x10CreateOrderReply\x12\x19\n" + + "\bOrder_no\x18\x01 \x01(\tR\aOrderNo\x12\x17\n" + + "\apay_url\x18\x02 \x01(\tR\x06payUrl\"v\n" + + "\x12ListRecordsRequest\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" + + "\x04page\x18\x02 \x01(\x03R\x04page\x12\x1b\n" + + "\tpage_size\x18\x03 \x01(\x03R\bpageSize\x12\x16\n" + + "\x06status\x18\x04 \x01(\x03R\x06status\"\xb2\x01\n" + + "\vListRecords\x12\x19\n" + + "\border_no\x18\x01 \x01(\tR\aorderNo\x12\x16\n" + + "\x06amount\x18\x02 \x01(\x03R\x06amount\x12\x1a\n" + + "\bcurrency\x18\x03 \x01(\tR\bcurrency\x12\x1d\n" + + "\n" + + "pay_method\x18\x04 \x01(\tR\tpayMethod\x12\x16\n" + + "\x06status\x18\x05 \x01(\x03R\x06status\x12\x1d\n" + + "\n" + + "created_at\x18\x06 \x01(\tR\tcreatedAt\"W\n" + + "\x10ListRecordsReply\x12-\n" + + "\x04list\x18\x01 \x03(\v2\x19.api.order.v1.ListRecordsR\x04list\x12\x14\n" + + "\x05total\x18\x02 \x01(\x03R\x05total\"-\n" + + "\x10GetDetailRequest\x12\x19\n" + + "\border_no\x18\x01 \x01(\tR\aorderNo\"\xb1\x01\n" + + "\x0eGetDetailReply\x12\x19\n" + + "\border_no\x18\x01 \x01(\tR\aorderNo\x12\x16\n" + + "\x06amount\x18\x02 \x01(\x03R\x06amount\x12\x1a\n" + + "\bcurrency\x18\x03 \x01(\tR\bcurrency\x12\x1d\n" + + "\n" + + "pay_method\x18\x04 \x01(\tR\tpayMethod\x12\x16\n" + + "\x06status\x18\x05 \x01(\x03R\x06status\x12\x19\n" + + "\btrade_no\x18\b \x01(\tR\atradeNo\"\x0f\n" + + "\rNotifyRequest\"%\n" + + "\vNotifyReply\x12\x16\n" + + "\x06result\x18\x01 \x01(\tR\x06result2\xb6\x03\n" + + "\x05Order\x12n\n" + + "\vCreateOrder\x12 .api.order.v1.CreateOrderRequest\x1a\x1e.api.order.v1.CreateOrderReply\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/v1/recharge/order\x12p\n" + + "\vListRecords\x12 .api.order.v1.ListRecordsRequest\x1a\x1e.api.order.v1.ListRecordsReply\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/v1/recharge/records\x12i\n" + + "\tGetDetail\x12\x1e.api.order.v1.GetDetailRequest\x1a\x1c.api.order.v1.GetDetailReply\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/v1/recharge/detail\x12`\n" + + "\x06Notify\x12\x1b.api.order.v1.NotifyRequest\x1a\x19.api.order.v1.NotifyReply\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/v1/recharge/notifyB)\n" + + "\fapi.order.v1P\x01Z\x17Cabinet/api/order/v1;v1b\x06proto3" + +var ( + file_api_order_v1_order_proto_rawDescOnce sync.Once + file_api_order_v1_order_proto_rawDescData []byte +) + +func file_api_order_v1_order_proto_rawDescGZIP() []byte { + file_api_order_v1_order_proto_rawDescOnce.Do(func() { + file_api_order_v1_order_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_order_v1_order_proto_rawDesc), len(file_api_order_v1_order_proto_rawDesc))) + }) + return file_api_order_v1_order_proto_rawDescData +} + +var file_api_order_v1_order_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_api_order_v1_order_proto_goTypes = []any{ + (*CreateOrderRequest)(nil), // 0: api.order.v1.CreateOrderRequest + (*CreateOrderReply)(nil), // 1: api.order.v1.CreateOrderReply + (*ListRecordsRequest)(nil), // 2: api.order.v1.ListRecordsRequest + (*ListRecords)(nil), // 3: api.order.v1.ListRecords + (*ListRecordsReply)(nil), // 4: api.order.v1.ListRecordsReply + (*GetDetailRequest)(nil), // 5: api.order.v1.GetDetailRequest + (*GetDetailReply)(nil), // 6: api.order.v1.GetDetailReply + (*NotifyRequest)(nil), // 7: api.order.v1.NotifyRequest + (*NotifyReply)(nil), // 8: api.order.v1.NotifyReply +} +var file_api_order_v1_order_proto_depIdxs = []int32{ + 3, // 0: api.order.v1.ListRecordsReply.list:type_name -> api.order.v1.ListRecords + 0, // 1: api.order.v1.Order.CreateOrder:input_type -> api.order.v1.CreateOrderRequest + 2, // 2: api.order.v1.Order.ListRecords:input_type -> api.order.v1.ListRecordsRequest + 5, // 3: api.order.v1.Order.GetDetail:input_type -> api.order.v1.GetDetailRequest + 7, // 4: api.order.v1.Order.Notify:input_type -> api.order.v1.NotifyRequest + 1, // 5: api.order.v1.Order.CreateOrder:output_type -> api.order.v1.CreateOrderReply + 4, // 6: api.order.v1.Order.ListRecords:output_type -> api.order.v1.ListRecordsReply + 6, // 7: api.order.v1.Order.GetDetail:output_type -> api.order.v1.GetDetailReply + 8, // 8: api.order.v1.Order.Notify:output_type -> api.order.v1.NotifyReply + 5, // [5:9] is the sub-list for method output_type + 1, // [1:5] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_api_order_v1_order_proto_init() } +func file_api_order_v1_order_proto_init() { + if File_api_order_v1_order_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_order_v1_order_proto_rawDesc), len(file_api_order_v1_order_proto_rawDesc)), + NumEnums: 0, + NumMessages: 9, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_order_v1_order_proto_goTypes, + DependencyIndexes: file_api_order_v1_order_proto_depIdxs, + MessageInfos: file_api_order_v1_order_proto_msgTypes, + }.Build() + File_api_order_v1_order_proto = out.File + file_api_order_v1_order_proto_goTypes = nil + file_api_order_v1_order_proto_depIdxs = nil +} diff --git a/api/order/v1/order.proto b/api/order/v1/order.proto new file mode 100644 index 0000000000000000000000000000000000000000..943b1397340ca8fa9efd4068239164f200349f40 --- /dev/null +++ b/api/order/v1/order.proto @@ -0,0 +1,90 @@ +syntax = "proto3"; + +package api.order.v1; + +import "google/api/annotations.proto"; + +option go_package = "Cabinet/api/order/v1;v1"; +option java_multiple_files = true; +option java_package = "api.order.v1"; + +service Order { + // Sends a greeting + //创建充值订单 + rpc CreateOrder (CreateOrderRequest) returns (CreateOrderReply) { + option (google.api.http) = { + post: "/v1/recharge/order" + body:"*" + }; + } + //获取充值记录列表 + rpc ListRecords (ListRecordsRequest) returns (ListRecordsReply) { + option (google.api.http) = { + post: "/v1/recharge/records" + body:"*" + }; + } + //获取充值详情 + rpc GetDetail (GetDetailRequest) returns (GetDetailReply) { + option (google.api.http) = { + post: "/v1/recharge/detail" + body:"*" + }; + } + //支付宝回调 + rpc Notify (NotifyRequest) returns (NotifyReply) { + option (google.api.http) = { + post: "/v1/recharge/notify" + body:"*" + }; + } +} +//创建充值订单 +message CreateOrderRequest { + int64 user_id=1; //用户Id + int64 amount=2; //充值金额 + string currency=3; //币种 cny-人民币, //php 菲律宾币 + string pay_method=4; //支付方式 支付宝 微信 +} +message CreateOrderReply { + string Order_no=1; //订单号 + string pay_url=2; //支付跳转url 用户点击跳转支付页面 +} +//获取充值记录列表 +message ListRecordsRequest { + int64 user_id=1; //用户Id + int64 page=2; //页码 + int64 page_size=3; //每页数量 + int64 status=4; //状态筛选 +} +message ListRecords { + string order_no=1; //订单号 + int64 amount=2; //金额 + string currency=3; //币种 + string pay_method=4; //支付方式 + int64 status=5; //状态文本 + string created_at=6; //创建时间 +} +message ListRecordsReply { + repeated ListRecords list=1; // + int64 total=2; //总记录数 +} +//获取充值详情 +message GetDetailRequest { + string order_no=1; //订单号 +} +message GetDetailReply { + string order_no=1; //订单号 + int64 amount=2; //金额 + string currency=3; //币种 + string pay_method=4; //支付方式 + int64 status=5; //创建时间 + string trade_no=8; //交易单号 //第三方支付平台返回 +} +//支付宝回调 +message NotifyRequest { + +} +message NotifyReply { + string result=1; //返回支付平台结果 成功或者失败 +} diff --git a/api/order/v1/order_grpc.pb.go b/api/order/v1/order_grpc.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..54a27afe7d07ad0af56c1d66836e3e1efb91d213 --- /dev/null +++ b/api/order/v1/order_grpc.pb.go @@ -0,0 +1,245 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v3.19.4 +// source: api/order/v1/order.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Order_CreateOrder_FullMethodName = "/api.order.v1.Order/CreateOrder" + Order_ListRecords_FullMethodName = "/api.order.v1.Order/ListRecords" + Order_GetDetail_FullMethodName = "/api.order.v1.Order/GetDetail" + Order_Notify_FullMethodName = "/api.order.v1.Order/Notify" +) + +// OrderClient is the client API for Order service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type OrderClient interface { + // Sends a greeting + // 创建充值订单 + CreateOrder(ctx context.Context, in *CreateOrderRequest, opts ...grpc.CallOption) (*CreateOrderReply, error) + // 获取充值记录列表 + ListRecords(ctx context.Context, in *ListRecordsRequest, opts ...grpc.CallOption) (*ListRecordsReply, error) + // 获取充值详情 + GetDetail(ctx context.Context, in *GetDetailRequest, opts ...grpc.CallOption) (*GetDetailReply, error) + // 支付宝回调 + Notify(ctx context.Context, in *NotifyRequest, opts ...grpc.CallOption) (*NotifyReply, error) +} + +type orderClient struct { + cc grpc.ClientConnInterface +} + +func NewOrderClient(cc grpc.ClientConnInterface) OrderClient { + return &orderClient{cc} +} + +func (c *orderClient) CreateOrder(ctx context.Context, in *CreateOrderRequest, opts ...grpc.CallOption) (*CreateOrderReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateOrderReply) + err := c.cc.Invoke(ctx, Order_CreateOrder_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *orderClient) ListRecords(ctx context.Context, in *ListRecordsRequest, opts ...grpc.CallOption) (*ListRecordsReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListRecordsReply) + err := c.cc.Invoke(ctx, Order_ListRecords_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *orderClient) GetDetail(ctx context.Context, in *GetDetailRequest, opts ...grpc.CallOption) (*GetDetailReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetDetailReply) + err := c.cc.Invoke(ctx, Order_GetDetail_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *orderClient) Notify(ctx context.Context, in *NotifyRequest, opts ...grpc.CallOption) (*NotifyReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(NotifyReply) + err := c.cc.Invoke(ctx, Order_Notify_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// OrderServer is the server API for Order service. +// All implementations must embed UnimplementedOrderServer +// for forward compatibility. +type OrderServer interface { + // Sends a greeting + // 创建充值订单 + CreateOrder(context.Context, *CreateOrderRequest) (*CreateOrderReply, error) + // 获取充值记录列表 + ListRecords(context.Context, *ListRecordsRequest) (*ListRecordsReply, error) + // 获取充值详情 + GetDetail(context.Context, *GetDetailRequest) (*GetDetailReply, error) + // 支付宝回调 + Notify(context.Context, *NotifyRequest) (*NotifyReply, error) + mustEmbedUnimplementedOrderServer() +} + +// UnimplementedOrderServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedOrderServer struct{} + +func (UnimplementedOrderServer) CreateOrder(context.Context, *CreateOrderRequest) (*CreateOrderReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateOrder not implemented") +} +func (UnimplementedOrderServer) ListRecords(context.Context, *ListRecordsRequest) (*ListRecordsReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListRecords not implemented") +} +func (UnimplementedOrderServer) GetDetail(context.Context, *GetDetailRequest) (*GetDetailReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDetail not implemented") +} +func (UnimplementedOrderServer) Notify(context.Context, *NotifyRequest) (*NotifyReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method Notify not implemented") +} +func (UnimplementedOrderServer) mustEmbedUnimplementedOrderServer() {} +func (UnimplementedOrderServer) testEmbeddedByValue() {} + +// UnsafeOrderServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to OrderServer will +// result in compilation errors. +type UnsafeOrderServer interface { + mustEmbedUnimplementedOrderServer() +} + +func RegisterOrderServer(s grpc.ServiceRegistrar, srv OrderServer) { + // If the following call pancis, it indicates UnimplementedOrderServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Order_ServiceDesc, srv) +} + +func _Order_CreateOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrderServer).CreateOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Order_CreateOrder_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrderServer).CreateOrder(ctx, req.(*CreateOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Order_ListRecords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRecordsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrderServer).ListRecords(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Order_ListRecords_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrderServer).ListRecords(ctx, req.(*ListRecordsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Order_GetDetail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetDetailRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrderServer).GetDetail(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Order_GetDetail_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrderServer).GetDetail(ctx, req.(*GetDetailRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Order_Notify_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NotifyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(OrderServer).Notify(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Order_Notify_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(OrderServer).Notify(ctx, req.(*NotifyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Order_ServiceDesc is the grpc.ServiceDesc for Order service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Order_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "api.order.v1.Order", + HandlerType: (*OrderServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateOrder", + Handler: _Order_CreateOrder_Handler, + }, + { + MethodName: "ListRecords", + Handler: _Order_ListRecords_Handler, + }, + { + MethodName: "GetDetail", + Handler: _Order_GetDetail_Handler, + }, + { + MethodName: "Notify", + Handler: _Order_Notify_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "api/order/v1/order.proto", +} diff --git a/api/order/v1/order_http.pb.go b/api/order/v1/order_http.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..f9d4780ce1baa48bc1709cf43b94f6ff3a1d50b5 --- /dev/null +++ b/api/order/v1/order_http.pb.go @@ -0,0 +1,210 @@ +// Code generated by protoc-gen-go-http. DO NOT EDIT. +// versions: +// - protoc-gen-go-http v2.9.0 +// - protoc v3.19.4 +// source: api/order/v1/order.proto + +package v1 + +import ( + context "context" + http "github.com/go-kratos/kratos/v2/transport/http" + binding "github.com/go-kratos/kratos/v2/transport/http/binding" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the kratos package it is being compiled against. +var _ = new(context.Context) +var _ = binding.EncodeURL + +const _ = http.SupportPackageIsVersion1 + +const OperationOrderCreateOrder = "/api.order.v1.Order/CreateOrder" +const OperationOrderGetDetail = "/api.order.v1.Order/GetDetail" +const OperationOrderListRecords = "/api.order.v1.Order/ListRecords" +const OperationOrderNotify = "/api.order.v1.Order/Notify" + +type OrderHTTPServer interface { + // CreateOrder Sends a greeting + //创建充值订单 + CreateOrder(context.Context, *CreateOrderRequest) (*CreateOrderReply, error) + // GetDetail获取充值详情 + GetDetail(context.Context, *GetDetailRequest) (*GetDetailReply, error) + // ListRecords获取充值记录列表 + ListRecords(context.Context, *ListRecordsRequest) (*ListRecordsReply, error) + // Notify支付宝回调 + Notify(context.Context, *NotifyRequest) (*NotifyReply, error) +} + +func RegisterOrderHTTPServer(s *http.Server, srv OrderHTTPServer) { + r := s.Route("/") + r.POST("/v1/recharge/order", _Order_CreateOrder0_HTTP_Handler(srv)) + r.POST("/v1/recharge/records", _Order_ListRecords0_HTTP_Handler(srv)) + r.POST("/v1/recharge/detail", _Order_GetDetail0_HTTP_Handler(srv)) + r.POST("/v1/recharge/notify", _Order_Notify0_HTTP_Handler(srv)) +} + +func _Order_CreateOrder0_HTTP_Handler(srv OrderHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in CreateOrderRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationOrderCreateOrder) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.CreateOrder(ctx, req.(*CreateOrderRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*CreateOrderReply) + return ctx.Result(200, reply) + } +} + +func _Order_ListRecords0_HTTP_Handler(srv OrderHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in ListRecordsRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationOrderListRecords) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.ListRecords(ctx, req.(*ListRecordsRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*ListRecordsReply) + return ctx.Result(200, reply) + } +} + +func _Order_GetDetail0_HTTP_Handler(srv OrderHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in GetDetailRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationOrderGetDetail) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.GetDetail(ctx, req.(*GetDetailRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*GetDetailReply) + return ctx.Result(200, reply) + } +} + +func _Order_Notify0_HTTP_Handler(srv OrderHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in NotifyRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationOrderNotify) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.Notify(ctx, req.(*NotifyRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*NotifyReply) + return ctx.Result(200, reply) + } +} + +type OrderHTTPClient interface { + // CreateOrder Sends a greeting + //创建充值订单 + CreateOrder(ctx context.Context, req *CreateOrderRequest, opts ...http.CallOption) (rsp *CreateOrderReply, err error) + // GetDetail获取充值详情 + GetDetail(ctx context.Context, req *GetDetailRequest, opts ...http.CallOption) (rsp *GetDetailReply, err error) + // ListRecords获取充值记录列表 + ListRecords(ctx context.Context, req *ListRecordsRequest, opts ...http.CallOption) (rsp *ListRecordsReply, err error) + // Notify支付宝回调 + Notify(ctx context.Context, req *NotifyRequest, opts ...http.CallOption) (rsp *NotifyReply, err error) +} + +type OrderHTTPClientImpl struct { + cc *http.Client +} + +func NewOrderHTTPClient(client *http.Client) OrderHTTPClient { + return &OrderHTTPClientImpl{client} +} + +// CreateOrder Sends a greeting +//创建充值订单 +func (c *OrderHTTPClientImpl) CreateOrder(ctx context.Context, in *CreateOrderRequest, opts ...http.CallOption) (*CreateOrderReply, error) { + var out CreateOrderReply + pattern := "/v1/recharge/order" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationOrderCreateOrder)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// GetDetail获取充值详情 +func (c *OrderHTTPClientImpl) GetDetail(ctx context.Context, in *GetDetailRequest, opts ...http.CallOption) (*GetDetailReply, error) { + var out GetDetailReply + pattern := "/v1/recharge/detail" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationOrderGetDetail)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// ListRecords获取充值记录列表 +func (c *OrderHTTPClientImpl) ListRecords(ctx context.Context, in *ListRecordsRequest, opts ...http.CallOption) (*ListRecordsReply, error) { + var out ListRecordsReply + pattern := "/v1/recharge/records" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationOrderListRecords)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// Notify支付宝回调 +func (c *OrderHTTPClientImpl) Notify(ctx context.Context, in *NotifyRequest, opts ...http.CallOption) (*NotifyReply, error) { + var out NotifyReply + pattern := "/v1/recharge/notify" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationOrderNotify)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/api/real/v1/real.pb.go b/api/real/v1/real.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..22bb265bd21f7c0dd23222857e5c3362c8f2d01e --- /dev/null +++ b/api/real/v1/real.pb.go @@ -0,0 +1,392 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.19.4 +// source: real/v1/real.proto + +package v1 + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RealNameRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Card string `protobuf:"bytes,1,opt,name=card,proto3" json:"card,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + UserId int32 `protobuf:"varint,3,opt,name=userId,proto3" json:"userId,omitempty"` +} + +func (x *RealNameRequest) Reset() { + *x = RealNameRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_real_v1_real_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RealNameRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RealNameRequest) ProtoMessage() {} + +func (x *RealNameRequest) ProtoReflect() protoreflect.Message { + mi := &file_real_v1_real_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RealNameRequest.ProtoReflect.Descriptor instead. +func (*RealNameRequest) Descriptor() ([]byte, []int) { + return file_real_v1_real_proto_rawDescGZIP(), []int{0} +} + +func (x *RealNameRequest) GetCard() string { + if x != nil { + return x.Card + } + return "" +} + +func (x *RealNameRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RealNameRequest) GetUserId() int32 { + if x != nil { + return x.UserId + } + return 0 +} + +type RealNameReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` +} + +func (x *RealNameReply) Reset() { + *x = RealNameReply{} + if protoimpl.UnsafeEnabled { + mi := &file_real_v1_real_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RealNameReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RealNameReply) ProtoMessage() {} + +func (x *RealNameReply) ProtoReflect() protoreflect.Message { + mi := &file_real_v1_real_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RealNameReply.ProtoReflect.Descriptor instead. +func (*RealNameReply) Descriptor() ([]byte, []int) { + return file_real_v1_real_proto_rawDescGZIP(), []int{1} +} + +func (x *RealNameReply) GetUserId() int32 { + if x != nil { + return x.UserId + } + return 0 +} + +type CourierAuthAddRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` + CompanyId int32 `protobuf:"varint,2,opt,name=companyId,proto3" json:"companyId,omitempty"` + CardPhotoUrl string `protobuf:"bytes,3,opt,name=cardPhotoUrl,proto3" json:"cardPhotoUrl,omitempty"` +} + +func (x *CourierAuthAddRequest) Reset() { + *x = CourierAuthAddRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_real_v1_real_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CourierAuthAddRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CourierAuthAddRequest) ProtoMessage() {} + +func (x *CourierAuthAddRequest) ProtoReflect() protoreflect.Message { + mi := &file_real_v1_real_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CourierAuthAddRequest.ProtoReflect.Descriptor instead. +func (*CourierAuthAddRequest) Descriptor() ([]byte, []int) { + return file_real_v1_real_proto_rawDescGZIP(), []int{2} +} + +func (x *CourierAuthAddRequest) GetUserId() int32 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *CourierAuthAddRequest) GetCompanyId() int32 { + if x != nil { + return x.CompanyId + } + return 0 +} + +func (x *CourierAuthAddRequest) GetCardPhotoUrl() string { + if x != nil { + return x.CardPhotoUrl + } + return "" +} + +type CourierAuthAddReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserId int32 `protobuf:"varint,1,opt,name=userId,proto3" json:"userId,omitempty"` +} + +func (x *CourierAuthAddReply) Reset() { + *x = CourierAuthAddReply{} + if protoimpl.UnsafeEnabled { + mi := &file_real_v1_real_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CourierAuthAddReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CourierAuthAddReply) ProtoMessage() {} + +func (x *CourierAuthAddReply) ProtoReflect() protoreflect.Message { + mi := &file_real_v1_real_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CourierAuthAddReply.ProtoReflect.Descriptor instead. +func (*CourierAuthAddReply) Descriptor() ([]byte, []int) { + return file_real_v1_real_proto_rawDescGZIP(), []int{3} +} + +func (x *CourierAuthAddReply) GetUserId() int32 { + if x != nil { + return x.UserId + } + return 0 +} + +var File_real_v1_real_proto protoreflect.FileDescriptor + +var file_real_v1_real_proto_rawDesc = []byte{ + 0x0a, 0x12, 0x72, 0x65, 0x61, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x2e, 0x76, + 0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x51, 0x0a, 0x0f, 0x52, 0x65, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x61, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x63, 0x61, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x22, 0x27, 0x0a, 0x0d, 0x52, 0x65, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x71, 0x0a, 0x15, 0x43, + 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x41, 0x64, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, + 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x49, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x61, + 0x72, 0x64, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x55, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x63, 0x61, 0x72, 0x64, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x55, 0x72, 0x6c, 0x22, 0x2d, + 0x0a, 0x13, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x41, 0x64, 0x64, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x32, 0xd4, 0x01, + 0x0a, 0x04, 0x52, 0x65, 0x61, 0x6c, 0x12, 0x5a, 0x0a, 0x08, 0x52, 0x65, 0x61, 0x6c, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x61, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x14, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x0e, 0x3a, 0x01, 0x2a, 0x22, 0x09, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x70, 0x0a, 0x0e, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x41, 0x75, 0x74, + 0x68, 0x41, 0x64, 0x64, 0x12, 0x22, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x61, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x41, 0x64, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, + 0x65, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x41, 0x75, + 0x74, 0x68, 0x41, 0x64, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x63, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x2f, + 0x61, 0x75, 0x74, 0x68, 0x42, 0x27, 0x0a, 0x0b, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x61, 0x6c, + 0x2e, 0x76, 0x31, 0x50, 0x01, 0x5a, 0x16, 0x43, 0x61, 0x62, 0x69, 0x6e, 0x65, 0x74, 0x2f, 0x61, + 0x70, 0x69, 0x2f, 0x72, 0x65, 0x61, 0x6c, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_real_v1_real_proto_rawDescOnce sync.Once + file_real_v1_real_proto_rawDescData = file_real_v1_real_proto_rawDesc +) + +func file_real_v1_real_proto_rawDescGZIP() []byte { + file_real_v1_real_proto_rawDescOnce.Do(func() { + file_real_v1_real_proto_rawDescData = protoimpl.X.CompressGZIP(file_real_v1_real_proto_rawDescData) + }) + return file_real_v1_real_proto_rawDescData +} + +var file_real_v1_real_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_real_v1_real_proto_goTypes = []any{ + (*RealNameRequest)(nil), // 0: api.real.v1.RealNameRequest + (*RealNameReply)(nil), // 1: api.real.v1.RealNameReply + (*CourierAuthAddRequest)(nil), // 2: api.real.v1.CourierAuthAddRequest + (*CourierAuthAddReply)(nil), // 3: api.real.v1.CourierAuthAddReply +} +var file_real_v1_real_proto_depIdxs = []int32{ + 0, // 0: api.real.v1.Real.RealName:input_type -> api.real.v1.RealNameRequest + 2, // 1: api.real.v1.Real.CourierAuthAdd:input_type -> api.real.v1.CourierAuthAddRequest + 1, // 2: api.real.v1.Real.RealName:output_type -> api.real.v1.RealNameReply + 3, // 3: api.real.v1.Real.CourierAuthAdd:output_type -> api.real.v1.CourierAuthAddReply + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_real_v1_real_proto_init() } +func file_real_v1_real_proto_init() { + if File_real_v1_real_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_real_v1_real_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*RealNameRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_real_v1_real_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*RealNameReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_real_v1_real_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*CourierAuthAddRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_real_v1_real_proto_msgTypes[3].Exporter = func(v any, i int) any { + switch v := v.(*CourierAuthAddReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_real_v1_real_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_real_v1_real_proto_goTypes, + DependencyIndexes: file_real_v1_real_proto_depIdxs, + MessageInfos: file_real_v1_real_proto_msgTypes, + }.Build() + File_real_v1_real_proto = out.File + file_real_v1_real_proto_rawDesc = nil + file_real_v1_real_proto_goTypes = nil + file_real_v1_real_proto_depIdxs = nil +} diff --git a/api/real/v1/real.proto b/api/real/v1/real.proto new file mode 100644 index 0000000000000000000000000000000000000000..0f689328bd2c9d5abb15d1475c95012bbe545b75 --- /dev/null +++ b/api/real/v1/real.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package api.real.v1; + +import "google/api/annotations.proto"; + +option go_package = "Cabinet/api/real/v1;v1"; +option java_multiple_files = true; +option java_package = "api.real.v1"; + +service Real { + rpc RealName (RealNameRequest) returns (RealNameReply) { + option (google.api.http) = { + post: "/realName" + body:"*" + }; + } + rpc CourierAuthAdd(CourierAuthAddRequest) returns (CourierAuthAddReply) { + option (google.api.http) = { + post: "/courier/auth" + body:"*" + }; + } +} + +message RealNameRequest { + string card=1; + string name=2; + int32 userId=3; +} +message RealNameReply { + int32 userId=1; +} + +message CourierAuthAddRequest { + int32 userId=1; + int32 companyId=2; + string cardPhotoUrl=3; +} +message CourierAuthAddReply { + int32 userId=1; + +} + diff --git a/api/real/v1/real_grpc.pb.go b/api/real/v1/real_grpc.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..c0014d97b44fc5768eb0db7215d60f502e35b544 --- /dev/null +++ b/api/real/v1/real_grpc.pb.go @@ -0,0 +1,148 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc v3.19.4 +// source: real/v1/real.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + Real_RealName_FullMethodName = "/api.real.v1.Real/RealName" + Real_CourierAuthAdd_FullMethodName = "/api.real.v1.Real/CourierAuthAdd" +) + +// RealClient is the client API for Real service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type RealClient interface { + RealName(ctx context.Context, in *RealNameRequest, opts ...grpc.CallOption) (*RealNameReply, error) + CourierAuthAdd(ctx context.Context, in *CourierAuthAddRequest, opts ...grpc.CallOption) (*CourierAuthAddReply, error) +} + +type realClient struct { + cc grpc.ClientConnInterface +} + +func NewRealClient(cc grpc.ClientConnInterface) RealClient { + return &realClient{cc} +} + +func (c *realClient) RealName(ctx context.Context, in *RealNameRequest, opts ...grpc.CallOption) (*RealNameReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RealNameReply) + err := c.cc.Invoke(ctx, Real_RealName_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *realClient) CourierAuthAdd(ctx context.Context, in *CourierAuthAddRequest, opts ...grpc.CallOption) (*CourierAuthAddReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CourierAuthAddReply) + err := c.cc.Invoke(ctx, Real_CourierAuthAdd_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RealServer is the server API for Real service. +// All implementations must embed UnimplementedRealServer +// for forward compatibility +type RealServer interface { + RealName(context.Context, *RealNameRequest) (*RealNameReply, error) + CourierAuthAdd(context.Context, *CourierAuthAddRequest) (*CourierAuthAddReply, error) + mustEmbedUnimplementedRealServer() +} + +// UnimplementedRealServer must be embedded to have forward compatible implementations. +type UnimplementedRealServer struct { +} + +func (UnimplementedRealServer) RealName(context.Context, *RealNameRequest) (*RealNameReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method RealName not implemented") +} +func (UnimplementedRealServer) CourierAuthAdd(context.Context, *CourierAuthAddRequest) (*CourierAuthAddReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method CourierAuthAdd not implemented") +} +func (UnimplementedRealServer) mustEmbedUnimplementedRealServer() {} + +// UnsafeRealServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RealServer will +// result in compilation errors. +type UnsafeRealServer interface { + mustEmbedUnimplementedRealServer() +} + +func RegisterRealServer(s grpc.ServiceRegistrar, srv RealServer) { + s.RegisterService(&Real_ServiceDesc, srv) +} + +func _Real_RealName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RealNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RealServer).RealName(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Real_RealName_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RealServer).RealName(ctx, req.(*RealNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Real_CourierAuthAdd_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CourierAuthAddRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RealServer).CourierAuthAdd(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Real_CourierAuthAdd_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RealServer).CourierAuthAdd(ctx, req.(*CourierAuthAddRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Real_ServiceDesc is the grpc.ServiceDesc for Real service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Real_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "api.real.v1.Real", + HandlerType: (*RealServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RealName", + Handler: _Real_RealName_Handler, + }, + { + MethodName: "CourierAuthAdd", + Handler: _Real_CourierAuthAdd_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "real/v1/real.proto", +} diff --git a/api/real/v1/real_http.pb.go b/api/real/v1/real_http.pb.go new file mode 100644 index 0000000000000000000000000000000000000000..aab4fe73f83a70e35c028c80c3019933f021f26c --- /dev/null +++ b/api/real/v1/real_http.pb.go @@ -0,0 +1,78 @@ +// Code generated by protoc-gen-go-http. DO NOT EDIT. +// versions: +// - protoc-gen-go-http v2.9.0 +// - protoc v3.19.4 +// source: api/real/v1/real.proto + +package v1 + +import ( + context "context" + http "github.com/go-kratos/kratos/v2/transport/http" + binding "github.com/go-kratos/kratos/v2/transport/http/binding" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the kratos package it is being compiled against. +var _ = new(context.Context) +var _ = binding.EncodeURL + +const _ = http.SupportPackageIsVersion1 + +const OperationRealRealName = "/api.real.v1.Real/RealName" + +type RealHTTPServer interface { + RealName(context.Context, *RealNameRequest) (*RealNameReply, error) +} + +func RegisterRealHTTPServer(s *http.Server, srv RealHTTPServer) { + r := s.Route("/") + r.POST("/realName", _Real_RealName0_HTTP_Handler(srv)) +} + +func _Real_RealName0_HTTP_Handler(srv RealHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in RealNameRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationRealRealName) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.RealName(ctx, req.(*RealNameRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*RealNameReply) + return ctx.Result(200, reply) + } +} + +type RealHTTPClient interface { + RealName(ctx context.Context, req *RealNameRequest, opts ...http.CallOption) (rsp *RealNameReply, err error) +} + +type RealHTTPClientImpl struct { + cc *http.Client +} + +func NewRealHTTPClient(client *http.Client) RealHTTPClient { + return &RealHTTPClientImpl{client} +} + +func (c *RealHTTPClientImpl) RealName(ctx context.Context, in *RealNameRequest, opts ...http.CallOption) (*RealNameReply, error) { + var out RealNameReply + pattern := "/realName" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationRealRealName)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/api/user/v1/user.pb.go b/api/user/v1/user.pb.go index 1d209d726803a30d2fd704a548c124b523b707dc..df7e1f826b88157e4c3397ffae0e74598091c0f6 100644 --- a/api/user/v1/user.pb.go +++ b/api/user/v1/user.pb.go @@ -22,10 +22,11 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// 短信发送 +// SendSmsRequest 发送短信的请求参数 +// 用于指定接收短信的手机号码 type SendSmsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` + Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` // 手机号码,需要接收验证码的用户手机号 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -67,8 +68,12 @@ func (x *SendSmsRequest) GetPhone() string { return "" } +// SendSmsReply 发送短信的响应结果 +// 返回短信发送状态 type SendSmsReply struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState `protogen:"open.v1"` + // 注:虽然当前为空,但实际应用中应包含发送结果状态 + Map string `protobuf:"bytes,1,opt,name=Map,proto3" json:"Map,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -103,13 +108,21 @@ func (*SendSmsReply) Descriptor() ([]byte, []int) { return file_api_user_v1_user_proto_rawDescGZIP(), []int{1} } -// 手机号+密码+验证码注册 +func (x *SendSmsReply) GetMap() string { + if x != nil { + return x.Map + } + return "" +} + +// RegisterRequest 用户注册请求参数 +// 用于提交用户注册所需的全部信息 type RegisterRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` - SendSms string `protobuf:"bytes,2,opt,name=SendSms,proto3" json:"SendSms,omitempty"` - Password string `protobuf:"bytes,3,opt,name=Password,proto3" json:"Password,omitempty"` - NewPassword string `protobuf:"bytes,4,opt,name=NewPassword,proto3" json:"NewPassword,omitempty"` + Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` // 手机号码,用户的注册手机号 + SendSms string `protobuf:"bytes,2,opt,name=SendSms,proto3" json:"SendSms,omitempty"` // 短信验证码,用于验证手机号真实性 + Password string `protobuf:"bytes,3,opt,name=Password,proto3" json:"Password,omitempty"` // 密码,用户的登录密码 + NewPassword string `protobuf:"bytes,4,opt,name=NewPassword,proto3" json:"NewPassword,omitempty"` // 确认密码,用于确保密码输入正确 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -172,9 +185,11 @@ func (x *RegisterRequest) GetNewPassword() string { return "" } +// RegisterReply 用户注册响应结果 +// 返回注册操作的结果状态 type RegisterReply struct { state protoimpl.MessageState `protogen:"open.v1"` - Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` + Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` // 消息,注册结果的详细描述或错误信息 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -216,11 +231,12 @@ func (x *RegisterReply) GetMsg() string { return "" } -// 手机号+密码登录 +// LoginRequest 密码登录请求参数 +// 用于用户通过手机号和密码进行登录 type LoginRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` - Password string `protobuf:"bytes,2,opt,name=Password,proto3" json:"Password,omitempty"` + Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` // 手机号码,用户的注册手机号 + Password string `protobuf:"bytes,2,opt,name=Password,proto3" json:"Password,omitempty"` // 密码,用户的登录密码 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -269,10 +285,12 @@ func (x *LoginRequest) GetPassword() string { return "" } +// LoginReply 密码登录响应结果 +// 返回登录操作的结果和访问令牌 type LoginReply struct { state protoimpl.MessageState `protogen:"open.v1"` - Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` - Token string `protobuf:"bytes,2,opt,name=Token,proto3" json:"Token,omitempty"` + Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` // 消息,登录结果的详细描述或错误信息 + Token string `protobuf:"bytes,2,opt,name=Token,proto3" json:"Token,omitempty"` // 访问令牌,用于后续API调用的身份认证 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -321,11 +339,12 @@ func (x *LoginReply) GetToken() string { return "" } -// 手机号+验证码登录 +// LoginSendSmsRequest 验证码登录请求参数 +// 用于用户通过手机号和短信验证码进行登录 type LoginSendSmsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` - SendSms string `protobuf:"bytes,2,opt,name=SendSms,proto3" json:"SendSms,omitempty"` + Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` // 手机号码,用户的注册手机号 + SendSms string `protobuf:"bytes,2,opt,name=SendSms,proto3" json:"SendSms,omitempty"` // 短信验证码,用于验证身份 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -374,10 +393,13 @@ func (x *LoginSendSmsRequest) GetSendSms() string { return "" } +// LoginSendSmsReply 验证码登录响应结果 +// 返回登录操作的结果和访问令牌 type LoginSendSmsReply struct { state protoimpl.MessageState `protogen:"open.v1"` - Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` - Token string `protobuf:"bytes,2,opt,name=Token,proto3" json:"Token,omitempty"` + Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` // 消息,登录结果的详细描述或错误信息 + Token string `protobuf:"bytes,2,opt,name=Token,proto3" json:"Token,omitempty"` // 访问令牌,用于后续API调用的身份认证 + UserId int64 `protobuf:"varint,3,opt,name=UserId,proto3" json:"UserId,omitempty"` // 用户ID unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -426,14 +448,232 @@ func (x *LoginSendSmsReply) GetToken() string { return "" } +func (x *LoginSendSmsReply) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +// 退出登录 +type LogoutRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Token string `protobuf:"bytes,1,opt,name=Token,proto3" json:"Token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogoutRequest) Reset() { + *x = LogoutRequest{} + mi := &file_api_user_v1_user_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogoutRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogoutRequest) ProtoMessage() {} + +func (x *LogoutRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_user_v1_user_proto_msgTypes[8] + 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 LogoutRequest.ProtoReflect.Descriptor instead. +func (*LogoutRequest) Descriptor() ([]byte, []int) { + return file_api_user_v1_user_proto_rawDescGZIP(), []int{8} +} + +func (x *LogoutRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +type LogoutReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Msg string `protobuf:"bytes,1,opt,name=Msg,proto3" json:"Msg,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogoutReply) Reset() { + *x = LogoutReply{} + mi := &file_api_user_v1_user_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogoutReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogoutReply) ProtoMessage() {} + +func (x *LogoutReply) ProtoReflect() protoreflect.Message { + mi := &file_api_user_v1_user_proto_msgTypes[9] + 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 LogoutReply.ProtoReflect.Descriptor instead. +func (*LogoutReply) Descriptor() ([]byte, []int) { + return file_api_user_v1_user_proto_rawDescGZIP(), []int{9} +} + +func (x *LogoutReply) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +// 忘记密码 +type UpdatePasswordRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Phone string `protobuf:"bytes,1,opt,name=Phone,proto3" json:"Phone,omitempty"` + SendSms string `protobuf:"bytes,2,opt,name=SendSms,proto3" json:"SendSms,omitempty"` + Password string `protobuf:"bytes,3,opt,name=Password,proto3" json:"Password,omitempty"` + NewPassword string `protobuf:"bytes,4,opt,name=NewPassword,proto3" json:"NewPassword,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdatePasswordRequest) Reset() { + *x = UpdatePasswordRequest{} + mi := &file_api_user_v1_user_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdatePasswordRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdatePasswordRequest) ProtoMessage() {} + +func (x *UpdatePasswordRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_user_v1_user_proto_msgTypes[10] + 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 UpdatePasswordRequest.ProtoReflect.Descriptor instead. +func (*UpdatePasswordRequest) Descriptor() ([]byte, []int) { + return file_api_user_v1_user_proto_rawDescGZIP(), []int{10} +} + +func (x *UpdatePasswordRequest) GetPhone() string { + if x != nil { + return x.Phone + } + return "" +} + +func (x *UpdatePasswordRequest) GetSendSms() string { + if x != nil { + return x.SendSms + } + return "" +} + +func (x *UpdatePasswordRequest) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *UpdatePasswordRequest) GetNewPassword() string { + if x != nil { + return x.NewPassword + } + return "" +} + +type UpdatePasswordReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=UserId,proto3" json:"UserId,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=Msg,proto3" json:"Msg,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdatePasswordReply) Reset() { + *x = UpdatePasswordReply{} + mi := &file_api_user_v1_user_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdatePasswordReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdatePasswordReply) ProtoMessage() {} + +func (x *UpdatePasswordReply) ProtoReflect() protoreflect.Message { + mi := &file_api_user_v1_user_proto_msgTypes[11] + 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 UpdatePasswordReply.ProtoReflect.Descriptor instead. +func (*UpdatePasswordReply) Descriptor() ([]byte, []int) { + return file_api_user_v1_user_proto_rawDescGZIP(), []int{11} +} + +func (x *UpdatePasswordReply) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *UpdatePasswordReply) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + var File_api_user_v1_user_proto protoreflect.FileDescriptor const file_api_user_v1_user_proto_rawDesc = "" + "\n" + "\x16api/user/v1/user.proto\x12\vapi.user.v1\x1a\x1cgoogle/api/annotations.proto\"&\n" + "\x0eSendSmsRequest\x12\x14\n" + - "\x05Phone\x18\x01 \x01(\tR\x05Phone\"\x0e\n" + - "\fSendSmsReply\"\x7f\n" + + "\x05Phone\x18\x01 \x01(\tR\x05Phone\" \n" + + "\fSendSmsReply\x12\x10\n" + + "\x03Map\x18\x01 \x01(\tR\x03Map\"\x7f\n" + "\x0fRegisterRequest\x12\x14\n" + "\x05Phone\x18\x01 \x01(\tR\x05Phone\x12\x18\n" + "\aSendSms\x18\x02 \x01(\tR\aSendSms\x12\x1a\n" + @@ -450,15 +690,30 @@ const file_api_user_v1_user_proto_rawDesc = "" + "\x05Token\x18\x02 \x01(\tR\x05Token\"E\n" + "\x13LoginSendSmsRequest\x12\x14\n" + "\x05Phone\x18\x01 \x01(\tR\x05Phone\x12\x18\n" + - "\aSendSms\x18\x02 \x01(\tR\aSendSms\";\n" + + "\aSendSms\x18\x02 \x01(\tR\aSendSms\"S\n" + "\x11LoginSendSmsReply\x12\x10\n" + "\x03msg\x18\x01 \x01(\tR\x03msg\x12\x14\n" + - "\x05Token\x18\x02 \x01(\tR\x05Token2\xf6\x02\n" + + "\x05Token\x18\x02 \x01(\tR\x05Token\x12\x16\n" + + "\x06UserId\x18\x03 \x01(\x03R\x06UserId\"%\n" + + "\rLogoutRequest\x12\x14\n" + + "\x05Token\x18\x01 \x01(\tR\x05Token\"\x1f\n" + + "\vLogoutReply\x12\x10\n" + + "\x03Msg\x18\x01 \x01(\tR\x03Msg\"\x85\x01\n" + + "\x15UpdatePasswordRequest\x12\x14\n" + + "\x05Phone\x18\x01 \x01(\tR\x05Phone\x12\x18\n" + + "\aSendSms\x18\x02 \x01(\tR\aSendSms\x12\x1a\n" + + "\bPassword\x18\x03 \x01(\tR\bPassword\x12 \n" + + "\vNewPassword\x18\x04 \x01(\tR\vNewPassword\"?\n" + + "\x13UpdatePasswordReply\x12\x16\n" + + "\x06UserId\x18\x01 \x01(\x03R\x06UserId\x12\x10\n" + + "\x03Msg\x18\x02 \x01(\tR\x03Msg2\xbe\x04\n" + "\x04User\x12V\n" + "\aSendSms\x12\x1b.api.user.v1.SendSmsRequest\x1a\x19.api.user.v1.SendSmsReply\"\x13\x82\xd3\xe4\x93\x02\r:\x01*\"\b/SendSms\x12Z\n" + "\bRegister\x12\x1c.api.user.v1.RegisterRequest\x1a\x1a.api.user.v1.RegisterReply\"\x14\x82\xd3\xe4\x93\x02\x0e:\x01*\"\t/register\x12N\n" + "\x05Login\x12\x19.api.user.v1.LoginRequest\x1a\x17.api.user.v1.LoginReply\"\x11\x82\xd3\xe4\x93\x02\v:\x01*\"\x06/login\x12j\n" + - "\fLoginSendSms\x12 .api.user.v1.LoginSendSmsRequest\x1a\x1e.api.user.v1.LoginSendSmsReply\"\x18\x82\xd3\xe4\x93\x02\x12:\x01*\"\r/loginSendSmsB'\n" + + "\fLoginSendSms\x12 .api.user.v1.LoginSendSmsRequest\x1a\x1e.api.user.v1.LoginSendSmsReply\"\x18\x82\xd3\xe4\x93\x02\x12:\x01*\"\r/loginSendSms\x12R\n" + + "\x06Logout\x12\x1a.api.user.v1.LogoutRequest\x1a\x18.api.user.v1.LogoutReply\"\x12\x82\xd3\xe4\x93\x02\f:\x01*\"\a/logout\x12r\n" + + "\x0eUpdatePassword\x12\".api.user.v1.UpdatePasswordRequest\x1a .api.user.v1.UpdatePasswordReply\"\x1a\x82\xd3\xe4\x93\x02\x14:\x01*\"\x0f/updatePasswordB'\n" + "\vapi.user.v1P\x01Z\x16Cabinet/api/user/v1;v1b\x06proto3" var ( @@ -473,31 +728,39 @@ func file_api_user_v1_user_proto_rawDescGZIP() []byte { return file_api_user_v1_user_proto_rawDescData } -var file_api_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_api_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_api_user_v1_user_proto_goTypes = []any{ - (*SendSmsRequest)(nil), // 0: api.user.v1.SendSmsRequest - (*SendSmsReply)(nil), // 1: api.user.v1.SendSmsReply - (*RegisterRequest)(nil), // 2: api.user.v1.RegisterRequest - (*RegisterReply)(nil), // 3: api.user.v1.RegisterReply - (*LoginRequest)(nil), // 4: api.user.v1.LoginRequest - (*LoginReply)(nil), // 5: api.user.v1.LoginReply - (*LoginSendSmsRequest)(nil), // 6: api.user.v1.LoginSendSmsRequest - (*LoginSendSmsReply)(nil), // 7: api.user.v1.LoginSendSmsReply + (*SendSmsRequest)(nil), // 0: api.user.v1.SendSmsRequest + (*SendSmsReply)(nil), // 1: api.user.v1.SendSmsReply + (*RegisterRequest)(nil), // 2: api.user.v1.RegisterRequest + (*RegisterReply)(nil), // 3: api.user.v1.RegisterReply + (*LoginRequest)(nil), // 4: api.user.v1.LoginRequest + (*LoginReply)(nil), // 5: api.user.v1.LoginReply + (*LoginSendSmsRequest)(nil), // 6: api.user.v1.LoginSendSmsRequest + (*LoginSendSmsReply)(nil), // 7: api.user.v1.LoginSendSmsReply + (*LogoutRequest)(nil), // 8: api.user.v1.LogoutRequest + (*LogoutReply)(nil), // 9: api.user.v1.LogoutReply + (*UpdatePasswordRequest)(nil), // 10: api.user.v1.UpdatePasswordRequest + (*UpdatePasswordReply)(nil), // 11: api.user.v1.UpdatePasswordReply } var file_api_user_v1_user_proto_depIdxs = []int32{ - 0, // 0: api.user.v1.User.SendSms:input_type -> api.user.v1.SendSmsRequest - 2, // 1: api.user.v1.User.Register:input_type -> api.user.v1.RegisterRequest - 4, // 2: api.user.v1.User.Login:input_type -> api.user.v1.LoginRequest - 6, // 3: api.user.v1.User.LoginSendSms:input_type -> api.user.v1.LoginSendSmsRequest - 1, // 4: api.user.v1.User.SendSms:output_type -> api.user.v1.SendSmsReply - 3, // 5: api.user.v1.User.Register:output_type -> api.user.v1.RegisterReply - 5, // 6: api.user.v1.User.Login:output_type -> api.user.v1.LoginReply - 7, // 7: api.user.v1.User.LoginSendSms:output_type -> api.user.v1.LoginSendSmsReply - 4, // [4:8] is the sub-list for method output_type - 0, // [0:4] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: api.user.v1.User.SendSms:input_type -> api.user.v1.SendSmsRequest + 2, // 1: api.user.v1.User.Register:input_type -> api.user.v1.RegisterRequest + 4, // 2: api.user.v1.User.Login:input_type -> api.user.v1.LoginRequest + 6, // 3: api.user.v1.User.LoginSendSms:input_type -> api.user.v1.LoginSendSmsRequest + 8, // 4: api.user.v1.User.Logout:input_type -> api.user.v1.LogoutRequest + 10, // 5: api.user.v1.User.UpdatePassword:input_type -> api.user.v1.UpdatePasswordRequest + 1, // 6: api.user.v1.User.SendSms:output_type -> api.user.v1.SendSmsReply + 3, // 7: api.user.v1.User.Register:output_type -> api.user.v1.RegisterReply + 5, // 8: api.user.v1.User.Login:output_type -> api.user.v1.LoginReply + 7, // 9: api.user.v1.User.LoginSendSms:output_type -> api.user.v1.LoginSendSmsReply + 9, // 10: api.user.v1.User.Logout:output_type -> api.user.v1.LogoutReply + 11, // 11: api.user.v1.User.UpdatePassword:output_type -> api.user.v1.UpdatePasswordReply + 6, // [6:12] is the sub-list for method output_type + 0, // [0:6] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } func init() { file_api_user_v1_user_proto_init() } @@ -511,7 +774,7 @@ func file_api_user_v1_user_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_user_v1_user_proto_rawDesc), len(file_api_user_v1_user_proto_rawDesc)), NumEnums: 0, - NumMessages: 8, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/api/user/v1/user.proto b/api/user/v1/user.proto index d73dd3413f49e926c62d4ae95a7e174a7129d34d..cb03d517044e12029971403049b9c73af8d24efb 100644 --- a/api/user/v1/user.proto +++ b/api/user/v1/user.proto @@ -64,6 +64,19 @@ service User { body: "*" }; } + + rpc Logout (LogoutRequest) returns (LogoutReply) { + option (google.api.http) = { + post: "/logout" + body: "*" + }; + } + rpc UpdatePassword (UpdatePasswordRequest) returns (UpdatePasswordReply) { + option (google.api.http) = { + post: "/updatePassword" + body: "*" + }; + } } // SendSmsRequest 发送短信的请求参数 @@ -76,6 +89,7 @@ message SendSmsRequest { // 返回短信发送状态 message SendSmsReply { // 注:虽然当前为空,但实际应用中应包含发送结果状态 + string Map = 1; } // RegisterRequest 用户注册请求参数 @@ -118,4 +132,30 @@ message LoginSendSmsRequest { message LoginSendSmsReply { string msg = 1; // 消息,登录结果的详细描述或错误信息 string Token = 2; // 访问令牌,用于后续API调用的身份认证 + int64 UserId = 3; // 用户ID +} + +//退出登录 +message LogoutRequest { + string Token = 1; +} + +message LogoutReply { + string Msg = 1; +} + + +//忘记密码 +message UpdatePasswordRequest { + string Phone = 1; + string SendSms = 2; + string Password = 3; + string NewPassword = 4; +} + +message UpdatePasswordReply { + int64 UserId = 1; + string Msg = 2; } + + diff --git a/api/user/v1/user_grpc.pb.go b/api/user/v1/user_grpc.pb.go index 539fba74afd2637cc9f8788815d7d96992682bf4..06653729271b042e9ecc7c0cc36b5acbdf2c0fce 100644 --- a/api/user/v1/user_grpc.pb.go +++ b/api/user/v1/user_grpc.pb.go @@ -19,20 +19,54 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - User_SendSms_FullMethodName = "/api.user.v1.User/SendSms" - User_Register_FullMethodName = "/api.user.v1.User/Register" - User_Login_FullMethodName = "/api.user.v1.User/Login" - User_LoginSendSms_FullMethodName = "/api.user.v1.User/LoginSendSms" + User_SendSms_FullMethodName = "/api.user.v1.User/SendSms" + User_Register_FullMethodName = "/api.user.v1.User/Register" + User_Login_FullMethodName = "/api.user.v1.User/Login" + User_LoginSendSms_FullMethodName = "/api.user.v1.User/LoginSendSms" + User_Logout_FullMethodName = "/api.user.v1.User/Logout" + User_UpdatePassword_FullMethodName = "/api.user.v1.User/UpdatePassword" ) // UserClient is the client API for User service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// User 用户服务接口 +// 该服务提供用户管理相关的功能,包括注册、登录、短信验证等 +// 版本:v1 +// 模块:user +// 命名空间:api.user.v1 type UserClient interface { + // SendSms 发送短信验证码 + // 功能:向指定手机号发送验证码短信 + // 用于注册、登录等需要验证身份的场景 + // HTTP路径:POST /SendSms + // 请求体:SendSmsRequest + // 响应:SendSmsReply SendSms(ctx context.Context, in *SendSmsRequest, opts ...grpc.CallOption) (*SendSmsReply, error) + // Register 用户注册 + // 功能:用户通过手机号、密码和验证码进行注册 + // 这是新用户进入系统的第一步 + // HTTP路径:POST /register + // 请求体:RegisterRequest + // 响应:RegisterReply Register(ctx context.Context, in *RegisterRequest, opts ...grpc.CallOption) (*RegisterReply, error) + // Login 用户登录(密码方式) + // 功能:用户通过手机号和密码进行登录 + // 返回登录状态和访问令牌 + // HTTP路径:POST /login + // 请求体:LoginRequest + // 响应:LoginReply Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginReply, error) + // LoginSendSms 用户登录(验证码方式) + // 功能:用户通过手机号和短信验证码进行登录 + // 提供更便捷的登录方式 + // HTTP路径:POST /loginSendSms + // 请求体:LoginSendSmsRequest + // 响应:LoginSendSmsReply LoginSendSms(ctx context.Context, in *LoginSendSmsRequest, opts ...grpc.CallOption) (*LoginSendSmsReply, error) + Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutReply, error) + UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, opts ...grpc.CallOption) (*UpdatePasswordReply, error) } type userClient struct { @@ -83,14 +117,66 @@ func (c *userClient) LoginSendSms(ctx context.Context, in *LoginSendSmsRequest, return out, nil } +func (c *userClient) Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*LogoutReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LogoutReply) + err := c.cc.Invoke(ctx, User_Logout_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userClient) UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, opts ...grpc.CallOption) (*UpdatePasswordReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdatePasswordReply) + err := c.cc.Invoke(ctx, User_UpdatePassword_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // UserServer is the server API for User service. // All implementations must embed UnimplementedUserServer // for forward compatibility. +// +// User 用户服务接口 +// 该服务提供用户管理相关的功能,包括注册、登录、短信验证等 +// 版本:v1 +// 模块:user +// 命名空间:api.user.v1 type UserServer interface { + // SendSms 发送短信验证码 + // 功能:向指定手机号发送验证码短信 + // 用于注册、登录等需要验证身份的场景 + // HTTP路径:POST /SendSms + // 请求体:SendSmsRequest + // 响应:SendSmsReply SendSms(context.Context, *SendSmsRequest) (*SendSmsReply, error) + // Register 用户注册 + // 功能:用户通过手机号、密码和验证码进行注册 + // 这是新用户进入系统的第一步 + // HTTP路径:POST /register + // 请求体:RegisterRequest + // 响应:RegisterReply Register(context.Context, *RegisterRequest) (*RegisterReply, error) + // Login 用户登录(密码方式) + // 功能:用户通过手机号和密码进行登录 + // 返回登录状态和访问令牌 + // HTTP路径:POST /login + // 请求体:LoginRequest + // 响应:LoginReply Login(context.Context, *LoginRequest) (*LoginReply, error) + // LoginSendSms 用户登录(验证码方式) + // 功能:用户通过手机号和短信验证码进行登录 + // 提供更便捷的登录方式 + // HTTP路径:POST /loginSendSms + // 请求体:LoginSendSmsRequest + // 响应:LoginSendSmsReply LoginSendSms(context.Context, *LoginSendSmsRequest) (*LoginSendSmsReply, error) + Logout(context.Context, *LogoutRequest) (*LogoutReply, error) + UpdatePassword(context.Context, *UpdatePasswordRequest) (*UpdatePasswordReply, error) mustEmbedUnimplementedUserServer() } @@ -113,6 +199,12 @@ func (UnimplementedUserServer) Login(context.Context, *LoginRequest) (*LoginRepl func (UnimplementedUserServer) LoginSendSms(context.Context, *LoginSendSmsRequest) (*LoginSendSmsReply, error) { return nil, status.Errorf(codes.Unimplemented, "method LoginSendSms not implemented") } +func (UnimplementedUserServer) Logout(context.Context, *LogoutRequest) (*LogoutReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented") +} +func (UnimplementedUserServer) UpdatePassword(context.Context, *UpdatePasswordRequest) (*UpdatePasswordReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdatePassword not implemented") +} func (UnimplementedUserServer) mustEmbedUnimplementedUserServer() {} func (UnimplementedUserServer) testEmbeddedByValue() {} @@ -206,6 +298,42 @@ func _User_LoginSendSms_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _User_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LogoutRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServer).Logout(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: User_Logout_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServer).Logout(ctx, req.(*LogoutRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _User_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdatePasswordRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServer).UpdatePassword(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: User_UpdatePassword_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServer).UpdatePassword(ctx, req.(*UpdatePasswordRequest)) + } + return interceptor(ctx, in, info, handler) +} + // User_ServiceDesc is the grpc.ServiceDesc for User service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -229,6 +357,14 @@ var User_ServiceDesc = grpc.ServiceDesc{ MethodName: "LoginSendSms", Handler: _User_LoginSendSms_Handler, }, + { + MethodName: "Logout", + Handler: _User_Logout_Handler, + }, + { + MethodName: "UpdatePassword", + Handler: _User_UpdatePassword_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/user/v1/user.proto", diff --git a/api/user/v1/user_http.pb.go b/api/user/v1/user_http.pb.go index 898075d68c62bcc08ddb297b3967d9c42cd48314..08bfa0d06e956cf3e427d21e13c23e819ebea208 100644 --- a/api/user/v1/user_http.pb.go +++ b/api/user/v1/user_http.pb.go @@ -21,14 +21,42 @@ const _ = http.SupportPackageIsVersion1 const OperationUserLogin = "/api.user.v1.User/Login" const OperationUserLoginSendSms = "/api.user.v1.User/LoginSendSms" +const OperationUserLogout = "/api.user.v1.User/Logout" const OperationUserRegister = "/api.user.v1.User/Register" const OperationUserSendSms = "/api.user.v1.User/SendSms" +const OperationUserUpdatePassword = "/api.user.v1.User/UpdatePassword" type UserHTTPServer interface { + // Login Login 用户登录(密码方式) + // 功能:用户通过手机号和密码进行登录 + // 返回登录状态和访问令牌 + // HTTP路径:POST /login + // 请求体:LoginRequest + // 响应:LoginReply Login(context.Context, *LoginRequest) (*LoginReply, error) + // LoginSendSms LoginSendSms 用户登录(验证码方式) + // 功能:用户通过手机号和短信验证码进行登录 + // 提供更便捷的登录方式 + // HTTP路径:POST /loginSendSms + // 请求体:LoginSendSmsRequest + // 响应:LoginSendSmsReply LoginSendSms(context.Context, *LoginSendSmsRequest) (*LoginSendSmsReply, error) + Logout(context.Context, *LogoutRequest) (*LogoutReply, error) + // Register Register 用户注册 + // 功能:用户通过手机号、密码和验证码进行注册 + // 这是新用户进入系统的第一步 + // HTTP路径:POST /register + // 请求体:RegisterRequest + // 响应:RegisterReply Register(context.Context, *RegisterRequest) (*RegisterReply, error) + // SendSms SendSms 发送短信验证码 + // 功能:向指定手机号发送验证码短信 + // 用于注册、登录等需要验证身份的场景 + // HTTP路径:POST /SendSms + // 请求体:SendSmsRequest + // 响应:SendSmsReply SendSms(context.Context, *SendSmsRequest) (*SendSmsReply, error) + UpdatePassword(context.Context, *UpdatePasswordRequest) (*UpdatePasswordReply, error) } func RegisterUserHTTPServer(s *http.Server, srv UserHTTPServer) { @@ -37,6 +65,8 @@ func RegisterUserHTTPServer(s *http.Server, srv UserHTTPServer) { r.POST("/register", _User_Register0_HTTP_Handler(srv)) r.POST("/login", _User_Login0_HTTP_Handler(srv)) r.POST("/loginSendSms", _User_LoginSendSms0_HTTP_Handler(srv)) + r.POST("/logout", _User_Logout0_HTTP_Handler(srv)) + r.POST("/updatePassword", _User_UpdatePassword0_HTTP_Handler(srv)) } func _User_SendSms0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) error { @@ -127,11 +157,81 @@ func _User_LoginSendSms0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) } } +func _User_Logout0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in LogoutRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationUserLogout) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.Logout(ctx, req.(*LogoutRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*LogoutReply) + return ctx.Result(200, reply) + } +} + +func _User_UpdatePassword0_HTTP_Handler(srv UserHTTPServer) func(ctx http.Context) error { + return func(ctx http.Context) error { + var in UpdatePasswordRequest + if err := ctx.Bind(&in); err != nil { + return err + } + if err := ctx.BindQuery(&in); err != nil { + return err + } + http.SetOperation(ctx, OperationUserUpdatePassword) + h := ctx.Middleware(func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.UpdatePassword(ctx, req.(*UpdatePasswordRequest)) + }) + out, err := h(ctx, &in) + if err != nil { + return err + } + reply := out.(*UpdatePasswordReply) + return ctx.Result(200, reply) + } +} + type UserHTTPClient interface { + // Login Login 用户登录(密码方式) + // 功能:用户通过手机号和密码进行登录 + // 返回登录状态和访问令牌 + // HTTP路径:POST /login + // 请求体:LoginRequest + // 响应:LoginReply Login(ctx context.Context, req *LoginRequest, opts ...http.CallOption) (rsp *LoginReply, err error) + // LoginSendSms LoginSendSms 用户登录(验证码方式) + // 功能:用户通过手机号和短信验证码进行登录 + // 提供更便捷的登录方式 + // HTTP路径:POST /loginSendSms + // 请求体:LoginSendSmsRequest + // 响应:LoginSendSmsReply LoginSendSms(ctx context.Context, req *LoginSendSmsRequest, opts ...http.CallOption) (rsp *LoginSendSmsReply, err error) + Logout(ctx context.Context, req *LogoutRequest, opts ...http.CallOption) (rsp *LogoutReply, err error) + // Register Register 用户注册 + // 功能:用户通过手机号、密码和验证码进行注册 + // 这是新用户进入系统的第一步 + // HTTP路径:POST /register + // 请求体:RegisterRequest + // 响应:RegisterReply Register(ctx context.Context, req *RegisterRequest, opts ...http.CallOption) (rsp *RegisterReply, err error) + // SendSms SendSms 发送短信验证码 + // 功能:向指定手机号发送验证码短信 + // 用于注册、登录等需要验证身份的场景 + // HTTP路径:POST /SendSms + // 请求体:SendSmsRequest + // 响应:SendSmsReply SendSms(ctx context.Context, req *SendSmsRequest, opts ...http.CallOption) (rsp *SendSmsReply, err error) + UpdatePassword(ctx context.Context, req *UpdatePasswordRequest, opts ...http.CallOption) (rsp *UpdatePasswordReply, err error) } type UserHTTPClientImpl struct { @@ -142,6 +242,12 @@ func NewUserHTTPClient(client *http.Client) UserHTTPClient { return &UserHTTPClientImpl{client} } +// Login Login 用户登录(密码方式) +// 功能:用户通过手机号和密码进行登录 +// 返回登录状态和访问令牌 +// HTTP路径:POST /login +// 请求体:LoginRequest +// 响应:LoginReply func (c *UserHTTPClientImpl) Login(ctx context.Context, in *LoginRequest, opts ...http.CallOption) (*LoginReply, error) { var out LoginReply pattern := "/login" @@ -155,6 +261,12 @@ func (c *UserHTTPClientImpl) Login(ctx context.Context, in *LoginRequest, opts . return &out, nil } +// LoginSendSms LoginSendSms 用户登录(验证码方式) +// 功能:用户通过手机号和短信验证码进行登录 +// 提供更便捷的登录方式 +// HTTP路径:POST /loginSendSms +// 请求体:LoginSendSmsRequest +// 响应:LoginSendSmsReply func (c *UserHTTPClientImpl) LoginSendSms(ctx context.Context, in *LoginSendSmsRequest, opts ...http.CallOption) (*LoginSendSmsReply, error) { var out LoginSendSmsReply pattern := "/loginSendSms" @@ -168,6 +280,25 @@ func (c *UserHTTPClientImpl) LoginSendSms(ctx context.Context, in *LoginSendSmsR return &out, nil } +func (c *UserHTTPClientImpl) Logout(ctx context.Context, in *LogoutRequest, opts ...http.CallOption) (*LogoutReply, error) { + var out LogoutReply + pattern := "/logout" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationUserLogout)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} + +// Register Register 用户注册 +// 功能:用户通过手机号、密码和验证码进行注册 +// 这是新用户进入系统的第一步 +// HTTP路径:POST /register +// 请求体:RegisterRequest +// 响应:RegisterReply func (c *UserHTTPClientImpl) Register(ctx context.Context, in *RegisterRequest, opts ...http.CallOption) (*RegisterReply, error) { var out RegisterReply pattern := "/register" @@ -181,6 +312,12 @@ func (c *UserHTTPClientImpl) Register(ctx context.Context, in *RegisterRequest, return &out, nil } +// SendSms SendSms 发送短信验证码 +// 功能:向指定手机号发送验证码短信 +// 用于注册、登录等需要验证身份的场景 +// HTTP路径:POST /SendSms +// 请求体:SendSmsRequest +// 响应:SendSmsReply func (c *UserHTTPClientImpl) SendSms(ctx context.Context, in *SendSmsRequest, opts ...http.CallOption) (*SendSmsReply, error) { var out SendSmsReply pattern := "/SendSms" @@ -193,3 +330,16 @@ func (c *UserHTTPClientImpl) SendSms(ctx context.Context, in *SendSmsRequest, op } return &out, nil } + +func (c *UserHTTPClientImpl) UpdatePassword(ctx context.Context, in *UpdatePasswordRequest, opts ...http.CallOption) (*UpdatePasswordReply, error) { + var out UpdatePasswordReply + pattern := "/updatePassword" + path := binding.EncodeURL(pattern, in, false) + opts = append(opts, http.Operation(OperationUserUpdatePassword)) + opts = append(opts, http.PathTemplate(pattern)) + err := c.cc.Invoke(ctx, "POST", path, in, &out, opts...) + if err != nil { + return nil, err + } + return &out, nil +} diff --git a/cabinet b/cabinet deleted file mode 100644 index 64d3490de8f6c232363853de5c8148ee6f2b86d4..0000000000000000000000000000000000000000 Binary files a/cabinet and /dev/null differ diff --git a/check_service.sh b/check_service.sh new file mode 100755 index 0000000000000000000000000000000000000000..f9c05f25adb8f21d7944f30e80b6889fd4a43b0c --- /dev/null +++ b/check_service.sh @@ -0,0 +1,220 @@ +#!/bin/bash + +# Cabinet 服务诊断工具 +# 用于排查 502 错误和服务状态问题 + +echo "==========================================" +echo " Cabinet 服务诊断工具" +echo "==========================================" +echo "" + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 检查结果统计 +PASS_COUNT=0 +FAIL_COUNT=0 + +# 检查函数 +check_pass() { + echo -e "${GREEN}✅ $1${NC}" + PASS_COUNT=$((PASS_COUNT + 1)) +} + +check_fail() { + echo -e "${RED}❌ $1${NC}" + FAIL_COUNT=$((FAIL_COUNT + 1)) +} + +check_warn() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +echo "开始诊断..." +echo "" + +# ==================== 1. 检查进程 ==================== +echo "【1/8】检查 Cabinet 进程..." +if ps aux | grep -v grep | grep Cabinet > /dev/null; then + check_pass "Cabinet 进程正在运行" + ps aux | grep -v grep | grep Cabinet | awk '{print " PID: " $2 ", CPU: " $3 "%, MEM: " $4 "%"}' +else + check_fail "Cabinet 进程未运行" + echo " 💡 启动命令: cd /Users/youkelili/gowork/src/cabinet/cmd/Cabinet && go run ." +fi +echo "" + +# ==================== 2. 检查 HTTP 端口 ==================== +echo "【2/8】检查 HTTP 端口 8000..." +if lsof -i :8000 > /dev/null 2>&1; then + check_pass "端口 8000 正在监听" + lsof -i :8000 | grep LISTEN | awk '{print " 进程: " $1 ", PID: " $2}' +else + check_fail "端口 8000 未被监听" + echo " 💡 检查是否有其他服务占用该端口" +fi +echo "" + +# ==================== 3. 检查 gRPC 端口 ==================== +echo "【3/8】检查 gRPC 端口 9000..." +if lsof -i :9000 > /dev/null 2>&1; then + check_pass "端口 9000 正在监听" +else + check_fail "端口 9000 未被监听" +fi +echo "" + +# ==================== 4. 测试 localhost 连接 ==================== +echo "【4/8】测试 localhost:8000 连接..." +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000 2>/dev/null) +if [ "$HTTP_CODE" != "000" ] && [ "$HTTP_CODE" != "" ]; then + check_pass "HTTP 服务响应 (状态码: $HTTP_CODE)" +else + check_fail "无法连接到 localhost:8000" + echo " 💡 服务可能未启动或端口配置错误" +fi +echo "" + +# ==================== 5. 测试 127.0.0.1 连接 ==================== +echo "【5/8】测试 127.0.0.1:8000 连接..." +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000 2>/dev/null) +if [ "$HTTP_CODE" != "000" ] && [ "$HTTP_CODE" != "" ]; then + check_pass "HTTP 服务响应 (状态码: $HTTP_CODE)" +else + check_fail "无法连接到 127.0.0.1:8000" +fi +echo "" + +# ==================== 6. 检查数据库连接 ==================== +echo "【6/8】检查数据库连接..." +DB_HOST="14.103.152.191" +if ping -c 1 -W 2 $DB_HOST > /dev/null 2>&1; then + check_pass "可以 ping 通数据库服务器 $DB_HOST" +else + check_fail "无法 ping 通数据库服务器 $DB_HOST" + echo " 💡 检查网络连接或数据库服务器状态" +fi +echo "" + +# ==================== 7. 测试实际接口 ==================== +echo "【7/8】测试快递员派件列表接口..." +RESPONSE=$(curl -s http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001 2>&1) +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001 2>/dev/null) + +if [ "$HTTP_CODE" = "200" ]; then + check_pass "接口响应正常 (HTTP 200)" + # 尝试解析 JSON 获取包裹数量 + TOTAL_PARCELS=$(echo "$RESPONSE" | grep -o '"total_parcels":[0-9]*' | grep -o '[0-9]*') + if [ -n "$TOTAL_PARCELS" ]; then + echo " 📦 查询到 $TOTAL_PARCELS 个包裹" + fi +elif [ "$HTTP_CODE" = "404" ]; then + check_warn "接口返回 404 (路由可能不存在)" + echo " 💡 检查 proto 文件是否正确生成" +elif [ "$HTTP_CODE" = "500" ]; then + check_warn "接口返回 500 (服务器内部错误)" + echo " 💡 查看服务日志获取详细错误信息" +elif [ "$HTTP_CODE" = "000" ] || [ -z "$HTTP_CODE" ]; then + check_fail "无法连接到接口" + echo " 💡 确认服务已启动且端口正确" +else + check_warn "接口返回异常状态码: $HTTP_CODE" +fi +echo "" + +# ==================== 8. 检查配置文件 ==================== +echo "【8/8】检查配置文件..." +CONFIG_FILE="/Users/youkelili/gowork/src/cabinet/configs/config.yaml" +if [ -f "$CONFIG_FILE" ]; then + check_pass "配置文件存在: $CONFIG_FILE" + echo " HTTP 地址: $(grep -A 1 'http:' $CONFIG_FILE | grep 'addr:' | awk '{print $2}')" + echo " gRPC 地址: $(grep -A 1 'grpc:' $CONFIG_FILE | grep 'addr:' | awk '{print $2}')" +else + check_fail "配置文件不存在: $CONFIG_FILE" +fi +echo "" + +# ==================== 诊断总结 ==================== +echo "==========================================" +echo " 诊断总结" +echo "==========================================" +echo "" +echo "通过: $PASS_COUNT 项" +echo "失败: $FAIL_COUNT 项" +echo "" + +# ==================== 给出建议 ==================== +if [ $FAIL_COUNT -eq 0 ]; then + echo -e "${GREEN}🎉 所有检查都通过!${NC}" + echo "" + echo "如果 ApiPost 仍然返回 502,请检查:" + echo "" + echo "1️⃣ ApiPost 请求地址配置:" + echo " ❌ 错误: http://0.0.0.0:8000/v1/cabinet/courier/parcels" + echo " ✅ 正确: http://localhost:8000/v1/cabinet/courier/parcels" + echo " ✅ 正确: http://127.0.0.1:8000/v1/cabinet/courier/parcels" + echo "" + echo "2️⃣ ApiPost 代理设置:" + echo " 打开 ApiPost -> 设置 -> 代理" + echo " 确保选择「不使用代理」或「系统代理」" + echo "" + echo "3️⃣ ApiPost 超时设置:" + echo " 建议设置为 30000ms (30秒) 以上" + echo "" + echo "4️⃣ 防火墙设置:" + echo " 系统偏好设置 -> 安全性与隐私 -> 防火墙" + echo " 确保允许 ApiPost 的网络连接" + echo "" +else + echo -e "${RED}❌ 发现 $FAIL_COUNT 个问题${NC}" + echo "" + echo "建议操作:" + echo "" + + # 如果服务未运行 + if ! ps aux | grep -v grep | grep Cabinet > /dev/null; then + echo "1. 启动服务:" + echo " cd /Users/youkelili/gowork/src/cabinet/cmd/Cabinet" + echo " go run ." + echo "" + fi + + # 如果端口未监听 + if ! lsof -i :8000 > /dev/null 2>&1; then + echo "2. 检查端口占用:" + echo " lsof -i :8000" + echo " 如果端口被占用,使用: kill -9 " + echo "" + fi + + # 如果数据库连接失败 + if ! ping -c 1 -W 2 $DB_HOST > /dev/null 2>&1; then + echo "3. 检查数据库连接:" + echo " ping $DB_HOST" + echo " mysql -h $DB_HOST -u root -p" + echo "" + fi +fi + +# ==================== 快速测试命令 ==================== +echo "==========================================" +echo " 快速测试命令" +echo "==========================================" +echo "" +echo "# 使用 curl 测试:" +echo "curl \"http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=-1\"" +echo "" +echo "# 查看服务日志:" +echo "cd /Users/youkelili/gowork/src/cabinet/cmd/Cabinet && go run ." +echo "" +echo "# 重启服务:" +echo "pkill -9 Cabinet && cd /Users/youkelili/gowork/src/cabinet/cmd/Cabinet && go run . &" +echo "" + +echo "==========================================" +echo " 诊断完成" +echo "==========================================" + diff --git a/check_table.go b/check_table.go deleted file mode 100644 index 9eb8eb4bbcdffd7e5c5754456ac82f8003ae55fa..0000000000000000000000000000000000000000 --- a/check_table.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "gorm.io/driver/mysql" - "gorm.io/gorm" -) - -func main() { - dsn := "root:39bf5f3ffe6b4447d254e448e21bf26d@tcp(14.103.152.191:3306)/run?parseTime=True&loc=Local" - db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) - if err != nil { - log.Fatal("Failed to connect to database:", err) - } - - fmt.Println("Database connected successfully!") - - // 检查表结构 - rows, err := db.Raw("DESCRIBE cabinet").Rows() - if err != nil { - log.Fatal("Failed to describe table:", err) - } - defer rows.Close() - - fmt.Println("Table structure:") - fmt.Println("Field\t\t\tType\t\t\tNull\tKey\tDefault\tExtra") - fmt.Println("-----\t\t\t----\t\t\t----\t---\t-------\t-----") - - for rows.Next() { - var field, typ, null, key, defaultVal, extra string - rows.Scan(&field, &typ, &null, &key, &defaultVal, &extra) - fmt.Printf("%s\t\t\t%s\t\t\t%s\t%s\t%s\t%s\n", field, typ, null, key, defaultVal, extra) - } - - // 检查数据 - var count int64 - db.Table("cabinet").Count(&count) - fmt.Printf("\nTotal records in cabinet table: %d\n", count) - - if count > 0 { - // 显示前几条记录 - rows, err := db.Raw("SELECT * FROM cabinet LIMIT 5").Rows() - if err != nil { - log.Fatal("Failed to query data:", err) - } - defer rows.Close() - - fmt.Println("\nFirst 5 records:") - columns, _ := rows.Columns() - fmt.Println(columns) - - for rows.Next() { - values := make([]interface{}, len(columns)) - valuePtrs := make([]interface{}, len(columns)) - for i := range values { - valuePtrs[i] = &values[i] - } - - rows.Scan(valuePtrs...) - - for i, val := range values { - if val != nil { - fmt.Printf("%v: %v\t", columns[i], val) - } else { - fmt.Printf("%v: NULL\t", columns[i]) - } - } - fmt.Println() - } - } -} diff --git a/cmd/Cabinet/main.go b/cmd/Cabinet/main.go index 5761afe13288f4d5fc644a7cb398e10aeb54e704..b2a8d517f9fe2447148ecf8a142f8a54d4b2e3c1 100644 --- a/cmd/Cabinet/main.go +++ b/cmd/Cabinet/main.go @@ -1,14 +1,12 @@ package main import ( - // 标准库 "flag" "os" + "sync" - // 内部依赖 - "Cabinet/internal/conf" + "cabinet/internal/conf" - // Kratos框架相关 "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/config" "github.com/go-kratos/kratos/v2/config/file" @@ -16,8 +14,8 @@ import ( "github.com/go-kratos/kratos/v2/middleware/tracing" "github.com/go-kratos/kratos/v2/transport/grpc" "github.com/go-kratos/kratos/v2/transport/http" + "github.com/gorilla/websocket" - // 自动设置GOMAXPROCS为CPU核心数 _ "go.uber.org/automaxprocs" ) @@ -38,11 +36,8 @@ var ( // init 初始化函数 // 在main函数执行前自动调用,用于初始化命令行参数 func init() { - // 定义配置文件路径参数 - // 参数名: conf - // 默认值: "../../configs" - // 描述: 配置文件路径,例如: -conf config.yaml - flag.StringVar(&flagconf, "conf", "../../configs", "config path, eg: -conf config.yaml") + // 支持从不同目录运行的配置文件路径 + flag.StringVar(&flagconf, "conf", "configs/config.yaml", "config path, eg: -conf config.yaml") } // newApp 创建Kratos应用实例 @@ -51,15 +46,16 @@ func init() { // - logger: 日志记录器 // - gs: gRPC服务器 // - hs: HTTP服务器 +// // 返回值: 配置好的Kratos应用实例 func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server) *kratos.App { // 创建并返回Kratos应用实例 return kratos.New( - kratos.ID(id), // 设置应用ID - kratos.Name(Name), // 设置应用名称 - kratos.Version(Version), // 设置应用版本 + kratos.ID(id), // 设置应用ID + kratos.Name(Name), // 设置应用名称 + kratos.Version(Version), // 设置应用版本 kratos.Metadata(map[string]string{}), // 设置应用元数据 - kratos.Logger(logger), // 设置应用日志记录器 + kratos.Logger(logger), // 设置应用日志记录器 kratos.Server( gs, // 注册gRPC服务器 hs, // 注册HTTP服务器 @@ -67,25 +63,101 @@ func newApp(logger log.Logger, gs *grpc.Server, hs *http.Server) *kratos.App { ) } +// WebSocket 升级器配置 +var upgrader = websocket.Upgrader{ + // 检查Origin,生产环境应该严格检查,这里为了演示允许所有 + CheckOrigin: func(r *http.Request) bool { return true }, +} + +// WebSocketManager 管理所有WebSocket连接 +type WebSocketManager struct { + clients map[*websocket.Conn]bool // 存储所有客户端连接 + clientsMux sync.RWMutex // 保护clients的读写锁 +} + +// NewWebSocketManager 创建WebSocket管理器 +func NewWebSocketManager() *WebSocketManager { + return &WebSocketManager{ + clients: make(map[*websocket.Conn]bool), + } +} + +// HandleWebSocket 处理WebSocket连接请求 +func (m *WebSocketManager) HandleWebSocket(ctx http.Context) error { + req := ctx.Request() + resp := ctx.Response() + + // 1. 将HTTP连接升级为WebSocket连接 + conn, err := upgrader.Upgrade(resp, req, nil) + if err != nil { + return err + } + defer conn.Close() // 确保连接最终关闭 + + // 2. 注册客户端到管理器 + m.clientsMux.Lock() + m.clients[conn] = true + m.clientsMux.Unlock() + + // 3. 连接断开时自动清理 + defer func() { + m.clientsMux.Lock() + delete(m.clients, conn) + m.clientsMux.Unlock() + }() + + // 4. 消息处理循环 + for { + // 读取客户端发送的消息 + messageType, message, err := conn.ReadMessage() + if err != nil { + // 如果读取出错(通常是客户端断开),退出循环 + break + } + + // 5. 简单回声:将收到的消息原样返回给客户端 + err = conn.WriteMessage(messageType, message) + if err != nil { + break + } + } + + return nil +} + // main 应用程序入口函数 // 负责启动整个应用,包括配置加载、依赖注入和服务启动 func main() { // 解析命令行参数 flag.Parse() - + // 创建日志记录器 // 使用标准输出作为日志输出目标,并添加各种日志字段 logger := log.With(log.NewStdLogger(os.Stdout), - "ts", log.DefaultTimestamp, // 时间戳 - "caller", log.DefaultCaller, // 调用者信息 - "service.id", id, // 服务ID - "service.name", Name, // 服务名称 - "service.version", Version, // 服务版本 + "ts", log.DefaultTimestamp, // 时间戳 + "caller", log.DefaultCaller, // 调用者信息 + "service.id", id, // 服务ID + "service.name", Name, // 服务名称 + "service.version", Version, // 服务版本 "trace.id", tracing.TraceID(), // 跟踪ID - "span.id", tracing.SpanID(), // 跨度ID + "span.id", tracing.SpanID(), // 跨度ID ) - - // 创建配置管理器 + // 智能查找配置文件:先尝试当前路径,再尝试上级目录 + if _, err := os.Stat(flagconf); os.IsNotExist(err) { + // 尝试上级目录的配置文件 + parentConf := "../" + flagconf + if _, err := os.Stat(parentConf); err == nil { + flagconf = parentConf + } else { + // 再尝试上上级目录(从cmd/Cabinet运行时) + grandparentConf := "../../" + flagconf + if _, err := os.Stat(grandparentConf); err == nil { + flagconf = grandparentConf + } + } + } + + // 在确定flagconf后再创建配置管理器 c := config.New( config.WithSource( file.NewSource(flagconf), // 使用文件作为配置源 @@ -107,9 +179,15 @@ func main() { panic(err) } + // Load Alipay config section separately + var ac conf.Alipay + if err := c.Value("alipay").Scan(&ac); err != nil { + panic(err) + } + // 使用 wire 进行依赖注入,构建应用实例 // wireApp 函数由 wire 工具自动生成,在 wire_gen.go 文件中定义 - app, cleanup, err := wireApp(bc.Server, bc.Data, logger) + app, cleanup, err := wireApp(bc.Server, bc.Data, &ac, logger) if err != nil { // 依赖注入失败时直接 panic panic(err) @@ -123,4 +201,29 @@ func main() { // 应用运行出错时直接 panic panic(err) } + + //// 创建WebSocket管理器 + //wsManager := NewWebSocketManager() + // + //// 创建HTTP服务器,监听8000端口 + //httpSrv := http.NewServer(http.Address(":8000")) + // + //// 注册WebSocket路由 + //// GET /ws 路径用于WebSocket连接 + //httpSrv.Route("GET").Path("/ws").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // // 创建Kratos上下文并处理WebSocket连接 + // ctx := http.NewContext(r.Context(), w, r) + // _ = wsManager.HandleWebSocket(ctx) + //}) + + //// 创建Kratos应用 + //app := kratos.New( + // kratos.Name("simple-websocket"), // 应用名称 + // kratos.Server(httpSrv), // 注册HTTP服务器 + //) + // + //// 启动应用 + //if err := app.Run(); err != nil { + // panic(err) + //} } diff --git a/cmd/Cabinet/wire.go b/cmd/Cabinet/wire.go index c8659ad54f26afba403b8d7c45fd9cf7c6f9d061..4d8c2f6daaca9f0aecaa433a9aefd830a3924312 100644 --- a/cmd/Cabinet/wire.go +++ b/cmd/Cabinet/wire.go @@ -6,28 +6,41 @@ package main import ( - "Cabinet/internal/biz" - "Cabinet/internal/conf" - "Cabinet/internal/data" - "Cabinet/internal/server" - "Cabinet/internal/service" + "cabinet/internal/biz" + "cabinet/internal/conf" + "cabinet/internal/data" + "cabinet/internal/server" + "cabinet/internal/service" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/log" "github.com/google/wire" ) +// 这些变量已经在main.go中定义,这里不需要重复定义 + // wireApp init kratos application. -func wireApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) { - panic(wire.Build(ProviderSet)) +// 注意:参数名不能与导入的包名冲突 +func wireApp(serverConf *conf.Server, dataConf *conf.Data, alipayConf *conf.Alipay, logger log.Logger) (*kratos.App, func(), error) { + panic(wire.Build( + server.NewGRPCServer, + server.NewHTTPServer, + service.NewGreeterService, + service.NewUserService, + service.NewHelpService, + service.NewCabinetService, + biz.NewGreeterUsecase, + biz.NewUserUsecase, + biz.NewHelpArticleUsecase, + biz.NewCabinetUsecase, + data.NewData, + data.Redis, + data.NewGreeterRepo, + data.NewUserRepo, + data.NewHelpArticleRepo, + data.NewCabinetRepo, + newApp, + )) } -// ProviderSet is providers set. -var ProviderSet = wire.NewSet( - server.NewGRPCServer, - server.NewHTTPServer, - data.ProviderSet, - biz.ProviderSet, - service.ProviderSet, - newApp, -) +// newApp函数已经在main.go中定义,这里不需要重复定义 diff --git a/cmd/Cabinet/wire_gen.go b/cmd/Cabinet/wire_gen.go index a0436c28fff2daa44184a7fa59ca7c07a3a5e83f..b1b3ee4817bd9b488d0b73eeafbbaf162cad378b 100644 --- a/cmd/Cabinet/wire_gen.go +++ b/cmd/Cabinet/wire_gen.go @@ -7,14 +7,13 @@ package main import ( - "Cabinet/internal/biz" - "Cabinet/internal/conf" - "Cabinet/internal/data" - "Cabinet/internal/server" - "Cabinet/internal/service" + "cabinet/internal/biz" + "cabinet/internal/conf" + "cabinet/internal/data" + "cabinet/internal/server" + "cabinet/internal/service" "github.com/go-kratos/kratos/v2" "github.com/go-kratos/kratos/v2/log" - "github.com/google/wire" ) import ( @@ -24,29 +23,34 @@ import ( // Injectors from wire.go: // wireApp init kratos application. -func wireApp(confServer *conf.Server, confData *conf.Data, logger log.Logger) (*kratos.App, func(), error) { - dataData, cleanup, err := data.NewData(confData, logger) +// 注意:参数名不能与导入的包名冲突 +func wireApp(serverConf *conf.Server, dataConf *conf.Data, alipayConf *conf.Alipay, logger log.Logger) (*kratos.App, func(), error) { + client, cleanup, err := data.Redis(dataConf, logger) if err != nil { return nil, nil, err } + dataData, cleanup2, err := data.NewData(dataConf, logger, client) + if err != nil { + cleanup() + return nil, nil, err + } greeterRepo := data.NewGreeterRepo(dataData, logger) greeterUsecase := biz.NewGreeterUsecase(greeterRepo, logger) greeterService := service.NewGreeterService(greeterUsecase) userRepo := data.NewUserRepo(dataData, logger) userUsecase := biz.NewUserUsecase(userRepo) userService := service.NewUserService(userUsecase, dataData, logger) + helpArticleRepo := data.NewHelpArticleRepo(dataData) + helpArticleUsecase := biz.NewHelpArticleUsecase(helpArticleRepo, logger) + helpService := service.NewHelpService(helpArticleUsecase, logger) cabinetRepo := data.NewCabinetRepo(dataData, logger) cabinetUsecase := biz.NewCabinetUsecase(cabinetRepo) cabinetService := service.NewCabinetService(cabinetUsecase, logger) - grpcServer := server.NewGRPCServer(confServer, greeterService, userService, cabinetService, logger) - httpServer := server.NewHTTPServer(confServer, greeterService, userService, cabinetService, logger) + grpcServer := server.NewGRPCServer(serverConf, greeterService, userService, helpService, cabinetService, logger) + httpServer := server.NewHTTPServer(serverConf, greeterService, userService, helpService, cabinetService, logger) app := newApp(logger, grpcServer, httpServer) return app, func() { + cleanup2() cleanup() }, nil } - -// wire.go: - -// ProviderSet is providers set. -var ProviderSet = wire.NewSet(server.NewGRPCServer, server.NewHTTPServer, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp) diff --git a/cmd/test_real.go b/cmd/test_real.go new file mode 100644 index 0000000000000000000000000000000000000000..8de88b7220c314a7e2643e1dce19da8be542ef7d --- /dev/null +++ b/cmd/test_real.go @@ -0,0 +1,66 @@ +package main + +import ( + "cabinet/internal/biz" + "cabinet/internal/data" + "cabinet/internal/conf" + "context" + "fmt" + "log" + + "github.com/go-kratos/kratos/v2/config" + "github.com/go-kratos/kratos/v2/config/file" + "github.com/redis/go-redis/v9" +) + +func main() { + // 加载配置 + c := config.New( + config.WithSource( + file.NewSource("../configs/config.yaml"), + ), + ) + defer c.Close() + + if err := c.Load(); err != nil { + log.Fatalf("加载配置失败: %v", err) + } + + var bc conf.Bootstrap + if err := c.Scan(&bc); err != nil { + log.Fatalf("解析配置失败: %v", err) + } + + // 初始化数据库连接 + redisClient := redis.NewClient(&redis.Options{ + Addr: bc.Data.Redis.Addr, + Password: "39bf5f3ffe6b4447d254e448e21bf26d", + DB: 0, + }) + + dataInstance, cleanup, err := data.NewData(bc.Data, nil, redisClient) + if err != nil { + log.Fatalf("初始化数据层失败: %v", err) + } + defer cleanup() + + // 创建仓库和用例 + realRepo := data.NewRealRepo(dataInstance, nil) + realUsecase := biz.NewRealUsecase(realRepo) + + // 测试实名认证 + ctx := context.Background() + req := &biz.RealNameRequest{ + Card: "110101199001011234", // 测试身份证号 + Name: "张三", // 测试姓名 + UserId: 123, // 测试用户ID + } + + fmt.Println("开始测试实名认证...") + _, err = realUsecase.RealName(ctx, req) + if err != nil { + fmt.Printf("实名认证失败: %v\n", err) + } else { + fmt.Println("实名认证成功!") + } +} \ No newline at end of file diff --git a/configs/config.yaml b/configs/config.yaml index aed8fb3701e81e0c6185af8b937e8475016cc839..73be924b2e184840392587949c2c0008ce00a56f 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -1,9 +1,9 @@ server: http: - addr: 0.0.0.0:8000 + addr: 0.0.0.0:8001 timeout: 1s grpc: - addr: 0.0.0.0:9000 + addr: 0.0.0.0:9002 timeout: 1s data: database: @@ -13,3 +13,13 @@ data: addr: 14.103.152.191:6379 read_timeout: 0.2s write_timeout: 0.2s + mqtt: + broker: "tcp://localhost:1883" # MQTT Broker地址(Docker容器端口) + client_id: "cabinet-service" # 客户端ID + username: "" # 用户名(可选) + password: "" # 密码(可选) + qos: 1 # 服务质量等级(0, 1, 2) +alipay: + app_id: "" # 支付宝应用ID + public_key: "" # 支付宝公钥 + skip_verify: false # 是否跳过验证(测试环境可设为true) diff --git "a/docs/502\351\224\231\350\257\257\346\216\222\346\237\245\346\214\207\345\215\227.md" "b/docs/502\351\224\231\350\257\257\346\216\222\346\237\245\346\214\207\345\215\227.md" new file mode 100644 index 0000000000000000000000000000000000000000..e32f8fb8905776050c5f9343483e8cc9ff01d769 --- /dev/null +++ "b/docs/502\351\224\231\350\257\257\346\216\222\346\237\245\346\214\207\345\215\227.md" @@ -0,0 +1,386 @@ +# 502 错误排查指南 + +## 问题描述 + +在 ApiPost 上测试接口时出现 502 Bad Gateway 错误,但在别人的电脑上可以正常工作。 + +## 快速诊断步骤 + +### 第一步:检查服务是否启动 + +```bash +# 方法1:检查进程 +ps aux | grep Cabinet + +# 方法2:检查端口是否被监听 +lsof -i :8000 +# 或者 +netstat -an | grep 8000 + +# 方法3:使用本地curl测试 +curl http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001 +``` + +**预期结果**: +- ✅ 能看到 Cabinet 进程 +- ✅ 端口 8000 正在被监听 +- ✅ curl 能够返回正常数据 + +### 第二步:检查 ApiPost 配置 + +1. **请求地址检查** + ``` + 错误示例:http://0.0.0.0:8000/xxx + 正确示例:http://localhost:8000/xxx + 或:http://127.0.0.1:8000/xxx + ``` + +2. **代理设置检查** + - 打开 ApiPost 设置 + - 检查是否启用了代理 + - 如果启用了代理,尝试关闭 + +3. **超时设置检查** + - 增加请求超时时间(建议 30 秒以上) + +### 第三步:启动服务并查看日志 + +```bash +# 进入项目目录 +cd /Users/youkelili/gowork/src/cabinet/cmd/Cabinet + +# 启动服务(查看完整日志) +go run . +``` + +**观察启动日志**: +``` +✅ 正常启动日志示例: +[INFO] server listening on: [::]:8000 +[INFO] server listening on: [::]:9000 + +❌ 异常日志示例: +panic: dial tcp 14.103.152.191:3306: connect: connection refused +panic: bind: address already in use +``` + +## 常见问题及解决方案 + +### 问题1:端口被占用 + +**症状**: +``` +panic: listen tcp :8000: bind: address already in use +``` + +**解决方案**: + +```bash +# 查找占用端口的进程 +lsof -i :8000 +# 或 +netstat -an | grep 8000 + +# 杀死占用端口的进程 +kill -9 + +# 或者修改配置文件使用其他端口 +# 编辑 configs/config.yaml +server: + http: + addr: 0.0.0.0:8001 # 改为 8001 +``` + +### 问题2:数据库连接失败 + +**症状**: +``` +panic: dial tcp 14.103.152.191:3306: i/o timeout +panic: Access denied for user 'root'@'xxx' +``` + +**解决方案**: + +```bash +# 测试数据库连接 +mysql -h 14.103.152.191 -P 3306 -u root -p + +# 如果连接失败,检查: +# 1. 网络是否通畅 +ping 14.103.152.191 + +# 2. 防火墙是否阻止 +# 3. 数据库用户权限是否正确 +``` + +### 问题3:ApiPost 使用了 0.0.0.0 + +**症状**: +- 服务已启动 +- curl localhost:8000 可以访问 +- ApiPost 请求 `http://0.0.0.0:8000` 返回 502 + +**原因**: +`0.0.0.0` 是一个特殊地址,表示"监听所有网络接口",但不能作为客户端请求地址。 + +**解决方案**: + +在 ApiPost 中修改请求地址: +``` +错误:http://0.0.0.0:8000/v1/cabinet/courier/parcels +正确:http://localhost:8000/v1/cabinet/courier/parcels +或: http://127.0.0.1:8000/v1/cabinet/courier/parcels +``` + +### 问题4:防火墙阻止 + +**症状**: +- 服务启动正常 +- curl 可以访问 +- ApiPost 无法访问 + +**解决方案**(macOS): + +```bash +# 检查防火墙状态 +sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate + +# 临时关闭防火墙测试 +sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off + +# 如果关闭防火墙后正常,添加应用到白名单 +# 系统偏好设置 -> 安全性与隐私 -> 防火墙 -> 防火墙选项 +# 添加 ApiPost 到允许列表 +``` + +### 问题5:ApiPost 代理配置问题 + +**症状**: +- 其他工具(curl、Postman)可以访问 +- 只有 ApiPost 返回 502 + +**解决方案**: + +1. 打开 ApiPost 设置 +2. 找到"代理设置" +3. 选择"不使用代理"或"系统代理" +4. 重启 ApiPost + +### 问题6:服务没有完全启动 + +**症状**: +- 进程存在但无法访问 +- 日志显示某些服务启动失败 + +**解决方案**: + +```bash +# 完全停止服务 +pkill -9 Cabinet + +# 清理可能的资源 +rm -rf /tmp/cabinet-* + +# 重新启动 +cd /Users/youkelili/gowork/src/cabinet/cmd/Cabinet +go run . +``` + +## 完整的诊断脚本 + +创建一个诊断脚本 `check_service.sh`: + +```bash +#!/bin/bash + +echo "========== Cabinet 服务诊断工具 ==========" +echo "" + +# 1. 检查进程 +echo "1. 检查进程..." +if ps aux | grep -v grep | grep Cabinet > /dev/null; then + echo "✅ Cabinet 进程正在运行" + ps aux | grep -v grep | grep Cabinet +else + echo "❌ Cabinet 进程未运行" +fi +echo "" + +# 2. 检查端口 +echo "2. 检查端口 8000..." +if lsof -i :8000 > /dev/null 2>&1; then + echo "✅ 端口 8000 正在监听" + lsof -i :8000 +else + echo "❌ 端口 8000 未被监听" +fi +echo "" + +# 3. 检查端口 9000 (gRPC) +echo "3. 检查端口 9000 (gRPC)..." +if lsof -i :9000 > /dev/null 2>&1; then + echo "✅ 端口 9000 正在监听" + lsof -i :9000 +else + echo "❌ 端口 9000 未被监听" +fi +echo "" + +# 4. 测试本地连接 +echo "4. 测试本地 HTTP 连接..." +if curl -s -o /dev/null -w "%{http_code}" http://localhost:8000 > /tmp/http_code 2>&1; then + HTTP_CODE=$(cat /tmp/http_code) + if [ "$HTTP_CODE" != "000" ]; then + echo "✅ HTTP 服务响应正常 (HTTP $HTTP_CODE)" + else + echo "❌ HTTP 服务无响应" + fi +else + echo "❌ 无法连接到 HTTP 服务" +fi +rm -f /tmp/http_code +echo "" + +# 5. 检查数据库连接 +echo "5. 检查数据库连接..." +if ping -c 1 14.103.152.191 > /dev/null 2>&1; then + echo "✅ 可以 ping 通数据库服务器" +else + echo "❌ 无法 ping 通数据库服务器" +fi +echo "" + +# 6. 测试实际接口 +echo "6. 测试快递员派件列表接口..." +RESPONSE=$(curl -s -w "\n%{http_code}" http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001 2>&1) +HTTP_CODE=$(echo "$RESPONSE" | tail -n 1) +BODY=$(echo "$RESPONSE" | head -n -1) + +if [ "$HTTP_CODE" = "200" ]; then + echo "✅ 接口响应正常 (HTTP 200)" + echo "响应数据: $BODY" | head -c 200 + echo "..." +elif [ "$HTTP_CODE" = "000" ]; then + echo "❌ 无法连接到服务" +else + echo "⚠️ 接口返回异常 (HTTP $HTTP_CODE)" + echo "响应: $BODY" +fi +echo "" + +# 7. 建议 +echo "========== 诊断建议 ==========" +if ps aux | grep -v grep | grep Cabinet > /dev/null && lsof -i :8000 > /dev/null 2>&1; then + echo "✅ 服务运行正常" + echo "" + echo "如果 ApiPost 仍然返回 502,请检查:" + echo "1. ApiPost 中的请求地址是否使用 localhost 或 127.0.0.1,而不是 0.0.0.0" + echo "2. ApiPost 的代理设置" + echo "3. ApiPost 的超时设置" + echo "4. 防火墙设置" +else + echo "❌ 服务未正常运行,请先启动服务:" + echo "" + echo "cd /Users/youkelili/gowork/src/cabinet/cmd/Cabinet" + echo "go run ." +fi +echo "" +echo "========== 诊断完成 ==========" +``` + +使用方法: + +```bash +# 保存脚本 +chmod +x check_service.sh + +# 运行诊断 +./check_service.sh +``` + +## ApiPost 正确配置示例 + +### 1. 基本设置 + +| 配置项 | 值 | +|--------|-----| +| 请求方法 | GET | +| 请求地址 | `http://localhost:8000/v1/cabinet/courier/parcels` | +| 超时时间 | 30000ms (30秒) | + +### 2. 查询参数 + +| 参数名 | 值 | 说明 | +|--------|-----|------| +| courier_id | 1001 | 快递员ID | +| parcel_status | -1 | 包裹状态(-1查询所有) | + +### 3. 请求头(可选) + +| Header | 值 | +|--------|-----| +| Content-Type | application/json | +| Accept | application/json | + +## 对比测试 + +如果在别人电脑上可以工作,请对比以下配置: + +| 检查项 | 你的电脑 | 别人的电脑 | +|--------|----------|------------| +| ApiPost 版本 | ? | ? | +| 请求地址 | ? | ? | +| 代理设置 | ? | ? | +| 防火墙状态 | ? | ? | +| 操作系统 | macOS | ? | +| Go 版本 | ? | ? | +| 数据库连接 | ? | ? | + +## 最终检查清单 + +使用以下清单逐项检查: + +- [ ] 服务已启动(`ps aux | grep Cabinet`) +- [ ] 端口 8000 正在监听(`lsof -i :8000`) +- [ ] curl 本地测试正常(`curl http://localhost:8000`) +- [ ] ApiPost 请求地址使用 localhost 或 127.0.0.1 +- [ ] ApiPost 没有启用代理 +- [ ] ApiPost 超时时间足够长(>30秒) +- [ ] 防火墙允许连接 +- [ ] 数据库连接正常 +- [ ] 测试数据已导入 + +## 快速修复命令 + +```bash +# 一键重启服务 +pkill -9 Cabinet && cd /Users/youkelili/gowork/src/cabinet/cmd/Cabinet && go run . & + +# 等待 3 秒 +sleep 3 + +# 测试接口 +curl "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=-1" +``` + +## 需要帮助? + +如果以上步骤都无法解决问题,请提供: + +1. **服务启动日志**:完整的启动输出 +2. **ApiPost 错误截图**:包含请求配置和错误信息 +3. **诊断脚本输出**:运行 `check_service.sh` 的完整输出 +4. **环境信息**: + ```bash + # 操作系统版本 + sw_vers + + # Go 版本 + go version + + # 网络状态 + ifconfig | grep inet + ``` + +这样可以更准确地定位问题! + diff --git "a/docs/MQTT\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/docs/MQTT\344\275\277\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 0000000000000000000000000000000000000000..e2dd158056b639799a0cdc9120fc33975f5cc5d8 --- /dev/null +++ "b/docs/MQTT\344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1,108 @@ +# MQTT 使用指南 + +## ✅ 已完成的工作 + +1. ✅ 已安装 MQTT 依赖:`github.com/eclipse/paho.mqtt.golang` +2. ✅ 已配置 MQTT(在 `configs/config.yaml`) +3. ✅ 已在代码中集成 MQTT +4. ✅ MQTT 容器已运行(端口 1883) + +## 🚀 启动服务 + +直接启动项目即可,系统会自动: + +1. 读取配置文件中的 MQTT 配置 +2. 连接 MQTT Broker(`tcp://localhost:1883`) +3. 创建事件发布器和订阅器 +4. 启动 MQTT 同步服务 + +```bash +go run cmd/Cabinet/main.go +``` + +## 📊 工作流程 + +``` +包裹状态更新 → 发布MQTT事件 → MQTT Broker → 订阅者接收 → 自动同步格口状态 +``` + +## 🔍 测试 + +### 1. 查看日志 + +启动服务后,查看日志应该看到: +``` +INFO 开始初始化MQTT客户端... +INFO MQTT客户端初始化成功 +INFO MQTT同步服务已启动,将使用MQTT事件驱动同步格口状态 +``` + +### 2. 订阅主题(查看消息) + +```bash +# 安装 mosquitto 客户端工具(如果还没有) +brew install mosquitto # macOS + +# 订阅主题,查看实时消息 +mosquitto_sub -h localhost -t "cabinet/parcel/status/changed/#" -v +``` + +### 3. 更新包裹状态 + +当你在代码中更新包裹状态时: +```go +parcel.ParcelStatus = 2 // 已取件 +db.Save(&parcel) +``` + +系统会自动: +- 发布 MQTT 事件到 `cabinet/parcel/status/changed/{cabinet_id}` +- 订阅者接收事件并同步格口状态 + +## ⚙️ 配置说明 + +在 `configs/config.yaml` 中: + +```yaml +data: + mqtt: + broker: "tcp://localhost:1883" # MQTT Broker地址 + client_id: "cabinet-service" # 客户端ID + username: "" # 用户名(可选) + password: "" # 密码(可选) + qos: 1 # 服务质量等级 +``` + +## 📝 注意事项 + +1. **MQTT Broker 必须运行**:确保 Docker 容器正在运行 +2. **端口映射**:确保 1883 端口已映射到主机 +3. **如果 MQTT 连接失败**:系统会自动降级到 GORM 钩子函数方式 + +## 🐛 故障排查 + +### 问题1:MQTT 连接失败 + +**检查**: +```bash +# 检查容器是否运行 +docker ps | grep mqtt + +# 检查端口是否开放 +netstat -an | grep 1883 +``` + +**解决**: +- 确保容器正在运行 +- 检查配置文件中的 broker 地址 + +### 问题2:没有收到消息 + +**检查**: +- 查看服务日志,确认 MQTT 客户端已连接 +- 使用 `mosquitto_sub` 订阅主题,查看是否有消息 + +## 🎉 完成 + +现在你的项目已经集成了 MQTT!当包裹状态变化时,系统会自动通过 MQTT 事件驱动同步格口状态。 + diff --git "a/docs/MQTT\345\256\236\347\216\260\346\226\271\346\241\210.md" "b/docs/MQTT\345\256\236\347\216\260\346\226\271\346\241\210.md" new file mode 100644 index 0000000000000000000000000000000000000000..5a477eb8a3ac43656dfac3cd7c7de42225f51926 --- /dev/null +++ "b/docs/MQTT\345\256\236\347\216\260\346\226\271\346\241\210.md" @@ -0,0 +1,300 @@ +# MQTT 实现格口状态自动同步方案 + +## 📋 概述 + +使用 **MQTT(Message Queuing Telemetry Transport)** 实现包裹状态变化时自动同步格口状态的功能。 + +## 🎯 优势 + +### 当前实现(GORM钩子函数) +- ✅ 简单直接,无需额外组件 +- ✅ 同步执行,实时性好 +- ❌ 耦合度高,难以扩展 +- ❌ 不支持分布式系统 +- ❌ 无法实现消息持久化 + +### MQTT实现 +- ✅ **解耦**:包裹更新和格口同步完全分离 +- ✅ **分布式**:支持多个服务实例同时处理 +- ✅ **可靠性**:消息持久化和重试机制 +- ✅ **可扩展**:可以添加更多事件监听者 +- ✅ **实时性**:事件驱动,响应快速 +- ✅ **监控**:可以监控消息流量和处理情况 + +## 🏗️ 架构设计 + +``` +┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ 包裹更新 │ ──────> │ MQTT Broker │ ──────> │ 格口同步服务 │ +│ (Parcel) │ 发布事件 │ (消息队列) │ 订阅事件 │ (SyncService)│ +└─────────────┘ └──────────────┘ └─────────────┘ + │ + │ 可以订阅多个服务 + ▼ + ┌─────────────┐ + │ 其他监听服务 │ + │ (可选) │ + └─────────────┘ +``` + +## 📦 依赖安装 + +### 1. 安装 MQTT 客户端库 + +```bash +go get github.com/eclipse/paho.mqtt.golang +``` + +### 2. 安装 MQTT Broker(可选,如果还没有) + +#### 使用 Docker 安装 Mosquitto(推荐) + +```bash +docker run -it -p 1883:1883 -p 9001:9001 eclipse-mosquitto +``` + +#### 或使用其他 MQTT Broker +- **Mosquitto**: 轻量级,适合开发测试 +- **EMQX**: 企业级,功能强大 +- **HiveMQ**: 商业版,性能优秀 + +## 🔧 配置 + +### 1. 在 `configs/config.yaml` 中添加 MQTT 配置 + +```yaml +mqtt: + broker: "tcp://localhost:1883" # MQTT Broker地址 + client_id: "cabinet-service" # 客户端ID + username: "" # 用户名(可选) + password: "" # 密码(可选) + qos: 1 # 服务质量等级(0, 1, 2) +``` + +### 2. 在 `internal/conf/conf.proto` 中添加配置定义 + +```protobuf +message MQTT { + string broker = 1; + string client_id = 2; + string username = 3; + string password = 4; + int32 qos = 5; +} +``` + +## 💻 使用方式 + +### 方式一:使用 MQTT(推荐用于生产环境) + +#### 1. 初始化 MQTT 客户端和事件发布器 + +```go +// 在 internal/data/data.go 中 +import "cabinet/internal/pkg" + +func NewData(c *conf.Data, logger log.Logger, redisClient *redis.Client) (*Data, func(), error) { + // ... 数据库初始化代码 ... + + // 初始化 MQTT 客户端 + mqttConfig := pkg.MQTTConfig{ + Broker: c.MQTT.Broker, + ClientID: c.MQTT.ClientId, + Username: c.MQTT.Username, + Password: c.MQTT.Password, + QoS: byte(c.MQTT.Qos), + } + + mqttClient, err := pkg.NewMQTTClient(mqttConfig, logger) + if err != nil { + return nil, nil, fmt.Errorf("初始化MQTT客户端失败: %v", err) + } + + // 创建事件发布器 + eventPublisher := pkg.NewEventPublisher(mqttClient, logger) + + // 设置全局事件发布器(供模型钩子函数使用) + model.SetGlobalEventPublisher(eventPublisher) + + // 创建事件订阅器 + eventSubscriber := pkg.NewEventSubscriber(mqttClient, logger) + + // 创建MQTT同步服务 + cabinetRepo := NewCabinetRepo(&Data{db: db}, logger) + mqttSyncService := NewMQTTSyncService(db, eventSubscriber, cabinetRepo.(*cabinetRepo), logger) + mqttSyncService.Start() + + // ... 其他初始化代码 ... +} +``` + +#### 2. 在模型层发布事件 + +```go +// 在 internal/model/parcel.go 的 AfterUpdate 钩子中 +func (p *Parcel) AfterUpdate(tx *gorm.DB) error { + if p.oldParcelStatus == p.ParcelStatus { + return nil + } + + // 发布MQTT事件 + var locker Locker + if err := tx.Where("locker_id = ?", p.LockerId).First(&locker).Error; err == nil { + event := pkg.ParcelStatusChangedEvent{ + ParcelId: p.ParcelId, + LockerId: p.LockerId, + CabinetId: locker.CabinetId, + OldStatus: p.oldParcelStatus, + NewStatus: p.ParcelStatus, + ExpressNo: p.ExpressNo, + Timestamp: time.Now().Unix(), + } + + if globalEventPublisher != nil { + globalEventPublisher.PublishParcelStatusChanged(event) + } + } + + return nil +} +``` + +#### 3. 订阅并处理事件 + +```go +// 在 MQTTSyncService 中自动订阅并处理 +func (s *MQTTSyncService) subscribeParcelStatusChanged() error { + return s.subscriber.SubscribeParcelStatusChanged(func(event pkg.ParcelStatusChangedEvent) error { + // 执行同步操作 + ctx := context.Background() + return s.cabinetRepo.SyncLockerStatus(ctx, event.CabinetId) + }) +} +``` + +### 方式二:继续使用 GORM 钩子(适合开发环境) + +如果不想使用 MQTT,可以继续使用当前的 GORM 钩子函数实现,代码已经兼容两种方式。 + +## 📊 MQTT 主题设计 + +### 主题命名规范 + +``` +cabinet/parcel/status/changed/{cabinet_id} # 特定智能柜的事件 +cabinet/parcel/status/changed # 通用事件(所有智能柜) +cabinet/locker/status/synced/{cabinet_id} # 同步完成通知 +``` + +### 消息格式 + +```json +{ + "parcel_id": 12345, + "locker_id": 101, + "cabinet_id": 1001, + "old_status": 0, + "new_status": 2, + "express_no": "SF1234567890", + "timestamp": 1704067200 +} +``` + +## 🔍 监控和调试 + +### 1. 使用 MQTT 客户端工具监控 + +#### 安装 MQTT.fx(推荐) +- 下载:https://mqttfx.jensd.de/ +- 可以订阅主题,查看实时消息 + +#### 使用命令行工具 + +```bash +# 安装 mosquitto 客户端工具 +brew install mosquitto # macOS +# 或 +apt-get install mosquitto-clients # Linux + +# 订阅主题 +mosquitto_sub -h localhost -t "cabinet/parcel/status/changed/#" -v + +# 发布测试消息 +mosquitto_pub -h localhost -t "cabinet/parcel/status/changed/1001" \ + -m '{"parcel_id":12345,"locker_id":101,"cabinet_id":1001,"old_status":0,"new_status":2}' +``` + +### 2. 查看日志 + +系统会在日志中记录: +- MQTT 连接状态 +- 事件发布情况 +- 事件处理结果 + +## ⚙️ 高级配置 + +### 1. QoS 级别选择 + +- **QoS 0**: 最多一次,性能最好,可能丢失消息 +- **QoS 1**: 至少一次(推荐),确保消息送达,可能重复 +- **QoS 2**: 仅一次,最可靠,性能最差 + +### 2. 消息持久化 + +在 MQTT Broker 中配置: +- 保留消息(Retained Messages) +- 持久会话(Persistent Sessions) + +### 3. 集群部署 + +多个服务实例可以同时订阅同一个主题,实现负载均衡。 + +## 🚀 部署步骤 + +### 1. 安装 MQTT Broker + +```bash +# 使用 Docker +docker run -d \ + --name mosquitto \ + -p 1883:1883 \ + -p 9001:9001 \ + -v $(pwd)/mosquitto.conf:/mosquitto/config/mosquitto.conf \ + eclipse-mosquitto +``` + +### 2. 配置应用 + +在 `configs/config.yaml` 中配置 MQTT Broker 地址。 + +### 3. 启动服务 + +```bash +go run cmd/Cabinet/main.go +``` + +### 4. 验证 + +更新包裹状态,观察: +- MQTT Broker 是否收到消息 +- 格口状态是否自动同步 +- 日志中是否有相关记录 + +## 📝 总结 + +### 当前实现(GORM钩子) +- ✅ 简单直接 +- ✅ 无需额外组件 +- ❌ 耦合度高 + +### MQTT实现 +- ✅ 解耦、分布式、可靠 +- ✅ 可扩展、可监控 +- ❌ 需要额外的 MQTT Broker + +**建议**: +- **开发环境**:使用 GORM 钩子函数(简单) +- **生产环境**:使用 MQTT(可靠、可扩展) + +两种方式可以共存,系统会自动检测是否配置了 MQTT,如果配置了就使用 MQTT,否则使用 GORM 钩子。 + diff --git "a/docs/MQTT\345\277\253\351\200\237\345\274\200\345\247\213.md" "b/docs/MQTT\345\277\253\351\200\237\345\274\200\345\247\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..faeb0878ba72898898486c4a1073cadea9d98542 --- /dev/null +++ "b/docs/MQTT\345\277\253\351\200\237\345\274\200\345\247\213.md" @@ -0,0 +1,159 @@ +# MQTT 实现方案 - 快速开始 + +## 📋 当前实现方式 + +### 方式一:GORM 钩子函数(当前默认) + +**实现技术**: +- ✅ **GORM 钩子函数**(BeforeUpdate, AfterUpdate) +- ✅ **GORM 回调函数**(Callback) +- ✅ **在 data 层注册回调函数** + +**优点**: +- 简单直接,无需额外组件 +- 同步执行,实时性好 +- 无需配置 + +**缺点**: +- 耦合度高,难以扩展 +- 不支持分布式系统 +- 无法实现消息持久化 + +### 方式二:MQTT 事件驱动(推荐生产环境) + +**实现技术**: +- ✅ **MQTT 消息队列**(发布/订阅模式) +- ✅ **事件驱动架构** +- ✅ **解耦设计** + +**优点**: +- 解耦、分布式、可靠 +- 可扩展、可监控 +- 支持消息持久化 + +**缺点**: +- 需要额外的 MQTT Broker +- 需要配置和部署 + +## 🚀 使用 MQTT 实现 + +### 1. 安装 MQTT 依赖 + +```bash +go get github.com/eclipse/paho.mqtt.golang +``` + +### 2. 安装 MQTT Broker(可选) + +#### 使用 Docker 安装 Mosquitto(推荐) + +```bash +docker run -d \ + --name mosquitto \ + -p 1883:1883 \ + -p 9001:9001 \ + eclipse-mosquitto +``` + +#### 或使用 Homebrew(macOS) + +```bash +brew install mosquitto +mosquitto -c /usr/local/etc/mosquitto/mosquitto.conf +``` + +### 3. 配置 MQTT + +在 `configs/config.yaml` 中添加: + +```yaml +mqtt: + broker: "tcp://localhost:1883" + client_id: "cabinet-service" + username: "" + password: "" + qos: 1 +``` + +### 4. 初始化 MQTT(在 data.go 中) + +```go +// 初始化 MQTT 客户端 +mqttConfig := pkg.MQTTConfig{ + Broker: c.MQTT.Broker, + ClientID: c.MQTT.ClientId, + Username: c.MQTT.Username, + Password: c.MQTT.Password, + QoS: byte(c.MQTT.Qos), +} + +mqttClient, err := pkg.NewMQTTClient(mqttConfig, logger) +if err != nil { + log.NewHelper(logger).Warnf("MQTT初始化失败,将使用GORM钩子函数: %v", err) +} else { + // 创建事件发布器 + eventPublisher := pkg.NewEventPublisher(mqttClient, logger) + + // 设置全局事件发布器 + model.SetGlobalEventPublisher(eventPublisher) + + // 创建事件订阅器并启动同步服务 + eventSubscriber := pkg.NewEventSubscriber(mqttClient, logger) + cabinetRepo := NewCabinetRepo(&Data{db: db}, logger) + mqttSyncService := NewMQTTSyncService(db, eventSubscriber, cabinetRepo.(*cabinetRepo), logger) + mqttSyncService.Start() +} +``` + +## 📊 工作流程 + +### GORM 钩子函数方式(当前) + +``` +包裹更新 → GORM钩子函数 → 直接同步格口状态 +``` + +### MQTT 方式 + +``` +包裹更新 → 发布MQTT事件 → MQTT Broker → 订阅者接收 → 同步格口状态 +``` + +## 🔍 测试 MQTT + +### 1. 订阅主题(查看消息) + +```bash +# 安装 mosquitto 客户端工具 +brew install mosquitto # macOS + +# 订阅主题 +mosquitto_sub -h localhost -t "cabinet/parcel/status/changed/#" -v +``` + +### 2. 发布测试消息 + +```bash +mosquitto_pub -h localhost -t "cabinet/parcel/status/changed/1001" \ + -m '{"parcel_id":12345,"locker_id":101,"cabinet_id":1001,"old_status":0,"new_status":2,"express_no":"SF1234567890","timestamp":1704067200}' +``` + +## 📝 总结 + +### 当前实现(GORM钩子) +- ✅ 已实现,可以直接使用 +- ✅ 无需额外配置 +- ✅ 适合开发环境 + +### MQTT实现 +- ✅ 代码已准备好 +- ⚠️ 需要安装依赖:`go get github.com/eclipse/paho.mqtt.golang` +- ⚠️ 需要安装 MQTT Broker +- ✅ 适合生产环境 + +**建议**: +- **开发环境**:使用 GORM 钩子函数(当前默认) +- **生产环境**:使用 MQTT(需要安装依赖和配置) + +两种方式可以共存,系统会自动检测是否配置了 MQTT。 + diff --git "a/docs/MQTT\346\265\213\350\257\225\346\214\207\345\215\227.md" "b/docs/MQTT\346\265\213\350\257\225\346\214\207\345\215\227.md" new file mode 100644 index 0000000000000000000000000000000000000000..dfbe6b6a7b36317efa6f63eba446cc24f5b5e040 --- /dev/null +++ "b/docs/MQTT\346\265\213\350\257\225\346\214\207\345\215\227.md" @@ -0,0 +1,300 @@ +# MQTT 测试指南 + +本指南将帮助您测试 MQTT 功能,验证包裹状态变化事件是否正确发布和订阅。 + +## 前置条件 + +1. **MQTT Broker 运行中** + ```bash + docker ps | grep mosquitto + ``` + 如果未运行,启动 MQTT Broker: + ```bash + docker run -d -p 1883:1883 -p 9001:9001 --name mosquitto eclipse-mosquitto + ``` + +2. **安装 MQTT 客户端工具**(可选,用于手动测试) + ```bash + # macOS + brew install mosquitto + + # 或使用 Docker 容器中的工具 + docker exec -it mosquitto mosquitto_sub -h localhost -p 1883 -t "test/#" -v + ``` + +3. **项目已启动** + ```bash + kratos run + ``` + +## MQTT 主题说明 + +### 发布主题 +- **特定智能柜主题**: `cabinet/parcel/status/changed/{cabinet_id}` +- **通用主题**: `cabinet/parcel/status/changed` + +### 订阅主题 +- **通配符订阅**: `cabinet/parcel/status/changed/+` (订阅所有智能柜的事件) +- **通用主题**: `cabinet/parcel/status/changed` + +## 事件消息格式 + +```json +{ + "parcel_id": 1001, + "locker_id": 101, + "cabinet_id": 1, + "old_status": 0, + "new_status": 1, + "timestamp": 1699344000, + "express_no": "TEST2024110701" +} +``` + +### 状态说明 +- `0`: 待取件 +- `1`: 滞留件 +- `2`: 已取件 +- `3`: 已撤回 +- `4`: 已完结 + +## 测试方法 + +### 方法 1: 使用测试脚本(推荐) + +运行测试脚本: +```bash +chmod +x test_mqtt.sh +./test_mqtt.sh +``` + +选择测试选项: +1. **订阅包裹状态变化事件** - 实时监听 MQTT 事件 +2. **手动发布测试消息** - 手动发送测试消息 +3. **通过 API 更新包裹状态** - 通过 API 触发 MQTT 事件 +4. **查看 MQTT 连接状态** - 测试 MQTT 连接 + +### 方法 2: 手动订阅 MQTT 事件 + +#### 2.1 订阅所有包裹状态变化事件 + +```bash +# 使用本地 mosquitto_sub +mosquitto_sub -h localhost -p 1883 -t "cabinet/parcel/status/changed/#" -v + +# 或使用 Docker 容器中的工具 +docker exec -it mosquitto mosquitto_sub -h localhost -p 1883 -t "cabinet/parcel/status/changed/#" -v +``` + +#### 2.2 订阅特定智能柜的事件 + +```bash +mosquitto_sub -h localhost -p 1883 -t "cabinet/parcel/status/changed/1" -v +``` + +### 方法 3: 手动发布测试消息 + +```bash +# 发布测试消息 +mosquitto_pub -h localhost -p 1883 -t "cabinet/parcel/status/changed" -m '{ + "parcel_id": 1001, + "locker_id": 101, + "cabinet_id": 1, + "old_status": 0, + "new_status": 1, + "timestamp": 1699344000, + "express_no": "TEST2024110701" +}' +``` + +### 方法 4: 通过 API 触发 MQTT 事件 + +#### 4.1 更新包裹状态(如果 API 已实现) + +```bash +# 使用 curl 更新包裹状态 +curl -X PUT "http://localhost:8001/v1/cabinet/parcel/1001" \ + -H "Content-Type: application/json" \ + -d '{ + "parcel_status": 1 + }' +``` + +#### 4.2 使用 ApiPost 或其他工具 + +1. 打开 ApiPost +2. 创建 PUT 请求:`http://localhost:8001/v1/cabinet/parcel/{parcel_id}` +3. 设置请求体: + ```json + { + "parcel_status": 1 + } + ``` +4. 发送请求 +5. 在订阅终端查看 MQTT 事件 + +## 测试流程 + +### 完整测试流程 + +1. **启动 MQTT Broker** + ```bash + docker ps | grep mosquitto + ``` + +2. **启动项目** + ```bash + kratos run + ``` + 检查日志,确认 MQTT 连接成功: + ``` + INFO ... msg=MQTT客户端初始化成功 + INFO ... msg=MQTT同步服务已启动,将使用MQTT事件驱动同步格口状态 + ``` + +3. **订阅 MQTT 事件**(在另一个终端) + ```bash + mosquitto_sub -h localhost -p 1883 -t "cabinet/parcel/status/changed/#" -v + ``` + +4. **触发包裹状态变化** + - 通过 API 更新包裹状态 + - 或手动发布测试消息 + +5. **验证结果** + - 在订阅终端看到 MQTT 事件 + - 检查项目日志,确认收到事件并执行同步 + - 检查数据库,确认格口状态已更新 + +## 验证要点 + +### 1. MQTT 连接状态 + +检查项目启动日志: +``` +INFO ... msg=MQTT客户端初始化成功 +INFO ... msg=MQTT同步服务已启动,将使用MQTT事件驱动同步格口状态 +``` + +如果看到: +``` +WARN ... msg=MQTT客户端初始化失败,将使用GORM钩子函数 +``` +说明 MQTT 连接失败,系统会回退到使用 GORM 钩子函数。 + +### 2. 事件发布 + +当包裹状态变化时,检查日志: +``` +INFO ... msg=发布包裹状态变化事件: ParcelId=1001, CabinetId=1, OldStatus=0, NewStatus=1 +``` + +### 3. 事件订阅和处理 + +检查日志,确认收到事件: +``` +INFO ... msg=收到包裹状态变化事件: ParcelId=1001, CabinetId=1, OldStatus=0, NewStatus=1 +INFO ... msg=同步格口状态成功: CabinetId=1 +``` + +### 4. 数据库验证 + +检查数据库,确认格口状态已更新: +```sql +-- 查看包裹状态 +SELECT parcel_id, express_no, parcel_status, locker_id +FROM parcel +WHERE parcel_id = 1001; + +-- 查看格口状态 +SELECT locker_id, status, cabinet_id +FROM locker +WHERE locker_id = 101; + +-- 查看智能柜可用格口数 +SELECT id, total_compartments, available_compartments +FROM cabinet +WHERE id = 1; +``` + +## 常见问题 + +### 1. MQTT 连接失败 + +**问题**: 日志显示 "MQTT客户端初始化失败" + +**解决方案**: +1. 检查 MQTT Broker 是否运行:`docker ps | grep mosquitto` +2. 检查配置:`configs/config.yaml` 中的 MQTT 配置 +3. 检查端口:确保 1883 端口未被占用 +4. 测试连接:`mosquitto_pub -h localhost -p 1883 -t "test" -m "test"` + +### 2. 收不到 MQTT 事件 + +**问题**: 订阅终端收不到消息 + +**解决方案**: +1. 确认主题名称正确 +2. 确认 QoS 级别匹配(建议使用 QoS 1) +3. 检查 MQTT Broker 日志:`docker logs mosquitto` +4. 确认事件已发布(检查项目日志) + +### 3. 事件发布但未处理 + +**问题**: 看到事件发布日志,但未看到处理日志 + +**解决方案**: +1. 确认 MQTT 同步服务已启动 +2. 检查事件格式是否正确 +3. 检查订阅主题是否匹配 +4. 查看错误日志 + +## 测试示例 + +### 示例 1: 完整测试流程 + +```bash +# 终端 1: 启动项目 +kratos run + +# 终端 2: 订阅 MQTT 事件 +mosquitto_sub -h localhost -p 1883 -t "cabinet/parcel/status/changed/#" -v + +# 终端 3: 更新包裹状态(假设 API 已实现) +curl -X PUT "http://localhost:8001/v1/cabinet/parcel/1001" \ + -H "Content-Type: application/json" \ + -d '{"parcel_status": 1}' +``` + +### 示例 2: 手动发布测试消息 + +```bash +# 终端 1: 订阅 +mosquitto_sub -h localhost -p 1883 -t "cabinet/parcel/status/changed/#" -v + +# 终端 2: 发布 +mosquitto_pub -h localhost -p 1883 -t "cabinet/parcel/status/changed" -m '{ + "parcel_id": 1001, + "locker_id": 101, + "cabinet_id": 1, + "old_status": 0, + "new_status": 1, + "timestamp": 1699344000, + "express_no": "TEST2024110701" +}' +``` + +## 总结 + +通过以上测试方法,您可以: +1. ✅ 验证 MQTT 连接是否正常 +2. ✅ 验证事件发布功能 +3. ✅ 验证事件订阅和处理功能 +4. ✅ 验证格口状态同步功能 + +如果遇到问题,请检查: +- MQTT Broker 是否运行 +- 项目日志中的错误信息 +- 数据库中的实际数据 +- MQTT 主题和消息格式 + diff --git "a/docs/\345\277\253\351\200\222\345\221\230\346\264\276\344\273\266\345\210\227\350\241\250API\350\257\264\346\230\216.md" "b/docs/\345\277\253\351\200\222\345\221\230\346\264\276\344\273\266\345\210\227\350\241\250API\350\257\264\346\230\216.md" new file mode 100644 index 0000000000000000000000000000000000000000..d7109af0a2d1684f1e3ad89df262fdf08498e348 --- /dev/null +++ "b/docs/\345\277\253\351\200\222\345\221\230\346\264\276\344\273\266\345\210\227\350\241\250API\350\257\264\346\230\216.md" @@ -0,0 +1,334 @@ +# 快递员派件列表 API 说明 + +## 功能概述 + +快递员派件列表接口用于根据快递员 ID 查询其派送的包裹列表,并按柜机进行层级分组展示。支持按派件状态进行筛选。 + +## 接口信息 + +### HTTP 接口 + +- **路径**: `GET /v1/cabinet/courier/parcels` +- **方法**: `GET` +- **Content-Type**: `application/json` + +### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| courier_id | int32 | 是 | 快递员ID | +| parcel_status | int32 | 否 | 包裹状态(-1表示查询所有状态,0-待取件,1-滞留件,2-已取件,3-已撤回,4-已完结) | + +### 包裹状态说明 + +- `-1`: 查询所有状态 +- `0`: 待取件 +- `1`: 滞留件 +- `2`: 已取件 +- `3`: 已撤回 +- `4`: 已完结 + +### 响应结构 + +响应数据按照**柜机-包裹**的层级结构组织: + +```json +{ + "groups": [ + { + "cabinet": { + "id": 1, + "cabinet_code": "CAB001", + "cabinet_name": "北京西站智能柜", + "cabinet_type": "快递柜", + "location": "北京市西城区", + "latitude": 39.9042, + "longitude": 116.4074, + "total_compartments": 50, + "available_compartments": 25, + "status": 1 + }, + "parcels": [ + { + "parcel_id": 1, + "express_no": "YTO123456789", + "recipient_name": "张三", + "recipient_phone": "13800138000", + "locker_id": 101, + "parcel_status": 0, + "dispatch_time": "2024-11-07 10:30:00", + "courier_id": 1001 + }, + { + "parcel_id": 2, + "express_no": "YTO987654321", + "recipient_name": "李四", + "recipient_phone": "13900139000", + "locker_id": 102, + "parcel_status": 0, + "dispatch_time": "2024-11-07 10:35:00", + "courier_id": 1001 + } + ], + "parcel_count": 2 + }, + { + "cabinet": { + "id": 2, + "cabinet_code": "CAB002", + "cabinet_name": "北京东站智能柜", + "cabinet_type": "快递柜", + "location": "北京市朝阳区", + "latitude": 39.9047, + "longitude": 116.4174, + "total_compartments": 40, + "available_compartments": 20, + "status": 1 + }, + "parcels": [ + { + "parcel_id": 3, + "express_no": "SF123456789", + "recipient_name": "王五", + "recipient_phone": "13700137000", + "locker_id": 201, + "parcel_status": 0, + "dispatch_time": "2024-11-07 11:00:00", + "courier_id": 1001 + } + ], + "parcel_count": 1 + } + ], + "total_parcels": 3, + "total_cabinets": 2 +} +``` + +### 响应字段说明 + +#### 顶层字段 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| groups | array | 按柜机分组的包裹列表(父-子层级结构) | +| total_parcels | int32 | 包裹总数 | +| total_cabinets | int32 | 涉及的柜机总数 | + +#### groups[].cabinet(柜机信息 - 父级) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | int64 | 柜机ID | +| cabinet_code | string | 柜机编码 | +| cabinet_name | string | 柜机名称 | +| cabinet_type | string | 柜机类型 | +| location | string | 安装位置 | +| latitude | float | 纬度 | +| longitude | float | 经度 | +| total_compartments | int32 | 总格口数量 | +| available_compartments | int32 | 可用格口数量 | +| status | int32 | 柜机状态(1-正常,2-维护中,3-停用,4-故障) | + +#### groups[].parcels[](包裹列表 - 子级) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| parcel_id | int32 | 包裹ID | +| express_no | string | 快递单号 | +| recipient_name | string | 收件人姓名 | +| recipient_phone | string | 收件人电话 | +| locker_id | int32 | 格口ID | +| parcel_status | int32 | 包裹状态 | +| dispatch_time | string | 派件时间 | +| courier_id | int32 | 快递员ID | + +#### groups[].parcel_count + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| parcel_count | int32 | 该柜机的包裹数量 | + +## 使用示例 + +### 示例 1: 查询快递员的所有包裹 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=-1" +``` + +### 示例 2: 查询快递员待取件的包裹 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=0" +``` + +### 示例 3: 查询快递员已取件的包裹 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=2" +``` + +### 示例 4: 使用 JavaScript/TypeScript + +```javascript +async function getCourierParcels(courierId, parcelStatus = -1) { + const response = await fetch( + `http://localhost:8000/v1/cabinet/courier/parcels?courier_id=${courierId}&parcel_status=${parcelStatus}` + ); + const data = await response.json(); + return data; +} + +// 使用示例 +getCourierParcels(1001, 0).then(data => { + console.log(`总计: ${data.total_parcels} 个包裹,分布在 ${data.total_cabinets} 个柜机`); + + data.groups.forEach(group => { + console.log(`柜机: ${group.cabinet.cabinet_name} (${group.cabinet.location})`); + console.log(` 包裹数量: ${group.parcel_count}`); + + group.parcels.forEach(parcel => { + console.log(` - ${parcel.express_no}: ${parcel.recipient_name} (${parcel.recipient_phone})`); + }); + }); +}); +``` + +### 示例 5: 使用 Go 客户端 + +```go +package main + +import ( + "context" + "fmt" + "log" + + pb "Cabinet/api/cabinet/v1" + "google.golang.org/grpc" +) + +func main() { + // 建立连接 + conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure()) + if err != nil { + log.Fatalf("连接失败: %v", err) + } + defer conn.Close() + + // 创建客户端 + client := pb.NewCabinetClient(conn) + + // 调用接口 + resp, err := client.ListCourierParcels(context.Background(), &pb.ListCourierParcelsRequest{ + CourierId: 1001, + ParcelStatus: 0, // 查询待取件 + }) + if err != nil { + log.Fatalf("查询失败: %v", err) + } + + // 处理响应 + fmt.Printf("总计: %d 个包裹,分布在 %d 个柜机\n", resp.TotalParcels, resp.TotalCabinets) + + for _, group := range resp.Groups { + fmt.Printf("柜机: %s (%s)\n", group.Cabinet.CabinetName, group.Cabinet.Location) + fmt.Printf(" 包裹数量: %d\n", group.ParcelCount) + + for _, parcel := range group.Parcels { + fmt.Printf(" - %s: %s (%s) - 状态: %d\n", + parcel.ExpressNo, + parcel.RecipientName, + parcel.RecipientPhone, + parcel.ParcelStatus) + } + } +} +``` + +## 数据结构说明 + +### 层级结构 + +接口返回的数据采用**二级层级结构**: + +``` +响应根对象 +├── groups[] (柜机分组数组) +│ ├── cabinet (柜机信息 - 父级) +│ ├── parcels[] (包裹列表 - 子级) +│ └── parcel_count (包裹数量统计) +├── total_parcels (包裹总数) +└── total_cabinets (柜机总数) +``` + +这种结构设计的优势: +1. **层级清晰**:父级是柜机,子级是该柜机内的包裹 +2. **便于展示**:前端可以直接用树形组件展示 +3. **统计方便**:每个柜机分组都包含该柜机的包裹数量 +4. **性能优化**:一次查询获取所有相关数据,减少网络请求 + +## 错误处理 + +### 错误响应格式 + +```json +{ + "code": 400, + "message": "快递员ID参数无效", + "details": [] +} +``` + +### 常见错误 + +| 错误码 | 错误信息 | 说明 | +|--------|----------|------| +| 400 | 快递员ID参数无效 | courier_id 参数小于等于 0 或未提供 | +| 404 | 快递员没有包裹 | 该快递员当前没有任何包裹 | +| 500 | 查询包裹列表失败 | 数据库查询错误 | +| 500 | 查询格口信息失败 | locker 表查询错误 | +| 500 | 查询柜机信息失败 | cabinet 表查询错误 | + +## 性能考虑 + +1. **批量查询优化**:使用 `IN` 查询减少数据库访问次数 +2. **数据分组**:在应用层进行数据分组,避免复杂的 SQL 查询 +3. **索引建议**: + - `parcel` 表的 `courier_id` 字段建议添加索引 + - `parcel` 表的 `parcel_status` 字段建议添加索引 + - `locker` 表的 `locker_id` 字段建议添加索引 + - `locker` 表的 `cabinet_id` 字段建议添加索引 + +## 数据库依赖 + +该接口依赖以下数据库表: + +1. **parcel** 表:存储包裹信息 +2. **locker** 表:存储格口信息(关联 parcel 和 cabinet) +3. **cabinet** 表:存储柜机信息 + +### locker 表结构要求 + +```sql +CREATE TABLE locker ( + locker_id INT PRIMARY KEY, + cabinet_id BIGINT NOT NULL, + -- 其他字段... + INDEX idx_locker_id (locker_id), + INDEX idx_cabinet_id (cabinet_id) +); +``` + +## 注意事项 + +1. **参数验证**:`courier_id` 必须大于 0 +2. **状态筛选**:`parcel_status` 为 -1 时查询所有状态,其他值只查询对应状态 +3. **空结果处理**:如果快递员没有包裹,返回空的 groups 数组 +4. **数据一致性**:如果某个包裹的格口找不到对应的柜机,该包裹会被跳过并记录警告日志 + +## 版本历史 + +- **v1.0.0** (2024-11-07): 初始版本,实现基本的快递员派件列表查询功能 + diff --git "a/docs/\346\265\213\350\257\225\346\225\260\346\215\256\350\257\264\346\230\216.md" "b/docs/\346\265\213\350\257\225\346\225\260\346\215\256\350\257\264\346\230\216.md" new file mode 100644 index 0000000000000000000000000000000000000000..b744af09880326bcf7127bae2aa9e9578656d87f --- /dev/null +++ "b/docs/\346\265\213\350\257\225\346\225\260\346\215\256\350\257\264\346\230\216.md" @@ -0,0 +1,269 @@ +# 测试数据说明 + +## 数据概览 + +测试数据文件:`test_courier_data.sql` + +该文件包含了完整的测试数据,用于测试快递员派件列表功能。 + +## 数据统计 + +### 柜机数据 +| 柜机ID | 柜机编码 | 柜机名称 | 位置 | 总格口数 | 可用格口数 | +|--------|----------|----------|------|----------|------------| +| 1 | CAB001 | 北京西站智能柜 | 北京市西城区北京西站南广场 | 50 | 20 | +| 2 | CAB002 | 北京东站智能柜 | 北京市朝阳区东直门外大街 | 40 | 15 | +| 3 | CAB003 | 中关村智能柜 | 北京市海淀区中关村大街 | 60 | 25 | +| 4 | CAB004 | 国贸智能柜 | 北京市朝阳区建国门外大街 | 45 | 18 | + +### 格口数据 +| 柜机 | 格口ID范围 | 格口数量 | +|------|------------|----------| +| 柜机1 | 101-110 | 10个 | +| 柜机2 | 201-208 | 8个 | +| 柜机3 | 301-307 | 7个 | +| 柜机4 | 401-406 | 6个 | + +### 快递员数据 +| 用户ID | 用户名 | 真实姓名 | 手机号 | 用户类型 | +|--------|--------|----------|--------|----------| +| 1001 | courier_zhang | 张三 | 13800138001 | 快递员(1) | +| 1002 | courier_li | 李四 | 13800138002 | 快递员(1) | + +### 收件人数据 +| 用户ID | 用户名 | 真实姓名 | 手机号 | 用户类型 | +|--------|--------|----------|--------|----------| +| 2001 | user_wang | 王五 | 13900139001 | 普通用户(0) | +| 2002 | user_zhao | 赵六 | 13900139002 | 普通用户(0) | +| 2003 | user_sun | 孙七 | 13900139003 | 普通用户(0) | +| 2004 | user_zhou | 周八 | 13900139004 | 普通用户(0) | +| 2005 | user_wu | 吴九 | 13900139005 | 普通用户(0) | + +## 快递员1001的包裹分布 + +### 按柜机分组统计 +| 柜机ID | 柜机名称 | 包裹数量 | 待取件 | 滞留件 | 已取件 | +|--------|----------|----------|--------|--------|--------| +| 1 | 北京西站智能柜 | 6个 | 4个 | 0个 | 2个 | +| 2 | 北京东站智能柜 | 4个 | 3个 | 1个 | 0个 | +| 3 | 中关村智能柜 | 3个 | 3个 | 0个 | 0个 | +| 4 | 国贸智能柜 | 2个 | 2个 | 0个 | 0个 | + +**总计**:15个包裹,分布在4个柜机 + +### 按状态统计 +- **待取件(0)**:12个 +- **滞留件(1)**:1个 +- **已取件(2)**:2个 + +### 详细列表 + +#### 柜机1 - 北京西站智能柜(6个包裹) +| 快递单号 | 收件人 | 格口ID | 状态 | 派件时间 | +|----------|--------|--------|------|----------| +| YTO202411070001 | 王五 | 101 | 待取件(0) | 2024-11-07 09:30:00 | +| YTO202411070002 | 赵六 | 102 | 待取件(0) | 2024-11-07 09:35:00 | +| SF202411070001 | 孙七 | 103 | 待取件(0) | 2024-11-07 09:40:00 | +| JD202411070001 | 周八 | 104 | 待取件(0) | 2024-11-07 10:00:00 | +| YTO202411060001 | 吴九 | 105 | 已取件(2) | 2024-11-06 14:30:00 | +| SF202411060002 | 王五 | 106 | 已取件(2) | 2024-11-06 15:00:00 | + +#### 柜机2 - 北京东站智能柜(4个包裹) +| 快递单号 | 收件人 | 格口ID | 状态 | 派件时间 | +|----------|--------|--------|------|----------| +| YTO202411070003 | 赵六 | 201 | 待取件(0) | 2024-11-07 10:30:00 | +| JD202411070002 | 孙七 | 202 | 待取件(0) | 2024-11-07 10:45:00 | +| SF202411070002 | 周八 | 203 | 待取件(0) | 2024-11-07 11:00:00 | +| YTO202411050001 | 吴九 | 204 | 滞留件(1) | 2024-11-05 09:00:00 | + +#### 柜机3 - 中关村智能柜(3个包裹) +| 快递单号 | 收件人 | 格口ID | 状态 | 派件时间 | +|----------|--------|--------|------|----------| +| EMS202411070001 | 王五 | 301 | 待取件(0) | 2024-11-07 11:30:00 | +| YTO202411070004 | 赵六 | 302 | 待取件(0) | 2024-11-07 11:45:00 | +| SF202411070003 | 孙七 | 303 | 待取件(0) | 2024-11-07 12:00:00 | + +#### 柜机4 - 国贸智能柜(2个包裹) +| 快递单号 | 收件人 | 格口ID | 状态 | 派件时间 | +|----------|--------|--------|------|----------| +| JD202411070003 | 周八 | 401 | 待取件(0) | 2024-11-07 13:00:00 | +| YTO202411070005 | 吴九 | 402 | 待取件(0) | 2024-11-07 13:15:00 | + +## 快递员1002的包裹分布 + +### 按柜机分组统计 +| 柜机ID | 柜机名称 | 包裹数量 | 待取件 | 已取件 | +|--------|----------|----------|--------|--------| +| 1 | 北京西站智能柜 | 2个 | 2个 | 0个 | +| 3 | 中关村智能柜 | 2个 | 1个 | 1个 | + +**总计**:4个包裹,分布在2个柜机 + +## 导入数据 + +### 方法1:MySQL命令行 + +```bash +mysql -u root -p cabinet_db < test_courier_data.sql +``` + +### 方法2:MySQL客户端 + +```sql +source /path/to/test_courier_data.sql; +``` + +### 方法3:使用数据库管理工具 + +在 Navicat、MySQL Workbench、DBeaver 等工具中: +1. 打开 SQL 文件 +2. 执行整个脚本 + +## 测试场景 + +### 场景1:查询快递员1001的所有包裹 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=-1" +``` + +**预期结果**: +- 返回4个柜机分组 +- 总包裹数:15个 +- 柜机分布:柜机1(6个)、柜机2(4个)、柜机3(3个)、柜机4(2个) + +### 场景2:查询快递员1001的待取件包裹 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=0" +``` + +**预期结果**: +- 返回4个柜机分组 +- 总包裹数:12个(只包含待取件) +- 柜机分布:柜机1(4个)、柜机2(3个)、柜机3(3个)、柜机4(2个) + +### 场景3:查询快递员1001的滞留件 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=1" +``` + +**预期结果**: +- 返回1个柜机分组(柜机2) +- 总包裹数:1个 +- 快递单号:YTO202411050001 + +### 场景4:查询快递员1001的已取件包裹 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=2" +``` + +**预期结果**: +- 返回1个柜机分组(柜机1) +- 总包裹数:2个 +- 快递单号:YTO202411060001, SF202411060002 + +### 场景5:查询快递员1002的所有包裹 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1002&parcel_status=-1" +``` + +**预期结果**: +- 返回2个柜机分组 +- 总包裹数:4个 +- 柜机分布:柜机1(2个)、柜机3(2个) + +### 场景6:查询不存在的快递员 + +```bash +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=9999&parcel_status=-1" +``` + +**预期结果**: +- 返回空列表 +- total_parcels: 0 +- total_cabinets: 0 + +## 数据验证SQL + +### 验证快递员1001的包裹按柜机分组 + +```sql +SELECT + c.id as cabinet_id, + c.cabinet_name, + c.location, + COUNT(p.parcel_id) as parcel_count, + SUM(CASE WHEN p.parcel_status = 0 THEN 1 ELSE 0 END) as pending, + SUM(CASE WHEN p.parcel_status = 1 THEN 1 ELSE 0 END) as overdue, + SUM(CASE WHEN p.parcel_status = 2 THEN 1 ELSE 0 END) as picked +FROM parcel p +JOIN locker l ON p.locker_id = l.locker_id +JOIN cabinet c ON l.cabinet_id = c.id +WHERE p.courier_id = 1001 +GROUP BY c.id, c.cabinet_name, c.location +ORDER BY c.id; +``` + +### 验证待取件包裹详情 + +```sql +SELECT + p.express_no, + p.recipient_name, + p.recipient_phone, + c.cabinet_name, + c.location, + l.locker_number, + p.dispatch_time +FROM parcel p +JOIN locker l ON p.locker_id = l.locker_id +JOIN cabinet c ON l.cabinet_id = c.id +WHERE p.courier_id = 1001 AND p.parcel_status = 0 +ORDER BY c.id, p.dispatch_time; +``` + +## 清理数据 + +如果需要清理测试数据,执行以下SQL: + +```sql +-- 删除包裹数据 +DELETE FROM parcel WHERE courier_id IN (1001, 1002); + +-- 删除格口数据 +DELETE FROM locker WHERE cabinet_id IN (1, 2, 3, 4); + +-- 删除柜机数据 +DELETE FROM cabinet WHERE id IN (1, 2, 3, 4); + +-- 删除用户数据 +DELETE FROM user WHERE user_id IN (1001, 1002, 2001, 2002, 2003, 2004, 2005); +``` + +## 注意事项 + +1. **主键冲突**:如果数据库中已存在相同ID的数据,请先清理或修改SQL中的ID值 +2. **外键约束**:确保 locker 表存在且有 cabinet_id 外键字段 +3. **字符编码**:确保数据库使用 UTF-8 编码,避免中文乱码 +4. **时间格式**:SQL中使用 `NOW()` 函数,会使用当前时间,如需特定时间请修改 +5. **密码加密**:user 表中的密码字段使用了占位符,实际使用时应使用加密后的密码 + +## 快速开始 + +```bash +# 1. 导入测试数据 +mysql -u root -p cabinet_db < test_courier_data.sql + +# 2. 启动服务 +cd cmd/Cabinet && go run . + +# 3. 测试接口 +curl -X GET "http://localhost:8000/v1/cabinet/courier/parcels?courier_id=1001&parcel_status=0" +``` + +祝测试愉快!🚀 + diff --git a/go.mod b/go.mod index 902e037faa2715a90ff5cb9949857430ecf4061d..3af4f538a43dba894ca22b3ecbd4ccea676de32d 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,55 @@ -module Cabinet +module cabinet -go 1.24.0 +go 1.25.1 require ( - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-kratos/kratos/v2 v2.9.1 + github.com/google/uuid v1.6.0 github.com/google/wire v0.7.0 + github.com/gorilla/websocket v1.5.3 + github.com/hashicorp/go-uuid v1.0.3 + github.com/qiniu/go-sdk/v7 v7.25.4 github.com/redis/go-redis/v9 v9.16.0 + github.com/smartwalle/alipay/v3 v3.2.27 go.uber.org/automaxprocs v1.6.0 - google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda + google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 gorm.io/driver/mysql v1.6.0 - gorm.io/gorm v1.31.0 + gorm.io/gorm v1.31.1 ) require ( dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/eclipse/paho.mqtt.golang v1.5.1 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-kratos/aegis v0.2.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/form/v4 v4.3.0 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/gofrs/flock v0.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect + github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/shirou/gopsutil/v3 v3.23.6 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/smartwalle/ncrypto v1.0.4 // indirect + github.com/smartwalle/ngx v1.0.11 // indirect + github.com/smartwalle/nsign v1.0.9 // indirect + github.com/tklauser/go-sysconf v0.3.11 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel v1.38.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect @@ -38,6 +58,7 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.37.0 // indirect golang.org/x/text v0.30.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/fileutil v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 07ba273fa6f979a804645f7ec531430162c14245..5486a64408e7ae3e1d833e1b1e22c2fe59df1d86 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -12,12 +16,17 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE= +github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU= github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= @@ -25,6 +34,7 @@ github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfU github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gammazero/toposort v0.1.1/go.mod h1:H2cozTnNpMw0hg2VHAYsAxmkHXBYroNangj2NTBQDvw= github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ= github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI= github.com/go-kratos/kratos/v2 v2.9.1 h1:EGif6/S/aK/RCR5clIbyhioTNyoSrii3FC118jG40Z0= @@ -34,42 +44,112 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/form/v4 v4.3.0 h1:OVttojbQv2WNCs4P+VnjPtrt/+30Ipw4890W3OaFlvk= github.com/go-playground/form/v4 v4.3.0/go.mod h1:Cpe1iYJKoXb1vILRXEwxpWMGWyQuqplQ/4cvPecy+Jo= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik= +github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig= +github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= +github.com/qiniu/go-sdk/v7 v7.25.4 h1:ulCKlTEyrZzmNytXweOrnva49+Q4+ASjYBCSXhkRWTo= +github.com/qiniu/go-sdk/v7 v7.25.4/go.mod h1:dmKtJ2ahhPWFVi9o1D5GemmWoh/ctuB9peqTowyTO8o= +github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4= github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/shirou/gopsutil/v3 v3.23.6 h1:5y46WPI9QBKBbK7EEccUPNXpJpNrvPuTD0O2zHEHT08= +github.com/shirou/gopsutil/v3 v3.23.6/go.mod h1:j7QX50DrXYggrpN30W0Mo+I4/8U2UUIQrnrhqUeWrAU= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/smartwalle/alipay/v3 v3.2.27 h1:2BbZEowroiyjZr3ggSdpMT9IS6fWzv4IBLKyhTdJvBA= +github.com/smartwalle/alipay/v3 v3.2.27/go.mod h1:02Yb5lwYM9HBnPnNGvJlxxYbjKYgONWcAcwekVURWN4= +github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8= +github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk= +github.com/smartwalle/ngx v1.0.11 h1:cC+4WNKEUDf3wr7vW/GrbL+Cp2AJAk5svckPvQ3led8= +github.com/smartwalle/ngx v1.0.11/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0= +github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E= +github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= +github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -84,30 +164,62 @@ go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJr go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE= google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 h1:vk5TfqZHNn0obhPIYeS+cxIFKFQgser/M2jnI+9c6MM= +google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101/go.mod h1:E17fc4PDhkr22dE3RgnH2hEubUaky6ZwW4VhANxyspg= google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= -gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= +modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= diff --git a/help_articles.sql b/help_articles.sql new file mode 100644 index 0000000000000000000000000000000000000000..3f86cf20f43bcc83764165c4622b4b2d2e08e9e2 --- /dev/null +++ b/help_articles.sql @@ -0,0 +1,34 @@ +-- 帮助文章表结构 +CREATE TABLE IF NOT EXISTS `help_articles` ( + `id` VARCHAR(36) NOT NULL COMMENT '文章ID', + `title` VARCHAR(255) NOT NULL COMMENT '文章标题', + `content` TEXT NOT NULL COMMENT '文章内容', + `category` VARCHAR(50) NOT NULL COMMENT '分类', + `sub_category` VARCHAR(50) NOT NULL COMMENT '子分类', + `order_weight` INT(11) DEFAULT 0 COMMENT '排序权重', + `view_count` INT(11) DEFAULT 0 COMMENT '浏览次数', + `is_active` TINYINT(1) DEFAULT 1 COMMENT '是否启用', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + INDEX `idx_category` (`category`), + INDEX `idx_sub_category` (`sub_category`), + INDEX `idx_active_order` (`is_active`, `order_weight`), + FULLTEXT INDEX `idx_title_content` (`title`, `content`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='帮助文章表'; + +-- 插入初始数据 - 账号分类 +INSERT INTO `help_articles` (`id`, `title`, `content`, `category`, `sub_category`, `order_weight`, `view_count`, `is_active`) +VALUES +('1', '如何认证', '详细介绍用户认证流程,包括身份验证、企业认证等内容。\n\n**认证步骤:**\n1. 登录账号\n2. 进入个人中心\n3. 点击认证管理\n4. 选择认证类型\n5. 填写相关信息并提交\n6. 等待审核', '账号', '如何认证', 1, 0, 1), + +('2', '修改登录密码', '提供修改登录密码的详细步骤和注意事项。\n\n**修改密码步骤:**\n1. 登录账号\n2. 进入账户安全设置\n3. 点击修改密码\n4. 输入当前密码和新密码\n5. 点击确认修改\n\n**密码要求:**\n- 长度8-20位\n- 包含字母和数字\n- 区分大小写', '账号', '修改登录密码', 2, 0, 1); + +-- 插入初始数据 - 派件/收件分类 +INSERT INTO `help_articles` (`id`, `title`, `content`, `category`, `sub_category`, `order_weight`, `view_count`, `is_active`) +VALUES +('3', '柜机安装/故障申报', '柜机安装流程和故障申报渠道说明。\n\n**柜机安装流程:**\n1. 联系客服预约安装\n2. 技术人员上门安装\n3. 设备调试和测试\n4. 培训操作人员\n\n**故障申报方式:**\n- 在线报修:登录后台 → 服务支持 → 故障申报\n- 电话报修:400-123-4567\n- 邮件报修:support@example.com', '派件/收件', '柜机安装/故障申报', 1, 0, 1), + +('4', '账户及提现问题', '关于账户余额和提现相关的常见问题解答。\n\n**提现流程:**\n1. 登录账号\n2. 进入财务管理\n3. 点击申请提现\n4. 填写提现金额和收款账户\n5. 提交申请\n\n**提现注意事项:**\n- 提现金额最低100元\n- 提现审核时间为1-3个工作日\n- 提现手续费为0.1%', '派件/收件', '账户及提现问题', 2, 0, 1), + +('5', '箱格租用', '箱格租用规则和计费标准说明。\n\n**租用流程:**\n1. 选择柜机位置\n2. 选择箱格类型和数量\n3. 确认租用周期\n4. 支付租金\n5. 开始使用\n\n**计费标准:**\n- 小箱:10元/月/格\n- 中箱:20元/月/格\n- 大箱:30元/月/格', '派件/收件', '箱格租用', 3, 0, 1); \ No newline at end of file diff --git a/insert_data.sql b/insert_data.sql deleted file mode 100644 index fd0ae6e632551c7e55389eb161d7538671123642..0000000000000000000000000000000000000000 Binary files a/insert_data.sql and /dev/null differ diff --git a/internal/biz/biz.go b/internal/biz/biz.go index 0da377ae37d0bbe96ce2044ff63315f0311c7c80..444cd8efbaca4ed886dff836a1d13b47de233a3a 100644 --- a/internal/biz/biz.go +++ b/internal/biz/biz.go @@ -2,12 +2,12 @@ package biz import ( "github.com/go-kratos/kratos/v2/errors" - v1 "Cabinet/api/helloworld/v1" + v1 "cabinet/api/helloworld/v1" "github.com/google/wire" ) // ProviderSet is biz providers. -var ProviderSet = wire.NewSet(NewGreeterUsecase, NewUserUsecase, NewCabinetUsecase) +var ProviderSet = wire.NewSet(NewGreeterUsecase, NewUserUsecase, NewHelpArticleUsecase) var ( // ErrUserNotFound is user not found. @@ -18,4 +18,8 @@ var ( ErrInvalidSmsCode = errors.New(401, "INVALID_SMS_CODE", "验证码错误") // ErrSmsCodeExpired 验证码已过期 ErrSmsCodeExpired = errors.New(401, "SMS_CODE_EXPIRED", "验证码已过期") + // ErrInvalidParameter 参数错误 + ErrInvalidParameter = errors.New(400, "INVALID_PARAMETER", "参数错误") + // ErrBillNotFound 账单不存在 + ErrBillNotFound = errors.New(404, "BILL_NOT_FOUND", "账单不存在") ) diff --git a/internal/biz/cabinet.go b/internal/biz/cabinet.go index eba288b8f0f7808998610131583a6af9ae1a6fff..ef0f87b11de4d43d0a8ddb0497bf5e0e18732fdf 100644 --- a/internal/biz/cabinet.go +++ b/internal/biz/cabinet.go @@ -1,7 +1,7 @@ package biz import ( - "Cabinet/internal/model" + "cabinet/internal/model" "context" "fmt" ) @@ -18,6 +18,14 @@ type CabinetRepo interface { DeleteFavoriteCabinet(ctx context.Context, userId int32, cabinetId int64) error // FindCabinetByParcelExpressNo 根据包裹号查询目的地柜机 FindCabinetByParcelExpressNo(ctx context.Context, expressNo string) (*model.Cabinet, *model.Parcel, error) + // ListCourierParcels 获取快递员的派件列表,按柜机分组 + ListCourierParcels(ctx context.Context, courierId int32, parcelStatus int32) (map[int64]*model.Cabinet, map[int64][]*model.Parcel, error) + // SyncLockerStatus 同步智能柜格口状态和可用数量 + // 根据包裹状态更新格口状态,并更新智能柜的可用格口数量 + SyncLockerStatus(ctx context.Context, cabinetId int64) error + // GetParcel 查询包裹详情 + // 支持两种查询方式:根据包裹ID或快递单号后5位 + GetParcel(ctx context.Context, parcelId int32, expressNoSuffix string) (*model.Parcel, *model.Cabinet, *model.Locker, error) } // CabinetUsecase 智能柜业务逻辑处理器 @@ -139,3 +147,73 @@ func (uc *CabinetUsecase) FindCabinetByParcelExpressNo(ctx context.Context, expr // 调用数据访问层查询 return uc.repo.FindCabinetByParcelExpressNo(ctx, expressNo) } + +// ListCourierParcels 获取快递员的派件列表,按柜机分组 +// 业务逻辑:根据快递员ID查询其派送的包裹,按柜机进行分组展示 +// 支持按包裹状态进行筛选,返回层级结构数据 +// ctx: 请求上下文 +// courierId: 快递员ID +// parcelStatus: 包裹状态(-1表示查询所有状态) +// 返回值: +// - map[int64]*model.Cabinet: 柜机ID到柜机信息的映射 +// - map[int64][]*model.Parcel: 柜机ID到包裹列表的映射 +// - error: 错误信息 +func (uc *CabinetUsecase) ListCourierParcels(ctx context.Context, courierId int32, parcelStatus int32) (map[int64]*model.Cabinet, map[int64][]*model.Parcel, error) { + // 参数验证 + if courierId <= 0 { + return nil, nil, fmt.Errorf("快递员ID无效") + } + + // 调用数据访问层查询 + return uc.repo.ListCourierParcels(ctx, courierId, parcelStatus) +} + +// SyncLockerStatus 同步智能柜格口状态和可用数量 +// 业务逻辑:根据包裹状态更新格口状态,确保格口使用数量与包裹状态一致 +// 1. 查询智能柜下所有格口 +// 2. 查询该智能柜下所有待取件和滞留件的包裹 +// 3. 根据包裹状态更新格口状态(有包裹且状态为待取件或滞留件 -> 占用,否则 -> 空闲) +// 4. 计算使用中的格口数量 +// 5. 更新智能柜的可用格口数量 = 总格口数 - 使用中的格口数 +// ctx: 请求上下文 +// cabinetId: 智能柜ID +// 返回值:错误信息 +func (uc *CabinetUsecase) SyncLockerStatus(ctx context.Context, cabinetId int64) error { + // 参数验证 + if cabinetId <= 0 { + return fmt.Errorf("智能柜ID无效") + } + + // 调用数据访问层同步格口状态 + return uc.repo.SyncLockerStatus(ctx, cabinetId) +} + +// GetParcel 查询包裹详情 +// 业务逻辑:根据包裹ID或快递单号后5位查询包裹详情 +// 支持两种查询方式: +// 1. 根据包裹ID查询(parcelId > 0) +// 2. 根据快递单号后5位查询(expressNoSuffix 不为空) +// ctx: 请求上下文 +// parcelId: 包裹ID(可选) +// expressNoSuffix: 快递单号后5位(可选) +// 返回值: +// - *model.Parcel: 包裹信息 +// - *model.Cabinet: 所在智能柜信息 +// - *model.Locker: 所在格口信息 +// - error: 错误信息 +func (uc *CabinetUsecase) GetParcel(ctx context.Context, parcelId int32, expressNoSuffix string) (*model.Parcel, *model.Cabinet, *model.Locker, error) { + // 参数验证:至少提供一个查询条件 + if parcelId <= 0 && expressNoSuffix == "" { + return nil, nil, nil, fmt.Errorf("请提供包裹ID或快递单号后5位") + } + + // 验证快递单号后5位格式 + if expressNoSuffix != "" { + if len(expressNoSuffix) != 5 { + return nil, nil, nil, fmt.Errorf("快递单号后5位格式不正确,应为5位字符") + } + } + + // 调用数据访问层查询 + return uc.repo.GetParcel(ctx, parcelId, expressNoSuffix) +} diff --git a/internal/biz/express_delivery.go b/internal/biz/express_delivery.go new file mode 100644 index 0000000000000000000000000000000000000000..694a5ec4119a9513a61f9d82c3f7a400b3443510 --- /dev/null +++ b/internal/biz/express_delivery.go @@ -0,0 +1,169 @@ +package biz + +import ( + "cabinet/internal/model" + "context" + "errors" + "time" +) + +// ExpressDeliveryRepo 快递通知仓库接口 +type ExpressDeliveryRepo interface { + // 创建快递通知 + CreateNotification(ctx context.Context, notification *model.ExpressDeliveryNotifications) error + // 根据用户ID获取通知列表 + GetNotificationsByUserID(ctx context.Context, userID int32, limit, offset int) ([]*model.ExpressDeliveryNotifications, error) + // 根据用户ID和消息类型获取通知列表 + GetNotificationsByUserIDAndType(ctx context.Context, userID int32, messageType int32, limit, offset int) ([]*model.ExpressDeliveryNotifications, error) + // 根据ID、用户ID和消息类型获取单个通知 + GetNotificationByIDAndUserAndType(ctx context.Context, id int32, userID int32, messageType int32) (*model.ExpressDeliveryNotifications, error) + // 获取用户未读通知数量 + GetUnreadCount(ctx context.Context, userID int32) (int64, error) + // 将通知标记为已读 + MarkAsRead(ctx context.Context, notificationID int32) error +} + +// ExpressDeliveryUsecase 快递通知业务接口 +type ExpressDeliveryUsecase interface { + // 发送快递通知 + SendNotification(ctx context.Context, userID int32, notificationType, content, remark, status string, messageType int32) error + // 获取用户通知列表 + GetUserNotifications(ctx context.Context, userID int32, page, pageSize int) ([]*model.ExpressDeliveryNotifications, int64, error) + // 根据用户ID和消息类型获取通知列表 + GetUserNotificationsByType(ctx context.Context, userID int32, messageType int32, page, pageSize int) ([]*model.ExpressDeliveryNotifications, int64, error) + // 根据ID、用户ID和消息类型获取单个通知 + GetNotificationByIDAndUserAndType(ctx context.Context, id int32, userID int32, messageType int32) (*model.ExpressDeliveryNotifications, error) + // 标记通知已读 + MarkNotificationRead(ctx context.Context, notificationID int32) error +} + +type expressDeliveryUsecase struct { + repo ExpressDeliveryRepo +} + +// NewExpressDeliveryUsecase 创建快递通知业务实例 +func NewExpressDeliveryUsecase(repo ExpressDeliveryRepo) ExpressDeliveryUsecase { + return &expressDeliveryUsecase{ + repo: repo, + } +} + +// SendNotification 发送快递通知 +func (uc *expressDeliveryUsecase) SendNotification(ctx context.Context, userID int32, notificationType, content, remark, status string, messageType int32) error { + if userID <= 0 { + return errors.New("用户ID无效") + } + + if notificationType == "" || content == "" { + return errors.New("通知类型和内容不能为空") + } + + // 设置默认状态 + if status == "" { + status = "未读" + } + + // 设置默认消息类型为系统消息 + if messageType <= 0 || messageType > 4 { + messageType = 1 // 默认1系统消息 + } + + // 创建通知记录 + notification := &model.ExpressDeliveryNotifications{ + UserId: userID, + NotificationType: notificationType, + NotificationContent: content, + MessageType: messageType, + Remark: remark, + Status: status, + CreateTime: time.Now(), + } + + // 保存到数据库 + return uc.repo.CreateNotification(ctx, notification) +} + +// GetUserNotifications 获取用户通知列表 +func (uc *expressDeliveryUsecase) GetUserNotifications(ctx context.Context, userID int32, page, pageSize int) ([]*model.ExpressDeliveryNotifications, int64, error) { + if userID <= 0 { + return nil, 0, errors.New("用户ID无效") + } + + // 验证分页参数 + if page <= 0 { + page = 1 + } + if pageSize <= 0 || pageSize > 100 { + pageSize = 20 + } + + // 计算偏移量 + offset := (page - 1) * pageSize + + // 获取通知列表 + notifications, err := uc.repo.GetNotificationsByUserID(ctx, userID, pageSize, offset) + if err != nil { + return nil, 0, err + } + + // 获取未读数量 + unreadCount, err := uc.repo.GetUnreadCount(ctx, userID) + if err != nil { + return nil, 0, err + } + + return notifications, unreadCount, nil +} + +// GetUserNotificationsByType 根据用户ID和消息类型获取通知列表 +func (uc *expressDeliveryUsecase) GetUserNotificationsByType(ctx context.Context, userID int32, messageType int32, page, pageSize int) ([]*model.ExpressDeliveryNotifications, int64, error) { + if userID <= 0 { + return nil, 0, errors.New("用户ID无效") + } + + // 验证分页参数 + if page <= 0 { + page = 1 + } + if pageSize <= 0 || pageSize > 100 { + pageSize = 20 + } + + // 计算偏移量 + offset := (page - 1) * pageSize + + // 获取通知列表 + notifications, err := uc.repo.GetNotificationsByUserIDAndType(ctx, userID, messageType, pageSize, offset) + if err != nil { + return nil, 0, err + } + + // 获取未读数量 + unreadCount, err := uc.repo.GetUnreadCount(ctx, userID) + if err != nil { + return nil, 0, err + } + + return notifications, unreadCount, nil +} + +// MarkNotificationRead 标记通知已读 +func (uc *expressDeliveryUsecase) MarkNotificationRead(ctx context.Context, notificationID int32) error { + if notificationID <= 0 { + return errors.New("通知ID无效") + } + + return uc.repo.MarkAsRead(ctx, notificationID) +} + +// GetNotificationByIDAndUserAndType 根据ID、用户ID和消息类型获取单个通知 +func (uc *expressDeliveryUsecase) GetNotificationByIDAndUserAndType(ctx context.Context, id int32, userID int32, messageType int32) (*model.ExpressDeliveryNotifications, error) { + if id <= 0 { + return nil, errors.New("通知ID无效") + } + if userID <= 0 { + return nil, errors.New("用户ID无效") + } + + return uc.repo.GetNotificationByIDAndUserAndType(ctx, id, userID, messageType) +} \ No newline at end of file diff --git a/internal/biz/greeter.go b/internal/biz/greeter.go index 69818cdf86c675bee8e9aeaf58a6b36e80d10304..dd9dcbcc603fb682602d0f22ba8084998f992dbb 100644 --- a/internal/biz/greeter.go +++ b/internal/biz/greeter.go @@ -2,7 +2,6 @@ package biz import ( "context" - "github.com/go-kratos/kratos/v2/log" ) diff --git a/internal/biz/help_article_usecase.go b/internal/biz/help_article_usecase.go new file mode 100644 index 0000000000000000000000000000000000000000..ed9b1174a7e6fa9bcd21f11d9261299b8711c787 --- /dev/null +++ b/internal/biz/help_article_usecase.go @@ -0,0 +1,102 @@ +package biz + +import ( + "cabinet/internal/model" + "context" + "fmt" + + "github.com/go-kratos/kratos/v2/log" +) + +// HelpArticleUsecase 帮助文章业务逻辑接口 +type HelpArticleUsecase interface { + GetCategories(ctx context.Context) ([]model.HelpCategory, error) + GetArticles(ctx context.Context, query model.ArticleQuery) ([]model.HelpArticle, int64, error) + SearchArticles(ctx context.Context, query model.ArticleQuery) ([]model.HelpArticle, int64, error) + GetArticleDetail(ctx context.Context, id string) (*model.HelpArticle, error) +} + +// HelpArticleRepo 帮助文章仓库接口 +type HelpArticleRepo interface { + GetCategories(ctx context.Context) ([]model.HelpCategory, error) + GetArticles(ctx context.Context, query model.ArticleQuery) ([]model.HelpArticle, int64, error) + SearchArticles(ctx context.Context, query model.ArticleQuery) ([]model.HelpArticle, int64, error) + GetArticleByID(ctx context.Context, id string) (*model.HelpArticle, error) + IncrementViewCount(ctx context.Context, id string) error +} + +// helpArticleUsecase 帮助文章业务逻辑实现 +type helpArticleUsecase struct { + repo HelpArticleRepo +} + +// NewHelpArticleUsecase 创建帮助文章业务逻辑实例 +func NewHelpArticleUsecase(repo HelpArticleRepo, logger log.Logger) HelpArticleUsecase { + return &helpArticleUsecase{repo: repo} +} + +// GetCategories 获取分类结构 +func (uc *helpArticleUsecase) GetCategories(ctx context.Context) ([]model.HelpCategory, error) { + categories, err := uc.repo.GetCategories(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get categories: %w", err) + } + return categories, nil +} + +// GetArticles 根据分类获取文章列表 +func (uc *helpArticleUsecase) GetArticles(ctx context.Context, query model.ArticleQuery) ([]model.HelpArticle, int64, error) { + // 参数验证 + if query.Page < 0 { + query.Page = 1 + } + if query.PageSize < 0 || query.PageSize > 100 { + query.PageSize = 10 + } + + articles, total, err := uc.repo.GetArticles(ctx, query) + if err != nil { + return nil, 0, fmt.Errorf("failed to get articles: %w", err) + } + + return articles, total, nil +} + +// SearchArticles 搜索文章 +func (uc *helpArticleUsecase) SearchArticles(ctx context.Context, query model.ArticleQuery) ([]model.HelpArticle, int64, error) { + // 参数验证 + if query.Page < 0 { + query.Page = 1 + } + if query.PageSize < 0 || query.PageSize > 100 { + query.PageSize = 10 + } + + articles, total, err := uc.repo.SearchArticles(ctx, query) + if err != nil { + return nil, 0, fmt.Errorf("failed to search articles: %w", err) + } + + return articles, total, nil +} + +// GetArticleDetail 获取文章详情 +func (uc *helpArticleUsecase) GetArticleDetail(ctx context.Context, id string) (*model.HelpArticle, error) { + // 参数验证 + if id == "" { + return nil, fmt.Errorf("article id is required") + } + + // 获取文章详情 + article, err := uc.repo.GetArticleByID(ctx, id) + if err != nil { + return nil, fmt.Errorf("failed to get article detail: %w", err) + } + + // 异步增加浏览次数(不阻塞主流程) + go func() { + _ = uc.repo.IncrementViewCount(context.Background(), id) + }() + + return article, nil +} \ No newline at end of file diff --git a/internal/biz/real.go b/internal/biz/real.go new file mode 100644 index 0000000000000000000000000000000000000000..07a7b9582894ba79cb98c82fa6668f6f0705d1c0 --- /dev/null +++ b/internal/biz/real.go @@ -0,0 +1,159 @@ +package biz + +import ( + "cabinet/internal/model" + "context" + "errors" + "time" +) + +// RealRepo 实名认证仓库接口 +type RealRepo interface { + // 检查用户是否已实名认证 + CheckUserRealName(ctx context.Context, userId int) (bool, error) + // 检查身份证是否已被使用 + CheckCardUsed(ctx context.Context, card string) (bool, error) + // 创建实名认证记录 + CreateRealName(ctx context.Context, realName *model.RealName) error + // 使用第三方服务验证真实姓名和身份证 + VerifyRealName(ctx context.Context, cardNo, realName string) (bool, error) + // 检查用户是否已进行快递员认证 + CheckCourierAuth(ctx context.Context, userId int) (bool, error) + // 创建快递员认证记录 + CreateCourierAuth(ctx context.Context, auth *model.CourierAuth) error + // 根据用户ID获取快递员认证信息 + GetCourierAuthByUserId(ctx context.Context, userId int) (*model.CourierAuth, error) +} + +type RealUsecase interface { + // 实名认证 + RealName(ctx context.Context, req *RealNameRequest) (*RealNameReply, error) + // 快递员认证 + CourierAuthAdd(ctx context.Context, req *CourierAuthAddRequest) (*CourierAuthAddReply, error) +} + +type RealNameRequest struct { + Card string + Name string + UserId int +} + +type RealNameReply struct { + UserId int +} + +type CourierAuthAddRequest struct { + UserId int + CompanyId int + CardPhotoUrl string +} + +type CourierAuthAddReply struct { + UserId int +} + +type realUsecase struct { + realRepo RealRepo +} + +func NewRealUsecase(realRepo RealRepo) RealUsecase { + return &realUsecase{realRepo: realRepo} +} + +// RealName 实名认证 +func (uc *realUsecase) RealName(ctx context.Context, req *RealNameRequest) (*RealNameReply, error) { + // 1. 验证用户登录状态 + if req.UserId <= 0 { + return nil, errors.New("用户未登录") + } + + // 2. 验证请求参数 + if req.Card == "" || req.Name == "" { + return nil, errors.New("身份证号和姓名不能为空") + } + + // 3. 检查用户是否已实名认证 + isRealName, err := uc.realRepo.CheckUserRealName(ctx, req.UserId) + if err != nil { + return nil, errors.New("检查认证状态失败") + } + if isRealName { + return nil, errors.New("用户已完成实名认证") + } + + // 4. 检查身份证是否已被使用 + isCardUsed, err := uc.realRepo.CheckCardUsed(ctx, req.Card) + if err != nil { + return nil, errors.New("检查身份证使用情况失败") + } + if isCardUsed { + return nil, errors.New("该身份证已被其他用户使用") + } + + // 5. 使用第三方服务验证真实姓名和身份证 + isVerified, err := uc.realRepo.VerifyRealName(ctx, req.Card, req.Name) + if err != nil { + return nil, errors.New("第三方认证服务异常") + } + if !isVerified { + return nil, errors.New("身份证号与姓名不匹配") + } + + // 6. 创建实名认证记录 + realName := &model.RealName{ + UserId: req.UserId, + Card: req.Card, + Name: req.Name, + } + err = uc.realRepo.CreateRealName(ctx, realName) + if err != nil { + return nil, errors.New("保存认证记录失败") + } + + return &RealNameReply{UserId: req.UserId}, nil +} + +// CourierAuthAdd 快递员认证 +func (uc *realUsecase) CourierAuthAdd(ctx context.Context, req *CourierAuthAddRequest) (*CourierAuthAddReply, error) { + // 1. 验证用户ID + if req.UserId <= 0 { + return nil, errors.New("用户ID无效") + } + + // 2. 验证公司ID + if req.CompanyId <= 0 { + return nil, errors.New("公司ID无效") + } + + // 3. 验证工牌照片URL + if req.CardPhotoUrl == "" { + return nil, errors.New("工牌照片不能为空") + } + + // 4. 检查用户是否已实名认证 + + // 5. 检查用户是否已进行快递员认证 + isAuth, err := uc.realRepo.CheckCourierAuth(ctx, req.UserId) + if err != nil { + return nil, errors.New("检查认证状态失败") + } + if isAuth { + return nil, errors.New("您已进行过快递员认证,无法重复认证") + } + + // 6. 创建快递员认证记录 + courierAuth := &model.CourierAuth{ + UserId: int32(req.UserId), + CompanyId: int32(req.CompanyId), + CardPhotoUrl: req.CardPhotoUrl, + Status: 0, // 0-待审核 + CreateTime: time.Now(), + UpdateTime: time.Now(), + } + + if err := uc.realRepo.CreateCourierAuth(ctx, courierAuth); err != nil { + return nil, errors.New("创建认证记录失败") + } + + return &CourierAuthAddReply{UserId: req.UserId}, nil +} diff --git a/internal/biz/user.go b/internal/biz/user.go index 716260c13dd803732f43a76b1bd78b065217ec99..d4ba498d102062dd7d8ac41bf738ec596f2e9ba1 100644 --- a/internal/biz/user.go +++ b/internal/biz/user.go @@ -1,7 +1,7 @@ package biz import ( - "Cabinet/internal/model" + "cabinet/internal/model" "context" ) @@ -10,6 +10,8 @@ type UserRepo interface { CreateUser(ctx context.Context, user *model.User) error GetUserByPhone(ctx context.Context, phone string) (*model.User, error) CheckUserExists(ctx context.Context, phone string) (bool, error) + UpdatePassword(ctx context.Context, phone, password string) error + GetUserByID(ctx context.Context, id int64) (*model.User, error) } // UserUsecase 用户业务逻辑 @@ -45,11 +47,20 @@ func (uc *UserUsecase) Register(ctx context.Context, phone, password string) (*m if err != nil { return nil, err } - return user, nil } // GetUserByPhone 根据手机号获取用户 func (uc *UserUsecase) GetUserByPhone(ctx context.Context, phone string) (*model.User, error) { return uc.repo.GetUserByPhone(ctx, phone) -} \ No newline at end of file +} + +// UpdatePassword 更新用户密码 +func (uc *UserUsecase) UpdatePassword(ctx context.Context, phone, password string) error { + return uc.repo.UpdatePassword(ctx, phone, password) +} + +// GetUserByID 根据用户ID获取用户 +func (uc *UserUsecase) GetUserByID(ctx context.Context, id int64) (*model.User, error) { + return uc.repo.GetUserByID(ctx, id) +} diff --git a/internal/conf/alipay.go b/internal/conf/alipay.go new file mode 100644 index 0000000000000000000000000000000000000000..fdeb5acb959322f1cdb91c43f059e0d9e39c9568 --- /dev/null +++ b/internal/conf/alipay.go @@ -0,0 +1,7 @@ +package conf + +type Alipay struct { + AppId string `json:"app_id"` + PublicKey string `json:"public_key"` + SkipVerify bool `json:"skip_verify"` +} \ No newline at end of file diff --git a/internal/conf/conf.pb.go b/internal/conf/conf.pb.go index 08ae47c7b973a081d6116e3ca4db5932ef50f582..5a0d16a7f49843a8130570f1e92537382b8f2b73 100644 --- a/internal/conf/conf.pb.go +++ b/internal/conf/conf.pb.go @@ -1,8 +1,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 +// protoc-gen-go v1.36.10 // protoc v3.19.4 -// source: internal/conf/conf.proto +// source: conf.proto package conf @@ -12,6 +12,7 @@ import ( durationpb "google.golang.org/protobuf/types/known/durationpb" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -22,21 +23,18 @@ const ( ) type Bootstrap struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Server *Server `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + Data *Data `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` unknownFields protoimpl.UnknownFields - - Server *Server `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` - Data *Data `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Bootstrap) Reset() { *x = Bootstrap{} - if protoimpl.UnsafeEnabled { - mi := &file_internal_conf_conf_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_conf_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Bootstrap) String() string { @@ -46,8 +44,8 @@ func (x *Bootstrap) String() string { func (*Bootstrap) ProtoMessage() {} func (x *Bootstrap) ProtoReflect() protoreflect.Message { - mi := &file_internal_conf_conf_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_conf_proto_msgTypes[0] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -59,7 +57,7 @@ func (x *Bootstrap) ProtoReflect() protoreflect.Message { // Deprecated: Use Bootstrap.ProtoReflect.Descriptor instead. func (*Bootstrap) Descriptor() ([]byte, []int) { - return file_internal_conf_conf_proto_rawDescGZIP(), []int{0} + return file_conf_proto_rawDescGZIP(), []int{0} } func (x *Bootstrap) GetServer() *Server { @@ -77,21 +75,18 @@ func (x *Bootstrap) GetData() *Data { } type Server struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Http *Server_HTTP `protobuf:"bytes,1,opt,name=http,proto3" json:"http,omitempty"` + Grpc *Server_GRPC `protobuf:"bytes,2,opt,name=grpc,proto3" json:"grpc,omitempty"` unknownFields protoimpl.UnknownFields - - Http *Server_HTTP `protobuf:"bytes,1,opt,name=http,proto3" json:"http,omitempty"` - Grpc *Server_GRPC `protobuf:"bytes,2,opt,name=grpc,proto3" json:"grpc,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Server) Reset() { *x = Server{} - if protoimpl.UnsafeEnabled { - mi := &file_internal_conf_conf_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_conf_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Server) String() string { @@ -101,8 +96,8 @@ func (x *Server) String() string { func (*Server) ProtoMessage() {} func (x *Server) ProtoReflect() protoreflect.Message { - mi := &file_internal_conf_conf_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_conf_proto_msgTypes[1] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -114,7 +109,7 @@ func (x *Server) ProtoReflect() protoreflect.Message { // Deprecated: Use Server.ProtoReflect.Descriptor instead. func (*Server) Descriptor() ([]byte, []int) { - return file_internal_conf_conf_proto_rawDescGZIP(), []int{1} + return file_conf_proto_rawDescGZIP(), []int{1} } func (x *Server) GetHttp() *Server_HTTP { @@ -132,22 +127,19 @@ func (x *Server) GetGrpc() *Server_GRPC { } type Data struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Database *Data_Database `protobuf:"bytes,1,opt,name=database,proto3" json:"database,omitempty"` + Redis *Data_Redis `protobuf:"bytes,2,opt,name=redis,proto3" json:"redis,omitempty"` + Mqtt *Data_MQTT `protobuf:"bytes,3,opt,name=mqtt,proto3" json:"mqtt,omitempty"` unknownFields protoimpl.UnknownFields - - Database *Data_Database `protobuf:"bytes,1,opt,name=database,proto3" json:"database,omitempty"` - Redis *Data_Redis `protobuf:"bytes,2,opt,name=redis,proto3" json:"redis,omitempty"` - Map *Data_Map `protobuf:"bytes,3,opt,name=map,proto3" json:"map,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Data) Reset() { *x = Data{} - if protoimpl.UnsafeEnabled { - mi := &file_internal_conf_conf_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_conf_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Data) String() string { @@ -157,8 +149,8 @@ func (x *Data) String() string { func (*Data) ProtoMessage() {} func (x *Data) ProtoReflect() protoreflect.Message { - mi := &file_internal_conf_conf_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_conf_proto_msgTypes[2] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -170,7 +162,7 @@ func (x *Data) ProtoReflect() protoreflect.Message { // Deprecated: Use Data.ProtoReflect.Descriptor instead. func (*Data) Descriptor() ([]byte, []int) { - return file_internal_conf_conf_proto_rawDescGZIP(), []int{2} + return file_conf_proto_rawDescGZIP(), []int{2} } func (x *Data) GetDatabase() *Data_Database { @@ -187,30 +179,27 @@ func (x *Data) GetRedis() *Data_Redis { return nil } -func (x *Data) GetMap() *Data_Map { +func (x *Data) GetMqtt() *Data_MQTT { if x != nil { - return x.Map + return x.Mqtt } return nil } type Server_HTTP struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` + Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` + Timeout *durationpb.Duration `protobuf:"bytes,3,opt,name=timeout,proto3" json:"timeout,omitempty"` unknownFields protoimpl.UnknownFields - - Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` - Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` - Timeout *durationpb.Duration `protobuf:"bytes,3,opt,name=timeout,proto3" json:"timeout,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Server_HTTP) Reset() { *x = Server_HTTP{} - if protoimpl.UnsafeEnabled { - mi := &file_internal_conf_conf_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_conf_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Server_HTTP) String() string { @@ -220,8 +209,8 @@ func (x *Server_HTTP) String() string { func (*Server_HTTP) ProtoMessage() {} func (x *Server_HTTP) ProtoReflect() protoreflect.Message { - mi := &file_internal_conf_conf_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_conf_proto_msgTypes[3] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -233,7 +222,7 @@ func (x *Server_HTTP) ProtoReflect() protoreflect.Message { // Deprecated: Use Server_HTTP.ProtoReflect.Descriptor instead. func (*Server_HTTP) Descriptor() ([]byte, []int) { - return file_internal_conf_conf_proto_rawDescGZIP(), []int{1, 0} + return file_conf_proto_rawDescGZIP(), []int{1, 0} } func (x *Server_HTTP) GetNetwork() string { @@ -258,22 +247,19 @@ func (x *Server_HTTP) GetTimeout() *durationpb.Duration { } type Server_GRPC struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` + Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` + Timeout *durationpb.Duration `protobuf:"bytes,3,opt,name=timeout,proto3" json:"timeout,omitempty"` unknownFields protoimpl.UnknownFields - - Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` - Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` - Timeout *durationpb.Duration `protobuf:"bytes,3,opt,name=timeout,proto3" json:"timeout,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Server_GRPC) Reset() { *x = Server_GRPC{} - if protoimpl.UnsafeEnabled { - mi := &file_internal_conf_conf_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_conf_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Server_GRPC) String() string { @@ -283,8 +269,8 @@ func (x *Server_GRPC) String() string { func (*Server_GRPC) ProtoMessage() {} func (x *Server_GRPC) ProtoReflect() protoreflect.Message { - mi := &file_internal_conf_conf_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_conf_proto_msgTypes[4] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -296,7 +282,7 @@ func (x *Server_GRPC) ProtoReflect() protoreflect.Message { // Deprecated: Use Server_GRPC.ProtoReflect.Descriptor instead. func (*Server_GRPC) Descriptor() ([]byte, []int) { - return file_internal_conf_conf_proto_rawDescGZIP(), []int{1, 1} + return file_conf_proto_rawDescGZIP(), []int{1, 1} } func (x *Server_GRPC) GetNetwork() string { @@ -321,21 +307,18 @@ func (x *Server_GRPC) GetTimeout() *durationpb.Duration { } type Data_Database struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Driver string `protobuf:"bytes,1,opt,name=driver,proto3" json:"driver,omitempty"` + Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` unknownFields protoimpl.UnknownFields - - Driver string `protobuf:"bytes,1,opt,name=driver,proto3" json:"driver,omitempty"` - Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Data_Database) Reset() { *x = Data_Database{} - if protoimpl.UnsafeEnabled { - mi := &file_internal_conf_conf_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_conf_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Data_Database) String() string { @@ -345,8 +328,8 @@ func (x *Data_Database) String() string { func (*Data_Database) ProtoMessage() {} func (x *Data_Database) ProtoReflect() protoreflect.Message { - mi := &file_internal_conf_conf_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_conf_proto_msgTypes[5] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -358,7 +341,7 @@ func (x *Data_Database) ProtoReflect() protoreflect.Message { // Deprecated: Use Data_Database.ProtoReflect.Descriptor instead. func (*Data_Database) Descriptor() ([]byte, []int) { - return file_internal_conf_conf_proto_rawDescGZIP(), []int{2, 0} + return file_conf_proto_rawDescGZIP(), []int{2, 0} } func (x *Data_Database) GetDriver() string { @@ -376,23 +359,20 @@ func (x *Data_Database) GetSource() string { } type Data_Redis struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` + Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` + ReadTimeout *durationpb.Duration `protobuf:"bytes,3,opt,name=read_timeout,json=readTimeout,proto3" json:"read_timeout,omitempty"` + WriteTimeout *durationpb.Duration `protobuf:"bytes,4,opt,name=write_timeout,json=writeTimeout,proto3" json:"write_timeout,omitempty"` unknownFields protoimpl.UnknownFields - - Network string `protobuf:"bytes,1,opt,name=network,proto3" json:"network,omitempty"` - Addr string `protobuf:"bytes,2,opt,name=addr,proto3" json:"addr,omitempty"` - ReadTimeout *durationpb.Duration `protobuf:"bytes,3,opt,name=read_timeout,json=readTimeout,proto3" json:"read_timeout,omitempty"` - WriteTimeout *durationpb.Duration `protobuf:"bytes,4,opt,name=write_timeout,json=writeTimeout,proto3" json:"write_timeout,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Data_Redis) Reset() { *x = Data_Redis{} - if protoimpl.UnsafeEnabled { - mi := &file_internal_conf_conf_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_conf_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Data_Redis) String() string { @@ -402,8 +382,8 @@ func (x *Data_Redis) String() string { func (*Data_Redis) ProtoMessage() {} func (x *Data_Redis) ProtoReflect() protoreflect.Message { - mi := &file_internal_conf_conf_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_conf_proto_msgTypes[6] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -415,7 +395,7 @@ func (x *Data_Redis) ProtoReflect() protoreflect.Message { // Deprecated: Use Data_Redis.ProtoReflect.Descriptor instead. func (*Data_Redis) Descriptor() ([]byte, []int) { - return file_internal_conf_conf_proto_rawDescGZIP(), []int{2, 1} + return file_conf_proto_rawDescGZIP(), []int{2, 1} } func (x *Data_Redis) GetNetwork() string { @@ -446,32 +426,33 @@ func (x *Data_Redis) GetWriteTimeout() *durationpb.Duration { return nil } -type Data_Map struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type Data_MQTT struct { + state protoimpl.MessageState `protogen:"open.v1"` + Broker string `protobuf:"bytes,1,opt,name=broker,proto3" json:"broker,omitempty"` // MQTT Broker地址,如 "tcp://localhost:1883" + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` // 客户端ID + Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"` // 用户名(可选) + Password string `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"` // 密码(可选) + Qos int32 `protobuf:"varint,5,opt,name=qos,proto3" json:"qos,omitempty"` // 服务质量等级(0, 1, 2) unknownFields protoimpl.UnknownFields - - ApiKey string `protobuf:"bytes,1,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` + sizeCache protoimpl.SizeCache } -func (x *Data_Map) Reset() { - *x = Data_Map{} - if protoimpl.UnsafeEnabled { - mi := &file_internal_conf_conf_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } +func (x *Data_MQTT) Reset() { + *x = Data_MQTT{} + mi := &file_conf_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } -func (x *Data_Map) String() string { +func (x *Data_MQTT) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Data_Map) ProtoMessage() {} +func (*Data_MQTT) ProtoMessage() {} -func (x *Data_Map) ProtoReflect() protoreflect.Message { - mi := &file_internal_conf_conf_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { +func (x *Data_MQTT) ProtoReflect() protoreflect.Message { + mi := &file_conf_proto_msgTypes[7] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -481,96 +462,100 @@ func (x *Data_Map) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Data_Map.ProtoReflect.Descriptor instead. -func (*Data_Map) Descriptor() ([]byte, []int) { - return file_internal_conf_conf_proto_rawDescGZIP(), []int{2, 2} +// Deprecated: Use Data_MQTT.ProtoReflect.Descriptor instead. +func (*Data_MQTT) Descriptor() ([]byte, []int) { + return file_conf_proto_rawDescGZIP(), []int{2, 2} +} + +func (x *Data_MQTT) GetBroker() string { + if x != nil { + return x.Broker + } + return "" +} + +func (x *Data_MQTT) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" } -func (x *Data_Map) GetApiKey() string { +func (x *Data_MQTT) GetUsername() string { if x != nil { - return x.ApiKey + return x.Username } return "" } -var File_internal_conf_conf_proto protoreflect.FileDescriptor - -var file_internal_conf_conf_proto_rawDesc = []byte{ - 0x0a, 0x18, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x2f, - 0x63, 0x6f, 0x6e, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x6b, 0x72, 0x61, 0x74, - 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5d, 0x0a, 0x09, 0x42, 0x6f, 0x6f, 0x74, 0x73, 0x74, - 0x72, 0x61, 0x70, 0x12, 0x2a, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, - 0x24, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xb8, 0x02, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x12, 0x2b, 0x0a, 0x04, 0x68, 0x74, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, - 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x12, 0x2b, 0x0a, - 0x04, 0x67, 0x72, 0x70, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6b, 0x72, - 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, - 0x47, 0x52, 0x50, 0x43, 0x52, 0x04, 0x67, 0x72, 0x70, 0x63, 0x1a, 0x69, 0x0a, 0x04, 0x48, 0x54, - 0x54, 0x50, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, - 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, - 0x12, 0x33, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, - 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x69, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x12, 0x18, 0x0a, - 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x74, - 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, - 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x22, 0xa5, 0x03, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6b, 0x72, - 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x44, 0x61, - 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, - 0x12, 0x2c, 0x0a, 0x05, 0x72, 0x65, 0x64, 0x69, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, - 0x61, 0x2e, 0x52, 0x65, 0x64, 0x69, 0x73, 0x52, 0x05, 0x72, 0x65, 0x64, 0x69, 0x73, 0x12, 0x26, - 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x6b, 0x72, - 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x4d, 0x61, - 0x70, 0x52, 0x03, 0x6d, 0x61, 0x70, 0x1a, 0x3a, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x1a, 0xb3, 0x01, 0x0a, 0x05, 0x52, 0x65, 0x64, 0x69, 0x73, 0x12, 0x18, 0x0a, 0x07, - 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, - 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x3c, 0x0a, 0x0c, 0x72, 0x65, - 0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x72, 0x65, 0x61, - 0x64, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x3e, 0x0a, 0x0d, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x77, 0x72, 0x69, 0x74, - 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x1a, 0x1e, 0x0a, 0x03, 0x4d, 0x61, 0x70, 0x12, - 0x17, 0x0a, 0x07, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x42, 0x1c, 0x5a, 0x1a, 0x43, 0x61, 0x62, 0x69, - 0x6e, 0x65, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, - 0x66, 0x3b, 0x63, 0x6f, 0x6e, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +func (x *Data_MQTT) GetPassword() string { + if x != nil { + return x.Password + } + return "" } +func (x *Data_MQTT) GetQos() int32 { + if x != nil { + return x.Qos + } + return 0 +} + +var File_conf_proto protoreflect.FileDescriptor + +const file_conf_proto_rawDesc = "" + + "\n" + + "\n" + + "conf.proto\x12\n" + + "kratos.api\x1a\x1egoogle/protobuf/duration.proto\"]\n" + + "\tBootstrap\x12*\n" + + "\x06server\x18\x01 \x01(\v2\x12.kratos.api.ServerR\x06server\x12$\n" + + "\x04data\x18\x02 \x01(\v2\x10.kratos.api.DataR\x04data\"\xb8\x02\n" + + "\x06Server\x12+\n" + + "\x04http\x18\x01 \x01(\v2\x17.kratos.api.Server.HTTPR\x04http\x12+\n" + + "\x04grpc\x18\x02 \x01(\v2\x17.kratos.api.Server.GRPCR\x04grpc\x1ai\n" + + "\x04HTTP\x12\x18\n" + + "\anetwork\x18\x01 \x01(\tR\anetwork\x12\x12\n" + + "\x04addr\x18\x02 \x01(\tR\x04addr\x123\n" + + "\atimeout\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\atimeout\x1ai\n" + + "\x04GRPC\x12\x18\n" + + "\anetwork\x18\x01 \x01(\tR\anetwork\x12\x12\n" + + "\x04addr\x18\x02 \x01(\tR\x04addr\x123\n" + + "\atimeout\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\atimeout\"\x90\x04\n" + + "\x04Data\x125\n" + + "\bdatabase\x18\x01 \x01(\v2\x19.kratos.api.Data.DatabaseR\bdatabase\x12,\n" + + "\x05redis\x18\x02 \x01(\v2\x16.kratos.api.Data.RedisR\x05redis\x12)\n" + + "\x04mqtt\x18\x03 \x01(\v2\x15.kratos.api.Data.MQTTR\x04mqtt\x1a:\n" + + "\bDatabase\x12\x16\n" + + "\x06driver\x18\x01 \x01(\tR\x06driver\x12\x16\n" + + "\x06source\x18\x02 \x01(\tR\x06source\x1a\xb3\x01\n" + + "\x05Redis\x12\x18\n" + + "\anetwork\x18\x01 \x01(\tR\anetwork\x12\x12\n" + + "\x04addr\x18\x02 \x01(\tR\x04addr\x12<\n" + + "\fread_timeout\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\vreadTimeout\x12>\n" + + "\rwrite_timeout\x18\x04 \x01(\v2\x19.google.protobuf.DurationR\fwriteTimeout\x1a\x85\x01\n" + + "\x04MQTT\x12\x16\n" + + "\x06broker\x18\x01 \x01(\tR\x06broker\x12\x1b\n" + + "\tclient_id\x18\x02 \x01(\tR\bclientId\x12\x1a\n" + + "\busername\x18\x03 \x01(\tR\busername\x12\x1a\n" + + "\bpassword\x18\x04 \x01(\tR\bpassword\x12\x10\n" + + "\x03qos\x18\x05 \x01(\x05R\x03qosB\x1cZ\x1aCabinet/internal/conf;confb\x06proto3" + var ( - file_internal_conf_conf_proto_rawDescOnce sync.Once - file_internal_conf_conf_proto_rawDescData = file_internal_conf_conf_proto_rawDesc + file_conf_proto_rawDescOnce sync.Once + file_conf_proto_rawDescData []byte ) -func file_internal_conf_conf_proto_rawDescGZIP() []byte { - file_internal_conf_conf_proto_rawDescOnce.Do(func() { - file_internal_conf_conf_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_conf_conf_proto_rawDescData) +func file_conf_proto_rawDescGZIP() []byte { + file_conf_proto_rawDescOnce.Do(func() { + file_conf_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_conf_proto_rawDesc), len(file_conf_proto_rawDesc))) }) - return file_internal_conf_conf_proto_rawDescData + return file_conf_proto_rawDescData } -var file_internal_conf_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 8) -var file_internal_conf_conf_proto_goTypes = []any{ +var file_conf_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_conf_proto_goTypes = []any{ (*Bootstrap)(nil), // 0: kratos.api.Bootstrap (*Server)(nil), // 1: kratos.api.Server (*Data)(nil), // 2: kratos.api.Data @@ -578,17 +563,17 @@ var file_internal_conf_conf_proto_goTypes = []any{ (*Server_GRPC)(nil), // 4: kratos.api.Server.GRPC (*Data_Database)(nil), // 5: kratos.api.Data.Database (*Data_Redis)(nil), // 6: kratos.api.Data.Redis - (*Data_Map)(nil), // 7: kratos.api.Data.Map + (*Data_MQTT)(nil), // 7: kratos.api.Data.MQTT (*durationpb.Duration)(nil), // 8: google.protobuf.Duration } -var file_internal_conf_conf_proto_depIdxs = []int32{ +var file_conf_proto_depIdxs = []int32{ 1, // 0: kratos.api.Bootstrap.server:type_name -> kratos.api.Server 2, // 1: kratos.api.Bootstrap.data:type_name -> kratos.api.Data 3, // 2: kratos.api.Server.http:type_name -> kratos.api.Server.HTTP 4, // 3: kratos.api.Server.grpc:type_name -> kratos.api.Server.GRPC 5, // 4: kratos.api.Data.database:type_name -> kratos.api.Data.Database 6, // 5: kratos.api.Data.redis:type_name -> kratos.api.Data.Redis - 7, // 6: kratos.api.Data.map:type_name -> kratos.api.Data.Map + 7, // 6: kratos.api.Data.mqtt:type_name -> kratos.api.Data.MQTT 8, // 7: kratos.api.Server.HTTP.timeout:type_name -> google.protobuf.Duration 8, // 8: kratos.api.Server.GRPC.timeout:type_name -> google.protobuf.Duration 8, // 9: kratos.api.Data.Redis.read_timeout:type_name -> google.protobuf.Duration @@ -600,125 +585,26 @@ var file_internal_conf_conf_proto_depIdxs = []int32{ 0, // [0:11] is the sub-list for field type_name } -func init() { file_internal_conf_conf_proto_init() } -func file_internal_conf_conf_proto_init() { - if File_internal_conf_conf_proto != nil { +func init() { file_conf_proto_init() } +func file_conf_proto_init() { + if File_conf_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_internal_conf_conf_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*Bootstrap); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_internal_conf_conf_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*Server); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_internal_conf_conf_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*Data); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_internal_conf_conf_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*Server_HTTP); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_internal_conf_conf_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*Server_GRPC); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_internal_conf_conf_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*Data_Database); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_internal_conf_conf_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*Data_Redis); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_internal_conf_conf_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*Data_Map); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_internal_conf_conf_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_conf_proto_rawDesc), len(file_conf_proto_rawDesc)), NumEnums: 0, NumMessages: 8, NumExtensions: 0, NumServices: 0, }, - GoTypes: file_internal_conf_conf_proto_goTypes, - DependencyIndexes: file_internal_conf_conf_proto_depIdxs, - MessageInfos: file_internal_conf_conf_proto_msgTypes, + GoTypes: file_conf_proto_goTypes, + DependencyIndexes: file_conf_proto_depIdxs, + MessageInfos: file_conf_proto_msgTypes, }.Build() - File_internal_conf_conf_proto = out.File - file_internal_conf_conf_proto_rawDesc = nil - file_internal_conf_conf_proto_goTypes = nil - file_internal_conf_conf_proto_depIdxs = nil + File_conf_proto = out.File + file_conf_proto_goTypes = nil + file_conf_proto_depIdxs = nil } diff --git a/internal/conf/conf.proto b/internal/conf/conf.proto index eb5bdd947b78ffe6999430d14b028d1d2ff1fe58..9d46352ba506a3b5b26f6ffa3ecfaeae2334f813 100644 --- a/internal/conf/conf.proto +++ b/internal/conf/conf.proto @@ -36,6 +36,14 @@ message Data { google.protobuf.Duration read_timeout = 3; google.protobuf.Duration write_timeout = 4; } + message MQTT { + string broker = 1; // MQTT Broker地址,如 "tcp://localhost:1883" + string client_id = 2; // 客户端ID + string username = 3; // 用户名(可选) + string password = 4; // 密码(可选) + int32 qos = 5; // 服务质量等级(0, 1, 2) + } Database database = 1; Redis redis = 2; + MQTT mqtt = 3; } diff --git a/internal/data/bill.go b/internal/data/bill.go new file mode 100644 index 0000000000000000000000000000000000000000..afd7acf663ded236cb1c42c51dcf9f3012403362 --- /dev/null +++ b/internal/data/bill.go @@ -0,0 +1,21 @@ +package data + +import ( + "cabinet/internal/biz" + + "github.com/go-kratos/kratos/v2/log" +) + +// BillRepo 账单数据访问实现 +type billRepo struct { + data *Data + log *log.Helper +} + +// NewBillRepo 创建账单数据访问实例 +func NewBillRepo(data *Data, logger log.Logger) biz.GreeterRepo { + return &greeterRepo{ + data: data, + log: log.NewHelper(logger), + } +} diff --git a/internal/data/cabinet.go b/internal/data/cabinet.go index c99d0f2bd991cdfc78aed12c438d36a7adf7a823..f21183a4feaab4228ae644f1336a3cf81cd54b1b 100644 --- a/internal/data/cabinet.go +++ b/internal/data/cabinet.go @@ -1,8 +1,8 @@ package data import ( - "Cabinet/internal/biz" - "Cabinet/internal/model" + "cabinet/internal/biz" + "cabinet/internal/model" "context" "fmt" "math" @@ -81,7 +81,7 @@ func (r *cabinetRepo) FindNearbyCabinets(ctx context.Context, latitude, longitud // 构建数据库查询:过滤出有经纬度信息且状态正常的柜机 query := r.data.db.WithContext(ctx). Where("latitude IS NOT NULL AND longitude IS NOT NULL"). // 确保有位置信息 - Where("status = ?", 1) // 状态为正常(1) + Where("status = ?", 1) // 状态为正常(1) // 执行数据库查询 err := query.Find(&cabinets).Error @@ -316,3 +316,312 @@ func (r *cabinetRepo) FindCabinetByParcelExpressNo(ctx context.Context, expressN r.log.Infof("根据包裹号查询柜机成功,快递单号: %s, 柜机ID: %d", expressNo, cabinet.Id) return &cabinet, &parcel, nil } + +// ListCourierParcels 获取快递员的派件列表,按柜机分组 +// ctx: 上下文 +// courierId: 快递员ID +// parcelStatus: 包裹状态(-1表示查询所有状态) +// 返回值: +// - map[int64]*model.Cabinet: 柜机ID到柜机信息的映射 +// - map[int64][]*model.Parcel: 柜机ID到包裹列表的映射 +// - error: 错误信息 +func (r *cabinetRepo) ListCourierParcels(ctx context.Context, courierId int32, parcelStatus int32) (map[int64]*model.Cabinet, map[int64][]*model.Parcel, error) { + // 第一步:查询快递员的包裹列表 + var parcels []*model.Parcel + query := r.data.db.WithContext(ctx).Where("courier_id = ?", courierId) + + // 如果指定了包裹状态,添加状态筛选条件 + if parcelStatus >= 0 { + query = query.Where("parcel_status = ?", parcelStatus) + } + + err := query.Find(&parcels).Error + if err != nil { + r.log.Errorf("查询快递员包裹列表失败,快递员ID: %d, 错误: %v", courierId, err) + return nil, nil, fmt.Errorf("查询包裹列表失败: %v", err) + } + + // 如果没有包裹,直接返回空结果 + if len(parcels) == 0 { + r.log.Infof("快递员 %d 没有包裹", courierId) + return make(map[int64]*model.Cabinet), make(map[int64][]*model.Parcel), nil + } + + // 第二步:提取所有的 locker_id + lockerIds := make([]int32, 0, len(parcels)) + for _, parcel := range parcels { + lockerIds = append(lockerIds, parcel.LockerId) + } + + // 第三步:查询 locker 表,获取 locker_id 到 cabinet_id 的映射 + type LockerCabinet struct { + LockerID int32 `gorm:"column:locker_id"` + CabinetID int64 `gorm:"column:cabinet_id"` + } + var lockerCabinets []LockerCabinet + err = r.data.db.WithContext(ctx). + Table("locker"). + Select("locker_id, cabinet_id"). + Where("locker_id IN ?", lockerIds). + Find(&lockerCabinets).Error + if err != nil { + r.log.Errorf("查询locker信息失败,错误: %v", err) + return nil, nil, fmt.Errorf("查询格口信息失败: %v", err) + } + + // 建立 locker_id 到 cabinet_id 的映射 + lockerToCabinet := make(map[int32]int64) + cabinetIds := make([]int64, 0) + for _, lc := range lockerCabinets { + lockerToCabinet[lc.LockerID] = lc.CabinetID + // 去重收集 cabinet_id + found := false + for _, id := range cabinetIds { + if id == lc.CabinetID { + found = true + break + } + } + if !found { + cabinetIds = append(cabinetIds, lc.CabinetID) + } + } + + // 第四步:查询所有涉及的柜机信息 + var cabinets []*model.Cabinet + if len(cabinetIds) > 0 { + err = r.data.db.WithContext(ctx). + Where("id IN ?", cabinetIds). + Find(&cabinets).Error + if err != nil { + r.log.Errorf("查询柜机信息失败,错误: %v", err) + return nil, nil, fmt.Errorf("查询柜机信息失败: %v", err) + } + } + + // 第五步:构建返回结果 + // 构建柜机ID到柜机对象的映射 + cabinetMap := make(map[int64]*model.Cabinet) + for _, cabinet := range cabinets { + cabinetMap[cabinet.Id] = cabinet + } + + // 按柜机分组包裹 + cabinetParcels := make(map[int64][]*model.Parcel) + for _, parcel := range parcels { + cabinetId, exists := lockerToCabinet[parcel.LockerId] + if !exists { + r.log.Warnf("包裹 %d 的格口 %d 没有找到对应的柜机", parcel.ParcelId, parcel.LockerId) + continue + } + cabinetParcels[cabinetId] = append(cabinetParcels[cabinetId], parcel) + } + + r.log.Infof("查询快递员 %d 的派件列表成功,涉及 %d 个柜机,%d 个包裹", courierId, len(cabinetMap), len(parcels)) + return cabinetMap, cabinetParcels, nil +} + +// SyncLockerStatus 同步智能柜格口状态和可用数量 +// 根据包裹状态更新格口状态,并更新智能柜的可用格口数量 +// 逻辑: +// 1. 查询智能柜下所有格口 +// 2. 查询该智能柜下所有待取件(0)和滞留件(1)的包裹 +// 3. 根据包裹状态更新格口状态: +// - 如果格口有包裹且包裹状态为待取件(0)或滞留件(1),格口状态设为占用(1) +// - 否则,格口状态设为空闲(0) +// +// 4. 计算使用中的格口数量(状态为占用的格口数) +// 5. 更新智能柜的 available_compartments = total_compartments - 使用中的格口数 +func (r *cabinetRepo) SyncLockerStatus(ctx context.Context, cabinetId int64) error { + // 第一步:查询智能柜信息,验证智能柜是否存在 + var cabinet model.Cabinet + err := r.data.db.WithContext(ctx). + Where("id = ?", cabinetId). + First(&cabinet).Error + if err != nil { + r.log.Errorf("查询智能柜失败,智能柜ID: %d, 错误: %v", cabinetId, err) + return fmt.Errorf("智能柜不存在: %v", err) + } + + // 第二步:查询该智能柜下所有格口 + var lockers []model.Locker + err = r.data.db.WithContext(ctx). + Where("cabinet_id = ?", cabinetId). + Find(&lockers).Error + if err != nil { + r.log.Errorf("查询格口列表失败,智能柜ID: %d, 错误: %v", cabinetId, err) + return fmt.Errorf("查询格口列表失败: %v", err) + } + + // 如果没有格口,直接返回 + if len(lockers) == 0 { + r.log.Warnf("智能柜 %d 没有格口", cabinetId) + // 更新智能柜的可用格口数为总格口数 + err = r.data.db.WithContext(ctx). + Model(&model.Cabinet{}). + Where("id = ?", cabinetId). + Update("available_compartments", cabinet.TotalCompartments).Error + if err != nil { + r.log.Errorf("更新智能柜可用格口数失败: %v", err) + return fmt.Errorf("更新智能柜可用格口数失败: %v", err) + } + return nil + } + + // 第三步:查询该智能柜下所有待取件(0)和滞留件(1)的包裹 + // 先获取所有格口ID + lockerIds := make([]int32, 0, len(lockers)) + for _, locker := range lockers { + lockerIds = append(lockerIds, locker.LockerId) + } + + // 查询这些格口中有包裹且状态为待取件或滞留件的包裹 + var activeParcels []model.Parcel + err = r.data.db.WithContext(ctx). + Where("locker_id IN ? AND parcel_status IN ?", lockerIds, []int8{0, 1}). + Find(&activeParcels).Error + if err != nil { + r.log.Errorf("查询包裹列表失败,智能柜ID: %d, 错误: %v", cabinetId, err) + return fmt.Errorf("查询包裹列表失败: %v", err) + } + + // 建立格口ID到包裹的映射(一个格口只能有一个包裹) + lockerToParcel := make(map[int32]*model.Parcel) + for i := range activeParcels { + parcel := &activeParcels[i] + lockerToParcel[parcel.LockerId] = parcel + } + + // 第四步:更新格口状态 + // 统计使用中的格口数量 + usedCount := int32(0) + + // 使用事务批量更新格口状态 + tx := r.data.db.WithContext(ctx).Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + for _, locker := range lockers { + parcel, hasParcel := lockerToParcel[locker.LockerId] + + // 判断格口是否应该被占用 + shouldBeOccupied := false + if hasParcel { + // 如果格口有包裹且包裹状态为待取件(0)或滞留件(1),则格口应该被占用 + if parcel.ParcelStatus == 0 || parcel.ParcelStatus == 1 { + shouldBeOccupied = true + } + } + + // 更新格口状态 + newStatus := int8(0) // 默认空闲 + if shouldBeOccupied { + newStatus = 1 // 占用 + usedCount++ + } + + // 如果状态需要更新,执行更新 + if locker.Status != newStatus { + err = tx.Model(&model.Locker{}). + Where("locker_id = ?", locker.LockerId). + Update("status", newStatus).Error + if err != nil { + tx.Rollback() + r.log.Errorf("更新格口状态失败,格口ID: %d, 错误: %v", locker.LockerId, err) + return fmt.Errorf("更新格口状态失败: %v", err) + } + r.log.Infof("更新格口 %d 状态: %d -> %d", locker.LockerId, locker.Status, newStatus) + } + } + + // 第五步:更新智能柜的可用格口数量 + availableCount := cabinet.TotalCompartments - usedCount + if availableCount < 0 { + availableCount = 0 + } + + err = tx.Model(&model.Cabinet{}). + Where("id = ?", cabinetId). + Update("available_compartments", availableCount).Error + if err != nil { + tx.Rollback() + r.log.Errorf("更新智能柜可用格口数失败,智能柜ID: %d, 错误: %v", cabinetId, err) + return fmt.Errorf("更新智能柜可用格口数失败: %v", err) + } + + // 提交事务 + err = tx.Commit().Error + if err != nil { + r.log.Errorf("提交事务失败: %v", err) + return fmt.Errorf("提交事务失败: %v", err) + } + + r.log.Infof("同步智能柜 %d 格口状态成功: 总格口数=%d, 使用中=%d, 可用=%d", + cabinetId, cabinet.TotalCompartments, usedCount, availableCount) + + return nil +} + +// GetParcel 查询包裹详情 +// 支持两种查询方式:根据包裹ID或快递单号后5位 +// ctx: 上下文 +// parcelId: 包裹ID(可选) +// expressNoSuffix: 快递单号后5位(可选) +// 返回值:包裹信息、智能柜信息、格口信息、错误信息 +func (r *cabinetRepo) GetParcel(ctx context.Context, parcelId int32, expressNoSuffix string) (*model.Parcel, *model.Cabinet, *model.Locker, error) { + var parcel model.Parcel + var err error + + // 第一步:根据包裹ID或快递单号后5位查询包裹信息 + if parcelId > 0 { + // 根据包裹ID查询 + err = r.data.db.WithContext(ctx). + Where("parcel_id = ?", parcelId). + First(&parcel).Error + if err != nil { + r.log.Errorf("根据包裹ID查询失败,包裹ID: %d, 错误: %v", parcelId, err) + return nil, nil, nil, fmt.Errorf("包裹不存在: %v", err) + } + } else if expressNoSuffix != "" { + // 根据快递单号后5位查询(使用 LIKE 查询) + err = r.data.db.WithContext(ctx). + Where("express_no LIKE ?", "%"+expressNoSuffix). + First(&parcel).Error + if err != nil { + r.log.Errorf("根据快递单号后5位查询失败,后5位: %s, 错误: %v", expressNoSuffix, err) + return nil, nil, nil, fmt.Errorf("包裹不存在: %v", err) + } + } else { + return nil, nil, nil, fmt.Errorf("请提供包裹ID或快递单号后5位") + } + + // 第二步:根据 locker_id 查询格口信息 + var locker model.Locker + err = r.data.db.WithContext(ctx). + Where("locker_id = ?", parcel.LockerId). + First(&locker).Error + if err != nil { + r.log.Errorf("查询格口信息失败,locker_id: %d, 错误: %v", parcel.LockerId, err) + // 格口信息查询失败不影响返回包裹信息,但返回错误 + return &parcel, nil, nil, fmt.Errorf("格口信息不存在: %v", err) + } + + // 第三步:根据 cabinet_id 查询智能柜信息 + var cabinet model.Cabinet + err = r.data.db.WithContext(ctx). + Where("id = ?", locker.CabinetId). + First(&cabinet).Error + if err != nil { + r.log.Errorf("查询智能柜信息失败,cabinet_id: %d, 错误: %v", locker.CabinetId, err) + // 智能柜信息查询失败不影响返回包裹信息,但返回错误 + return &parcel, nil, &locker, fmt.Errorf("智能柜信息不存在: %v", err) + } + + r.log.Infof("查询包裹详情成功,包裹ID: %d, 快递单号: %s, 智能柜ID: %d, 格口ID: %d", + parcel.ParcelId, parcel.ExpressNo, cabinet.Id, locker.LockerId) + + return &parcel, &cabinet, &locker, nil +} diff --git a/internal/data/data.go b/internal/data/data.go index a5b41f30b87d5fb950c5a811293e567d42250ba1..818028248b77a86e8ffc6a13a43c64a8b2bdd3a5 100644 --- a/internal/data/data.go +++ b/internal/data/data.go @@ -1,19 +1,21 @@ package data import ( - "Cabinet/internal/conf" - "Cabinet/internal/model" + "cabinet/internal/conf" + "cabinet/internal/model" + "cabinet/internal/pkg" "context" + "time" + "github.com/go-kratos/kratos/v2/log" "github.com/google/wire" "github.com/redis/go-redis/v9" "gorm.io/driver/mysql" "gorm.io/gorm" - "time" ) // ProviderSet is data providers. -var ProviderSet = wire.NewSet(NewData, NewGreeterRepo, NewUserRepo, NewCabinetRepo) +var ProviderSet = wire.NewSet(NewData, NewGreeterRepo, NewRealRepo, NewExpressDeliveryRepo, NewMessageRepo, NewUserRepo, NewCabinetRepo, NewHelpArticleRepo, Redis) // Data . type Data struct { @@ -22,14 +24,20 @@ type Data struct { Redis *redis.Client } +// Db 返回数据库连接(供其他包访问) +func (d *Data) Db() *gorm.DB { + return d.db +} + // NewData . -func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) { - // 初始化MySQL +func NewData(c *conf.Data, logger log.Logger, redisClient *redis.Client) (*Data, func(), error) { dsn := c.Database.Source + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic(err) } + sqlDB, err := db.DB() if err != nil { panic(err) @@ -40,20 +48,304 @@ func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) { sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) - // 自动迁移数据库表 - err = db.AutoMigrate(&model.User{}) + // 执行数据库迁移 + err = db.AutoMigrate(&model.RealName{}, &model.ExpressDeliveryNotifications{}, &model.User{}) if err != nil { panic(err) } + log.NewHelper(logger).Info("数据库表迁移完成") + + // 保存全局数据库连接,供钩子函数使用 + globalDB = db + + // 初始化 MQTT(如果配置了) + var mqttSyncService *MQTTSyncService + var mqttClient *pkg.MQTTClient + mqttConfig := c.GetMqtt() + if mqttConfig != nil && mqttConfig.Broker != "" { + logHelper := log.NewHelper(logger) + logHelper.Info("开始初始化MQTT客户端...") + + // 创建 MQTT 配置 + mqttCfg := pkg.MQTTConfig{ + Broker: mqttConfig.Broker, + ClientID: mqttConfig.ClientId, + Username: mqttConfig.Username, + Password: mqttConfig.Password, + QoS: byte(mqttConfig.Qos), + } + + // 初始化 MQTT 客户端 + client, err := pkg.NewMQTTClient(mqttCfg, logger) + if err != nil { + logHelper.Warnf("MQTT客户端初始化失败,将使用GORM钩子函数: %v", err) + } else { + mqttClient = client + logHelper.Info("MQTT客户端初始化成功") + + // 创建事件发布器 + eventPublisher := pkg.NewEventPublisher(mqttClient, logger) + + // 设置全局事件发布器(供模型钩子函数使用) + model.SetGlobalEventPublisher(eventPublisher) + + // 创建事件订阅器 + eventSubscriber := pkg.NewEventSubscriber(mqttClient, logger) + + // 创建MQTT同步服务 + cabinetRepoInstance := NewCabinetRepo(&Data{db: db}, logger) + if repo, ok := cabinetRepoInstance.(*cabinetRepo); ok { + mqttSyncService = NewMQTTSyncService(db, eventSubscriber, repo, logger) + } + + // 启动MQTT同步服务 + if err := mqttSyncService.Start(); err != nil { + logHelper.Errorf("启动MQTT同步服务失败: %v", err) + } else { + logHelper.Info("MQTT同步服务已启动,将使用MQTT事件驱动同步格口状态") + } + } + } else { + log.NewHelper(logger).Info("未配置MQTT,将使用GORM钩子函数同步格口状态") + } + + // 注册包裹更新后的回调函数,自动同步格口状态 + // 如果使用MQTT,这个回调函数会发布事件;否则直接执行同步 + registerParcelUpdateCallback(db, logger) + + cleanup := func() { + log.NewHelper(logger).Info("closing the data resources") + // 关闭MQTT同步服务 + if mqttSyncService != nil { + mqttSyncService.Stop() + } + // 关闭MQTT客户端 + if mqttClient != nil { + mqttClient.Close() + } + // 关闭Redis连接 + if redisClient != nil { + redisClient.Close() + } + } + return &Data{db: db, Redis: redisClient}, cleanup, nil +} + +// MessageRepo 消息仓库 +type MessageRepo struct { + data *Data + log log.Logger +} + +// NewMessageRepo 创建消息仓库实例 +func NewMessageRepo(data *Data, logger log.Logger) *MessageRepo { + return &MessageRepo{ + data: data, + log: logger, + } +} + +var ctx = context.Background() + +// globalDB 全局数据库连接,用于钩子函数中执行同步操作 +var globalDB *gorm.DB + +// registerParcelUpdateCallback 注册包裹更新后的回调函数 +// 当包裹状态发生变化时,自动同步对应智能柜的格口状态 +func registerParcelUpdateCallback(db *gorm.DB, logger log.Logger) { + logHelper := log.NewHelper(logger) + + // 注册包裹更新后的回调 + db.Callback().Update().After("gorm:after_update").Register("sync_locker_status", func(tx *gorm.DB) { + // 检查是否是 Parcel 模型的更新 + if tx.Statement.Schema != nil && tx.Statement.Schema.Table == "parcel" { + var lockerId int32 + var parcelId int32 + + // 尝试从不同的方式获取包裹信息 + if parcel, ok := tx.Statement.Dest.(*model.Parcel); ok { + parcelId = parcel.ParcelId + lockerId = parcel.LockerId + } else if parcel, ok := tx.Statement.Dest.(model.Parcel); ok { + parcelId = parcel.ParcelId + lockerId = parcel.LockerId + } else if values, ok := tx.Statement.Dest.(map[string]interface{}); ok { + // 从 map 中提取 + if id, exists := values["parcel_id"]; exists { + if idInt, ok := id.(int32); ok { + parcelId = idInt + } + } + if lid, exists := values["locker_id"]; exists { + if lidInt, ok := lid.(int32); ok { + lockerId = lidInt + } + } + } else if tx.Statement.Schema != nil { + // 从 Schema 中获取主键值 + for _, field := range tx.Statement.Schema.PrimaryFields { + if field.Name == "ParcelId" { + if value, ok := tx.Statement.ReflectValue.Interface().(model.Parcel); ok { + parcelId = value.ParcelId + lockerId = value.LockerId + } + } + } + } + + // 如果无法从 Dest 中获取,尝试从 WHERE 条件中获取 + if parcelId == 0 && lockerId == 0 { + // 尝试从 Statement 的 WHERE 条件中获取 parcel_id + // 使用反射或直接查询数据库获取更新后的包裹信息 + // 由于 GORM 的 Statement 结构复杂,我们采用更简单的方式: + // 在 AfterUpdate 钩子中,包裹已经更新完成,我们可以通过查询获取最新状态 + // 但这里我们简化处理:如果无法获取,就跳过(由手动调用 SyncLockerStatus 来保证一致性) + } + + // 如果找到了 locker_id,执行同步 + if lockerId > 0 { + // 异步执行同步操作,避免阻塞更新操作 + go func(lockerId int32) { + // 使用全局数据库连接来执行同步操作,避免事务冲突 + if globalDB != nil { + syncLockerStatusForParcel(globalDB, lockerId, logHelper) + } + }(lockerId) + } + } + }) + + logHelper.Info("已注册包裹状态更新自动同步格口状态的回调函数") +} + +// syncLockerStatusForParcel 为包裹同步格口状态 +// 当包裹状态变化时,自动同步对应智能柜的格口状态 +func syncLockerStatusForParcel(db *gorm.DB, lockerId int32, logHelper *log.Helper) { + // 通过 locker_id 找到 cabinet_id + var locker model.Locker + if err := db.Where("locker_id = ?", lockerId).First(&locker).Error; err != nil { + logHelper.Warnf("同步格口状态失败:找不到格口 %d, 错误: %v", lockerId, err) + return + } + + // 调用同步函数(复用 SyncLockerStatus 的逻辑) + // 由于这是在回调中,我们需要直接执行同步逻辑 + ctx := context.Background() + + // 查询智能柜信息 + var cabinet model.Cabinet + if err := db.WithContext(ctx).Where("id = ?", locker.CabinetId).First(&cabinet).Error; err != nil { + logHelper.Warnf("同步格口状态失败:找不到智能柜 %d, 错误: %v", locker.CabinetId, err) + return + } + + // 查询该智能柜下所有格口 + var lockers []model.Locker + if err := db.WithContext(ctx).Where("cabinet_id = ?", locker.CabinetId).Find(&lockers).Error; err != nil { + logHelper.Warnf("同步格口状态失败:查询格口列表失败, 错误: %v", err) + return + } + + if len(lockers) == 0 { + // 更新智能柜的可用格口数为总格口数 + db.WithContext(ctx).Model(&model.Cabinet{}). + Where("id = ?", locker.CabinetId). + Update("available_compartments", cabinet.TotalCompartments) + return + } + + // 查询该智能柜下所有待取件(0)和滞留件(1)的包裹 + lockerIds := make([]int32, 0, len(lockers)) + for _, l := range lockers { + lockerIds = append(lockerIds, l.LockerId) + } + + var activeParcels []model.Parcel + if err := db.WithContext(ctx). + Where("locker_id IN ? AND parcel_status IN ?", lockerIds, []int8{0, 1}). + Find(&activeParcels).Error; err != nil { + logHelper.Warnf("同步格口状态失败:查询包裹列表失败, 错误: %v", err) + return + } + + // 建立格口ID到包裹的映射 + lockerToParcel := make(map[int32]*model.Parcel) + for i := range activeParcels { + parcel := &activeParcels[i] + lockerToParcel[parcel.LockerId] = parcel + } + // 使用事务批量更新格口状态 + tx := db.WithContext(ctx).Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + usedCount := int32(0) + for _, l := range lockers { + parcel, hasParcel := lockerToParcel[l.LockerId] + + shouldBeOccupied := false + if hasParcel { + if parcel.ParcelStatus == 0 || parcel.ParcelStatus == 1 { + shouldBeOccupied = true + } + } + + newStatus := int8(0) + if shouldBeOccupied { + newStatus = 1 + usedCount++ + } + + if l.Status != newStatus { + if err := tx.Model(&model.Locker{}). + Where("locker_id = ?", l.LockerId). + Update("status", newStatus).Error; err != nil { + tx.Rollback() + logHelper.Errorf("同步格口状态失败:更新格口 %d 状态失败, 错误: %v", l.LockerId, err) + return + } + } + } + + // 更新智能柜的可用格口数量 + availableCount := cabinet.TotalCompartments - usedCount + if availableCount < 0 { + availableCount = 0 + } + + if err := tx.Model(&model.Cabinet{}). + Where("id = ?", locker.CabinetId). + Update("available_compartments", availableCount).Error; err != nil { + tx.Rollback() + logHelper.Errorf("同步格口状态失败:更新智能柜可用格口数失败, 错误: %v", err) + return + } + + if err := tx.Commit().Error; err != nil { + logHelper.Errorf("同步格口状态失败:提交事务失败, 错误: %v", err) + return + } + + logHelper.Infof("自动同步智能柜 %d 格口状态成功: 总格口数=%d, 使用中=%d, 可用=%d", + locker.CabinetId, cabinet.TotalCompartments, usedCount, availableCount) +} + +func Redis(c *conf.Data, logger log.Logger) (*redis.Client, func(), error) { // 初始化Redis rdb := redis.NewClient(&redis.Options{ + //Addr: "14.103.196.10:6379", + //Password: "9c7174555a0b43b35acb135acb7fd137", Addr: c.Redis.Addr, Password: "39bf5f3ffe6b4447d254e448e21bf26d", // no password set DB: 0, // use default DB }) // 测试Redis连接 + var err error err = rdb.Set(context.Background(), "key", "value", 0).Err() if err != nil { panic(err) @@ -65,5 +357,6 @@ func NewData(c *conf.Data, logger log.Logger) (*Data, func(), error) { rdb.Close() } } - return &Data{db: db, Redis: rdb}, cleanup, nil + + return rdb, cleanup, nil } diff --git a/internal/data/express_delivery.go b/internal/data/express_delivery.go new file mode 100644 index 0000000000000000000000000000000000000000..b3ef21c58dfce1a6b1aae6dd89ed48785439eb63 --- /dev/null +++ b/internal/data/express_delivery.go @@ -0,0 +1,81 @@ +package data + +import ( + "cabinet/internal/biz" + "cabinet/internal/model" + "context" + + "github.com/go-kratos/kratos/v2/log" + "gorm.io/gorm" +) + +// ExpressDeliveryRepo 实现biz.ExpressDeliveryRepo接口 +type expressDeliveryRepo struct { + db *gorm.DB + logger log.Logger +} + +// NewExpressDeliveryRepo 创建快递消息仓库 +func NewExpressDeliveryRepo(data *Data, logger log.Logger) biz.ExpressDeliveryRepo { + return &expressDeliveryRepo{ + db: data.db, + logger: logger, + } +} + +// CreateNotification 创建快递通知 +func (r *expressDeliveryRepo) CreateNotification(ctx context.Context, notification *model.ExpressDeliveryNotifications) error { + return r.db.Create(notification).Error +} + +// GetNotificationsByUserID 根据用户ID获取通知列表 +func (r *expressDeliveryRepo) GetNotificationsByUserID(ctx context.Context, userID int32, limit, offset int) ([]*model.ExpressDeliveryNotifications, error) { + var notifications []*model.ExpressDeliveryNotifications + err := r.db.Where("user_id = ?", userID).Order("create_time DESC").Limit(limit).Offset(offset).Find(¬ifications).Error + return notifications, err +} + +// GetNotificationsByUserIDAndType 根据用户ID和消息类型获取通知列表 +func (r *expressDeliveryRepo) GetNotificationsByUserIDAndType(ctx context.Context, userID int32, messageType int32, limit, offset int) ([]*model.ExpressDeliveryNotifications, error) { + var notifications []*model.ExpressDeliveryNotifications + query := r.db.Where("user_id = ?", userID) + + // 如果指定了有效的消息类型(1-4),则添加类型筛选条件 + if messageType > 0 && messageType <= 4 { + query = query.Where("message_type = ?", messageType) + } + + err := query.Order("create_time DESC").Limit(limit).Offset(offset).Find(¬ifications).Error + return notifications, err +} + +// GetUnreadCount 获取用户未读通知数量 +func (r *expressDeliveryRepo) GetUnreadCount(ctx context.Context, userID int32) (int64, error) { + var count int64 + // 根据状态字段统计未读通知数量 + err := r.db.Model(&model.ExpressDeliveryNotifications{}).Where("user_id = ? AND status = ?", userID, "unread").Count(&count).Error + return count, err +} + +// MarkAsRead 将通知标记为已读 +func (r *expressDeliveryRepo) MarkAsRead(ctx context.Context, notificationID int32) error { + // 更新状态字段为已读 + return r.db.Model(&model.ExpressDeliveryNotifications{}).Where("id = ?", notificationID).Update("status", "read").Error +} + +// GetNotificationByIDAndUserAndType 根据ID、用户ID和消息类型获取单个通知 +func (r *expressDeliveryRepo) GetNotificationByIDAndUserAndType(ctx context.Context, id int32, userID int32, messageType int32) (*model.ExpressDeliveryNotifications, error) { + var notification model.ExpressDeliveryNotifications + query := r.db.Where("id = ? AND user_id = ?", id, userID) + + // 如果指定了有效的消息类型(1-4),则添加类型筛选条件 + if messageType > 0 && messageType <= 4 { + query = query.Where("message_type = ?", messageType) + } + + err := query.First(¬ification).Error + if err != nil { + return nil, err + } + return ¬ification, nil +} diff --git a/internal/data/greeter.go b/internal/data/greeter.go index 2e39022724c6fd1d05f29942e19340594faf017c..6c92589a22472a770068f92b28d22db0f0479e82 100644 --- a/internal/data/greeter.go +++ b/internal/data/greeter.go @@ -3,7 +3,7 @@ package data import ( "context" - "Cabinet/internal/biz" + "cabinet/internal/biz" "github.com/go-kratos/kratos/v2/log" ) diff --git a/internal/data/help_article_repo.go b/internal/data/help_article_repo.go new file mode 100644 index 0000000000000000000000000000000000000000..7b5417dcedcec1bd3de2e57d2747bcee55453bba --- /dev/null +++ b/internal/data/help_article_repo.go @@ -0,0 +1,179 @@ +package data + +import ( + "cabinet/internal/biz" + "cabinet/internal/model" + "context" + "fmt" + "strings" + + "gorm.io/gorm" +) + +// HelpArticleRepo 帮助文章仓库实现 +type HelpArticleRepo struct { + db *gorm.DB +} + +// NewHelpArticleRepo 创建帮助文章仓库实例 +func NewHelpArticleRepo(data *Data) biz.HelpArticleRepo { + return &HelpArticleRepo{db: data.db} +} + +// GetCategories 获取分类结构 +func (r *HelpArticleRepo) GetCategories(ctx context.Context) ([]model.HelpCategory, error) { + var results []struct { + Category string + SubCategory string + } + + // 查询所有启用的文章的分类和子分类 + err := r.db.WithContext(ctx).Model(&model.HelpArticle{}). + Select("DISTINCT category, sub_category"). + Where("is_active = ?", true). + Order("category"). + Find(&results).Error + + if err != nil { + return nil, fmt.Errorf("failed to get categories: %w", err) + } + + // 构建分类结构 + categoryMap := make(map[string]map[string]bool) + for _, result := range results { + if _, exists := categoryMap[result.Category]; !exists { + categoryMap[result.Category] = make(map[string]bool) + } + categoryMap[result.Category][result.SubCategory] = true + } + + // 转换为返回格式 + var categories []model.HelpCategory + for category, subCategoryMap := range categoryMap { + hc := model.HelpCategory{ + Category: category, + SubCategories: make([]string, 0, len(subCategoryMap)), + } + for subCategory := range subCategoryMap { + hc.SubCategories = append(hc.SubCategories, subCategory) + } + categories = append(categories, hc) + } + + return categories, nil +} + +// GetArticles 根据分类获取文章列表 +func (r *HelpArticleRepo) GetArticles(ctx context.Context, query model.ArticleQuery) ([]model.HelpArticle, int64, error) { + db := r.db.WithContext(ctx).Model(&model.HelpArticle{}).Where("is_active = ?", true) + + // 应用分类筛选 - 添加分类名称映射处理 + category := query.Category + // 映射常见的分类名称变体 + categoryMap := map[string]string{ + "账户管理": "账号", + "注册登录": "账号", + } + if mapped, exists := categoryMap[category]; exists { + category = mapped + } + if category != "" { + db = db.Where("category = ?", category) + } + + // 应用子分类筛选 - 添加子分类名称映射处理 + subCategory := query.SubCategory + subCategoryMap := map[string]string{ + "注册登录": "如何认证", + } + if mapped, exists := subCategoryMap[subCategory]; exists { + subCategory = mapped + } + if subCategory != "" { + db = db.Where("sub_category = ?", subCategory) + } + + // 计算总数 + var total int64 + if err := db.Count(&total).Error; err != nil { + return nil, 0, fmt.Errorf("failed to count articles: %w", err) + } + + // 分页查询 + if query.Page <= 0 { + query.Page = 1 + } + if query.PageSize <= 0 { + query.PageSize = 10 + } + offset := (query.Page - 1) * query.PageSize + + var articles []model.HelpArticle + if err := db.Order("order_weight DESC, created_at DESC"). + Offset(offset).Limit(query.PageSize). + Find(&articles).Error; err != nil { + return nil, 0, fmt.Errorf("failed to get articles: %w", err) + } + + return articles, total, nil +} + +// SearchArticles 搜索文章 +func (r *HelpArticleRepo) SearchArticles(ctx context.Context, query model.ArticleQuery) ([]model.HelpArticle, int64, error) { + db := r.db.WithContext(ctx).Model(&model.HelpArticle{}).Where("is_active = ?", true) + + // 应用关键词搜索 + if query.Keyword != "" { + keyword := strings.TrimSpace(query.Keyword) + // 使用LIKE搜索标题和内容 + db = db.Where("title LIKE ? OR content LIKE ?", "%"+keyword+"%", "%"+keyword+"%") + } + + // 计算总数 + var total int64 + if err := db.Count(&total).Error; err != nil { + return nil, 0, fmt.Errorf("failed to count search results: %w", err) + } + + // 分页查询 + if query.Page <= 0 { + query.Page = 1 + } + if query.PageSize <= 0 { + query.PageSize = 10 + } + offset := (query.Page - 1) * query.PageSize + + var articles []model.HelpArticle + if err := db.Order("order_weight DESC, created_at DESC"). + Offset(offset).Limit(query.PageSize). + Find(&articles).Error; err != nil { + return nil, 0, fmt.Errorf("failed to search articles: %w", err) + } + + return articles, total, nil +} + +// GetArticleByID 根据ID获取文章详情 +func (r *HelpArticleRepo) GetArticleByID(ctx context.Context, id string) (*model.HelpArticle, error) { + var article model.HelpArticle + if err := r.db.WithContext(ctx).Where("id = ? AND is_active = ?", id, true).First(&article).Error; err != nil { + if err == gorm.ErrRecordNotFound { + return nil, fmt.Errorf("article not found: %s", id) + } + return nil, fmt.Errorf("failed to get article: %w", err) + } + return &article, nil +} + +// IncrementViewCount 增加文章浏览次数 +func (r *HelpArticleRepo) IncrementViewCount(ctx context.Context, id string) error { + result := r.db.WithContext(ctx).Model(&model.HelpArticle{}).Where("id = ?", id).UpdateColumn("view_count", gorm.Expr("view_count + ?", 1)) + if result.Error != nil { + return fmt.Errorf("failed to increment view count: %w", result.Error) + } + if result.RowsAffected == 0 { + return fmt.Errorf("article not found: %s", id) + } + return nil +} \ No newline at end of file diff --git a/internal/data/mqtt_sync.go b/internal/data/mqtt_sync.go new file mode 100644 index 0000000000000000000000000000000000000000..1b986fe791664dc2248c33bbb87a36068f273b8e --- /dev/null +++ b/internal/data/mqtt_sync.go @@ -0,0 +1,87 @@ +package data + +import ( + "cabinet/internal/pkg" + "context" + "fmt" + + "github.com/go-kratos/kratos/v2/log" + "gorm.io/gorm" +) + +// MQTTSyncService MQTT同步服务 +// 订阅包裹状态变化事件,自动同步格口状态 +type MQTTSyncService struct { + db *gorm.DB + subscriber *pkg.EventSubscriber + logger *log.Helper + cabinetRepo *cabinetRepo // 用于调用同步方法 +} + +// NewMQTTSyncService 创建MQTT同步服务 +func NewMQTTSyncService(db *gorm.DB, subscriber *pkg.EventSubscriber, cabinetRepo *cabinetRepo, logger log.Logger) *MQTTSyncService { + service := &MQTTSyncService{ + db: db, + subscriber: subscriber, + logger: log.NewHelper(logger), + cabinetRepo: cabinetRepo, + } + + // 订阅包裹状态变化事件 + if err := service.subscribeParcelStatusChanged(); err != nil { + log.NewHelper(logger).Errorf("订阅包裹状态变化事件失败: %v", err) + } + + return service +} + +// subscribeParcelStatusChanged 订阅包裹状态变化事件 +func (s *MQTTSyncService) subscribeParcelStatusChanged() error { + return s.subscriber.SubscribeParcelStatusChanged(func(event interface{}) error { + // 解析事件 + eventMap, ok := event.(map[string]interface{}) + if !ok { + return fmt.Errorf("事件格式错误") + } + + // 提取 cabinet_id + cabinetId, ok := eventMap["cabinet_id"].(float64) + if !ok { + return fmt.Errorf("无法获取 cabinet_id") + } + + parcelId, _ := eventMap["parcel_id"].(float64) + oldStatus, _ := eventMap["old_status"].(float64) + newStatus, _ := eventMap["new_status"].(float64) + + s.logger.Infof("收到包裹状态变化事件: ParcelId=%.0f, CabinetId=%.0f, OldStatus=%.0f, NewStatus=%.0f", + parcelId, cabinetId, oldStatus, newStatus) + + // 异步执行同步操作 + go func() { + ctx := context.Background() + if err := s.cabinetRepo.SyncLockerStatus(ctx, int64(cabinetId)); err != nil { + s.logger.Errorf("同步格口状态失败: CabinetId=%.0f, Error=%v", cabinetId, err) + } else { + s.logger.Infof("同步格口状态成功: CabinetId=%.0f", cabinetId) + } + }() + + return nil + }) +} + +// Start 启动MQTT同步服务 +func (s *MQTTSyncService) Start() error { + s.logger.Info("MQTT同步服务已启动") + return nil +} + +// Stop 停止MQTT同步服务 +func (s *MQTTSyncService) Stop() error { + // 取消订阅(如果需要) + // 注意:MQTT客户端的关闭由 EventSubscriber 处理 + s.logger.Info("MQTT同步服务已停止") + return nil +} + diff --git a/internal/data/order.go b/internal/data/order.go new file mode 100644 index 0000000000000000000000000000000000000000..4574c08505b2c5b02f891dd6978c2e3b920a9505 --- /dev/null +++ b/internal/data/order.go @@ -0,0 +1,19 @@ +package data + +import ( + "cabinet/internal/biz" + "github.com/go-kratos/kratos/v2/log" +) + +type orderRepo struct { + data *Data + log *log.Helper +} + +// NewOrderRepo 创建订单数据访问实例 +func NewOrderRepo(data *Data, UserRepo biz.UserRepo, logger log.Logger) biz.GreeterRepo { + return &greeterRepo{ + data: data, + log: log.NewHelper(logger), + } +} diff --git a/internal/data/real.go b/internal/data/real.go new file mode 100644 index 0000000000000000000000000000000000000000..331059f4914385a2295e3e910261b6b138055b7d --- /dev/null +++ b/internal/data/real.go @@ -0,0 +1,71 @@ +package data + +import ( + "cabinet/internal/biz" + "cabinet/internal/model" + "cabinet/internal/untl" + "context" + + "github.com/go-kratos/kratos/v2/log" + "gorm.io/gorm" +) + +type realRepo struct { + db *gorm.DB +} + +// NewRealRepo 创建真实姓名仓库 +func NewRealRepo(data *Data, logger log.Logger) biz.RealRepo { + return &realRepo{db: data.db} +} + +// CheckUserRealName 检查用户是否已实名认证 +func (r *realRepo) CheckUserRealName(ctx context.Context, userId int) (bool, error) { + var count int64 + err := r.db.Model(&model.RealName{}).Where("user_id = ?", userId).Count(&count).Error + return count > 0, err +} + +// CheckCardUsed 检查身份证是否已被使用 +func (r *realRepo) CheckCardUsed(ctx context.Context, card string) (bool, error) { + var count int64 + err := r.db.Model(&model.RealName{}).Where("card = ?", card).Count(&count).Error + return count > 0, err +} + +func (r *realRepo) CreateRealName(ctx context.Context, realName *model.RealName) error { + return r.db.Create(realName).Error +} + +// VerifyRealName 使用第三方服务验证真实姓名和身份证 +func (r *realRepo) VerifyRealName(ctx context.Context, cardNo, realName string) (bool, error) { + // 调用untl包中的第三方实名认证功能 + return untl.Realname(cardNo, realName), nil +} + +// // CheckCourierAuth 检查用户是否已进行快递员认证 +func (r *realRepo) CheckCourierAuth(ctx context.Context, userId int) (bool, error) { + var count int64 + err := r.db.Model(&model.CourierAuth{}).Where("user_id = ?", userId).Count(&count).Error + return count > 0, err +} + +// CreateCourierAuth 创建快递员认证记录 +func (r *realRepo) CreateCourierAuth(ctx context.Context, auth *model.CourierAuth) error { + return r.db.Create(auth).Error +} + +// GetCourierAuthByUserId 根据用户ID获取快递员认证信息 +func (r *realRepo) GetCourierAuthByUserId(ctx context.Context, userId int) (*model.CourierAuth, error) { + var auth model.CourierAuth + err := r.db.Where("user_id = ?", userId).First(&auth).Error + if err != nil { + return nil, err + } + return &auth, nil +} + +// AutoMigrate 自动迁移表结构 +//func (r *realRepo) AutoMigrate() error { +// return r.db.AutoMigrate(&model.RealName{}, &model.CourierAuth{}) +//} diff --git a/internal/data/user.go b/internal/data/user.go index 778c7c56b082ab1c1027840a8857461802c7981e..e14cc1010e51eea68808a69e063c01d015356f43 100644 --- a/internal/data/user.go +++ b/internal/data/user.go @@ -1,11 +1,12 @@ package data import ( - "Cabinet/internal/biz" - "Cabinet/internal/model" + "cabinet/internal/biz" + "cabinet/internal/model" "context" "github.com/go-kratos/kratos/v2/log" + "gorm.io/gorm" ) // UserRepo 用户数据访问实现 @@ -45,4 +46,22 @@ func (r *userRepo) CheckUserExists(ctx context.Context, phone string) (bool, err var count int64 err := r.data.db.Model(&model.User{}).Where("phone = ?", phone).Count(&count).Error return count > 0, err -} \ No newline at end of file +} + +// UpdatePassword 更新用户密码 +func (r *userRepo) UpdatePassword(ctx context.Context, phone, password string) error { + return r.data.db.Model(&model.User{}).Where("phone = ?", phone).Update("password", password).Error +} + +// GetUserByID 根据用户ID获取用户 +func (r *userRepo) GetUserByID(ctx context.Context, id int64) (*model.User, error) { + var user model.User + err := r.data.db.Where("user_id = ?", id).First(&user).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return nil, err + } + return nil, err + } + return &user, nil +} diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go new file mode 100644 index 0000000000000000000000000000000000000000..e5e0ce394032a83daea14d6241f5bd7fded81f8d --- /dev/null +++ b/internal/middleware/auth.go @@ -0,0 +1,96 @@ +package middleware + +//import ( +// "Cabinet/internal/pkg" +// "context" +// "fmt" +// "github.com/go-kratos/kratos/v2/middleware" +// "github.com/go-kratos/kratos/v2/transport" +// "strings" +//) +// +//// AuthMiddleware 创建认证中间件,支持白名单接口跳过认证 +//func AuthMiddleware() middleware.Middleware { +// // 定义公开白名单接口 +// openEndpoints := map[string]bool{ +// "/user.v1.User/Login": true, +// "/user.v1.User/Register": true, +// "/user.v1.User/SendSms": true, +// "/user.v1.User/LoginSendSms": true, +// "/login": true, // 支持HTTP路径格式(proto中定义的小写路径) +// "/register": true, +// "/SendSms": true, +// "/loginSendSms": true, +// "login": true, // 支持简单的login路径 +// } +// return func(handler middleware.Handler) middleware.Handler { +// return func(ctx context.Context, req interface{}) (interface{}, error) { +// // 获取传输信息 +// if tr, ok := transport.FromServerContext(ctx); ok { +// // 1. 检查是否是白名单接口 +// operation := tr.Operation() +// +// // 检查操作名是否在白名单中 +// if openEndpoints[operation] { +// return handler(ctx, req) +// } +// +// // 2. 检查是否有跳过认证的标志 +// if tr.RequestHeader().Get("X-Skip-Auth") == "true" || +// tr.RequestHeader().Get("skip-auth") == "true" { +// return handler(ctx, req) +// } +// +// // 3. 检查路径是否包含login关键字(宽松匹配) +// path := tr.RequestHeader().Get(":path") +// if path == "" { +// // 尝试从其他可能的头获取路径 +// path = tr.RequestHeader().Get("X-URI") +// if path == "" { +// path = tr.RequestHeader().Get("X-Forwarded-Uri") +// } +// } +// +// // 如果路径包含login,跳过验证 +// if strings.Contains(strings.ToLower(path), "login") { +// return handler(ctx, req) +// } +// +// // 4. 从不同位置获取token +// token := tr.RequestHeader().Get("token") +// +// // 如果token为空,尝试从Authorization头获取 +// if token == "" { +// auth := tr.RequestHeader().Get("Authorization") +// if auth != "" && strings.HasPrefix(auth, "Bearer ") { +// token = strings.TrimPrefix(auth, "Bearer ") +// } +// } +// +// // 5. 验证token是否存在 +// if token == "" { +// return nil, fmt.Errorf("未提供认证token") +// } +// +// // 6. 解析token并获取用户信息 +// claims, errMsg := pkg.GetToken(token) +// if errMsg != "" || claims == nil { +// if errMsg == "" { +// errMsg = "无效的token" +// } +// return nil, fmt.Errorf("token无效: %v", errMsg) +// } +// +// // 7. 从claims中获取用户ID并存储到context中 +// userID, ok := claims["userId"] +// if ok { +// ctx = context.WithValue(ctx, "userID", userID) +// } +// } +// +// +// // 继续处理请求 +// return handler(ctx, req) +// } +// } +//} diff --git a/internal/model/balanceFlow.go b/internal/model/balanceFlow.go new file mode 100644 index 0000000000000000000000000000000000000000..c54d2e800862a337cb110d50de1082a5966d5774 --- /dev/null +++ b/internal/model/balanceFlow.go @@ -0,0 +1,19 @@ +package model + +import "gorm.io/gorm" + +// 余额流水表 +type BalanceFlow struct { + gorm.Model + Id int32 `gorm:"column:id;type:int(11);comment:流水ID;primaryKey;not null;" json:"id"` // 流水ID + UserId int32 `gorm:"column:user_id;type:int(11);comment:用户ID;not null;" json:"user_id"` // 用户ID + OrderNo string `gorm:"column:order_no;type:varchar(32);comment:关联订单号;not null;" json:"order_no"` // 关联订单号 + Amount float64 `gorm:"column:amount;type:decimal(10, 2);comment:变动金额;not null;" json:"amount"` // 变动金额 + FlowType int8 `gorm:"column:flow_type;type:tinyint(1);comment:流水类型:1-充值,2-消费,3-退款;not null;" json:"flow_type"` // 流水类型:1-充值,2-消费,3-退款 + Balance float64 `gorm:"column:balance;type:decimal(10, 2);comment:变动后余额;not null;" json:"balance"` // 变动后余额 + Remark string `gorm:"column:remark;type:varchar(500);comment:备注;default:NULL;" json:"remark"` // 备注 +} + +func (BalanceFlow) TableName() string { + return "balance_flow" +} diff --git a/internal/model/bill.go b/internal/model/bill.go new file mode 100644 index 0000000000000000000000000000000000000000..fef9ded035295beda0b984976d52fd8734b9794c --- /dev/null +++ b/internal/model/bill.go @@ -0,0 +1,27 @@ +package model + +import ( + "gorm.io/gorm" + "time" +) + +// Bill 账单模型 +type Bill struct { + gorm.Model + BillID string `gorm:"type:varchar(32);uniqueIndex;not null" json:"bill_id"` // 账单业务ID + UserID int32 `gorm:"type:int32;not null" json:"user_id"` // 用户ID + Amount float64 `gorm:"type:decimal(16,2);not null" json:"amount"` // 金额,支持正负 + AmountFormatted string `gorm:"type:varchar(50);not null" json:"amount_formatted"` // 格式化金额,如"+P1,000.00" + BillType int `gorm:"type:int;not null" json:"bill_type"` // 账单类型: 1-余额充值 2-派件费 3-提现 + BillTypeLabel string `gorm:"type:varchar(20);not null" json:"bill_type_label"` // 类型显示文本: "余额充值"/"派件费"/"提现" + PaymentMethod int64 `gorm:"type:int;not null" json:"payment_method"` // 支付方式: wechat/alipay/balance/wechat_balance + PaymentLabel string `gorm:"type:varchar(50);not null" json:"payment_label"` // 支付方式显示文本: "微信支付"/"支付宝"/"账户余额"/"微信支付/账户余额" + TransactionNo string `gorm:"type:varchar(64);uniqueIndex;not null" json:"transaction_no"` // 交易单号 + TransactionTime time.Time `gorm:"type:datetime;not null" json:"transaction_time"` // 交易时间 + Status int `gorm:"type:int;not null" json:"status"` // 状态: 1-成功 2-处理中 3-失败 +} + +// TableName 指定表名 +func (Bill) TableName() string { + return "bills" +} diff --git a/internal/model/consumeOrder.go b/internal/model/consumeOrder.go new file mode 100644 index 0000000000000000000000000000000000000000..194591119d34e8b22a81d3cd1aacde5c35b61766 --- /dev/null +++ b/internal/model/consumeOrder.go @@ -0,0 +1,18 @@ +package model + +import "gorm.io/gorm" + +// 消费订单表 +type ConsumeOrder struct { + gorm.Model + Id int32 `gorm:"column:id;type:int(11);comment:订单ID;primaryKey;not null;" json:"id"` // 订单ID + OrderNo string `gorm:"column:order_no;type:varchar(32);comment:订单号;not null;" json:"order_no"` // 订单号 + UserId int32 `gorm:"column:user_id;type:int(11);comment:用户ID;not null;" json:"user_id"` // 用户ID + Amount float64 `gorm:"column:amount;type:decimal(10, 2);comment:消费金额;not null;" json:"amount"` // 消费金额 + OrderType int8 `gorm:"column:order_type;type:tinyint(1);comment:订单类型:1-快递柜使用,2-其他服务;not null;default:1;" json:"order_type"` // 订单类型:1-快递柜使用,2-其他服务 + Status int8 `gorm:"column:status;type:tinyint(1);comment:状态:1-待支付,2-支付成功,3-支付失败;not null;default:1;" json:"status"` // 状态:1-待支付,2-支付成功,3-支付失败 +} + +func (ConsumeOrder) TableName() string { + return "consume_order" +} diff --git a/internal/model/courier_auth.go b/internal/model/courier_auth.go new file mode 100644 index 0000000000000000000000000000000000000000..315e198d79eb66738f3e06fec916b5aba3eb7080 --- /dev/null +++ b/internal/model/courier_auth.go @@ -0,0 +1,13 @@ +package model + +import "time" + +type CourierAuth struct { + Id int32 `json:"id"` // 主键ID + UserId int32 `json:"user_id"` // 用户ID(关联用户表) + CompanyId int32 `json:"company_id"` // 所属公司ID(关联公司表) + CardPhotoUrl string `json:"card_photo_url"` // 工牌/名片照片地址 + Status int8 `json:"status"` // 认证状态:0-待审核,1-审核通过,2-审核拒绝 + CreateTime time.Time `json:"create_time"` // 创建时间 + UpdateTime time.Time `json:"update_time"` // 更新时间 +} diff --git a/internal/model/express_delivery.go b/internal/model/express_delivery.go new file mode 100644 index 0000000000000000000000000000000000000000..0f702880a8ac3438e93f217c954541b40abe1c34 --- /dev/null +++ b/internal/model/express_delivery.go @@ -0,0 +1,14 @@ +package model + +import "time" + +type ExpressDeliveryNotifications struct { + Id int32 `json:"id"` + UserId int32 `json:"user_id"` // 关联的用户ID,用于标识通知所属用户 + NotificationType string `json:"notification_type"` // 通知类型,如待取件超时通知、已取件通知等 + NotificationContent string `json:"notification_content"` // 通知内容 + MessageType int32 `json:"message_type"` // 消息类型:1系统消息,2派件消息,3揽收消息,4活动消息 + Remark string `json:"remark"` // 备注信息 + Status string `json:"status"` // 状态,如未读、已读、已处理等 + CreateTime time.Time `json:"create_time"` // 通知创建时间 +} diff --git a/internal/model/help_article.go b/internal/model/help_article.go new file mode 100644 index 0000000000000000000000000000000000000000..5d5972c5910b4e1b6dff9cf78327e01dc7e3a567 --- /dev/null +++ b/internal/model/help_article.go @@ -0,0 +1,39 @@ +package model + +import ( + "time" +) + +// HelpArticle 帮助文章模型 +type HelpArticle struct { + ID string `gorm:"column:id;primaryKey;type:varchar(36)" json:"id"` + Title string `gorm:"column:title;not null;type:varchar(255)" json:"title"` + Content string `gorm:"column:content;not null;type:text" json:"content"` + Category string `gorm:"column:category;not null;type:varchar(50);index" json:"category"` + SubCategory string `gorm:"column:sub_category;not null;type:varchar(50);index" json:"sub_category"` + OrderWeight int `gorm:"column:order_weight;default:0;index:idx_active_order" json:"order_weight"` + ViewCount int `gorm:"column:view_count;default:0" json:"view_count"` + IsActive bool `gorm:"column:is_active;default:true;index:idx_active_order" json:"is_active"` + CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP;autoUpdateTime" json:"updated_at"` +} + +// TableName 指定表名 +func (HelpArticle) TableName() string { + return "help_articles" +} + +// HelpCategory 分类结构 +type HelpCategory struct { + Category string `json:"category"` + SubCategories []string `json:"sub_categories"` +} + +// ArticleQuery 文章查询参数 +type ArticleQuery struct { + Category string + SubCategory string + Keyword string + Page int + PageSize int +} diff --git a/internal/model/locker.go b/internal/model/locker.go new file mode 100644 index 0000000000000000000000000000000000000000..a0a98584c01eb0e198aee6f333d829e0f3a90658 --- /dev/null +++ b/internal/model/locker.go @@ -0,0 +1,17 @@ +package model + +import "time" + +type Locker struct { + LockerId int32 `gorm:"column:locker_id;type:INT;comment:格口ID(主键);primaryKey;" json:"locker_id"` // 格口ID(主键) + CabinetId int64 `gorm:"column:cabinet_id;type:BIGINT;comment:柜机ID(关联cabinet表的id);not null;" json:"cabinet_id"` // 柜机ID(关联cabinet表的id) + LockerNumber string `gorm:"column:locker_number;type:VARCHAR(20);comment:格口编号(如A01、B02);not null;" json:"locker_number"` // 格口编号(如A01、B02) + Size string `gorm:"column:size;type:VARCHAR(20);comment:格口大小(small/medium/large);default:medium;" json:"size"` // 格口大小(small/medium/large) + Status int8 `gorm:"column:status;type:TINYINT;comment:状态(0-空闲,1-占用,2-故障);default:0;" json:"status"` // 状态(0-空闲,1-占用,2-故障) + CreatedTime time.Time `gorm:"column:created_time;type:DATETIME;comment:创建时间;default:CURRENT_TIMESTAMP;" json:"created_time"` // 创建时间 + UpdatedTime time.Time `gorm:"column:updated_time;type:DATETIME;comment:更新时间;default:CURRENT_TIMESTAMP;" json:"updated_time"` // 更新时间 +} + +func (Locker) TableName() string { + return "locker" +} diff --git a/internal/model/message.go b/internal/model/message.go new file mode 100644 index 0000000000000000000000000000000000000000..7c83c645d426215a1f03d9557cbc4627734baa56 --- /dev/null +++ b/internal/model/message.go @@ -0,0 +1,26 @@ +package model + +import ( + "time" +) + +// Message 消息模型 +type Message struct { + ID uint `json:"id"` + UserID int `json:"user_id"` // 接收者ID + SenderID int `json:"sender_id"` // 发送者ID + Type int `json:"type"` // 消息类型:1-系统通知,2-业务消息,3-聊天消息 + Title string `json:"title"` // 消息标题 + Content string `json:"content"` // 消息内容 + IsRead bool `json:"is_read"` // 是否已读 + CreatedAt time.Time `json:"created_at"` // 创建时间 +} + +// WebSocketMessage WebSocket通信消息格式 +type WebSocketMessage struct { + Type string `json:"type"` // 消息类型:connect, disconnect, message, error + UserID int `json:"user_id,omitempty"` + Data interface{} `json:"data,omitempty"` + Error string `json:"error,omitempty"` + Time time.Time `json:"time"` +} \ No newline at end of file diff --git a/internal/model/order.go b/internal/model/order.go new file mode 100644 index 0000000000000000000000000000000000000000..c6f7ef6f87fe62b4c363ebfcf195a776b474c1aa --- /dev/null +++ b/internal/model/order.go @@ -0,0 +1,24 @@ +package model + +import ( + "gorm.io/gorm" + "time" +) + +// 订单表 +type RechargeOrder struct { + gorm.Model + Id int32 `gorm:"column:id;type:int(11);comment:订单ID;primaryKey;not null;" json:"id"` + OrderNo string `gorm:"column:order_no;type:varchar(32);comment:订单号;not null;" json:"order_no"` + UserId int32 `gorm:"column:user_id;type:int(11);comment:用户ID;not null;" json:"user_id"` + Amount float64 `gorm:"column:amount;type:decimal(10, 2);comment:充值金额;not null;" json:"amount"` + FinalAmount float64 `gorm:"column:final_amount;type:decimal(10, 2);comment:实际支付金额;not null;" json:"final_amount"` + Currency string `gorm:"column:currency;type:varchar(10);comment:币种;not null;default:cny;" json:"currency"` + PayMethod string `gorm:"column:pay_method;type:varchar(20);comment:支付方式:alipay-支付宝,wechat-微信;not null;default:alipay;" json:"pay_method"` + Status int8 `gorm:"column:status;type:tinyint(1);comment:状态:1-待支付,2-支付成功,3-支付失败;not null;default:1;" json:"status"` + TradeNo string `gorm:"column:trade_no;type:varchar(64);comment:第三方交易号;default:NULL;" json:"trade_no"` + PayTime time.Time `gorm:"column:pay_time;type:datetime;comment:支付时间;default:NULL;" json:"pay_time"` +} + +func (RechargeOrder) TableName() string { return "recharge_order" } + diff --git a/internal/model/parcel.go b/internal/model/parcel.go index 4867a0443c33e9ee5d81a913ce85415e2b20aae5..f0a2bd9fba12884935c408dd86583aa58a04c5a6 100644 --- a/internal/model/parcel.go +++ b/internal/model/parcel.go @@ -1,6 +1,23 @@ package model -import "time" +import ( + "time" + + "gorm.io/gorm" +) + +// 全局事件发布器(在 data 层初始化时设置) +var globalEventPublisher interface { + PublishParcelStatusChanged(event interface{}) error +} + +// SetGlobalEventPublisher 设置全局事件发布器 +// 在 data 层初始化 MQTT 时调用 +func SetGlobalEventPublisher(publisher interface { + PublishParcelStatusChanged(event interface{}) error +}) { + globalEventPublisher = publisher +} type Parcel struct { ParcelId int32 `gorm:"column:parcel_id;type:int;comment:包裹ID(主键);primaryKey;not null;" json:"parcel_id"` // 包裹ID(主键) @@ -11,8 +28,73 @@ type Parcel struct { ParcelStatus int8 `gorm:"column:parcel_status;type:tinyint;comment:状态(0-待取件,1-滞留件,2-已取件,3-已撤回,4-已完结);not null;default:0;" json:"parcel_status"` // 状态(0-待取件,1-滞留件,2-已取件,3-已撤回,4-已完结) DispatchTime time.Time `gorm:"column:dispatch_time;type:datetime;comment:派件时间;default:NULL;" json:"dispatch_time"` // 派件时间 CourierId int32 `gorm:"column:courier_id;type:int;comment:派件员ID(关联user表的user_id);not null;" json:"courier_id"` // 派件员ID(关联user表的user_id) + + // 用于钩子函数中保存旧状态 + oldParcelStatus int8 `gorm:"-"` } func (Parcel) TableName() string { return "parcel" } + +// BeforeUpdate GORM钩子:更新前保存旧状态 +func (p *Parcel) BeforeUpdate(tx *gorm.DB) error { + // 查询旧状态 + var oldParcel Parcel + if err := tx.First(&oldParcel, p.ParcelId).Error; err == nil { + p.oldParcelStatus = oldParcel.ParcelStatus + } + return nil +} + +// AfterUpdate GORM钩子:更新后自动同步格口状态 +// 当包裹状态发生变化时,发布MQTT事件或直接同步格口状态 +func (p *Parcel) AfterUpdate(tx *gorm.DB) error { + // 检查状态是否发生变化 + if p.oldParcelStatus == p.ParcelStatus { + return nil // 状态未变化,不需要同步 + } + + // 状态发生变化,需要同步格口状态 + // 通过 locker_id 找到 cabinet_id + var locker Locker + if err := tx.Where("locker_id = ?", p.LockerId).First(&locker).Error; err != nil { + // 如果找不到格口,记录日志但不影响更新操作 + return nil + } + + // 如果配置了MQTT事件发布器,发布事件 + if globalEventPublisher != nil { + // 发布MQTT事件(异步执行,避免阻塞) + go func() { + event := map[string]interface{}{ + "parcel_id": p.ParcelId, + "locker_id": p.LockerId, + "cabinet_id": locker.CabinetId, + "old_status": p.oldParcelStatus, + "new_status": p.ParcelStatus, + "express_no": p.ExpressNo, + "timestamp": time.Now().Unix(), + } + if err := globalEventPublisher.PublishParcelStatusChanged(event); err != nil { + // 如果发布失败,记录日志但不影响更新操作 + } + }() + } else { + // 如果没有配置MQTT,使用直接同步方式(兼容旧实现) + // 实际的同步逻辑由 data 层的回调函数处理 + go func(cabinetId int64) { + syncLockerStatusForCabinet(cabinetId) + }(locker.CabinetId) + } + + return nil +} + +// syncLockerStatusForCabinet 同步智能柜格口状态(供钩子函数调用) +// 注意:这个函数通过 data 包的全局变量访问数据库连接 +// 实际执行逻辑在 data 层的回调函数中 +func syncLockerStatusForCabinet(cabinetId int64) { + // 实际的同步逻辑在 data 层的回调函数中实现 + // 这里只是占位,实际的同步由 data 层的 registerParcelUpdateCallback 处理 +} diff --git a/internal/model/real.go b/internal/model/real.go new file mode 100644 index 0000000000000000000000000000000000000000..694ead9c6daa73ede01a635b1be1b2f266cb9bd0 --- /dev/null +++ b/internal/model/real.go @@ -0,0 +1,10 @@ +package model + +import "gorm.io/gorm" + +type RealName struct { + gorm.Model + Card string `gorm:"type:varchar(150);comment:'身份证号'"` + Name string `gorm:"type:varchar(100);comment:'姓名'"` + UserId int +} diff --git a/internal/model/user.go b/internal/model/user.go index 0763ab21e5682ccf383c8a056d38e773b0d371c8..af3d859eb74dd280c8c9c37b96e09e54f87255d1 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -2,16 +2,17 @@ package model import "time" +// 用户表 type User struct { - UserId int64 `gorm:"column:user_id;type:bigint;comment:用户ID(主键);primaryKey;not null;" json:"user_id"` // 用户ID(主键) - Username string `gorm:"column:username;type:varchar(50);comment:用户名;not null;" json:"username"` // 用户名 - Password string `gorm:"column:password;type:varchar(100);comment:密码(加密存储);not null;" json:"password"` // 密码(加密存储) - RealName string `gorm:"column:real_name;type:varchar(50);comment:真实姓名;not null;" json:"real_name"` // 真实姓名 - IdCard string `gorm:"column:id_card;type:varchar(18);comment:身份证号;not null;" json:"id_card"` // 身份证号 - Phone string `gorm:"column:phone;type:varchar(11);comment:手机号;not null;" json:"phone"` // 手机号 + UserId int64 `gorm:"column:user_id;type:bigint;comment:用户ID(主键);primaryKey;not null;" json:"user_id"` // 用户ID(主键) + Username string `gorm:"column:username;type:varchar(50);comment:用户名;not null;" json:"username"` // 用户名 + Password string `gorm:"column:password;type:varchar(100);comment:密码(加密存储);not null;" json:"password"` // 密码(加密存储) + RealName string `gorm:"column:real_name;type:varchar(50);comment:真实姓名;not null;" json:"real_name"` // 真实姓名 + IdCard string `gorm:"column:id_card;type:varchar(18);comment:身份证号;not null;" json:"id_card"` // 身份证号 + Phone string `gorm:"column:phone;type:varchar(11);comment:手机号;not null;" json:"phone"` // 手机号 UserType string `gorm:"column:user_type;type:varchar(191);comment:用户类型(0-普通会员,1-快递员);default:NULL;" json:"user_type"` // 用户类型(0-普通会员,1-快递员) - CreateTime time.Time `gorm:"column:create_time;type:datetime(3);comment:创建时间;default:NULL;" json:"create_time"` // 创建时间 - UpdateTime time.Time `gorm:"column:update_time;type:datetime(3);comment:更新时间;default:NULL;" json:"update_time"` // 更新时间 + CreateTime time.Time `gorm:"column:create_time;type:datetime(3);comment:创建时间;default:NULL;" json:"create_time"` // 创建时间 + UpdateTime time.Time `gorm:"column:update_time;type:datetime(3);comment:更新时间;default:NULL;" json:"update_time"` // 更新时间 } func (u *User) TableName() string { diff --git a/internal/pkg/alipay.go b/internal/pkg/alipay.go new file mode 100644 index 0000000000000000000000000000000000000000..ae17c1172b3fd50d609f8569170fb255e4b1e6c2 --- /dev/null +++ b/internal/pkg/alipay.go @@ -0,0 +1,34 @@ +package pkg + +import ( + "fmt" + "github.com/smartwalle/alipay/v3" +) + +func Alipay(OutTradeNo, TotalAmount string) string { + appId := "2021000149637324" + var privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4PK47nQChur/COYvl0Vl2kRCfo5iIkHiIo8GaL3R48oVKiZuhgpjAAD8jWBIUkF6vkhOpuuSHdTbn7V1I47pepqaOlNTvpLztTRmqJYyutam0uZKcx7rix+i9hDp1mtBWoXP7BVsYWzPxcCH29eyoK5tEG5oVbkS8A63ZTmpoi4vMm6yGi8Sh/CB9Q6K/zmLJeDsvZWIGKOdiOxXbn/sUIMw1Z39VkXd88X51d2QfydsQ7nR4cw7eyfahZnlJu8ux8Fbph1p5IbZoxj+zaBya1KsRUiuNyO6O3BdDWQzlsM98Dj2wvgJq+dmZZmTAgtddlS6M3Nr9aMSLmXZbLLdRAgMBAAECggEAEgfuTWtKOonaZpnXNUDN7qq348fRD9yDz2uncP2f+o5RuT/KeX4ckP31L/JYoqaWfbxgLEZcQ12XaA91B8Mx5D2PI3kxddq7msXm8x4FrLuexk87KBjsE3HZN2MPjZ7/MNtQTIrzpWiXptOAn3jmFndeFMZWyC4oj/YUN6ZTB7QmEUsHuJS3aR/+bViHyGK043mZYpy0i4a/VnYiEcSaiUiRKKw6l28bdD6OaZYbIbXENiIVFSlDtPs9MdGFlxOaMfmgvRtB8aGUJOY+dZ7ui71Ieaj0NZ4Tgzv6Vs0yfXjJi5RDh2Jw8vfXOuI3UOSUFeOfAbM9jXTTr62pYh0w2QKBgQDbrfp23J94KrVP5vILmhsRBHcI8Rgra9bZPnJAAkQRjGcSloKQN9nxIEjgZ3h9IEGWhvhxtDeRLTYFCcSMaR857btKPgoqSo+3stnsmUd3op/Lg/tm2gjLioNv8Q3EAzIEZuq5uZmBof0I3cmxRXuOtv3SAh3Ptce1vd1y9VjAewKBgQDWspUJIwAs8PRLEM/9IKblR5RcJKyO9dQi2JwxF+pO/iRvGBf4ydUayICZQmuYiYO4h3mDtvevQWKvNxhUd4OIhgfCkAd70ibMZsz6lMN7Rh94swoPtxeAHvLQPaNH0gAR34DyYMC1QQNu1okOW4Brb5pwXaKTg5AW2IlxAcirowKBgQC4h9VLwRmnrGBvhPSvMD0qN1DyTutZlhV8mUsJX53kC8eNQqbcFD5boR32epQor07I7gmGIbdHmO22n7TdqXQUihD4VVLeoZFF4tPSPhNdeFq4bt5gUChxKaItBxTkITcDoZvsdO5wiSzvJLbG1THr+OUPYr29+9mNXbbGWTe7PwKBgC/0J1GpHarsDsSNdsdE+cIQYNTL5DbyNXWrCAf3HYCTRzQBH6mT+yNGUWLp8PpbDK60o7GFtoDQi+gy2B5Vq21c8nLElMdVQxE4jOQTQi5QlUryQncMIBWf1AArJEMwZo+Xake40JIreUbSaD5qtZ5JiSc74sosAoz3xIsXKOkdAoGAXiVJSQ8OX85jPvimdEvaJRW9yM4IOE/WlWx5t3A4zphRl70fGdRHMGhXgcr1CziX+mG0hpqIP23PqucisjE0X+jmir1TQwMYQS28iy+tbsE0I3dvgAlFsc7BUCuX9s6Z9ShrzvokzlnVpmgDYJ0Zkh7vG/1IE7zgG3SBKjsLVsk=" // 必须,上一步中使用 RSA签名验签工具 生成的私钥 + var client, err = alipay.New(appId, privateKey, false) + if err != nil { + fmt.Println(err) + return "" + } + + var p = alipay.TradeWapPay{} + p.NotifyURL = "http://250c7226.f30.cpolar.top/v1/recharge/notify" //回调地址 + p.ReturnURL = "http://www.baidu.com" //同步返回地址 + p.Subject = "充值订单" + p.OutTradeNo = OutTradeNo + p.TotalAmount = TotalAmount + p.ProductCode = "QUICK_WAP_WAY" + + url, err := client.TradeWapPay(p) + if err != nil { + fmt.Println(err) + } + + // 这个 payURL 即是用于打开支付宝支付页面的 URL,可将输出的内容复制,到浏览器中访问该 URL 即可打开支付页面。 + var payURL = url.String() + fmt.Println(payURL) + return payURL +} diff --git a/internal/pkg/event_publisher.go b/internal/pkg/event_publisher.go new file mode 100644 index 0000000000000000000000000000000000000000..4cd26129dffb66cd68eac75b6f3d9dac078c43a7 --- /dev/null +++ b/internal/pkg/event_publisher.go @@ -0,0 +1,149 @@ +package pkg + +import ( + "encoding/json" + "fmt" + + "github.com/go-kratos/kratos/v2/log" +) + +// EventPublisher 事件发布器 +// 用于发布包裹状态变化等事件 +type EventPublisher struct { + mqttClient *MQTTClient + logger *log.Helper +} + +// NewEventPublisher 创建事件发布器 +func NewEventPublisher(mqttClient *MQTTClient, logger log.Logger) *EventPublisher { + return &EventPublisher{ + mqttClient: mqttClient, + logger: log.NewHelper(logger), + } +} + +// ParcelStatusChangedEvent 包裹状态变化事件 +type ParcelStatusChangedEvent struct { + ParcelId int32 `json:"parcel_id"` // 包裹ID + LockerId int32 `json:"locker_id"` // 格口ID + CabinetId int64 `json:"cabinet_id"` // 智能柜ID + OldStatus int8 `json:"old_status"` // 旧状态 + NewStatus int8 `json:"new_status"` // 新状态 + Timestamp int64 `json:"timestamp"` // 时间戳 + ExpressNo string `json:"express_no"` // 快递单号 +} + +// PublishParcelStatusChanged 发布包裹状态变化事件 +// 支持 ParcelStatusChangedEvent 类型和 map[string]interface{} 类型 +func (p *EventPublisher) PublishParcelStatusChanged(event interface{}) error { + var eventData ParcelStatusChangedEvent + var cabinetId int64 + + // 处理不同类型的事件 + switch e := event.(type) { + case ParcelStatusChangedEvent: + eventData = e + cabinetId = e.CabinetId + case map[string]interface{}: + // 从 map 中提取数据 + if id, ok := e["cabinet_id"].(int64); ok { + cabinetId = id + } else if id, ok := e["cabinet_id"].(float64); ok { + cabinetId = int64(id) + } else { + return fmt.Errorf("无法获取 cabinet_id") + } + + // 转换为 ParcelStatusChangedEvent + if parcelId, ok := e["parcel_id"].(int32); ok { + eventData.ParcelId = parcelId + } else if parcelId, ok := e["parcel_id"].(float64); ok { + eventData.ParcelId = int32(parcelId) + } + + if lockerId, ok := e["locker_id"].(int32); ok { + eventData.LockerId = lockerId + } else if lockerId, ok := e["locker_id"].(float64); ok { + eventData.LockerId = int32(lockerId) + } + + eventData.CabinetId = cabinetId + + if oldStatus, ok := e["old_status"].(int8); ok { + eventData.OldStatus = oldStatus + } else if oldStatus, ok := e["old_status"].(float64); ok { + eventData.OldStatus = int8(oldStatus) + } + + if newStatus, ok := e["new_status"].(int8); ok { + eventData.NewStatus = newStatus + } else if newStatus, ok := e["new_status"].(float64); ok { + eventData.NewStatus = int8(newStatus) + } + + if expressNo, ok := e["express_no"].(string); ok { + eventData.ExpressNo = expressNo + } + + if timestamp, ok := e["timestamp"].(int64); ok { + eventData.Timestamp = timestamp + } else if timestamp, ok := e["timestamp"].(float64); ok { + eventData.Timestamp = int64(timestamp) + } + default: + return fmt.Errorf("不支持的事件类型: %T", event) + } + + // 主题格式: cabinet/parcel/status/changed/{cabinet_id} + topic := fmt.Sprintf("cabinet/parcel/status/changed/%d", cabinetId) + + // 序列化事件 + data, err := json.Marshal(eventData) + if err != nil { + return fmt.Errorf("序列化事件失败: %v", err) + } + + // 发布消息(QoS=1,确保至少送达一次) + if err := p.mqttClient.Publish(topic, data, 1); err != nil { + return fmt.Errorf("发布事件失败: %v", err) + } + + // 同时发布到通用主题,供其他订阅者使用 + generalTopic := "cabinet/parcel/status/changed" + if err := p.mqttClient.Publish(generalTopic, data, 1); err != nil { + p.logger.Warnf("发布到通用主题失败: %v", err) + } + + p.logger.Infof("发布包裹状态变化事件: ParcelId=%d, CabinetId=%d, OldStatus=%d, NewStatus=%d", + eventData.ParcelId, eventData.CabinetId, eventData.OldStatus, eventData.NewStatus) + + return nil +} + +// LockerStatusSyncEvent 格口状态同步事件 +type LockerStatusSyncEvent struct { + CabinetId int64 `json:"cabinet_id"` // 智能柜ID + TotalCompartments int32 `json:"total_compartments"` // 总格口数 + UsedCompartments int32 `json:"used_compartments"` // 使用中的格口数 + AvailableCompartments int32 `json:"available_compartments"` // 可用格口数 + Timestamp int64 `json:"timestamp"` // 时间戳 +} + +// PublishLockerStatusSynced 发布格口状态同步完成事件 +func (p *EventPublisher) PublishLockerStatusSynced(event LockerStatusSyncEvent) error { + topic := fmt.Sprintf("cabinet/locker/status/synced/%d", event.CabinetId) + + data, err := json.Marshal(event) + if err != nil { + return fmt.Errorf("序列化事件失败: %v", err) + } + + if err := p.mqttClient.Publish(topic, data, 1); err != nil { + return fmt.Errorf("发布事件失败: %v", err) + } + + p.logger.Infof("发布格口状态同步事件: CabinetId=%d, Used=%d, Available=%d", + event.CabinetId, event.UsedCompartments, event.AvailableCompartments) + + return nil +} diff --git a/internal/pkg/event_subscriber.go b/internal/pkg/event_subscriber.go new file mode 100644 index 0000000000000000000000000000000000000000..2249bfbbaa7cfe1293f7be95c0f7b9fcc80bea5e --- /dev/null +++ b/internal/pkg/event_subscriber.go @@ -0,0 +1,107 @@ +package pkg + +import ( + "encoding/json" + "fmt" + + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/go-kratos/kratos/v2/log" +) + +// EventSubscriber 事件订阅器 +// 用于订阅并处理包裹状态变化等事件 +type EventSubscriber struct { + mqttClient *MQTTClient + logger *log.Helper + handlers map[string]EventHandler +} + +// EventHandler 事件处理函数类型 +type EventHandler func(topic string, payload []byte) error + +// NewEventSubscriber 创建事件订阅器 +func NewEventSubscriber(mqttClient *MQTTClient, logger log.Logger) *EventSubscriber { + return &EventSubscriber{ + mqttClient: mqttClient, + logger: log.NewHelper(logger), + handlers: make(map[string]EventHandler), + } +} + +// RegisterHandler 注册事件处理函数 +func (s *EventSubscriber) RegisterHandler(topic string, handler EventHandler) error { + s.handlers[topic] = handler + + // 订阅主题 + if err := s.mqttClient.Subscribe(topic, 1, func(client mqtt.Client, msg mqtt.Message) { + s.logger.Debugf("收到MQTT消息: Topic=%s, Payload=%s", msg.Topic(), string(msg.Payload())) + + // 查找对应的处理函数 + if handler, exists := s.handlers[msg.Topic()]; exists { + if err := handler(msg.Topic(), msg.Payload()); err != nil { + s.logger.Errorf("处理事件失败: Topic=%s, Error=%v", msg.Topic(), err) + } + } else { + // 尝试匹配通配符主题 + for pattern, handler := range s.handlers { + if matchTopic(pattern, msg.Topic()) { + if err := handler(msg.Topic(), msg.Payload()); err != nil { + s.logger.Errorf("处理事件失败: Topic=%s, Error=%v", msg.Topic(), err) + } + return + } + } + s.logger.Warnf("未找到对应的事件处理函数: Topic=%s", msg.Topic()) + } + }); err != nil { + return fmt.Errorf("订阅主题失败: %v", err) + } + + s.logger.Infof("注册事件处理函数: Topic=%s", topic) + return nil +} + +// matchTopic 匹配主题(支持通配符) +func matchTopic(pattern, topic string) bool { + // 简单的通配符匹配实现 + // 支持 + (单级通配符) 和 # (多级通配符) + if pattern == topic { + return true + } + + // 这里可以实现更复杂的通配符匹配逻辑 + // 暂时只支持精确匹配 + return false +} + +// SubscribeParcelStatusChanged 订阅包裹状态变化事件 +// 注意:ParcelStatusChangedEvent 类型定义在 event_publisher.go 中 +func (s *EventSubscriber) SubscribeParcelStatusChanged(handler func(event interface{}) error) error { + // 订阅特定智能柜的主题 + specificTopic := "cabinet/parcel/status/changed/+" + + // 订阅通用主题 + generalTopic := "cabinet/parcel/status/changed" + + // 注册处理函数 + eventHandler := func(topic string, payload []byte) error { + var event map[string]interface{} + if err := json.Unmarshal(payload, &event); err != nil { + return fmt.Errorf("解析事件失败: %v", err) + } + return handler(event) + } + + // 注册两个主题的处理函数 + if err := s.RegisterHandler(specificTopic, eventHandler); err != nil { + return err + } + + if err := s.RegisterHandler(generalTopic, eventHandler); err != nil { + return err + } + + return nil +} + + diff --git a/internal/pkg/jwt.go b/internal/pkg/jwt.go index f5f1964c6bbd67ca7358397d417318190eb6fdbf..621902cc698e967b2a9bb105fde6c3e69ce34867 100644 --- a/internal/pkg/jwt.go +++ b/internal/pkg/jwt.go @@ -2,9 +2,8 @@ package pkg import ( "fmt" - "time" - "github.com/dgrijalva/jwt-go" + "time" ) const ( diff --git a/internal/pkg/mqtt.go b/internal/pkg/mqtt.go new file mode 100644 index 0000000000000000000000000000000000000000..e708a2671e211b8131d249df261309c99252ae82 --- /dev/null +++ b/internal/pkg/mqtt.go @@ -0,0 +1,117 @@ +package pkg + +import ( + "encoding/json" + "fmt" + "time" + + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/go-kratos/kratos/v2/log" +) + +// MQTTClient MQTT客户端封装 +type MQTTClient struct { + client mqtt.Client + logger *log.Helper +} + +// MQTTConfig MQTT配置 +type MQTTConfig struct { + Broker string // MQTT Broker地址,如 "tcp://localhost:1883" + ClientID string // 客户端ID + Username string // 用户名(可选) + Password string // 密码(可选) + QoS byte // 服务质量等级(0, 1, 2) +} + +// NewMQTTClient 创建MQTT客户端 +func NewMQTTClient(config MQTTConfig, logger log.Logger) (*MQTTClient, error) { + opts := mqtt.NewClientOptions() + opts.AddBroker(config.Broker) + opts.SetClientID(config.ClientID) + if config.Username != "" { + opts.SetUsername(config.Username) + } + if config.Password != "" { + opts.SetPassword(config.Password) + } + opts.SetKeepAlive(60 * time.Second) + opts.SetPingTimeout(10 * time.Second) + opts.SetAutoReconnect(true) + opts.SetDefaultPublishHandler(func(client mqtt.Client, msg mqtt.Message) { + log.NewHelper(logger).Warnf("收到未订阅的消息: Topic=%s, Payload=%s", msg.Topic(), string(msg.Payload())) + }) + + client := mqtt.NewClient(opts) + if token := client.Connect(); token.Wait() && token.Error() != nil { + return nil, fmt.Errorf("连接MQTT Broker失败: %v", token.Error()) + } + + log.NewHelper(logger).Infof("MQTT客户端连接成功: Broker=%s, ClientID=%s", config.Broker, config.ClientID) + + return &MQTTClient{ + client: client, + logger: log.NewHelper(logger), + }, nil +} + +// Publish 发布消息 +func (m *MQTTClient) Publish(topic string, payload interface{}, qos byte) error { + var data []byte + var err error + + switch v := payload.(type) { + case []byte: + data = v + case string: + data = []byte(v) + default: + data, err = json.Marshal(payload) + if err != nil { + return fmt.Errorf("序列化消息失败: %v", err) + } + } + + token := m.client.Publish(topic, qos, false, data) + if token.Wait() && token.Error() != nil { + return fmt.Errorf("发布消息失败: %v", token.Error()) + } + + m.logger.Debugf("发布MQTT消息: Topic=%s, Payload=%s", topic, string(data)) + return nil +} + +// Subscribe 订阅主题 +func (m *MQTTClient) Subscribe(topic string, qos byte, handler mqtt.MessageHandler) error { + token := m.client.Subscribe(topic, qos, handler) + if token.Wait() && token.Error() != nil { + return fmt.Errorf("订阅主题失败: %v", token.Error()) + } + + m.logger.Infof("订阅MQTT主题: Topic=%s, QoS=%d", topic, qos) + return nil +} + +// Unsubscribe 取消订阅 +func (m *MQTTClient) Unsubscribe(topics ...string) error { + token := m.client.Unsubscribe(topics...) + if token.Wait() && token.Error() != nil { + return fmt.Errorf("取消订阅失败: %v", token.Error()) + } + + m.logger.Infof("取消订阅MQTT主题: Topics=%v", topics) + return nil +} + +// Close 关闭MQTT客户端 +func (m *MQTTClient) Close() { + if m.client != nil && m.client.IsConnected() { + m.client.Disconnect(250) + m.logger.Info("MQTT客户端已断开连接") + } +} + +// IsConnected 检查是否已连接 +func (m *MQTTClient) IsConnected() bool { + return m.client != nil && m.client.IsConnected() +} diff --git a/internal/pkg/sendSms.go b/internal/pkg/sendSms.go index a78b2de5e6d6dd1942c048d0c0ab5131f5158f5c..1ce35bc0060316df1cc43d2b083bc0e7f21ff9fe 100644 --- a/internal/pkg/sendSms.go +++ b/internal/pkg/sendSms.go @@ -7,8 +7,8 @@ import "fmt" func SendSms(phone, code string) (string, error) { // 这里返回一个简单的字符串而不是复杂的结构体 // 实际项目中在非Windows环境下可以使用阿里云SDK - fmt.Printf("模拟发送短信到 %s,验证码:%s\n", phone, code) - return "{\"Code\":\"OK\"}", nil + fmt.Printf("模拟发送短信到 %s,验证码:%s", phone, code) + return "", nil } ////import ( diff --git a/internal/server/grpc.go b/internal/server/grpc.go index befc24467eec6d94a4e34c6f790c476bf6d483ea..5d6ddc0a9f12fdfa8e4c9088d900d0cffec747de 100644 --- a/internal/server/grpc.go +++ b/internal/server/grpc.go @@ -1,11 +1,12 @@ package server import ( - v3 "Cabinet/api/cabinet/v1" - v1 "Cabinet/api/helloworld/v1" - v2 "Cabinet/api/user/v1" - "Cabinet/internal/conf" - "Cabinet/internal/service" + "cabinet/api/helloworld/v1" + helpv1 "cabinet/api/help/v1" + userv1 "cabinet/api/user/v1" + cabinetv1 "cabinet/api/cabinet/v1" + "cabinet/internal/conf" + "cabinet/internal/service" "github.com/go-kratos/kratos/v2/log" "github.com/go-kratos/kratos/v2/middleware/recovery" @@ -13,10 +14,11 @@ import ( ) // NewGRPCServer new a gRPC server. -func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, user *service.UserService, cabinet *service.CabinetService, logger log.Logger) *grpc.Server { +func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, user *service.UserService, help *service.HelpService, cabinet *service.CabinetService, logger log.Logger) *grpc.Server { var opts = []grpc.ServerOption{ grpc.Middleware( recovery.Recovery(), + //middleware.AuthMiddleware(), ), } if c.Grpc.Network != "" { @@ -29,8 +31,10 @@ func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, user *servic opts = append(opts, grpc.Timeout(c.Grpc.Timeout.AsDuration())) } srv := grpc.NewServer(opts...) + // Register Service v1.RegisterGreeterServer(srv, greeter) - v2.RegisterUserServer(srv, user) - v3.RegisterCabinetServer(srv, cabinet) + userv1.RegisterUserServer(srv, user) + helpv1.RegisterHelpServiceServer(srv, help) + cabinetv1.RegisterCabinetServer(srv, cabinet) return srv } diff --git a/internal/server/http.go b/internal/server/http.go index b9178065119eb410e0ca241747c0eeac102b7166..cd8bfbf3525f4366d5f0c6d7a3deac06d896c344 100644 --- a/internal/server/http.go +++ b/internal/server/http.go @@ -1,16 +1,7 @@ package server import ( - // API定义的导入 - v3 "Cabinet/api/cabinet/v1" - v1 "Cabinet/api/helloworld/v1" - v2 "Cabinet/api/user/v1" - - // 内部依赖 - "Cabinet/internal/conf" - "Cabinet/internal/service" - - // 第三方依赖 + // 标准库导入 "context" "encoding/json" "fmt" @@ -18,125 +9,143 @@ import ( "net/http" "net/url" + // API定义的导入 + "cabinet/api/helloworld/v1" + helpv1 "cabinet/api/help/v1" + userv1 "cabinet/api/user/v1" + cabinetv1 "cabinet/api/cabinet/v1" + + // 内部依赖 + "cabinet/internal/conf" + "cabinet/internal/service" + "github.com/go-kratos/kratos/v2/log" + khttp "github.com/go-kratos/kratos/v2/transport/http" "github.com/go-kratos/kratos/v2/middleware" "github.com/go-kratos/kratos/v2/middleware/recovery" - krhttp "github.com/go-kratos/kratos/v2/transport/http" ) -// NewHTTPServer 创建HTTP服务器实例 -// 该函数用于初始化和配置HTTP服务器,是服务启动的核心组件之一 -// 参数: -// - c: 服务器配置,包含网络、地址、超时等设置 -// - greeter: 问候服务实例 -// - user: 用户服务实例 -// - cabinet: 智能柜服务实例 -// - logger: 日志记录器 -// 返回值:配置好的HTTP服务器实例 -func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, user *service.UserService, cabinet *service.CabinetService, logger log.Logger) *krhttp.Server { - // 初始化HTTP服务器选项切片 - var opts = []krhttp.ServerOption{ - // 添加恢复中间件,用于捕获和处理panic,确保服务不会因异常而崩溃 - // 添加CORS中间件,允许跨域请求 - krhttp.Middleware( +// corsMiddleware 创建CORS中间件 +func corsMiddleware() middleware.Middleware { + return func(handler middleware.Handler) middleware.Handler { + return func(ctx context.Context, req interface{}) (interface{}, error) { + // 尝试获取HTTP请求上下文 + if httpCtx, ok := ctx.(khttp.Context); ok { + resp := httpCtx.Response() + resp.Header().Set("Access-Control-Allow-Origin", "*") + resp.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + } + return handler(ctx, req) + } + } +} + +// NewHTTPServer new an HTTP server. +func NewHTTPServer(c *conf.Server, greeter *service.GreeterService, user *service.UserService, help *service.HelpService, cabinet *service.CabinetService, logger log.Logger) *khttp.Server { + // 创建CORS处理器包装器 + corsWrapper := func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusNoContent) + return + } + + h.ServeHTTP(w, r) + }) + } + + var opts = []khttp.ServerOption{ + khttp.Middleware( recovery.Recovery(), - corsMiddleware(), // CORS中间件 ), + // 设置HTTP处理器包装器 + khttp.Filter(corsWrapper), } - - // 根据配置设置网络类型(如tcp、unix等) + + // 根据配置设置网络类型 if c.Http.Network != "" { - opts = append(opts, krhttp.Network(c.Http.Network)) + opts = append(opts, khttp.Network(c.Http.Network)) } - + // 根据配置设置监听地址 if c.Http.Addr != "" { - opts = append(opts, krhttp.Address(c.Http.Addr)) + opts = append(opts, khttp.Address(c.Http.Addr)) } - + // 根据配置设置超时时间 if c.Http.Timeout != nil { - opts = append(opts, krhttp.Timeout(c.Http.Timeout.AsDuration())) + opts = append(opts, khttp.Timeout(c.Http.Timeout.AsDuration())) } - - // 添加 CORS Filter,在底层处理所有请求的 CORS - opts = append(opts, krhttp.Filter(corsFilter())) - - // 创建HTTP服务器实例 - srv := krhttp.NewServer(opts...) - - // 注册各种服务到HTTP服务器 - // 这样HTTP服务器就能处理对应的API请求 - v1.RegisterGreeterHTTPServer(srv, greeter) // 注册问候服务 - v2.RegisterUserHTTPServer(srv, user) // 注册用户服务 - v3.RegisterCabinetHTTPServer(srv, cabinet) // 注册智能柜服务 - - // 注册地图搜索代理接口 - // 用于代理高德地图API请求,解决前端跨域和Key平台不匹配问题 - r := srv.Route("/") - - // 处理 GET 请求(OPTIONS 请求由 CORS Filter 处理) - r.GET("/api/map/search", func(ctx krhttp.Context) error { - return handleMapSearch(ctx) - }) - - // 返回配置完成的HTTP服务器 + + srv := khttp.NewServer(opts...) + + // 注册HTTP服务器 + v1.RegisterGreeterHTTPServer(srv, greeter) + userv1.RegisterUserHTTPServer(srv, user) + helpv1.RegisterHelpServiceHTTPServer(srv, help) + cabinetv1.RegisterCabinetHTTPServer(srv, cabinet) + return srv } // handleMapSearch 处理地图搜索请求,代理高德地图API // 解决前端直接调用高德API时的跨域和Key平台不匹配问题 -func handleMapSearch(ctx krhttp.Context) error { +func handleMapSearch(ctx khttp.Context) error { // 获取查询参数 query := ctx.Request().URL.Query() keyword := query.Get("keyword") if keyword == "" { return ctx.JSON(400, map[string]interface{}{ "status": "0", - "info": "搜索关键词不能为空", + "info": "搜索关键词不能为空", }) } - + city := query.Get("city") if city == "" { city = "北京" // 默认城市 } - + // 高德地图API Key(服务端类型) amapKey := "3ca0556efb61e0c531b6e6aba3304eb3" - + // 构建高德地图API请求URL(正确编码参数) apiURL := fmt.Sprintf("https://restapi.amap.com/v3/place/text?key=%s&keywords=%s&city=%s&offset=20&page=1&extensions=all", amapKey, url.QueryEscape(keyword), url.QueryEscape(city)) - + // 发起HTTP请求到高德地图API resp, err := http.Get(apiURL) if err != nil { return ctx.JSON(500, map[string]interface{}{ "status": "0", - "info": fmt.Sprintf("请求高德地图API失败: %v", err), + "info": fmt.Sprintf("请求高德地图API失败: %v", err), }) } defer resp.Body.Close() - + // 读取响应数据 body, err := io.ReadAll(resp.Body) if err != nil { return ctx.JSON(500, map[string]interface{}{ "status": "0", - "info": fmt.Sprintf("读取响应失败: %v", err), + "info": fmt.Sprintf("读取响应失败: %v", err), }) } - + // 解析JSON响应 var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { return ctx.JSON(500, map[string]interface{}{ "status": "0", - "info": fmt.Sprintf("解析响应失败: %v", err), + "info": fmt.Sprintf("解析响应失败: %v", err), }) } - + // 返回结果给前端 return ctx.JSON(200, result) } @@ -148,7 +157,7 @@ func corsFilter() func(http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 获取请求的来源 origin := r.Header.Get("Origin") - + // 设置CORS响应头 if origin != "" { // 如果有Origin头,使用具体的源地址 @@ -158,64 +167,20 @@ func corsFilter() func(http.Handler) http.Handler { // 如果没有Origin头,使用通配符(仅限非凭证请求) w.Header().Set("Access-Control-Allow-Origin", "*") } - + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, Accept, Origin") w.Header().Set("Access-Control-Max-Age", "3600") w.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type") - + // 如果是OPTIONS预检请求,直接返回空响应,不继续处理 if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent) return } - + // 继续处理其他请求 next.ServeHTTP(w, r) }) } } - -// corsMiddleware 创建CORS中间件,允许跨域请求 -// 解决前端从不同端口访问后端时的跨域问题 -// Kratos框架的HTTP中间件实现,支持所有跨域场景 -func corsMiddleware() middleware.Middleware { - return func(handler middleware.Handler) middleware.Handler { - return func(ctx context.Context, req interface{}) (interface{}, error) { - // 尝试转换为HTTP上下文 - if httpCtx, ok := ctx.(krhttp.Context); ok { - httpReq := httpCtx.Request() - resp := httpCtx.Response() - - // 获取请求的来源,用于设置CORS - origin := httpReq.Header.Get("Origin") - - // 设置CORS响应头 - if origin != "" { - // 如果有Origin头,使用具体的源地址 - resp.Header().Set("Access-Control-Allow-Origin", origin) - // 只有在有具体origin时才设置credentials(因为不能同时使用*和credentials) - resp.Header().Set("Access-Control-Allow-Credentials", "true") - } else { - // 如果没有Origin头,使用通配符(仅限非凭证请求) - resp.Header().Set("Access-Control-Allow-Origin", "*") - } - - resp.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH") - resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, Accept, Origin") - resp.Header().Set("Access-Control-Max-Age", "3600") - resp.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type") - - // 如果是OPTIONS预检请求,直接返回空响应,不继续处理 - if httpReq.Method == "OPTIONS" { - resp.WriteHeader(http.StatusNoContent) - // 返回一个空的响应,使用JSON格式确保Kratos正确处理 - return map[string]interface{}{}, nil - } - } - - // 继续处理其他请求 - return handler(ctx, req) - } - } -} diff --git a/internal/service/bill.go b/internal/service/bill.go new file mode 100644 index 0000000000000000000000000000000000000000..6a92e0848ec8407852aacf2f53345f441d8ec7a1 --- /dev/null +++ b/internal/service/bill.go @@ -0,0 +1,114 @@ +package service + +import ( + "cabinet/api/bill/v1" + "cabinet/internal/data" + "cabinet/internal/model" + "context" + "fmt" + "github.com/go-kratos/kratos/v2/errors" + "github.com/go-kratos/kratos/v2/log" + + "gorm.io/gorm" +) + +// BillService 账单服务实现 +type BillService struct { + v1.UnimplementedBillServiceServer + db *gorm.DB + log *log.Helper +} + +// NewBillService 创建账单服务实例 +func NewBillService(data *data.Data, logger log.Logger) *BillService { + return &BillService{ + db: data.Db(), + log: log.NewHelper(logger), + } +} + +// / 获取充值记录列表(分页,按创建时间倒序) +func (s *BillService) ListBills(ctx context.Context, req *v1.ListBillsRequest) (*v1.ListBillsReply, error) { + page := req.Page + if page <= 0 { + page = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = 10 + } + offset := (page - 1) * pageSize + + query := s.db.WithContext(ctx).Model(&model.Bill{}) + if req.UserId > 0 { + query = query.Where("user_id = ?", req.UserId) + } + var total int64 + if err := query.Count(&total).Error; err != nil { + return nil, err + } + + var bill []model.Bill + if err := query.Order("created_at DESC").Limit(int(pageSize)).Offset(int(offset)).Find(&bill).Error; err != nil { + return nil, err + } + + list := make([]*v1.BillRecord, 0, len(bill)) + for _, o := range bill { + list = append(list, &v1.BillRecord{ + BillId: o.BillID, + UserId: int64(o.UserID), + Amount: float64(float32(o.Amount)), + AmountFormatted: o.AmountFormatted, + BillType: int64(int32(o.BillType)), + BillTypeLabel: o.BillTypeLabel, + PaymentMethod: o.PaymentMethod, + PaymentLabel: o.PaymentLabel, + TransactionNo: o.TransactionNo, + }) + } + + return &v1.ListBillsReply{ + Total: total, + List: list, + }, nil +} + +// 获取充值订单详情 +// ✅ 修复后的代码 +func (s *BillService) GetBillDetail(ctx context.Context, req *v1.GetBillDetailRequest) (*v1.GetBillDetailReply, error) { + var bill model.Bill + db := s.db.WithContext(ctx) + + // 构建查询条件:bill_id 或 transaction_no + if req.BillId != "" { + db = db.Where("bill_id = ?", req.BillId) + } else if req.TransactionNo != "" { + db = db.Where("transaction_no = ?", req.TransactionNo) + } else { + return nil, fmt.Errorf("必须提供 bill_id 或 transaction_no") + } + + // 执行查询 + if err := db.First(&bill).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("订单没找到") + } + return nil, err + } + + // 返回结果 + return &v1.GetBillDetailReply{ + Id: uint64(bill.ID), + BillId: bill.BillID, + UserId: int64(bill.UserID), + Amount: bill.Amount, + AmountFormatted: bill.AmountFormatted, + BillType: int64(bill.BillType), + BillTypeLabel: bill.BillTypeLabel, + PaymentMethod: bill.PaymentMethod, + PaymentLabel: bill.PaymentLabel, + TransactionNo: bill.TransactionNo, + Status: int64(bill.Status), + }, nil +} diff --git a/internal/service/billservice.go b/internal/service/billservice.go new file mode 100644 index 0000000000000000000000000000000000000000..640b629ec524bd09bed597f9f507ac946a65533f --- /dev/null +++ b/internal/service/billservice.go @@ -0,0 +1,22 @@ +package service + +import ( + "context" + + pb "cabinet/api/bill/v1" +) + +type BillServiceService struct { + pb.UnimplementedBillServiceServer +} + +func NewBillServiceService() *BillServiceService { + return &BillServiceService{} +} + +func (s *BillServiceService) GetBillDetail(ctx context.Context, req *pb.GetBillDetailRequest) (*pb.GetBillDetailReply, error) { + return &pb.GetBillDetailReply{}, nil +} +func (s *BillServiceService) ListBills(ctx context.Context, req *pb.ListBillsRequest) (*pb.ListBillsReply, error) { + return &pb.ListBillsReply{}, nil +} diff --git a/internal/service/cabinet.go b/internal/service/cabinet.go index edbd306731a909f14f392092394241d15ac605af..11373e776c2d19dad3e99871780bc39f7b6d2329 100644 --- a/internal/service/cabinet.go +++ b/internal/service/cabinet.go @@ -4,8 +4,9 @@ import ( "context" "fmt" - pb "Cabinet/api/cabinet/v1" - "Cabinet/internal/biz" + pb "cabinet/api/cabinet/v1" + "cabinet/internal/biz" + "github.com/go-kratos/kratos/v2/log" ) @@ -107,17 +108,17 @@ func (s *CabinetService) NearbyCabinet(ctx context.Context, req *pb.NearbyCabine cabinetInfos := make([]*pb.CabinetInfo, len(cabinets)) for i, cabinet := range cabinets { cabinetInfos[i] = &pb.CabinetInfo{ - Id: cabinet.Id, // 柜机ID - CabinetCode: cabinet.CabinetCode, // 柜机编码 - CabinetName: cabinet.CabinetName, // 柜机名称 - CabinetType: cabinet.CabinetType, // 柜机类型 - Location: cabinet.Location, // 安装位置 - Latitude: cabinet.Latitude, // 纬度 - Longitude: cabinet.Longitude, // 经度 - TotalCompartments: cabinet.TotalCompartments, // 总格口数量 + Id: cabinet.Id, // 柜机ID + CabinetCode: cabinet.CabinetCode, // 柜机编码 + CabinetName: cabinet.CabinetName, // 柜机名称 + CabinetType: cabinet.CabinetType, // 柜机类型 + Location: cabinet.Location, // 安装位置 + Latitude: cabinet.Latitude, // 纬度 + Longitude: cabinet.Longitude, // 经度 + TotalCompartments: cabinet.TotalCompartments, // 总格口数量 AvailableCompartments: cabinet.AvailableCompartments, // 可用格口数量 - Status: int32(cabinet.Status), // 状态 - Distance: distances[i], // 距离(公里) + Status: int32(cabinet.Status), // 状态 + Distance: distances[i], // 距离(公里) } } @@ -321,3 +322,154 @@ func (s *CabinetService) FindCabinetByParcel(ctx context.Context, req *pb.FindCa Parcel: parcelInfo, }, nil } + +// ListCourierParcels 获取快递员的派件列表 +// 根据快递员ID查询其派送的包裹,按柜机进行层级分组 +// 支持按包裹状态进行筛选 +// ctx: 请求上下文 +// req: 查询请求参数 +// 返回值:按柜机分组的包裹列表和错误信息 +func (s *CabinetService) ListCourierParcels(ctx context.Context, req *pb.ListCourierParcelsRequest) (*pb.ListCourierParcelsReply, error) { + // 参数验证 + if req.CourierId <= 0 { + s.log.Errorf("快递员ID参数无效") + return nil, fmt.Errorf("快递员ID参数无效") + } + + // 调用业务逻辑层查询 + cabinetMap, cabinetParcels, err := s.cabinetUc.ListCourierParcels(ctx, req.CourierId, req.ParcelStatus) + if err != nil { + s.log.Errorf("查询快递员派件列表失败: %v", err) + return nil, err + } + + // 转换为 proto 响应格式,构建层级结构 + groups := make([]*pb.CabinetParcelGroup, 0, len(cabinetMap)) + totalParcels := 0 + + // 遍历每个柜机,构建柜机-包裹分组 + for cabinetId, cabinet := range cabinetMap { + parcels := cabinetParcels[cabinetId] + + // 转换柜机信息 + cabinetInfo := &pb.CabinetInfo{ + Id: cabinet.Id, + CabinetCode: cabinet.CabinetCode, + CabinetName: cabinet.CabinetName, + CabinetType: cabinet.CabinetType, + Location: cabinet.Location, + Latitude: cabinet.Latitude, + Longitude: cabinet.Longitude, + TotalCompartments: cabinet.TotalCompartments, + AvailableCompartments: cabinet.AvailableCompartments, + Status: int32(cabinet.Status), + } + + // 转换包裹列表 + parcelInfos := make([]*pb.ParcelInfo, len(parcels)) + for i, parcel := range parcels { + parcelInfos[i] = &pb.ParcelInfo{ + ParcelId: parcel.ParcelId, + ExpressNo: parcel.ExpressNo, + RecipientName: parcel.RecipientName, + RecipientPhone: parcel.RecipientPhone, + LockerId: parcel.LockerId, + ParcelStatus: int32(parcel.ParcelStatus), + DispatchTime: parcel.DispatchTime.Format("2006-01-02 15:04:05"), + CourierId: parcel.CourierId, + } + } + + // 构建分组 + group := &pb.CabinetParcelGroup{ + Cabinet: cabinetInfo, + Parcels: parcelInfos, + ParcelCount: int32(len(parcels)), + } + groups = append(groups, group) + totalParcels += len(parcels) + } + + s.log.Infof("查询快递员派件列表成功,快递员ID: %d, 柜机数: %d, 包裹数: %d", req.CourierId, len(cabinetMap), totalParcels) + + return &pb.ListCourierParcelsReply{ + Groups: groups, + TotalParcels: int32(totalParcels), + TotalCabinets: int32(len(cabinetMap)), + }, nil +} + +// FindCabinet 根据ID查找智能柜 +// ctx: 请求上下文 +// req: 查找请求参数 +// 返回值:智能柜信息和错误信息 +func (s *CabinetService) FindCabinet(ctx context.Context, req *pb.FindCabinetRequest) (*pb.FindCabinetReply, error) { + return &pb.FindCabinetReply{}, nil +} + +// GetParcel 查询包裹详情 +// 根据包裹ID或快递单号后5位查询包裹详情 +// ctx: 请求上下文 +// req: 查询请求参数 +// 返回值:包裹详细信息(包括所在智能柜和格口信息) +func (s *CabinetService) GetParcel(ctx context.Context, req *pb.GetParcelRequest) (*pb.GetParcelReply, error) { + // 参数验证:至少提供一个查询条件 + if req.ParcelId <= 0 && req.ExpressNoSuffix == "" { + s.log.Errorf("请提供包裹ID或快递单号后5位") + return nil, fmt.Errorf("请提供包裹ID或快递单号后5位") + } + + // 调用业务逻辑层查询 + parcel, cabinet, locker, err := s.cabinetUc.GetParcel(ctx, req.ParcelId, req.ExpressNoSuffix) + if err != nil { + s.log.Errorf("查询包裹详情失败: %v", err) + return nil, err + } + + // 转换为 proto 响应格式 + parcelInfo := &pb.ParcelInfo{ + ParcelId: parcel.ParcelId, + ExpressNo: parcel.ExpressNo, + RecipientName: parcel.RecipientName, + RecipientPhone: parcel.RecipientPhone, + LockerId: parcel.LockerId, + ParcelStatus: int32(parcel.ParcelStatus), + } + + reply := &pb.GetParcelReply{ + Parcel: parcelInfo, + } + + // 如果查询到智能柜信息,添加到响应中 + if cabinet != nil { + cabinetInfo := &pb.CabinetInfo{ + Id: cabinet.Id, + CabinetCode: cabinet.CabinetCode, + CabinetName: cabinet.CabinetName, + CabinetType: cabinet.CabinetType, + Location: cabinet.Location, + Latitude: cabinet.Latitude, + Longitude: cabinet.Longitude, + TotalCompartments: cabinet.TotalCompartments, + AvailableCompartments: cabinet.AvailableCompartments, + Status: int32(cabinet.Status), + } + reply.Cabinet = cabinetInfo + } + + // 如果查询到格口信息,添加到响应中 + if locker != nil { + lockerInfo := &pb.LockerInfo{ + LockerId: locker.LockerId, + LockerNumber: locker.LockerNumber, + Size: locker.Size, + Status: int32(locker.Status), + CabinetId: locker.CabinetId, + } + reply.Locker = lockerInfo + } + + s.log.Infof("查询包裹详情成功,包裹ID: %d, 快递单号: %s", parcel.ParcelId, parcel.ExpressNo) + + return reply, nil +} diff --git a/internal/service/express_delivery.go b/internal/service/express_delivery.go new file mode 100644 index 0000000000000000000000000000000000000000..10c45a327f5031b3bfa0804b3a9cb984f040bd87 --- /dev/null +++ b/internal/service/express_delivery.go @@ -0,0 +1,438 @@ +package service + +import ( + "cabinet/internal/biz" + "cabinet/internal/model" + "context" + "encoding/json" + "log" + "net/http" + "strconv" + + "github.com/gorilla/websocket" + "gorm.io/gorm" +) + +// ExpressDeliveryService 快递通知服务 +type ExpressDeliveryService struct { + usecase biz.ExpressDeliveryUsecase + wsManager *WebSocketManager +} + +// NewExpressDeliveryService 创建快递通知服务实例 +func NewExpressDeliveryService(usecase biz.ExpressDeliveryUsecase, wsManager *WebSocketManager) *ExpressDeliveryService { + return &ExpressDeliveryService{ + usecase: usecase, + wsManager: wsManager, + } +} + +// SendDeliveryNotification 发送快递通知并通过WebSocket推送 +func (s *ExpressDeliveryService) SendDeliveryNotification(userID int32, notificationType, content, remark, status string, messageType int32) error { + // 1. 保存通知到数据库 + ctx := context.Background() + err := s.usecase.SendNotification(ctx, userID, notificationType, content, remark, status, messageType) + if err != nil { + return err + } + + // 2. 构建通知消息(不再包装WebSocketMessage,避免重复封装) + notification := map[string]interface{}{ + "user_id": userID, + "notification_type": notificationType, + "content": content, + "remark": remark, + "status": status, + } + + // 3. 通过WebSocket实时推送给在线用户 + err = s.wsManager.SendToUser(int(userID), notification) + if err != nil { + log.Printf("用户 %d 不在线,通知已保存: %v\n", userID, err) + // 用户不在线,消息已保存到数据库,用户上线后可以从数据库获取 + } + + return nil +} + +// GetUserNotificationsByType 根据用户ID和消息类型获取通知列表 +func (s *ExpressDeliveryService) GetUserNotificationsByType(w http.ResponseWriter, r *http.Request) { + // 从查询参数获取用户ID + userIDStr := r.URL.Query().Get("user_id") + if userIDStr == "" { + http.Error(w, "缺少用户ID", http.StatusBadRequest) + return + } + + // 从查询参数获取消息类型 + messageTypeStr := r.URL.Query().Get("message_type") + if messageTypeStr == "" { + http.Error(w, "缺少消息类型", http.StatusBadRequest) + return + } + + userID, err := strconv.ParseInt(userIDStr, 10, 32) + if err != nil { + http.Error(w, "无效的用户ID", http.StatusBadRequest) + return + } + + messageType, err := strconv.ParseInt(messageTypeStr, 10, 32) + if err != nil { + http.Error(w, "无效的消息类型", http.StatusBadRequest) + return + } + + // 获取分页参数 + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size")) + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 20 + } + + // 获取通知列表 + ctx := r.Context() + notifications, unreadCount, err := s.usecase.GetUserNotificationsByType(ctx, int32(userID), int32(messageType), page, pageSize) + if err != nil { + http.Error(w, "获取通知失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 返回结果 + response := map[string]interface{}{ + "notifications": notifications, + "unread_count": unreadCount, + "page": page, + "page_size": pageSize, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// GetUserNotificationFields 根据用户ID和消息类型获取指定字段的通知列表 +func (s *ExpressDeliveryService) GetUserNotificationFields(w http.ResponseWriter, r *http.Request) { + // 从查询参数获取用户ID + userIDStr := r.URL.Query().Get("user_id") + if userIDStr == "" { + http.Error(w, "缺少用户ID", http.StatusBadRequest) + return + } + + // 从查询参数获取消息类型 + messageTypeStr := r.URL.Query().Get("message_type") + if messageTypeStr == "" { + http.Error(w, "缺少消息类型", http.StatusBadRequest) + return + } + + userID, err := strconv.ParseInt(userIDStr, 10, 32) + if err != nil { + http.Error(w, "无效的用户ID", http.StatusBadRequest) + return + } + + messageType, err := strconv.ParseInt(messageTypeStr, 10, 32) + if err != nil { + http.Error(w, "无效的消息类型", http.StatusBadRequest) + return + } + + // 获取分页参数 + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size")) + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 20 + } + + // 获取通知列表 + ctx := r.Context() + notifications, unreadCount, err := s.usecase.GetUserNotificationsByType(ctx, int32(userID), int32(messageType), page, pageSize) + if err != nil { + http.Error(w, "获取通知失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 构造只包含指定字段的响应 + type NotificationField struct { + ID int32 `json:"id"` // 添加id字段,用于详情查询 + NotificationType string `json:"notification_type"` + NotificationContent string `json:"notification_content"` + CreateTime string `json:"create_time"` + } + + var result []NotificationField + for _, notification := range notifications { + result = append(result, NotificationField{ + ID: notification.Id, // 使用正确的字段名Id + NotificationType: notification.NotificationType, + NotificationContent: notification.NotificationContent, + CreateTime: notification.CreateTime.Format("2006-01-02 15:04:05"), + }) + } + + // 返回结果 + response := map[string]interface{}{ + "notifications": result, + "unread_count": unreadCount, + "page": page, + "page_size": pageSize, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// GetUserNotifications 获取用户通知列表 +func (s *ExpressDeliveryService) GetUserNotifications(w http.ResponseWriter, r *http.Request) { + // 从查询参数获取用户ID + userIDStr := r.URL.Query().Get("user_id") + if userIDStr == "" { + http.Error(w, "缺少用户ID", http.StatusBadRequest) + return + } + + userID, err := strconv.ParseInt(userIDStr, 10, 32) + if err != nil { + http.Error(w, "无效的用户ID", http.StatusBadRequest) + return + } + + // 获取分页参数 + page, _ := strconv.Atoi(r.URL.Query().Get("page")) + pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size")) + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 20 + } + + // 获取通知列表 + ctx := r.Context() + notifications, unreadCount, err := s.usecase.GetUserNotifications(ctx, int32(userID), page, pageSize) + if err != nil { + http.Error(w, "获取通知失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 返回结果 + response := map[string]interface{}{ + "notifications": notifications, + "unread_count": unreadCount, + "page": page, + "page_size": pageSize, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +// GetNotificationByID 根据ID、用户ID和消息类型获取单个通知 +func (s *ExpressDeliveryService) GetNotificationByID(w http.ResponseWriter, r *http.Request) { + // 从查询参数获取通知ID + idStr := r.URL.Query().Get("id") + if idStr == "" { + http.Error(w, "缺少通知ID", http.StatusBadRequest) + return + } + + // 从查询参数获取用户ID + userIDStr := r.URL.Query().Get("user_id") + if userIDStr == "" { + http.Error(w, "缺少用户ID", http.StatusBadRequest) + return + } + + // 从查询参数获取消息类型 + messageTypeStr := r.URL.Query().Get("message_type") + if messageTypeStr == "" { + http.Error(w, "缺少消息类型", http.StatusBadRequest) + return + } + + // 解析参数 + id, err := strconv.ParseInt(idStr, 10, 32) + if err != nil { + http.Error(w, "无效的通知ID", http.StatusBadRequest) + return + } + + userID, err := strconv.ParseInt(userIDStr, 10, 32) + if err != nil { + http.Error(w, "无效的用户ID", http.StatusBadRequest) + return + } + + messageType, err := strconv.ParseInt(messageTypeStr, 10, 32) + if err != nil { + http.Error(w, "无效的消息类型", http.StatusBadRequest) + return + } + + // 获取通知 + ctx := r.Context() + notification, err := s.usecase.GetNotificationByIDAndUserAndType(ctx, int32(id), int32(userID), int32(messageType)) + if err != nil { + if err == gorm.ErrRecordNotFound { + http.Error(w, "通知不存在", http.StatusNotFound) + return + } + http.Error(w, "获取通知失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 构造只包含指定字段的响应 + type NotificationField struct { + Remark string `json:"remark"` + NotificationType string `json:"notification_type"` + CreateTime string `json:"create_time"` + } + + result := NotificationField{ + Remark: notification.Remark, + NotificationType: notification.NotificationType, + CreateTime: notification.CreateTime.Format("2006-01-02 15:04:05"), + } + + // 返回完整响应,包含状态码、消息和数据 + w.Header().Set("Content-Type", "application/json") + response := map[string]interface{}{ + "code": 0, + "message": "success", + "data": result, + } + json.NewEncoder(w).Encode(response) +} + +// MarkNotificationRead 标记通知已读 +func (s *ExpressDeliveryService) MarkNotificationRead(w http.ResponseWriter, r *http.Request) { + // 从查询参数获取通知ID + notificationIDStr := r.URL.Query().Get("notification_id") + if notificationIDStr == "" { + http.Error(w, "缺少通知ID", http.StatusBadRequest) + return + } + + notificationID, err := strconv.ParseInt(notificationIDStr, 10, 32) + if err != nil { + http.Error(w, "无效的通知ID", http.StatusBadRequest) + return + } + + // 标记已读 + ctx := r.Context() + err = s.usecase.MarkNotificationRead(ctx, int32(notificationID)) + if err != nil { + http.Error(w, "标记已读失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 返回成功 + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "success"}) +} + +// SendNotificationHandler 处理发送快递通知的HTTP请求 +func (s *ExpressDeliveryService) SendNotificationHandler(w http.ResponseWriter, r *http.Request) { + // 解析请求体 + var req struct { + UserID int32 `json:"user_id"` + NotificationType string `json:"notification_type"` + Content string `json:"content"` + Remark string `json:"remark"` + Status string `json:"status"` + MessageType int32 `json:"message_type"` + } + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "无效的请求格式: "+err.Error(), http.StatusBadRequest) + return + } + + // 验证必填字段 + if req.UserID <= 0 { + http.Error(w, "缺少用户ID", http.StatusBadRequest) + return + } + if req.NotificationType == "" { + http.Error(w, "缺少通知类型", http.StatusBadRequest) + return + } + if req.Content == "" { + http.Error(w, "缺少通知内容", http.StatusBadRequest) + return + } + + // 发送通知 + err := s.SendDeliveryNotification(req.UserID, req.NotificationType, req.Content, req.Remark, req.Status, req.MessageType) + if err != nil { + http.Error(w, "发送通知失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 返回成功响应 + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "通知发送成功"}) +} + +// HandleDeliveryWebSocket 处理快递通知的WebSocket连接 +func (s *ExpressDeliveryService) HandleDeliveryWebSocket(w http.ResponseWriter, r *http.Request) { + // 从查询参数获取用户ID + userIDStr := r.URL.Query().Get("user_id") + if userIDStr == "" { + http.Error(w, "缺少用户ID", http.StatusBadRequest) + return + } + + userID, err := strconv.Atoi(userIDStr) + if err != nil { + http.Error(w, "无效的用户ID", http.StatusBadRequest) + return + } + + // 升级HTTP连接为WebSocket连接 + upgrader := websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, + } + + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Printf("WebSocket升级失败: %v\n", err) + return + } + + // 创建新的客户端 + client := &Client{ + UserID: userID, + Conn: conn, + Send: make(chan []byte, 256), + Manager: s.wsManager, + } + + // 注册客户端 + client.Manager.register <- client + + // 发送连接成功消息 + welcomeMsg := model.WebSocketMessage{ + Type: "delivery_connect", + UserID: userID, + Data: "快递通知WebSocket连接成功", + } + msgBytes, _ := json.Marshal(welcomeMsg) + client.Send <- msgBytes + + // 启动goroutine处理消息读写 + go client.writePump() + go client.readPump() +} \ No newline at end of file diff --git a/internal/service/greeter.go b/internal/service/greeter.go index f7fff6dbf6da213f558d0637f94e7a65388f6250..ca20b2643fb78dddd02b411f3e85f06220d83f18 100644 --- a/internal/service/greeter.go +++ b/internal/service/greeter.go @@ -1,8 +1,8 @@ package service import ( - v1 "Cabinet/api/helloworld/v1" - "Cabinet/internal/biz" + "cabinet/api/helloworld/v1" + "cabinet/internal/biz" "context" ) diff --git a/internal/service/help_service.go b/internal/service/help_service.go new file mode 100644 index 0000000000000000000000000000000000000000..08de17bb01203e298526ea234ddb87728dc10185 --- /dev/null +++ b/internal/service/help_service.go @@ -0,0 +1,144 @@ +package service + +import ( + "cabinet/api/help/v1" + "cabinet/internal/biz" + "cabinet/internal/model" + "context" + "fmt" + + "github.com/go-kratos/kratos/v2/log" +) + +// HelpService 帮助中心服务 +type HelpService struct { + v1.UnimplementedHelpServiceServer + usecase biz.HelpArticleUsecase + log *log.Helper +} + +// NewHelpService 创建帮助中心服务实例 +func NewHelpService(usecase biz.HelpArticleUsecase, logger log.Logger) *HelpService { + return &HelpService{ + usecase: usecase, + log: log.NewHelper(logger), + } +} + +// GetCategories 获取帮助中心分类结构 +func (s *HelpService) GetCategories(ctx context.Context, req *v1.GetCategoriesRequest) (*v1.GetCategoriesReply, error) { + categories, err := s.usecase.GetCategories(ctx) + if err != nil { + s.log.Errorf("failed to get categories: %v", err) + return nil, fmt.Errorf("failed to get categories: %w", err) + } + + // 转换为proto响应格式 + reply := &v1.GetCategoriesReply{ + Categories: make([]*v1.HelpCategory, 0, len(categories)), + } + + for _, category := range categories { + reply.Categories = append(reply.Categories, &v1.HelpCategory{ + Category: category.Category, + SubCategories: category.SubCategories, + }) + } + + return reply, nil +} + +// GetArticles 根据分类获取文章列表 +func (s *HelpService) GetArticles(ctx context.Context, req *v1.GetArticlesRequest) (*v1.GetArticlesReply, error) { + // 构建查询参数 + query := model.ArticleQuery{ + Category: req.Category, + SubCategory: req.SubCategory, + Page: int(req.Page), + PageSize: int(req.PageSize), + } + + articles, total, err := s.usecase.GetArticles(ctx, query) + if err != nil { + s.log.Errorf("failed to get articles: %v", err) + return nil, fmt.Errorf("failed to get articles: %w", err) + } + + // 转换为proto响应格式 + reply := &v1.GetArticlesReply{ + Articles: make([]*v1.HelpArticle, 0, len(articles)), + Total: int32(total), + CurrentPage: req.Page, + PageSize: req.PageSize, + } + + for _, article := range articles { + reply.Articles = append(reply.Articles, s.convertToProtoArticle(article)) + } + + return reply, nil +} + +// SearchArticles 搜索帮助文章 +func (s *HelpService) SearchArticles(ctx context.Context, req *v1.SearchArticlesRequest) (*v1.SearchArticlesReply, error) { + // 构建查询参数 + query := model.ArticleQuery{ + Keyword: req.Keyword, + Page: int(req.Page), + PageSize: int(req.PageSize), + } + + articles, total, err := s.usecase.SearchArticles(ctx, query) + if err != nil { + s.log.Errorf("failed to search articles: %v", err) + return nil, fmt.Errorf("failed to search articles: %w", err) + } + + // 转换为proto响应格式 + reply := &v1.SearchArticlesReply{ + Articles: make([]*v1.HelpArticle, 0, len(articles)), + Total: int32(total), + CurrentPage: req.Page, + PageSize: req.PageSize, + } + + for _, article := range articles { + reply.Articles = append(reply.Articles, s.convertToProtoArticle(article)) + } + + return reply, nil +} + +// GetArticleDetail 获取文章详情 +func (s *HelpService) GetArticleDetail(ctx context.Context, req *v1.GetArticleDetailRequest) (*v1.GetArticleDetailReply, error) { + article, err := s.usecase.GetArticleDetail(ctx, req.Id) + if err != nil { + s.log.Errorf("failed to get article detail: %v", err) + return nil, fmt.Errorf("failed to get article detail: %w", err) + } + + // 转换为proto响应格式 + reply := &v1.GetArticleDetailReply{ + Article: s.convertToProtoArticle(*article), + } + + return reply, nil +} + +// convertToProtoArticle 将模型转换为proto格式 +func (s *HelpService) convertToProtoArticle(article model.HelpArticle) *v1.HelpArticle { + return &v1.HelpArticle{ + Id: article.ID, + Title: article.Title, + Content: article.Content, + Category: article.Category, + SubCategory: article.SubCategory, + OrderWeight: int32(article.OrderWeight), + ViewCount: int32(article.ViewCount), + IsActive: article.IsActive, + CreatedAt: article.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: article.UpdatedAt.Format("2006-01-02 15:04:05"), + } +} + +// 注意:不再需要手动注册HTTP路由,使用Kratos自动生成的HTTP路由注册 diff --git a/internal/service/helpservice.go b/internal/service/helpservice.go new file mode 100644 index 0000000000000000000000000000000000000000..13272b18381525bc2bb1a570f4ed9d7247c55b49 --- /dev/null +++ b/internal/service/helpservice.go @@ -0,0 +1,28 @@ +package service + +import ( + "context" + + pb "cabinet/api/help/v1" +) + +type HelpServiceService struct { + pb.UnimplementedHelpServiceServer +} + +func NewHelpServiceService() *HelpServiceService { + return &HelpServiceService{} +} + +func (s *HelpServiceService) GetCategories(ctx context.Context, req *pb.GetCategoriesRequest) (*pb.GetCategoriesReply, error) { + return &pb.GetCategoriesReply{}, nil +} +func (s *HelpServiceService) GetArticles(ctx context.Context, req *pb.GetArticlesRequest) (*pb.GetArticlesReply, error) { + return &pb.GetArticlesReply{}, nil +} +func (s *HelpServiceService) SearchArticles(ctx context.Context, req *pb.SearchArticlesRequest) (*pb.SearchArticlesReply, error) { + return &pb.SearchArticlesReply{}, nil +} +func (s *HelpServiceService) GetArticleDetail(ctx context.Context, req *pb.GetArticleDetailRequest) (*pb.GetArticleDetailReply, error) { + return &pb.GetArticleDetailReply{}, nil +} diff --git a/internal/service/message.go b/internal/service/message.go new file mode 100644 index 0000000000000000000000000000000000000000..fb9645d6eeaf9792539a7719a2b631b5d57b76a6 --- /dev/null +++ b/internal/service/message.go @@ -0,0 +1,123 @@ +package service + +import ( + "cabinet/internal/model" + "log" + "net/http" + "strconv" + + "github.com/gorilla/websocket" +) + +// MessageService 消息服务 +type MessageService struct { + wsManager *WebSocketManager + messageRepo interface{} +} + +// NewMessageService 创建消息服务实例 +func NewMessageService(wsManager *WebSocketManager, messageRepo interface{}) *MessageService { + return &MessageService{ + wsManager: wsManager, + messageRepo: messageRepo, + } +} + +// WebSocket连接升级器 +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + // 允许所有CORS请求 + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +// HandleWebSocket 处理WebSocket连接请求 +func (s *MessageService) HandleWebSocket(w http.ResponseWriter, r *http.Request) { + // 从查询参数获取用户ID + userIDStr := r.URL.Query().Get("user_id") + if userIDStr == "" { + http.Error(w, "缺少用户ID", http.StatusBadRequest) + return + } + + userID, err := strconv.Atoi(userIDStr) + if err != nil { + http.Error(w, "无效的用户ID", http.StatusBadRequest) + return + } + + // 升级HTTP连接为WebSocket连接 + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Printf("WebSocket升级失败: %v\n", err) + return + } + + // 创建新的客户端 + client := &Client{ + UserID: userID, + Conn: conn, + Send: make(chan []byte, 256), + Manager: s.wsManager, + } + + // 注册客户端 + client.Manager.register <- client + + // 启动goroutine处理消息读写 + go client.writePump() + go client.readPump() +} + +// SendMessage 发送消息给指定用户 +func (s *MessageService) SendMessage(userID int, title, content string, msgType int) error { + message := model.Message{ + UserID: userID, + Type: msgType, + Title: title, + Content: content, + IsRead: false, + } + + // 尝试通过WebSocket发送 + err := s.wsManager.SendToUser(userID, message) + if err != nil { + // 用户不在线,可以将消息存储到数据库,等用户上线后推送 + log.Printf("用户 %d 不在线,消息将稍后推送: %v\n", userID, err) + // 这里可以添加数据库存储逻辑 + } + + return nil +} + +// BroadcastMessage 广播消息给所有在线用户 +func (s *MessageService) BroadcastMessage(title, content string, msgType int) { + message := model.Message{ + Type: msgType, + Title: title, + Content: content, + IsRead: false, + } + + s.wsManager.Broadcast(message) +} + +// GetOnlineStatus 获取用户在线状态 +func (s *MessageService) GetOnlineStatus(userID int) bool { + s.wsManager.mutex.RLock() + defer s.wsManager.mutex.RUnlock() + _, ok := s.wsManager.clients[userID] + return ok +} + +// GetOnlineStatistics 获取在线统计信息 +func (s *MessageService) GetOnlineStatistics() (map[string]interface{}, error) { + stats := map[string]interface{}{ + "online_count": s.wsManager.GetOnlineCount(), + "online_users": s.wsManager.GetOnlineUsers(), + } + + return stats, nil +} \ No newline at end of file diff --git a/internal/service/order.go b/internal/service/order.go new file mode 100644 index 0000000000000000000000000000000000000000..40e857ede800037242cba98303c8a6db9726de4d --- /dev/null +++ b/internal/service/order.go @@ -0,0 +1,466 @@ +package service + +import ( + "cabinet/api/order/v1" + "cabinet/internal/biz" + "cabinet/internal/data" + "cabinet/internal/model" + "cabinet/internal/pkg" + "context" + "crypto" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "github.com/go-kratos/kratos/v2/log" + "github.com/go-kratos/kratos/v2/transport/http" + "github.com/google/uuid" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "io" + "net/url" + "sort" + "strings" + "time" +) + +type OrderService struct { + v1.UnimplementedOrderServer + db *gorm.DB + userRepo biz.UserRepo + log *log.Helper +} + +func NewOrderService(data *data.Data, userRepo biz.UserRepo, logger log.Logger) *OrderService { + return &OrderService{db: data.Db(), userRepo: userRepo, log: log.NewHelper(logger)} +} + +// 创建充值订单 +func (s *OrderService) CreateOrder(ctx context.Context, req *v1.CreateOrderRequest) (*v1.CreateOrderReply, error) { + if req.Amount <= 0 { + return nil, fmt.Errorf("金额必须大于0") + } + + user, err := s.userRepo.GetUserByID(ctx, req.UserId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("用户不存在") + } + return nil, err + } + + if user == nil { + return nil, fmt.Errorf("用户不存在") + } + + if strings.ToLower(user.UserType) == "disabled" { + return nil, fmt.Errorf("用户状态异常,无法创建订单") + } + + orderNo := fmt.Sprintf("RO%s%s", time.Now().Format("20060102150405"), uuid.NewString()[:8]) + + order := &model.RechargeOrder{ + OrderNo: orderNo, + UserId: int32(user.UserId), + Amount: float64(req.Amount), + FinalAmount: float64(req.Amount), + Currency: req.Currency, + PayMethod: req.PayMethod, + Status: 1, + } + + if err := s.db.WithContext(ctx).Create(order).Error; err != nil { + return nil, err + } + + payURL := pkg.Alipay(orderNo, fmt.Sprintf("%.2f", float64(req.Amount))) + + return &v1.CreateOrderReply{ + OrderNo: orderNo, + PayUrl: payURL, + }, nil +} + +// 获取充值记录列表(分页,按创建时间倒序) +func (s *OrderService) ListRecords(ctx context.Context, req *v1.ListRecordsRequest) (*v1.ListRecordsReply, error) { + page := req.Page + if page <= 0 { + page = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = 10 + } + offset := (page - 1) * pageSize + + query := s.db.WithContext(ctx).Model(&model.RechargeOrder{}) + if req.UserId > 0 { + query = query.Where("user_id = ?", req.UserId) + } + if req.Status > 0 { + query = query.Where("status = ?", req.Status) + } + + var total int64 + if err := query.Count(&total).Error; err != nil { + return nil, err + } + + var orders []model.RechargeOrder + if err := query.Order("created_at DESC").Limit(int(pageSize)).Offset(int(offset)).Find(&orders).Error; err != nil { + return nil, err + } + + list := make([]*v1.ListRecords, 0, len(orders)) + for _, o := range orders { + list = append(list, &v1.ListRecords{ + OrderNo: o.OrderNo, + Amount: int64(o.Amount), + Currency: o.Currency, + PayMethod: o.PayMethod, + Status: int64(o.Status), + CreatedAt: o.CreatedAt.Format("2006-01-02 15:04:05"), + }) + } + + return &v1.ListRecordsReply{List: list, Total: total}, nil +} + +// 获取充值订单详情 +func (s *OrderService) GetDetail(ctx context.Context, req *v1.GetDetailRequest) (*v1.GetDetailReply, error) { + if req.OrderNo == "" { + return nil, errors.New("订单必须存在") + } + + var o model.RechargeOrder + if err := s.db.WithContext(ctx).Where("order_no = ?", req.OrderNo).First(&o).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("订单没找到") + } + return nil, err + } + + return &v1.GetDetailReply{ + OrderNo: o.OrderNo, + Amount: int64(o.Amount), + Currency: o.Currency, + PayMethod: o.PayMethod, + Status: int64(o.Status), + TradeNo: o.TradeNo, + }, nil +} + +// buildSignContent 构造支付宝签名原文 +func buildSignContent(form url.Values) string { + keys := make([]string, 0, len(form)) + for k := range form { + if k == "sign" || k == "sign_type" { + continue + } + keys = append(keys, k) + } + sort.Strings(keys) + var b strings.Builder + for i, k := range keys { + vals := form[k] + v := "" + if len(vals) > 0 { + v = vals[0] + } + if i > 0 { + b.WriteString("&") + } + b.WriteString(k) + b.WriteString("=") + b.WriteString(v) + } + return b.String() +} + +// verifyAlipaySign 使用支付宝公钥验证签名(RSA2) +// 简化版验签函数(只支持 RSA2) +func verifyAlipaySign(params map[string]string, sign, publicKey string) error { + // 移除空值和签名参数 + filteredParams := make(map[string]string) + for k, v := range params { + if v != "" && k != "sign" && k != "sign_type" { + filteredParams[k] = v + } + } + + // 按字典序排序 + keys := make([]string, 0, len(filteredParams)) + for k := range filteredParams { + keys = append(keys, k) + } + sort.Strings(keys) + + // 构建待签名字符串 + var signContent string + for i, k := range keys { + if i > 0 { + signContent += "&" + } + signContent += k + "=" + filteredParams[k] + } + + // 解码签名 + decodedSign, err := base64.StdEncoding.DecodeString(sign) + if err != nil { + return fmt.Errorf("failed to decode signature: %v", err) + } + + // 解析公钥 + block, _ := pem.Decode([]byte("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u+qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyehkd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdgcKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbcmwIDAQAB")) + if block == nil { + return fmt.Errorf("failed to parse public key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return fmt.Errorf("failed to parse public key: %v", err) + } + + rsaPub, ok := pub.(*rsa.PublicKey) + if !ok { + return fmt.Errorf("not RSA public key") + } + + // 计算 SHA256 hash + hash := sha256.Sum256([]byte(signContent)) + + // 验证签名 + err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, hash[:], decodedSign) + if err != nil { + return fmt.Errorf("signature verification failed: %v", err) + } + + return nil +} + +func (s *OrderService) Notify(ctx context.Context, req *v1.NotifyRequest) (*v1.NotifyReply, error) { + s.log.Info("Received Alipay notification callback") + + hr, ok := http.RequestFromServerContext(ctx) + if !ok { + s.log.Error("Failed to get HTTP request from context") + return &v1.NotifyReply{Result: "fail"}, nil + } + + r := hr + + // 根据 Content-Type 处理不同格式 + contentType := r.Header.Get("Content-Type") + s.log.Infof("Content-Type: %s", contentType) + + var outTradeNo, tradeStatus, tradeNo, totalAmountStr, appId string + + if strings.Contains(contentType, "application/json") { + // 处理 JSON 格式 + bodyBytes, err := io.ReadAll(r.Body) + if err != nil { + s.log.Errorf("Failed to read JSON body: %v", err) + return &v1.NotifyReply{Result: "fail"}, nil + } + + var jsonData map[string]interface{} + if err := json.Unmarshal(bodyBytes, &jsonData); err != nil { + s.log.Errorf("Failed to parse JSON: %v", err) + return &v1.NotifyReply{Result: "fail"}, nil + } + + s.log.Infof("JSON data: %+v", jsonData) + + // 从 JSON 中提取字段 + if val, ok := jsonData["out_trade_no"].(string); ok { + outTradeNo = val + } + if val, ok := jsonData["order_no"].(string); ok { // 也支持 order_no + outTradeNo = val + } + if val, ok := jsonData["trade_status"].(string); ok { + tradeStatus = val + } + if val, ok := jsonData["trade_no"].(string); ok { + tradeNo = val + } + if val, ok := jsonData["total_amount"].(string); ok { + totalAmountStr = val + } + if val, ok := jsonData["app_id"].(string); ok { + appId = val + } + //为验签准备参数 + //params := make(map[string]string) + //for k, v := range jsonData { + // if str, ok := v.(string); ok { + // params[k] = str + // } + //} + } else { + // 处理表单格式 + if err := r.ParseForm(); err != nil { + s.log.Errorf("Failed to parse form: %v", err) + return &v1.NotifyReply{Result: "fail"}, nil + } + + outTradeNo = r.Form.Get("out_trade_no") + tradeStatus = r.Form.Get("trade_status") + tradeNo = r.Form.Get("trade_no") + totalAmountStr = r.Form.Get("total_amount") + appId = r.Form.Get("app_id") + } + ////为验签准备参数 + //params := make(map[string]string) + //for k, v := range r.Form { + // if len(v) > 0 { + // params[k] = v[0] + // } + //} + s.log.Infof("Final - out_trade_no: '%s'", outTradeNo) + s.log.Infof("Final - trade_status: '%s'", tradeStatus) + s.log.Infof("Final - trade_no: '%s'", tradeNo) + s.log.Infof("Final - total_amount: '%s'", totalAmountStr) + s.log.Infof("Final - app_id: '%s'", appId) + + // 在检查必需字段之前,如果是测试数据,设置默认值 + if outTradeNo != "" && tradeStatus == "" { + // 如果是测试,设置默认交易状态 + tradeStatus = "TRADE_SUCCESS" + s.log.Info("Using default trade_status for testing") + } + if outTradeNo == "" || tradeStatus == "" { + + s.log.Info("Using default trade_status for testing") + } + // 校验 app_id(可选)确保是我们自己的应用(如有需要可改为读取配置) + const expectedAppID = "2021000149637324" + if appId != "" && appId != expectedAppID { + s.log.Errorf("AppID mismatch: expected %s, got %s", expectedAppID, appId) + return &v1.NotifyReply{Result: "fail"}, nil + } + + // 验证签名 - 对于测试环境,可以先跳过签名验证 + // 为了调试,先返回success,后续再启用签名验证 + // 实际生产环境必须启用签名验证! + + //alipayPublicKey := "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl4vcTjuW8WOi/JzWRs++1Gpjp5z2NpT6JPyQaImoolXQlQbuQTsJZazuhDT5DTTxF955lMPxTEchVkaeFcMgAQVCXv+s//PTZNezzOflMIdQjbsUsJBeMgU5L4y2MIZ0yXMPxo4J5HxPCmp71+NQDlyC8HEotgsdqSSEVKbfMtub1q1mcHZQL+fjlktjyT77quxAiIeg7lA+zDcg49b5n/zAH0iiLW/hIjBAczrOxG8kTHM95F7eSQM5lkhAOn6nvS8Fnm2QOzsVMoq9vS572OGyArBjt3D3WyH28kgZ2PymJxyR4BJ3QN2/05xsA4nBLmIc77pMNxgdv2Kn3niSZQIDAQAB" + //获取签名 + //sign, exists := params["sign"] + //if !exists { + // s.log.Errorf("Alipay signature not found in parameters") + // return &v1.NotifyReply{Result: "fail"}, nil + //} + //验签 + //if err := verifyAlipaySign(params, sign, alipayPublicKey); err != nil { + // s.log.Errorf("Alipay signature verification failed:%v", err) + // return &v1.NotifyReply{Result: "fail"}, nil + //} + //s.log.Infof("Alipay signature verification successful") + // 幂等处理:查订单并加锁 + tx := s.db.WithContext(ctx).Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + var ord model.RechargeOrder + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("order_no = ?", outTradeNo).First(&ord).Error; err != nil { + tx.Rollback() + s.log.Errorf("Order not found: %s, error: %v", outTradeNo, err) + return &v1.NotifyReply{Result: "fail"}, nil + } + + // 已处理的订单直接返回 success(幂等) + if ord.Status == 2 && tradeStatus == "TRADE_SUCCESS" { + tx.Rollback() + s.log.Infof("Order already paid: %s", outTradeNo) + return &v1.NotifyReply{Result: "success"}, nil + } + if ord.Status == 3 && tradeStatus == "TRADE_CLOSED" { + tx.Rollback() + s.log.Infof("Order already closed: %s", outTradeNo) + return &v1.NotifyReply{Result: "success"}, nil + } + + // 基于交易状态更新订单 + switch tradeStatus { + case "TRADE_SUCCESS", "TRADE_FINISHED": + // 解析金额 + var paidAmount float64 + if totalAmountStr != "" { + fmt.Sscanf(totalAmountStr, "%f", &paidAmount) + } + if paidAmount <= 0 { + paidAmount = ord.FinalAmount + } + + // 更新订单为支付成功 + ord.Status = 2 + ord.TradeNo = tradeNo + ord.PayTime = time.Now() + ord.FinalAmount = paidAmount + if err := tx.Save(&ord).Error; err != nil { + tx.Rollback() + s.log.Errorf("Failed to update order: %v", err) + return &v1.NotifyReply{Result: "fail"}, nil + } + + // 更新余额流水(基于上一次余额进行累加) + var lastFlow model.BalanceFlow + var lastBalance float64 + if err := tx.Where("user_id = ?", ord.UserId).Order("id DESC").Limit(1).First(&lastFlow).Error; err == nil { + lastBalance = lastFlow.Balance + } + newBalance := lastBalance + paidAmount + bf := &model.BalanceFlow{ + UserId: ord.UserId, + OrderNo: ord.OrderNo, + Amount: paidAmount, + FlowType: 1, // 充值 + Balance: newBalance, + Remark: "充值成功", + } + + // 避免重复插入同一订单的充值流水 + var exist int64 + if err := tx.Model(&model.BalanceFlow{}).Where("order_no = ? AND flow_type = 1", ord.OrderNo).Count(&exist).Error; err != nil { + tx.Rollback() + s.log.Errorf("Failed to check existing balance flow: %v", err) + return &v1.NotifyReply{Result: "fail"}, nil + } + if exist == 0 { + if err := tx.Create(bf).Error; err != nil { + tx.Rollback() + s.log.Errorf("Failed to create balance flow: %v", err) + return &v1.NotifyReply{Result: "fail"}, nil + } + } + + case "TRADE_CLOSED": + // 标记订单为交易关闭/失败 + ord.Status = 3 + if err := tx.Save(&ord).Error; err != nil { + tx.Rollback() + s.log.Errorf("Failed to close order: %v", err) + return &v1.NotifyReply{Result: "fail"}, nil + } + default: + // 未知状态,不处理 + tx.Rollback() + s.log.Warnf("Unknown trade status: %s for order: %s", tradeStatus, outTradeNo) + return &v1.NotifyReply{Result: "fail"}, nil + } + + if err := tx.Commit().Error; err != nil { + s.log.Errorf("Failed to commit transaction: %v", err) + return &v1.NotifyReply{Result: "fail"}, nil + } + s.log.Infof("Order processed successfully: %s, status: %s", outTradeNo, tradeStatus) + return &v1.NotifyReply{Result: "success"}, nil +} diff --git a/internal/service/real.go b/internal/service/real.go new file mode 100644 index 0000000000000000000000000000000000000000..94c61115b99521b55a44afb3e1177af70627709e --- /dev/null +++ b/internal/service/real.go @@ -0,0 +1,89 @@ +package service + +import ( + realv1 "cabinet/api/real/v1" + "cabinet/internal/biz" + "cabinet/internal/untl" + "context" + "net/http" +) + +type RealService struct { + realv1.UnimplementedRealServer + realUsecase biz.RealUsecase +} + +func NewRealService(realUsecase biz.RealUsecase) *RealService { + return &RealService{realUsecase: realUsecase} +} + +func (s *RealService) RealName(ctx context.Context, req *realv1.RealNameRequest) (*realv1.RealNameReply, error) { + // 转换请求参数 + bizReq := &biz.RealNameRequest{ + Card: req.GetCard(), + Name: req.GetName(), + UserId: int(req.GetUserId()), + } + + // 调用业务逻辑层 + resp, err := s.realUsecase.RealName(ctx, bizReq) + if err != nil { + return nil, err + } + + // 转换响应参数 + return &realv1.RealNameReply{ + UserId: int32(resp.UserId), + }, nil +} + +// CourierAuthAdd 快递员认证添加 +func (s *RealService) CourierAuthAdd(ctx context.Context, req *realv1.CourierAuthAddRequest) (*realv1.CourierAuthAddReply, error) { + // 转换请求参数 + bizReq := &biz.CourierAuthAddRequest{ + UserId: int(req.GetUserId()), + CompanyId: int(req.GetCompanyId()), + CardPhotoUrl: req.GetCardPhotoUrl(), + } + + // 调用业务逻辑层 + resp, err := s.realUsecase.CourierAuthAdd(ctx, bizReq) + if err != nil { + return nil, err + } + + // 转换响应参数 + return &realv1.CourierAuthAddReply{ + UserId: int32(resp.UserId), + }, nil +} + +// UploadCardPhoto 上传工牌照片 +func (s *RealService) UploadCardPhoto(w http.ResponseWriter, r *http.Request) { + // 解析多部分表单 + err := r.ParseMultipartForm(10 << 20) // 10MB + if err != nil { + http.Error(w, "解析表单失败: "+err.Error(), http.StatusBadRequest) + return + } + + // 获取上传的文件 + file, header, err := r.FormFile("card_photo") + if err != nil { + http.Error(w, "获取文件失败: "+err.Error(), http.StatusBadRequest) + return + } + defer file.Close() + + // 使用七牛云上传 + uploader := &untl.QiNiuYun{} + fileUrl, err := uploader.Qiniuyun(file, header) + if err != nil { + http.Error(w, "上传文件失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 返回文件URL + w.Header().Set("Content-Type", "application/json") + w.Write([]byte(`{"file_url": "` + fileUrl + `"}`)) +} diff --git a/internal/service/service.go b/internal/service/service.go index 55b04c7089993e98bd86f0a2be1cb5f16974cb86..ab8c12beef17a3d10f756b6f7da418ad5ca86c6f 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -1,10 +1,12 @@ package service -import "github.com/google/wire" +import ( + "github.com/google/wire" +) // ProviderSet is service providers. var ProviderSet = wire.NewSet( NewGreeterService, NewUserService, - NewCabinetService, + NewHelpService, ) diff --git a/internal/service/user.go b/internal/service/user.go index 0f08f3bf7a8db7b85035a1ee7fb099479525f4c0..9c575d232dca6cdb513ae4254c3b0efe7334a10d 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -1,16 +1,15 @@ package service import ( - pb "Cabinet/api/user/v1" - "Cabinet/internal/biz" - "Cabinet/internal/data" - "Cabinet/internal/pkg" + pb "cabinet/api/user/v1" + "cabinet/internal/biz" + "cabinet/internal/data" + "cabinet/internal/pkg" "context" "fmt" "github.com/go-kratos/kratos/v2/log" "math/rand" "regexp" - "strconv" "time" ) @@ -82,13 +81,14 @@ func (s *UserService) SendSms(ctx context.Context, req *pb.SendSmsRequest) (*pb. return nil, fmt.Errorf("每分钟内只能发送一条短信") } //4.短信发送成功存储redis缓存 + //5.亮点:异步短信发送,避免短信的重复发送(消息队列、幂等性) // 生成6位数字验证码 //code := generateSmsCode() - code := rand.Intn(9000) + 1000 + code := rand.Intn(900000) + 100000 // 在Redis中存储验证码,设置过期时间为15分钟 - key := fmt.Sprintf("sms:code:%s", req.Phone) + key := fmt.Sprintf("send" + req.Phone) err := s.data.Redis.Set(ctx, key, code, 15*time.Minute).Err() if err != nil { s.log.Errorf("存储验证码到Redis失败: %v", err) @@ -96,22 +96,15 @@ func (s *UserService) SendSms(ctx context.Context, req *pb.SendSmsRequest) (*pb. } // 检查用户是否已存在 - user, err := s.userUc.GetUserByPhone(ctx, req.Phone) - if err == nil && user != nil { - return &pb.SendSmsReply{}, nil - } + //user, err := s.userUc.GetUserByPhone(ctx, req.Phone) + //if err == nil && user != nil { + // return &pb.SendSmsReply{}, nil + //} // 记录日志 s.log.Infof("发送验证码到手机号: %s, 验证码: %s", req.Phone, code) - // 调用短信发送 - sms, err := pkg.SendSms(req.Phone, strconv.Itoa(code)) - if err != nil { - s.log.Errorf("发送短信失败: %v", err) - return nil, fmt.Errorf("发送短信失败: %w", err) - } - - s.log.Infof("短信发送成功: %s, 验证码: %s, 响应: %s", req.Phone, code, sms) + //s.log.Infof("短信发送成功: %s, 验证码: %s, 响应: %s", req.Phone, code, sms) //s.log.Infof("短信发送成功: %s, 验证码: %s", req.Phone, code) incr := s.data.Redis.Incr(ctx, "sendSms"+req.Phone) @@ -119,13 +112,23 @@ func (s *UserService) SendSms(ctx context.Context, req *pb.SendSmsRequest) (*pb. s.data.Redis.Expire(ctx, "sendSms"+req.Phone, 1*time.Minute) } - return &pb.SendSmsReply{}, nil + return &pb.SendSmsReply{ + Map: "短信发送成功", + }, nil } // 手机号+验证码+密码+二次密码注册 func (s *UserService) Register(ctx context.Context, req *pb.RegisterRequest) (*pb.RegisterReply, error) { + user, err := s.userUc.GetUserByPhone(ctx, req.Phone) + if err != nil { + s.log.Warnf("手机号已存在: %s", req.Phone) + return nil, fmt.Errorf("手机号已存在,请重试") + } + if user.Phone != "" { + return nil, fmt.Errorf("手机号已存在,请重试") + } // 验证验证码 - key := fmt.Sprintf("sms:code:%s", req.Phone) + key := fmt.Sprintf("send" + req.Phone) storedCode, err := s.data.Redis.Get(ctx, key).Result() if err != nil { if err.Error() == "" { @@ -134,17 +137,18 @@ func (s *UserService) Register(ctx context.Context, req *pb.RegisterRequest) (*p s.log.Errorf("从Redis获取验证码失败: %v", err) return nil, fmt.Errorf("获取验证码失败: %w", err) } - + //s.data.Redis // 验证输入的验证码是否正确 if storedCode != req.SendSms { return nil, biz.ErrInvalidSmsCode } // 验证码验证成功后删除,避免重复使用 - err = s.data.Redis.Del(ctx, key).Err() - if err != nil { - s.log.Warnf("删除验证码失败: %v", err) - } + //err = s.data.Redis.Del(ctx, key).Err() + ////s.data.db.Model().Create() + //if err != nil { + // s.log.Warnf("删除验证码失败: %v", err) + //} // 使用MD5对用户的密码进行加密后注册 (拓展:加盐加密 其他的加密方式) encryptedPassword := pkg.Md5(req.Password) @@ -193,7 +197,12 @@ func (s *UserService) Login(ctx context.Context, req *pb.LoginRequest) (*pb.Logi s.log.Errorf("生成token失败: %v", err) return nil, fmt.Errorf("生成token失败") } - res := s.data.Redis.Set(ctx, "token", token, 1*time.Hour) + key := fmt.Sprintf("token:%d", user.UserId) + res, err := s.data.Redis.Set(ctx, key, token, 1*time.Hour).Result() + if err != nil { + s.log.Errorf("token存入缓存失败", err) + return nil, err + } s.log.Errorf("token存入缓存成功", res) return &pb.LoginReply{ Msg: "登录成功", @@ -211,7 +220,7 @@ func (s *UserService) LoginSendSms(ctx context.Context, req *pb.LoginSendSmsRequ } //验证短信验证码 - key := fmt.Sprintf("sms:code:%s", req.Phone) + key := fmt.Sprintf("send" + req.Phone) storedCode, err := s.data.Redis.Get(ctx, key).Result() if err != nil { if storedCode == "" { @@ -231,15 +240,88 @@ func (s *UserService) LoginSendSms(ctx context.Context, req *pb.LoginSendSmsRequ s.log.Warnf("删除验证码失败: %v", err) // 不影响登录流程,只记录警告日志 } + token, err := pkg.TokenHandler(int(user.UserId)) if err != nil { s.log.Errorf("生成token失败: %v", err) return nil, fmt.Errorf("生成token失败") } - res := s.data.Redis.Set(ctx, "token", token, 1*time.Hour) - s.log.Errorf("token存入缓存成功", res) + key = fmt.Sprintf("token") + err = s.data.Redis.Set(ctx, key, token, 1*time.Hour).Err() + if err != nil { + s.log.Errorf("token存入缓存失败:%v", err) + return nil, fmt.Errorf("系统错误") + } + return &pb.LoginSendSmsReply{ Msg: "登录成功", Token: token, }, nil } + +// 用户退出登录 +func (s *UserService) Logout(ctx context.Context, req *pb.LogoutRequest) (*pb.LogoutReply, error) { + s.log.Infof("接收退出登录的token请求:%s", req.Token) + //key := fmt.Sprintf("token" + user.Phone) + //直接删除redis中的token + key := fmt.Sprintf("token") + err := s.data.Redis.Del(ctx, key).Err() + if err != nil { + s.log.Warnf("删除token失败:%v", err) + return nil, fmt.Errorf("退出登录失败") + } + s.log.Infof("退出登录成功,token已删除") + return &pb.LogoutReply{ + Msg: "退出成功", + }, nil +} + +// 忘记密码 +func (s *UserService) UpdatePassword(ctx context.Context, req *pb.UpdatePasswordRequest) (*pb.UpdatePasswordReply, error) { + // 检查手机号是否存在 + user, err := s.userUc.GetUserByPhone(ctx, req.Phone) + if err != nil { + s.log.Warnf("手机号不存在: %s", req.Phone) + return nil, fmt.Errorf("手机号不存在,请重试") + } + + // 验证短信验证码 + key := fmt.Sprintf("send" + req.Phone) + storedCode, err := s.data.Redis.Get(ctx, key).Result() + if err != nil { + s.log.Errorf("从Redis获取验证码失败: %v", err) + return nil, fmt.Errorf("验证码已过期,请重新获取") + } + + if storedCode != req.SendSms { + s.log.Warnf("验证码错误: %s, 输入: %s, 存储: %s", req.Phone, req.SendSms, storedCode) + return nil, fmt.Errorf("验证码错误") + } + + // 验证两次密码是否一致 + if req.Password != req.NewPassword { + s.log.Warnf("两次输入的密码不一致: %s vs %s", req.Password, req.NewPassword) + return nil, fmt.Errorf("两次密码不一致,请重试") + } + + // 更新密码 + encryptedPassword := pkg.Md5(req.NewPassword) + err = s.userUc.UpdatePassword(ctx, req.Phone, encryptedPassword) + if err != nil { + s.log.Errorf("更新密码失败: %v", err) + return nil, fmt.Errorf("密码更新失败,请重试") + } + + // 验证码验证成功后删除,避免重复使用 + //err = s.data.Redis.Del(ctx, key).Err() + //if err != nil { + // s.log.Warnf("删除验证码失败: %v", err) + //} + + // 记录日志 + s.log.Infof("用户找回密码成功,手机号: %s, 用户ID: %d", req.Phone, user.UserId) + return &pb.UpdatePasswordReply{ + UserId: user.UserId, + Msg: "找回密码成功", + }, nil +} diff --git a/internal/service/websocket_manager.go b/internal/service/websocket_manager.go new file mode 100644 index 0000000000000000000000000000000000000000..de19f1c1ac97e414dfef023c5501007302191aae --- /dev/null +++ b/internal/service/websocket_manager.go @@ -0,0 +1,246 @@ +package service + +import ( + "cabinet/internal/model" + "encoding/json" + "fmt" + "log" + "sync" + "time" + + "github.com/gorilla/websocket" +) + +// WebSocketManager 管理所有WebSocket连接 +type WebSocketManager struct { + // 客户端连接映射表: userID -> *Client + clients map[int]*Client + // 保护clients的互斥锁 + mutex sync.RWMutex + // 注册客户端的通道 + register chan *Client + // 注销客户端的通道 + unregister chan *Client + // 广播消息的通道 + broadcast chan []byte +} + +// Client 代表一个WebSocket客户端连接 +type Client struct { + // 用户ID + UserID int + // WebSocket连接 + Conn *websocket.Conn + // 发送消息的通道 + Send chan []byte + // 所属的管理器 + Manager *WebSocketManager +} + +// NewWebSocketManager 创建一个新的WebSocket管理器 +func NewWebSocketManager() *WebSocketManager { + return &WebSocketManager{ + clients: make(map[int]*Client), + register: make(chan *Client), + unregister: make(chan *Client), + broadcast: make(chan []byte), + } +} + +// Run 启动WebSocket管理器 +func (manager *WebSocketManager) Run() { + for { + select { + case client := <-manager.register: + manager.mutex.Lock() + manager.clients[client.UserID] = client + manager.mutex.Unlock() + log.Printf("用户 %d 已连接,当前在线人数: %d\n", client.UserID, len(manager.clients)) + + // 发送连接成功消息 + welcomeMsg := model.WebSocketMessage{ + Type: "connect", + UserID: client.UserID, + Data: "WebSocket连接成功", + Time: time.Now(), + } + msgBytes, _ := json.Marshal(welcomeMsg) + client.Send <- msgBytes + + case client := <-manager.unregister: + manager.mutex.Lock() + if _, ok := manager.clients[client.UserID]; ok { + delete(manager.clients, client.UserID) + close(client.Send) + } + manager.mutex.Unlock() + log.Printf("用户 %d 已断开连接,当前在线人数: %d\n", client.UserID, len(manager.clients)) + + case message := <-manager.broadcast: + manager.mutex.RLock() + for _, client := range manager.clients { + select { + case client.Send <- message: + default: + close(client.Send) + delete(manager.clients, client.UserID) + } + } + manager.mutex.RUnlock() + } + } +} + +// SendToUser 向特定用户发送消息 +func (manager *WebSocketManager) SendToUser(userID int, message interface{}) error { + manager.mutex.RLock() + client, ok := manager.clients[userID] + manager.mutex.RUnlock() + + if !ok { + return fmt.Errorf("用户 %d 不在线", userID) + } + + // 构建WebSocket消息 + wsMessage := model.WebSocketMessage{ + Type: "message", + UserID: userID, + Data: message, + Time: time.Now(), + } + + msgBytes, err := json.Marshal(wsMessage) + if err != nil { + return err + } + + select { + case client.Send <- msgBytes: + return nil + default: + // 发送失败,关闭连接 + close(client.Send) + manager.mutex.Lock() + delete(manager.clients, userID) + manager.mutex.Unlock() + return fmt.Errorf("向用户 %d 发送消息失败", userID) + } +} + +// Broadcast 广播消息给所有在线用户 +func (manager *WebSocketManager) Broadcast(message interface{}) { + // 构建WebSocket消息 + wsMessage := model.WebSocketMessage{ + Type: "message", + Data: message, + Time: time.Now(), + } + + msgBytes, err := json.Marshal(wsMessage) + if err != nil { + log.Printf("广播消息序列化失败: %v\n", err) + return + } + + manager.broadcast <- msgBytes +} + +// GetOnlineCount 获取在线用户数 +func (manager *WebSocketManager) GetOnlineCount() int { + manager.mutex.RLock() + defer manager.mutex.RUnlock() + return len(manager.clients) +} + +// GetOnlineUsers 获取所有在线用户ID +func (manager *WebSocketManager) GetOnlineUsers() []int { + manager.mutex.RLock() + defer manager.mutex.RUnlock() + + users := make([]int, 0, len(manager.clients)) + for userID := range manager.clients { + users = append(users, userID) + } + return users +} + +// readPump 从WebSocket连接读取消息 +func (c *Client) readPump() { + defer func() { + c.Manager.unregister <- c + c.Conn.Close() + }() + + // 设置读取限制 + c.Conn.SetReadLimit(1024 * 1024) // 1MB + // 设置读取超时时间 + c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second)) + // 设置pong处理函数,用于心跳检测 + c.Conn.SetPongHandler(func(string) error { + c.Conn.SetReadDeadline(time.Now().Add(60 * time.Second)) + return nil + }) + + for { + _, message, err := c.Conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Printf("WebSocket读取错误: %v\n", err) + } + break + } + + // 处理接收到的消息 + log.Printf("收到用户 %d 的消息: %s\n", c.UserID, message) + + // 可以在这里添加消息处理逻辑,例如解析消息并调用相应的服务 + var wsMsg model.WebSocketMessage + if err := json.Unmarshal(message, &wsMsg); err == nil { + // 处理消息... + } + } +} + +// writePump 向WebSocket连接写入消息 +func (c *Client) writePump() { + ticker := time.NewTicker(30 * time.Second) + defer func() { + ticker.Stop() + c.Conn.Close() + }() + + for { + select { + case message, ok := <-c.Send: + c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) + if !ok { + // 通道已关闭 + c.Conn.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + + w, err := c.Conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + w.Write(message) + + // 添加队列中的其他消息 + n := len(c.Send) + for i := 0; i < n; i++ { + w.Write([]byte{'\n'}) + w.Write(<-c.Send) + } + + if err := w.Close(); err != nil { + return + } + case <-ticker.C: + // 发送ping消息进行心跳检测 + c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) + if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil { + return + } + } + } +} \ No newline at end of file diff --git a/internal/untl/realName.go b/internal/untl/realName.go new file mode 100644 index 0000000000000000000000000000000000000000..c3b460dcb81a19e6cb35927ca0fe1b4fa7d91e86 --- /dev/null +++ b/internal/untl/realName.go @@ -0,0 +1,126 @@ +package untl + +import ( + "crypto/hmac" + "crypto/sha1" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/hashicorp/go-uuid" + "io" + "io/ioutil" + "net/http" + gourl "net/url" + "strings" + "time" +) + +func calcAuthorization(secretId string, secretKey string) (auth string, datetime string, err error) { + timeLocation, _ := time.LoadLocation("Etc/GMT") + datetime = time.Now().In(timeLocation).Format("Mon, 02 Jan 2006 15:04:05 GMT") + signStr := fmt.Sprintf("x-date: %s", datetime) + + // hmac-sha1 + mac := hmac.New(sha1.New, []byte(secretKey)) + mac.Write([]byte(signStr)) + sign := base64.StdEncoding.EncodeToString(mac.Sum(nil)) + + auth = fmt.Sprintf("{\"id\":\"%s\", \"x-date\":\"%s\", \"signature\":\"%s\"}", + secretId, datetime, sign) + + return auth, datetime, nil +} + +func urlencode(params map[string]string) string { + var p = gourl.Values{} + for k, v := range params { + p.Add(k, v) + } + return p.Encode() +} + +func Realname(cardNo, realName string) bool { + // 云市场分配的密钥Id + secretId := "heuyqCOqFRx9jTqU" + // 云市场分配的密钥Key + secretKey := "FhQhkuPhvJUl9bbfC1Tzj4aiOgaWPfLq" + // 签名 + auth, _, _ := calcAuthorization(secretId, secretKey) + + // 请求方法 + method := "POST" + reqID, err := uuid.GenerateUUID() + if err != nil { + panic(err) + } + // 请求头 + headers := map[string]string{"Authorization": auth, "request-id": reqID} + + // 查询参数 + queryParams := make(map[string]string) + + // body参数 + bodyParams := make(map[string]string) + bodyParams["cardNo"] = cardNo + bodyParams["realName"] = realName + bodyParamStr := urlencode(bodyParams) + // url参数拼接 + url := "https://ap-beijing.cloudmarket-apigw.com/service-18c38npd/idcard/VerifyIdcardv2" + + if len(queryParams) > 0 { + url = fmt.Sprintf("%s?%s", url, urlencode(queryParams)) + } + + bodyMethods := map[string]bool{"POST": true, "PUT": true, "PATCH": true} + var body io.Reader = nil + if bodyMethods[method] { + body = strings.NewReader(bodyParamStr) + headers["Content-Type"] = "application/x-www-form-urlencoded" + } + + client := &http.Client{ + Timeout: 5 * time.Second, + } + request, err := http.NewRequest(method, url, body) + if err != nil { + panic(err) + } + for k, v := range headers { + request.Header.Set(k, v) + } + response, err := client.Do(request) + if err != nil { + panic(err) + } + defer response.Body.Close() + + bodyBytes, err := ioutil.ReadAll(response.Body) + if err != nil { + panic(err) + } + fmt.Println(string(bodyBytes)) + var resq T + json.Unmarshal(bodyBytes, &resq) + if resq.ErrorCode == 0 && resq.Result.Isok { + return true + } + return false +} + +type T struct { + ErrorCode int `json:"error_code"` + Reason string `json:"reason"` + Result struct { + Realname string `json:"realname"` + Idcard string `json:"idcard"` + Isok bool `json:"isok"` + IdCardInfor struct { + Province string `json:"province"` + City string `json:"city"` + District string `json:"district"` + Area string `json:"area"` + Sex string `json:"sex"` + Birthday string `json:"birthday"` + } `json:"IdCardInfor"` + } `json:"result"` +} diff --git a/internal/untl/upload.go b/internal/untl/upload.go new file mode 100644 index 0000000000000000000000000000000000000000..2435b04795e83a0c8fb57506a418c366386d5148 --- /dev/null +++ b/internal/untl/upload.go @@ -0,0 +1,40 @@ +package untl + +import ( + "context" + "github.com/qiniu/go-sdk/v7/storagev2/credentials" + "github.com/qiniu/go-sdk/v7/storagev2/http_client" + "github.com/qiniu/go-sdk/v7/storagev2/uploader" + "mime/multipart" +) + +type Upload interface { + Qiniuyun(file multipart.File, m *multipart.FileHeader) (string, error) +} + +type QiNiuYun struct { + AccessKey string + SecretKey string +} + +func (q *QiNiuYun) Qiniuyun(file multipart.File, m *multipart.FileHeader) (string, error) { + accessKey := "e-QQxZe_wxoHGHufh2iyHrEmRxfJhGKUkfj_-zJJ" + secretKey := "xQe31UZGjI9fYm5V52bhopqk0szfg5A10fm2m6zu" + mac := credentials.NewCredentials(accessKey, secretKey) + bucket := "luohaowen2303a" + key := m.Filename + uploadManager := uploader.NewUploadManager(&uploader.UploadManagerOptions{ + Options: http_client.Options{ + Credentials: mac, + }, + }) + err := uploadManager.UploadReader(context.Background(), file, &uploader.ObjectOptions{ + BucketName: bucket, + ObjectName: &key, + CustomVars: map[string]string{ + "name": "github logo", + }, + FileName: key, + }, nil) + return "http://t3w4oqvuv.hn-bkt.clouddn.com/" + key, err +} diff --git a/openapi.yaml b/openapi.yaml index 6d32c1b48351dab184c4f61c0c831d5635622fc1..7dbe05398542a60a07d84ce3484a5eb174e335fe 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3,6 +3,35 @@ openapi: 3.0.3 info: +<<<<<<< HEAD +<<<<<<< HEAD + title: Real API + version: 0.0.1 +paths: + /realName: + post: + tags: + - Real + operationId: Real_RealName +======= + title: HelpService API + version: 0.0.1 +paths: + /v1/help/articleDetail: + post: + tags: + - HelpService + description: 获取文章详情 + operationId: HelpService_GetArticleDetail +>>>>>>> wangziyue + requestBody: + content: + application/json: + schema: +<<<<<<< HEAD + $ref: '#/components/schemas/RealNameRequest' + required: true +======= title: Cabinet API description: |- Cabinet 智能柜服务接口 @@ -29,6 +58,9 @@ paths: application/json: schema: $ref: '#/components/schemas/api.cabinet.v1.CreateCabinetRequest' +======= + $ref: '#/components/schemas/GetArticleDetailRequest' +>>>>>>> wangziyue required: true responses: "200": @@ -36,6 +68,7 @@ paths: content: application/json: schema: +<<<<<<< HEAD $ref: '#/components/schemas/api.cabinet.v1.CreateCabinetReply' /v1/cabinet/delete: delete: @@ -48,11 +81,30 @@ paths: 请求体:DeleteCabinetRequest 响应:DeleteCabinetReply operationId: Cabinet_DeleteCabinet +======= + $ref: '#/components/schemas/GetArticleDetailReply' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/help/articles: + post: + tags: + - HelpService + description: 根据分类获取文章列表 + operationId: HelpService_GetArticles +>>>>>>> wangziyue requestBody: content: application/json: schema: +<<<<<<< HEAD $ref: '#/components/schemas/api.cabinet.v1.DeleteCabinetRequest' +======= + $ref: '#/components/schemas/GetArticlesRequest' +>>>>>>> wangziyue required: true responses: "200": @@ -60,6 +112,7 @@ paths: content: application/json: schema: +<<<<<<< HEAD $ref: '#/components/schemas/api.cabinet.v1.DeleteCabinetReply' /v1/cabinet/favorite/delete: delete: @@ -83,12 +136,34 @@ paths: in: query schema: type: string +======= + $ref: '#/components/schemas/GetArticlesReply' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/help/articles/search: + post: + tags: + - HelpService + description: 搜索帮助文章 + operationId: HelpService_SearchArticles + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SearchArticlesRequest' + required: true +>>>>>>> wangziyue responses: "200": description: OK content: application/json: schema: +<<<<<<< HEAD $ref: '#/components/schemas/api.cabinet.v1.DeleteFavoriteCabinetReply' /v1/cabinet/favorite/find: get: @@ -112,12 +187,34 @@ paths: in: query schema: type: string +======= + $ref: '#/components/schemas/SearchArticlesReply' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/help/categories: + post: + tags: + - HelpService + description: 获取帮助中心分类结构 + operationId: HelpService_GetCategories + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetCategoriesRequest' + required: true +>>>>>>> wangziyue responses: "200": description: OK content: application/json: schema: +<<<<<<< HEAD $ref: '#/components/schemas/api.cabinet.v1.FindFavoriteCabinetReply' /v1/cabinet/favorite/list: get: @@ -272,12 +369,183 @@ paths: required: true schema: type: string +>>>>>>> origin/master responses: "200": description: OK content: application/json: schema: +<<<<<<< HEAD + $ref: '#/components/schemas/RealNameReply' +======= + $ref: '#/components/schemas/GetCategoriesReply' +>>>>>>> wangziyue + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GetArticleDetailReply: + type: object + properties: + article: + $ref: '#/components/schemas/HelpArticle' + GetArticleDetailRequest: + type: object + properties: + id: + type: string + GetArticlesReply: + type: object + properties: + articles: + type: array + items: + $ref: '#/components/schemas/HelpArticle' + total: + type: integer + format: int32 + currentPage: + type: integer + format: int32 + pageSize: + type: integer + format: int32 + GetArticlesRequest: + type: object + properties: + category: + type: string + subCategory: + type: string + page: + type: integer + format: int32 + pageSize: + type: integer + format: int32 + GetCategoriesReply: + type: object + properties: + categories: + type: array + items: + $ref: '#/components/schemas/HelpCategory' + GetCategoriesRequest: + type: object + properties: {} + description: 请求响应消息定义 + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. +<<<<<<< HEAD + RealNameReply: + type: object + properties: + userId: + type: integer + format: int32 + RealNameRequest: + type: object + properties: + card: + type: string + name: + type: string + userId: +======= + HelpArticle: + type: object + properties: + id: + type: string + title: + type: string + content: + type: string + category: + type: string + subCategory: + type: string + orderWeight: + type: integer + format: int32 + viewCount: + type: integer + format: int32 + isActive: + type: boolean + createdAt: + type: string + updatedAt: + type: string + HelpCategory: + type: object + properties: + category: + type: string + subCategories: + type: array + items: + type: string + description: 数据结构定义 + SearchArticlesReply: + type: object + properties: + articles: + type: array + items: + $ref: '#/components/schemas/HelpArticle' + total: + type: integer + format: int32 + currentPage: + type: integer + format: int32 + pageSize: + type: integer + format: int32 + SearchArticlesRequest: + type: object + properties: + keyword: + type: string + page: + type: integer + format: int32 + pageSize: +>>>>>>> wangziyue + type: integer + format: int32 + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: +<<<<<<< HEAD + - name: Real +======= $ref: '#/components/schemas/api.cabinet.v1.FindCabinetByParcelReply' /v1/cabinet/update: put: @@ -480,3 +748,7 @@ components: 用于修改现有智能柜的配置和状态 tags: - name: Cabinet +>>>>>>> origin/master +======= + - name: HelpService +>>>>>>> wangziyue diff --git a/quick_test_mqtt.sh b/quick_test_mqtt.sh new file mode 100755 index 0000000000000000000000000000000000000000..1136a273f895aee486d7ddfac6f9d7f887b7d5f1 --- /dev/null +++ b/quick_test_mqtt.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# MQTT 快速测试脚本 + +echo "==========================================" +echo "MQTT 快速测试" +echo "==========================================" +echo "" + +# 检查 MQTT Broker +if ! docker ps | grep -q mosquitto; then + echo "✗ MQTT Broker 未运行" + echo "请先启动: docker run -d -p 1883:1883 --name mosquitto eclipse-mosquitto" + exit 1 +fi + +echo "✓ MQTT Broker 正在运行" +echo "" + +# 检查项目是否运行 +if ! lsof -i :8001 >/dev/null 2>&1; then + echo "⚠ 警告: 项目可能未运行(端口 8001 未被占用)" + echo "请先启动项目: kratos run" + echo "" +fi + +echo "==========================================" +echo "开始订阅 MQTT 事件..." +echo "主题: cabinet/parcel/status/changed/#" +echo "按 Ctrl+C 停止" +echo "==========================================" +echo "" + +# 使用 Docker 容器中的 mosquitto_sub +docker exec -it friendly_golick mosquitto_sub -h localhost -p 1883 -t "cabinet/parcel/status/changed/#" -v + diff --git a/stop_all.sh b/stop_all.sh new file mode 100755 index 0000000000000000000000000000000000000000..9494434b1bd89801a9279eff57086aacd0a61789 --- /dev/null +++ b/stop_all.sh @@ -0,0 +1,142 @@ +#!/bin/bash + +# Cabinet 项目进程停止脚本 +# 用于停止所有相关的 Cabinet 服务进程 + +echo "=== Cabinet 服务停止脚本 ===" + +# 定义要停止的进程名称列表 +PROCESSES=("Cabinet" "go" "kratos") + +# 定义要检查的端口列表 +PORTS=(8001 9002 1883) + +# 函数:优雅结束进程 +graceful_kill() { + local pid=$1 + local name=$2 + + echo "尝试优雅结束进程: $name (PID: $pid)" + + # 发送 SIGTERM 信号 + kill -TERM $pid 2>/dev/null + + # 等待最多5秒 + local count=0 + while [ $count -lt 5 ] && kill -0 $pid 2>/dev/null; do + sleep 1 + count=$((count + 1)) + done + + # 如果进程还在运行,强制结束 + if kill -0 $pid 2>/dev/null; then + echo "强制结束进程: $name (PID: $pid)" + kill -KILL $pid 2>/dev/null + fi +} + +# 函数:停止指定名称的进程 +stop_processes_by_name() { + local process_name=$1 + echo "查找 $process_name 进程..." + + # 使用 pgrep 查找进程 + local pids=$(pgrep -f "$process_name" 2>/dev/null) + + if [ -n "$pids" ]; then + echo "找到以下 $process_name 进程:" + for pid in $pids; do + # 获取进程详细信息 + local cmdline=$(ps -p $pid -o pid=,ppid=,cmd= 2>/dev/null) + echo " PID:$pid PPID:$(ps -p $pid -o ppid= 2>/dev/null) $cmdline" + + # 检查是否是我们的Cabinet项目进程 + if echo "$cmdline" | grep -q "cabinet\|Cabinet" 2>/dev/null; then + graceful_kill $pid "$process_name" + fi + done + else + echo "未找到 $process_name 进程" + fi +} + +# 函数:停止指定端口的进程 +stop_processes_by_port() { + local port=$1 + echo "检查端口 $port 的占用情况..." + + # 使用 lsof 检查端口占用 + local pid=$(lsof -ti :$port 2>/dev/null) + + if [ -n "$pid" ]; then + echo "端口 $port 被进程 $pid 占用" + local cmdline=$(ps -p $pid -o pid=,cmd= 2>/dev/null) + echo " PID:$pid $cmdline" + + graceful_kill $pid "端口$port进程" + else + echo "端口 $port 未被占用" + fi +} + +# 函数:清理可能的僵尸进程 +cleanup_zombies() { + echo "清理可能存在的僵尸进程..." + + # 查找僵尸进程 + local zombies=$(ps aux | awk '{if ($8 == "Z") print $2}' 2>/dev/null) + + if [ -n "$zombies" ]; then + echo "找到僵尸进程:" + echo "$zombies" + echo "$zombies" | while read -r pid; do + # 尝试清理僵尸进程 + kill -CHLD $(ps -p $pid -o ppid= 2>/dev/null) 2>/dev/null + done + fi +} + +# 主执行流程 +echo "开始停止 Cabinet 相关进程..." + +# 1. 停止指定的进程名称 +for process in "${PROCESSES[@]}"; do + stop_processes_by_name "$process" +done + +# 2. 检查并停止指定端口的进程 +for port in "${PORTS[@]}"; do + stop_processes_by_port $port +done + +# 3. 清理僵尸进程 +cleanup_zombies + +# 4. 最终验证 +echo "" +echo "=== 最终验证 ===" + +# 检查是否还有相关进程 +echo "检查剩余进程:" +remaining=$(ps aux | grep -E "(Cabinet|kratos|go.*cabinet)" | grep -v grep 2>/dev/null) +if [ -n "$remaining" ]; then + echo "警告: 仍有一些相关进程在运行:" + echo "$remaining" +else + echo "✓ 所有相关进程已停止" +fi + +# 检查端口占用 +echo "" +echo "检查端口占用:" +for port in "${PORTS[@]}"; do + if lsof -i :$port >/dev/null 2>&1; then + echo "⚠️ 端口 $port 仍被占用:" + lsof -i :$port 2>/dev/null + else + echo "✓ 端口 $port 已释放" + fi +done + +echo "" +echo "=== 停止脚本执行完成 ===" diff --git a/stop_server.sh b/stop_server.sh new file mode 100755 index 0000000000000000000000000000000000000000..7dc9f4480d2a331607566d627f7be19829c3fd59 --- /dev/null +++ b/stop_server.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# 停止 Cabinet 服务脚本 + +echo "正在查找并停止 Cabinet 服务进程..." + +# 查找并停止 kratos run 进程 +pkill -f "kratos run" 2>/dev/null + +# 查找并停止 go run Cabinet 进程 +pkill -f "go run.*Cabinet" 2>/dev/null + +# 查找并停止 Cabinet 可执行文件进程 +pkill -f "/Cabinet" 2>/dev/null + +# 等待进程退出 +sleep 2 + +# 检查端口占用情况 +echo "" +echo "检查端口占用情况:" +if lsof -i :8001 >/dev/null 2>&1; then + echo "警告: 端口 8001 仍被占用" + lsof -i :8001 +else + echo "✓ 端口 8001 已释放" +fi + +if lsof -i :9002 >/dev/null 2>&1; then + echo "警告: 端口 9002 仍被占用" + lsof -i :9002 +else + echo "✓ 端口 9002 已释放" +fi + +echo "" +echo "完成!" + diff --git a/stop_services.sh b/stop_services.sh new file mode 100755 index 0000000000000000000000000000000000000000..57ae24a5d254374f90a138e324206fbbd92a50cf --- /dev/null +++ b/stop_services.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Cabinet 服务快速停止脚本 +# 简洁版本:只停止 Cabinet 相关的进程和端口 + +echo "=== 停止 Cabinet 服务 ===" + +# 停止所有 Cabinet 进程 +echo "停止 Cabinet 进程..." +pkill -f "Cabinet" 2>/dev/null && echo "✓ 已停止 Cabinet 进程" || echo "ℹ️ 没有找到 Cabinet 进程" + +# 停止 kratos 进程 +echo "停止 kratos 进程..." +pkill -f "kratos" 2>/dev/null && echo "✓ 已停止 kratos 进程" || echo "ℹ️ 没有找到 kratos 进程" + +# 等待一下让进程完全停止 +sleep 2 + +# 检查端口是否释放 +echo "" +echo "检查端口状态:" +ports=(8001 9002) +for port in "${ports[@]}"; do + if lsof -i :$port >/dev/null 2>&1; then + echo "⚠️ 端口 $port 仍被占用" + else + echo "✓ 端口 $port 已释放" + fi +done + +echo "" +echo "=== 完成 ===" diff --git a/test_mqtt.sh b/test_mqtt.sh new file mode 100755 index 0000000000000000000000000000000000000000..adba217ad6112305d98345db93457952c2140305 --- /dev/null +++ b/test_mqtt.sh @@ -0,0 +1,156 @@ +#!/bin/bash +# MQTT 测试脚本 + +echo "==========================================" +echo "MQTT 功能测试脚本" +echo "==========================================" +echo "" + +# 检查 MQTT Broker 是否运行 +echo "1. 检查 MQTT Broker 状态..." +if docker ps | grep -q mosquitto; then + echo "✓ MQTT Broker (Mosquitto) 正在运行" +else + echo "✗ MQTT Broker 未运行,请先启动:" + echo " docker run -d -p 1883:1883 -p 9001:9001 eclipse-mosquitto" + exit 1 +fi + +# 检查 mosquitto 客户端工具 +echo "" +echo "2. 检查 MQTT 客户端工具..." +if command -v mosquitto_sub &> /dev/null; then + echo "✓ mosquitto_sub 已安装" + MOSQUITTO_SUB="mosquitto_sub" +elif docker exec friendly_golick which mosquitto_sub &> /dev/null; then + echo "✓ 使用 Docker 容器中的 mosquitto_sub" + MOSQUITTO_SUB="docker exec -i friendly_golick mosquitto_sub" +else + echo "✗ 未找到 mosquitto_sub 工具" + echo " 请安装 mosquitto 客户端工具:" + echo " macOS: brew install mosquitto" + echo " 或使用 Docker 容器中的工具" + exit 1 +fi + +if command -v mosquitto_pub &> /dev/null; then + echo "✓ mosquitto_pub 已安装" + MOSQUITTO_PUB="mosquitto_pub" +elif docker exec friendly_golick which mosquitto_pub &> /dev/null; then + echo "✓ 使用 Docker 容器中的 mosquitto_pub" + MOSQUITTO_PUB="docker exec -i friendly_golick mosquitto_pub" +else + echo "✗ 未找到 mosquitto_pub 工具" + exit 1 +fi + +echo "" +echo "==========================================" +echo "测试选项:" +echo "==========================================" +echo "1. 订阅包裹状态变化事件(实时监听)" +echo "2. 手动发布测试消息" +echo "3. 通过 API 更新包裹状态(触发 MQTT 事件)" +echo "4. 查看 MQTT 连接状态" +echo "" +read -p "请选择测试选项 (1-4): " choice + +case $choice in + 1) + echo "" + echo "==========================================" + echo "订阅包裹状态变化事件" + echo "==========================================" + echo "正在订阅主题: cabinet/parcel/status/changed/#" + echo "按 Ctrl+C 停止监听" + echo "" + $MOSQUITTO_SUB -h localhost -p 1883 -t "cabinet/parcel/status/changed/#" -v + ;; + 2) + echo "" + echo "==========================================" + echo "手动发布测试消息" + echo "==========================================" + read -p "请输入包裹ID: " parcel_id + read -p "请输入格口ID: " locker_id + read -p "请输入智能柜ID: " cabinet_id + read -p "请输入旧状态 (0-4): " old_status + read -p "请输入新状态 (0-4): " new_status + + # 生成测试消息 + timestamp=$(date +%s) + message=$(cat </dev/null || echo "$message" + echo "" + + echo "$message" | $MOSQUITTO_PUB -h localhost -p 1883 -t "cabinet/parcel/status/changed" -l + + echo "" + echo "✓ 消息已发布" + ;; + 3) + echo "" + echo "==========================================" + echo "通过 API 更新包裹状态" + echo "==========================================" + read -p "请输入包裹ID: " parcel_id + read -p "请输入新状态 (0-待取件, 1-滞留件, 2-已取件, 3-已撤回, 4-已完结): " new_status + + echo "" + echo "正在更新包裹状态..." + echo "同时请在另一个终端运行: ./test_mqtt.sh (选择选项1) 来监听 MQTT 事件" + echo "" + + # 注意:这里需要实际的 API 端点来更新包裹状态 + # 如果还没有实现更新包裹状态的 API,可以提示用户 + echo "提示:请使用 ApiPost 或其他工具调用更新包裹状态的 API" + echo "例如:PUT /v1/cabinet/parcel/{parcel_id} 或类似的端点" + echo "" + echo "或者使用 curl 命令(如果 API 已实现):" + echo "curl -X PUT \"http://localhost:8001/v1/cabinet/parcel/$parcel_id\" \\" + echo " -H \"Content-Type: application/json\" \\" + echo " -d '{\"parcel_status\": $new_status}'" + ;; + 4) + echo "" + echo "==========================================" + echo "查看 MQTT 连接状态" + echo "==========================================" + echo "检查 MQTT Broker 连接..." + echo "" + + # 测试连接 + echo "测试发布消息..." + echo '{"test": "connection"}' | $MOSQUITTO_PUB -h localhost -p 1883 -t "test/connection" -l + + echo "" + echo "测试订阅消息(5秒后自动停止)..." + timeout 5 $MOSQUITTO_SUB -h localhost -p 1883 -t "test/connection" -C 1 & + sleep 1 + echo '{"test": "connection"}' | $MOSQUITTO_PUB -h localhost -p 1883 -t "test/connection" -l + wait + + echo "" + echo "✓ MQTT 连接正常" + ;; + *) + echo "无效选项" + exit 1 + ;; +esac + diff --git a/test_publish_mqtt.sh b/test_publish_mqtt.sh new file mode 100755 index 0000000000000000000000000000000000000000..d8324b0ad4a5a33acfc6bc5d95ea8021371d26dd --- /dev/null +++ b/test_publish_mqtt.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# 手动发布 MQTT 测试消息 + +echo "==========================================" +echo "发布 MQTT 测试消息" +echo "==========================================" +echo "" + +# 检查 MQTT Broker +if ! docker ps | grep -q mosquitto; then + echo "✗ MQTT Broker 未运行" + exit 1 +fi + +# 默认测试数据 +PARCEL_ID=${1:-1001} +LOCKER_ID=${2:-101} +CABINET_ID=${3:-1} +OLD_STATUS=${4:-0} +NEW_STATUS=${5:-1} +EXPRESS_NO=${6:-"TEST$(date +%Y%m%d%H%M%S)"} +TIMESTAMP=$(date +%s) + +# 生成测试消息 +MESSAGE=$(cat </dev/null || echo "$MESSAGE" +echo "" + +# 发布消息 +echo "$MESSAGE" | docker exec -i friendly_golick mosquitto_pub -h localhost -p 1883 -t "cabinet/parcel/status/changed" -l + +echo "" +echo "✓ 消息已发布" +echo "" +echo "提示: 在另一个终端运行以下命令来监听事件:" +echo " ./quick_test_mqtt.sh" +echo "或" +echo " docker exec -it friendly_golick mosquitto_sub -h localhost -p 1883 -t 'cabinet/parcel/status/changed/#' -v" + diff --git a/web/App.vue b/web/App.vue index 8c2b732101d2c5679c3000a773ee4bb16995a630..5c7d6bba37ac16da8ecffc5aee7fbbd6f894a9dc 100644 --- a/web/App.vue +++ b/web/App.vue @@ -1,17 +1,19 @@ diff --git a/web/api/location.js b/web/api/location.js new file mode 100644 index 0000000000000000000000000000000000000000..52ead637fd9c67e952ee8abf4e5e6ac5acdc717a --- /dev/null +++ b/web/api/location.js @@ -0,0 +1,273 @@ +/** + * 位置获取工具函数 + * 提供获取当前位置经纬度的功能 + */ + +/** + * 获取当前位置的经纬度 + * @param {Object} options 配置选项 + * @param {String} options.type 坐标类型,默认为 'gcj02'(国测局坐标系,高德地图使用) + * - 'wgs84': GPS坐标 + * - 'gcj02': 国测局坐标(高德、腾讯地图使用) + * @param {Boolean} options.geocode 是否返回地址信息,默认为 false + * @param {Number} options.timeout 超时时间(毫秒),默认为 10000 + * @returns {Promise} 返回包含经纬度的对象 + * - latitude: 纬度 + * - longitude: 经度 + * - address: 地址信息(如果 geocode 为 true) + * + * @example + * // 基本用法 + * getCurrentLocation().then(res => { + * console.log('纬度:', res.latitude); + * console.log('经度:', res.longitude); + * }).catch(err => { + * console.error('获取位置失败:', err); + * }); + * + * @example + * // 获取地址信息 + * getCurrentLocation({ geocode: true }).then(res => { + * console.log('位置:', res.latitude, res.longitude); + * console.log('地址:', res.address); + * }); + */ +export function getCurrentLocation(options = {}) { + const { + type = 'gcj02', + geocode = false, + timeout = 10000 + } = options; + + return new Promise((resolve, reject) => { + // 检查是否支持 uni.getLocation + if (typeof uni === 'undefined' || !uni.getLocation) { + reject(new Error('当前环境不支持获取位置信息')); + return; + } + + uni.getLocation({ + type: type, + geocode: geocode, + timeout: timeout, + success: (res) => { + console.log('获取位置成功:', res); + resolve({ + latitude: res.latitude, + longitude: res.longitude, + address: res.address || null, + accuracy: res.accuracy || null, + altitude: res.altitude || null, + speed: res.speed || null, + verticalAccuracy: res.verticalAccuracy || null, + horizontalAccuracy: res.horizontalAccuracy || null + }); + }, + fail: (err) => { + console.error('获取位置失败:', err); + let errorMsg = '获取位置失败'; + + if (err.errMsg) { + if (err.errMsg.includes('auth deny') || err.errMsg.includes('permission')) { + errorMsg = '用户拒绝了位置权限'; + } else if (err.errMsg.includes('timeout')) { + errorMsg = '获取位置超时'; + } else if (err.errMsg.includes('fail')) { + errorMsg = '获取位置失败,请检查定位服务是否开启'; + } + } + + reject(new Error(errorMsg)); + } + }); + }); +} + +/** + * 通过IP获取位置(降级方案,精度较低) + * @param {String} amapKey 高德地图API Key(可选,如果不提供则使用默认值) + * @returns {Promise} 返回包含经纬度的对象 + * + * @example + * getLocationByIP().then(res => { + * console.log('纬度:', res.latitude); + * console.log('经度:', res.longitude); + * }); + */ +export function getLocationByIP(amapKey = 'daec86b990d1c76dbc03ced411fc70a3') { + return new Promise((resolve, reject) => { + const url = `https://restapi.amap.com/v3/ip?key=${amapKey}`; + + uni.request({ + url: url, + method: 'GET', + timeout: 5000, + header: { + 'Content-Type': 'application/json' + }, + success: (res) => { + if (res.statusCode === 200) { + const data = res.data; + if (data.status === '1') { + let latitude = 39.9042; // 默认北京 + let longitude = 116.4074; + + // 解析返回的位置信息 + if (data.rectangle && typeof data.rectangle === 'string') { + const rectangle = data.rectangle.split(';'); + if (rectangle.length >= 2) { + const point1 = rectangle[0].split(','); + const point2 = rectangle[1].split(','); + longitude = (parseFloat(point1[0]) + parseFloat(point2[0])) / 2; + latitude = (parseFloat(point1[1]) + parseFloat(point2[1])) / 2; + } + } else if (data.lon && data.lat) { + longitude = parseFloat(data.lon); + latitude = parseFloat(data.lat); + } + + resolve({ + latitude: latitude, + longitude: longitude, + province: data.province || null, + city: data.city || null, + adcode: data.adcode || null, + rectangle: data.rectangle || null + }); + } else { + reject(new Error(data.info || 'IP定位失败')); + } + } else { + reject(new Error(`请求失败,状态码: ${res.statusCode}`)); + } + }, + fail: (err) => { + console.error('IP定位请求失败:', err); + reject(new Error('IP定位失败')); + } + }); + }); +} + +/** + * 获取位置(带降级方案) + * 先尝试获取精确位置,失败则使用IP定位 + * @param {Object} options 配置选项 + * @returns {Promise} 返回包含经纬度的对象 + * + * @example + * getLocationWithFallback().then(res => { + * console.log('纬度:', res.latitude); + * console.log('经度:', res.longitude); + * console.log('来源:', res.source); // 'gps' 或 'ip' + * }); + */ +export async function getLocationWithFallback(options = {}) { + try { + // 先尝试获取精确位置 + const location = await getCurrentLocation(options); + return { + ...location, + source: 'gps' + }; + } catch (error) { + console.log('精确定位失败,尝试IP定位:', error); + try { + // 降级到IP定位 + const location = await getLocationByIP(options.amapKey); + return { + ...location, + source: 'ip' + }; + } catch (ipError) { + console.error('IP定位也失败:', ipError); + // 如果都失败,返回默认位置(北京) + return { + latitude: 39.9042, + longitude: 116.4074, + source: 'default' + }; + } + } +} + +/** + * 检查位置权限 + * @returns {Promise} 是否有位置权限 + */ +export function checkLocationPermission() { + return new Promise((resolve) => { + if (typeof uni === 'undefined' || !uni.getSetting) { + resolve(false); + return; + } + + uni.getSetting({ + success: (res) => { + // #ifdef MP-WEIXIN + // 微信小程序 + const locationAuth = res.authSetting['scope.userLocation']; + resolve(locationAuth === true || locationAuth === undefined); + // #endif + + // #ifdef APP-PLUS + // App环境,默认返回true + resolve(true); + // #endif + + // #ifdef H5 + // H5环境,检查浏览器权限 + if (navigator.permissions) { + navigator.permissions.query({ name: 'geolocation' }).then(result => { + resolve(result.state === 'granted' || result.state === 'prompt'); + }).catch(() => { + resolve(true); // 如果不支持,默认允许 + }); + } else { + resolve(true); // 如果不支持,默认允许 + } + // #endif + + // 其他环境默认返回true + // #ifndef MP-WEIXIN || APP-PLUS || H5 + resolve(true); + // #endif + }, + fail: () => { + resolve(false); + } + }); + }); +} + +/** + * 请求位置权限 + * @returns {Promise} 是否授权成功 + */ +export function requestLocationPermission() { + return new Promise((resolve, reject) => { + if (typeof uni === 'undefined' || !uni.authorize) { + reject(new Error('当前环境不支持权限请求')); + return; + } + + // #ifdef MP-WEIXIN + uni.authorize({ + scope: 'scope.userLocation', + success: () => { + resolve(true); + }, + fail: (err) => { + console.error('请求位置权限失败:', err); + reject(new Error('用户拒绝了位置权限')); + } + }); + // #endif + + // #ifndef MP-WEIXIN + // 其他环境直接返回true + resolve(true); + // #endif + }); +} + diff --git a/web/api/user.js b/web/api/user.js new file mode 100644 index 0000000000000000000000000000000000000000..63800a4b270cf14cdae915bc858870a4eddc29cf --- /dev/null +++ b/web/api/user.js @@ -0,0 +1,99 @@ +// API服务封装 +const BASE_URL = 'http://localhost:8000'; + +// 封装请求方法,统一处理响应和错误 +const request = (options) => { + // 添加请求头,如token + const token = uni.getStorageSync('token'); + if (token) { + options.header = options.header || {}; + options.header['Authorization'] = `Bearer ${token}`; + } + + return new Promise((resolve, reject) => { + uni.request({ + ...options, + success: (res) => { + resolve(res); + }, + fail: (err) => { + reject(err); + } + }); + }); +}; + +// 发送短信验证码 +export const sendSms = (phone) => { + return request({ + url: `${BASE_URL}/SendSms`, + method: 'POST', + data: { + Phone: phone // 保持首字母大写,与后端protobuf定义一致 + } + }); +}; + +// 注册 +export const register = (data) => { + return request({ + url: `${BASE_URL}/register`, + method: 'POST', + data: { + Phone: data.phone, + SendSms: data.code, + Password: data.password, + NewPassword: data.confirmPassword + } + }); +}; + +// 手机号密码登录 +export const login = (data) => { + return request({ + url: `${BASE_URL}/login`, + method: 'POST', + data: { + Phone: data.phone, + Password: data.password + } + }); +}; + +// 手机号验证码登录 +export const loginBySms = (data) => { + return request({ + url: `${BASE_URL}/loginSendSms`, + method: 'POST', + data: { + Phone: data.phone, + SendSms: data.code + } + }); +}; + +// 退出登录 +export const logout = (token, userId) => { + return request({ + url: `${BASE_URL}/logout`, + method: 'POST', + data: { + Token: token, + UserId: userId + } + }); +}; + +// 忘记密码(更新密码) +export const updatePassword = (data) => { + return request({ + url: `${BASE_URL}/updatePassword`, + method: 'POST', + data: { + Phone: data.phone, + SendSms: data.code, + Password: data.password, + NewPassword: data.confirmPassword + } + }); +}; \ No newline at end of file diff --git "a/web/docs/\344\275\215\347\275\256\350\216\267\345\217\226\344\275\277\347\224\250\346\214\207\345\215\227.md" "b/web/docs/\344\275\215\347\275\256\350\216\267\345\217\226\344\275\277\347\224\250\346\214\207\345\215\227.md" new file mode 100644 index 0000000000000000000000000000000000000000..42e62869f22d24b0fc0a88c0e8d034cd51b2d6c3 --- /dev/null +++ "b/web/docs/\344\275\215\347\275\256\350\216\267\345\217\226\344\275\277\347\224\250\346\214\207\345\215\227.md" @@ -0,0 +1,450 @@ +# 位置获取使用指南 + +## 📍 概述 + +本项目提供了通用的位置获取工具函数,可以在任何页面中轻松获取当前位置的经纬度。 + +## 🚀 快速开始 + +### 1. 导入工具函数 + +```javascript +import { getCurrentLocation, getLocationWithFallback } from '@/api/location.js'; +``` + +### 2. 基本使用 + +#### 方式一:获取精确位置(推荐) + +```javascript +// 在 Vue 组件中使用 +export default { + methods: { + async getMyLocation() { + try { + const location = await getCurrentLocation(); + console.log('纬度:', location.latitude); + console.log('经度:', location.longitude); + + // 使用位置信息 + this.latitude = location.latitude; + this.longitude = location.longitude; + } catch (error) { + console.error('获取位置失败:', error); + uni.showToast({ + title: error.message || '获取位置失败', + icon: 'none' + }); + } + } + } +} +``` + +#### 方式二:带降级方案(推荐用于生产环境) + +```javascript +import { getLocationWithFallback } from '@/api/location.js'; + +export default { + methods: { + async getMyLocation() { + try { + const location = await getLocationWithFallback(); + console.log('纬度:', location.latitude); + console.log('经度:', location.longitude); + console.log('来源:', location.source); // 'gps'、'ip' 或 'default' + + // 使用位置信息 + this.latitude = location.latitude; + this.longitude = location.longitude; + } catch (error) { + console.error('获取位置失败:', error); + } + } + } +} +``` + +## 📖 API 文档 + +### getCurrentLocation(options) + +获取当前位置的经纬度(精确位置,需要用户授权)。 + +**参数:** +- `options.type` (String, 可选): 坐标类型 + - `'wgs84'`: GPS坐标 + - `'gcj02'`: 国测局坐标(高德、腾讯地图使用,默认值) +- `options.geocode` (Boolean, 可选): 是否返回地址信息,默认 `false` +- `options.timeout` (Number, 可选): 超时时间(毫秒),默认 `10000` + +**返回值:** +```javascript +{ + latitude: 39.9042, // 纬度 + longitude: 116.4074, // 经度 + address: null, // 地址信息(如果 geocode 为 true) + accuracy: null, // 精度(米) + altitude: null, // 海拔(米) + speed: null, // 速度(米/秒) + // ... 其他信息 +} +``` + +**示例:** +```javascript +// 基本用法 +const location = await getCurrentLocation(); + +// 获取地址信息 +const location = await getCurrentLocation({ geocode: true }); + +// 使用GPS坐标 +const location = await getCurrentLocation({ type: 'wgs84' }); +``` + +### getLocationByIP(amapKey) + +通过IP获取位置(降级方案,精度较低,不需要用户授权)。 + +**参数:** +- `amapKey` (String, 可选): 高德地图API Key,默认使用项目配置的Key + +**返回值:** +```javascript +{ + latitude: 39.9042, // 纬度 + longitude: 116.4074, // 经度 + province: '北京市', // 省份 + city: '北京市', // 城市 + adcode: '110000', // 行政区划代码 + rectangle: '...' // 矩形区域 +} +``` + +**示例:** +```javascript +const location = await getLocationByIP(); +console.log('位置:', location.latitude, location.longitude); +console.log('城市:', location.city); +``` + +### getLocationWithFallback(options) + +获取位置(带降级方案):先尝试获取精确位置,失败则使用IP定位。 + +**参数:** +- `options`: 同 `getCurrentLocation` 的参数 + +**返回值:** +```javascript +{ + latitude: 39.9042, // 纬度 + longitude: 116.4074, // 经度 + source: 'gps', // 来源:'gps'、'ip' 或 'default' + // ... 其他信息(根据来源不同而不同) +} +``` + +**示例:** +```javascript +const location = await getLocationWithFallback(); +if (location.source === 'gps') { + console.log('使用GPS定位,精度高'); +} else if (location.source === 'ip') { + console.log('使用IP定位,精度较低'); +} else { + console.log('使用默认位置(北京)'); +} +``` + +### checkLocationPermission() + +检查位置权限。 + +**返回值:** +- `Promise`: 是否有位置权限 + +**示例:** +```javascript +const hasPermission = await checkLocationPermission(); +if (!hasPermission) { + // 请求权限 + await requestLocationPermission(); +} +``` + +### requestLocationPermission() + +请求位置权限(仅微信小程序)。 + +**返回值:** +- `Promise`: 是否授权成功 + +**示例:** +```javascript +try { + await requestLocationPermission(); + // 权限已授予,可以获取位置 + const location = await getCurrentLocation(); +} catch (error) { + console.error('用户拒绝了位置权限'); +} +``` + +## 💡 使用场景 + +### 场景1:页面加载时获取位置 + +```javascript +export default { + data() { + return { + latitude: null, + longitude: null + }; + }, + onLoad() { + this.getMyLocation(); + }, + methods: { + async getMyLocation() { + try { + const location = await getLocationWithFallback(); + this.latitude = location.latitude; + this.longitude = location.longitude; + } catch (error) { + console.error('获取位置失败:', error); + } + } + } +} +``` + +### 场景2:用户点击按钮时获取位置 + +```javascript +export default { + methods: { + async handleGetLocation() { + uni.showLoading({ title: '获取位置中...' }); + try { + const location = await getCurrentLocation(); + uni.hideLoading(); + uni.showToast({ + title: `位置: ${location.latitude}, ${location.longitude}`, + icon: 'success' + }); + } catch (error) { + uni.hideLoading(); + uni.showToast({ + title: error.message || '获取位置失败', + icon: 'none' + }); + } + } + } +} +``` + +### 场景3:获取位置后调用API + +```javascript +export default { + methods: { + async loadNearbyCabinets() { + try { + // 先获取位置 + const location = await getLocationWithFallback(); + + // 调用后端API获取附近的智能柜 + const res = await uni.request({ + url: `http://localhost:8000/v1/cabinet/nearby`, + method: 'GET', + data: { + latitude: location.latitude, + longitude: location.longitude, + radius: 10, + limit: 50 + } + }); + + console.log('附近的智能柜:', res.data); + } catch (error) { + console.error('加载失败:', error); + } + } + } +} +``` + +### 场景4:检查权限后再获取位置 + +```javascript +export default { + methods: { + async getLocationWithPermissionCheck() { + // 检查权限 + const hasPermission = await checkLocationPermission(); + + if (!hasPermission) { + // 请求权限 + try { + await requestLocationPermission(); + } catch (error) { + uni.showModal({ + title: '需要位置权限', + content: '请允许访问位置信息以使用此功能', + showCancel: false + }); + return; + } + } + + // 获取位置 + try { + const location = await getCurrentLocation(); + console.log('位置:', location); + } catch (error) { + console.error('获取位置失败:', error); + } + } + } +} +``` + +## ⚠️ 注意事项 + +1. **权限要求**: + - 获取精确位置需要用户授权位置权限 + - 在微信小程序中,需要在 `manifest.json` 中配置位置权限 + - 在App中,需要在 `manifest.json` 中配置位置权限 + +2. **坐标系统**: + - 默认使用 `gcj02` 坐标系(国测局坐标),这是高德地图和腾讯地图使用的坐标系 + - 如果需要使用GPS坐标,请设置 `type: 'wgs84'` + +3. **降级方案**: + - 如果用户拒绝位置权限,可以使用 `getLocationWithFallback()` 自动降级到IP定位 + - IP定位精度较低,但不需要用户授权 + +4. **错误处理**: + - 建议始终使用 `try-catch` 处理错误 + - 根据错误类型给用户友好的提示 + +5. **性能考虑**: + - 位置获取可能需要几秒钟,建议显示加载提示 + - 可以缓存位置信息,避免频繁请求 + +## 🔧 配置 + +### manifest.json 配置(微信小程序) + +```json +{ + "mp-weixin": { + "permission": { + "scope.userLocation": { + "desc": "你的位置信息将用于查找附近的智能柜" + } + } + } +} +``` + +### manifest.json 配置(App) + +```json +{ + "app-plus": { + "distribute": { + "android": { + "permissions": [ + "", + "" + ] + }, + "ios": { + "privacyDescription": { + "NSLocationWhenInUseUsageDescription": "此应用需要获取您的位置信息以查找附近的智能柜" + } + } + } + } +} +``` + +## 📝 完整示例 + +```vue + + + +``` + +## 🐛 常见问题 + +### Q1: 获取位置失败,提示"用户拒绝了位置权限" +**A:** 需要引导用户到设置中开启位置权限,或者使用 `getLocationWithFallback()` 降级到IP定位。 + +### Q2: 在H5环境中获取位置失败 +**A:** H5环境需要HTTPS协议才能获取位置,或者使用IP定位作为降级方案。 + +### Q3: 位置精度不够 +**A:** 确保使用 `getCurrentLocation()` 而不是 `getLocationByIP()`,并且用户已授权位置权限。 + +### Q4: 如何缓存位置信息? +**A:** 可以将位置信息保存到本地存储,避免频繁请求: +```javascript +// 保存位置 +uni.setStorageSync('lastLocation', location); + +// 读取位置 +const lastLocation = uni.getStorageSync('lastLocation'); +``` + +## 📚 相关文档 + +- [uni.getLocation API文档](https://uniapp.dcloud.net.cn/api/location/location.html) +- [高德地图API文档](https://lbs.amap.com/api/webservice/guide/api/ipconfig) + diff --git a/web/manifest.json b/web/manifest.json index ecad88b9370ab19bc1f00f5bfcb55626babc701c..29c5d18344649ad584dd67a79b4cbeef75bb218b 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -5,7 +5,7 @@ "versionName" : "1.0.0", "versionCode" : "100", "transformPx" : false, - /* 5+App特有相关 */ + "app-plus" : { "usingComponents" : true, "nvueStyleCompiler" : "uni-app", @@ -16,11 +16,11 @@ "autoclose" : true, "delay" : 0 }, - /* 模块配置 */ + "modules" : {}, - /* 应用发布信息 */ + "distribute" : { - /* android打包配置 */ + "android" : { "permissions" : [ "", @@ -46,7 +46,7 @@ "" ] }, - /* ios打包配置 */ + "ios" : {}, /* SDK配置 */ "sdkConfigs" : { @@ -59,9 +59,9 @@ } } }, - /* 快应用特有相关 */ + "quickapp" : {}, - /* 小程序特有相关 */ + "mp-weixin" : { "appid" : "", "setting" : { diff --git a/web/pages.json b/web/pages.json index 49dfd09eaa343290c0d65d0a49415a669c9482d0..e01247a50a90c2dc53a67271ab0df010e1b8a50b 100644 --- a/web/pages.json +++ b/web/pages.json @@ -1,22 +1,155 @@ { - "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages + "pages": [ { - "path": "pages/index/index", - "style": { - "navigationBarTitleText": "uni-app" - } + "path" : "pages/indexs/indexs", + "style" : { + "navigationBarTitleText": "首页", + "enablePullDownRefresh": false + } + }, + { + "path" : "pages/message/message", + "style" : { + "navigationBarTitleText": "消息中心", + "enablePullDownRefresh": false + } + }, + { + "path" : "pages/my/my", + "style" : { + "navigationBarTitleText": "我的", + "enablePullDownRefresh": false + } + }, + { + "path": "pages/my_delivery/my_delivery", + "style": { + "navigationBarTitleText": "派件中心" + } + }, + { + "path" : "pages/real/real", + "style" : { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + }, + { + "path" : "pages/realName/realName", + "style" : { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + }, + { + "path" : "pages/message_system_list/message_list", + "style" : { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + }, + { + "path" : "pages/help/help", + "style" : + { + "navigationBarTitleText": "帮助中心", + "enablePullDownRefresh": false + } + + }, + { + "path" : "pages/message_system_list_two/message_list_two", + "style" : { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + }, + { + "path" : "pages/message_system_list_three/message_list_three", + "style" : { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } }, { - "path": "pages/map/map", - "style": { - "navigationBarTitleText": "地图搜索" + "path" : "pages/message_system_list_four/message_list_four", + "style" : { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + }, + { + "path" : "pages/message_system_detail/message_detail", + "style" : { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + }, + { + "path": "pages/profile/profile", + "style": { + "navigationBarTitleText": "个人中心" + } + }, + { + "path": "pages/login/login", + "style": { + "navigationBarTitleText": "登录", + "navigationStyle": "custom" + } + }, + { + "path": "pages/register/register", + "style": { + "navigationBarTitleText": "注册" + } + }, + { + "path": "pages/forgotPassword/forgotPassword", + "style": { + "navigationBarTitleText": "找回密码" + } + }, + + { + "path": "pages/map/map", + "style": { + "navigationBarTitleText": "地图搜索" + } + } + + + ], + "tabBar": { + "color": "#999", + "selectedColor": "#409EFF", + "backgroundColor": "#fff", + "borderStyle": "black", + "list": [ + { + "pagePath": "pages/indexs/indexs", + "text": "首页", + "iconPath": "static/icon_home.png", + "selectedIconPath": "static/icon_home_active.png" + }, + { + "pagePath": "pages/message/message", + "text": "消息", + "iconPath": "static/icon_message.png", + "selectedIconPath": "static/icon_message_active.png" + }, + { + "pagePath": "pages/my/my", + "text": "我的", + "iconPath": "static/icon_mine.png", + "selectedIconPath": "static/icon_mine_active.png" } - } - ], + ] + }, "globalStyle": { "navigationBarTextStyle": "black", - "navigationBarTitleText": "uni-app", + "navigationBarTitleText": "小默盒管家", "navigationBarBackgroundColor": "#F8F8F8", "backgroundColor": "#F8F8F8" } -} +} \ No newline at end of file diff --git a/web/pages/forgotPassword/forgotPassword.vue b/web/pages/forgotPassword/forgotPassword.vue new file mode 100644 index 0000000000000000000000000000000000000000..46950cb2c0825ea767e5d923964b87df07502438 --- /dev/null +++ b/web/pages/forgotPassword/forgotPassword.vue @@ -0,0 +1,304 @@ + + + + + \ No newline at end of file diff --git a/web/pages/help/help.vue b/web/pages/help/help.vue new file mode 100644 index 0000000000000000000000000000000000000000..b8424bb5f06489ee93602ff56bdafc2b45158866 --- /dev/null +++ b/web/pages/help/help.vue @@ -0,0 +1,156 @@ + + + + + \ No newline at end of file diff --git a/web/pages/index/index.vue b/web/pages/index/index.vue deleted file mode 100644 index 9f75171ae6ffe2d30641ec7030e98f7b3c106d80..0000000000000000000000000000000000000000 --- a/web/pages/index/index.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - - - diff --git a/web/pages/indexs/indexs.vue b/web/pages/indexs/indexs.vue new file mode 100644 index 0000000000000000000000000000000000000000..fc2f539cdbf7b9c9278c52b7f33ee85d0b696208 --- /dev/null +++ b/web/pages/indexs/indexs.vue @@ -0,0 +1,262 @@ + + + + + \ No newline at end of file diff --git a/web/pages/login/login.vue b/web/pages/login/login.vue new file mode 100644 index 0000000000000000000000000000000000000000..f429cfa47d37d5ea5392d3f58cb12c4d833e1ba2 --- /dev/null +++ b/web/pages/login/login.vue @@ -0,0 +1,342 @@ + + + + + \ No newline at end of file diff --git a/web/pages/message/message.vue b/web/pages/message/message.vue new file mode 100644 index 0000000000000000000000000000000000000000..85e05ed6f65ad2c4c9a388172e19f068bd42e049 --- /dev/null +++ b/web/pages/message/message.vue @@ -0,0 +1,267 @@ + + + + + \ No newline at end of file diff --git a/web/pages/message_system_detail/message_detail.vue b/web/pages/message_system_detail/message_detail.vue new file mode 100644 index 0000000000000000000000000000000000000000..49e45d4e73350eb7376a752d294511b2f290ee1a --- /dev/null +++ b/web/pages/message_system_detail/message_detail.vue @@ -0,0 +1,250 @@ + + + + + \ No newline at end of file diff --git a/web/pages/message_system_list/message_list.vue b/web/pages/message_system_list/message_list.vue new file mode 100644 index 0000000000000000000000000000000000000000..93e97fc8bf738da0c84013df1498aa1f244d084d --- /dev/null +++ b/web/pages/message_system_list/message_list.vue @@ -0,0 +1,241 @@ + + + + + \ No newline at end of file diff --git a/web/pages/message_system_list_four/message_list_four.vue b/web/pages/message_system_list_four/message_list_four.vue new file mode 100644 index 0000000000000000000000000000000000000000..90315f57b5b7acd881fc379a40e271d0b63f2288 --- /dev/null +++ b/web/pages/message_system_list_four/message_list_four.vue @@ -0,0 +1,241 @@ + + + + + \ No newline at end of file diff --git a/web/pages/message_system_list_three/message_list_three.vue b/web/pages/message_system_list_three/message_list_three.vue new file mode 100644 index 0000000000000000000000000000000000000000..722220bb819abc6b4f0e2c38eb900d364a9a9c81 --- /dev/null +++ b/web/pages/message_system_list_three/message_list_three.vue @@ -0,0 +1,241 @@ + + + + + \ No newline at end of file diff --git a/web/pages/message_system_list_two/message_list_two.vue b/web/pages/message_system_list_two/message_list_two.vue new file mode 100644 index 0000000000000000000000000000000000000000..bdcb73802d561379ac6f4846f30d79b426eac9e1 --- /dev/null +++ b/web/pages/message_system_list_two/message_list_two.vue @@ -0,0 +1,241 @@ + + + + + \ No newline at end of file diff --git a/web/pages/my/my.vue b/web/pages/my/my.vue new file mode 100644 index 0000000000000000000000000000000000000000..238e18c29fbbd4efc19b62f31f2df3c4e73a1a5f --- /dev/null +++ b/web/pages/my/my.vue @@ -0,0 +1,491 @@ + + + + + \ No newline at end of file diff --git a/web/pages/my_delivery/my_delivery.vue b/web/pages/my_delivery/my_delivery.vue new file mode 100644 index 0000000000000000000000000000000000000000..95cc3dd7187b1fb3486656662075a6fd62d3373c --- /dev/null +++ b/web/pages/my_delivery/my_delivery.vue @@ -0,0 +1,433 @@ + + + + + \ No newline at end of file diff --git a/web/pages/profile/profile.vue b/web/pages/profile/profile.vue new file mode 100644 index 0000000000000000000000000000000000000000..4d1b9048a8d1c190813c02ee8baa873241329a5a --- /dev/null +++ b/web/pages/profile/profile.vue @@ -0,0 +1,277 @@ + + + + + \ No newline at end of file diff --git a/web/pages/real/real.vue b/web/pages/real/real.vue new file mode 100644 index 0000000000000000000000000000000000000000..25719d4bbaafbae75012b540ddc511263ad547d3 --- /dev/null +++ b/web/pages/real/real.vue @@ -0,0 +1,172 @@ + + + + + \ No newline at end of file diff --git a/web/pages/realName/realName.vue b/web/pages/realName/realName.vue new file mode 100644 index 0000000000000000000000000000000000000000..309cfeccb5809061682bfffd6e58485cecdb57ea --- /dev/null +++ b/web/pages/realName/realName.vue @@ -0,0 +1,394 @@ + + + + + \ No newline at end of file diff --git a/web/pages/register/register.vue b/web/pages/register/register.vue new file mode 100644 index 0000000000000000000000000000000000000000..ee55d8cf85042b4dc409f1dbddeb1ff1f3a38b3e --- /dev/null +++ b/web/pages/register/register.vue @@ -0,0 +1,321 @@ + + + + + \ No newline at end of file diff --git a/web/static/one.png b/web/static/one.png new file mode 100644 index 0000000000000000000000000000000000000000..fb5e073195d97f5cbb38aa329952c797521e3acf Binary files /dev/null and b/web/static/one.png differ diff --git "a/web/static/qq\345\244\264\345\203\217.jpg" "b/web/static/qq\345\244\264\345\203\217.jpg" new file mode 100644 index 0000000000000000000000000000000000000000..d7ca84e75eb8b2ef49b2635975268ae85bd869e9 Binary files /dev/null and "b/web/static/qq\345\244\264\345\203\217.jpg" differ diff --git "a/web/static/swt-icon-\345\277\253\351\200\222.png" "b/web/static/swt-icon-\345\277\253\351\200\222.png" new file mode 100644 index 0000000000000000000000000000000000000000..27b3485eafafdbf843c1d5ab0b67020cdbee93bd Binary files /dev/null and "b/web/static/swt-icon-\345\277\253\351\200\222.png" differ diff --git a/web/static/three.png b/web/static/three.png new file mode 100644 index 0000000000000000000000000000000000000000..e08e9e19fe4bd02892037b26444600a73abae905 Binary files /dev/null and b/web/static/three.png differ diff --git a/web/static/two.png b/web/static/two.png new file mode 100644 index 0000000000000000000000000000000000000000..683b56e317260a4875b3fa28826a9f9019326b03 Binary files /dev/null and b/web/static/two.png differ diff --git "a/web/static/\344\277\241\346\201\257 (1).png" "b/web/static/\344\277\241\346\201\257 (1).png" new file mode 100644 index 0000000000000000000000000000000000000000..e406b5964ec2dad673e7c9a8b0e6a70d150a8c04 Binary files /dev/null and "b/web/static/\344\277\241\346\201\257 (1).png" differ diff --git "a/web/static/\345\234\250\347\272\277\345\222\250\350\257\242.png" "b/web/static/\345\234\250\347\272\277\345\222\250\350\257\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..d10bb3e092b3682bb0b1af8001180dccb0ef7c17 Binary files /dev/null and "b/web/static/\345\234\250\347\272\277\345\222\250\350\257\242.png" differ diff --git "a/web/static/\345\256\236\345\220\215\350\256\244\350\257\201.png" "b/web/static/\345\256\236\345\220\215\350\256\244\350\257\201.png" new file mode 100644 index 0000000000000000000000000000000000000000..4253cf130996ad01319abf430f74eea5cd04dfac Binary files /dev/null and "b/web/static/\345\256\236\345\220\215\350\256\244\350\257\201.png" differ diff --git "a/web/static/\345\270\256\345\212\251\344\270\255\345\277\203.png" "b/web/static/\345\270\256\345\212\251\344\270\255\345\277\203.png" new file mode 100644 index 0000000000000000000000000000000000000000..9fef6f836c79962f03821980da8e1d58b971e57e Binary files /dev/null and "b/web/static/\345\270\256\345\212\251\344\270\255\345\277\203.png" differ diff --git "a/web/static/\345\277\253\351\200\222\346\217\275\346\224\266\344\277\241\346\201\257.png" "b/web/static/\345\277\253\351\200\222\346\217\275\346\224\266\344\277\241\346\201\257.png" new file mode 100644 index 0000000000000000000000000000000000000000..04ca405d843245d9a1e0cb453a03f331de9e0cec Binary files /dev/null and "b/web/static/\345\277\253\351\200\222\346\217\275\346\224\266\344\277\241\346\201\257.png" differ diff --git "a/web/static/\346\210\221\347\232\204.png" "b/web/static/\346\210\221\347\232\204.png" new file mode 100644 index 0000000000000000000000000000000000000000..d3739c9932a892dadd2619adb86078ede4a07cf8 Binary files /dev/null and "b/web/static/\346\210\221\347\232\204.png" differ diff --git "a/web/static/\346\211\253\347\240\201.png" "b/web/static/\346\211\253\347\240\201.png" new file mode 100644 index 0000000000000000000000000000000000000000..7e07e0df113db9d2e45474de62e5ae7a08d41402 Binary files /dev/null and "b/web/static/\346\211\253\347\240\201.png" differ diff --git "a/web/static/\346\237\245\346\211\276(1).png" "b/web/static/\346\237\245\346\211\276(1).png" new file mode 100644 index 0000000000000000000000000000000000000000..b0b295a0c0af35ac29d8f740c5257b58fb8c2a19 Binary files /dev/null and "b/web/static/\346\237\245\346\211\276(1).png" differ diff --git "a/web/static/\346\237\245\346\211\276.png" "b/web/static/\346\237\245\346\211\276.png" new file mode 100644 index 0000000000000000000000000000000000000000..ba4f6edfc9e32e6c78da560453ad0545c93cad4f Binary files /dev/null and "b/web/static/\346\237\245\346\211\276.png" differ diff --git "a/web/static/\346\264\276\344\273\266\346\217\220\351\206\222.png" "b/web/static/\346\264\276\344\273\266\346\217\220\351\206\222.png" new file mode 100644 index 0000000000000000000000000000000000000000..df68f31131855debc2be493e46e2862406b86190 Binary files /dev/null and "b/web/static/\346\264\276\344\273\266\346\217\220\351\206\222.png" differ diff --git "a/web/static/\347\224\265\350\257\235\345\256\242\346\234\215.png" "b/web/static/\347\224\265\350\257\235\345\256\242\346\234\215.png" new file mode 100644 index 0000000000000000000000000000000000000000..98873b2ce77cac6ab94de97ed0f0e4e6c432dcd1 Binary files /dev/null and "b/web/static/\347\224\265\350\257\235\345\256\242\346\234\215.png" differ diff --git "a/web/static/\350\256\276\347\275\256.png" "b/web/static/\350\256\276\347\275\256.png" new file mode 100644 index 0000000000000000000000000000000000000000..76582dfb5c0069725c98f798f0f3750cc04542d8 Binary files /dev/null and "b/web/static/\350\256\276\347\275\256.png" differ diff --git "a/web/static/\350\257\246\346\203\205.png" "b/web/static/\350\257\246\346\203\205.png" new file mode 100644 index 0000000000000000000000000000000000000000..48f50ca49b1c40fe88e90daf536a1a10d114957b Binary files /dev/null and "b/web/static/\350\257\246\346\203\205.png" differ diff --git "a/web/static/\350\272\253\344\273\275\350\257\206\345\210\253\350\256\244\350\257\201.png" "b/web/static/\350\272\253\344\273\275\350\257\206\345\210\253\350\256\244\350\257\201.png" new file mode 100644 index 0000000000000000000000000000000000000000..d1e0bf1040c30de9f84263feee108502e4958b89 Binary files /dev/null and "b/web/static/\350\272\253\344\273\275\350\257\206\345\210\253\350\256\244\350\257\201.png" differ diff --git "a/web/static/\351\231\204\350\277\221.png" "b/web/static/\351\231\204\350\277\221.png" new file mode 100644 index 0000000000000000000000000000000000000000..c9eb87e46b53ca0eeeed9ab709a45d8a1f944dba Binary files /dev/null and "b/web/static/\351\231\204\350\277\221.png" differ diff --git "a/web/static/\351\246\226\351\241\265.png" "b/web/static/\351\246\226\351\241\265.png" new file mode 100644 index 0000000000000000000000000000000000000000..aad4acc0cc8146dc88bb017d06491b07dc07c46b Binary files /dev/null and "b/web/static/\351\246\226\351\241\265.png" differ diff --git "a/web/\346\220\234\347\264\242.png" "b/web/\346\220\234\347\264\242.png" new file mode 100644 index 0000000000000000000000000000000000000000..321ed69ae7758034cbb09c967813c64ff56b8507 Binary files /dev/null and "b/web/\346\220\234\347\264\242.png" differ