feat: admin APIs for IM management and version management
- ImAdminController: list users (paged), ban/unban, list groups, message stats - ImMessageRepository: countByAppId, countTodayByAppId - AppVersionController: unpublish + gray release (grayEnabled, grayPercent) - RnBundleController: list, unpublish, gray release - AppVersionEntity/RnBundleEntity: add grayEnabled, grayPercent fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
37f34876be
当前提交
3285dfe79c
1
.java-version
普通文件
1
.java-version
普通文件
@ -0,0 +1 @@
|
||||
17
|
||||
@ -0,0 +1,76 @@
|
||||
package com.xuqm.im.controller;
|
||||
|
||||
import com.xuqm.common.model.ApiResponse;
|
||||
import com.xuqm.im.entity.ImAccountEntity;
|
||||
import com.xuqm.im.entity.ImGroupEntity;
|
||||
import com.xuqm.im.repository.ImAccountRepository;
|
||||
import com.xuqm.im.repository.ImGroupRepository;
|
||||
import com.xuqm.im.repository.ImMessageRepository;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/im/admin")
|
||||
public class ImAdminController {
|
||||
|
||||
private final ImAccountRepository accountRepository;
|
||||
private final ImGroupRepository groupRepository;
|
||||
private final ImMessageRepository messageRepository;
|
||||
|
||||
public ImAdminController(ImAccountRepository accountRepository,
|
||||
ImGroupRepository groupRepository,
|
||||
ImMessageRepository messageRepository) {
|
||||
this.accountRepository = accountRepository;
|
||||
this.groupRepository = groupRepository;
|
||||
this.messageRepository = messageRepository;
|
||||
}
|
||||
|
||||
/** List all registered IM users for the given appId. */
|
||||
@GetMapping("/users")
|
||||
public ResponseEntity<ApiResponse<Page<ImAccountEntity>>> listUsers(
|
||||
@RequestParam String appId,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
return ResponseEntity.ok(ApiResponse.success(
|
||||
accountRepository.findByAppId(appId, PageRequest.of(page, size))));
|
||||
}
|
||||
|
||||
/** Ban or unban a user. */
|
||||
@PutMapping("/users/{userId}/status")
|
||||
public ResponseEntity<ApiResponse<ImAccountEntity>> updateUserStatus(
|
||||
@RequestParam String appId,
|
||||
@PathVariable String userId,
|
||||
@RequestBody Map<String, String> body) {
|
||||
ImAccountEntity account = accountRepository.findByAppIdAndUserId(appId, userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
account.setStatus(ImAccountEntity.Status.valueOf(body.get("status").toUpperCase()));
|
||||
return ResponseEntity.ok(ApiResponse.success(accountRepository.save(account)));
|
||||
}
|
||||
|
||||
/** List all groups for the given appId. */
|
||||
@GetMapping("/groups")
|
||||
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> listGroups(@RequestParam String appId) {
|
||||
return ResponseEntity.ok(ApiResponse.success(groupRepository.findByAppId(appId)));
|
||||
}
|
||||
|
||||
/** Message statistics for the given appId. */
|
||||
@GetMapping("/stats")
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> stats(@RequestParam String appId) {
|
||||
long totalMessages = messageRepository.countByAppId(appId);
|
||||
long totalUsers = accountRepository.countByAppId(appId);
|
||||
long totalGroups = groupRepository.countByAppId(appId);
|
||||
long todayMessages = messageRepository.countTodayByAppId(appId);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(Map.of(
|
||||
"totalMessages", totalMessages,
|
||||
"totalUsers", totalUsers,
|
||||
"totalGroups", totalGroups,
|
||||
"todayMessages", todayMessages
|
||||
)));
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,14 @@
|
||||
package com.xuqm.im.repository;
|
||||
|
||||
import com.xuqm.im.entity.ImAccountEntity;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ImAccountRepository extends JpaRepository<ImAccountEntity, String> {
|
||||
Optional<ImAccountEntity> findByAppIdAndUserId(String appId, String userId);
|
||||
boolean existsByAppIdAndUserId(String appId, String userId);
|
||||
Page<ImAccountEntity> findByAppId(String appId, Pageable pageable);
|
||||
long countByAppId(String appId);
|
||||
}
|
||||
|
||||
@ -6,4 +6,5 @@ import java.util.List;
|
||||
|
||||
public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String> {
|
||||
List<ImGroupEntity> findByAppId(String appId);
|
||||
long countByAppId(String appId);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public interface ImMessageRepository extends JpaRepository<ImMessageEntity, String> {
|
||||
Page<ImMessageEntity> findByAppIdAndToIdOrderByCreatedAtDesc(
|
||||
@ -26,4 +27,13 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
|
||||
@Param("userId") String userId,
|
||||
@Param("peerId") String peerId,
|
||||
Pageable pageable);
|
||||
|
||||
long countByAppId(String appId);
|
||||
|
||||
@Query("select count(m) from ImMessageEntity m where m.appId = :appId and m.createdAt >= :since")
|
||||
long countByAppIdAndCreatedAtAfter(@Param("appId") String appId, @Param("since") LocalDateTime since);
|
||||
|
||||
default long countTodayByAppId(String appId) {
|
||||
return countByAppIdAndCreatedAtAfter(appId, LocalDateTime.now().toLocalDate().atStartOfDay());
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,12 +5,7 @@ import com.xuqm.update.entity.AppVersionEntity;
|
||||
import com.xuqm.update.repository.AppVersionRepository;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -104,6 +99,26 @@ public class AppVersionController {
|
||||
public ResponseEntity<ApiResponse<AppVersionEntity>> publish(@PathVariable String id) {
|
||||
AppVersionEntity entity = versionRepository.findById(id).orElseThrow();
|
||||
entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED);
|
||||
entity.setGrayEnabled(false);
|
||||
entity.setGrayPercent(0);
|
||||
return ResponseEntity.ok(ApiResponse.success(versionRepository.save(entity)));
|
||||
}
|
||||
|
||||
@PostMapping("/app/{id}/unpublish")
|
||||
public ResponseEntity<ApiResponse<AppVersionEntity>> unpublish(@PathVariable String id) {
|
||||
AppVersionEntity entity = versionRepository.findById(id).orElseThrow();
|
||||
entity.setPublishStatus(AppVersionEntity.PublishStatus.DEPRECATED);
|
||||
return ResponseEntity.ok(ApiResponse.success(versionRepository.save(entity)));
|
||||
}
|
||||
|
||||
@PostMapping("/app/{id}/gray")
|
||||
public ResponseEntity<ApiResponse<AppVersionEntity>> gray(
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
AppVersionEntity entity = versionRepository.findById(id).orElseThrow();
|
||||
entity.setGrayEnabled(Boolean.TRUE.equals(body.get("enabled")));
|
||||
entity.setGrayPercent(body.get("percent") instanceof Number n ? n.intValue() : 0);
|
||||
entity.setPublishStatus(AppVersionEntity.PublishStatus.PUBLISHED);
|
||||
return ResponseEntity.ok(ApiResponse.success(versionRepository.save(entity)));
|
||||
}
|
||||
|
||||
|
||||
@ -5,12 +5,7 @@ import com.xuqm.update.entity.RnBundleEntity;
|
||||
import com.xuqm.update.repository.RnBundleRepository;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -21,6 +16,7 @@ import java.security.DigestInputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@ -102,10 +98,47 @@ public class RnBundleController {
|
||||
return ResponseEntity.ok(ApiResponse.success(bundleRepository.save(entity)));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
public ResponseEntity<ApiResponse<List<RnBundleEntity>>> list(
|
||||
@RequestParam String appId,
|
||||
@RequestParam(required = false) String moduleId,
|
||||
@RequestParam(required = false) String platform) {
|
||||
List<RnBundleEntity> result;
|
||||
if (moduleId != null && platform != null) {
|
||||
RnBundleEntity.Platform p = RnBundleEntity.Platform.valueOf(platform.toUpperCase());
|
||||
result = bundleRepository.findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(appId, moduleId, p);
|
||||
} else if (moduleId != null) {
|
||||
result = bundleRepository.findByAppIdAndModuleIdOrderByCreatedAtDesc(appId, moduleId);
|
||||
} else {
|
||||
result = bundleRepository.findByAppIdOrderByCreatedAtDesc(appId);
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success(result));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/publish")
|
||||
public ResponseEntity<ApiResponse<RnBundleEntity>> publish(@PathVariable String id) {
|
||||
RnBundleEntity entity = bundleRepository.findById(id).orElseThrow();
|
||||
entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED);
|
||||
entity.setGrayEnabled(false);
|
||||
entity.setGrayPercent(0);
|
||||
return ResponseEntity.ok(ApiResponse.success(bundleRepository.save(entity)));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/unpublish")
|
||||
public ResponseEntity<ApiResponse<RnBundleEntity>> unpublish(@PathVariable String id) {
|
||||
RnBundleEntity entity = bundleRepository.findById(id).orElseThrow();
|
||||
entity.setPublishStatus(RnBundleEntity.PublishStatus.DEPRECATED);
|
||||
return ResponseEntity.ok(ApiResponse.success(bundleRepository.save(entity)));
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/gray")
|
||||
public ResponseEntity<ApiResponse<RnBundleEntity>> gray(
|
||||
@PathVariable String id,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
RnBundleEntity entity = bundleRepository.findById(id).orElseThrow();
|
||||
entity.setGrayEnabled(Boolean.TRUE.equals(body.get("enabled")));
|
||||
entity.setGrayPercent(body.get("percent") instanceof Number n ? n.intValue() : 0);
|
||||
entity.setPublishStatus(RnBundleEntity.PublishStatus.PUBLISHED);
|
||||
return ResponseEntity.ok(ApiResponse.success(bundleRepository.save(entity)));
|
||||
}
|
||||
|
||||
|
||||
@ -50,6 +50,12 @@ public class AppVersionEntity {
|
||||
@Column(length = 256)
|
||||
private String marketUrl;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean grayEnabled = false;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int grayPercent = 0;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@ -86,6 +92,12 @@ public class AppVersionEntity {
|
||||
public String getMarketUrl() { return marketUrl; }
|
||||
public void setMarketUrl(String marketUrl) { this.marketUrl = marketUrl; }
|
||||
|
||||
public boolean isGrayEnabled() { return grayEnabled; }
|
||||
public void setGrayEnabled(boolean grayEnabled) { this.grayEnabled = grayEnabled; }
|
||||
|
||||
public int getGrayPercent() { return grayPercent; }
|
||||
public void setGrayPercent(int grayPercent) { this.grayPercent = grayPercent; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
|
||||
@ -47,6 +47,12 @@ public class RnBundleEntity {
|
||||
@Column(nullable = false, length = 16)
|
||||
private PublishStatus publishStatus;
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean grayEnabled = false;
|
||||
|
||||
@Column(nullable = false)
|
||||
private int grayPercent = 0;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@ -80,6 +86,12 @@ public class RnBundleEntity {
|
||||
public PublishStatus getPublishStatus() { return publishStatus; }
|
||||
public void setPublishStatus(PublishStatus publishStatus) { this.publishStatus = publishStatus; }
|
||||
|
||||
public boolean isGrayEnabled() { return grayEnabled; }
|
||||
public void setGrayEnabled(boolean grayEnabled) { this.grayEnabled = grayEnabled; }
|
||||
|
||||
public int getGrayPercent() { return grayPercent; }
|
||||
public void setGrayPercent(int grayPercent) { this.grayPercent = grayPercent; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ import java.util.Optional;
|
||||
public interface RnBundleRepository extends JpaRepository<RnBundleEntity, String> {
|
||||
List<RnBundleEntity> findByAppIdAndModuleIdAndPlatformOrderByCreatedAtDesc(
|
||||
String appId, String moduleId, RnBundleEntity.Platform platform);
|
||||
List<RnBundleEntity> findByAppIdAndModuleIdOrderByCreatedAtDesc(String appId, String moduleId);
|
||||
List<RnBundleEntity> findByAppIdOrderByCreatedAtDesc(String appId);
|
||||
Optional<RnBundleEntity> findTopByAppIdAndModuleIdAndPlatformAndPublishStatusOrderByCreatedAtDesc(
|
||||
String appId, String moduleId, RnBundleEntity.Platform platform, RnBundleEntity.PublishStatus status);
|
||||
}
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户