diff --git a/pkg/utils/time.go b/pkg/utils/time.go index c5f224e..1011801 100644 --- a/pkg/utils/time.go +++ b/pkg/utils/time.go @@ -217,3 +217,23 @@ func ParseCustomTime(s string) (CustomTime, error) { } return ct, nil } + +// ParseDateOnly 解析 YYYY-MM-DD;空串返回零值(写入 DB 为 NULL) +func ParseDateOnly(s string) (CustomTime, error) { + if s == "" { + return CustomTime{}, nil + } + t, err := time.ParseInLocation(time.DateOnly, s, AsiaShanghai) + if err != nil { + return CustomTime{}, fmt.Errorf("invalid date format: %s", s) + } + return CustomTime{Time: t}, nil +} + +// DateString 返回 YYYY-MM-DD;零值返回空串 +func (ct CustomTime) DateString() string { + if ct.IsZero() { + return "" + } + return ct.Time.Format(time.DateOnly) +} diff --git a/rpc/user/pb/user.pb.go b/rpc/user/pb/user.pb.go index 0a1316c..73d4023 100644 --- a/rpc/user/pb/user.pb.go +++ b/rpc/user/pb/user.pb.go @@ -7,12 +7,13 @@ package user 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" + + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" ) const ( @@ -419,6 +420,564 @@ func (x *UserStatusReq) GetStatus() int32 { return 0 } +type EnsureBizIdentityReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + Mobile string `protobuf:"bytes,1,opt,name=mobile,proto3" json:"mobile,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` + Gender uint32 `protobuf:"varint,4,opt,name=gender,proto3" json:"gender,omitempty"` + Birthday string `protobuf:"bytes,5,opt,name=birthday,proto3" json:"birthday,omitempty"` + ClientCode string `protobuf:"bytes,6,opt,name=client_code,json=clientCode,proto3" json:"client_code,omitempty"` // 如 sale_beauty / store_beauty / store_pos + CredentialType string `protobuf:"bytes,7,opt,name=credential_type,json=credentialType,proto3" json:"credential_type,omitempty"` // password / wecom / openid ... + Identifier string `protobuf:"bytes,8,opt,name=identifier,proto3" json:"identifier,omitempty"` // 登录标识;空则默认用手机号 + Secret string `protobuf:"bytes,9,opt,name=secret,proto3" json:"secret,omitempty"` // 密码明文等,服务端哈希后入库 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureBizIdentityReq) Reset() { + *x = EnsureBizIdentityReq{} + mi := &file_user_user_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureBizIdentityReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureBizIdentityReq) ProtoMessage() {} + +func (x *EnsureBizIdentityReq) ProtoReflect() protoreflect.Message { + mi := &file_user_user_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) +} + +func (*EnsureBizIdentityReq) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{6} +} + +func (x *EnsureBizIdentityReq) GetMobile() string { + if x != nil { + return x.Mobile + } + return "" +} + +func (x *EnsureBizIdentityReq) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *EnsureBizIdentityReq) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +func (x *EnsureBizIdentityReq) GetGender() uint32 { + if x != nil { + return x.Gender + } + return 0 +} + +func (x *EnsureBizIdentityReq) GetBirthday() string { + if x != nil { + return x.Birthday + } + return "" +} + +func (x *EnsureBizIdentityReq) GetClientCode() string { + if x != nil { + return x.ClientCode + } + return "" +} + +func (x *EnsureBizIdentityReq) GetCredentialType() string { + if x != nil { + return x.CredentialType + } + return "" +} + +func (x *EnsureBizIdentityReq) GetIdentifier() string { + if x != nil { + return x.Identifier + } + return "" +} + +func (x *EnsureBizIdentityReq) GetSecret() string { + if x != nil { + return x.Secret + } + return "" +} + +type EnsureBizIdentityData struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Created bool `protobuf:"varint,2,opt,name=created,proto3" json:"created,omitempty"` // 是否新建了 users + Mobile string `protobuf:"bytes,3,opt,name=mobile,proto3" json:"mobile,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureBizIdentityData) Reset() { + *x = EnsureBizIdentityData{} + mi := &file_user_user_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureBizIdentityData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureBizIdentityData) ProtoMessage() {} + +func (x *EnsureBizIdentityData) ProtoReflect() protoreflect.Message { + mi := &file_user_user_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 EnsureBizIdentityData.ProtoReflect.Descriptor instead. +func (*EnsureBizIdentityData) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{7} +} + +func (x *EnsureBizIdentityData) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *EnsureBizIdentityData) GetCreated() bool { + if x != nil { + return x.Created + } + return false +} + +func (x *EnsureBizIdentityData) GetMobile() string { + if x != nil { + return x.Mobile + } + return "" +} + +func (x *EnsureBizIdentityData) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// 内部:按 ids 批量查用户基础信息 +type UsersByIdsReq 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 *UsersByIdsReq) Reset() { + *x = UsersByIdsReq{} + mi := &file_user_user_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UsersByIdsReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UsersByIdsReq) ProtoMessage() {} + +func (x *UsersByIdsReq) ProtoReflect() protoreflect.Message { + mi := &file_user_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 UsersByIdsReq.ProtoReflect.Descriptor instead. +func (*UsersByIdsReq) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{8} +} + +func (x *UsersByIdsReq) GetIds() []int64 { + if x != nil { + return x.Ids + } + return nil +} + +type UserBriefItem struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Mobile string `protobuf:"bytes,2,opt,name=mobile,proto3" json:"mobile,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Avatar string `protobuf:"bytes,4,opt,name=avatar,proto3" json:"avatar,omitempty"` + Gender uint32 `protobuf:"varint,5,opt,name=gender,proto3" json:"gender,omitempty"` + Birthday string `protobuf:"bytes,6,opt,name=birthday,proto3" json:"birthday,omitempty"` + Status uint32 `protobuf:"varint,7,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UserBriefItem) Reset() { + *x = UserBriefItem{} + mi := &file_user_user_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UserBriefItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserBriefItem) ProtoMessage() {} + +func (x *UserBriefItem) ProtoReflect() protoreflect.Message { + mi := &file_user_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 UserBriefItem.ProtoReflect.Descriptor instead. +func (*UserBriefItem) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{9} +} + +func (x *UserBriefItem) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *UserBriefItem) GetMobile() string { + if x != nil { + return x.Mobile + } + return "" +} + +func (x *UserBriefItem) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UserBriefItem) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +func (x *UserBriefItem) GetGender() uint32 { + if x != nil { + return x.Gender + } + return 0 +} + +func (x *UserBriefItem) GetBirthday() string { + if x != nil { + return x.Birthday + } + return "" +} + +func (x *UserBriefItem) GetStatus() uint32 { + if x != nil { + return x.Status + } + return 0 +} + +type UsersByIdsData struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*UserBriefItem `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UsersByIdsData) Reset() { + *x = UsersByIdsData{} + mi := &file_user_user_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UsersByIdsData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UsersByIdsData) ProtoMessage() {} + +func (x *UsersByIdsData) ProtoReflect() protoreflect.Message { + mi := &file_user_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 UsersByIdsData.ProtoReflect.Descriptor instead. +func (*UsersByIdsData) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{10} +} + +func (x *UsersByIdsData) GetItems() []*UserBriefItem { + if x != nil { + return x.Items + } + return nil +} + +// 内部:修改用户手机号(同步凭证 identifier) +type UpdateMobileReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Mobile string `protobuf:"bytes,2,opt,name=mobile,proto3" json:"mobile,omitempty"` // 新手机号明文 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateMobileReq) Reset() { + *x = UpdateMobileReq{} + mi := &file_user_user_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateMobileReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateMobileReq) ProtoMessage() {} + +func (x *UpdateMobileReq) ProtoReflect() protoreflect.Message { + mi := &file_user_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 UpdateMobileReq.ProtoReflect.Descriptor instead. +func (*UpdateMobileReq) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{11} +} + +func (x *UpdateMobileReq) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *UpdateMobileReq) GetMobile() string { + if x != nil { + return x.Mobile + } + return "" +} + +type UpdateMobileData struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Mobile string `protobuf:"bytes,2,opt,name=mobile,proto3" json:"mobile,omitempty"` // 加密后手机号 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateMobileData) Reset() { + *x = UpdateMobileData{} + mi := &file_user_user_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateMobileData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateMobileData) ProtoMessage() {} + +func (x *UpdateMobileData) ProtoReflect() protoreflect.Message { + mi := &file_user_user_proto_msgTypes[12] + 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 UpdateMobileData.ProtoReflect.Descriptor instead. +func (*UpdateMobileData) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{12} +} + +func (x *UpdateMobileData) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *UpdateMobileData) GetMobile() string { + if x != nil { + return x.Mobile + } + return "" +} + +// 内部:撤销业务端开通(删除/禁用销售或门店时) +type RevokeBizClientReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ClientCode string `protobuf:"bytes,2,opt,name=client_code,json=clientCode,proto3" json:"client_code,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeBizClientReq) Reset() { + *x = RevokeBizClientReq{} + mi := &file_user_user_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeBizClientReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeBizClientReq) ProtoMessage() {} + +func (x *RevokeBizClientReq) ProtoReflect() protoreflect.Message { + mi := &file_user_user_proto_msgTypes[13] + 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 RevokeBizClientReq.ProtoReflect.Descriptor instead. +func (*RevokeBizClientReq) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{13} +} + +func (x *RevokeBizClientReq) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *RevokeBizClientReq) GetClientCode() string { + if x != nil { + return x.ClientCode + } + return "" +} + +type RevokeBizClientData struct { + state protoimpl.MessageState `protogen:"open.v1"` + Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RevokeBizClientData) Reset() { + *x = RevokeBizClientData{} + mi := &file_user_user_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RevokeBizClientData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RevokeBizClientData) ProtoMessage() {} + +func (x *RevokeBizClientData) ProtoReflect() protoreflect.Message { + mi := &file_user_user_proto_msgTypes[14] + 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 RevokeBizClientData.ProtoReflect.Descriptor instead. +func (*RevokeBizClientData) Descriptor() ([]byte, []int) { + return file_user_user_proto_rawDescGZIP(), []int{14} +} + +func (x *RevokeBizClientData) GetRevoked() bool { + if x != nil { + return x.Revoked + } + return false +} + var File_user_user_proto protoreflect.FileDescriptor const file_user_user_proto_rawDesc = "" + @@ -451,14 +1010,61 @@ const file_user_user_proto_rawDesc = "" + "\x04size\x18\x03 \x01(\x05R\x04size\"7\n" + "\rUserStatusReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" + - "\x06status\x18\x02 \x01(\x05R\x06status2\xa8\x03\n" + + "\x06status\x18\x02 \x01(\x05R\x06status\"\x90\x02\n" + + "\x14EnsureBizIdentityReq\x12\x16\n" + + "\x06mobile\x18\x01 \x01(\tR\x06mobile\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x16\n" + + "\x06avatar\x18\x03 \x01(\tR\x06avatar\x12\x16\n" + + "\x06gender\x18\x04 \x01(\rR\x06gender\x12\x1a\n" + + "\bbirthday\x18\x05 \x01(\tR\bbirthday\x12\x1f\n" + + "\vclient_code\x18\x06 \x01(\tR\n" + + "clientCode\x12'\n" + + "\x0fcredential_type\x18\a \x01(\tR\x0ecredentialType\x12\x1e\n" + + "\n" + + "identifier\x18\b \x01(\tR\n" + + "identifier\x12\x16\n" + + "\x06secret\x18\t \x01(\tR\x06secret\"v\n" + + "\x15EnsureBizIdentityData\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x18\n" + + "\acreated\x18\x02 \x01(\bR\acreated\x12\x16\n" + + "\x06mobile\x18\x03 \x01(\tR\x06mobile\x12\x12\n" + + "\x04name\x18\x04 \x01(\tR\x04name\"!\n" + + "\rUsersByIdsReq\x12\x10\n" + + "\x03ids\x18\x01 \x03(\x03R\x03ids\"\xaf\x01\n" + + "\rUserBriefItem\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" + + "\x06mobile\x18\x02 \x01(\tR\x06mobile\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x16\n" + + "\x06avatar\x18\x04 \x01(\tR\x06avatar\x12\x16\n" + + "\x06gender\x18\x05 \x01(\rR\x06gender\x12\x1a\n" + + "\bbirthday\x18\x06 \x01(\tR\bbirthday\x12\x16\n" + + "\x06status\x18\a \x01(\rR\x06status\";\n" + + "\x0eUsersByIdsData\x12)\n" + + "\x05items\x18\x01 \x03(\v2\x13.user.UserBriefItemR\x05items\"B\n" + + "\x0fUpdateMobileReq\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x16\n" + + "\x06mobile\x18\x02 \x01(\tR\x06mobile\"C\n" + + "\x10UpdateMobileData\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x16\n" + + "\x06mobile\x18\x02 \x01(\tR\x06mobile\"N\n" + + "\x12RevokeBizClientReq\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x1f\n" + + "\vclient_code\x18\x02 \x01(\tR\n" + + "clientCode\"/\n" + + "\x13RevokeBizClientData\x12\x18\n" + + "\arevoked\x18\x01 \x01(\bR\arevoked2\xb6\x05\n" + "\x04User\x12O\n" + "\bRegister\x12\x11.user.RegisterReq\x1a\x0e.user.Response\" \x82\xd3\xe4\x93\x02\x1a:\x01*\"\x15/customer/v3/register\x12`\n" + "\x0eRegisterByUser\x12\x17.user.RegisterByUserReq\x1a\x0e.user.Response\"%\x82\xd3\xe4\x93\x02\x1f:\x01*\"\x1a/customer/v3/register/user\x12F\n" + "\x05Login\x12\x0e.user.LoginReq\x1a\x0e.user.Response\"\x1d\x82\xd3\xe4\x93\x02\x17:\x01*\"\x12/customer/v3/login\x12P\n" + "\tUserItems\x12\x12.user.UserItemsReq\x1a\x0e.user.Response\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\x12\x14/admin/v3/user/items\x12S\n" + "\n" + - "UserStatus\x12\x13.user.UserStatusReq\x1a\x0e.user.Response\" \x82\xd3\xe4\x93\x02\x1a:\x01*\x1a\x15/admin/v3/user/statusB\x18Z\x16lone-services/rpc/userb\x06proto3" + "UserStatus\x12\x13.user.UserStatusReq\x1a\x0e.user.Response\" \x82\xd3\xe4\x93\x02\x1a:\x01*\x1a\x15/admin/v3/user/status\x12L\n" + + "\x11EnsureBizIdentity\x12\x1a.user.EnsureBizIdentityReq\x1a\x1b.user.EnsureBizIdentityData\x127\n" + + "\n" + + "UsersByIds\x12\x13.user.UsersByIdsReq\x1a\x14.user.UsersByIdsData\x12=\n" + + "\fUpdateMobile\x12\x15.user.UpdateMobileReq\x1a\x16.user.UpdateMobileData\x12F\n" + + "\x0fRevokeBizClient\x12\x18.user.RevokeBizClientReq\x1a\x19.user.RevokeBizClientDataB\x18Z\x16lone-services/rpc/userb\x06proto3" var ( file_user_user_proto_rawDescOnce sync.Once @@ -472,31 +1078,49 @@ func file_user_user_proto_rawDescGZIP() []byte { return file_user_user_proto_rawDescData } -var file_user_user_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_user_user_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_user_user_proto_goTypes = []any{ - (*Response)(nil), // 0: user.Response - (*RegisterReq)(nil), // 1: user.RegisterReq - (*RegisterByUserReq)(nil), // 2: user.RegisterByUserReq - (*LoginReq)(nil), // 3: user.LoginReq - (*UserItemsReq)(nil), // 4: user.UserItemsReq - (*UserStatusReq)(nil), // 5: user.UserStatusReq + (*Response)(nil), // 0: user.Response + (*RegisterReq)(nil), // 1: user.RegisterReq + (*RegisterByUserReq)(nil), // 2: user.RegisterByUserReq + (*LoginReq)(nil), // 3: user.LoginReq + (*UserItemsReq)(nil), // 4: user.UserItemsReq + (*UserStatusReq)(nil), // 5: user.UserStatusReq + (*EnsureBizIdentityReq)(nil), // 6: user.EnsureBizIdentityReq + (*EnsureBizIdentityData)(nil), // 7: user.EnsureBizIdentityData + (*UsersByIdsReq)(nil), // 8: user.UsersByIdsReq + (*UserBriefItem)(nil), // 9: user.UserBriefItem + (*UsersByIdsData)(nil), // 10: user.UsersByIdsData + (*UpdateMobileReq)(nil), // 11: user.UpdateMobileReq + (*UpdateMobileData)(nil), // 12: user.UpdateMobileData + (*RevokeBizClientReq)(nil), // 13: user.RevokeBizClientReq + (*RevokeBizClientData)(nil), // 14: user.RevokeBizClientData } var file_user_user_proto_depIdxs = []int32{ - 1, // 0: user.User.Register:input_type -> user.RegisterReq - 2, // 1: user.User.RegisterByUser:input_type -> user.RegisterByUserReq - 3, // 2: user.User.Login:input_type -> user.LoginReq - 4, // 3: user.User.UserItems:input_type -> user.UserItemsReq - 5, // 4: user.User.UserStatus:input_type -> user.UserStatusReq - 0, // 5: user.User.Register:output_type -> user.Response - 0, // 6: user.User.RegisterByUser:output_type -> user.Response - 0, // 7: user.User.Login:output_type -> user.Response - 0, // 8: user.User.UserItems:output_type -> user.Response - 0, // 9: user.User.UserStatus:output_type -> user.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 + 9, // 0: user.UsersByIdsData.items:type_name -> user.UserBriefItem + 1, // 1: user.User.Register:input_type -> user.RegisterReq + 2, // 2: user.User.RegisterByUser:input_type -> user.RegisterByUserReq + 3, // 3: user.User.Login:input_type -> user.LoginReq + 4, // 4: user.User.UserItems:input_type -> user.UserItemsReq + 5, // 5: user.User.UserStatus:input_type -> user.UserStatusReq + 6, // 6: user.User.EnsureBizIdentity:input_type -> user.EnsureBizIdentityReq + 8, // 7: user.User.UsersByIds:input_type -> user.UsersByIdsReq + 11, // 8: user.User.UpdateMobile:input_type -> user.UpdateMobileReq + 13, // 9: user.User.RevokeBizClient:input_type -> user.RevokeBizClientReq + 0, // 10: user.User.Register:output_type -> user.Response + 0, // 11: user.User.RegisterByUser:output_type -> user.Response + 0, // 12: user.User.Login:output_type -> user.Response + 0, // 13: user.User.UserItems:output_type -> user.Response + 0, // 14: user.User.UserStatus:output_type -> user.Response + 7, // 15: user.User.EnsureBizIdentity:output_type -> user.EnsureBizIdentityData + 10, // 16: user.User.UsersByIds:output_type -> user.UsersByIdsData + 12, // 17: user.User.UpdateMobile:output_type -> user.UpdateMobileData + 14, // 18: user.User.RevokeBizClient:output_type -> user.RevokeBizClientData + 10, // [10:19] is the sub-list for method output_type + 1, // [1:10] 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_user_user_proto_init() } @@ -510,7 +1134,7 @@ func file_user_user_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_user_user_proto_rawDesc), len(file_user_user_proto_rawDesc)), NumEnums: 0, - NumMessages: 6, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, diff --git a/rpc/user/pb/user_grpc.pb.go b/rpc/user/pb/user_grpc.pb.go index 64ea666..fa25b04 100644 --- a/rpc/user/pb/user_grpc.pb.go +++ b/rpc/user/pb/user_grpc.pb.go @@ -19,11 +19,15 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - User_Register_FullMethodName = "/user.User/Register" - User_RegisterByUser_FullMethodName = "/user.User/RegisterByUser" - User_Login_FullMethodName = "/user.User/Login" - User_UserItems_FullMethodName = "/user.User/UserItems" - User_UserStatus_FullMethodName = "/user.User/UserStatus" + User_Register_FullMethodName = "/user.User/Register" + User_RegisterByUser_FullMethodName = "/user.User/RegisterByUser" + User_Login_FullMethodName = "/user.User/Login" + User_UserItems_FullMethodName = "/user.User/UserItems" + User_UserStatus_FullMethodName = "/user.User/UserStatus" + User_EnsureBizIdentity_FullMethodName = "/user.User/EnsureBizIdentity" + User_UsersByIds_FullMethodName = "/user.User/UsersByIds" + User_UpdateMobile_FullMethodName = "/user.User/UpdateMobile" + User_RevokeBizClient_FullMethodName = "/user.User/RevokeBizClient" ) // UserClient is the client API for User service. @@ -35,6 +39,14 @@ type UserClient interface { Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*Response, error) UserItems(ctx context.Context, in *UserItemsReq, opts ...grpc.CallOption) (*Response, error) UserStatus(ctx context.Context, in *UserStatusReq, opts ...grpc.CallOption) (*Response, error) + // 内部:业务创建销售/门店时调用 + EnsureBizIdentity(ctx context.Context, in *EnsureBizIdentityReq, opts ...grpc.CallOption) (*EnsureBizIdentityData, error) + // 内部:批量查用户基础信息 + UsersByIds(ctx context.Context, in *UsersByIdsReq, opts ...grpc.CallOption) (*UsersByIdsData, error) + // 内部:修改手机号 + UpdateMobile(ctx context.Context, in *UpdateMobileReq, opts ...grpc.CallOption) (*UpdateMobileData, error) + // 内部:撤销业务端开通 + RevokeBizClient(ctx context.Context, in *RevokeBizClientReq, opts ...grpc.CallOption) (*RevokeBizClientData, error) } type userClient struct { @@ -95,6 +107,46 @@ func (c *userClient) UserStatus(ctx context.Context, in *UserStatusReq, opts ... return out, nil } +func (c *userClient) EnsureBizIdentity(ctx context.Context, in *EnsureBizIdentityReq, opts ...grpc.CallOption) (*EnsureBizIdentityData, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(EnsureBizIdentityData) + err := c.cc.Invoke(ctx, User_EnsureBizIdentity_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userClient) UsersByIds(ctx context.Context, in *UsersByIdsReq, opts ...grpc.CallOption) (*UsersByIdsData, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UsersByIdsData) + err := c.cc.Invoke(ctx, User_UsersByIds_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userClient) UpdateMobile(ctx context.Context, in *UpdateMobileReq, opts ...grpc.CallOption) (*UpdateMobileData, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateMobileData) + err := c.cc.Invoke(ctx, User_UpdateMobile_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *userClient) RevokeBizClient(ctx context.Context, in *RevokeBizClientReq, opts ...grpc.CallOption) (*RevokeBizClientData, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RevokeBizClientData) + err := c.cc.Invoke(ctx, User_RevokeBizClient_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. @@ -104,6 +156,14 @@ type UserServer interface { Login(context.Context, *LoginReq) (*Response, error) UserItems(context.Context, *UserItemsReq) (*Response, error) UserStatus(context.Context, *UserStatusReq) (*Response, error) + // 内部:业务创建销售/门店时调用 + EnsureBizIdentity(context.Context, *EnsureBizIdentityReq) (*EnsureBizIdentityData, error) + // 内部:批量查用户基础信息 + UsersByIds(context.Context, *UsersByIdsReq) (*UsersByIdsData, error) + // 内部:修改手机号 + UpdateMobile(context.Context, *UpdateMobileReq) (*UpdateMobileData, error) + // 内部:撤销业务端开通 + RevokeBizClient(context.Context, *RevokeBizClientReq) (*RevokeBizClientData, error) mustEmbedUnimplementedUserServer() } @@ -129,6 +189,18 @@ func (UnimplementedUserServer) UserItems(context.Context, *UserItemsReq) (*Respo func (UnimplementedUserServer) UserStatus(context.Context, *UserStatusReq) (*Response, error) { return nil, status.Error(codes.Unimplemented, "method UserStatus not implemented") } +func (UnimplementedUserServer) EnsureBizIdentity(context.Context, *EnsureBizIdentityReq) (*EnsureBizIdentityData, error) { + return nil, status.Error(codes.Unimplemented, "method EnsureBizIdentity not implemented") +} +func (UnimplementedUserServer) UsersByIds(context.Context, *UsersByIdsReq) (*UsersByIdsData, error) { + return nil, status.Error(codes.Unimplemented, "method UsersByIds not implemented") +} +func (UnimplementedUserServer) UpdateMobile(context.Context, *UpdateMobileReq) (*UpdateMobileData, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateMobile not implemented") +} +func (UnimplementedUserServer) RevokeBizClient(context.Context, *RevokeBizClientReq) (*RevokeBizClientData, error) { + return nil, status.Error(codes.Unimplemented, "method RevokeBizClient not implemented") +} func (UnimplementedUserServer) mustEmbedUnimplementedUserServer() {} func (UnimplementedUserServer) testEmbeddedByValue() {} @@ -240,6 +312,78 @@ func _User_UserStatus_Handler(srv interface{}, ctx context.Context, dec func(int return interceptor(ctx, in, info, handler) } +func _User_EnsureBizIdentity_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EnsureBizIdentityReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServer).EnsureBizIdentity(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: User_EnsureBizIdentity_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServer).EnsureBizIdentity(ctx, req.(*EnsureBizIdentityReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _User_UsersByIds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UsersByIdsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServer).UsersByIds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: User_UsersByIds_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServer).UsersByIds(ctx, req.(*UsersByIdsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _User_UpdateMobile_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateMobileReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServer).UpdateMobile(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: User_UpdateMobile_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServer).UpdateMobile(ctx, req.(*UpdateMobileReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _User_RevokeBizClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RevokeBizClientReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UserServer).RevokeBizClient(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: User_RevokeBizClient_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UserServer).RevokeBizClient(ctx, req.(*RevokeBizClientReq)) + } + 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) @@ -267,6 +411,22 @@ var User_ServiceDesc = grpc.ServiceDesc{ MethodName: "UserStatus", Handler: _User_UserStatus_Handler, }, + { + MethodName: "EnsureBizIdentity", + Handler: _User_EnsureBizIdentity_Handler, + }, + { + MethodName: "UsersByIds", + Handler: _User_UsersByIds_Handler, + }, + { + MethodName: "UpdateMobile", + Handler: _User_UpdateMobile_Handler, + }, + { + MethodName: "RevokeBizClient", + Handler: _User_RevokeBizClient_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "user/user.proto", diff --git a/rpc/user/user.pb b/rpc/user/user.pb index f97c4bf..65945de 100644 Binary files a/rpc/user/user.pb and b/rpc/user/user.pb differ diff --git a/rpc/user/user.proto b/rpc/user/user.proto index 0b76625..460e4a3 100644 --- a/rpc/user/user.proto +++ b/rpc/user/user.proto @@ -50,6 +50,64 @@ message UserStatusReq { int32 status = 2; } +message EnsureBizIdentityReq { + string mobile = 1; + string name = 2; + string avatar = 3; + uint32 gender = 4; + string birthday = 5; + string client_code = 6; + string credential_type = 7; + string identifier = 8; + string secret = 9; +} + +message EnsureBizIdentityData { + int64 user_id = 1; + bool created = 2; + string mobile = 3; + string name = 4; +} + +message UsersByIdsReq { + repeated int64 ids = 1; +} + +message UserBriefItem { + int64 id = 1; + string mobile = 2; + string name = 3; + string avatar = 4; + uint32 gender = 5; + string birthday = 6; + uint32 status = 7; +} + +message UsersByIdsData { + repeated UserBriefItem items = 1; +} + +// 内部:修改用户手机号(同步凭证 identifier) +message UpdateMobileReq { + int64 user_id = 1; + string mobile = 2; // 新手机号明文 +} + +message UpdateMobileData { + int64 user_id = 1; + string mobile = 2; // 加密后手机号 +} + +// 内部:撤销业务端开通(删除/禁用销售或门店时) +message RevokeBizClientReq { + int64 user_id = 1; + string client_code = 2; +} + +message RevokeBizClientData { + bool revoked = 1; +} + service User { rpc Register(RegisterReq) returns (Response) { option (google.api.http) = { @@ -81,4 +139,9 @@ service User { body: "*" }; } + + rpc EnsureBizIdentity(EnsureBizIdentityReq) returns (EnsureBizIdentityData); + rpc UsersByIds(UsersByIdsReq) returns (UsersByIdsData); + rpc UpdateMobile(UpdateMobileReq) returns (UpdateMobileData); + rpc RevokeBizClient(RevokeBizClientReq) returns (RevokeBizClientData); } diff --git a/services/sale/internal/dao/sale.go b/services/sale/internal/dao/sale.go index 6761c01..1329f8b 100644 --- a/services/sale/internal/dao/sale.go +++ b/services/sale/internal/dao/sale.go @@ -13,10 +13,8 @@ const ( SaleTypeAgentCorp uint8 = 2 // 销售商(法人) SaleTypeAgentPerson uint8 = 3 // 销售商(个人) - SaleStatusPending uint8 = 9 // 待处理 - SaleStatusApproved uint8 = 1 // 已通过 - SaleStatusRejected uint8 = 2 // 未通过 - SaleStatusDisabled uint8 = 3 // 禁用 + SaleStatusEnabled uint8 = 1 // 开启 + SaleStatusDisabled uint8 = 2 // 禁用 ) type RegionNames struct { @@ -55,33 +53,35 @@ func (r *RegionNames) Scan(value interface{}) error { } type SaleCreate struct { - Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - SaleId int64 `gorm:"column:sale_id" json:"sale_id"` - Mobile string `gorm:"column:mobile" json:"mobile"` - Name string `gorm:"column:name" json:"name"` - IdCardFront string `gorm:"column:id_card_front" json:"id_card_front"` - IdCardBack string `gorm:"column:id_card_back" json:"id_card_back"` - BusinessLicense string `gorm:"column:business_license" json:"business_license"` - Region RegionNames `gorm:"column:region;type:json" json:"region"` - ProvinceId int64 `gorm:"column:province_id" json:"province_id"` - CityId int64 `gorm:"column:city_id" json:"city_id"` - DistrictId int64 `gorm:"column:district_id" json:"district_id"` - TerritoryId int64 `gorm:"column:territory_id" json:"territory_id"` - TerritoryName string `gorm:"column:territory_name" json:"territory_name"` - GroupId int64 `gorm:"column:group_id" json:"group_id"` - Address string `gorm:"column:address" json:"address"` - Status uint8 `gorm:"column:status" json:"status"` - Avatar string `gorm:"column:avatar" json:"avatar"` - Account string `gorm:"column:account" json:"account"` - Type uint8 `gorm:"column:type" json:"type"` - Sex uint8 `gorm:"column:sex" json:"sex"` - BirthDate string `gorm:"column:birth_date" json:"birth_date"` - AdminId int64 `gorm:"column:admin_id" json:"admin_id"` - AdminName string `gorm:"column:admin_name" json:"admin_name"` + Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + UserId int64 `gorm:"column:user_id" json:"user_id"` + SaleId int64 `gorm:"column:sale_id" json:"sale_id"` + Mobile string `gorm:"column:mobile" json:"mobile"` + Name string `gorm:"column:name" json:"name"` + IdCardFront string `gorm:"column:id_card_front" json:"id_card_front"` + IdCardBack string `gorm:"column:id_card_back" json:"id_card_back"` + BusinessLicense string `gorm:"column:business_license" json:"business_license"` + Region RegionNames `gorm:"column:region;type:json" json:"region"` + ProvinceId int64 `gorm:"column:province_id" json:"province_id"` + CityId int64 `gorm:"column:city_id" json:"city_id"` + DistrictId int64 `gorm:"column:district_id" json:"district_id"` + TerritoryId int64 `gorm:"column:territory_id" json:"territory_id"` + TerritoryName string `gorm:"column:territory_name" json:"territory_name"` + GroupId int64 `gorm:"column:group_id" json:"group_id"` + Address string `gorm:"column:address" json:"address"` + Status uint8 `gorm:"column:status" json:"status"` + Avatar string `gorm:"column:avatar" json:"avatar"` + Account string `gorm:"column:account" json:"account"` + Type uint8 `gorm:"column:type" json:"type"` + Sex uint8 `gorm:"column:sex" json:"sex"` + BirthDate utils.CustomTime `gorm:"column:birth_date;type:date" json:"birth_date"` + AdminId int64 `gorm:"column:admin_id" json:"admin_id"` + AdminName string `gorm:"column:admin_name" json:"admin_name"` } type SaleInfo struct { Id int64 `gorm:"column:id" json:"id"` + UserId int64 `gorm:"column:user_id" json:"user_id"` SaleId int64 `gorm:"column:sale_id" json:"sale_id"` Mobile string `gorm:"column:mobile" json:"mobile"` Name string `gorm:"column:name" json:"name"` @@ -101,7 +101,7 @@ type SaleInfo struct { Account string `gorm:"column:account" json:"account"` Type uint8 `gorm:"column:type" json:"type"` Sex uint8 `gorm:"column:sex" json:"sex"` - BirthDate string `gorm:"column:birth_date" json:"birth_date"` + BirthDate utils.CustomTime `gorm:"column:birth_date;type:date" json:"birth_date"` Reason string `gorm:"column:reason" json:"reason"` AdminId int64 `gorm:"column:admin_id" json:"admin_id"` AdminName string `gorm:"column:admin_name" json:"admin_name"` @@ -113,6 +113,8 @@ type SaleInfo struct { type SaleStatusUpdate struct { Id int64 `gorm:"column:id;primaryKey" json:"id"` + UserId int64 `gorm:"column:user_id" json:"user_id"` + Mobile string `gorm:"column:mobile" json:"mobile"` Name string `gorm:"column:name" json:"name"` Status uint8 `gorm:"column:status" json:"status"` Reason string `gorm:"column:reason" json:"reason"` diff --git a/services/sale/internal/logic/createLogic.go b/services/sale/internal/logic/createLogic.go index 85c5990..00b5db3 100644 --- a/services/sale/internal/logic/createLogic.go +++ b/services/sale/internal/logic/createLogic.go @@ -2,6 +2,7 @@ package logic import ( "context" + "strconv" "lone-services/pkg/modelbase" "lone-services/pkg/utils" @@ -43,16 +44,24 @@ func (l *CreateLogic) Create(in *sale.CreateReq) (*sale.Response, error) { return failResponse(utils.ErrorNoLoginInfo), nil } - encryptMobile, err := utils.EncryptPhone(req.Mobile) - if err != nil { - l.Errorf("encrypt mobile: %v", err) + identity, idErr := ensureSaleIdentity(l.ctx, l.svcCtx.UserSvcName, req.Mobile, req.Password) + if idErr != nil { + l.Errorf("sale ensure identity: %v", idErr) + if msg, ok := userRPCError(idErr); ok { + return outResponse(utils.Fail, msg), nil + } + return failResponse(utils.Fail), nil + } + if identity.GetUserId() < utils.NumberOne || identity.GetMobile() == utils.StringEmpty { return failResponse(utils.Fail), nil } m := model.SaleModel{}.Init() var existing dao.SaleStatusUpdate - if err := m.GetOne(modelbase.Params{Eq: map[string]string{"mobile": encryptMobile}}, &existing); err != nil { - l.Errorf("sale create check: %v", err) + if err := m.GetOne(modelbase.Params{ + Eq: map[string]string{"user_id": strconv.FormatInt(identity.GetUserId(), utils.NumberTen)}, + }, &existing); err != nil { + l.Errorf("sale create check user_id: %v", err) return failResponse(utils.Fail), nil } if existing.Id > utils.NumberZero { @@ -81,9 +90,15 @@ func (l *CreateLogic) Create(in *sale.CreateReq) (*sale.Response, error) { return outResponse(utils.ErrorParams, "请选择区县"), nil } + birthDate, bErr := utils.ParseDateOnly(req.BirthDate) + if bErr != nil { + return outResponse(utils.ErrorParams, "生日格式不正确"), nil + } + data := dao.SaleCreate{ + UserId: identity.GetUserId(), SaleId: req.SaleId, - Mobile: encryptMobile, + Mobile: identity.GetMobile(), Name: req.Name, IdCardFront: req.IdCardFront, IdCardBack: req.IdCardBack, @@ -96,11 +111,11 @@ func (l *CreateLogic) Create(in *sale.CreateReq) (*sale.Response, error) { TerritoryName: territoryName, GroupId: req.GroupId, Address: req.Address, - Status: dao.SaleStatusApproved, + Status: dao.SaleStatusEnabled, Account: req.Account, Type: uint8(req.Type), Sex: uint8(req.Sex), - BirthDate: req.BirthDate, + BirthDate: birthDate, AdminId: adminInfo.ID, AdminName: adminInfo.Name, } diff --git a/services/sale/internal/logic/editLogic.go b/services/sale/internal/logic/editLogic.go index 0074003..ccbabad 100644 --- a/services/sale/internal/logic/editLogic.go +++ b/services/sale/internal/logic/editLogic.go @@ -51,12 +51,25 @@ func (l *EditLogic) Edit(in *sale.EditReq) (*sale.Response, error) { return failResponse(utils.ErrorNotFund), nil } - encryptMobile, err := utils.EncryptPhone(req.Mobile) - if err != nil { - l.Errorf("encrypt mobile: %v", err) - return failResponse(utils.Fail), nil - } - if encryptMobile != info.Mobile { + encryptMobile := info.Mobile + oldPlain, _ := utils.DecryptPhone(info.Mobile) + if req.Mobile != oldPlain { + if info.UserId < utils.NumberOne { + return outResponse(utils.Fail, "销售未绑定用户,无法修改手机号"), nil + } + updated, uErr := updateSaleUserMobile(l.ctx, l.svcCtx.UserSvcName, info.UserId, req.Mobile) + if uErr != nil { + l.Errorf("sale edit sync user mobile: %v", uErr) + if msg, ok := userRPCError(uErr); ok { + return outResponse(utils.Fail, msg), nil + } + return failResponse(utils.Fail), nil + } + encryptMobile = updated.GetMobile() + if encryptMobile == utils.StringEmpty { + return failResponse(utils.Fail), nil + } + var check dao.SaleStatusUpdate if err := m.GetOne(modelbase.Params{Eq: map[string]string{"mobile": encryptMobile}}, &check); err != nil { l.Errorf("sale edit mobile check: %v", err) @@ -104,7 +117,11 @@ func (l *EditLogic) Edit(in *sale.EditReq) (*sale.Response, error) { info.Address = req.Address info.Type = uint8(req.Type) info.Sex = uint8(req.Sex) - info.BirthDate = req.BirthDate + birthDate, bErr := utils.ParseDateOnly(req.BirthDate) + if bErr != nil { + return outResponse(utils.ErrorParams, "生日格式不正确"), nil + } + info.BirthDate = birthDate info.Account = req.Account info.AdminId = adminInfo.ID info.AdminName = adminInfo.Name diff --git a/services/sale/internal/logic/helper.go b/services/sale/internal/logic/helper.go index c998717..fee9891 100644 --- a/services/sale/internal/logic/helper.go +++ b/services/sale/internal/logic/helper.go @@ -63,7 +63,7 @@ func fetchRegionNames(ctx context.Context, choreSvcName string, ids ...int64) (m func querySaleNames(req validator.NamesValidator) ([]dao.SaleNameItem, error) { w := modelbase.Params{ - Eq: map[string]string{"status": strconv.Itoa(int(dao.SaleStatusApproved))}, + Eq: map[string]string{"status": strconv.Itoa(int(dao.SaleStatusEnabled))}, Order: "id desc", Like: map[string]string{}, } diff --git a/services/sale/internal/logic/infoInternalLogic.go b/services/sale/internal/logic/infoInternalLogic.go index 98a963b..54d8e4d 100644 --- a/services/sale/internal/logic/infoInternalLogic.go +++ b/services/sale/internal/logic/infoInternalLogic.go @@ -74,7 +74,7 @@ func toSaleInfoData(info *dao.SaleInfo) *sale.InfoData { Account: info.Account, Type: uint32(info.Type), Sex: uint32(info.Sex), - BirthDate: info.BirthDate, + BirthDate: info.BirthDate.DateString(), Reason: info.Reason, AdminId: info.AdminId, AdminName: info.AdminName, diff --git a/services/sale/internal/logic/itemsLogic.go b/services/sale/internal/logic/itemsLogic.go index 09b18e0..8de9b36 100644 --- a/services/sale/internal/logic/itemsLogic.go +++ b/services/sale/internal/logic/itemsLogic.go @@ -53,7 +53,7 @@ func (l *ItemsLogic) Items(in *sale.ItemsReq) (*sale.Response, error) { Like: map[string]string{}, In: map[string][]string{ "status": { - strconv.Itoa(int(dao.SaleStatusApproved)), + strconv.Itoa(int(dao.SaleStatusEnabled)), strconv.Itoa(int(dao.SaleStatusDisabled)), }, }, diff --git a/services/sale/internal/logic/statusLogic.go b/services/sale/internal/logic/statusLogic.go index 50158c1..9c0680a 100644 --- a/services/sale/internal/logic/statusLogic.go +++ b/services/sale/internal/logic/statusLogic.go @@ -43,12 +43,6 @@ func (l *StatusLogic) Status(in *sale.StatusReq) (*sale.Response, error) { w := modelbase.Params{ Eq: map[string]string{"id": strconv.FormatInt(req.Id, 10)}, - In: map[string][]string{ - "status": { - strconv.Itoa(int(dao.SaleStatusApproved)), - strconv.Itoa(int(dao.SaleStatusDisabled)), - }, - }, } var info dao.SaleStatusUpdate m := model.SaleModel{}.Init() @@ -60,7 +54,38 @@ func (l *StatusLogic) Status(in *sale.StatusReq) (*sale.Response, error) { return failResponse(utils.ErrorNotFund), nil } - info.Status = uint8(req.Status) + newStatus := uint8(req.Status) + if newStatus == info.Status { + return okStringResponse(utils.StringEmpty), nil + } + + if info.UserId > utils.NumberZero { + switch newStatus { + case dao.SaleStatusDisabled: + if _, err := revokeSaleUserClient(l.ctx, l.svcCtx.UserSvcName, info.UserId); err != nil { + l.Errorf("sale status revoke client: %v", err) + if msg, ok := userRPCError(err); ok { + return outResponse(utils.Fail, msg), nil + } + return failResponse(utils.Fail), nil + } + case dao.SaleStatusEnabled: + plainMobile, dErr := utils.DecryptPhone(info.Mobile) + if dErr != nil || plainMobile == utils.StringEmpty { + l.Errorf("sale status decrypt mobile: %v", dErr) + return failResponse(utils.Fail), nil + } + if _, err := reopenSaleUserClient(l.ctx, l.svcCtx.UserSvcName, plainMobile); err != nil { + l.Errorf("sale status reopen client: %v", err) + if msg, ok := userRPCError(err); ok { + return outResponse(utils.Fail, msg), nil + } + return failResponse(utils.Fail), nil + } + } + } + + info.Status = newStatus info.Reason = req.Reason info.AdminId = adminInfo.ID info.AdminName = adminInfo.Name diff --git a/services/sale/internal/logic/userIdentity.go b/services/sale/internal/logic/userIdentity.go new file mode 100644 index 0000000..784970d --- /dev/null +++ b/services/sale/internal/logic/userIdentity.go @@ -0,0 +1,62 @@ +package logic + +import ( + "context" + + "lone-services/pkg/rpcclient" + "lone-services/pkg/utils" + userpb "lone-services/rpc/user/pb" + + "google.golang.org/grpc/status" +) + +const saleClientCode = "sale_beauty" + +func ensureSaleIdentity(ctx context.Context, userSvcName, mobile, password string) (*userpb.EnsureBizIdentityData, error) { + cli, err := rpcclient.Get(userSvcName) + if err != nil { + return nil, err + } + return userpb.NewUserClient(cli.Conn()).EnsureBizIdentity(ctx, &userpb.EnsureBizIdentityReq{ + Mobile: mobile, + ClientCode: saleClientCode, + CredentialType: "password", + Secret: password, + }) +} + +func updateSaleUserMobile(ctx context.Context, userSvcName string, userId int64, mobile string) (*userpb.UpdateMobileData, error) { + cli, err := rpcclient.Get(userSvcName) + if err != nil { + return nil, err + } + return userpb.NewUserClient(cli.Conn()).UpdateMobile(ctx, &userpb.UpdateMobileReq{ + UserId: userId, + Mobile: mobile, + }) +} + +func revokeSaleUserClient(ctx context.Context, userSvcName string, userId int64) (*userpb.RevokeBizClientData, error) { + cli, err := rpcclient.Get(userSvcName) + if err != nil { + return nil, err + } + return userpb.NewUserClient(cli.Conn()).RevokeBizClient(ctx, &userpb.RevokeBizClientReq{ + UserId: userId, + ClientCode: saleClientCode, + }) +} + +func reopenSaleUserClient(ctx context.Context, userSvcName, mobile string) (*userpb.EnsureBizIdentityData, error) { + return ensureSaleIdentity(ctx, userSvcName, mobile, utils.StringEmpty) +} + +func userRPCError(err error) (string, bool) { + if err == nil { + return utils.StringEmpty, false + } + if st, ok := status.FromError(err); ok { + return st.Message(), true + } + return err.Error(), true +} diff --git a/services/sale/internal/svc/serviceContext.go b/services/sale/internal/svc/serviceContext.go index e5ebfba..6ff1734 100644 --- a/services/sale/internal/svc/serviceContext.go +++ b/services/sale/internal/svc/serviceContext.go @@ -13,6 +13,7 @@ type ServiceContext struct { DB *gorm.DB Prefix string ChoreSvcName string + UserSvcName string } func NewServiceContext(c config.Config, db *gorm.DB) *ServiceContext { @@ -20,11 +21,16 @@ func NewServiceContext(c config.Config, db *gorm.DB) *ServiceContext { if choreSvc == utils.StringEmpty { logx.Error("config services.chore empty") } + userSvc := utils.GetConfigString("services.user") + if userSvc == utils.StringEmpty { + logx.Error("config services.user empty") + } return &ServiceContext{ Config: c, DB: db, Prefix: utils.GetConfigString("mysql.prefix"), ChoreSvcName: choreSvc, + UserSvcName: userSvc, } } diff --git a/services/sale/run.toml b/services/sale/run.toml index 05a692a..6f83ec6 100644 --- a/services/sale/run.toml +++ b/services/sale/run.toml @@ -2,7 +2,7 @@ login_out_time=43200 #api接口超时时间分 login_refresh_out_time=83200 name = "sale-service" - listenOn = "0.0.0.0:10900" + listenOn = "0.0.0.0:10300" mode = "dev" [log] path = "logs" @@ -52,4 +52,8 @@ db = 0 [encrypt] - data_key = "u2t9T3luZtoRfhBstkFN6TiIMW38BA8a" \ No newline at end of file + data_key = "u2t9T3luZtoRfhBstkFN6TiIMW38BA8a" + +[services] + chore = "chore-service" + user = "user-service" \ No newline at end of file diff --git a/services/sale/validator/validator.go b/services/sale/validator/validator.go index ff0be34..da346e1 100644 --- a/services/sale/validator/validator.go +++ b/services/sale/validator/validator.go @@ -153,7 +153,7 @@ type ItemsValidator struct { PageSize uint32 `validate:"omitempty,min=1,max=100"` SaleId int64 `validate:"omitempty,gte=0"` Type uint32 `validate:"omitempty,oneof=1 2 3"` - Status uint32 `validate:"omitempty,oneof=1 2 3 9"` + Status uint32 `validate:"omitempty,oneof=1 2"` } func (p ItemsValidator) GetMessage() validate.ValidatorMessages { @@ -168,8 +168,8 @@ func (p ItemsValidator) GetMessage() validate.ValidatorMessages { type StatusValidator struct { Id int64 `validate:"required,gt=0"` - Status uint32 `validate:"required,oneof=1 2 3"` - Reason string `validate:"required_if=Status 2 required_if=Status 3,max=255"` + Status uint32 `validate:"required,oneof=1 2"` + Reason string `validate:"required_if=Status 2,max=255"` } func (p StatusValidator) GetMessage() validate.ValidatorMessages { @@ -177,8 +177,8 @@ func (p StatusValidator) GetMessage() validate.ValidatorMessages { "Id.required": "ID不能为空", "Id.gt": "ID必须大于0", "Status.required": "状态不能为空", - "Status.oneof": "状态不对", - "Reason.required_if": "未通过或禁用状态,理由不能为空", + "Status.oneof": "状态不对,仅支持1开启或2禁用", + "Reason.required_if": "禁用时理由不能为空", "Reason.max": "理由长度不能超过255", } } diff --git a/services/user/internal/dao/client.go b/services/user/internal/dao/client.go new file mode 100644 index 0000000..d5b7211 --- /dev/null +++ b/services/user/internal/dao/client.go @@ -0,0 +1,11 @@ +package dao + +type Client struct { + Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + Code string `gorm:"column:code" json:"code"` + Name string `gorm:"column:name" json:"name"` + AllowedGrants string `gorm:"column:allowed_grants;type:json" json:"allowed_grants"` + SubjectType string `gorm:"column:subject_type" json:"subject_type"` + StoreStaff uint8 `gorm:"column:store_staff" json:"store_staff"` + Status uint8 `gorm:"column:status" json:"status"` +} diff --git a/services/user/internal/dao/const.go b/services/user/internal/dao/const.go new file mode 100644 index 0000000..e138a67 --- /dev/null +++ b/services/user/internal/dao/const.go @@ -0,0 +1,13 @@ +package dao + +const ( + StatusEnabled uint8 = 1 + StatusDisabled uint8 = 2 + + CredentialTypePassword = "password" + CredentialTypeWecom = "wecom" + CredentialTypeOpenid = "openid" + + // ExtraJsonEmpty MySQL JSON 列不能写空串,无扩展字段时用 {} + ExtraJsonEmpty = "{}" +) diff --git a/services/user/internal/dao/session.go b/services/user/internal/dao/session.go new file mode 100644 index 0000000..d0882c6 --- /dev/null +++ b/services/user/internal/dao/session.go @@ -0,0 +1,20 @@ +package dao + +import "lone-services/pkg/utils" + +type Token struct { + Token string `json:"token"` + Refresh string `json:"refresh"` + Info UserLoginSession `json:"info"` +} + +type UserLoginSession struct { + Id int64 `json:"id"` + Name string `json:"name"` + Avatar string `json:"avatar"` + Mobile string `json:"mobile"` + Gender uint8 `json:"gender"` + Birthday string `json:"birthday"` + Status uint8 `json:"status"` + LastTime utils.CustomTime `json:"last_time"` +} diff --git a/services/user/internal/dao/user.go b/services/user/internal/dao/user.go index 9c3192e..5b724b3 100644 --- a/services/user/internal/dao/user.go +++ b/services/user/internal/dao/user.go @@ -2,107 +2,37 @@ package dao import "lone-services/pkg/utils" +// UserRow 主用户(表 users) +type UserRow struct { + Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + Mobile string `gorm:"column:mobile" json:"mobile"` + Name string `gorm:"column:name" json:"name"` + Avatar string `gorm:"column:avatar" json:"avatar"` + Gender uint8 `gorm:"column:gender" json:"gender"` + Birthday utils.CustomTime `gorm:"column:birthday;type:date" json:"birthday"` + 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"` +} + +// UserCreate 新建用户 type UserCreate struct { - Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - Openid string `gorm:"column:openid" json:"openid"` - Username string `gorm:"column:username" json:"username"` - Nickname string `gorm:"column:nickname" json:"nickname"` - Avatar string `gorm:"column:avatar" json:"avatar"` - Mobile string `gorm:"column:mobile" json:"mobile"` - Gender uint8 `gorm:"column:gender" json:"gender"` - Birthday string `gorm:"column:birthday" json:"birthday"` - AppId uint32 `gorm:"column:app_id" json:"app_id"` - Status uint8 `gorm:"column:status" json:"status"` - IsRegistered uint8 `gorm:"column:is_registered" json:"is_registered"` - IsFaceVerified uint8 `gorm:"column:is_face_verified" json:"is_face_verified"` - IsProfileCompleted uint8 `gorm:"column:is_profile_completed" json:"is_profile_completed"` - OnTrialNum int `gorm:"column:on_trial_num" json:"on_trial_num"` -} - -type UserCreateByPwd struct { - Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` - Type uint8 `gorm:"column:type" json:"type"` - Account string `gorm:"column:account" json:"account"` - Password string `gorm:"column:password" json:"password"` - Salt string `gorm:"column:salt" json:"salt"` - Mobile string `gorm:"column:mobile" json:"mobile"` - AppId uint32 `gorm:"column:app_id" json:"app_id"` - Status uint8 `gorm:"column:status" json:"status"` - IsRegistered uint8 `gorm:"column:is_registered" json:"is_registered"` - IsFaceVerified uint8 `gorm:"column:is_face_verified" json:"is_face_verified"` - IsProfileCompleted uint8 `gorm:"column:is_profile_completed" json:"is_profile_completed"` - OnTrialNum int `gorm:"column:on_trial_num" json:"on_trial_num"` -} - -type UserExist struct { - Id int64 `gorm:"column:id" json:"id"` -} - -type UserInfo struct { - Id int64 `gorm:"column:id" json:"id"` - Openid string `gorm:"column:openid" json:"openid"` - Username string `gorm:"column:username" json:"username"` - Nickname string `gorm:"column:nickname" json:"nickname"` - Avatar string `gorm:"column:avatar" json:"avatar"` - Mobile string `gorm:"column:mobile" json:"mobile"` - Gender uint8 `gorm:"column:gender" json:"gender"` - Birthday string `gorm:"column:birthday" json:"birthday"` - Type uint8 `gorm:"column:type" json:"type"` - Account string `gorm:"column:account" json:"account"` - AppId uint32 `gorm:"column:app_id" json:"app_id"` - Status uint8 `gorm:"column:status" json:"status"` -} - -type UserLoginRow struct { - UserInfo - Salt string `gorm:"column:salt" json:"-"` - Password string `gorm:"column:password" json:"-"` -} - -type Token struct { - Token string `json:"token"` - Refresh string `json:"refresh"` - Info UserLoginSession `json:"info"` -} - -type UserLoginSession struct { - Id int64 `json:"id"` - Openid string `json:"openid"` - Username string `json:"username"` - Nickname string `json:"nickname"` - Avatar string `json:"avatar"` - Mobile string `json:"mobile"` - Gender uint8 `json:"gender"` - Birthday string `json:"birthday"` - Type uint8 `json:"type"` - Account string `json:"account"` - AppId uint32 `json:"app_id"` - LastTime utils.CustomTime `json:"last_time"` -} - -type UserListRow struct { - Id int64 `gorm:"column:id" json:"id"` - Username string `gorm:"column:username" json:"username"` - Nickname string `gorm:"column:nickname" json:"nickname"` - Avatar string `gorm:"column:avatar" json:"avatar"` - Mobile string `gorm:"column:mobile" json:"mobile"` - OnTrialNum int `gorm:"column:on_trial_num" json:"on_trial_num"` - Gender int `gorm:"column:gender" json:"gender"` - Birthday string `gorm:"column:birthday" json:"birthday"` - 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"` + Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + Mobile string `gorm:"column:mobile" json:"mobile"` + Name string `gorm:"column:name" json:"name"` + Avatar string `gorm:"column:avatar" json:"avatar"` + Gender uint8 `gorm:"column:gender" json:"gender"` + Birthday utils.CustomTime `gorm:"column:birthday;type:date" json:"birthday"` + Status uint8 `gorm:"column:status" json:"status"` } type UserListItem struct { Id int64 `json:"id"` - Username string `json:"username"` - Nickname string `json:"nickname"` + Name string `json:"name"` HeadPortrait string `json:"head_portrait"` Mobile string `json:"mobile"` OriginMobile string `json:"origin_mobile"` - OnTrialNum int `json:"on_trial_num"` - Gender int `json:"gender"` + Gender uint8 `json:"gender"` Birthday string `json:"birthday"` Status uint8 `json:"status"` CreatedAt utils.CustomTime `json:"created_at"` diff --git a/services/user/internal/dao/user_client.go b/services/user/internal/dao/user_client.go new file mode 100644 index 0000000..fece24b --- /dev/null +++ b/services/user/internal/dao/user_client.go @@ -0,0 +1,11 @@ +package dao + +import "lone-services/pkg/utils" + +type UserClient struct { + Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + UserId int64 `gorm:"column:user_id" json:"user_id"` + ClientCode string `gorm:"column:client_code" json:"client_code"` + Status uint8 `gorm:"column:status" json:"status"` + OpenedAt utils.CustomTime `gorm:"column:opened_at" json:"opened_at"` +} diff --git a/services/user/internal/dao/user_credential.go b/services/user/internal/dao/user_credential.go new file mode 100644 index 0000000..463bff0 --- /dev/null +++ b/services/user/internal/dao/user_credential.go @@ -0,0 +1,11 @@ +package dao + +type UserCredential struct { + Id int64 `gorm:"column:id;primaryKey;autoIncrement" json:"id"` + UserId int64 `gorm:"column:user_id" json:"user_id"` + CredentialType string `gorm:"column:credential_type" json:"credential_type"` + Identifier string `gorm:"column:identifier" json:"identifier"` + Secret string `gorm:"column:secret" json:"secret"` + ExtraJson string `gorm:"column:extra_json;type:json" json:"extra_json"` + Status uint8 `gorm:"column:status" json:"status"` +} diff --git a/services/user/internal/logic/ensureBizIdentityLogic.go b/services/user/internal/logic/ensureBizIdentityLogic.go new file mode 100644 index 0000000..a356ef5 --- /dev/null +++ b/services/user/internal/logic/ensureBizIdentityLogic.go @@ -0,0 +1,215 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + user "lone-services/rpc/user/pb" + "lone-services/services/user/internal/dao" + "lone-services/services/user/internal/model" + "lone-services/services/user/internal/svc" + "lone-services/services/user/validator" + + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gorm.io/gorm" +) + +type EnsureBizIdentityLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewEnsureBizIdentityLogic(ctx context.Context, svcCtx *svc.ServiceContext) *EnsureBizIdentityLogic { + return &EnsureBizIdentityLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *EnsureBizIdentityLogic) EnsureBizIdentity(in *user.EnsureBizIdentityReq) (*user.EnsureBizIdentityData, error) { + var req validator.EnsureBizIdentityValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return nil, status.Error(codes.InvalidArgument, msg) + } + + var client dao.Client + clientModel := model.ClientModel{}.Init() + if err := clientModel.GetOne(modelbase.Params{ + Eq: map[string]string{ + "code": req.ClientCode, + "status": strconv.Itoa(int(dao.StatusEnabled)), + }, + }, &client); err != nil { + l.Errorf("ensure identity client: %v", err) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + if client.Id < utils.NumberOne { + return nil, status.Error(codes.InvalidArgument, "端配置不存在或已禁用") + } + + encryptMobile, encErr := utils.EncryptPhone(req.Mobile) + if encErr != nil { + l.Errorf("ensure identity encrypt mobile: %v", encErr) + return nil, status.Error(codes.Internal, utils.ErrorEncryptAesError.Msg) + } + + var ( + userId int64 + created bool + name = req.Name + ) + + txErr := l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error { + userModel := model.UserModel{}.Init() + userModel.Base = userModel.Base.WithTX(tx) + + var exist dao.UserRow + if err := userModel.GetOne(modelbase.Params{ + Eq: map[string]string{"mobile": encryptMobile}, + }, &exist); err != nil { + return err + } + + if exist.Id > utils.NumberZero { + userId = exist.Id + if name == utils.StringEmpty { + name = exist.Name + } + if exist.Status == dao.StatusDisabled { + return status.Error(codes.FailedPrecondition, "用户已禁用") + } + } else { + birthday, bErr := utils.ParseDateOnly(req.Birthday) + if bErr != nil { + return status.Error(codes.InvalidArgument, "生日格式不正确") + } + add := dao.UserCreate{ + Mobile: encryptMobile, + Name: req.Name, + Avatar: req.Avatar, + Gender: uint8(req.Gender), + Birthday: birthday, + Status: dao.StatusEnabled, + } + if err := userModel.Create(&add); err != nil { + return err + } + userId = add.Id + created = true + } + + if req.CredentialType != utils.StringEmpty { + if err := ensureCredential(tx, userId, req, encryptMobile); err != nil { + return err + } + } + + return ensureUserClient(tx, userId, req.ClientCode) + }) + if txErr != nil { + if st, ok := status.FromError(txErr); ok { + return nil, st.Err() + } + l.Errorf("ensure identity tx: %v", txErr) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + + return &user.EnsureBizIdentityData{ + UserId: userId, + Created: created, + Mobile: encryptMobile, + Name: name, + }, nil +} + +func ensureCredential(tx *gorm.DB, userId int64, req validator.EnsureBizIdentityValidator, encryptMobile string) error { + identifier := req.Identifier + if identifier == utils.StringEmpty { + identifier = encryptMobile + } + + secret := utils.StringEmpty + if req.Secret != utils.StringEmpty { + hashed, err := utils.EncryptPassword(req.Secret) + if err != nil { + return err + } + secret = hashed + } + + credModel := model.UserCredentialModel{}.Init() + credModel.Base = credModel.Base.WithTX(tx) + + var exist dao.UserCredential + if err := credModel.GetOne(modelbase.Params{ + Eq: map[string]string{ + "credential_type": req.CredentialType, + "identifier": identifier, + }, + }, &exist); err != nil { + return err + } + + if exist.Id > utils.NumberZero { + if exist.UserId != userId { + return status.Error(codes.AlreadyExists, "登录凭证已被其他用户占用") + } + if secret == utils.StringEmpty { + return nil + } + _, err := credModel.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(exist.Id, utils.NumberTen)}, + }, map[string]interface{}{ + "secret": secret, + "status": dao.StatusEnabled, + }) + return err + } + + return credModel.Create(&dao.UserCredential{ + UserId: userId, + CredentialType: req.CredentialType, + Identifier: identifier, + Secret: secret, + ExtraJson: dao.ExtraJsonEmpty, + Status: dao.StatusEnabled, + }) +} + +func ensureUserClient(tx *gorm.DB, userId int64, clientCode string) error { + clientModel := model.UserClientModel{}.Init() + clientModel.Base = clientModel.Base.WithTX(tx) + + var exist dao.UserClient + if err := clientModel.GetOne(modelbase.Params{ + Eq: map[string]string{ + "user_id": strconv.FormatInt(userId, utils.NumberTen), + "client_code": clientCode, + }, + }, &exist); err != nil { + return err + } + if exist.Id > utils.NumberZero { + if exist.Status == dao.StatusEnabled { + return nil + } + _, err := clientModel.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(exist.Id, utils.NumberTen)}, + }, map[string]interface{}{"status": dao.StatusEnabled}) + return err + } + + return clientModel.Create(&dao.UserClient{ + UserId: userId, + ClientCode: clientCode, + Status: dao.StatusEnabled, + OpenedAt: utils.Now(), + }) +} diff --git a/services/user/internal/logic/loginLogic.go b/services/user/internal/logic/loginLogic.go index fb6af88..6956b2e 100644 --- a/services/user/internal/logic/loginLogic.go +++ b/services/user/internal/logic/loginLogic.go @@ -2,6 +2,8 @@ package logic import ( "context" + "strconv" + "lone-services/pkg/modelbase" "lone-services/pkg/utils" "lone-services/pkg/validate" @@ -34,15 +36,31 @@ func (l *LoginLogic) Login(in *user.LoginReq) (*user.Response, error) { return outResponse(utils.ErrorParams, msg), nil } - var row dao.UserLoginRow - err := model.UserModel{}.Init().GetOne(modelbase.Params{ + var cred dao.UserCredential + credModel := model.UserCredentialModel{}.Init() + if err := credModel.GetOne(modelbase.Params{ Eq: map[string]string{ - "openid": v.Openid, + "credential_type": dao.CredentialTypeOpenid, + "identifier": v.Openid, + "status": utils.StringStatusOk, + }, + }, &cred); err != nil { + l.Errorf("login by openid credential: %v", err) + return failResponse(utils.Fail), nil + } + if cred.Id < utils.NumberOne || cred.UserId < utils.NumberOne { + return failResponse(utils.ErrorNotFund), nil + } + + var row dao.UserRow + userModel := model.UserModel{}.Init() + if err := userModel.GetOne(modelbase.Params{ + Eq: map[string]string{ + "id": strconv.FormatInt(cred.UserId, utils.NumberTen), "status": utils.StringStatusOk, }, - }, &row) - if err != nil { - l.Errorf("login by openid: %v", err) + }, &row); err != nil { + l.Errorf("login by openid user: %v", err) return failResponse(utils.Fail), nil } if row.Id < utils.NumberOne { @@ -50,7 +68,7 @@ func (l *LoginLogic) Login(in *user.LoginReq) (*user.Response, error) { } mobile := decryptMobile(row.Mobile) - session := toSession(row.UserInfo, mobile) + session := toSession(row, mobile) ret := buildToken(session) if status := setLogin(ret.Token, ret.Refresh, session); status.Code != utils.Ok.Code { return failResponse(status), nil diff --git a/services/user/internal/logic/registerByUserLogic.go b/services/user/internal/logic/registerByUserLogic.go index 4adf11b..144f3c2 100644 --- a/services/user/internal/logic/registerByUserLogic.go +++ b/services/user/internal/logic/registerByUserLogic.go @@ -2,6 +2,8 @@ package logic import ( "context" + "errors" + "lone-services/pkg/modelbase" "lone-services/pkg/redis" "lone-services/pkg/utils" @@ -11,9 +13,9 @@ import ( "lone-services/services/user/internal/model" "lone-services/services/user/internal/svc" "lone-services/services/user/validator" - "strconv" "github.com/zeromicro/go-zero/core/logx" + "gorm.io/gorm" ) type RegisterByUserLogic struct { @@ -49,54 +51,85 @@ func (l *RegisterByUserLogic) RegisterByUser(in *user.RegisterByUserReq) (*user. return outResponse(utils.ErrorMissingParams, "验证码错误"), nil } - var old dao.UserExist - err = model.UserModel{}.Init().GetOne(modelbase.Params{ + credModel := model.UserCredentialModel{}.Init() + var oldCred dao.UserCredential + if err := credModel.GetOne(modelbase.Params{ Eq: map[string]string{ - "account": v.Account, - "type": strconv.FormatUint(uint64(v.Type), utils.NumberTen), + "credential_type": dao.CredentialTypePassword, + "identifier": v.Account, }, - }, &old) - if err != nil { + }, &oldCred); err != nil { l.Errorf("registerByUser check account: %v", err) return failResponse(utils.Fail), nil } - if old.Id > utils.NumberZero { + if oldCred.Id > utils.NumberZero { return failResponse(utils.ErrorDataIsExist), nil } - add := dao.UserCreateByPwd{ - Type: uint8(v.Type), - Account: v.Account, - Salt: utils.GetRandString(utils.NumberFive), - AppId: v.AppId, - Status: utils.NumberOne, - IsRegistered: utils.NumberOne, - IsFaceVerified: utils.NumberTwo, - IsProfileCompleted: utils.NumberTwo, - OnTrialNum: utils.NumberOne, - } - pwd := utils.GetSaltPassword(add.Salt, v.Pwd) - add.Password, err = utils.EncryptPassword(pwd) - if err != nil { - l.Errorf("registerByUser encrypt password: %v", err) + secret, pErr := utils.EncryptPassword(v.Pwd) + if pErr != nil { + l.Errorf("registerByUser encrypt password: %v", pErr) return failResponse(utils.Fail), nil } + encryptMobile := utils.StringEmpty if v.Type == utils.NumberOne { - encryptMobile, cErr := utils.EncryptPhone(v.Account) + mobile, cErr := utils.EncryptPhone(v.Account) if cErr != nil { l.Errorf("registerByUser encrypt mobile: %v", cErr) return failResponse(utils.ErrorEncryptAesError), nil } - add.Mobile = encryptMobile + encryptMobile = mobile } - err = model.UserModel{}.Init().CreateByPwd(&add) - if err != nil || add.Id < utils.NumberOne { - l.Errorf("registerByUser create: %v", err) + var userId int64 + txErr := l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error { + userModel := model.UserModel{}.Init() + userModel.Base = userModel.Base.WithTX(tx) + + if encryptMobile != utils.StringEmpty { + var exist dao.UserRow + if err := userModel.GetOne(modelbase.Params{ + Eq: map[string]string{"mobile": encryptMobile}, + }, &exist); err != nil { + return err + } + if exist.Id > utils.NumberZero { + if exist.Status == dao.StatusDisabled { + return errors.New("user disabled") + } + userId = exist.Id + } + } + + if userId < utils.NumberOne { + add := dao.UserCreate{ + Mobile: encryptMobile, + Name: v.Account, + Status: dao.StatusEnabled, + } + if err := userModel.Create(&add); err != nil { + return err + } + userId = add.Id + } + + credTx := model.UserCredentialModel{}.Init() + credTx.Base = credTx.Base.WithTX(tx) + return credTx.Create(&dao.UserCredential{ + UserId: userId, + CredentialType: dao.CredentialTypePassword, + Identifier: v.Account, + Secret: secret, + ExtraJson: dao.ExtraJsonEmpty, + Status: dao.StatusEnabled, + }) + }) + if txErr != nil { + l.Errorf("registerByUser tx: %v", txErr) return failResponse(utils.Fail), nil } redis.Client.Del(l.ctx, codeKey) - return okResponse(map[string]any{"id": add.Id}), nil + return okResponse(map[string]any{"id": userId}), nil } diff --git a/services/user/internal/logic/registerLogic.go b/services/user/internal/logic/registerLogic.go index 8598bae..17fc750 100644 --- a/services/user/internal/logic/registerLogic.go +++ b/services/user/internal/logic/registerLogic.go @@ -2,6 +2,9 @@ package logic import ( "context" + "errors" + "strconv" + "lone-services/pkg/modelbase" "lone-services/pkg/utils" "lone-services/pkg/validate" @@ -12,6 +15,7 @@ import ( "lone-services/services/user/validator" "github.com/zeromicro/go-zero/core/logx" + "gorm.io/gorm" ) type RegisterLogic struct { @@ -39,15 +43,18 @@ func (l *RegisterLogic) Register(in *user.RegisterReq) (*user.Response, error) { openid = "mock_" + utils.MD5Encrypt(v.Mobile+utils.Now().String()) } - var exist dao.UserExist - err := model.UserModel{}.Init().GetOne(modelbase.Params{ - Eq: map[string]string{"openid": openid}, - }, &exist) - if err != nil { + credModel := model.UserCredentialModel{}.Init() + var existCred dao.UserCredential + if err := credModel.GetOne(modelbase.Params{ + Eq: map[string]string{ + "credential_type": dao.CredentialTypeOpenid, + "identifier": openid, + }, + }, &existCred); err != nil { l.Errorf("register check openid: %v", err) return failResponse(utils.Fail), nil } - if exist.Id > utils.NumberZero { + if existCred.Id > utils.NumberZero { return failResponse(utils.ErrorExist), nil } @@ -57,38 +64,76 @@ func (l *RegisterLogic) Register(in *user.RegisterReq) (*user.Response, error) { return failResponse(utils.ErrorEncryptAesError), nil } - add := dao.UserCreate{ - Openid: openid, - Username: v.Username, - Nickname: v.Nickname, - Avatar: v.Avatar, - Mobile: encryptMobile, - Gender: uint8(v.Gender), - Birthday: v.Birthday, - AppId: v.AppId, - Status: utils.NumberOne, - IsRegistered: utils.NumberOne, - IsFaceVerified: utils.NumberTwo, - IsProfileCompleted: utils.NumberTwo, - OnTrialNum: utils.NumberOne, + name := v.Username + if name == utils.StringEmpty { + name = v.Nickname } - err = model.UserModel{}.Init().Create(&add) - if err != nil { - l.Errorf("register create: %v", err) + + if _, bErr := utils.ParseDateOnly(v.Birthday); bErr != nil { + return outResponse(utils.ErrorParams, "生日格式不正确"), nil + } + + var userRow dao.UserRow + txErr := l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error { + userModel := model.UserModel{}.Init() + userModel.Base = userModel.Base.WithTX(tx) + + var exist dao.UserRow + if err := userModel.GetOne(modelbase.Params{ + Eq: map[string]string{"mobile": encryptMobile}, + }, &exist); err != nil { + return err + } + + if exist.Id > utils.NumberZero { + if exist.Status == dao.StatusDisabled { + return errors.New("user disabled") + } + userRow = exist + } else { + birthday, _ := utils.ParseDateOnly(v.Birthday) + add := dao.UserCreate{ + Mobile: encryptMobile, + Name: name, + Avatar: v.Avatar, + Gender: uint8(v.Gender), + Birthday: birthday, + Status: dao.StatusEnabled, + } + if err := userModel.Create(&add); err != nil { + return err + } + userRow = dao.UserRow{ + Id: add.Id, + Mobile: add.Mobile, + Name: add.Name, + Avatar: add.Avatar, + Gender: add.Gender, + Birthday: add.Birthday, + Status: add.Status, + } + } + + credTx := model.UserCredentialModel{}.Init() + credTx.Base = credTx.Base.WithTX(tx) + extra := dao.ExtraJsonEmpty + if v.AppId > utils.NumberZero { + extra = `{"appid":` + strconv.FormatUint(uint64(v.AppId), utils.NumberTen) + `}` + } + return credTx.Create(&dao.UserCredential{ + UserId: userRow.Id, + CredentialType: dao.CredentialTypeOpenid, + Identifier: openid, + ExtraJson: extra, + Status: dao.StatusEnabled, + }) + }) + if txErr != nil { + l.Errorf("register tx: %v", txErr) return failResponse(utils.Fail), nil } - session := toSession(dao.UserInfo{ - Id: add.Id, - Openid: add.Openid, - Username: add.Username, - Nickname: add.Nickname, - Avatar: add.Avatar, - Gender: add.Gender, - Birthday: add.Birthday, - AppId: add.AppId, - Status: add.Status, - }, v.Mobile) + session := toSession(userRow, v.Mobile) ret := buildToken(session) if status := setLogin(ret.Token, ret.Refresh, session); status.Code != utils.Ok.Code { return failResponse(status), nil diff --git a/services/user/internal/logic/response.go b/services/user/internal/logic/response.go index 12e7aea..2392cd7 100644 --- a/services/user/internal/logic/response.go +++ b/services/user/internal/logic/response.go @@ -46,25 +46,21 @@ func decryptMobile(mobile string) string { return plain } -func toSession(info dao.UserInfo, mobilePlain string) dao.UserLoginSession { +func toSession(info dao.UserRow, mobilePlain string) dao.UserLoginSession { return dao.UserLoginSession{ Id: info.Id, - Openid: info.Openid, - Username: info.Username, - Nickname: info.Nickname, + Name: info.Name, Avatar: info.Avatar, Mobile: mobilePlain, Gender: info.Gender, - Birthday: info.Birthday, - Type: info.Type, - Account: info.Account, - AppId: info.AppId, + Birthday: info.Birthday.DateString(), + Status: info.Status, LastTime: utils.Now(), } } func buildToken(session dao.UserLoginSession) dao.Token { - seed := session.Mobile + session.Openid + utils.Now().String() + seed := session.Mobile + strconv.FormatInt(session.Id, utils.NumberTen) + utils.Now().String() token := utils.MD5Encrypt(seed) return dao.Token{ Token: token, diff --git a/services/user/internal/logic/revokeBizClientLogic.go b/services/user/internal/logic/revokeBizClientLogic.go new file mode 100644 index 0000000..24cab10 --- /dev/null +++ b/services/user/internal/logic/revokeBizClientLogic.go @@ -0,0 +1,68 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + user "lone-services/rpc/user/pb" + "lone-services/services/user/internal/dao" + "lone-services/services/user/internal/model" + "lone-services/services/user/internal/svc" + "lone-services/services/user/validator" + + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type RevokeBizClientLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewRevokeBizClientLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RevokeBizClientLogic { + return &RevokeBizClientLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// RevokeBizClient 内部:撤销某业务端开通(不删 users,保留其他端) +func (l *RevokeBizClientLogic) RevokeBizClient(in *user.RevokeBizClientReq) (*user.RevokeBizClientData, error) { + var req validator.RevokeBizClientValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return nil, status.Error(codes.InvalidArgument, msg) + } + + clientModel := model.UserClientModel{}.Init() + var row dao.UserClient + if err := clientModel.GetOne(modelbase.Params{ + Eq: map[string]string{ + "user_id": strconv.FormatInt(req.UserId, utils.NumberTen), + "client_code": req.ClientCode, + }, + }, &row); err != nil { + l.Errorf("revoke biz client get: %v", err) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + if row.Id < utils.NumberOne { + return &user.RevokeBizClientData{Revoked: false}, nil + } + if row.Status == dao.StatusDisabled { + return &user.RevokeBizClientData{Revoked: true}, nil + } + + if _, err := clientModel.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(row.Id, utils.NumberTen)}, + }, map[string]interface{}{"status": dao.StatusDisabled}); err != nil { + l.Errorf("revoke biz client edit: %v", err) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + + return &user.RevokeBizClientData{Revoked: true}, nil +} diff --git a/services/user/internal/logic/updateMobileLogic.go b/services/user/internal/logic/updateMobileLogic.go new file mode 100644 index 0000000..6d36782 --- /dev/null +++ b/services/user/internal/logic/updateMobileLogic.go @@ -0,0 +1,111 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + user "lone-services/rpc/user/pb" + "lone-services/services/user/internal/dao" + "lone-services/services/user/internal/model" + "lone-services/services/user/internal/svc" + "lone-services/services/user/validator" + + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gorm.io/gorm" +) + +type UpdateMobileLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewUpdateMobileLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateMobileLogic { + return &UpdateMobileLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// UpdateMobile 内部:修改用户手机号,并同步 password 凭证的 identifier +func (l *UpdateMobileLogic) UpdateMobile(in *user.UpdateMobileReq) (*user.UpdateMobileData, error) { + var req validator.UpdateMobileValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return nil, status.Error(codes.InvalidArgument, msg) + } + + userModel := model.UserModel{}.Init() + var row dao.UserRow + if err := userModel.GetOne(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(req.UserId, utils.NumberTen)}, + }, &row); err != nil { + l.Errorf("update mobile get user: %v", err) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + if row.Id < utils.NumberOne { + return nil, status.Error(codes.NotFound, utils.ErrorNotFund.Msg) + } + + newEncrypt, encErr := utils.EncryptPhone(req.Mobile) + if encErr != nil { + l.Errorf("update mobile encrypt: %v", encErr) + return nil, status.Error(codes.Internal, utils.ErrorEncryptAesError.Msg) + } + if newEncrypt == row.Mobile { + return &user.UpdateMobileData{UserId: row.Id, Mobile: row.Mobile}, nil + } + + var occupied dao.UserRow + if err := userModel.GetOne(modelbase.Params{ + Eq: map[string]string{"mobile": newEncrypt}, + }, &occupied); err != nil { + l.Errorf("update mobile unique check: %v", err) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + if occupied.Id > utils.NumberZero && occupied.Id != row.Id { + return nil, status.Error(codes.AlreadyExists, utils.ErrorDataIsExist.Msg) + } + + oldMobile := row.Mobile + txErr := l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error { + um := model.UserModel{}.Init() + um.Base = um.Base.WithTX(tx) + if _, err := um.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(row.Id, utils.NumberTen)}, + }, map[string]interface{}{"mobile": newEncrypt}); err != nil { + return err + } + + credModel := model.UserCredentialModel{}.Init() + credModel.Base = credModel.Base.WithTX(tx) + var cred dao.UserCredential + if err := credModel.GetOne(modelbase.Params{ + Eq: map[string]string{ + "user_id": strconv.FormatInt(row.Id, utils.NumberTen), + "credential_type": dao.CredentialTypePassword, + "identifier": oldMobile, + }, + }, &cred); err != nil { + return err + } + if cred.Id < utils.NumberOne { + return nil + } + _, err := credModel.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(cred.Id, utils.NumberTen)}, + }, map[string]interface{}{"identifier": newEncrypt}) + return err + }) + if txErr != nil { + l.Errorf("update mobile tx: %v", txErr) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + + return &user.UpdateMobileData{UserId: row.Id, Mobile: newEncrypt}, nil +} diff --git a/services/user/internal/logic/userItemsLogic.go b/services/user/internal/logic/userItemsLogic.go index c430cec..60c2d43 100644 --- a/services/user/internal/logic/userItemsLogic.go +++ b/services/user/internal/logic/userItemsLogic.go @@ -63,8 +63,9 @@ func (l *UserItemsLogic) UserItems(in *user.UserItemsReq) (*user.Response, error } } - var list []dao.UserListRow - result, err := model.UserModel{}.Init().Page(params, &list) + var list []dao.UserRow + userModel := model.UserModel{}.Init() + result, err := userModel.Page(params, &list) if err != nil { l.Errorf("user items: %v", err) return failResponse(utils.Fail), nil @@ -79,14 +80,12 @@ func (l *UserItemsLogic) UserItems(in *user.UserItemsReq) (*user.Response, error } items = append(items, dao.UserListItem{ Id: row.Id, - Username: row.Username, - Nickname: row.Nickname, + Name: row.Name, HeadPortrait: headPortrait, Mobile: mobile, OriginMobile: row.Mobile, - OnTrialNum: row.OnTrialNum, Gender: row.Gender, - Birthday: row.Birthday, + Birthday: row.Birthday.DateString(), Status: row.Status, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt, diff --git a/services/user/internal/logic/usersByIdsLogic.go b/services/user/internal/logic/usersByIdsLogic.go new file mode 100644 index 0000000..05da653 --- /dev/null +++ b/services/user/internal/logic/usersByIdsLogic.go @@ -0,0 +1,79 @@ +package logic + +import ( + "context" + "strconv" + + "lone-services/pkg/modelbase" + "lone-services/pkg/utils" + "lone-services/pkg/validate" + user "lone-services/rpc/user/pb" + "lone-services/services/user/internal/dao" + "lone-services/services/user/internal/model" + "lone-services/services/user/internal/svc" + "lone-services/services/user/validator" + + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type UsersByIdsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewUsersByIdsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UsersByIdsLogic { + return &UsersByIdsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *UsersByIdsLogic) UsersByIds(in *user.UsersByIdsReq) (*user.UsersByIdsData, error) { + var req validator.UsersByIdsValidator + if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty { + return nil, status.Error(codes.InvalidArgument, msg) + } + + ids := make([]string, 0, len(req.Ids)) + seen := make(map[int64]struct{}, 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 &user.UsersByIdsData{Items: []*user.UserBriefItem{}}, nil + } + + var rows []dao.UserRow + userModel := model.UserModel{}.Init() + if err := userModel.Items(modelbase.Params{ + In: map[string][]string{"id": ids}, + }, &rows); err != nil { + l.Errorf("users by ids: %v", err) + return nil, status.Error(codes.Internal, utils.Fail.Msg) + } + + items := make([]*user.UserBriefItem, 0, len(rows)) + for i := range rows { + items = append(items, &user.UserBriefItem{ + Id: rows[i].Id, + Mobile: rows[i].Mobile, + Name: rows[i].Name, + Avatar: rows[i].Avatar, + Gender: uint32(rows[i].Gender), + Birthday: rows[i].Birthday.DateString(), + Status: uint32(rows[i].Status), + }) + } + return &user.UsersByIdsData{Items: items}, nil +} diff --git a/services/user/internal/model/client.go b/services/user/internal/model/client.go new file mode 100644 index 0000000..3930a8f --- /dev/null +++ b/services/user/internal/model/client.go @@ -0,0 +1,16 @@ +package model + +import "lone-services/pkg/modelbase" + +type ClientModel struct { + modelbase.Base +} + +func (m ClientModel) TableName() string { + return modelbase.Prefix() + "client" +} + +func (m ClientModel) Init() ClientModel { + m.Table = m.TableName() + return m +} diff --git a/services/user/internal/model/user.go b/services/user/internal/model/user.go index d8c1272..d19f484 100644 --- a/services/user/internal/model/user.go +++ b/services/user/internal/model/user.go @@ -21,7 +21,3 @@ func (m UserModel) Init() UserModel { func (m UserModel) Create(data *dao.UserCreate) error { return m.Base.Create(data) } - -func (m UserModel) CreateByPwd(data *dao.UserCreateByPwd) error { - return m.Base.Create(data) -} diff --git a/services/user/internal/model/user_client.go b/services/user/internal/model/user_client.go new file mode 100644 index 0000000..a631be9 --- /dev/null +++ b/services/user/internal/model/user_client.go @@ -0,0 +1,23 @@ +package model + +import ( + "lone-services/pkg/modelbase" + "lone-services/services/user/internal/dao" +) + +type UserClientModel struct { + modelbase.Base +} + +func (m UserClientModel) TableName() string { + return modelbase.Prefix() + "user_client" +} + +func (m UserClientModel) Init() UserClientModel { + m.Table = m.TableName() + return m +} + +func (m UserClientModel) Create(data *dao.UserClient) error { + return m.Base.Create(data) +} diff --git a/services/user/internal/model/user_credential.go b/services/user/internal/model/user_credential.go new file mode 100644 index 0000000..bdaad57 --- /dev/null +++ b/services/user/internal/model/user_credential.go @@ -0,0 +1,23 @@ +package model + +import ( + "lone-services/pkg/modelbase" + "lone-services/services/user/internal/dao" +) + +type UserCredentialModel struct { + modelbase.Base +} + +func (m UserCredentialModel) TableName() string { + return modelbase.Prefix() + "user_credential" +} + +func (m UserCredentialModel) Init() UserCredentialModel { + m.Table = m.TableName() + return m +} + +func (m UserCredentialModel) Create(data *dao.UserCredential) error { + return m.Base.Create(data) +} diff --git a/services/user/internal/server/userserver.go b/services/user/internal/server/userserver.go index 7bb8099..6e30e12 100644 --- a/services/user/internal/server/userserver.go +++ b/services/user/internal/server/userserver.go @@ -47,3 +47,27 @@ func (s *UserServer) UserStatus(ctx context.Context, in *user.UserStatusReq) (*u l := logic.NewUserStatusLogic(ctx, s.svcCtx) return l.UserStatus(in) } + +// 内部:业务创建销售/门店时调用 +func (s *UserServer) EnsureBizIdentity(ctx context.Context, in *user.EnsureBizIdentityReq) (*user.EnsureBizIdentityData, error) { + l := logic.NewEnsureBizIdentityLogic(ctx, s.svcCtx) + return l.EnsureBizIdentity(in) +} + +// 内部:批量查用户基础信息 +func (s *UserServer) UsersByIds(ctx context.Context, in *user.UsersByIdsReq) (*user.UsersByIdsData, error) { + l := logic.NewUsersByIdsLogic(ctx, s.svcCtx) + return l.UsersByIds(in) +} + +// 内部:修改手机号 +func (s *UserServer) UpdateMobile(ctx context.Context, in *user.UpdateMobileReq) (*user.UpdateMobileData, error) { + l := logic.NewUpdateMobileLogic(ctx, s.svcCtx) + return l.UpdateMobile(in) +} + +// 内部:撤销业务端开通 +func (s *UserServer) RevokeBizClient(ctx context.Context, in *user.RevokeBizClientReq) (*user.RevokeBizClientData, error) { + l := logic.NewRevokeBizClientLogic(ctx, s.svcCtx) + return l.RevokeBizClient(in) +} diff --git a/services/user/userClient/user.go b/services/user/userClient/user.go index 7b62baf..22d00d4 100644 --- a/services/user/userClient/user.go +++ b/services/user/userClient/user.go @@ -14,12 +14,21 @@ import ( ) type ( - LoginReq = user.LoginReq - RegisterByUserReq = user.RegisterByUserReq - RegisterReq = user.RegisterReq - Response = user.Response - UserItemsReq = user.UserItemsReq - UserStatusReq = user.UserStatusReq + EnsureBizIdentityData = user.EnsureBizIdentityData + EnsureBizIdentityReq = user.EnsureBizIdentityReq + LoginReq = user.LoginReq + RegisterByUserReq = user.RegisterByUserReq + RegisterReq = user.RegisterReq + Response = user.Response + RevokeBizClientData = user.RevokeBizClientData + RevokeBizClientReq = user.RevokeBizClientReq + UpdateMobileData = user.UpdateMobileData + UpdateMobileReq = user.UpdateMobileReq + UserBriefItem = user.UserBriefItem + UserItemsReq = user.UserItemsReq + UserStatusReq = user.UserStatusReq + UsersByIdsData = user.UsersByIdsData + UsersByIdsReq = user.UsersByIdsReq User interface { Register(ctx context.Context, in *RegisterReq, opts ...grpc.CallOption) (*Response, error) @@ -27,6 +36,14 @@ type ( Login(ctx context.Context, in *LoginReq, opts ...grpc.CallOption) (*Response, error) UserItems(ctx context.Context, in *UserItemsReq, opts ...grpc.CallOption) (*Response, error) UserStatus(ctx context.Context, in *UserStatusReq, opts ...grpc.CallOption) (*Response, error) + // 内部:业务创建销售/门店时调用 + EnsureBizIdentity(ctx context.Context, in *EnsureBizIdentityReq, opts ...grpc.CallOption) (*EnsureBizIdentityData, error) + // 内部:批量查用户基础信息 + UsersByIds(ctx context.Context, in *UsersByIdsReq, opts ...grpc.CallOption) (*UsersByIdsData, error) + // 内部:修改手机号 + UpdateMobile(ctx context.Context, in *UpdateMobileReq, opts ...grpc.CallOption) (*UpdateMobileData, error) + // 内部:撤销业务端开通 + RevokeBizClient(ctx context.Context, in *RevokeBizClientReq, opts ...grpc.CallOption) (*RevokeBizClientData, error) } defaultUser struct { @@ -64,3 +81,27 @@ func (m *defaultUser) UserStatus(ctx context.Context, in *UserStatusReq, opts .. client := user.NewUserClient(m.cli.Conn()) return client.UserStatus(ctx, in, opts...) } + +// 内部:业务创建销售/门店时调用 +func (m *defaultUser) EnsureBizIdentity(ctx context.Context, in *EnsureBizIdentityReq, opts ...grpc.CallOption) (*EnsureBizIdentityData, error) { + client := user.NewUserClient(m.cli.Conn()) + return client.EnsureBizIdentity(ctx, in, opts...) +} + +// 内部:批量查用户基础信息 +func (m *defaultUser) UsersByIds(ctx context.Context, in *UsersByIdsReq, opts ...grpc.CallOption) (*UsersByIdsData, error) { + client := user.NewUserClient(m.cli.Conn()) + return client.UsersByIds(ctx, in, opts...) +} + +// 内部:修改手机号 +func (m *defaultUser) UpdateMobile(ctx context.Context, in *UpdateMobileReq, opts ...grpc.CallOption) (*UpdateMobileData, error) { + client := user.NewUserClient(m.cli.Conn()) + return client.UpdateMobile(ctx, in, opts...) +} + +// 内部:撤销业务端开通 +func (m *defaultUser) RevokeBizClient(ctx context.Context, in *RevokeBizClientReq, opts ...grpc.CallOption) (*RevokeBizClientData, error) { + client := user.NewUserClient(m.cli.Conn()) + return client.RevokeBizClient(ctx, in, opts...) +} diff --git a/services/user/validator/user.go b/services/user/validator/user.go index bb63466..cef3e4c 100644 --- a/services/user/validator/user.go +++ b/services/user/validator/user.go @@ -81,3 +81,63 @@ func (p UserStatusValidator) GetMessage() validate.ValidatorMessages { "Status.oneof": "状态传值不对", } } + +type EnsureBizIdentityValidator struct { + Mobile string `validate:"required,max=20"` + Name string `validate:"omitempty,max=64"` + Avatar string `validate:"omitempty,max=255"` + Gender uint32 `validate:"omitempty,oneof=0 1 2 3"` + Birthday string `validate:"omitempty,max=12"` + ClientCode string `validate:"required,max=32"` + CredentialType string `validate:"omitempty,max=32"` + Identifier string `validate:"omitempty,max=128"` + Secret string `validate:"omitempty,max=255"` +} + +func (p EnsureBizIdentityValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "Mobile.required": "手机号不能为空", + "Mobile.max": "手机号过长", + "ClientCode.required": "端编码不能为空", + "ClientCode.max": "端编码过长", + } +} + +type UsersByIdsValidator struct { + Ids []int64 `validate:"required,min=1,dive,gt=0"` +} + +func (p UsersByIdsValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "Ids.required": "用户ID不能为空", + "Ids.min": "用户ID不能为空", + } +} + +type UpdateMobileValidator struct { + UserId int64 `validate:"required,gt=0"` + Mobile string `validate:"required,max=20"` +} + +func (p UpdateMobileValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "UserId.required": "用户ID不能为空", + "UserId.gt": "用户ID必须大于0", + "Mobile.required": "手机号不能为空", + "Mobile.max": "手机号过长", + } +} + +type RevokeBizClientValidator struct { + UserId int64 `validate:"required,gt=0"` + ClientCode string `validate:"required,max=32"` +} + +func (p RevokeBizClientValidator) GetMessage() validate.ValidatorMessages { + return validate.ValidatorMessages{ + "UserId.required": "用户ID不能为空", + "UserId.gt": "用户ID必须大于0", + "ClientCode.required": "端编码不能为空", + "ClientCode.max": "端编码过长", + } +} diff --git a/services/wecom/run.toml b/services/wecom/run.toml index 035630f..bd86fad 100644 --- a/services/wecom/run.toml +++ b/services/wecom/run.toml @@ -16,7 +16,7 @@ compress = false [wecom] - skip = false + skip = true department_id = 1 corp_id = "wwb83e24dcf1946e3e" icon_url = "https://skin-test-api.ailuowan.com/images/20260509/1778321760824864613.png"