package com.xuqm.update.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Id; import jakarta.persistence.Table; import java.time.LocalDateTime; /** * 一个经过平台签名的 RN 插件发布物。 * *

身份字段全部来自 ZIP 内的 rn-manifest.json,发布、灰度和下线只能改变发布状态, * 不能修改这些不可变字段。历史旧记录缺少 archiveSha256/signature,永远不会进入候选集。

*/ @Entity @Table(name = "update_rn_bundle") public class RnBundleEntity { public enum Platform { ANDROID, IOS } public enum ModuleType { COMMON, APP, BUZ } public enum PublishStatus { DRAFT, PUBLISHED, DEPRECATED } public enum GrayMode { PERCENT, MEMBERS } @Id private String id; @Column(nullable = false, length = 64) private String appKey; @Column(nullable = false, length = 256) private String packageName; @Column(nullable = false, length = 64) private String moduleId; @Enumerated(EnumType.STRING) @Column(nullable = false, length = 16) private ModuleType moduleType; @Enumerated(EnumType.STRING) @Column(nullable = false, length = 16) private Platform platform; @Column(nullable = false, length = 64) private String version; @Column(nullable = false, length = 128) private String appVersionRange; @Column(nullable = false, length = 128) private String builtAgainstNativeBaselineId; @Column(nullable = false, length = 128) private String buildId; @Column(nullable = false, length = 32) private String bundleFormat; @Column(length = 128) private String commonVersionRange; @Column(nullable = false) private Integer minNativeApiLevel; /** ZIP 内真实 JS/Hermes Bundle 的 SHA-256。 */ @Column(nullable = false, length = 64) private String bundleSha256; /** 客户端实际下载并校验的整个 .xuqm.zip 的 SHA-256。 */ @Column(nullable = false, length = 64) private String archiveSha256; @Column(nullable = false, length = 512) private String bundleUrl; /** Ed25519 签名覆盖的规范 JSON,必须与上面的身份字段逐项一致。 */ @Column(nullable = false, columnDefinition = "TEXT") private String signedManifest; @Column(nullable = false, length = 64) private String signingKeyId; @Column(nullable = false, length = 256) private String signature; @Column(length = 512) private String note; @Enumerated(EnumType.STRING) @Column(nullable = false, length = 16) private PublishStatus publishStatus; @Column(length = 16) private String publishMode = "MANUAL"; private LocalDateTime scheduledPublishAt; @Column(nullable = false) private boolean grayEnabled; @Column(nullable = false) private int grayPercent; @Enumerated(EnumType.STRING) @Column(nullable = false, length = 24) private GrayMode grayMode = GrayMode.PERCENT; @Column(columnDefinition = "TEXT") private String grayMemberIds; @Column(columnDefinition = "TEXT") private String grayGroupNames; @Column(columnDefinition = "TEXT") private String extraMemberIds; @Column(nullable = false) private LocalDateTime createdAt; public boolean hasTerminalIdentity() { return hasText(packageName) && moduleType != null && hasText(appVersionRange) && hasText(builtAgainstNativeBaselineId) && hasText(buildId) && hasText(bundleFormat) && minNativeApiLevel != null && minNativeApiLevel > 0 && isSha256(bundleSha256) && isSha256(archiveSha256) && hasText(signedManifest) && hasText(signingKeyId) && hasText(signature); } private static boolean hasText(String value) { return value != null && !value.isBlank(); } private static boolean isSha256(String value) { return value != null && value.matches("(?i)[a-f0-9]{64}"); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getAppKey() { return appKey; } public void setAppKey(String appKey) { this.appKey = appKey; } public String getPackageName() { return packageName; } public void setPackageName(String packageName) { this.packageName = packageName; } public String getModuleId() { return moduleId; } public void setModuleId(String moduleId) { this.moduleId = moduleId; } public ModuleType getModuleType() { return moduleType; } public void setModuleType(ModuleType moduleType) { this.moduleType = moduleType; } public Platform getPlatform() { return platform; } public void setPlatform(Platform platform) { this.platform = platform; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getAppVersionRange() { return appVersionRange; } public void setAppVersionRange(String value) { this.appVersionRange = value; } public String getBuiltAgainstNativeBaselineId() { return builtAgainstNativeBaselineId; } public void setBuiltAgainstNativeBaselineId(String value) { this.builtAgainstNativeBaselineId = value; } public String getBuildId() { return buildId; } public void setBuildId(String buildId) { this.buildId = buildId; } public String getBundleFormat() { return bundleFormat; } public void setBundleFormat(String bundleFormat) { this.bundleFormat = bundleFormat; } public String getCommonVersionRange() { return commonVersionRange; } public void setCommonVersionRange(String value) { this.commonVersionRange = value; } public Integer getMinNativeApiLevel() { return minNativeApiLevel; } public void setMinNativeApiLevel(Integer value) { this.minNativeApiLevel = value; } public String getBundleSha256() { return bundleSha256; } public void setBundleSha256(String value) { this.bundleSha256 = value; } public String getArchiveSha256() { return archiveSha256; } public void setArchiveSha256(String value) { this.archiveSha256 = value; } public String getBundleUrl() { return bundleUrl; } public void setBundleUrl(String bundleUrl) { this.bundleUrl = bundleUrl; } public String getSignedManifest() { return signedManifest; } public void setSignedManifest(String value) { this.signedManifest = value; } public String getSigningKeyId() { return signingKeyId; } public void setSigningKeyId(String value) { this.signingKeyId = value; } public String getSignature() { return signature; } public void setSignature(String value) { this.signature = value; } public String getNote() { return note; } public void setNote(String note) { this.note = note; } public PublishStatus getPublishStatus() { return publishStatus; } public void setPublishStatus(PublishStatus value) { this.publishStatus = value; } public String getPublishMode() { return publishMode; } public void setPublishMode(String value) { this.publishMode = value; } public LocalDateTime getScheduledPublishAt() { return scheduledPublishAt; } public void setScheduledPublishAt(LocalDateTime value) { this.scheduledPublishAt = value; } public boolean isGrayEnabled() { return grayEnabled; } public void setGrayEnabled(boolean value) { this.grayEnabled = value; } public int getGrayPercent() { return grayPercent; } public void setGrayPercent(int value) { this.grayPercent = value; } public GrayMode getGrayMode() { return grayMode; } public void setGrayMode(GrayMode value) { this.grayMode = value; } public String getGrayMemberIds() { return grayMemberIds; } public void setGrayMemberIds(String value) { this.grayMemberIds = value; } public String getGrayGroupNames() { return grayGroupNames; } public void setGrayGroupNames(String value) { this.grayGroupNames = value; } public String getExtraMemberIds() { return extraMemberIds; } public void setExtraMemberIds(String value) { this.extraMemberIds = value; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime value) { this.createdAt = value; } }