将所有 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>
203 lines
6.0 KiB
Go
203 lines
6.0 KiB
Go
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)
|
||
}
|