From 9111cb26e70336f2ece11020a6abdd7b712a74bd Mon Sep 17 00:00:00 2001 From: zzw <1464003642@qq.com> Date: Mon, 31 Aug 2026 15:04:38 +0800 Subject: [PATCH] fix: wecom user list --- bff/Dockerfile | 3 + rpc/wecom/pb/wecom.pb.go | 24 ++++- rpc/wecom/wecom.pb | Bin 14967 -> 15025 bytes rpc/wecom/wecom.proto | 2 + services/wecom/internal/dao/user.go | 35 +++++--- .../wecom/internal/logic/createUserLogic.go | 7 ++ services/wecom/internal/logic/helper.go | 82 ++++++++++++++++++ .../wecom/internal/logic/itemsUsersLogic.go | 1 + .../wecom/internal/logic/updateUserLogic.go | 19 ++-- services/wecom/sql/agents.sql | 13 --- services/wecom/validator/validator.go | 52 ++++++----- 11 files changed, 181 insertions(+), 57 deletions(-) delete mode 100644 services/wecom/sql/agents.sql diff --git a/bff/Dockerfile b/bff/Dockerfile index f6d10a0..361269c 100644 --- a/bff/Dockerfile +++ b/bff/Dockerfile @@ -13,6 +13,7 @@ COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download +COPY rpc/ ./rpc COPY bff/ ./bff/ WORKDIR /src/bff @@ -32,6 +33,8 @@ RUN apk add --no-cache tzdata \ && echo "Asia/Shanghai" > /etc/timezone COPY --from=builder /out/main . +COPY --from=builder /src/rpc ./rpc +COPY --from=builder /src/bff/etc ./etc EXPOSE 10000 diff --git a/rpc/wecom/pb/wecom.pb.go b/rpc/wecom/pb/wecom.pb.go index 493c450..aec68ea 100644 --- a/rpc/wecom/pb/wecom.pb.go +++ b/rpc/wecom/pb/wecom.pb.go @@ -202,6 +202,7 @@ type CreateUserReq struct { Mobile string `protobuf:"bytes,4,opt,name=mobile,proto3" json:"mobile,omitempty"` Avatar string `protobuf:"bytes,5,opt,name=avatar,proto3" json:"avatar,omitempty"` Status uint32 `protobuf:"varint,6,opt,name=status,proto3" json:"status,omitempty"` + AgentIds []int64 `protobuf:"varint,7,rep,packed,name=agent_ids,json=agentIds,proto3" json:"agent_ids,omitempty"` // 应用 agents.id,多选 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -278,6 +279,13 @@ func (x *CreateUserReq) GetStatus() uint32 { return 0 } +func (x *CreateUserReq) GetAgentIds() []int64 { + if x != nil { + return x.AgentIds + } + return nil +} + type UpdateUserReq struct { state protoimpl.MessageState `protogen:"open.v1"` Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` // wecom_users 主键,优先 @@ -287,6 +295,7 @@ type UpdateUserReq struct { Mobile string `protobuf:"bytes,5,opt,name=mobile,proto3" json:"mobile,omitempty"` Avatar string `protobuf:"bytes,6,opt,name=avatar,proto3" json:"avatar,omitempty"` Status uint32 `protobuf:"varint,7,opt,name=status,proto3" json:"status,omitempty"` + AgentIds []int64 `protobuf:"varint,8,rep,packed,name=agent_ids,json=agentIds,proto3" json:"agent_ids,omitempty"` // 应用 agents.id,多选 unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -370,6 +379,13 @@ func (x *UpdateUserReq) GetStatus() uint32 { return 0 } +func (x *UpdateUserReq) GetAgentIds() []int64 { + if x != nil { + return x.AgentIds + } + return nil +} + type StatusUserReq struct { state protoimpl.MessageState `protogen:"open.v1"` Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` @@ -482,14 +498,15 @@ const file_wecom_wecom_proto_rawDesc = "" + "\tpage_size\x18\x02 \x01(\rR\bpageSize\x12\x12\n" + "\x04name\x18\x03 \x01(\tR\x04name\x12\x12\n" + "\x04type\x18\x04 \x01(\rR\x04type\x12\x16\n" + - "\x06status\x18\x05 \x01(\rR\x06status\"\x98\x01\n" + + "\x06status\x18\x05 \x01(\rR\x06status\"\xb5\x01\n" + "\rCreateUserReq\x12\x17\n" + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" + "\x04type\x18\x02 \x01(\rR\x04type\x12\x12\n" + "\x04name\x18\x03 \x01(\tR\x04name\x12\x16\n" + "\x06mobile\x18\x04 \x01(\tR\x06mobile\x12\x16\n" + "\x06avatar\x18\x05 \x01(\tR\x06avatar\x12\x16\n" + - "\x06status\x18\x06 \x01(\rR\x06status\"\xa8\x01\n" + + "\x06status\x18\x06 \x01(\rR\x06status\x12\x1b\n" + + "\tagent_ids\x18\a \x03(\x03R\bagentIds\"\xc5\x01\n" + "\rUpdateUserReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x12\n" + @@ -497,7 +514,8 @@ const file_wecom_wecom_proto_rawDesc = "" + "\x04name\x18\x04 \x01(\tR\x04name\x12\x16\n" + "\x06mobile\x18\x05 \x01(\tR\x06mobile\x12\x16\n" + "\x06avatar\x18\x06 \x01(\tR\x06avatar\x12\x16\n" + - "\x06status\x18\a \x01(\rR\x06status\"7\n" + + "\x06status\x18\a \x01(\rR\x06status\x12\x1b\n" + + "\tagent_ids\x18\b \x03(\x03R\bagentIds\"7\n" + "\rStatusUserReq\x12\x0e\n" + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" + "\x06status\x18\x02 \x01(\rR\x06status\"\x1f\n" + diff --git a/rpc/wecom/wecom.pb b/rpc/wecom/wecom.pb index 3948e86bdf29134b0903e8263a451c95b60fa524..3036c3dd9ecf080ee3cc6cc106a947d3de6623f2 100644 GIT binary patch delta 90 zcmexfvaxi7lPTld&CaI0jEq|+2b#^47n0`UOiWMBD~ZocDVAVYVAfy`;s6VHrW7k3 Vo&3gZr3|VX4iq(;rlPTlo&CaI0jEplT2b#@fQd%*Y(|qOT^X8A30NFtd*8l(j diff --git a/rpc/wecom/wecom.proto b/rpc/wecom/wecom.proto index 77f08eb..ce80cc4 100644 --- a/rpc/wecom/wecom.proto +++ b/rpc/wecom/wecom.proto @@ -74,6 +74,7 @@ message CreateUserReq { string mobile = 4; string avatar = 5; uint32 status = 6; + repeated int64 agent_ids = 7; // 应用 agents.id,多选 } message UpdateUserReq { @@ -84,6 +85,7 @@ message UpdateUserReq { string mobile = 5; string avatar = 6; uint32 status = 7; + repeated int64 agent_ids = 8; // 应用 agents.id,多选 } message StatusUserReq { diff --git a/services/wecom/internal/dao/user.go b/services/wecom/internal/dao/user.go index f895095..e5e2b28 100644 --- a/services/wecom/internal/dao/user.go +++ b/services/wecom/internal/dao/user.go @@ -20,24 +20,33 @@ type WecomUser struct { Mobile string `gorm:"column:mobile" json:"mobile"` Avatar string `gorm:"column:avatar" json:"avatar"` Status uint8 `gorm:"column:status" json:"status"` + AgentIds string `gorm:"column:agent_ids;type:json" json:"agent_ids"` CreatedAt utils.CustomTime `gorm:"column:created_at" json:"created_at"` UpdatedAt utils.CustomTime `gorm:"column:updated_at" json:"updated_at"` } +// UserAgentItem 用户绑定的应用快照 +type UserAgentItem struct { + Id int64 `json:"id"` + AgentId int64 `json:"agent_id"` + Name string `json:"name"` +} + type UserListItem struct { - Id int64 `json:"id"` - UserId int64 `json:"user_id"` - Type uint8 `json:"type"` - TypeText string `json:"type_text"` - Name string `json:"name"` - Mobile string `json:"mobile"` - OriginMobile string `json:"origin_mobile"` - Avatar string `json:"avatar"` - Status uint8 `json:"status"` - BizName string `json:"biz_name"` - WecomUserId string `json:"wecom_user_id"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` + Id int64 `json:"id"` + UserId int64 `json:"user_id"` + Type uint8 `json:"type"` + TypeText string `json:"type_text"` + Name string `json:"name"` + Mobile string `json:"mobile"` + OriginMobile string `json:"origin_mobile"` + Avatar string `json:"avatar"` + Status uint8 `json:"status"` + BizName string `json:"biz_name"` + WecomUserId string `json:"wecom_user_id"` + Agents []UserAgentItem `json:"agents"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` } type UserTypeOption struct { diff --git a/services/wecom/internal/logic/createUserLogic.go b/services/wecom/internal/logic/createUserLogic.go index 782eba7..c3bfbbf 100644 --- a/services/wecom/internal/logic/createUserLogic.go +++ b/services/wecom/internal/logic/createUserLogic.go @@ -80,6 +80,12 @@ func (l *CreateUserLogic) CreateUser(in *wecom.CreateUserReq) (*wecom.Response, } } + agentsJSON, aErr := buildAgentsJSON(req.AgentIds) + if aErr != nil { + l.Errorf("wecom create agents: %v", aErr) + return outResponse(utils.ErrorParams, aErr.Error()), nil + } + now := utils.Now() row := dao.WecomUser{ UserId: req.UserId, @@ -89,6 +95,7 @@ func (l *CreateUserLogic) CreateUser(in *wecom.CreateUserReq) (*wecom.Response, Mobile: encryptMobile, Avatar: req.Avatar, Status: userStatus, + AgentIds: agentsJSON, CreatedAt: now, UpdatedAt: now, } diff --git a/services/wecom/internal/logic/helper.go b/services/wecom/internal/logic/helper.go index 3f0e816..e15780d 100644 --- a/services/wecom/internal/logic/helper.go +++ b/services/wecom/internal/logic/helper.go @@ -1,8 +1,15 @@ package logic import ( + "fmt" + "strconv" + + "lone-services/pkg/modelbase" "lone-services/pkg/utils" "lone-services/services/wecom/internal/dao" + "lone-services/services/wecom/internal/model" + + jsoniter "github.com/json-iterator/go" ) func departmentID() int { @@ -47,3 +54,78 @@ func resolveStatus(reqStatus uint32, current uint8) uint8 { } return dao.StatusEnabled } + +// buildAgentsJSON 按 agents 表主键 id 查出应用,落库为 [{id,agent_id,name}] +func buildAgentsJSON(ids []int64) (string, error) { + ids = cleanAgentIds(ids) + if len(ids) == 0 { + return "", fmt.Errorf("应用不能为空") + } + + idStrs := make([]string, 0, len(ids)) + for _, id := range ids { + idStrs = append(idStrs, strconv.FormatInt(id, utils.NumberTen)) + } + + var rows []dao.WecomAgent + am := model.AgentModel{}.Init() + if err := am.Items(modelbase.Params{ + In: map[string][]string{"id in ?": idStrs}, + }, &rows); err != nil { + return "", err + } + + byID := make(map[int64]dao.WecomAgent, len(rows)) + for _, row := range rows { + byID[row.Id] = row + } + + items := make([]dao.UserAgentItem, 0, len(ids)) + for _, id := range ids { + row, ok := byID[id] + if !ok { + return "", fmt.Errorf("应用不存在: %d", id) + } + items = append(items, dao.UserAgentItem{ + Id: row.Id, + AgentId: row.AgentId, + Name: row.Name, + }) + } + + buf, err := jsoniter.Marshal(items) + if err != nil { + return "", err + } + return string(buf), nil +} + +func parseAgents(raw string) []dao.UserAgentItem { + if raw == utils.StringEmpty { + return []dao.UserAgentItem{} + } + var items []dao.UserAgentItem + if err := jsoniter.Unmarshal([]byte(raw), &items); err != nil { + return []dao.UserAgentItem{} + } + if items == nil { + return []dao.UserAgentItem{} + } + return items +} + +func cleanAgentIds(ids []int64) []int64 { + out := make([]int64, 0, len(ids)) + seen := make(map[int64]struct{}, len(ids)) + for _, id := range ids { + if id < utils.NumberOne { + continue + } + if _, ok := seen[id]; ok { + continue + } + seen[id] = struct{}{} + out = append(out, id) + } + return out +} diff --git a/services/wecom/internal/logic/itemsUsersLogic.go b/services/wecom/internal/logic/itemsUsersLogic.go index 1546a5a..c6c35f8 100644 --- a/services/wecom/internal/logic/itemsUsersLogic.go +++ b/services/wecom/internal/logic/itemsUsersLogic.go @@ -98,6 +98,7 @@ func (l *ItemsUsersLogic) ItemsUsers(in *wecom.ItemsUserReq) (*wecom.Response, e Status: row.Status, BizName: bizName, WecomUserId: row.WecomUserId, + Agents: parseAgents(row.AgentIds), CreatedAt: row.CreatedAt.Format(), }) } diff --git a/services/wecom/internal/logic/updateUserLogic.go b/services/wecom/internal/logic/updateUserLogic.go index 29acb79..559e2ab 100644 --- a/services/wecom/internal/logic/updateUserLogic.go +++ b/services/wecom/internal/logic/updateUserLogic.go @@ -70,7 +70,7 @@ func (l *UpdateUserLogic) UpdateUser(in *wecom.UpdateUserReq) (*wecom.Response, } typeVal := row.Type if req.Type > utils.NumberZero { - typeVal = row.Type + typeVal = uint8(req.Type) } userStatus := resolveStatus(req.Status, row.Status) @@ -109,16 +109,25 @@ func (l *UpdateUserLogic) UpdateUser(in *wecom.UpdateUserReq) (*wecom.Response, } } - if _, err := m.Edit(modelbase.Params{ - Eq: map[string]string{"id": strconv.FormatInt(row.Id, utils.NumberTen)}, - }, map[string]interface{}{ + data := map[string]interface{}{ "name": name, "mobile": encryptMobile, "avatar": avatar, "status": userStatus, "type": typeVal, "updated_at": utils.Now(), - }); err != nil { + } + if len(req.AgentIds) > utils.NumberZero { + agentsJSON, aErr := buildAgentsJSON(req.AgentIds) + if aErr != nil { + l.Errorf("wecom update agents: %v", aErr) + return outResponse(utils.ErrorParams, aErr.Error()), nil + } + data["agent_ids"] = agentsJSON + } + if _, err := m.Edit(modelbase.Params{ + Eq: map[string]string{"id": strconv.FormatInt(row.Id, utils.NumberTen)}, + }, data); err != nil { l.Errorf("wecom update user db: %v", err) return failResponse(utils.Fail), nil } diff --git a/services/wecom/sql/agents.sql b/services/wecom/sql/agents.sql deleted file mode 100644 index 61cad2e..0000000 --- a/services/wecom/sql/agents.sql +++ /dev/null @@ -1,13 +0,0 @@ --- 企业微信应用表(库:dms-wecom,前缀为空时表名为 agents) -CREATE TABLE IF NOT EXISTS `agents` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(64) NOT NULL DEFAULT '' COMMENT '应用名称', - `agent_id` bigint NOT NULL DEFAULT 0 COMMENT '企业微信 AgentId', - `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_agent_id` (`agent_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企业微信应用'; - -INSERT INTO `agents` (`id`, `name`, `agent_id`, `created_at`) VALUES - (1, '审核通知', 100003, '2026-05-22 16:14:31'), - (2, '消息通知', 100004, '2026-05-22 16:14:33'); diff --git a/services/wecom/validator/validator.go b/services/wecom/validator/validator.go index 02ade5f..a90b18b 100644 --- a/services/wecom/validator/validator.go +++ b/services/wecom/validator/validator.go @@ -3,37 +3,42 @@ package validator import "lone-services/pkg/validate" type CreateUserValidator struct { - UserId int64 `validate:"required,min=1"` - Type uint32 `validate:"required,oneof=1 2"` - Name string `validate:"required,max=64"` - Mobile string `validate:"required,mobile"` - Avatar string `validate:"omitempty,max=1024"` - Status uint32 `validate:"omitempty,oneof=1 2"` + UserId int64 `validate:"required,min=1"` + Type uint32 `validate:"required,oneof=1 2"` + Name string `validate:"required,max=64"` + Mobile string `validate:"required,mobile"` + Avatar string `validate:"omitempty,max=1024"` + Status uint32 `validate:"omitempty,oneof=1 2"` + AgentIds []int64 `validate:"required,min=1,dive,gt=0"` } func (p CreateUserValidator) GetMessage() validate.ValidatorMessages { return validate.ValidatorMessages{ - "UserId.required": "业务用户ID不能为空", - "UserId.min": "业务用户ID无效", - "Type.required": "类型不能为空", - "Type.oneof": "类型不对,1:管理员 2:销售", - "Name.required": "姓名不能为空", - "Name.max": "姓名长度不能超过64", - "Mobile.required": "手机号不能为空", - "Mobile.mobile": "手机号格式错误", - "Avatar.max": "头像长度不能超过1024", - "Status.oneof": "状态不对,1:启用 2:禁用", + "UserId.required": "业务用户ID不能为空", + "UserId.min": "业务用户ID无效", + "Type.required": "类型不能为空", + "Type.oneof": "类型不对,1:管理员 2:销售", + "Name.required": "姓名不能为空", + "Name.max": "姓名长度不能超过64", + "Mobile.required": "手机号不能为空", + "Mobile.mobile": "手机号格式错误", + "Avatar.max": "头像长度不能超过1024", + "Status.oneof": "状态不对,1:启用 2:禁用", + "AgentIds.required": "应用不能为空", + "AgentIds.min": "至少选择一个应用", + "AgentIds.gt": "应用ID无效", } } type UpdateUserValidator struct { - Id int64 `validate:"omitempty,min=1"` - UserId int64 `validate:"omitempty,min=1"` - Type uint32 `validate:"omitempty,oneof=1 2"` - Name string `validate:"omitempty,max=64"` - Mobile string `validate:"omitempty,mobile"` - Avatar string `validate:"omitempty,max=1024"` - Status uint32 `validate:"omitempty,oneof=1 2"` + Id int64 `validate:"omitempty,min=1"` + UserId int64 `validate:"omitempty,min=1"` + Type uint32 `validate:"omitempty,oneof=1 2"` + Name string `validate:"omitempty,max=64"` + Mobile string `validate:"omitempty,mobile"` + Avatar string `validate:"omitempty,max=1024"` + Status uint32 `validate:"omitempty,oneof=1 2"` + AgentIds []int64 `validate:"omitempty,dive,gt=0"` } func (p UpdateUserValidator) GetMessage() validate.ValidatorMessages { @@ -45,6 +50,7 @@ func (p UpdateUserValidator) GetMessage() validate.ValidatorMessages { "Mobile.mobile": "手机号格式错误", "Avatar.max": "头像长度不能超过1024", "Status.oneof": "状态不对,1:启用 2:禁用", + "AgentIds.gt": "应用ID无效", } }