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

查看文件

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

查看文件

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