diff --git a/pkg/wecom/auth/token.go b/pkg/wecom/auth/token.go new file mode 100644 index 0000000..5617385 --- /dev/null +++ b/pkg/wecom/auth/token.go @@ -0,0 +1,77 @@ +package auth + +import ( + "context" + "crypto/md5" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "net/http" + "time" + + "lone-services/pkg/redis" + "lone-services/pkg/utils" +) + +type Client struct { + CorpId string + CorpSecret string +} + +type tokenResponse struct { + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` +} + +func NewClient(corpId, corpSecret string) *Client { + return &Client{ + CorpId: corpId, + CorpSecret: corpSecret, + } +} + +func (c *Client) AccessToken(ctx context.Context) (string, error) { + secretMd5 := md5.Sum([]byte(c.CorpSecret)) + key := fmt.Sprintf("wecom:access_token:%s:%s", c.CorpId, hex.EncodeToString(secretMd5[:])[:8]) + + if redis.Client != nil { + token, err := redis.Client.Get(ctx, key).Result() + if err == nil && token != utils.StringEmpty { + return token, nil + } + } + + url := fmt.Sprintf( + "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", + c.CorpId, + c.CorpSecret, + ) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return utils.StringEmpty, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return utils.StringEmpty, err + } + defer resp.Body.Close() + + var result tokenResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return utils.StringEmpty, err + } + if result.ErrCode != utils.NumberZero { + return utils.StringEmpty, errors.New(result.ErrMsg) + } + + expire := time.Duration(result.ExpiresIn-300) * time.Second + if redis.Client != nil { + _ = redis.Client.Set(ctx, key, result.AccessToken, expire).Err() + } + return result.AccessToken, nil +} diff --git a/pkg/wecom/user/client.go b/pkg/wecom/user/client.go new file mode 100644 index 0000000..39819a5 --- /dev/null +++ b/pkg/wecom/user/client.go @@ -0,0 +1,33 @@ +package user + +import "lone-services/pkg/wecom/auth" + +type Client struct { + auth *auth.Client +} + +func NewClient(authClient *auth.Client) *Client { + return &Client{auth: authClient} +} + +type CreateRequest struct { + UserId string `json:"userid"` + Name string `json:"name"` + Mobile string `json:"mobile"` + AvatarMediaID string `json:"avatar_mediaid,omitempty"` + Enable int `json:"enable"` + Department []int `json:"department"` +} + +type UpdateRequest struct { + UserId string `json:"userid"` + Name string `json:"name"` + Mobile string `json:"mobile"` + AvatarMediaID string `json:"avatar_mediaid,omitempty"` + Enable int `json:"enable"` +} + +type apiResponse struct { + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` +} diff --git a/pkg/wecom/user/create.go b/pkg/wecom/user/create.go new file mode 100644 index 0000000..27ba9b5 --- /dev/null +++ b/pkg/wecom/user/create.go @@ -0,0 +1,50 @@ +package user + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + + "lone-services/pkg/utils" +) + +func (c *Client) Create(ctx context.Context, reqData CreateRequest) error { + token, err := c.auth.AccessToken(ctx) + if err != nil { + return err + } + + url := fmt.Sprintf( + "https://qyapi.weixin.qq.com/cgi-bin/user/create?access_token=%s", + token, + ) + + body, err := json.Marshal(reqData) + if err != nil { + return err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var result apiResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return err + } + if result.ErrCode != utils.NumberZero { + return errors.New(result.ErrMsg) + } + return nil +} diff --git a/pkg/wecom/user/delete.go b/pkg/wecom/user/delete.go new file mode 100644 index 0000000..7a3cd63 --- /dev/null +++ b/pkg/wecom/user/delete.go @@ -0,0 +1,45 @@ +package user + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + + "lone-services/pkg/utils" +) + +func (c *Client) Delete(ctx context.Context, userId string) error { + token, err := c.auth.AccessToken(ctx) + if err != nil { + return err + } + + apiURL := fmt.Sprintf( + "https://qyapi.weixin.qq.com/cgi-bin/user/delete?access_token=%s&userid=%s", + token, + url.QueryEscape(userId), + ) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) + if err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var result apiResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return err + } + if result.ErrCode != utils.NumberZero { + return errors.New(result.ErrMsg) + } + return nil +} diff --git a/pkg/wecom/user/update.go b/pkg/wecom/user/update.go new file mode 100644 index 0000000..14d1b14 --- /dev/null +++ b/pkg/wecom/user/update.go @@ -0,0 +1,50 @@ +package user + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + + "lone-services/pkg/utils" +) + +func (c *Client) Update(ctx context.Context, reqData UpdateRequest) error { + token, err := c.auth.AccessToken(ctx) + if err != nil { + return err + } + + url := fmt.Sprintf( + "https://qyapi.weixin.qq.com/cgi-bin/user/update?access_token=%s", + token, + ) + + body, err := json.Marshal(reqData) + if err != nil { + return err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(body)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var result apiResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return err + } + if result.ErrCode != utils.NumberZero { + return errors.New(result.ErrMsg) + } + return nil +} diff --git a/rpc/sale/pb/sale.pb.go b/rpc/sale/pb/sale.pb.go index 85d99e6..a317eb9 100644 --- a/rpc/sale/pb/sale.pb.go +++ b/rpc/sale/pb/sale.pb.go @@ -1290,6 +1290,146 @@ func (x *StatusReq) GetReason() string { return "" } +type NamesByIdsReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ids []int64 `protobuf:"varint,1,rep,packed,name=ids,proto3" json:"ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NamesByIdsReq) Reset() { + *x = NamesByIdsReq{} + mi := &file_sale_sale_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NamesByIdsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NamesByIdsReq) ProtoMessage() {} + +func (x *NamesByIdsReq) ProtoReflect() protoreflect.Message { + mi := &file_sale_sale_proto_msgTypes[15] + 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 NamesByIdsReq.ProtoReflect.Descriptor instead. +func (*NamesByIdsReq) Descriptor() ([]byte, []int) { + return file_sale_sale_proto_rawDescGZIP(), []int{15} +} + +func (x *NamesByIdsReq) GetIds() []int64 { + if x != nil { + return x.Ids + } + return nil +} + +type NamesByIdsItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NamesByIdsItem) Reset() { + *x = NamesByIdsItem{} + mi := &file_sale_sale_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NamesByIdsItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NamesByIdsItem) ProtoMessage() {} + +func (x *NamesByIdsItem) ProtoReflect() protoreflect.Message { + mi := &file_sale_sale_proto_msgTypes[16] + 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 NamesByIdsItem.ProtoReflect.Descriptor instead. +func (*NamesByIdsItem) Descriptor() ([]byte, []int) { + return file_sale_sale_proto_rawDescGZIP(), []int{16} +} + +func (x *NamesByIdsItem) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *NamesByIdsItem) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type NamesByIdsData struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*NamesByIdsItem `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *NamesByIdsData) Reset() { + *x = NamesByIdsData{} + mi := &file_sale_sale_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *NamesByIdsData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NamesByIdsData) ProtoMessage() {} + +func (x *NamesByIdsData) ProtoReflect() protoreflect.Message { + mi := &file_sale_sale_proto_msgTypes[17] + 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 NamesByIdsData.ProtoReflect.Descriptor instead. +func (*NamesByIdsData) Descriptor() ([]byte, []int) { + return file_sale_sale_proto_rawDescGZIP(), []int{17} +} + +func (x *NamesByIdsData) GetItems() []*NamesByIdsItem { + if x != nil { + return x.Items + } + return nil +} + var File_sale_sale_proto protoreflect.FileDescriptor const file_sale_sale_proto_rawDesc = "" + @@ -1418,7 +1558,14 @@ const file_sale_sale_proto_rawDesc = "" + "\tStatusReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" + "\x06status\x18\x02 \x01(\rR\x06status\x12\x16\n" + - "\x06reason\x18\x03 \x01(\tR\x06reason2\xee\x06\n" + + "\x06reason\x18\x03 \x01(\tR\x06reason\"!\n" + + "\rNamesByIdsReq\x12\x10\n" + + "\x03ids\x18\x01 \x03(\x03R\x03ids\"4\n" + + "\x0eNamesByIdsItem\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"<\n" + + "\x0eNamesByIdsData\x12*\n" + + "\x05items\x18\x01 \x03(\v2\x14.sale.NamesByIdsItemR\x05items2\xa7\a\n" + "\x04Sale\x12S\n" + "\n" + "GroupItems\x12\x13.sale.GroupItemsReq\x1a\x0e.sale.Response\" \x82\xd3\xe4\x93\x02\x1a:\x01*\x12\x15/admin/v3/sales/group\x12U\n" + @@ -1433,7 +1580,9 @@ const file_sale_sale_proto_rawDesc = "" + "\x04Info\x12\r.sale.InfoReq\x1a\x0e.sale.Response\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\x12\x14/admin/v3/sales/info\x12C\n" + "\x05Items\x12\x0e.sale.ItemsReq\x1a\x0e.sale.Response\"\x1a\x82\xd3\xe4\x93\x02\x14:\x01*\x12\x0f/admin/v3/sales\x12L\n" + "\x06Status\x12\x0f.sale.StatusReq\x1a\x0e.sale.Response\"!\x82\xd3\xe4\x93\x02\x1b:\x01*\x1a\x16/admin/v3/sales/status\x12-\n" + - "\fInfoInternal\x12\r.sale.InfoReq\x1a\x0e.sale.InfoDataB\x18Z\x16lone-services/rpc/saleb\x06proto3" + "\fInfoInternal\x12\r.sale.InfoReq\x1a\x0e.sale.InfoData\x127\n" + + "\n" + + "NamesByIds\x12\x13.sale.NamesByIdsReq\x1a\x14.sale.NamesByIdsDataB\x18Z\x16lone-services/rpc/saleb\x06proto3" var ( file_sale_sale_proto_rawDescOnce sync.Once @@ -1447,7 +1596,7 @@ func file_sale_sale_proto_rawDescGZIP() []byte { return file_sale_sale_proto_rawDescData } -var file_sale_sale_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_sale_sale_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_sale_sale_proto_goTypes = []any{ (*Response)(nil), // 0: sale.Response (*GroupEmptyReq)(nil), // 1: sale.GroupEmptyReq @@ -1464,40 +1613,46 @@ var file_sale_sale_proto_goTypes = []any{ (*InfoData)(nil), // 12: sale.InfoData (*ItemsReq)(nil), // 13: sale.ItemsReq (*StatusReq)(nil), // 14: sale.StatusReq + (*NamesByIdsReq)(nil), // 15: sale.NamesByIdsReq + (*NamesByIdsItem)(nil), // 16: sale.NamesByIdsItem + (*NamesByIdsData)(nil), // 17: sale.NamesByIdsData } var file_sale_sale_proto_depIdxs = []int32{ 5, // 0: sale.NamesData.items:type_name -> sale.NameItem 10, // 1: sale.InfoData.region:type_name -> sale.Region 11, // 2: sale.InfoData.group:type_name -> sale.GroupItem - 2, // 3: sale.Sale.GroupItems:input_type -> sale.GroupItemsReq - 3, // 4: sale.Sale.GroupCreate:input_type -> sale.GroupCreateReq - 1, // 5: sale.Sale.GroupNames:input_type -> sale.GroupEmptyReq - 1, // 6: sale.Sale.Types:input_type -> sale.GroupEmptyReq - 4, // 7: sale.Sale.Names:input_type -> sale.NamesReq - 4, // 8: sale.Sale.NamesInternal:input_type -> sale.NamesReq - 7, // 9: sale.Sale.Create:input_type -> sale.CreateReq - 8, // 10: sale.Sale.Edit:input_type -> sale.EditReq - 9, // 11: sale.Sale.Info:input_type -> sale.InfoReq - 13, // 12: sale.Sale.Items:input_type -> sale.ItemsReq - 14, // 13: sale.Sale.Status:input_type -> sale.StatusReq - 9, // 14: sale.Sale.InfoInternal:input_type -> sale.InfoReq - 0, // 15: sale.Sale.GroupItems:output_type -> sale.Response - 0, // 16: sale.Sale.GroupCreate:output_type -> sale.Response - 0, // 17: sale.Sale.GroupNames:output_type -> sale.Response - 0, // 18: sale.Sale.Types:output_type -> sale.Response - 0, // 19: sale.Sale.Names:output_type -> sale.Response - 6, // 20: sale.Sale.NamesInternal:output_type -> sale.NamesData - 0, // 21: sale.Sale.Create:output_type -> sale.Response - 0, // 22: sale.Sale.Edit:output_type -> sale.Response - 0, // 23: sale.Sale.Info:output_type -> sale.Response - 0, // 24: sale.Sale.Items:output_type -> sale.Response - 0, // 25: sale.Sale.Status:output_type -> sale.Response - 12, // 26: sale.Sale.InfoInternal:output_type -> sale.InfoData - 15, // [15:27] is the sub-list for method output_type - 3, // [3:15] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 16, // 3: sale.NamesByIdsData.items:type_name -> sale.NamesByIdsItem + 2, // 4: sale.Sale.GroupItems:input_type -> sale.GroupItemsReq + 3, // 5: sale.Sale.GroupCreate:input_type -> sale.GroupCreateReq + 1, // 6: sale.Sale.GroupNames:input_type -> sale.GroupEmptyReq + 1, // 7: sale.Sale.Types:input_type -> sale.GroupEmptyReq + 4, // 8: sale.Sale.Names:input_type -> sale.NamesReq + 4, // 9: sale.Sale.NamesInternal:input_type -> sale.NamesReq + 7, // 10: sale.Sale.Create:input_type -> sale.CreateReq + 8, // 11: sale.Sale.Edit:input_type -> sale.EditReq + 9, // 12: sale.Sale.Info:input_type -> sale.InfoReq + 13, // 13: sale.Sale.Items:input_type -> sale.ItemsReq + 14, // 14: sale.Sale.Status:input_type -> sale.StatusReq + 9, // 15: sale.Sale.InfoInternal:input_type -> sale.InfoReq + 15, // 16: sale.Sale.NamesByIds:input_type -> sale.NamesByIdsReq + 0, // 17: sale.Sale.GroupItems:output_type -> sale.Response + 0, // 18: sale.Sale.GroupCreate:output_type -> sale.Response + 0, // 19: sale.Sale.GroupNames:output_type -> sale.Response + 0, // 20: sale.Sale.Types:output_type -> sale.Response + 0, // 21: sale.Sale.Names:output_type -> sale.Response + 6, // 22: sale.Sale.NamesInternal:output_type -> sale.NamesData + 0, // 23: sale.Sale.Create:output_type -> sale.Response + 0, // 24: sale.Sale.Edit:output_type -> sale.Response + 0, // 25: sale.Sale.Info:output_type -> sale.Response + 0, // 26: sale.Sale.Items:output_type -> sale.Response + 0, // 27: sale.Sale.Status:output_type -> sale.Response + 12, // 28: sale.Sale.InfoInternal:output_type -> sale.InfoData + 17, // 29: sale.Sale.NamesByIds:output_type -> sale.NamesByIdsData + 17, // [17:30] is the sub-list for method output_type + 4, // [4:17] 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_sale_sale_proto_init() } @@ -1511,7 +1666,7 @@ func file_sale_sale_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_sale_sale_proto_rawDesc), len(file_sale_sale_proto_rawDesc)), NumEnums: 0, - NumMessages: 15, + NumMessages: 18, NumExtensions: 0, NumServices: 1, }, diff --git a/rpc/sale/pb/sale_grpc.pb.go b/rpc/sale/pb/sale_grpc.pb.go index e49e419..0cdbecf 100644 --- a/rpc/sale/pb/sale_grpc.pb.go +++ b/rpc/sale/pb/sale_grpc.pb.go @@ -31,6 +31,7 @@ const ( Sale_Items_FullMethodName = "/sale.Sale/Items" Sale_Status_FullMethodName = "/sale.Sale/Status" Sale_InfoInternal_FullMethodName = "/sale.Sale/InfoInternal" + Sale_NamesByIds_FullMethodName = "/sale.Sale/NamesByIds" ) // SaleClient is the client API for Sale service. @@ -52,6 +53,8 @@ type SaleClient interface { Status(ctx context.Context, in *StatusReq, opts ...grpc.CallOption) (*Response, error) // 销售详情 InfoInternal(ctx context.Context, in *InfoReq, opts ...grpc.CallOption) (*InfoData, error) + // 按 id 批量查销售 id+name(内部) + NamesByIds(ctx context.Context, in *NamesByIdsReq, opts ...grpc.CallOption) (*NamesByIdsData, error) } type saleClient struct { @@ -182,6 +185,16 @@ func (c *saleClient) InfoInternal(ctx context.Context, in *InfoReq, opts ...grpc return out, nil } +func (c *saleClient) NamesByIds(ctx context.Context, in *NamesByIdsReq, opts ...grpc.CallOption) (*NamesByIdsData, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(NamesByIdsData) + err := c.cc.Invoke(ctx, Sale_NamesByIds_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // SaleServer is the server API for Sale service. // All implementations must embed UnimplementedSaleServer // for forward compatibility. @@ -201,6 +214,8 @@ type SaleServer interface { Status(context.Context, *StatusReq) (*Response, error) // 销售详情 InfoInternal(context.Context, *InfoReq) (*InfoData, error) + // 按 id 批量查销售 id+name(内部) + NamesByIds(context.Context, *NamesByIdsReq) (*NamesByIdsData, error) mustEmbedUnimplementedSaleServer() } @@ -247,6 +262,9 @@ func (UnimplementedSaleServer) Status(context.Context, *StatusReq) (*Response, e func (UnimplementedSaleServer) InfoInternal(context.Context, *InfoReq) (*InfoData, error) { return nil, status.Error(codes.Unimplemented, "method InfoInternal not implemented") } +func (UnimplementedSaleServer) NamesByIds(context.Context, *NamesByIdsReq) (*NamesByIdsData, error) { + return nil, status.Error(codes.Unimplemented, "method NamesByIds not implemented") +} func (UnimplementedSaleServer) mustEmbedUnimplementedSaleServer() {} func (UnimplementedSaleServer) testEmbeddedByValue() {} @@ -484,6 +502,24 @@ func _Sale_InfoInternal_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _Sale_NamesByIds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NamesByIdsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SaleServer).NamesByIds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Sale_NamesByIds_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SaleServer).NamesByIds(ctx, req.(*NamesByIdsReq)) + } + return interceptor(ctx, in, info, handler) +} + // Sale_ServiceDesc is the grpc.ServiceDesc for Sale service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -539,6 +575,10 @@ var Sale_ServiceDesc = grpc.ServiceDesc{ MethodName: "InfoInternal", Handler: _Sale_InfoInternal_Handler, }, + { + MethodName: "NamesByIds", + Handler: _Sale_NamesByIds_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "sale/sale.proto", diff --git a/rpc/sale/sale.pb b/rpc/sale/sale.pb index 500cddd..dfad0f3 100644 Binary files a/rpc/sale/sale.pb and b/rpc/sale/sale.pb differ diff --git a/rpc/sale/sale.proto b/rpc/sale/sale.proto index 068e04a..b57db00 100644 --- a/rpc/sale/sale.proto +++ b/rpc/sale/sale.proto @@ -72,6 +72,8 @@ service Sale { // 销售详情 rpc InfoInternal(InfoReq) returns (InfoData); + // ids 批量查销售 + rpc NamesByIds(NamesByIdsReq) returns (NamesByIdsData); } message Response { @@ -208,3 +210,16 @@ message StatusReq { uint32 status = 2; string reason = 3; } + +message NamesByIdsReq { + repeated int64 ids = 1; +} + +message NamesByIdsItem { + int64 id = 1; + string name = 2; +} + +message NamesByIdsData { + repeated NamesByIdsItem items = 1; +} diff --git a/rpc/wecom/pb/wecom.pb.go b/rpc/wecom/pb/wecom.pb.go new file mode 100644 index 0000000..0d93a88 --- /dev/null +++ b/rpc/wecom/pb/wecom.pb.go @@ -0,0 +1,541 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v3.19.4 +// source: wecom/wecom.proto + +package wecom + +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 Response struct { + state protoimpl.MessageState `protogen:"open.v1"` + Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + Msg string `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"` + Data string `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Response) Reset() { + *x = Response{} + mi := &file_wecom_wecom_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Response) ProtoMessage() {} + +func (x *Response) ProtoReflect() protoreflect.Message { + mi := &file_wecom_wecom_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 Response.ProtoReflect.Descriptor instead. +func (*Response) Descriptor() ([]byte, []int) { + return file_wecom_wecom_proto_rawDescGZIP(), []int{0} +} + +func (x *Response) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +func (x *Response) GetMsg() string { + if x != nil { + return x.Msg + } + return "" +} + +func (x *Response) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +type ItemsUserReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + Page uint32 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + PageSize uint32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Type uint32 `protobuf:"varint,4,opt,name=type,proto3" json:"type,omitempty"` // 1: 管理员,2: 销售 + Status uint32 `protobuf:"varint,5,opt,name=status,proto3" json:"status,omitempty"` // 1: 启用,2: 禁用 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ItemsUserReq) Reset() { + *x = ItemsUserReq{} + mi := &file_wecom_wecom_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ItemsUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ItemsUserReq) ProtoMessage() {} + +func (x *ItemsUserReq) ProtoReflect() protoreflect.Message { + mi := &file_wecom_wecom_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 ItemsUserReq.ProtoReflect.Descriptor instead. +func (*ItemsUserReq) Descriptor() ([]byte, []int) { + return file_wecom_wecom_proto_rawDescGZIP(), []int{1} +} + +func (x *ItemsUserReq) GetPage() uint32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ItemsUserReq) GetPageSize() uint32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ItemsUserReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ItemsUserReq) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *ItemsUserReq) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +type CreateUserReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Type uint32 `protobuf:"varint,2,opt,name=type,proto3" json:"type,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Mobile string `protobuf:"bytes,4,opt,name=mobile,proto3" json:"mobile,omitempty"` + Avatar string `protobuf:"bytes,5,opt,name=avatar,proto3" json:"avatar,omitempty"` + Status uint32 `protobuf:"varint,6,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateUserReq) Reset() { + *x = CreateUserReq{} + mi := &file_wecom_wecom_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUserReq) ProtoMessage() {} + +func (x *CreateUserReq) ProtoReflect() protoreflect.Message { + mi := &file_wecom_wecom_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 CreateUserReq.ProtoReflect.Descriptor instead. +func (*CreateUserReq) Descriptor() ([]byte, []int) { + return file_wecom_wecom_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateUserReq) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *CreateUserReq) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *CreateUserReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateUserReq) GetMobile() string { + if x != nil { + return x.Mobile + } + return "" +} + +func (x *CreateUserReq) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +func (x *CreateUserReq) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +type UpdateUserReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // wecom_users 主键,优先 + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + Mobile string `protobuf:"bytes,5,opt,name=mobile,proto3" json:"mobile,omitempty"` + Avatar string `protobuf:"bytes,6,opt,name=avatar,proto3" json:"avatar,omitempty"` + Status uint32 `protobuf:"varint,7,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateUserReq) Reset() { + *x = UpdateUserReq{} + mi := &file_wecom_wecom_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUserReq) ProtoMessage() {} + +func (x *UpdateUserReq) ProtoReflect() protoreflect.Message { + mi := &file_wecom_wecom_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 UpdateUserReq.ProtoReflect.Descriptor instead. +func (*UpdateUserReq) Descriptor() ([]byte, []int) { + return file_wecom_wecom_proto_rawDescGZIP(), []int{3} +} + +func (x *UpdateUserReq) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *UpdateUserReq) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *UpdateUserReq) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *UpdateUserReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdateUserReq) GetMobile() string { + if x != nil { + return x.Mobile + } + return "" +} + +func (x *UpdateUserReq) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +func (x *UpdateUserReq) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +type StatusUserReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Status uint32 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"` // 1: 启用,2: 禁用 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StatusUserReq) Reset() { + *x = StatusUserReq{} + mi := &file_wecom_wecom_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StatusUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusUserReq) ProtoMessage() {} + +func (x *StatusUserReq) ProtoReflect() protoreflect.Message { + mi := &file_wecom_wecom_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 StatusUserReq.ProtoReflect.Descriptor instead. +func (*StatusUserReq) Descriptor() ([]byte, []int) { + return file_wecom_wecom_proto_rawDescGZIP(), []int{4} +} + +func (x *StatusUserReq) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *StatusUserReq) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +type DeleteUserReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteUserReq) Reset() { + *x = DeleteUserReq{} + mi := &file_wecom_wecom_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteUserReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUserReq) ProtoMessage() {} + +func (x *DeleteUserReq) ProtoReflect() protoreflect.Message { + mi := &file_wecom_wecom_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 DeleteUserReq.ProtoReflect.Descriptor instead. +func (*DeleteUserReq) Descriptor() ([]byte, []int) { + return file_wecom_wecom_proto_rawDescGZIP(), []int{5} +} + +func (x *DeleteUserReq) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +var File_wecom_wecom_proto protoreflect.FileDescriptor + +const file_wecom_wecom_proto_rawDesc = "" + + "\n" + + "\x11wecom/wecom.proto\x12\x05wecom\x1a\x1cgoogle/api/annotations.proto\"D\n" + + "\bResponse\x12\x12\n" + + "\x04code\x18\x01 \x01(\x05R\x04code\x12\x10\n" + + "\x03msg\x18\x02 \x01(\tR\x03msg\x12\x12\n" + + "\x04data\x18\x03 \x01(\tR\x04data\"\x7f\n" + + "\fItemsUserReq\x12\x12\n" + + "\x04page\x18\x01 \x01(\rR\x04page\x12\x1b\n" + + "\tpage_size\x18\x02 \x01(\rR\bpageSize\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x04 \x01(\rR\x04type\x12\x16\n" + + "\x06status\x18\x05 \x01(\rR\x06status\"\x98\x01\n" + + "\rCreateUserReq\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" + + "\x04type\x18\x02 \x01(\rR\x04type\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x16\n" + + "\x06mobile\x18\x04 \x01(\tR\x06mobile\x12\x16\n" + + "\x06avatar\x18\x05 \x01(\tR\x06avatar\x12\x16\n" + + "\x06status\x18\x06 \x01(\rR\x06status\"\xa8\x01\n" + + "\rUpdateUserReq\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x12\n" + + "\x04type\x18\x03 \x01(\rR\x04type\x12\x12\n" + + "\x04name\x18\x04 \x01(\tR\x04name\x12\x16\n" + + "\x06mobile\x18\x05 \x01(\tR\x06mobile\x12\x16\n" + + "\x06avatar\x18\x06 \x01(\tR\x06avatar\x12\x16\n" + + "\x06status\x18\a \x01(\rR\x06status\"7\n" + + "\rStatusUserReq\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" + + "\x06status\x18\x02 \x01(\rR\x06status\"\x1f\n" + + "\rDeleteUserReq\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id2\xc2\x03\n" + + "\x05Wecom\x12S\n" + + "\n" + + "ItemsUsers\x12\x13.wecom.ItemsUserReq\x1a\x0f.wecom.Response\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\x12\x14/admin/v3/wecom/user\x12T\n" + + "\n" + + "CreateUser\x12\x14.wecom.CreateUserReq\x1a\x0f.wecom.Response\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/admin/v3/wecom/user\x12T\n" + + "\n" + + "UpdateUser\x12\x14.wecom.UpdateUserReq\x1a\x0f.wecom.Response\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\x1a\x14/admin/v3/wecom/user\x12[\n" + + "\n" + + "StatusUser\x12\x14.wecom.StatusUserReq\x1a\x0f.wecom.Response\"&\x82\xd3\xe4\x93\x02 :\x01*\x1a\x1b/admin/v3/wecom/user/status\x12[\n" + + "\n" + + "DeleteUser\x12\x14.wecom.DeleteUserReq\x1a\x0f.wecom.Response\"&\x82\xd3\xe4\x93\x02 :\x01*\x1a\x1b/admin/v3/wecom/user/deleteB\x19Z\x17lone-services/rpc/wecomb\x06proto3" + +var ( + file_wecom_wecom_proto_rawDescOnce sync.Once + file_wecom_wecom_proto_rawDescData []byte +) + +func file_wecom_wecom_proto_rawDescGZIP() []byte { + file_wecom_wecom_proto_rawDescOnce.Do(func() { + file_wecom_wecom_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_wecom_wecom_proto_rawDesc), len(file_wecom_wecom_proto_rawDesc))) + }) + return file_wecom_wecom_proto_rawDescData +} + +var file_wecom_wecom_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_wecom_wecom_proto_goTypes = []any{ + (*Response)(nil), // 0: wecom.Response + (*ItemsUserReq)(nil), // 1: wecom.ItemsUserReq + (*CreateUserReq)(nil), // 2: wecom.CreateUserReq + (*UpdateUserReq)(nil), // 3: wecom.UpdateUserReq + (*StatusUserReq)(nil), // 4: wecom.StatusUserReq + (*DeleteUserReq)(nil), // 5: wecom.DeleteUserReq +} +var file_wecom_wecom_proto_depIdxs = []int32{ + 1, // 0: wecom.Wecom.ItemsUsers:input_type -> wecom.ItemsUserReq + 2, // 1: wecom.Wecom.CreateUser:input_type -> wecom.CreateUserReq + 3, // 2: wecom.Wecom.UpdateUser:input_type -> wecom.UpdateUserReq + 4, // 3: wecom.Wecom.StatusUser:input_type -> wecom.StatusUserReq + 5, // 4: wecom.Wecom.DeleteUser:input_type -> wecom.DeleteUserReq + 0, // 5: wecom.Wecom.ItemsUsers:output_type -> wecom.Response + 0, // 6: wecom.Wecom.CreateUser:output_type -> wecom.Response + 0, // 7: wecom.Wecom.UpdateUser:output_type -> wecom.Response + 0, // 8: wecom.Wecom.StatusUser:output_type -> wecom.Response + 0, // 9: wecom.Wecom.DeleteUser:output_type -> wecom.Response + 5, // [5:10] is the sub-list for method output_type + 0, // [0:5] 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_wecom_wecom_proto_init() } +func file_wecom_wecom_proto_init() { + if File_wecom_wecom_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_wecom_wecom_proto_rawDesc), len(file_wecom_wecom_proto_rawDesc)), + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_wecom_wecom_proto_goTypes, + DependencyIndexes: file_wecom_wecom_proto_depIdxs, + MessageInfos: file_wecom_wecom_proto_msgTypes, + }.Build() + File_wecom_wecom_proto = out.File + file_wecom_wecom_proto_goTypes = nil + file_wecom_wecom_proto_depIdxs = nil +} diff --git a/rpc/wecom/pb/wecom_grpc.pb.go b/rpc/wecom/pb/wecom_grpc.pb.go new file mode 100644 index 0000000..195536d --- /dev/null +++ b/rpc/wecom/pb/wecom_grpc.pb.go @@ -0,0 +1,273 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc v3.19.4 +// source: wecom/wecom.proto + +package wecom + +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 ( + Wecom_ItemsUsers_FullMethodName = "/wecom.Wecom/ItemsUsers" + Wecom_CreateUser_FullMethodName = "/wecom.Wecom/CreateUser" + Wecom_UpdateUser_FullMethodName = "/wecom.Wecom/UpdateUser" + Wecom_StatusUser_FullMethodName = "/wecom.Wecom/StatusUser" + Wecom_DeleteUser_FullMethodName = "/wecom.Wecom/DeleteUser" +) + +// WecomClient is the client API for Wecom 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 WecomClient interface { + ItemsUsers(ctx context.Context, in *ItemsUserReq, opts ...grpc.CallOption) (*Response, error) + CreateUser(ctx context.Context, in *CreateUserReq, opts ...grpc.CallOption) (*Response, error) + UpdateUser(ctx context.Context, in *UpdateUserReq, opts ...grpc.CallOption) (*Response, error) + StatusUser(ctx context.Context, in *StatusUserReq, opts ...grpc.CallOption) (*Response, error) + DeleteUser(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*Response, error) +} + +type wecomClient struct { + cc grpc.ClientConnInterface +} + +func NewWecomClient(cc grpc.ClientConnInterface) WecomClient { + return &wecomClient{cc} +} + +func (c *wecomClient) ItemsUsers(ctx context.Context, in *ItemsUserReq, opts ...grpc.CallOption) (*Response, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Response) + err := c.cc.Invoke(ctx, Wecom_ItemsUsers_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *wecomClient) CreateUser(ctx context.Context, in *CreateUserReq, opts ...grpc.CallOption) (*Response, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Response) + err := c.cc.Invoke(ctx, Wecom_CreateUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *wecomClient) UpdateUser(ctx context.Context, in *UpdateUserReq, opts ...grpc.CallOption) (*Response, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Response) + err := c.cc.Invoke(ctx, Wecom_UpdateUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *wecomClient) StatusUser(ctx context.Context, in *StatusUserReq, opts ...grpc.CallOption) (*Response, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Response) + err := c.cc.Invoke(ctx, Wecom_StatusUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *wecomClient) DeleteUser(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*Response, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(Response) + err := c.cc.Invoke(ctx, Wecom_DeleteUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// WecomServer is the server API for Wecom service. +// All implementations must embed UnimplementedWecomServer +// for forward compatibility. +type WecomServer interface { + ItemsUsers(context.Context, *ItemsUserReq) (*Response, error) + CreateUser(context.Context, *CreateUserReq) (*Response, error) + UpdateUser(context.Context, *UpdateUserReq) (*Response, error) + StatusUser(context.Context, *StatusUserReq) (*Response, error) + DeleteUser(context.Context, *DeleteUserReq) (*Response, error) + mustEmbedUnimplementedWecomServer() +} + +// UnimplementedWecomServer 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 UnimplementedWecomServer struct{} + +func (UnimplementedWecomServer) ItemsUsers(context.Context, *ItemsUserReq) (*Response, error) { + return nil, status.Error(codes.Unimplemented, "method ItemsUsers not implemented") +} +func (UnimplementedWecomServer) CreateUser(context.Context, *CreateUserReq) (*Response, error) { + return nil, status.Error(codes.Unimplemented, "method CreateUser not implemented") +} +func (UnimplementedWecomServer) UpdateUser(context.Context, *UpdateUserReq) (*Response, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateUser not implemented") +} +func (UnimplementedWecomServer) StatusUser(context.Context, *StatusUserReq) (*Response, error) { + return nil, status.Error(codes.Unimplemented, "method StatusUser not implemented") +} +func (UnimplementedWecomServer) DeleteUser(context.Context, *DeleteUserReq) (*Response, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteUser not implemented") +} +func (UnimplementedWecomServer) mustEmbedUnimplementedWecomServer() {} +func (UnimplementedWecomServer) testEmbeddedByValue() {} + +// UnsafeWecomServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to WecomServer will +// result in compilation errors. +type UnsafeWecomServer interface { + mustEmbedUnimplementedWecomServer() +} + +func RegisterWecomServer(s grpc.ServiceRegistrar, srv WecomServer) { + // If the following call panics, it indicates UnimplementedWecomServer 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(&Wecom_ServiceDesc, srv) +} + +func _Wecom_ItemsUsers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ItemsUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WecomServer).ItemsUsers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Wecom_ItemsUsers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WecomServer).ItemsUsers(ctx, req.(*ItemsUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Wecom_CreateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WecomServer).CreateUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Wecom_CreateUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WecomServer).CreateUser(ctx, req.(*CreateUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Wecom_UpdateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WecomServer).UpdateUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Wecom_UpdateUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WecomServer).UpdateUser(ctx, req.(*UpdateUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Wecom_StatusUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StatusUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WecomServer).StatusUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Wecom_StatusUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WecomServer).StatusUser(ctx, req.(*StatusUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Wecom_DeleteUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteUserReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WecomServer).DeleteUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Wecom_DeleteUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WecomServer).DeleteUser(ctx, req.(*DeleteUserReq)) + } + return interceptor(ctx, in, info, handler) +} + +// Wecom_ServiceDesc is the grpc.ServiceDesc for Wecom service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Wecom_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "wecom.Wecom", + HandlerType: (*WecomServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ItemsUsers", + Handler: _Wecom_ItemsUsers_Handler, + }, + { + MethodName: "CreateUser", + Handler: _Wecom_CreateUser_Handler, + }, + { + MethodName: "UpdateUser", + Handler: _Wecom_UpdateUser_Handler, + }, + { + MethodName: "StatusUser", + Handler: _Wecom_StatusUser_Handler, + }, + { + MethodName: "DeleteUser", + Handler: _Wecom_DeleteUser_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "wecom/wecom.proto", +} diff --git a/rpc/wecom/wecom.pb b/rpc/wecom/wecom.pb new file mode 100644 index 0000000..0beb3dc Binary files /dev/null and b/rpc/wecom/wecom.pb differ diff --git a/rpc/wecom/wecom.proto b/rpc/wecom/wecom.proto new file mode 100644 index 0000000..f087596 --- /dev/null +++ b/rpc/wecom/wecom.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package wecom; +option go_package = "lone-services/rpc/wecom"; +import "google/api/annotations.proto"; + +service Wecom { + rpc ItemsUsers(ItemsUserReq) returns (Response) { + option (google.api.http) = { + get: "/admin/v3/wecom/user" + body: "*" + }; + } + rpc CreateUser(CreateUserReq) returns (Response) { + option (google.api.http) = { + post: "/admin/v3/wecom/user" + body: "*" + }; + } + rpc UpdateUser(UpdateUserReq) returns (Response) { + option (google.api.http) = { + put: "/admin/v3/wecom/user" + body: "*" + }; + } + rpc StatusUser(StatusUserReq) returns (Response) { + option (google.api.http) = { + put: "/admin/v3/wecom/user/status" + body: "*" + }; + } + rpc DeleteUser(DeleteUserReq) returns (Response) { + option (google.api.http) = { + put: "/admin/v3/wecom/user/delete" + body: "*" + }; + } +} + +message Response { + int32 code = 1; + string msg = 2; + string data = 3; +} + +message ItemsUserReq { + uint32 page = 1; + uint32 page_size = 2; + string name = 3; + uint32 type = 4; // 1: 管理员,2: 销售 + uint32 status = 5; // 1: 启用,2: 禁用 +} + +message CreateUserReq { + int64 user_id = 1; + uint32 type = 2; + string name = 3; + string mobile = 4; + string avatar = 5; + uint32 status = 6; +} + +message UpdateUserReq { + int64 id = 1; // wecom_users 主键,优先 + int64 user_id = 2; + uint32 type = 3; + string name = 4; + string mobile = 5; + string avatar = 6; + uint32 status = 7; +} + +message StatusUserReq { + int64 id = 1; + uint32 status = 2; // 1: 启用,2: 禁用 +} + +message DeleteUserReq { + int64 id = 1; +} diff --git a/services/sale/internal/logic/namesByIdsLogic.go b/services/sale/internal/logic/namesByIdsLogic.go new file mode 100644 index 0000000..611050a --- /dev/null +++ b/services/sale/internal/logic/namesByIdsLogic.go @@ -0,0 +1,76 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + sale "lone-services/rpc/sale/pb" + "lone-services/services/sale/internal/dao" + "lone-services/services/sale/internal/model" + "lone-services/services/sale/internal/svc" + "lone-services/services/sale/validator" + + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type NamesByIdsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewNamesByIdsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *NamesByIdsLogic { + return &NamesByIdsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *NamesByIdsLogic) NamesByIds(in *sale.NamesByIdsReq) (*sale.NamesByIdsData, error) { + var req validator.NamesByIdsValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return nil, status.Error(codes.InvalidArgument, msg) + } + if len(req.Ids) < utils.NumberOne { + return &sale.NamesByIdsData{Items: []*sale.NamesByIdsItem{}}, nil + } + + seen := make(map[int64]struct{}, len(req.Ids)) + ids := make([]string, 0, len(req.Ids)) + for _, id := range req.Ids { + if id < utils.NumberOne { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + ids = append(ids, strconv.FormatInt(id, utils.NumberTen)) + } + if len(ids) < utils.NumberOne { + return &sale.NamesByIdsData{Items: []*sale.NamesByIdsItem{}}, nil + } + + var rows []dao.SaleNameItem + if err := (model.SaleModel{}.Init().Items(modelbase.Params{ + In: map[string][]string{"id in ?": ids}, + }, &rows)); err != nil { + l.Errorf("sale NamesByIds: %v", err) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + + items := make([]*sale.NamesByIdsItem, 0, len(rows)) + for _, row := range rows { + items = append(items, &sale.NamesByIdsItem{ + Id: row.Id, + Name: row.Name, + }) + } + return &sale.NamesByIdsData{Items: items}, nil +} diff --git a/services/sale/internal/server/saleServer.go b/services/sale/internal/server/saleServer.go index c43d735..04d9ff5 100644 --- a/services/sale/internal/server/saleServer.go +++ b/services/sale/internal/server/saleServer.go @@ -85,3 +85,9 @@ func (s *SaleServer) InfoInternal(ctx context.Context, in *sale.InfoReq) (*sale. l := logic.NewInfoInternalLogic(ctx, s.svcCtx) return l.InfoInternal(in) } + +// 按 id 批量查销售 id+name(内部) +func (s *SaleServer) NamesByIds(ctx context.Context, in *sale.NamesByIdsReq) (*sale.NamesByIdsData, error) { + l := logic.NewNamesByIdsLogic(ctx, s.svcCtx) + return l.NamesByIds(in) +} diff --git a/services/sale/saleClient/sale.go b/services/sale/saleClient/sale.go index 4d7ff6c..9ab2af6 100644 --- a/services/sale/saleClient/sale.go +++ b/services/sale/saleClient/sale.go @@ -24,6 +24,9 @@ type ( InfoReq = sale.InfoReq ItemsReq = sale.ItemsReq NameItem = sale.NameItem + NamesByIdsData = sale.NamesByIdsData + NamesByIdsItem = sale.NamesByIdsItem + NamesByIdsReq = sale.NamesByIdsReq NamesData = sale.NamesData NamesReq = sale.NamesReq Region = sale.Region @@ -46,6 +49,8 @@ type ( Status(ctx context.Context, in *StatusReq, opts ...grpc.CallOption) (*Response, error) // 销售详情 InfoInternal(ctx context.Context, in *InfoReq, opts ...grpc.CallOption) (*InfoData, error) + // 按 id 批量查销售 id+name(内部) + NamesByIds(ctx context.Context, in *NamesByIdsReq, opts ...grpc.CallOption) (*NamesByIdsData, error) } defaultSale struct { @@ -121,3 +126,9 @@ func (m *defaultSale) InfoInternal(ctx context.Context, in *InfoReq, opts ...grp client := sale.NewSaleClient(m.cli.Conn()) return client.InfoInternal(ctx, in, opts...) } + +// 按 id 批量查销售 id+name(内部) +func (m *defaultSale) NamesByIds(ctx context.Context, in *NamesByIdsReq, opts ...grpc.CallOption) (*NamesByIdsData, error) { + client := sale.NewSaleClient(m.cli.Conn()) + return client.NamesByIds(ctx, in, opts...) +} diff --git a/services/sale/validator/validator.go b/services/sale/validator/validator.go index 73716ce..ff0be34 100644 --- a/services/sale/validator/validator.go +++ b/services/sale/validator/validator.go @@ -182,3 +182,16 @@ func (p StatusValidator) GetMessage() validate.ValidatorMessages { "Reason.max": "理由长度不能超过255", } } + +type NamesByIdsValidator struct { + Ids []int64 `validate:"required,min=1,dive,gt=0"` +} + +func (p NamesByIdsValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "Ids.required": "ids不能为空", + "Ids.min": "ids不能为空", + "Ids.dive": "id无效", + "Ids.gt": "id必须大于0", + } +} diff --git a/services/wecom/.gitignore b/services/wecom/.gitignore new file mode 100644 index 0000000..0457a55 --- /dev/null +++ b/services/wecom/.gitignore @@ -0,0 +1,2 @@ +# Ignore go build binary +wecom diff --git a/services/wecom/Dockerfile b/services/wecom/Dockerfile new file mode 100644 index 0000000..a319b43 --- /dev/null +++ b/services/wecom/Dockerfile @@ -0,0 +1,39 @@ +FROM golang:1.26.5-alpine AS builder + +WORKDIR /src + +ENV GOPROXY=https://goproxy.cn,direct \ + CGO_ENABLED=0 \ + GOOS=linux \ + GOARCH=amd64 + +COPY pkg ./pkg +COPY go.mod go.sum ./ + +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download + +COPY rpc/ ./rpc +COPY services/wecom/ ./services/wecom/ + +WORKDIR /src/services/wecom + +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + go build -ldflags="-s -w" -o /out/main ./ + +FROM alpine:3.22 + +WORKDIR /app + +ENV TZ=Asia/Shanghai + +RUN apk add --no-cache tzdata \ + && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && echo "Asia/Shanghai" > /etc/timezone + +COPY --from=builder /out/main . + +EXPOSE 10800 + +CMD ["./main", "-f", "etc/wecom.yaml"] diff --git a/services/wecom/etc/wecom.yaml b/services/wecom/etc/wecom.yaml new file mode 100644 index 0000000..aaf7ad5 --- /dev/null +++ b/services/wecom/etc/wecom.yaml @@ -0,0 +1,7 @@ +Nacos: + Hosts: + - rnacos:8848 + NamespaceId: test + Group: LONE_SERVICES + RegisterIP: wecom + ConfigID: wecom diff --git a/services/wecom/internal/client/contact.go b/services/wecom/internal/client/contact.go new file mode 100644 index 0000000..dbd0199 --- /dev/null +++ b/services/wecom/internal/client/contact.go @@ -0,0 +1,14 @@ +package client + +import ( + "lone-services/pkg/utils" + "lone-services/pkg/wecom/auth" + "lone-services/pkg/wecom/user" +) + +func NewContactClient() *user.Client { + return user.NewClient(auth.NewClient( + utils.GetConfigString("wecom.corp_id"), + utils.GetConfigString("wecom.contact_secret"), + )) +} diff --git a/services/wecom/internal/config/config.go b/services/wecom/internal/config/config.go new file mode 100644 index 0000000..a1979dd --- /dev/null +++ b/services/wecom/internal/config/config.go @@ -0,0 +1,13 @@ +package config + +type Config struct { + Nacos NacosConf +} + +type NacosConf struct { + Hosts []string + NamespaceId string `json:",optional"` + Group string `json:",optional"` + RegisterIP string `json:",optional"` + ConfigID string `json:",optional"` +} diff --git a/services/wecom/internal/dao/user.go b/services/wecom/internal/dao/user.go new file mode 100644 index 0000000..d5dc3f8 --- /dev/null +++ b/services/wecom/internal/dao/user.go @@ -0,0 +1,41 @@ +package dao + +import "lone-services/pkg/utils" + +const ( + UserTypeAdmin uint8 = 1 // 管理员 + UserTypeSale uint8 = 2 // 销售 + + StatusEnabled uint8 = 1 // 启用 + StatusDisabled uint8 = 2 // 禁用 +) + +// WecomUser 用户企业微信绑定 +type WecomUser struct { + Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + UserId int64 `gorm:"column:user_id" json:"user_id"` + Type uint8 `gorm:"column:type" json:"type"` + WecomUserId string `gorm:"column:wecom_user_id" json:"wecom_user_id"` + Name string `gorm:"column:name" json:"name"` + Mobile string `gorm:"column:mobile" json:"mobile"` + Avatar string `gorm:"column:avatar" json:"avatar"` + Status uint8 `gorm:"column:status" json:"status"` + CreatedAt utils.CustomTime `gorm:"column:created_at" json:"created_at"` + UpdatedAt utils.CustomTime `gorm:"column:updated_at" json:"updated_at"` +} + +type UserListItem struct { + Id int64 `json:"id"` + UserId int64 `json:"user_id"` + Type uint8 `json:"type"` + TypeText string `json:"type_text"` + Name string `json:"name"` + Mobile string `json:"mobile"` + OriginMobile string `json:"origin_mobile"` + Avatar string `json:"avatar"` + Status uint8 `json:"status"` + BizName string `json:"biz_name"` + WecomUserId string `json:"wecom_user_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} diff --git a/services/wecom/internal/logic/createUserLogic.go b/services/wecom/internal/logic/createUserLogic.go new file mode 100644 index 0000000..782eba7 --- /dev/null +++ b/services/wecom/internal/logic/createUserLogic.go @@ -0,0 +1,101 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + wecomuser "lone-services/pkg/wecom/user" + wecom "lone-services/rpc/wecom/pb" + "lone-services/services/wecom/internal/client" + "lone-services/services/wecom/internal/dao" + "lone-services/services/wecom/internal/model" + "lone-services/services/wecom/internal/svc" + "lone-services/services/wecom/validator" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CreateUserLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewCreateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateUserLogic { + return &CreateUserLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *CreateUserLogic) CreateUser(in *wecom.CreateUserReq) (*wecom.Response, error) { + var req validator.CreateUserValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return outResponse(utils.ErrorParams, msg), nil + } + + userStatus := resolveStatus(req.Status, dao.StatusEnabled) + + m := model.UserModel{}.Init() + var existing dao.WecomUser + if err := m.GetOne(modelbase.Params{ + Eq: map[string]string{ + "user_id": strconv.FormatInt(req.UserId, utils.NumberTen), + "type": strconv.FormatUint(uint64(req.Type), utils.NumberTen), + "status": strconv.Itoa(int(dao.StatusEnabled)), + }, + }, &existing); err != nil { + l.Errorf("wecom create user check: %v", err) + return failResponse(utils.Fail), nil + } + if existing.Id > utils.NumberZero { + return failResponse(utils.ErrorDataIsExist), nil + } + + encryptMobile, err := utils.EncryptPhone(req.Mobile) + if err != nil { + l.Errorf("wecom encrypt mobile: %v", err) + return failResponse(utils.Fail), nil + } + + wecomUserId := utils.RandString(16) + if !utils.GetConfigBool("wecom.skip") { + createReq := wecomuser.CreateRequest{ + UserId: wecomUserId, + Name: req.Name, + Mobile: req.Mobile, + Enable: statusEnable(userStatus), + Department: []int{departmentID()}, + } + if req.Avatar != utils.StringEmpty { + createReq.AvatarMediaID = req.Avatar + } + if err := client.NewContactClient().Create(l.ctx, createReq); err != nil { + l.Errorf("wecom create user: %v", err) + return failWecomHTTP(), nil + } + } + + now := utils.Now() + row := dao.WecomUser{ + UserId: req.UserId, + Type: uint8(req.Type), + WecomUserId: wecomUserId, + Name: req.Name, + Mobile: encryptMobile, + Avatar: req.Avatar, + Status: userStatus, + CreatedAt: now, + UpdatedAt: now, + } + if err := m.Create(&row); err != nil { + l.Errorf("wecom create user db: %v", err) + return failResponse(utils.Fail), nil + } + + return okResponse(nil), nil +} diff --git a/services/wecom/internal/logic/deleteUserLogic.go b/services/wecom/internal/logic/deleteUserLogic.go new file mode 100644 index 0000000..c5342d0 --- /dev/null +++ b/services/wecom/internal/logic/deleteUserLogic.go @@ -0,0 +1,70 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + wecom "lone-services/rpc/wecom/pb" + "lone-services/services/wecom/internal/client" + "lone-services/services/wecom/internal/dao" + "lone-services/services/wecom/internal/model" + "lone-services/services/wecom/internal/svc" + "lone-services/services/wecom/validator" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DeleteUserLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewDeleteUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteUserLogic { + return &DeleteUserLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *DeleteUserLogic) DeleteUser(in *wecom.DeleteUserReq) (*wecom.Response, error) { + var req validator.DeleteUserValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return outResponse(utils.ErrorParams, msg), nil + } + + m := model.UserModel{}.Init() + var row dao.WecomUser + if err := m.GetOne(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(req.Id, utils.NumberTen)}, + }, &row); err != nil { + l.Errorf("wecom delete user get: %v", err) + return failResponse(utils.Fail), nil + } + if row.Id < utils.NumberOne { + return failResponse(utils.ErrorNotFund), nil + } + + if !utils.GetConfigBool("wecom.skip") { + if err := client.NewContactClient().Delete(l.ctx, row.WecomUserId); err != nil { + l.Errorf("wecom http delete user: %v", err) + return failWecomHTTP(), nil + } + } + + if _, err := m.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(row.Id, utils.NumberTen)}, + }, map[string]interface{}{ + "status": dao.StatusDisabled, + "updated_at": utils.Now(), + }); err != nil { + l.Errorf("wecom delete user db: %v", err) + return failResponse(utils.Fail), nil + } + + return okResponse(nil), nil +} diff --git a/services/wecom/internal/logic/helper.go b/services/wecom/internal/logic/helper.go new file mode 100644 index 0000000..b64d07a --- /dev/null +++ b/services/wecom/internal/logic/helper.go @@ -0,0 +1,42 @@ +package logic + +import ( + "lone-services/pkg/utils" + "lone-services/services/wecom/internal/dao" +) + +func departmentID() int { + id := utils.GetConfigInt("wecom.department_id") + if id < utils.NumberOne { + return utils.NumberOne + } + return id +} + +func statusEnable(status uint8) int { + if status == dao.StatusDisabled { + return utils.NumberZero + } + return utils.NumberOne +} + +func typeText(t uint8) string { + switch t { + case dao.UserTypeAdmin: + return "管理员" + case dao.UserTypeSale: + return "销售" + default: + return utils.StringEmpty + } +} + +func resolveStatus(reqStatus uint32, current uint8) uint8 { + if reqStatus == uint32(dao.StatusEnabled) || reqStatus == uint32(dao.StatusDisabled) { + return uint8(reqStatus) + } + if current > utils.NumberZero { + return current + } + return dao.StatusEnabled +} diff --git a/services/wecom/internal/logic/itemsUsersLogic.go b/services/wecom/internal/logic/itemsUsersLogic.go new file mode 100644 index 0000000..cc13ea6 --- /dev/null +++ b/services/wecom/internal/logic/itemsUsersLogic.go @@ -0,0 +1,141 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/rpcclient" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + sale "lone-services/rpc/sale/pb" + wecom "lone-services/rpc/wecom/pb" + "lone-services/services/wecom/internal/dao" + "lone-services/services/wecom/internal/model" + "lone-services/services/wecom/internal/svc" + "lone-services/services/wecom/validator" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ItemsUsersLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewItemsUsersLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ItemsUsersLogic { + return &ItemsUsersLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *ItemsUsersLogic) ItemsUsers(in *wecom.ItemsUserReq) (*wecom.Response, error) { + var req validator.ItemsUserValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return outResponse(utils.ErrorParams, msg), nil + } + + page := int(req.Page) + size := int(req.PageSize) + if page < utils.NumberOne { + page = modelbase.DefaultPage + } + if size < utils.NumberOne { + size = modelbase.DefaultSize + } + + w := modelbase.Params{ + Eq: map[string]string{}, + Like: map[string]string{}, + Order: "id desc", + Page: page, + Size: size, + } + if req.Type > utils.NumberZero { + w.Eq["type"] = strconv.FormatUint(uint64(req.Type), utils.NumberTen) + } + w.Eq["status"] = strconv.FormatUint(uint64(dao.StatusEnabled), utils.NumberTen) + if req.Name != utils.StringEmpty { + w.Like["name LIKE ?"] = "%" + req.Name + "%" + } + + var rows []dao.WecomUser + result, err := model.UserModel{}.Init().Page(w, &rows) + if err != nil { + l.Errorf("wecom items users: %v", err) + return failResponse(utils.Fail), nil + } + + saleNames, err := l.fetchSaleNames(rows) + if err != nil { + l.Errorf("wecom items fetch sale names: %v", err) + return failResponse(utils.Fail), nil + } + + items := make([]dao.UserListItem, 0, len(rows)) + for _, row := range rows { + mobile := utils.StringEmpty + if dec, dErr := utils.DecryptPhone(row.Mobile); dErr == nil { + mobile = utils.DecryptPhoneReplace(dec) + } + // 管理员人员未获取到 + bizName := utils.StringEmpty + if row.Type == dao.UserTypeSale { + bizName = saleNames[row.UserId] + } + items = append(items, dao.UserListItem{ + Id: row.Id, + UserId: row.UserId, + Type: row.Type, + TypeText: typeText(row.Type), + Name: row.Name, + Mobile: mobile, + OriginMobile: row.Mobile, + Avatar: row.Avatar, + Status: row.Status, + BizName: bizName, + WecomUserId: row.WecomUserId, + CreatedAt: row.CreatedAt.Format(), + }) + } + + return okResponse(map[string]any{ + "count": result.Count, + "items": items, + }), nil +} + +func (l *ItemsUsersLogic) fetchSaleNames(rows []dao.WecomUser) (map[int64]string, error) { + result := make(map[int64]string) + seen := make(map[int64]struct{}) + ids := make([]int64, 0) + for _, row := range rows { + if row.Type != dao.UserTypeSale || row.UserId < utils.NumberOne { + continue + } + if _, ok := seen[row.UserId]; ok { + continue + } + seen[row.UserId] = struct{}{} + ids = append(ids, row.UserId) + } + if len(ids) < utils.NumberOne { + return result, nil + } + + cli, err := rpcclient.Get(l.svcCtx.SaleSvcName) + if err != nil { + return nil, err + } + res, err := sale.NewSaleClient(cli.Conn()).NamesByIds(l.ctx, &sale.NamesByIdsReq{Ids: ids}) + if err != nil { + return nil, err + } + for _, item := range res.GetItems() { + result[item.GetId()] = item.GetName() + } + return result, nil +} diff --git a/services/wecom/internal/logic/response.go b/services/wecom/internal/logic/response.go new file mode 100644 index 0000000..46e3f3d --- /dev/null +++ b/services/wecom/internal/logic/response.go @@ -0,0 +1,38 @@ +package logic + +import ( + "lone-services/pkg/utils" + wecom "lone-services/rpc/wecom/pb" + + jsoniter "github.com/json-iterator/go" +) + +func okResponse(data any) *wecom.Response { + buf, _ := jsoniter.Marshal(data) + return &wecom.Response{ + Code: utils.Ok.Code, + Msg: utils.Ok.Msg, + Data: string(buf), + } +} + +func failResponse(status utils.Status) *wecom.Response { + return &wecom.Response{ + Code: status.Code, + Msg: status.Msg, + } +} + +func outResponse(status utils.Status, msg string) *wecom.Response { + return &wecom.Response{ + Code: status.Code, + Msg: msg, + } +} + +func failWecomHTTP() *wecom.Response { + return &wecom.Response{ + Code: int32(utils.ErrorWecomHttpError.GetCode()), + Msg: utils.ErrorWecomHttpError.GetMsg(), + } +} diff --git a/services/wecom/internal/logic/statusUserLogic.go b/services/wecom/internal/logic/statusUserLogic.go new file mode 100644 index 0000000..c13232f --- /dev/null +++ b/services/wecom/internal/logic/statusUserLogic.go @@ -0,0 +1,87 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + wecomuser "lone-services/pkg/wecom/user" + wecom "lone-services/rpc/wecom/pb" + "lone-services/services/wecom/internal/client" + "lone-services/services/wecom/internal/dao" + "lone-services/services/wecom/internal/model" + "lone-services/services/wecom/internal/svc" + "lone-services/services/wecom/validator" + + "github.com/zeromicro/go-zero/core/logx" +) + +type StatusUserLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewStatusUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *StatusUserLogic { + return &StatusUserLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *StatusUserLogic) StatusUser(in *wecom.StatusUserReq) (*wecom.Response, error) { + var req validator.StatusUserValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return outResponse(utils.ErrorParams, msg), nil + } + + m := model.UserModel{}.Init() + var row dao.WecomUser + if err := m.GetOne(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(req.Id, utils.NumberTen)}, + }, &row); err != nil { + l.Errorf("wecom status user get: %v", err) + return failResponse(utils.Fail), nil + } + if row.Id < utils.NumberOne { + return failResponse(utils.ErrorNotFund), nil + } + + userStatus := uint8(req.Status) + plainMobile, err := utils.DecryptPhone(row.Mobile) + if err != nil { + l.Errorf("wecom status decrypt mobile: %v", err) + return failResponse(utils.Fail), nil + } + + if !utils.GetConfigBool("wecom.skip") { + updateReq := wecomuser.UpdateRequest{ + UserId: row.WecomUserId, + Name: row.Name, + Mobile: plainMobile, + Enable: statusEnable(userStatus), + } + if row.Avatar != utils.StringEmpty { + updateReq.AvatarMediaID = row.Avatar + } + if err := client.NewContactClient().Update(l.ctx, updateReq); err != nil { + l.Errorf("wecom http status user: %v", err) + return failWecomHTTP(), nil + } + } + + if _, err := m.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(row.Id, utils.NumberTen)}, + }, map[string]interface{}{ + "status": userStatus, + "updated_at": utils.Now(), + }); err != nil { + l.Errorf("wecom status user db: %v", err) + return failResponse(utils.Fail), nil + } + + return okResponse(nil), nil +} diff --git a/services/wecom/internal/logic/updateUserLogic.go b/services/wecom/internal/logic/updateUserLogic.go new file mode 100644 index 0000000..29acb79 --- /dev/null +++ b/services/wecom/internal/logic/updateUserLogic.go @@ -0,0 +1,127 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + wecomuser "lone-services/pkg/wecom/user" + wecom "lone-services/rpc/wecom/pb" + "lone-services/services/wecom/internal/client" + "lone-services/services/wecom/internal/dao" + "lone-services/services/wecom/internal/model" + "lone-services/services/wecom/internal/svc" + "lone-services/services/wecom/validator" + + "github.com/zeromicro/go-zero/core/logx" +) + +type UpdateUserLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewUpdateUserLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateUserLogic { + return &UpdateUserLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *UpdateUserLogic) UpdateUser(in *wecom.UpdateUserReq) (*wecom.Response, error) { + var req validator.UpdateUserValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return outResponse(utils.ErrorParams, msg), nil + } + if req.Id < utils.NumberOne && (req.UserId < utils.NumberOne || req.Type < utils.NumberOne) { + return outResponse(utils.ErrorParams, "请传入 id 或 user_id+type"), nil + } + + m := model.UserModel{}.Init() + var row dao.WecomUser + w := modelbase.Params{Order: "id desc"} + if req.Id > utils.NumberZero { + w.Eq = map[string]string{"id": strconv.FormatInt(req.Id, utils.NumberTen)} + } else { + w.Eq = map[string]string{ + "user_id": strconv.FormatInt(req.UserId, utils.NumberTen), + "type": strconv.FormatUint(uint64(req.Type), utils.NumberTen), + } + } + if err := m.GetOne(w, &row); err != nil { + l.Errorf("wecom update user get: %v", err) + return failResponse(utils.Fail), nil + } + if row.Id < utils.NumberOne { + return failResponse(utils.ErrorNotFund), nil + } + + name := row.Name + if req.Name != utils.StringEmpty { + name = req.Name + } + avatar := row.Avatar + if req.Avatar != utils.StringEmpty { + avatar = req.Avatar + } + typeVal := row.Type + if req.Type > utils.NumberZero { + typeVal = row.Type + } + userStatus := resolveStatus(req.Status, row.Status) + + plainMobile := utils.StringEmpty + encryptMobile := row.Mobile + if req.Mobile != utils.StringEmpty { + enc, err := utils.EncryptPhone(req.Mobile) + if err != nil { + l.Errorf("wecom update encrypt mobile: %v", err) + return failResponse(utils.Fail), nil + } + encryptMobile = enc + plainMobile = req.Mobile + } else { + dec, err := utils.DecryptPhone(row.Mobile) + if err != nil { + l.Errorf("wecom update decrypt mobile: %v", err) + return failResponse(utils.Fail), nil + } + plainMobile = dec + } + + if !utils.GetConfigBool("wecom.skip") { + updateReq := wecomuser.UpdateRequest{ + UserId: row.WecomUserId, + Name: name, + Mobile: plainMobile, + Enable: statusEnable(userStatus), + } + if avatar != utils.StringEmpty { + updateReq.AvatarMediaID = avatar + } + if err := client.NewContactClient().Update(l.ctx, updateReq); err != nil { + l.Errorf("wecom http update user: %v", err) + return failWecomHTTP(), nil + } + } + + if _, err := m.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(row.Id, utils.NumberTen)}, + }, map[string]interface{}{ + "name": name, + "mobile": encryptMobile, + "avatar": avatar, + "status": userStatus, + "type": typeVal, + "updated_at": utils.Now(), + }); err != nil { + l.Errorf("wecom update user db: %v", err) + return failResponse(utils.Fail), nil + } + + return okResponse(nil), nil +} diff --git a/services/wecom/internal/model/user.go b/services/wecom/internal/model/user.go new file mode 100644 index 0000000..507ae89 --- /dev/null +++ b/services/wecom/internal/model/user.go @@ -0,0 +1,23 @@ +package model + +import ( + "lone-services/pkg/modelbase" + "lone-services/services/wecom/internal/dao" +) + +type UserModel struct { + modelbase.Base +} + +func (m UserModel) TableName() string { + return modelbase.Prefix() + "users" +} + +func (m UserModel) Init() UserModel { + m.Table = m.TableName() + return m +} + +func (m UserModel) Create(data *dao.WecomUser) error { + return m.Base.Create(data) +} diff --git a/services/wecom/internal/server/wecomServer.go b/services/wecom/internal/server/wecomServer.go new file mode 100644 index 0000000..166a519 --- /dev/null +++ b/services/wecom/internal/server/wecomServer.go @@ -0,0 +1,49 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.10.1 +// Source: wecom.proto + +package server + +import ( + "context" + + "lone-services/rpc/wecom/pb" + "lone-services/services/wecom/internal/logic" + "lone-services/services/wecom/internal/svc" +) + +type WecomServer struct { + svcCtx *svc.ServiceContext + wecom.UnimplementedWecomServer +} + +func NewWecomServer(svcCtx *svc.ServiceContext) *WecomServer { + return &WecomServer{ + svcCtx: svcCtx, + } +} + +func (s *WecomServer) ItemsUsers(ctx context.Context, in *wecom.ItemsUserReq) (*wecom.Response, error) { + l := logic.NewItemsUsersLogic(ctx, s.svcCtx) + return l.ItemsUsers(in) +} + +func (s *WecomServer) CreateUser(ctx context.Context, in *wecom.CreateUserReq) (*wecom.Response, error) { + l := logic.NewCreateUserLogic(ctx, s.svcCtx) + return l.CreateUser(in) +} + +func (s *WecomServer) UpdateUser(ctx context.Context, in *wecom.UpdateUserReq) (*wecom.Response, error) { + l := logic.NewUpdateUserLogic(ctx, s.svcCtx) + return l.UpdateUser(in) +} + +func (s *WecomServer) StatusUser(ctx context.Context, in *wecom.StatusUserReq) (*wecom.Response, error) { + l := logic.NewStatusUserLogic(ctx, s.svcCtx) + return l.StatusUser(in) +} + +func (s *WecomServer) DeleteUser(ctx context.Context, in *wecom.DeleteUserReq) (*wecom.Response, error) { + l := logic.NewDeleteUserLogic(ctx, s.svcCtx) + return l.DeleteUser(in) +} diff --git a/services/wecom/internal/svc/serviceContext.go b/services/wecom/internal/svc/serviceContext.go new file mode 100644 index 0000000..c2493e6 --- /dev/null +++ b/services/wecom/internal/svc/serviceContext.go @@ -0,0 +1,28 @@ +package svc + +import ( + "lone-services/pkg/utils" + "lone-services/services/wecom/internal/config" + + "github.com/zeromicro/go-zero/core/logx" + "gorm.io/gorm" +) + +type ServiceContext struct { + Config config.Config + DB *gorm.DB + SaleSvcName string +} + +func NewServiceContext(c config.Config, db *gorm.DB) *ServiceContext { + saleSvc := utils.GetConfigString("services.sale") + if saleSvc == utils.StringEmpty { + logx.Error("config services.sale empty") + } + + return &ServiceContext{ + Config: c, + DB: db, + SaleSvcName: saleSvc, + } +} diff --git a/services/wecom/run.toml b/services/wecom/run.toml new file mode 100644 index 0000000..035630f --- /dev/null +++ b/services/wecom/run.toml @@ -0,0 +1,78 @@ +[base] + login_out_time=43200 #api接口超时时间分 + login_refresh_out_time=83200 + name = "wecom-service" + listenOn = "0.0.0.0:10800" + mode = "dev" +[log] + path = "logs" + serviceName = "wecom-service" + mode = "file" + encoding = "plain" + level = "info" + keepDays = 7 + maxSize = 50 + maxBackups = 5 + compress = false + +[wecom] + skip = false + department_id = 1 + corp_id = "wwb83e24dcf1946e3e" + icon_url = "https://skin-test-api.ailuowan.com/images/20260509/1778321760824864613.png" + + review_agent_id = 1000002 + notify_agent_id = 1000003 + + review_secret = "bgLqhoLm_SFnEpxvOYX3RZKNMJbPYE9dc_bIHhntuDw" + contact_secret = "2wXeMVHIvq_Z9X-7h4xtfwH9JXJuMpnZa9gKOKlmGkg" + notify_secret = "7oFeb8MFpJG-vWLxce6qj2mqzwZAiSKmR9Qwrnr57vA" + + # 直接审批人 + product_reviewer_ids = "user962uMB|user69TMqt|user97vhfF" + store_reviewer_ids = "user962uMB|user69TMqt|user97vhfF" + address_reviewer_ids = "user962uMB|user69TMqt|user97vhfF" + + # 一审 + product_notify_first_ids = "user962uMB|user69TMqt|user97vhfF" + store_notify_first_ids = "user962uMB|user69TMqt|user97vhfF" + address_notify_first_ids = "user962uMB|user69TMqt|user97vhfF" + + # 二审 + product_notify_second_ids = "user962uMB|user69TMqt|user97vhfF" + store_notify_second_ids = "user962uMB|user69TMqt|user97vhfF" + address_notify_second_ids = "user962uMB|user69TMqt|user97vhfF" + + # 小程序添加销售 + sales_notify_apply_ids = "user962uMB|user69TMqt|user97vhfF" + +[mysql] + host = '39.106.171.204' + port = 33066 + user = 'root' + password = 'MOLXRZNOU4Y4' + database = 'dms-wecom' + charset = 'utf8mb4' + prefix = '' + debug = true +[mysql_read] + host = '39.106.171.204' + port = 33066 + user = 'root' + password = 'MOLXRZNOU4Y4' + database = 'dms-wecom' + charset = 'utf8mb4' + prefix = '' + +[redis] + host = '39.106.171.204' + password = 'lLMLcuPpzSj' + port = 6379 + db = 0 + +[encrypt] + data_key = "u2t9T3luZtoRfhBstkFN6TiIMW38BA8a" + +[services] + sale = "sale-service" + admin = "admin-service" diff --git a/services/wecom/validator/validator.go b/services/wecom/validator/validator.go new file mode 100644 index 0000000..02ade5f --- /dev/null +++ b/services/wecom/validator/validator.go @@ -0,0 +1,91 @@ +package validator + +import "lone-services/pkg/validate" + +type CreateUserValidator struct { + UserId int64 `validate:"required,min=1"` + Type uint32 `validate:"required,oneof=1 2"` + Name string `validate:"required,max=64"` + Mobile string `validate:"required,mobile"` + Avatar string `validate:"omitempty,max=1024"` + Status uint32 `validate:"omitempty,oneof=1 2"` +} + +func (p CreateUserValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "UserId.required": "业务用户ID不能为空", + "UserId.min": "业务用户ID无效", + "Type.required": "类型不能为空", + "Type.oneof": "类型不对,1:管理员 2:销售", + "Name.required": "姓名不能为空", + "Name.max": "姓名长度不能超过64", + "Mobile.required": "手机号不能为空", + "Mobile.mobile": "手机号格式错误", + "Avatar.max": "头像长度不能超过1024", + "Status.oneof": "状态不对,1:启用 2:禁用", + } +} + +type UpdateUserValidator struct { + Id int64 `validate:"omitempty,min=1"` + UserId int64 `validate:"omitempty,min=1"` + Type uint32 `validate:"omitempty,oneof=1 2"` + Name string `validate:"omitempty,max=64"` + Mobile string `validate:"omitempty,mobile"` + Avatar string `validate:"omitempty,max=1024"` + Status uint32 `validate:"omitempty,oneof=1 2"` +} + +func (p UpdateUserValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "Id.min": "ID无效", + "UserId.min": "业务用户ID无效", + "Type.oneof": "类型不对,1:管理员 2:销售", + "Name.max": "姓名长度不能超过64", + "Mobile.mobile": "手机号格式错误", + "Avatar.max": "头像长度不能超过1024", + "Status.oneof": "状态不对,1:启用 2:禁用", + } +} + +type ItemsUserValidator struct { + Page uint32 `validate:"omitempty,min=1"` + PageSize uint32 `validate:"omitempty,min=1,max=100"` + Name string `validate:"omitempty,max=64"` + Type uint32 `validate:"omitempty,oneof=1 2"` +} + +func (p ItemsUserValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "Page.min": "页码最小为1", + "PageSize.min": "每页数量最小为1", + "PageSize.max": "每页数量最大为100", + "Name.max": "名称长度不能超过64", + "Type.oneof": "类型不对,1:管理员 2:销售", + } +} + +type StatusUserValidator struct { + Id int64 `validate:"required,min=1"` + Status uint32 `validate:"required,oneof=1 2"` +} + +func (p StatusUserValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "Id.required": "ID不能为空", + "Id.min": "ID无效", + "Status.required": "状态不能为空", + "Status.oneof": "状态不对,1:启用 2:禁用", + } +} + +type DeleteUserValidator struct { + Id int64 `validate:"required,min=1"` +} + +func (p DeleteUserValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "Id.required": "ID不能为空", + "Id.min": "ID无效", + } +} diff --git a/services/wecom/wecom.go b/services/wecom/wecom.go new file mode 100644 index 0000000..16145ab --- /dev/null +++ b/services/wecom/wecom.go @@ -0,0 +1,152 @@ +package main + +import ( + "flag" + "net" + "os" + "strconv" + + "lone-services/pkg/discovery" + "lone-services/pkg/modelbase" + "lone-services/pkg/mysql" + "lone-services/pkg/redis" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + wecom "lone-services/rpc/wecom/pb" + "lone-services/services/wecom/internal/config" + "lone-services/services/wecom/internal/server" + "lone-services/services/wecom/internal/svc" + + "github.com/zeromicro/go-zero/core/conf" + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/core/service" + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" +) + +var configFile = flag.String("f", "etc/wecom.yaml", "the config file") + +func main() { + flag.Parse() + var c config.Config + conf.MustLoad(*configFile, &c) + + nacosParam := utils.NacosConfig{ + Hosts: c.Nacos.Hosts, + NamespaceId: c.Nacos.NamespaceId, + Group: c.Nacos.Group, + ConfigID: c.Nacos.ConfigID, + } + utils.InitConfig(nacosParam) + + logConf := logx.LogConf{ + ServiceName: utils.GetConfigString("log.serviceName"), + Mode: utils.GetConfigString("log.mode"), + Encoding: utils.GetConfigString("log.encoding"), + Level: utils.GetConfigString("log.level"), + Path: utils.GetConfigString("log.path"), + KeepDays: utils.GetConfigInt("log.keepDays"), + MaxSize: utils.GetConfigInt("log.maxSize"), + MaxBackups: utils.GetConfigInt("log.maxBackups"), + Compress: utils.GetConfigBool("log.compress"), + } + logx.SetUp(logConf) + logx.AddWriter(logx.NewWriter(os.Stdout)) + + listenOn := utils.GetConfigString("base.listenOn") + mode := utils.GetConfigString("base.mode") + serviceName := utils.GetConfigString("base.name") + + if err := discovery.Init(discovery.Config{ + Hosts: c.Nacos.Hosts, + NamespaceId: c.Nacos.NamespaceId, + Group: c.Nacos.Group, + }); err != nil { + logx.Errorf("nacos init: %v", err) + os.Exit(1) + } + + port, err := listenPort(listenOn) + if err != nil { + logx.Errorf("parse ListenOn: %v", err) + os.Exit(1) + } + + if err := discovery.Register(discovery.Instance{ + ServiceName: serviceName, + IP: c.Nacos.RegisterIP, + Port: port, + Group: c.Nacos.Group, + }); err != nil { + logx.Errorf("nacos register: %v", err) + os.Exit(1) + } + logx.Infof("服务注册成功: %s:%d", c.Nacos.RegisterIP, port) + + defer func() { + if err := discovery.Deregister(); err != nil { + logx.Errorf("nacos deregister: %v", err) + } + }() + + db, err := mysql.New(mysql.Config{ + Host: utils.GetConfigString("mysql.host"), + Port: utils.GetConfigInt("mysql.port"), + User: utils.GetConfigString("mysql.user"), + Password: utils.GetConfigString("mysql.password"), + Database: utils.GetConfigString("mysql.database"), + Charset: utils.GetConfigString("mysql.charset"), + Prefix: utils.GetConfigString("mysql.prefix"), + ReadHost: utils.GetConfigString("mysql_read.host"), + ReadPort: utils.GetConfigInt("mysql_read.port"), + ReadUser: utils.GetConfigString("mysql_read.user"), + ReadPassword: utils.GetConfigString("mysql_read.password"), + ReadDatabase: utils.GetConfigString("mysql_read.database"), + }) + if err != nil { + logx.Errorf("mysql init: %v", err) + os.Exit(1) + } + + if err := redis.Init(redis.Config{ + Host: utils.GetConfigString("redis.host"), + Port: utils.GetConfigInt("redis.port"), + Password: utils.GetConfigString("redis.password"), + DB: utils.GetConfigInt("redis.db"), + }); err != nil { + logx.Errorf("redis init: %v", err) + os.Exit(1) + } + + debug := utils.GetConfigBool("mysql.debug") + modelbase.Init(db, modelbase.Config{Prefix: utils.GetConfigString("mysql.prefix"), Debug: debug}) + + rpcConf := zrpc.RpcServerConf{ + ListenOn: listenOn, + } + rpcConf.Mode = mode + + ctx := svc.NewServiceContext(c, db) + + s := zrpc.MustNewServer(rpcConf, func(grpcServer *grpc.Server) { + wecom.RegisterWecomServer(grpcServer, server.NewWecomServer(ctx)) + if mode == service.DevMode || mode == service.TestMode { + reflection.Register(grpcServer) + } + }) + defer s.Stop() + + s.AddUnaryInterceptors(validate.UnaryServerInterceptor(validate.MustNew())) + + logx.Infof("Starting rpc server at %s...", listenOn) + s.Start() +} + +func listenPort(listenOn string) (uint64, error) { + _, portStr, err := net.SplitHostPort(listenOn) + if err != nil { + return 0, err + } + return strconv.ParseUint(portStr, 10, 64) +} diff --git a/services/wecom/wecomClient/wecom.go b/services/wecom/wecomClient/wecom.go new file mode 100644 index 0000000..81dc97c --- /dev/null +++ b/services/wecom/wecomClient/wecom.go @@ -0,0 +1,66 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.10.1 +// Source: wecom.proto + +package wecomClient + +import ( + "context" + + "lone-services/rpc/wecom/pb" + + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" +) + +type ( + CreateUserReq = wecom.CreateUserReq + DeleteUserReq = wecom.DeleteUserReq + ItemsUserReq = wecom.ItemsUserReq + Response = wecom.Response + StatusUserReq = wecom.StatusUserReq + UpdateUserReq = wecom.UpdateUserReq + + Wecom interface { + ItemsUsers(ctx context.Context, in *ItemsUserReq, opts ...grpc.CallOption) (*Response, error) + CreateUser(ctx context.Context, in *CreateUserReq, opts ...grpc.CallOption) (*Response, error) + UpdateUser(ctx context.Context, in *UpdateUserReq, opts ...grpc.CallOption) (*Response, error) + StatusUser(ctx context.Context, in *StatusUserReq, opts ...grpc.CallOption) (*Response, error) + DeleteUser(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*Response, error) + } + + defaultWecom struct { + cli zrpc.Client + } +) + +func NewWecom(cli zrpc.Client) Wecom { + return &defaultWecom{ + cli: cli, + } +} + +func (m *defaultWecom) ItemsUsers(ctx context.Context, in *ItemsUserReq, opts ...grpc.CallOption) (*Response, error) { + client := wecom.NewWecomClient(m.cli.Conn()) + return client.ItemsUsers(ctx, in, opts...) +} + +func (m *defaultWecom) CreateUser(ctx context.Context, in *CreateUserReq, opts ...grpc.CallOption) (*Response, error) { + client := wecom.NewWecomClient(m.cli.Conn()) + return client.CreateUser(ctx, in, opts...) +} + +func (m *defaultWecom) UpdateUser(ctx context.Context, in *UpdateUserReq, opts ...grpc.CallOption) (*Response, error) { + client := wecom.NewWecomClient(m.cli.Conn()) + return client.UpdateUser(ctx, in, opts...) +} + +func (m *defaultWecom) StatusUser(ctx context.Context, in *StatusUserReq, opts ...grpc.CallOption) (*Response, error) { + client := wecom.NewWecomClient(m.cli.Conn()) + return client.StatusUser(ctx, in, opts...) +} + +func (m *defaultWecom) DeleteUser(ctx context.Context, in *DeleteUserReq, opts ...grpc.CallOption) (*Response, error) { + client := wecom.NewWecomClient(m.cli.Conn()) + return client.DeleteUser(ctx, in, opts...) +}