You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

136 lines
4.0 KiB

package memory
import (
"strings"
"time"
"yunyan/comm"
"yunyan/pb"
)
// 分页与范围上限。放常量而不是散在各处:这些值同时约束着「数据导出拉全量」
// 与「月视图一次拉一个月」两种用法,改一处即可。
const (
defaultListSize = 200
maxListSize = 1000
maxUpcomingDays = 30
maxTitleLen = 200
maxBatchIDs = 200
)
// requireUID 取会话 uid。所有接口的第一步。
func requireUID(session comm.IUserSession) (string, *pb.ErrorData) {
uid := session.GetUserId()
if uid == "" {
return "", &pb.ErrorData{Code: pb.ErrorCode_NoLogin, Message: pb.ErrorCode_NoLogin.String()}
}
return uid, nil
}
// normalizeItem 校验并规整一条待写入的记忆项。
//
// 客户端传上来的 item 里,凡是不该由客户端决定的字段一律在这里覆盖掉
// (uid/id/create_time/gen_round/source_id…),不要指望调用方老实。
func normalizeItem(uid string, item *pb.DBMemoryItem) *pb.ErrorData {
if item == nil {
return &pb.ErrorData{Code: pb.ErrorCode_MemoryParamInvalid, Message: "item 为空"}
}
item.Category = strings.ToLower(strings.TrimSpace(item.Category))
if item.Category == "" {
item.Category = comm.MemoryCatTodo
}
if !comm.IsValidMemoryCategory(item.Category) {
return &pb.ErrorData{Code: pb.ErrorCode_MemoryCategoryInvalid, Message: "不支持的分类: " + item.Category}
}
item.Title = strings.TrimSpace(item.Title)
if item.Title == "" {
return &pb.ErrorData{Code: pb.ErrorCode_MemoryParamInvalid, Message: "标题不能为空"}
}
if len([]rune(item.Title)) > maxTitleLen {
item.Title = string([]rune(item.Title)[:maxTitleLen])
}
// happen_date 永不为空——这是拾忆页能工作的前提,日历按日期排,
// 没有日期的项根本渲染不出来。客户端没给就落在它自己的「今天」。
item.HappenDate = strings.TrimSpace(item.HappenDate)
if item.HappenDate == "" {
loc := comm.LoadMemoryLocation(item.Tz)
item.HappenDate = comm.FormatMemoryDate(time.Now().In(loc))
item.DateCertain = false
} else if _, ok := comm.ParseMemoryDate(item.HappenDate); !ok {
return &pb.ErrorData{Code: pb.ErrorCode_MemoryParamInvalid, Message: "happen_date 格式应为 YYYY-MM-DD"}
}
if item.HappenTime != "" {
if _, _, ok := comm.ParseMemoryClock(item.HappenTime); !ok {
return &pb.ErrorData{Code: pb.ErrorCode_MemoryParamInvalid, Message: "happen_time 格式应为 HH:mm"}
}
}
if !comm.IsValidMemoryRepeat(item.RepeatRule) {
return &pb.ErrorData{Code: pb.ErrorCode_MemoryParamInvalid, Message: "不支持的重复规则: " + item.RepeatRule}
}
if item.RepeatRule == comm.MemoryRepeatWeekly && (item.Weekday < 1 || item.Weekday > 7) {
// 没给星期几就按 happen_date 那天推出来,别让它落成 0 永远匹配不上
if d, ok := comm.ParseMemoryDate(item.HappenDate); ok {
w := int32(d.Weekday())
if w == 0 {
w = 7
}
item.Weekday = w
} else {
item.Weekday = 1
}
}
if item.AmountCents < 0 {
return &pb.ErrorData{Code: pb.ErrorCode_MemoryParamInvalid, Message: "金额不能为负"}
}
if item.Currency == "" {
item.Currency = "CNY"
}
if item.RemindAhead < 0 {
item.RemindAhead = 0
}
if item.Source == "" {
item.Source = comm.MemorySrcManual
}
// client_key 服务端保证非空:它参与 (uid, client_key) 唯一索引,而 MySQL 的
// 唯一索引不像放行 NULL 那样放行空串,留空会让同一用户的第二条记录插不进去。
if strings.TrimSpace(item.ClientKey) == "" {
item.ClientKey = comm.NewMemoryClientKey()
}
item.Uid = uid
return nil
}
// clampListSize 分页尺寸夹取
func clampListSize(size int32) int32 {
if size <= 0 {
return defaultListSize
}
if size > maxListSize {
return maxListSize
}
return size
}
// clampIDs 批量操作的 id 数量夹取,顺带去重
func clampIDs(ids []uint64) []uint64 {
seen := map[uint64]bool{}
out := make([]uint64, 0, len(ids))
for _, id := range ids {
if id == 0 || seen[id] {
continue
}
seen[id] = true
out = append(out, id)
if len(out) >= maxBatchIDs {
break
}
}
return out
}