服务端: - IssueResponse 移除 isResolved,统一使用 status 字段 - IssueEventResponse 重构为 ExceptionInfo 嵌套结构,breadcrumbs/tags/device 序列化为 Object - IssueResponse 新增 affectedUsers 字段,通过 log_issue_users 去重计数 - WebhookResponse 将 secret 替换为 hasSecret boolean - WebhookRequest 新增 secret 字段 - LogWebhookEntity 新增 secret 列 - 新增 LogIssueUserEntity / LogIssueUserRepository 实现 affectedUsers 去重 - LogIssueRepository.findByFilters 新增 assignee、environment 过滤 - LogService.queryIssues 新增 assignee、environment 参数 - LogService.toIssueResponse 修正 IssueResponse 构造(移除 isResolved) - LogService.toIssueEventResponse 修正 ExceptionInfo 构造,JSON 字段反序列化为 Object - LogController:assign 改为 @RequestBody;GET /issues 新增 assignee/environment 参数;upload 新增 bundleVersion 参数 - WebhookService:事件类型匹配改为 issue.created/issue.fatal/issue.error/issue.warning/issue.updated;新增 HMAC-SHA256 X-BugCollect-Signature 签名;toResponse 修正为 hasSecret - SourcemapService:按平台生成正确后缀(.android.bundle.map/.ios.bundle.map);新增 bundleVersion 参数;200MB 大小限制;.map 扩展名校验 - V4 迁移 SQL:log_issue_users 表、log_webhooks.secret 列、log_sessions 表、event_id 索引 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
58 行
1.7 KiB
Java
58 行
1.7 KiB
Java
package com.xuqm.bugcollect.entity;
|
||
|
||
import jakarta.persistence.*;
|
||
import java.time.LocalDateTime;
|
||
|
||
/**
|
||
* 记录每个 Issue 影响的唯一用户,用于正确计算 affectedUsers(去重)。
|
||
* 通过联合主键 (issue_id, user_id) 保证唯一性。
|
||
*/
|
||
@Entity
|
||
@Table(name = "log_issue_users")
|
||
@IdClass(LogIssueUserEntity.IssueUserId.class)
|
||
public class LogIssueUserEntity {
|
||
|
||
@Id
|
||
@Column(nullable = false)
|
||
private Long issueId;
|
||
|
||
@Id
|
||
@Column(nullable = false, length = 128)
|
||
private String userId;
|
||
|
||
@Column(nullable = false)
|
||
private LocalDateTime firstSeenAt;
|
||
|
||
public Long getIssueId() { return issueId; }
|
||
public void setIssueId(Long issueId) { this.issueId = issueId; }
|
||
|
||
public String getUserId() { return userId; }
|
||
public void setUserId(String userId) { this.userId = userId; }
|
||
|
||
public LocalDateTime getFirstSeenAt() { return firstSeenAt; }
|
||
public void setFirstSeenAt(LocalDateTime firstSeenAt) { this.firstSeenAt = firstSeenAt; }
|
||
|
||
public static class IssueUserId implements java.io.Serializable {
|
||
private Long issueId;
|
||
private String userId;
|
||
|
||
public IssueUserId() {}
|
||
public IssueUserId(Long issueId, String userId) {
|
||
this.issueId = issueId;
|
||
this.userId = userId;
|
||
}
|
||
|
||
@Override
|
||
public boolean equals(Object o) {
|
||
if (this == o) return true;
|
||
if (!(o instanceof IssueUserId that)) return false;
|
||
return java.util.Objects.equals(issueId, that.issueId) && java.util.Objects.equals(userId, that.userId);
|
||
}
|
||
|
||
@Override
|
||
public int hashCode() {
|
||
return java.util.Objects.hash(issueId, userId);
|
||
}
|
||
}
|
||
}
|