feat(sample): 集成 Sentry 异常监控功能

- 添加 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 版本定义
这个提交包含在:
XuqmGroup 2026-04-24 16:46:38 +08:00
父节点 3285dfe79c
当前提交 f79a27862f
共有 6 个文件被更改,包括 190 次插入11 次删除

查看文件

@ -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:

查看文件

@ -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; }
}

查看文件

@ -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<ServiceActivationRequestEntity, String> {
Optional<ServiceActivationRequestEntity> findFirstByAppIdAndPlatformAndServiceTypeOrderByCreatedAtDesc(
String appId, FeatureServiceEntity.Platform platform, FeatureServiceEntity.ServiceType serviceType);
List<ServiceActivationRequestEntity> findByAppIdOrderByCreatedAtDesc(String appId);
Page<ServiceActivationRequestEntity> findByStatusOrderByCreatedAtDesc(Status status, Pageable pageable);
Page<ServiceActivationRequestEntity> findAllByOrderByCreatedAtDesc(Pageable pageable);
}

查看文件

@ -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<FeatureServiceEntity> 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<ServiceActivationRequestEntity> listRequestsByApp(String appId) {
return requestRepository.findByAppIdOrderByCreatedAtDesc(appId);
}
public FeatureServiceEntity getOrFail(String appId, FeatureServiceEntity.Platform platform,

查看文件

@ -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:

查看文件

@ -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: