Files
kevin 1fec77e81f 新增个人中心页面与用户资料自助修改接口
- 新增 GET/PUT /api/me:登录用户可查看并修改自己的 nickname/gender/birthday,空串清空、缺省保持不变,生日校验格式与未来日期
- users 表新增 gender/birthday 字段(迁移 v6)及 model.Date 日期类型,管理员用户接口同步支持
- 前端新增 /profile 个人中心页面、头像下拉入口与登录守卫,保存后同步会话用户信息
- 静态服务对未命中的无扩展名路径回退 index.html,避免刷新前端路由 404
- 重新生成 Swagger 文档,补充 /api/me 与资料字段相关测试
2026-09-21 16:05:23 +08:00

82 lines
1.7 KiB
Go

package model
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"time"
)
const dateLayout = "2006-01-02"
// Date 仅日期字段(YYYY-MM-DD),零值表示未设置,JSON 序列化为 null。
type Date struct {
time.Time
}
// IsZero 是否未设置。
func (d Date) IsZero() bool {
return d.Time.IsZero()
}
// Value 实现 driver.Valuer:零值写入 NULL。
func (d Date) Value() (driver.Value, error) {
if d.Time.IsZero() {
return nil, nil
}
return d.Time.Format(dateLayout), nil
}
// Scan 实现 sql.Scanner,兼容 time.Time、字符串与 []byte。
func (d *Date) Scan(value any) error {
switch v := value.(type) {
case nil:
d.Time = time.Time{}
case time.Time:
d.Time = v
case string:
return d.parse(v)
case []byte:
return d.parse(string(v))
default:
return fmt.Errorf("unsupported date value: %T", value)
}
return nil
}
func (d *Date) parse(value string) error {
if value == "" {
d.Time = time.Time{}
return nil
}
for _, layout := range []string{dateLayout, time.RFC3339} {
if parsed, err := time.Parse(layout, value); err == nil {
d.Time = parsed
return nil
}
}
return errors.New("invalid date: " + value)
}
// MarshalJSON 输出 YYYY-MM-DD,未设置时输出 null。
func (d Date) MarshalJSON() ([]byte, error) {
if d.Time.IsZero() {
return []byte("null"), nil
}
return json.Marshal(d.Time.Format(dateLayout))
}
// UnmarshalJSON 支持 null、空串与 YYYY-MM-DD。
func (d *Date) UnmarshalJSON(data []byte) error {
if string(data) == "null" {
d.Time = time.Time{}
return nil
}
var value string
if err := json.Unmarshal(data, &value); err != nil {
return err
}
return d.parse(value)
}