// 功能描述:报修现场照片关联与本人受保护读取;不公开照片文件目录。 // 版本:1.0.0。 package user import ( "crypto/sha256" "fmt" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" common "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common" "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/upload" "git.apinb.com/heqiapp/platforms/backend/api/internal/models" "github.com/gin-gonic/gin" "gorm.io/gorm" "time" ) // ticketPhotoRequest 记录添加时间,导入照片不冒充已验证的原始拍摄时间。 type ticketPhotoRequest struct { URI string `json:"uri" binding:"required"` AddedAt time.Time `json:"added_at" binding:"required"` Source string `json:"source" binding:"required,oneof=camera gallery"` } func UploadTicketPhoto(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } upload.UploadTicketPhoto(ctx, account.Identity) } // UploadedTicketPhoto 恢复本人草稿照片,不接受其他账户或任意路径。 func UploadedTicketPhoto(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } upload.ServeOwnedTicketPhoto(ctx, account.Identity, ctx.Param("name")) } // saveTicketPhotos 与工单创建共用事务,任一图片不属于本人时全部回滚。 func saveTicketPhotos(tx *gorm.DB, ticket models.CsTicket, owner string, photos []ticketPhotoRequest) error { seen := map[string]bool{} for index, photo := range photos { if seen[photo.URI] || !upload.OwnsTicketPhoto(owner, photo.URI) || photo.AddedAt.IsZero() || photo.AddedAt.After(time.Now().Add(5*time.Minute)) { return errcode.ErrInvalidArgument } seen[photo.URI] = true evidence := models.CsTicketEvidence{ Entity: common.NewEntity(common.StatusEnable), CsTicketID: ticket.ID, EvidenceType: "reported", MediaType: "image", FileURI: photo.URI, CapturedAt: photo.AddedAt, ReceivedAt: time.Now(), Source: "user_" + photo.Source, IntegrityStatus: "capture_time_unknown", OperatorIdentity: owner, RequestNo: fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("user-photo:%s:%d", ticket.Identity, index)))), } if err := tx.Create(&evidence).Error; err != nil { return err } } return nil } // ticketPhotosForList 一次查询本人列表涉及的证据,只发布元数据与公开证据UUID。 func ticketPhotosForList(tickets []models.CsTicket) (map[uint64][]gin.H, error) { result := map[uint64][]gin.H{} ids := make([]uint64, 0, len(tickets)) for _, ticket := range tickets { ids = append(ids, ticket.ID) } if len(ids) == 0 { return result, nil } var photos []models.CsTicketEvidence if err := impl.DBService.Where("cs_ticket_id IN ? AND evidence_type = ? AND status <> ?", ids, "reported", common.StatusArchived).Order("created_at asc").Find(&photos).Error; err != nil { return nil, err } for _, photo := range photos { result[photo.CsTicketID] = append(result[photo.CsTicketID], gin.H{"identity": photo.Identity, "added_at": photo.CapturedAt, "source": photo.Source, "integrity_status": photo.IntegrityStatus}) } return result, nil } // TicketPhoto 不允许借工单UUID读取其他工单照片,也不允许读取他人报修。 func TicketPhoto(ctx *gin.Context) { account, ok := common.UserAccount(ctx) if !ok { return } var ticket models.CsTicket if impl.DBService.Where("identity = ? AND user_account_id = ? AND status <> ?", ctx.Param("identity"), account.ID, common.StatusArchived).First(&ticket).Error != nil { ctx.Status(404) return } var photo models.CsTicketEvidence if impl.DBService.Where("identity = ? AND cs_ticket_id = ? AND evidence_type = ? AND status <> ?", ctx.Param("photoIdentity"), ticket.ID, "reported", common.StatusArchived).First(&photo).Error != nil { ctx.Status(404) return } upload.ServeTicketPhoto(ctx, account.Identity, photo.FileURI) }