package memory import ( "yunyan/comm" "yunyan/pb" "google.golang.org/protobuf/proto" ) // Update 修改记忆项 // // 只覆盖 fields 点名的字段,并自动置 user_edited=true —— 会议重新生成时靠这个标记 // 保住用户的修改。fields 为空时按白名单全量覆盖。 // // @Summary 修改记忆项 // @Description 修改一条拾忆条目,自动标记为用户已编辑 // @Tags Memory // @Accept json // @Produce json // @Security BearerAuth // @Param data body pb.MemoryUpdateReq true "请求数据" // @Success 200 {object} comm.HttpResult{data=pb.MemoryUpdateResp} "响应数据" // @Router /api/home/memory_update [post] func (this *apiComp) Update(session comm.IUserSession, req *pb.MemoryUpdateReq) (resp *pb.MemoryUpdateResp, errdata *pb.ErrorData) { uid, errdata := requireUID(session) if errdata != nil { return } if req.Id == 0 || req.Item == nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_MemoryParamInvalid, Message: "缺少 id 或 item"} return } old, err := this.module.model.getItem(uid, req.Id) if err != nil { this.module.Errorf("memory_update uid:%s id:%d err:%v", uid, req.Id, err) errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: "查询失败"} return } if old == nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_MemoryItemNotFound, Message: "记忆项不存在"} return } // 在 old 的副本上套用改动,再整体校验一遍——只校验新值会漏掉 // 「把 category 改成 expense 但没给金额」这类跨字段问题。 // ⚠️ 不能写 merged := *old —— pb 结构体内嵌 protoimpl.MessageState(含 sync.Mutex), // 值拷贝会把锁一起复制走,go vet 直接报 copylocks。 merged := proto.Clone(old).(*pb.DBMemoryItem) fields := normalizeFields(req.Fields) if len(fields) == 0 { fields = make([]string, 0, len(itemFieldWhitelist)) for f := range itemFieldWhitelist { fields = append(fields, f) } } applyFields(merged, req.Item, fields) if errdata = normalizeItem(uid, merged); errdata != nil { return } // 这些字段不接受客户端修改,一律拿回原值 merged.Id = old.Id merged.ClientKey = old.ClientKey merged.Source = old.Source merged.SourceId = old.SourceId merged.GenRound = old.GenRound merged.CreateTime = old.CreateTime merged.UserEdited = true this.module.recalcRemind(merged) if err := this.module.model.saveItem(merged); err != nil { this.module.Errorf("memory_update uid:%s id:%d 保存失败 err:%v", uid, req.Id, err) errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: "保存失败"} return } resp = &pb.MemoryUpdateResp{Item: merged} return } // applyFields 把 src 里被点名的字段搬到 dst func applyFields(dst, src *pb.DBMemoryItem, fields []string) { for _, f := range fields { switch f { case "category": dst.Category = src.Category case "title": dst.Title = src.Title case "detail": dst.Detail = src.Detail case "happen_date": dst.HappenDate = src.HappenDate case "happen_time": dst.HappenTime = src.HappenTime case "date_certain": dst.DateCertain = src.DateCertain case "due_raw": dst.DueRaw = src.DueRaw case "tz": dst.Tz = src.Tz case "repeat_rule": dst.RepeatRule = src.RepeatRule case "repeat_raw": dst.RepeatRaw = src.RepeatRaw case "weekday": dst.Weekday = src.Weekday case "amount_cents": dst.AmountCents = src.AmountCents case "currency": dst.Currency = src.Currency case "owner": dst.Owner = src.Owner case "state": dst.State = src.State case "remind_ahead": dst.RemindAhead = src.RemindAhead case "extra": dst.Extra = src.Extra } } }