Files
junhong_cmp_fiber/internal/handler/app/personal_customer.go
huang 867e97af11 重构:统一 IoT 模型到 internal/model/ 目录
将所有 IoT 相关的数据模型从 internal/iot/model/ 迁移到 internal/model/,
实现全局统一的模型层架构,符合项目横向分层设计原则。

变更内容:
- 迁移 11 个 IoT 模型文件(carrier, iot_card, device, order, package 等)
- 删除 internal/iot/model/ 目录
- 更新文档中的模型路径引用(25 处)
- 创建重构总结文档
- 归档 OpenSpec 变更为 2026-01-12-refactor-iot-model-location
- 创建 model-organization 规格文档

验证结果:
- 编译通过(go build 成功)
- 静态分析通过(go vet 无错误)
- 代码格式通过(go fmt 无变更)
- 无 Go 代码引用旧路径

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-12 16:01:53 +08:00

203 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: 从 context 中获取当前登录的客户 ID
// customerID := c.Locals("customer_id").(uint)
// TODO: 使用微信授权码换取 OpenID 和 UnionID
// wxOpenID, wxUnionID, err := wechatService.GetUserInfo(req.Code)
// TODO: 绑定微信
// if err := h.service.BindWechat(c.Context(), customerID, wxOpenID, wxUnionID); err != nil {
// return errors.Wrap(errors.CodeInternalError, "绑定微信失败", err)
// }
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)
}