docs(api): 添加联调接口文档并实现功能服务管理

- 创建了完整的 API 联调文档,包含各服务地址、ID 约定和鉴权规则
- 实现了 FeatureServiceManager 用于管理服务激活和配置功能
- 添加了安全配置确保各服务间正确的身份验证机制
- 定义了统一的响应格式和错误码处理规范
- 集成了 IM、推送和更新服务的管理接口实现
这个提交包含在:
XuqmGroup 2026-04-29 16:07:22 +08:00
父节点 c3968e808d
当前提交 d13c6c9bc5
共有 3 个文件被更改,包括 40 次插入26 次删除

查看文件

@ -26,6 +26,7 @@
- `tenant-platform` 的“服务配置”只认租户 `app.id`
- `tenant-platform` 的“IM 管理”必须带 `appKey`
- `tenant-platform` 的“离线推送 / 版本管理”只需要一次开通,平台差异配置在各自的管理页里维护,不再按 Android / iOS / 鸿蒙分别申请开通
- `im-service` 代码里沿用旧参数名 `appId`,但这是历史命名,调用方传的是 `appKey`
## 初始化管理员账号

查看文件

@ -39,19 +39,21 @@ public class FeatureServiceManager {
}
List<FeatureServiceEntity> normalized = new ArrayList<>();
services.stream()
.filter(service -> service.getServiceType() == FeatureServiceEntity.ServiceType.IM)
.findFirst()
.ifPresent(normalized::add);
services.stream()
.filter(service -> service.getServiceType() != FeatureServiceEntity.ServiceType.IM)
.forEach(normalized::add);
for (FeatureServiceEntity.ServiceType serviceType : List.of(
FeatureServiceEntity.ServiceType.IM,
FeatureServiceEntity.ServiceType.PUSH,
FeatureServiceEntity.ServiceType.UPDATE)) {
services.stream()
.filter(service -> service.getServiceType() == serviceType)
.findFirst()
.ifPresent(normalized::add);
}
return normalized.isEmpty() ? services : normalized;
}
/**
* Submit an activation request. Disabling is immediate; enabling requires ops approval.
* IM is app-wide, so duplicate checks ignore platform.
* IM / PUSH / UPDATE are app-wide, so duplicate checks ignore platform.
*/
@Transactional
public ServiceActivationRequestEntity submitActivationRequest(
@ -60,20 +62,13 @@ public class FeatureServiceManager {
FeatureServiceEntity.ServiceType serviceType,
String applyReason) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
if (isAppWideService(serviceType)) {
requestRepository.findFirstByAppIdAndServiceTypeOrderByCreatedAtDesc(appId, serviceType)
.ifPresent(req -> {
if (req.getStatus() == Status.PENDING) {
throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理");
}
});
} else {
requestRepository.findFirstByAppIdAndPlatformAndServiceTypeOrderByCreatedAtDesc(appId, platform, serviceType)
.ifPresent(req -> {
if (req.getStatus() == Status.PENDING) {
throw new BusinessException(400, "已有待审核的开通申请,请等待运营人员处理");
}
});
}
ServiceActivationRequestEntity req = new ServiceActivationRequestEntity();
@ -93,7 +88,7 @@ public class FeatureServiceManager {
@Transactional
public FeatureServiceEntity disable(String appId, FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {
if (serviceType == FeatureServiceEntity.ServiceType.IM) {
if (isAppWideService(serviceType)) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(appId, serviceType);
if (services.isEmpty()) {
throw new BusinessException(404, "服务未开通");
@ -125,17 +120,19 @@ public class FeatureServiceManager {
req.setReviewedAt(LocalDateTime.now());
requestRepository.save(req);
if (req.getServiceType() == FeatureServiceEntity.ServiceType.IM) {
if (isAppWideService(req.getServiceType())) {
List<FeatureServiceEntity> services = repository.findByAppIdAndServiceType(req.getAppId(), req.getServiceType());
if (services.isEmpty()) {
FeatureServiceEntity created = new FeatureServiceEntity();
created.setId(UUID.randomUUID().toString());
created.setAppId(req.getAppId());
created.setPlatform(req.getPlatform());
created.setServiceType(req.getServiceType());
created.setEnabled(true);
created.setCreatedAt(LocalDateTime.now());
repository.save(created);
for (FeatureServiceEntity.Platform platform : FeatureServiceEntity.Platform.values()) {
FeatureServiceEntity created = new FeatureServiceEntity();
created.setId(UUID.randomUUID().toString());
created.setAppId(req.getAppId());
created.setPlatform(platform);
created.setServiceType(req.getServiceType());
created.setEnabled(true);
created.setCreatedAt(LocalDateTime.now());
repository.save(created);
}
} else {
services.forEach(service -> service.setEnabled(true));
repository.saveAll(services);
@ -366,6 +363,12 @@ public class FeatureServiceManager {
};
}
private boolean isAppWideService(FeatureServiceEntity.ServiceType serviceType) {
return serviceType == FeatureServiceEntity.ServiceType.IM
|| serviceType == FeatureServiceEntity.ServiceType.PUSH
|| serviceType == FeatureServiceEntity.ServiceType.UPDATE;
}
private JsonNode readConfigNode(String appId,
FeatureServiceEntity.Platform platform,
FeatureServiceEntity.ServiceType serviceType) {

查看文件

@ -1,5 +1,7 @@
package com.xuqm.update.config;
import com.xuqm.common.security.JwtAuthFilter;
import com.xuqm.common.security.JwtUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@ -8,6 +10,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@ -18,6 +21,12 @@ import java.util.List;
@EnableWebSecurity
public class SecurityConfig {
private final JwtUtil jwtUtil;
public SecurityConfig(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
@ -35,6 +44,7 @@ public class SecurityConfig {
).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class)
.httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable);
return http.build();