fix(canvas): 画布任务恢复与价格邮件展示 - 修复刷新找回和名称同步

This commit is contained in:
Rou
2026-09-06 00:44:08 +08:00
parent 61f13d8d4a
commit d4d8a9decd
8 changed files with 80 additions and 24 deletions

View File

@@ -195,9 +195,8 @@ func (s *Service) SendRegistrationEmailCode(rawEmail string) error {
if err := s.repo.Create(&record); err != nil {
return err
}
brandName := s.appearanceBrandName()
setting = resolveEmailSender(setting, brandName)
if err := s.deliverEmail(setting, email, brandName+"注册验证码", registrationEmailBody(brandName, code)); err != nil {
setting = resolveEmailSender(setting, s.appearanceBrandName())
if err := s.deliverEmail(setting, email, setting.FromName+"注册验证码", registrationEmailBody(setting.FromName, code)); err != nil {
cleanupErr := s.repo.DeleteEmailVerificationCode(record.ID)
if cleanupErr != nil {
return errors.Join(

View File

@@ -74,9 +74,8 @@ func (s *Service) SendPasswordResetEmailCode(rawEmail string) error {
if err := s.repo.Create(&record); err != nil {
return err
}
brandName := s.appearanceBrandName()
setting = resolveEmailSender(setting, brandName)
if err := s.deliverEmail(setting, email, brandName+"密码重置验证码", passwordResetEmailBody(brandName, code)); err != nil {
setting = resolveEmailSender(setting, s.appearanceBrandName())
if err := s.deliverEmail(setting, email, setting.FromName+"密码重置验证码", passwordResetEmailBody(setting.FromName, code)); err != nil {
if cleanupErr := s.repo.DeleteEmailVerificationCode(record.ID); cleanupErr != nil {
log.Printf("password reset email cleanup failed: recipient=%s error=%v", maskedEmail(email), cleanupErr)
}

View File

@@ -187,8 +187,38 @@ func TestPasswordResetDeliveryFailureRemovesUnusableCode(t *testing.T) {
}
}
func TestCustomAppearanceBrandsRegistrationAndPasswordResetEmails(t *testing.T) {
func TestSenderNameBrandsRegistrationAndPasswordResetEmails(t *testing.T) {
for _, tc := range []struct {
name string
fromName string
wantName string
}{
{name: "default inherits appearance", fromName: defaultAppearanceBrandName, wantName: "HIMA Studio"},
{name: "empty inherits appearance", fromName: "", wantName: "HIMA Studio"},
{name: "custom sender overrides appearance", fromName: "Custom Canvas", wantName: "Custom Canvas"},
{name: "custom sender is trimmed", fromName: " 自定义工作台 ", wantName: "自定义工作台"},
} {
t.Run(tc.name, func(t *testing.T) {
assertRegistrationAndPasswordResetEmailBrand(t, tc.fromName, tc.wantName)
})
}
}
func assertRegistrationAndPasswordResetEmailBrand(t *testing.T, fromName, wantName string) {
t.Helper()
svc, db := newPasswordResetTestService(t)
_, emailSetting, err := svc.readEmailSetting()
if err != nil {
t.Fatal(err)
}
emailSetting.FromName = fromName
settingJSON, err := json.Marshal(emailSetting)
if err != nil {
t.Fatal(err)
}
if err := db.Model(&model.SystemSetting{}).Where("key = ?", emailSettingKey).Update("value_json", string(settingJSON)).Error; err != nil {
t.Fatal(err)
}
appearanceJSON, err := json.Marshal(AppearanceSetting{
SchemaVersion: appearanceSchemaVersion,
BrandName: "HIMA Studio",
@@ -237,13 +267,16 @@ func TestCustomAppearanceBrandsRegistrationAndPasswordResetEmails(t *testing.T)
t.Fatalf("deliveries = %d, want 2", len(deliveries))
}
for _, delivered := range deliveries {
if delivered.fromName != "HIMA Studio" || !strings.Contains(delivered.subject, "HIMA Studio") || !strings.Contains(delivered.body, "HIMA Studio") || strings.Contains(delivered.subject, defaultAppearanceBrandName) || strings.Contains(delivered.body, defaultAppearanceBrandName) {
t.Fatalf("email did not inherit custom appearance: %#v", delivered)
if delivered.fromName != wantName || !strings.Contains(delivered.subject, wantName) || !strings.Contains(delivered.body, wantName) || strings.Contains(delivered.subject, defaultAppearanceBrandName) || strings.Contains(delivered.body, defaultAppearanceBrandName) {
t.Fatalf("email did not use resolved sender name %q: %#v", wantName, delivered)
}
}
if deliveries[0].subject != "HIMA Studio注册验证码" || deliveries[1].subject != "HIMA Studio密码重置验证码" {
if deliveries[0].subject != wantName+"注册验证码" || deliveries[1].subject != wantName+"密码重置验证码" {
t.Fatalf("unexpected branded subjects: %#v", deliveries)
}
if !strings.Contains(deliveries[0].body, "你正在注册"+wantName+"。") {
t.Fatalf("registration body did not use resolved sender name: %q", deliveries[0].body)
}
}
func newPasswordResetTestService(t *testing.T) (*Service, *gorm.DB) {

View File

@@ -41,6 +41,7 @@ type TaskSummary struct {
}
type TaskClientContext struct {
NodeID string `json:"nodeId,omitempty"`
ConversationID string `json:"conversationId,omitempty"`
MessageID string `json:"messageId,omitempty"`
BatchIndex int `json:"batchIndex,omitempty"`
@@ -134,6 +135,7 @@ func taskClientContext(raw string) *TaskClientContext {
var input struct {
Metadata struct {
Source string `json:"source"`
NodeID string `json:"nodeId"`
ConversationID string `json:"conversationId"`
MessageID string `json:"messageId"`
BatchIndex int `json:"batchIndex"`
@@ -150,16 +152,20 @@ func taskClientContext(raw string) *TaskClientContext {
return nil
}
metadata := input.Metadata
context := &TaskClientContext{NodeID: metadata.NodeID}
if metadata.Source == "create-page" && metadata.ConversationID != "" && metadata.MessageID != "" {
return &TaskClientContext{
ConversationID: metadata.ConversationID,
MessageID: metadata.MessageID,
BatchIndex: metadata.BatchIndex,
BatchCount: metadata.BatchCount,
}
context.ConversationID = metadata.ConversationID
context.MessageID = metadata.MessageID
context.BatchIndex = metadata.BatchIndex
context.BatchCount = metadata.BatchCount
return context
}
if metadata.ShotID != "" && metadata.WorkflowStepID != "" {
return &TaskClientContext{DomainProjectID: metadata.DomainProjectID, ShotID: metadata.ShotID, WorkflowStepID: metadata.WorkflowStepID, ArtifactType: metadata.ArtifactType}
context.DomainProjectID = metadata.DomainProjectID
context.ShotID = metadata.ShotID
context.WorkflowStepID = metadata.WorkflowStepID
context.ArtifactType = metadata.ArtifactType
return context
}
chapterOperation := ""
if metadata.Operation == "chapter_character_breakdown" {
@@ -168,13 +174,15 @@ func taskClientContext(raw string) *TaskClientContext {
chapterOperation = "storyboard"
}
if chapterOperation == "" || metadata.DomainProjectID == "" || metadata.ChapterID == "" {
return nil
}
return &TaskClientContext{
DomainProjectID: metadata.DomainProjectID,
ChapterID: metadata.ChapterID,
ChapterOperation: chapterOperation,
if context.NodeID == "" {
return nil
}
return context
}
context.DomainProjectID = metadata.DomainProjectID
context.ChapterID = metadata.ChapterID
context.ChapterOperation = chapterOperation
return context
}
// 列表只暴露首个可访问媒体地址,避免把完整生成结果和内嵌数据带回前端。

View File

@@ -51,6 +51,13 @@ func TestTaskClientContextRequiresCreatePageMetadata(t *testing.T) {
}
}
func TestTaskClientContextPreservesCanvasNodeID(t *testing.T) {
context := taskClientContext(`{"mode":"image","metadata":{"nodeId":"canvas-node-1","source":"canvas"}}`)
if context == nil || context.NodeID != "canvas-node-1" {
t.Fatalf("canvas node context was not preserved: %+v", context)
}
}
func TestTaskClientContextProjectsChapterOperations(t *testing.T) {
characters := taskClientContext(`{"metadata":{"domainProjectId":"project-1","chapterId":"chapter-1","operation":"chapter_character_breakdown"}}`)
if characters == nil || characters.DomainProjectID != "project-1" || characters.ChapterID != "chapter-1" || characters.ChapterOperation != "characters" {