// Package app 提供移动端(H5/小程序)的 HTTP 处理器 // 包含个人客户认证、注册、微信绑定等功能的 Handler 实现 package app import ( "github.com/break/junhong_cmp_fiber/internal/service/personal_customer" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/break/junhong_cmp_fiber/pkg/response" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) // PersonalCustomerHandler 个人客户处理器 type PersonalCustomerHandler struct { service *personal_customer.Service logger *zap.Logger } // NewPersonalCustomerHandler 创建个人客户处理器实例 func NewPersonalCustomerHandler(service *personal_customer.Service, logger *zap.Logger) *PersonalCustomerHandler { return &PersonalCustomerHandler{ service: service, logger: logger, } } // SendCodeRequest 发送验证码请求 type SendCodeRequest struct { Phone string `json:"phone" validate:"required,len=11"` // 手机号(11位) } // SendCode 发送验证码 // POST /api/c/v1/login/send-code func (h *PersonalCustomerHandler) SendCode(c *fiber.Ctx) error { var req SendCodeRequest if err := c.BodyParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数解析失败") } // 发送验证码 if err := h.service.SendVerificationCode(c.Context(), req.Phone); err != nil { h.logger.Error("发送验证码失败", zap.String("phone", req.Phone), zap.Error(err), ) return errors.Wrap(errors.CodeInternalError, "发送验证码失败", err) } return response.Success(c, fiber.Map{ "message": "验证码已发送", }) } // LoginRequest 登录请求 type LoginRequest struct { Phone string `json:"phone" validate:"required,len=11"` // 手机号(11位) Code string `json:"code" validate:"required,len=6"` // 验证码(6位) } // LoginResponse 登录响应 type LoginResponse struct { Token string `json:"token"` // 访问令牌 Customer *PersonalCustomerDTO `json:"customer"` // 客户信息 } // PersonalCustomerDTO 个人客户 DTO type PersonalCustomerDTO struct { ID uint `json:"id"` Phone string `json:"phone"` Nickname string `json:"nickname"` AvatarURL string `json:"avatar_url"` WxOpenID string `json:"wx_open_id"` Status int `json:"status"` } // Login 登录(手机号 + 验证码) // POST /api/c/v1/login func (h *PersonalCustomerHandler) Login(c *fiber.Ctx) error { var req LoginRequest if err := c.BodyParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数解析失败") } // 登录 token, customer, err := h.service.LoginByPhone(c.Context(), req.Phone, req.Code) if err != nil { h.logger.Error("登录失败", zap.String("phone", req.Phone), zap.Error(err), ) return errors.Wrap(errors.CodeInternalError, "登录失败", err) } // 构造响应 // 注意:Phone 字段已从 PersonalCustomer 模型移除,需要从 PersonalCustomerPhone 表查询 resp := &LoginResponse{ Token: token, Customer: &PersonalCustomerDTO{ ID: customer.ID, Phone: req.Phone, // 使用请求中的手机号(临时方案) Nickname: customer.Nickname, AvatarURL: customer.AvatarURL, WxOpenID: customer.WxOpenID, Status: customer.Status, }, } return response.Success(c, resp) } // BindWechatRequest 绑定微信请求 type BindWechatRequest struct { Code string `json:"code" validate:"required"` // 微信授权码 } // BindWechat 绑定微信 // POST /api/c/v1/bind-wechat // TODO: 实现微信 OAuth 授权逻辑 func (h *PersonalCustomerHandler) BindWechat(c *fiber.Ctx) error { var req BindWechatRequest if err := c.BodyParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数解析失败") } // TODO: 实现完整的微信绑定流程 // 1. 从 context 中获取当前登录的客户 ID // 2. 使用微信授权码换取 OpenID 和 UnionID // 3. 调用 service 层的 BindWechat 方法绑定微信 return response.Success(c, fiber.Map{ "message": "微信绑定功能暂未实现,待微信 SDK 对接后启用", }) } // UpdateProfileRequest 更新个人资料请求 type UpdateProfileRequest struct { Nickname string `json:"nickname"` // 昵称 AvatarURL string `json:"avatar_url"` // 头像 URL } // UpdateProfile 更新个人资料 // PUT /api/c/v1/profile func (h *PersonalCustomerHandler) UpdateProfile(c *fiber.Ctx) error { var req UpdateProfileRequest if err := c.BodyParser(&req); err != nil { return errors.New(errors.CodeInvalidParam, "请求参数解析失败") } // 从 context 中获取当前登录的客户 ID customerID, ok := c.Locals("customer_id").(uint) if !ok { return errors.New(errors.CodeUnauthorized, "未找到客户信息") } if err := h.service.UpdateProfile(c.Context(), customerID, req.Nickname, req.AvatarURL); err != nil { h.logger.Error("更新个人资料失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return errors.Wrap(errors.CodeInternalError, "更新个人资料失败", err) } return response.Success(c, fiber.Map{ "message": "更新成功", }) } // GetProfile 获取个人资料 // GET /api/c/v1/profile func (h *PersonalCustomerHandler) GetProfile(c *fiber.Ctx) error { // 从 context 中获取当前登录的客户 ID customerID, ok := c.Locals("customer_id").(uint) if !ok { return errors.New(errors.CodeUnauthorized, "未找到客户信息") } // 获取客户资料(包含主手机号) customer, phone, err := h.service.GetProfileWithPhone(c.Context(), customerID) if err != nil { h.logger.Error("获取个人资料失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return errors.Wrap(errors.CodeInternalError, "获取个人资料失败", err) } // 构造响应 resp := &PersonalCustomerDTO{ ID: customer.ID, Phone: phone, // 使用查询到的主手机号 Nickname: customer.Nickname, AvatarURL: customer.AvatarURL, WxOpenID: customer.WxOpenID, Status: customer.Status, } return response.Success(c, resp) }