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 版本定义
这个提交包含在:
父节点
3285dfe79c
当前提交
f79a27862f
@ -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:
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户