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
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
|
|
}
|
|
|