fix: group history endpoint, user-only group list, reset-password, change-password path

- im-service: add GET /messages/group-history/{groupId} for group message history
- im-service: add findGroupHistory query to ImMessageRepository
- im-service: listGroups now filters by user membership (JSON_CONTAINS on member_ids)
- im-service: add groupHistory/listUserGroups/addGroupMember/removeGroupMember methods
- demo-service: add POST /auth/reset-password (unauthenticated forgot-password flow)
- demo-service: rename /user/reset-password → /user/change-password to match client

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-04-27 11:57:46 +08:00
父节点 8011fe591a
当前提交 624554a173
共有 9 个文件被更改,包括 71 次插入4 次删除

查看文件

@ -60,6 +60,22 @@ public class DemoAuthController {
); );
} }
@PostMapping("/reset-password")
public ApiResponse<Void> resetPassword(@RequestBody ResetPasswordRequest body) {
if (body.appId() == null || body.appId().isBlank()) {
return ApiResponse.badRequest("appId is required");
}
if (body.userId() == null || body.userId().isBlank()) {
return ApiResponse.badRequest("userId is required");
}
if (body.newPassword() == null || body.newPassword().length() < 6) {
return ApiResponse.badRequest("password must be at least 6 characters");
}
authService.resetPassword(body.appId(), body.userId(), body.newPassword());
return ApiResponse.ok();
}
public record RegisterRequest(String appId, String userId, String password, String nickname) {} public record RegisterRequest(String appId, String userId, String password, String nickname) {}
public record LoginRequest(String appId, String userId, String password) {} public record LoginRequest(String appId, String userId, String password) {}
public record ResetPasswordRequest(String appId, String userId, String newPassword) {}
} }

查看文件

@ -36,8 +36,8 @@ public class DemoUserController {
userService.updateProfile(appId, userId, body.nickname(), body.avatar(), body.gender())); userService.updateProfile(appId, userId, body.nickname(), body.avatar(), body.gender()));
} }
@PostMapping("/user/reset-password") @PostMapping("/user/change-password")
public ApiResponse<Void> resetPassword( public ApiResponse<Void> changePassword(
@RequestParam String appId, @RequestParam String appId,
Authentication auth, Authentication auth,
@RequestBody ResetPasswordRequest body) { @RequestBody ResetPasswordRequest body) {

查看文件

@ -83,6 +83,14 @@ public class DemoAuthService {
return new AuthResult(demoToken, imToken, toProfile(user)); return new AuthResult(demoToken, imToken, toProfile(user));
} }
@Transactional
public void resetPassword(String appId, String userId, String newPassword) {
DemoUserEntity user = userRepository.findByAppIdAndUserId(appId, userId)
.orElseThrow(() -> new BusinessException(404, "User not found: " + userId));
user.setPasswordHash(passwordEncoder.encode(newPassword));
userRepository.save(user);
}
private String generateDemoToken(String appId, String userId) { private String generateDemoToken(String appId, String userId) {
return jwtUtil.generate(userId, Map.of("appId", appId, "role", "USER")); return jwtUtil.generate(userId, Map.of("appId", appId, "role", "USER"));
} }

查看文件

@ -30,8 +30,9 @@ public class GroupController {
@GetMapping @GetMapping
public ResponseEntity<ApiResponse<List<ImGroupEntity>>> list( public ResponseEntity<ApiResponse<List<ImGroupEntity>>> list(
@RequestParam String appId) { @RequestParam String appId,
return ResponseEntity.ok(ApiResponse.success(groupService.listByApp(appId))); @AuthenticationPrincipal String userId) {
return ResponseEntity.ok(ApiResponse.success(groupService.listUserGroups(appId, userId)));
} }
@PostMapping("/{groupId}/members") @PostMapping("/{groupId}/members")

查看文件

@ -51,4 +51,14 @@ public class MessageController {
@RequestParam(defaultValue = "20") int size) { @RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(messageService.history(appId, userId, toId, page, size))); return ResponseEntity.ok(ApiResponse.success(messageService.history(appId, userId, toId, page, size)));
} }
@GetMapping("/group-history/{groupId}")
public ResponseEntity<ApiResponse<?>> groupHistory(
@PathVariable String groupId,
@AuthenticationPrincipal String userId,
@RequestParam String appId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return ResponseEntity.ok(ApiResponse.success(messageService.groupHistory(appId, groupId, page, size)));
}
} }

查看文件

@ -2,9 +2,21 @@ package com.xuqm.im.repository;
import com.xuqm.im.entity.ImGroupEntity; import com.xuqm.im.entity.ImGroupEntity;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List; import java.util.List;
public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String> { public interface ImGroupRepository extends JpaRepository<ImGroupEntity, String> {
List<ImGroupEntity> findByAppId(String appId); List<ImGroupEntity> findByAppId(String appId);
long countByAppId(String appId); long countByAppId(String appId);
@Query(value = """
SELECT * FROM im_group
WHERE app_id = :appId
AND JSON_CONTAINS(member_ids, JSON_QUOTE(:userId))
ORDER BY created_at DESC
""", nativeQuery = true)
List<ImGroupEntity> findUserGroups(
@Param("appId") String appId,
@Param("userId") String userId);
} }

查看文件

@ -64,6 +64,18 @@ public interface ImMessageRepository extends JpaRepository<ImMessageEntity, Stri
@Param("peerId") String peerId, @Param("peerId") String peerId,
Pageable pageable); Pageable pageable);
@Query("""
select m from ImMessageEntity m
where m.appId = :appId
and m.chatType = com.xuqm.im.entity.ImMessageEntity$ChatType.GROUP
and m.toId = :groupId
order by m.createdAt desc
""")
Page<ImMessageEntity> findGroupHistory(
@Param("appId") String appId,
@Param("groupId") String groupId,
Pageable pageable);
long countByAppId(String appId); long countByAppId(String appId);
@Query("select count(m) from ImMessageEntity m where m.appId = :appId and m.createdAt >= :since") @Query("select count(m) from ImMessageEntity m where m.appId = :appId and m.createdAt >= :since")

查看文件

@ -67,6 +67,10 @@ public class ImGroupService {
return groupRepository.findByAppId(appId); return groupRepository.findByAppId(appId);
} }
public List<ImGroupEntity> listUserGroups(String appId, String userId) {
return groupRepository.findUserGroups(appId, userId);
}
private String toJson(List<String> list) { private String toJson(List<String> list) {
try { return objectMapper.writeValueAsString(list); } catch (Exception e) { return "[]"; } try { return objectMapper.writeValueAsString(list); } catch (Exception e) { return "[]"; }
} }

查看文件

@ -109,6 +109,10 @@ public class MessageService {
appId, userId, toId, PageRequest.of(page, size)); appId, userId, toId, PageRequest.of(page, size));
} }
public Page<ImMessageEntity> groupHistory(String appId, String groupId, int page, int size) {
return messageRepository.findGroupHistory(appId, groupId, PageRequest.of(page, size));
}
public List<ImMessageRepository.ConversationSummary> conversations(String appId, String userId, int size) { public List<ImMessageRepository.ConversationSummary> conversations(String appId, String userId, int size) {
return messageRepository.findConversations(appId, userId, size); return messageRepository.findConversations(appId, userId, size);
} }