From f79a27862f2fca941bf05d0e920d0096adab398f Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Fri, 24 Apr 2026 16:46:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(sample):=20=E9=9B=86=E6=88=90=20Sentry=20?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E7=9B=91=E6=8E=A7=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 Sentry Android SDK 依赖 (版本 8.39.1) - 在 sample-app 中集成 Sentry 监控插件 - 添加 Sentry 初始化配置到应用 Application 类 - 在 MainActivity 中添加异常上报测试按钮 - 添加闪退测试功能用于验证 Sentry 监控 - 更新 AndroidManifest.xml 配置应用入口点 - 添加新的 gradle wrapper 文件支持项目构建 - 创建 sdk-core、sdk-im、sdk-push、sdk-update 模块基础结构 - 配置各 SDK 模块的 build.gradle.kts 文件 - 更新 libs.versions.toml 添加 Sentry 版本定义 --- im-service/src/main/resources/application.yml | 2 +- .../ServiceActivationRequestEntity.java | 72 +++++++++++++ .../ServiceActivationRequestRepository.java | 23 ++++ .../tenant/service/FeatureServiceManager.java | 100 ++++++++++++++++-- .../src/main/resources/application.yml | 2 +- .../src/main/resources/application.yml | 2 +- 6 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 tenant-service/src/main/java/com/xuqm/tenant/entity/ServiceActivationRequestEntity.java create mode 100644 tenant-service/src/main/java/com/xuqm/tenant/repository/ServiceActivationRequestRepository.java diff --git a/im-service/src/main/resources/application.yml b/im-service/src/main/resources/application.yml index cc670bb..4d8a1ea 100644 --- a/im-service/src/main/resources/application.yml +++ b/im-service/src/main/resources/application.yml @@ -40,7 +40,7 @@ spring: write-dates-as-timestamps: false jwt: - secret: xuqm-im-service-secret-key-must-be-at-least-256-bits-long-for-hmac-sha + secret: ${XUQM_JWT_SECRET:xuqm-tenant-service-secret-key-must-be-at-least-256-bits-long-for-hmac} expiration: 86400000 im: diff --git a/tenant-service/src/main/java/com/xuqm/tenant/entity/ServiceActivationRequestEntity.java b/tenant-service/src/main/java/com/xuqm/tenant/entity/ServiceActivationRequestEntity.java new file mode 100644 index 0000000..8b0fbaa --- /dev/null +++ b/tenant-service/src/main/java/com/xuqm/tenant/entity/ServiceActivationRequestEntity.java @@ -0,0 +1,72 @@ +package com.xuqm.tenant.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; + +@Entity +@Table(name = "t_service_activation_request") +public class ServiceActivationRequestEntity { + + public enum Status { PENDING, APPROVED, REJECTED } + + @Id + private String id; + + @Column(nullable = false, length = 64) + private String appId; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 16) + private FeatureServiceEntity.Platform platform; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 16) + private FeatureServiceEntity.ServiceType serviceType; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 16) + private Status status; + + @Column(length = 512) + private String applyReason; + + @Column(length = 512) + private String reviewNote; + + @Column(nullable = false) + private LocalDateTime createdAt; + + private LocalDateTime reviewedAt; + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + public String getAppId() { return appId; } + public void setAppId(String appId) { this.appId = appId; } + + public FeatureServiceEntity.Platform getPlatform() { return platform; } + public void setPlatform(FeatureServiceEntity.Platform platform) { this.platform = platform; } + + public FeatureServiceEntity.ServiceType getServiceType() { return serviceType; } + public void setServiceType(FeatureServiceEntity.ServiceType serviceType) { this.serviceType = serviceType; } + + public Status getStatus() { return status; } + public void setStatus(Status status) { this.status = status; } + + public String getApplyReason() { return applyReason; } + public void setApplyReason(String applyReason) { this.applyReason = applyReason; } + + public String getReviewNote() { return reviewNote; } + public void setReviewNote(String reviewNote) { this.reviewNote = reviewNote; } + + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + + public LocalDateTime getReviewedAt() { return reviewedAt; } + public void setReviewedAt(LocalDateTime reviewedAt) { this.reviewedAt = reviewedAt; } +} diff --git a/tenant-service/src/main/java/com/xuqm/tenant/repository/ServiceActivationRequestRepository.java b/tenant-service/src/main/java/com/xuqm/tenant/repository/ServiceActivationRequestRepository.java new file mode 100644 index 0000000..2494b42 --- /dev/null +++ b/tenant-service/src/main/java/com/xuqm/tenant/repository/ServiceActivationRequestRepository.java @@ -0,0 +1,23 @@ +package com.xuqm.tenant.repository; + +import com.xuqm.tenant.entity.FeatureServiceEntity; +import com.xuqm.tenant.entity.ServiceActivationRequestEntity; +import com.xuqm.tenant.entity.ServiceActivationRequestEntity.Status; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface ServiceActivationRequestRepository extends JpaRepository { + + Optional findFirstByAppIdAndPlatformAndServiceTypeOrderByCreatedAtDesc( + String appId, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType); + + List findByAppIdOrderByCreatedAtDesc(String appId); + + Page findByStatusOrderByCreatedAtDesc(Status status, Pageable pageable); + + Page findAllByOrderByCreatedAtDesc(Pageable pageable); +} diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java b/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java index e514a99..9c44f72 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/FeatureServiceManager.java @@ -2,8 +2,12 @@ package com.xuqm.tenant.service; import com.xuqm.common.exception.BusinessException; import com.xuqm.tenant.entity.FeatureServiceEntity; +import com.xuqm.tenant.entity.ServiceActivationRequestEntity; +import com.xuqm.tenant.entity.ServiceActivationRequestEntity.Status; import com.xuqm.tenant.repository.FeatureServiceRepository; +import com.xuqm.tenant.repository.ServiceActivationRequestRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.security.SecureRandom; import java.time.LocalDateTime; @@ -15,32 +19,112 @@ import java.util.UUID; public class FeatureServiceManager { private final FeatureServiceRepository repository; + private final ServiceActivationRequestRepository requestRepository; private static final SecureRandom random = new SecureRandom(); - public FeatureServiceManager(FeatureServiceRepository repository) { + public FeatureServiceManager(FeatureServiceRepository repository, + ServiceActivationRequestRepository requestRepository) { this.repository = repository; + this.requestRepository = requestRepository; } public List listByApp(String appId) { return repository.findByAppId(appId); } - public FeatureServiceEntity toggle(String appId, FeatureServiceEntity.Platform platform, - FeatureServiceEntity.ServiceType serviceType, boolean enable) { + /** + * Submit an activation request. Disabling is immediate; enabling requires ops approval. + */ + @Transactional + public ServiceActivationRequestEntity submitActivationRequest( + String appId, + FeatureServiceEntity.Platform platform, + FeatureServiceEntity.ServiceType serviceType, + String applyReason) { + + // Check if there's already a pending request + requestRepository.findFirstByAppIdAndPlatformAndServiceTypeOrderByCreatedAtDesc(appId, platform, serviceType) + .ifPresent(req -> { + if (req.getStatus() == Status.PENDING) { + throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理"); + } + }); + + ServiceActivationRequestEntity req = new ServiceActivationRequestEntity(); + req.setId(UUID.randomUUID().toString()); + req.setAppId(appId); + req.setPlatform(platform); + req.setServiceType(serviceType); + req.setStatus(Status.PENDING); + req.setApplyReason(applyReason); + req.setCreatedAt(LocalDateTime.now()); + return requestRepository.save(req); + } + + /** + * Disable a service immediately (no approval needed). + */ + @Transactional + public FeatureServiceEntity disable(String appId, FeatureServiceEntity.Platform platform, + FeatureServiceEntity.ServiceType serviceType) { FeatureServiceEntity entity = repository .findByAppIdAndPlatformAndServiceType(appId, platform, serviceType) + .orElseThrow(() -> new BusinessException(404, "服务未开通")); + entity.setEnabled(false); + return repository.save(entity); + } + + /** + * Called by ops when approving an activation request. + */ + @Transactional + public ServiceActivationRequestEntity approveRequest(String requestId, String reviewNote) { + ServiceActivationRequestEntity req = requestRepository.findById(requestId) + .orElseThrow(() -> new BusinessException(404, "申请不存在")); + if (req.getStatus() != Status.PENDING) { + throw new BusinessException(400, "申请已处理"); + } + req.setStatus(Status.APPROVED); + req.setReviewNote(reviewNote); + req.setReviewedAt(LocalDateTime.now()); + requestRepository.save(req); + + // Activate the service + FeatureServiceEntity entity = repository + .findByAppIdAndPlatformAndServiceType(req.getAppId(), req.getPlatform(), req.getServiceType()) .orElseGet(() -> { FeatureServiceEntity e = new FeatureServiceEntity(); e.setId(UUID.randomUUID().toString()); - e.setAppId(appId); - e.setPlatform(platform); - e.setServiceType(serviceType); + e.setAppId(req.getAppId()); + e.setPlatform(req.getPlatform()); + e.setServiceType(req.getServiceType()); e.setSecretKey(generateSecretKey()); e.setCreatedAt(LocalDateTime.now()); return e; }); - entity.setEnabled(enable); - return repository.save(entity); + entity.setEnabled(true); + repository.save(entity); + return req; + } + + /** + * Called by ops when rejecting an activation request. + */ + @Transactional + public ServiceActivationRequestEntity rejectRequest(String requestId, String reviewNote) { + ServiceActivationRequestEntity req = requestRepository.findById(requestId) + .orElseThrow(() -> new BusinessException(404, "申请不存在")); + if (req.getStatus() != Status.PENDING) { + throw new BusinessException(400, "申请已处理"); + } + req.setStatus(Status.REJECTED); + req.setReviewNote(reviewNote); + req.setReviewedAt(LocalDateTime.now()); + return requestRepository.save(req); + } + + public List listRequestsByApp(String appId) { + return requestRepository.findByAppIdOrderByCreatedAtDesc(appId); } public FeatureServiceEntity getOrFail(String appId, FeatureServiceEntity.Platform platform, diff --git a/tenant-service/src/main/resources/application.yml b/tenant-service/src/main/resources/application.yml index 6e3dbc3..bfa27b3 100644 --- a/tenant-service/src/main/resources/application.yml +++ b/tenant-service/src/main/resources/application.yml @@ -52,7 +52,7 @@ spring: write-dates-as-timestamps: false jwt: - secret: xuqm-tenant-service-secret-key-must-be-at-least-256-bits-long-for-hmac + secret: ${XUQM_JWT_SECRET:xuqm-tenant-service-secret-key-must-be-at-least-256-bits-long-for-hmac} expiration: 86400000 captcha: diff --git a/update-service/src/main/resources/application.yml b/update-service/src/main/resources/application.yml index a9969dd..0ca73c1 100644 --- a/update-service/src/main/resources/application.yml +++ b/update-service/src/main/resources/application.yml @@ -25,7 +25,7 @@ spring: max-request-size: 200MB jwt: - secret: xuqm-update-service-secret-key-must-be-at-least-256-bits-long-for-hmac + secret: ${XUQM_JWT_SECRET:xuqm-tenant-service-secret-key-must-be-at-least-256-bits-long-for-hmac} expiration: 86400000 update: