feat: complete phase 02 RBAC and data scope recovery

This commit is contained in:
2026-09-10 10:58:43 +08:00
parent b46b48eb33
commit 5b51d5b3f1
115 changed files with 3608 additions and 1135 deletions
@@ -1,9 +1,10 @@
package com.danyang.match.platform.account.controller;
import com.danyang.match.platform.account.service.AccountLifecycleService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -17,7 +18,6 @@ import java.util.Map;
public class AccountController {
private final AccountLifecycleService accountService;
private final JwtTokenService jwtTokenService;
/**
* 退出婚恋服务(暂停推荐)
@@ -25,8 +25,8 @@ public class AccountController {
*/
@PostMapping("/withdraw")
public ApiResponse<Map<String, Object>> withdrawService(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(accountService.withdrawDatingService(userId, userId));
}
@@ -36,8 +36,8 @@ public class AccountController {
*/
@PostMapping("/resume")
public ApiResponse<Map<String, Object>> resumeService(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(accountService.resumeDatingService(userId));
}
@@ -47,8 +47,8 @@ public class AccountController {
*/
@GetMapping("/can-withdraw")
public ApiResponse<Map<String, Object>> canWithdraw(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(accountService.checkCanWithdraw(userId));
}
@@ -59,9 +59,9 @@ public class AccountController {
*/
@PostMapping("/close")
public ApiResponse<Map<String, Object>> closeAccount(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String reason = body.getOrDefault("reason", "");
return ApiResponse.ok(accountService.closeAccount(userId, userId, reason));
}
@@ -72,8 +72,8 @@ public class AccountController {
*/
@GetMapping("/privacy-settings")
public ApiResponse<Map<String, Object>> getPrivacySettings(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(accountService.getPrivacySettings(userId));
}
@@ -83,22 +83,11 @@ public class AccountController {
*/
@PutMapping("/privacy-settings")
public ApiResponse<Map<String, Object>> updatePrivacySettings(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> settings) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(accountService.updatePrivacySettings(userId, settings));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,11 @@
package com.danyang.match.platform.account.controller;
import com.danyang.match.platform.account.service.AccountLifecycleService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -17,18 +19,18 @@ import java.util.Map;
public class AccountOperatorController {
private final AccountLifecycleService accountService;
private final JwtTokenService jwtTokenService;
/**
* 强制注销用户账号(管理端)
* POST /api/v1/operator/account/{userId}/close
*/
@PostMapping("/{userId}/close")
@PreAuthorize("hasAuthority('user.status.change')")
public ApiResponse<Map<String, Object>> closeAccount(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String userId,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String reason = body.getOrDefault("reason", "管理员强制注销");
return ApiResponse.ok(accountService.closeAccount(userId, operatorId, reason));
}
@@ -38,17 +40,11 @@ public class AccountOperatorController {
* GET /api/v1/operator/account/{userId}/can-withdraw
*/
@GetMapping("/{userId}/can-withdraw")
@PreAuthorize("hasAuthority('user.view')")
public ApiResponse<Map<String, Object>> canWithdraw(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String userId) {
return ApiResponse.ok(accountService.checkCanWithdraw(userId));
}
private String extractOperatorId(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new RuntimeException("未认证");
}
String token = authHeader.substring(7);
return jwtTokenService.getUserIdFromToken(token);
}
}
@@ -1,9 +1,12 @@
package com.danyang.match.platform.activity.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.activity.service.ActivityService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -18,27 +21,26 @@ import java.util.Map;
public class ActivityController {
private final ActivityService activityService;
private final JwtTokenService jwtTokenService;
@GetMapping
public ApiResponse<?> getActivities(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(activityService.getActivities(userId));
}
@GetMapping("/upcoming")
public ApiResponse<?> getUpcoming(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(activityService.getUpcomingActivities(userId));
}
@GetMapping("/{id}")
public ApiResponse<Map<String, Object>> getActivityDetail(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
Map<String, Object> detail = activityService.getActivityDetail(id, userId);
if (detail == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.NOT_FOUND);
@@ -48,10 +50,10 @@ public class ActivityController {
@PostMapping("/{id}/register")
public ApiResponse<Map<String, Object>> register(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String emergencyContactName = body.get("emergencyContactName");
String emergencyContactPhone = body.get("emergencyContactPhone");
return ApiResponse.ok(activityService.registerActivity(userId, id, emergencyContactName, emergencyContactPhone));
@@ -59,17 +61,17 @@ public class ActivityController {
@DeleteMapping("/{id}/register")
public ApiResponse<Void> cancelRegistration(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
activityService.cancelRegistration(userId, id);
return ApiResponse.ok();
}
@GetMapping("/my")
public ApiResponse<List<Map<String, Object>>> getMyActivities(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(activityService.getMyActivities(userId));
}
@@ -77,60 +79,53 @@ public class ActivityController {
@GetMapping("/types/{activityType}")
public ApiResponse<?> getActivitiesByType(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String activityType) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(activityService.getActivitiesByType(userId, activityType));
}
@GetMapping("/{id}/organizations")
public ApiResponse<List<Map<String, Object>>> getActivityOrganizations(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(activityService.getActivityOrganizations(id));
}
@GetMapping("/{id}/capacity-groups")
public ApiResponse<List<Map<String, Object>>> getActivityCapacityGroups(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(activityService.getActivityCapacityGroups(id));
}
@PostMapping("/operator")
@PreAuthorize("hasAuthority('activity.create')")
public ApiResponse<?> createActivityOperator(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, Object> body) {
String operatorId = extractUserId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(activityService.createActivityFromAdmin(body, operatorId));
}
@PostMapping("/operator/{id}/organizations")
@PreAuthorize("hasAuthority('activity.create')")
public ApiResponse<?> addActivityOrganization(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String operatorId = extractUserId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(activityService.addActivityOrganization(id, body.get("organizationId"), body.get("role"), operatorId));
}
@PostMapping("/operator/{id}/capacity-groups")
@PreAuthorize("hasAuthority('activity.create')")
public ApiResponse<?> addCapacityGroup(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, Object> body) {
String operatorId = extractUserId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(activityService.addCapacityGroup(id, body, operatorId));
}
private String extractUserId(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return null;
}
String token = authHeader.substring(7);
if (!jwtTokenService.validateToken(token)) {
return null;
}
return jwtTokenService.getUserIdFromToken(token);
}
}
@@ -3,10 +3,13 @@ package com.danyang.match.platform.activity.controller;
import com.danyang.match.platform.activity.dto.ActivityFeedbackDTO;
import com.danyang.match.platform.activity.dto.ActivityMatchItemDTO;
import com.danyang.match.platform.activity.service.ActivityInterestService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.relationship.service.RelationshipService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
@@ -23,7 +26,6 @@ public class ActivityInterestController {
private final ActivityInterestService interestService;
private final RelationshipService relationshipService;
private final JwtTokenService jwtTokenService;
/**
* 提交活动反馈 + 表达意向
@@ -31,10 +33,10 @@ public class ActivityInterestController {
*/
@PostMapping("/{id}/feedback")
public ApiResponse<Map<String, Object>> submitFeedback(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody ActivityFeedbackDTO body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
Map<String, Object> result = interestService.submitFeedback(
userId,
id,
@@ -52,9 +54,9 @@ public class ActivityInterestController {
*/
@GetMapping("/{id}/interests/mutual")
public ApiResponse<List<ActivityMatchItemDTO>> getMutualMatches(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(interestService.getMutualMatches(userId, id));
}
@@ -64,10 +66,10 @@ public class ActivityInterestController {
*/
@GetMapping("/{id}/interests/mutual/{partnerId}")
public ApiResponse<Map<String, Object>> checkMutualInterest(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@PathVariable String partnerId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
boolean mutual = interestService.hasMutualInterest(userId, partnerId, id);
Map<String, Object> result = new HashMap<>();
result.put("hasMutual", mutual);
@@ -82,10 +84,10 @@ public class ActivityInterestController {
*/
@PostMapping("/{id}/interests/confirm")
public ApiResponse<Map<String, Object>> confirmInterest(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String partnerId = body.get("partnerId");
if (partnerId == null || partnerId.isBlank()) {
return ApiResponse.error(
@@ -106,22 +108,12 @@ public class ActivityInterestController {
* GET /api/v1/operator/activities/{id}/interest-stats
*/
@GetMapping("/operator/{id}/interest-stats")
@PreAuthorize("hasAuthority('activity.view')")
public ApiResponse<Map<String, Object>> getActivityInterestStats(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(interestService.getActivityInterestStats(id));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,13 @@
package com.danyang.match.platform.activity.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.activity.service.ActivityService;
import com.danyang.match.platform.activity.mapper.ActivityMapper;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -20,23 +22,24 @@ import com.danyang.match.platform.shared.constant.Constants;
*/
@RestController
@RequestMapping("/operator/activities")
@PreAuthorize("hasAuthority('activity.view')")
@RequiredArgsConstructor
public class ActivityOperatorController {
private final ActivityService activityService;
private final ActivityMapper activityMapper;
private final JwtTokenService jwtTokenService;
/**
* 创建活动
* POST /api/v1/operator/activities
*/
@PostMapping
@PreAuthorize("hasAuthority('activity.create')")
@Transactional
public ApiResponse<Activity> createActivity(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, Object> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
Activity activity = new Activity();
activity.setTitle((String) body.get("title"));
activity.setDescription((String) body.get("description"));
@@ -63,10 +66,11 @@ public class ActivityOperatorController {
* PUT /api/v1/operator/activities/{id}/publish
*/
@PutMapping("/{id}/publish")
@PreAuthorize("hasAuthority('activity.create')")
public ApiResponse<?> publishActivity(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(activityService.publishActivity(id, operatorId));
}
@@ -75,10 +79,11 @@ public class ActivityOperatorController {
* PUT /api/v1/operator/activities/{id}/cancel
*/
@PutMapping("/{id}/cancel")
@PreAuthorize("hasAuthority('activity.create')")
public ApiResponse<?> cancelActivity(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(activityService.cancelActivity(id, operatorId));
}
@@ -88,7 +93,7 @@ public class ActivityOperatorController {
*/
@GetMapping
public ApiResponse<List<Map<String, Object>>> getActivities(
@RequestHeader("Authorization") String authHeader) {
@AuthenticationPrincipal OperatorPrincipal principal) {
List<Activity> activities = activityMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Activity>()
.eq(Activity::getDeletedAt, 0)
@@ -114,7 +119,7 @@ public class ActivityOperatorController {
*/
@GetMapping("/{id}/registrations")
public ApiResponse<List<Map<String, Object>>> getRegistrations(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(activityService.getRegistrations(id));
}
@@ -124,10 +129,11 @@ public class ActivityOperatorController {
* POST /api/v1/operator/activities/registrations/{rid}/approve
*/
@PostMapping("/registrations/{rid}/approve")
@PreAuthorize("hasAuthority('activity.review_registration')")
public ApiResponse<?> approveRegistration(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String rid) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(activityService.approveRegistration(rid, operatorId));
}
@@ -137,25 +143,14 @@ public class ActivityOperatorController {
* Body: { "reason": "..." }
*/
@PostMapping("/registrations/{rid}/reject")
@PreAuthorize("hasAuthority('activity.review_registration')")
public ApiResponse<?> rejectRegistration(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String rid,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String reason = body.get("reason");
return ApiResponse.ok(activityService.rejectRegistration(rid, operatorId, reason));
}
private String extractOperatorId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,12 @@
package com.danyang.match.platform.activity.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.activity.service.CheckinService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -17,7 +20,6 @@ import java.util.Map;
public class CheckinController {
private final CheckinService checkinService;
private final JwtTokenService jwtTokenService;
/**
* 生成签到码
@@ -25,9 +27,9 @@ public class CheckinController {
*/
@PostMapping("/{id}/checkin-token")
public ApiResponse<Map<String, Object>> generateCheckinToken(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(checkinService.generateCheckinToken(userId, id));
}
@@ -37,10 +39,11 @@ public class CheckinController {
* Body: { "token": "..." }
*/
@PostMapping("/operator/checkin/scan")
@PreAuthorize("hasAuthority('activity.checkin')")
public ApiResponse<Map<String, Object>> scanCheckin(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String token = body.get("token");
if (token == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "token不能为空");
@@ -54,11 +57,12 @@ public class CheckinController {
* Body: { "userId": "..." }
*/
@PostMapping("/operator/{id}/checkin/manual")
@PreAuthorize("hasAuthority('activity.checkin')")
public ApiResponse<Map<String, Object>> manualCheckin(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String userId = body.get("userId");
if (userId == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "userId不能为空");
@@ -71,27 +75,13 @@ public class CheckinController {
* GET /api/v1/operator/activities/{id}/checkin-stats
*/
@GetMapping("/operator/{id}/checkin-stats")
@PreAuthorize("hasAuthority('activity.checkin')")
public ApiResponse<Map<String, Object>> getCheckinStats(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(checkinService.getCheckinStats(id));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractOperatorId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -2,9 +2,10 @@ package com.danyang.match.platform.activity_mutual.controller;
import com.danyang.match.platform.activity_mutual.dto.MutualInterestResultDTO;
import com.danyang.match.platform.activity_mutual.service.ActivityMutualConsentService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -20,7 +21,6 @@ import java.util.Map;
public class ActivityMutualConsentController {
private final ActivityMutualConsentService consentService;
private final JwtTokenService jwtTokenService;
/**
* P16-T03: 检测双向互选结果
@@ -28,9 +28,9 @@ public class ActivityMutualConsentController {
*/
@GetMapping("/results")
public ApiResponse<List<MutualInterestResultDTO>> getMutualResults(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String activityId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(consentService.detectMutualResults(activityId));
}
@@ -40,10 +40,10 @@ public class ActivityMutualConsentController {
*/
@PostMapping("/consent")
public ApiResponse<?> createConsent(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String activityId,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String partnerId = body.get("partnerId");
return ApiResponse.ok(consentService.createMutualConsent(activityId, userId, partnerId));
}
@@ -54,10 +54,10 @@ public class ActivityMutualConsentController {
*/
@PostMapping("/confirm")
public ApiResponse<Map<String, Object>> confirmConnection(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String activityId,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String partnerId = body.get("partnerId");
return ApiResponse.ok(consentService.confirmConnection(userId, activityId, partnerId));
}
@@ -68,10 +68,10 @@ public class ActivityMutualConsentController {
*/
@PostMapping("/decline")
public ApiResponse<Map<String, Object>> declineConnection(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String activityId,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String partnerId = body.get("partnerId");
return ApiResponse.ok(consentService.declineConnection(userId, activityId, partnerId));
}
@@ -82,10 +82,10 @@ public class ActivityMutualConsentController {
*/
@GetMapping("/status/{partnerId}")
public ApiResponse<Map<String, Object>> getMyConsentStatus(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String activityId,
@PathVariable String partnerId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(consentService.getMyConsentStatus(userId, activityId, partnerId));
}
@@ -95,21 +95,10 @@ public class ActivityMutualConsentController {
*/
@GetMapping("/my-results")
public ApiResponse<List<MutualInterestResultDTO>> getMyMutualResults(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(consentService.getMyMutualResults(userId));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -3,6 +3,7 @@ package com.danyang.match.platform.analytics;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
@@ -14,6 +15,7 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/operator/analytics")
@PreAuthorize("hasAuthority('analytics.view')")
@RequiredArgsConstructor
public class AnalyticsController {
@@ -3,9 +3,10 @@ package com.danyang.match.platform.appeal.controller;
import com.danyang.match.platform.appeal.dto.AppealProcessDTO;
import com.danyang.match.platform.appeal.dto.AppealSubmitDTO;
import com.danyang.match.platform.appeal.service.AppealService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -19,7 +20,6 @@ import java.util.Map;
public class AppealController {
private final AppealService appealService;
private final JwtTokenService jwtTokenService;
/**
* 获取账号状态(含是否可申诉)
@@ -27,8 +27,8 @@ public class AppealController {
*/
@GetMapping("/account-status")
public ApiResponse<Map<String, Object>> getAccountStatus(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(appealService.getAccountStatus(userId));
}
@@ -38,9 +38,9 @@ public class AppealController {
*/
@PostMapping
public ApiResponse<Map<String, Object>> submitAppeal(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody AppealSubmitDTO body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(appealService.submitAppeal(userId, body));
}
@@ -49,21 +49,10 @@ public class AppealController {
* GET /api/v1/appeals/my
*/
@GetMapping("/my")
public ApiResponse<Object> getMyAppeals(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Object> getMyAppeals(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(appealService.getMyAppeals(userId));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -2,9 +2,11 @@ package com.danyang.match.platform.appeal.controller;
import com.danyang.match.platform.appeal.dto.AppealProcessDTO;
import com.danyang.match.platform.appeal.service.AppealService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -14,18 +16,18 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/operator/appeals")
@PreAuthorize("hasAuthority('risk.report.view')")
@RequiredArgsConstructor
public class AppealOperatorController {
private final AppealService appealService;
private final JwtTokenService jwtTokenService;
/**
* 获取待处理申诉
* GET /api/v1/operator/appeals/pending
*/
@GetMapping("/pending")
public ApiResponse<Object> getPendingAppeals(@RequestHeader("Authorization") String authHeader) {
public ApiResponse<Object> getPendingAppeals(@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(appealService.getPendingAppeals());
}
@@ -35,7 +37,7 @@ public class AppealOperatorController {
*/
@GetMapping("/{id}")
public ApiResponse<Object> getAppealDetail(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(appealService.getAppealDetail(id));
}
@@ -46,11 +48,12 @@ public class AppealOperatorController {
* Body: { "decision": "approve|reject|escalate", "decisionNote": "..." }
*/
@PostMapping("/{id}/process")
@PreAuthorize("hasAuthority('risk.report.process')")
public ApiResponse<Map<String, Object>> processAppeal(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody AppealProcessDTO body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(appealService.processAppeal(id, body, operatorId));
}
@@ -59,7 +62,7 @@ public class AppealOperatorController {
* GET /api/v1/operator/appeals/stats
*/
@GetMapping("/stats")
public ApiResponse<Map<String, Object>> getAppealStats(@RequestHeader("Authorization") String authHeader) {
public ApiResponse<Map<String, Object>> getAppealStats(@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(appealService.getAppealStats());
}
@@ -69,21 +72,9 @@ public class AppealOperatorController {
*/
@GetMapping("/users/{userId}")
public ApiResponse<Object> getUserAppeals(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String userId) {
return ApiResponse.ok(appealService.getUserAppeals(userId));
}
private String extractOperatorId(String authHeader) {
String token = extractToken(authHeader);
return token;
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -3,6 +3,7 @@ package com.danyang.match.platform.audit.controller;
import com.danyang.match.platform.audit.service.AuditLogService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -12,6 +13,7 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/operator/audit")
@PreAuthorize("hasAuthority('permission.manage')")
@RequiredArgsConstructor
public class AuditLogController {
@@ -1,7 +1,9 @@
package com.danyang.match.platform.auth.controller;
import com.danyang.match.platform.auth.service.AuthService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@@ -14,7 +16,6 @@ import java.util.Map;
public class AuthController {
private final AuthService authService;
private final JwtTokenService jwtTokenService;
/**
* 微信登录
@@ -25,7 +26,8 @@ public class AuthController {
public ApiResponse<Map<String, Object>> wechatLogin(@RequestBody Map<String, String> body) {
String code = body.get("code");
if (code == null || code.isBlank()) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "code 不能为空");
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "code 不能为空");
}
return ApiResponse.ok(authService.wechatLogin(code));
}
@@ -37,14 +39,15 @@ public class AuthController {
* Body: { "phone": "13800138000" }
*/
@PostMapping("/phone/bind")
public ApiResponse<Void> bindPhone(@RequestHeader("Authorization") String authHeader,
@PreAuthorize("hasRole('USER')")
public ApiResponse<Void> bindPhone(@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String phone = body.get("phone");
if (phone == null || phone.isBlank()) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "手机号不能为空");
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "手机号不能为空");
}
authService.bindPhone(userId, phone);
authService.bindPhone(principal.id(), phone);
return ApiResponse.ok();
}
@@ -58,7 +61,8 @@ public class AuthController {
String userId = body.get("userId");
String refreshToken = body.get("refreshToken");
if (userId == null || refreshToken == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "参数缺失");
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "参数缺失");
}
return ApiResponse.ok(authService.refreshToken(userId, refreshToken));
}
@@ -68,10 +72,10 @@ public class AuthController {
* POST /api/v1/auth/logout
*/
@PostMapping("/logout")
public ApiResponse<Void> logout(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
String token = extractToken(authHeader);
authService.logout(userId, token);
@PreAuthorize("hasRole('USER')")
public ApiResponse<Void> logout(@AuthenticationPrincipal UserPrincipal principal,
@RequestAttribute("currentUserToken") String token) {
authService.logout(principal.id(), token);
return ApiResponse.ok();
}
@@ -80,21 +84,8 @@ public class AuthController {
* GET /api/v1/me/status
*/
@GetMapping("/me/status")
public ApiResponse<Map<String, Object>> getMeStatus(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
return ApiResponse.ok(authService.getMeStatus(userId));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
@PreAuthorize("hasRole('USER')")
public ApiResponse<Map<String, Object>> getMeStatus(@AuthenticationPrincipal UserPrincipal principal) {
return ApiResponse.ok(authService.getMeStatus(principal.id()));
}
}
@@ -0,0 +1,30 @@
package com.danyang.match.platform.auth.controller;
import com.danyang.match.platform.auth.service.OperatorAuthService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.enums.ApiResponseCode;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
@RequestMapping("/auth/operator")
@RequiredArgsConstructor
public class OperatorAuthController {
private final OperatorAuthService operatorAuthService;
@PostMapping("/login")
public ApiResponse<Map<String, Object>> login(@RequestBody Map<String, String> body) {
String username = body.get("username");
String password = body.get("password");
if (username == null || username.isBlank() || password == null || password.isBlank()) {
return ApiResponse.error(ApiResponseCode.BAD_REQUEST, "用户名和密码不能为空");
}
return ApiResponse.ok(operatorAuthService.login(username, password));
}
}
@@ -9,6 +9,10 @@ import com.danyang.match.platform.permission.mapper.OperatorMapper;
import com.danyang.match.platform.permission.mapper.PermissionMapper;
import com.danyang.match.platform.permission.entity.Permission;
import com.danyang.match.platform.shared.constant.Constants;
import com.danyang.match.platform.shared.enums.ApiResponseCode;
import com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException;
import com.danyang.match.platform.shared.exception.GlobalExceptionHandler.UnauthorizedException;
import org.springframework.security.access.AccessDeniedException;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,6 +24,7 @@ import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
@RequiredArgsConstructor
@@ -43,7 +48,7 @@ public class OperatorAuthService {
*/
public Map<String, Object> login(String username, String password) {
Operator operator = operatorMapper.findByUsername(username)
.orElseThrow(() -> new RuntimeException("用户名或密码错误"));
.orElseThrow(() -> new UnauthorizedException("用户名或密码错误"));
// 检查账号是否已被锁定
String lockKey = "auth:operator_login_lock:" + operator.getId();
@@ -52,12 +57,13 @@ public class OperatorAuthService {
long remainingSeconds = java.time.Duration.between(
java.time.Instant.now(), java.time.Instant.parse(lockedUntil)).getSeconds();
if (remainingSeconds > 0) {
throw new RuntimeException("账号已锁定,请 " + remainingSeconds / 60 + " 分钟后重试");
throw new BusinessException(ApiResponseCode.RATE_LIMITED,
"账号已锁定,请 " + Math.max(1, remainingSeconds / 60) + " 分钟后重试");
}
}
if (!Constants.ACCOUNT_STATUS_NORMAL.equals(operator.getStatus())) {
throw new RuntimeException("账号已禁用,请联系管理员");
if (!Set.of("active", Constants.ACCOUNT_STATUS_NORMAL).contains(operator.getStatus())) {
throw new AccessDeniedException("账号已禁用,请联系管理员");
}
if (!passwordEncoder.matches(password, operator.getPasswordHash())) {
@@ -76,7 +82,7 @@ public class OperatorAuthService {
log.warn("Operator 账号被锁定: username={}, ip=unknown", username);
}
log.warn("登录失败: username={}, ip={}", username, "unknown");
throw new RuntimeException("用户名或密码错误");
throw new UnauthorizedException("用户名或密码错误");
}
// 登录成功:清除失败计数和锁定
@@ -115,6 +121,7 @@ public class OperatorAuthService {
result.put("refreshToken", refreshToken);
result.put("operatorId", operator.getId());
result.put("name", operator.getName());
result.put("username", operator.getUsername());
result.put("organizationId", operator.getOrganizationId());
result.put("permissions", permissionCodes);
result.put("expiresIn", jwtTokenService.getExpirationMs() / 1000);
@@ -148,6 +155,7 @@ public class OperatorAuthService {
result.put("operatorId", operator.getId());
result.put("name", operator.getName());
result.put("username", operator.getUsername());
result.put("username", operator.getUsername());
result.put("organizationId", operator.getOrganizationId());
result.put("status", operator.getStatus());
result.put("permissions", permissionCodes);
@@ -2,9 +2,11 @@ package com.danyang.match.platform.content.controller;
import com.danyang.match.platform.content.dto.ContentArticleDTO;
import com.danyang.match.platform.content.dto.ContentCategoryDTO;
import com.danyang.match.platform.content.service.ContentService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@@ -13,7 +15,6 @@ import java.util.Map;
@RequiredArgsConstructor
public class ContentController {
private final ContentService contentService;
private final JwtTokenService jwtTokenService;
@GetMapping("/categories")
public ApiResponse<List<ContentCategoryDTO>> getCategories() {
@@ -22,14 +23,12 @@ public class ContentController {
@GetMapping("/articles")
public ApiResponse<List<ContentArticleDTO>> getArticles(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@RequestParam(required = false) String categoryId) {
return ApiResponse.ok(contentService.getPublishedArticles(categoryId));
}
@GetMapping("/articles/{id}")
public ApiResponse<ContentArticleDTO> getArticleDetail(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@PathVariable String id) {
ContentArticleDTO detail = contentService.getArticleDetail(id);
if (detail == null) return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.NOT_FOUND);
@@ -37,61 +36,52 @@ public class ContentController {
}
// ---- Admin ----
@PreAuthorize("hasAuthority('content.create')")
@PostMapping("/operator/categories")
public ApiResponse<ContentCategoryDTO> createCategory(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, Object> body) {
return ApiResponse.ok(contentService.createCategory(body));
}
@PreAuthorize("hasAuthority('content.edit')")
@PutMapping("/operator/categories/{id}")
public ApiResponse<ContentCategoryDTO> updateCategory(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id, @RequestBody Map<String, Object> body) {
return ApiResponse.ok(contentService.updateCategory(id, body));
}
@PreAuthorize("hasAuthority('content.create')")
@PostMapping("/operator/articles")
public ApiResponse<ContentArticleDTO> createArticle(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, Object> body) {
String operatorId = extractUserId(authHeader);
return ApiResponse.ok(contentService.createArticle(body, operatorId));
return ApiResponse.ok(contentService.createArticle(body, principal.id()));
}
@PreAuthorize("hasAuthority('content.edit')")
@PutMapping("/operator/articles/{id}")
public ApiResponse<ContentArticleDTO> updateArticle(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id, @RequestBody Map<String, Object> body) {
String operatorId = extractUserId(authHeader);
return ApiResponse.ok(contentService.updateArticle(id, body, operatorId));
return ApiResponse.ok(contentService.updateArticle(id, body, principal.id()));
}
@PreAuthorize("hasAuthority('content.publish')")
@PostMapping("/operator/articles/{id}/publish")
public ApiResponse<ContentArticleDTO> publishArticle(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
String operatorId = extractUserId(authHeader);
return ApiResponse.ok(contentService.publishArticle(id, operatorId));
return ApiResponse.ok(contentService.publishArticle(id, principal.id()));
}
@PreAuthorize("hasAuthority('content.offline')")
@PostMapping("/operator/articles/{id}/offline")
public ApiResponse<ContentArticleDTO> offlineArticle(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
String operatorId = extractUserId(authHeader);
return ApiResponse.ok(contentService.offlineArticle(id, operatorId));
return ApiResponse.ok(contentService.offlineArticle(id, principal.id()));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,14 @@
package com.danyang.match.platform.counseling.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.counseling.dto.CounselingDTO;
import com.danyang.match.platform.counseling.dto.CounselingRequest;
import com.danyang.match.platform.counseling.service.CounselingServiceApp;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
@@ -22,7 +25,6 @@ import java.util.Map;
public class CounselingController {
private final CounselingServiceApp counselingServiceApp;
private final JwtTokenService jwtTokenService;
// ---- 用户端 ----
@@ -33,24 +35,24 @@ public class CounselingController {
@PostMapping
public ApiResponse<CounselingDTO> create(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody CounselingRequest request) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(counselingServiceApp.createCounseling(userId, request));
}
@GetMapping
public ApiResponse<List<CounselingDTO>> getMyCounselings(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(counselingServiceApp.getMyCounselings(userId));
}
@GetMapping("/{id}")
public ApiResponse<CounselingDTO> getDetail(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
CounselingDTO detail = counselingServiceApp.getCounselingDetail(id, userId);
if (detail == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.NOT_FOUND);
@@ -60,94 +62,86 @@ public class CounselingController {
@PostMapping("/{id}/cancel")
public ApiResponse<CounselingDTO> cancel(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(counselingServiceApp.cancelCounseling(id, userId));
}
// ---- 后台端 ----
@PreAuthorize("hasAuthority('counseling.service.assign')")
@GetMapping("/operator/queue")
public ApiResponse<List<CounselingDTO>> getPendingQueue(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String operatorId = extractUserId(authHeader);
public ApiResponse<List<CounselingDTO>> getPendingQueue() {
return ApiResponse.ok(counselingServiceApp.getPendingQueue());
}
@PreAuthorize("hasAuthority('counseling.service.assign')")
@PostMapping("/operator/{id}/assign")
public ApiResponse<CounselingDTO> assignCounselor(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String operatorId = extractUserId(authHeader);
String counselorId = body.get("counselorId");
return ApiResponse.ok(counselingServiceApp.assignCounselor(id, operatorId, counselorId));
return ApiResponse.ok(counselingServiceApp.assignCounselor(id, principal.id(), counselorId));
}
@PreAuthorize("hasAuthority('counseling.service.process')")
@PostMapping("/operator/{id}/schedule")
public ApiResponse<CounselingDTO> schedule(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
Instant startAt = Instant.parse(body.get("startAt"));
Instant endAt = Instant.parse(body.get("endAt"));
return ApiResponse.ok(counselingServiceApp.scheduleCounseling(id, startAt, endAt));
return ApiResponse.ok(counselingServiceApp.scheduleCounseling(id, principal, startAt, endAt));
}
@PreAuthorize("hasAuthority('counseling.service.process')")
@PostMapping("/operator/{id}/start")
public ApiResponse<CounselingDTO> startCounseling(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(counselingServiceApp.startCounseling(id));
return ApiResponse.ok(counselingServiceApp.startCounseling(id, principal));
}
@PreAuthorize("hasAuthority('counseling.note.create')")
@PostMapping("/operator/{id}/followup")
public ApiResponse<Object> addFollowup(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String operatorId = extractUserId(authHeader);
return ApiResponse.ok(counselingServiceApp.addFollowup(
id, operatorId,
id, principal,
body.get("followupType"), body.get("content"),
body.get("resultCode"),
body.get("nextFollowupAt") != null ? Instant.parse(body.get("nextFollowupAt")) : null));
}
@PreAuthorize("hasAuthority('counseling.service.process')")
@PostMapping("/operator/{id}/complete")
public ApiResponse<CounselingDTO> complete(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(counselingServiceApp.completeCounseling(id));
return ApiResponse.ok(counselingServiceApp.completeCounseling(id, principal));
}
@PreAuthorize("hasAuthority('counseling.service.process')")
@PostMapping("/operator/{id}/close")
public ApiResponse<CounselingDTO> close(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
return ApiResponse.ok(counselingServiceApp.closeCounseling(id, body.getOrDefault("reason", "")));
return ApiResponse.ok(counselingServiceApp.closeCounseling(id, principal, body.getOrDefault("reason", "")));
}
// ---- Counselor端(INV-012: 只看分配给自己的)----
@PreAuthorize("hasAuthority('counseling.service.view')")
@GetMapping("/operator/assigned")
public ApiResponse<List<CounselingDTO>> getAssigned(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String operatorId = extractUserId(authHeader);
return ApiResponse.ok(counselingServiceApp.getCounselorAssignedServices(operatorId));
@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(counselingServiceApp.getCounselorAssignedServices(principal.id()));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -11,6 +11,8 @@ import com.danyang.match.platform.notification.service.NotificationService;
import com.danyang.match.platform.permission.entity.Operator;
import com.danyang.match.platform.permission.mapper.OperatorMapper;
import com.danyang.match.platform.shared.constant.Constants;
import com.danyang.match.platform.shared.security.DataScopeGuard;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.user.entity.User;
import com.danyang.match.platform.user.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
@@ -40,6 +42,7 @@ public class CounselingServiceApp {
private final NotificationService notificationService;
private final UserMapper userMapper;
private final com.danyang.match.platform.risk.service.RiskReferralService riskReferralService;
private final DataScopeGuard dataScopeGuard;
/** P13A-T05: 获取咨询介绍 */
public List<Map<String, Object>> getServiceIntro() {
@@ -148,9 +151,10 @@ public class CounselingServiceApp {
/** P13A-T22: 安排时间 */
@Transactional
public CounselingDTO scheduleCounseling(String counselingId, Instant startAt, Instant endAt) {
public CounselingDTO scheduleCounseling(String counselingId, OperatorPrincipal principal, Instant startAt, Instant endAt) {
CounselingService cs = counselingMapper.selectById(counselingId);
if (cs == null) throw new RuntimeException("咨询不存在");
assertCanProcess(cs, principal);
cs.setStatus(Constants.COUNSELING_STATUS_SCHEDULED);
cs.setScheduledStartAt(startAt);
cs.setScheduledEndAt(endAt);
@@ -161,9 +165,10 @@ public class CounselingServiceApp {
/** P13A-T23: 开始咨询 */
@Transactional
public CounselingDTO startCounseling(String counselingId) {
public CounselingDTO startCounseling(String counselingId, OperatorPrincipal principal) {
CounselingService cs = counselingMapper.selectById(counselingId);
if (cs == null) throw new RuntimeException("咨询不存在");
assertCanProcess(cs, principal);
cs.setStatus(Constants.COUNSELING_STATUS_IN_PROGRESS);
cs.setUpdatedAt(Instant.now());
counselingMapper.updateById(cs);
@@ -172,13 +177,16 @@ public class CounselingServiceApp {
/** P13A-T24: 添加跟进记录 */
@Transactional
public FollowupDTO addFollowup(String counselingId, String operatorId,
public FollowupDTO addFollowup(String counselingId, OperatorPrincipal principal,
String followupType, String content,
String resultCode, Instant nextFollowupAt) {
CounselingService cs = counselingMapper.selectById(counselingId);
if (cs == null) throw new RuntimeException("咨询不存在");
assertCanProcess(cs, principal);
CounselingFollowup fu = new CounselingFollowup();
fu.setId(UUID.randomUUID().toString());
fu.setCounselingId(counselingId);
fu.setOperatorId(operatorId);
fu.setOperatorId(principal.id());
fu.setFollowupType(followupType);
fu.setContent(content);
fu.setResultCode(resultCode);
@@ -190,9 +198,10 @@ public class CounselingServiceApp {
/** P13A-T25: 完成咨询 */
@Transactional
public CounselingDTO completeCounseling(String counselingId) {
public CounselingDTO completeCounseling(String counselingId, OperatorPrincipal principal) {
CounselingService cs = counselingMapper.selectById(counselingId);
if (cs == null) throw new RuntimeException("咨询不存在");
assertCanProcess(cs, principal);
cs.setStatus(Constants.COUNSELING_STATUS_COMPLETED);
cs.setCompletedAt(Instant.now());
cs.setUpdatedAt(Instant.now());
@@ -207,9 +216,10 @@ public class CounselingServiceApp {
/** P13A-T26: 关闭咨询 */
@Transactional
public CounselingDTO closeCounseling(String counselingId, String reason) {
public CounselingDTO closeCounseling(String counselingId, OperatorPrincipal principal, String reason) {
CounselingService cs = counselingMapper.selectById(counselingId);
if (cs == null) throw new RuntimeException("咨询不存在");
assertCanProcess(cs, principal);
cs.setStatus(Constants.COUNSELING_STATUS_CLOSED);
cs.setClosedReason(reason);
cs.setUpdatedAt(Instant.now());
@@ -246,11 +256,17 @@ public class CounselingServiceApp {
if (op != null) dto.setAssignedOperatorName(op.getName());
}
List<CounselingFollowup> followups = followupMapper.findByCounselingId(cs.getId());
dto.setFollowups(followups.stream().map(this::toFollowupDTO).toList());
return dto;
}
private void assertCanProcess(CounselingService counseling, OperatorPrincipal principal) {
boolean counselingManager = principal.hasPermission("counseling.service.assign");
if (!counselingManager && !principal.id().equals(counseling.getAssignedOperatorId())) {
dataScopeGuard.deny(principal.id(), "counseling_service", counseling.getId(),
"只能处理分配给自己的咨询服务");
}
}
private FollowupDTO toFollowupDTO(CounselingFollowup fu) {
FollowupDTO dto = new FollowupDTO();
dto.setId(fu.getId());
@@ -1,9 +1,10 @@
package com.danyang.match.platform.discover.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.discover.service.DiscoverService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -19,7 +20,6 @@ import com.danyang.match.platform.profile.mapper.UserProfileMapper;
public class DiscoverController {
private final DiscoverService discoverService;
private final JwtTokenService jwtTokenService;
private final UserProfileMapper userProfileMapper;
/**
@@ -28,7 +28,7 @@ public class DiscoverController {
*/
@GetMapping
public ApiResponse<Map<String, Object>> search(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestParam(required = false) Integer minAge,
@RequestParam(required = false) Integer maxAge,
@RequestParam(required = false) String regionCode,
@@ -37,7 +37,7 @@ public class DiscoverController {
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int pageSize) {
String userId = extractUserId(authHeader);
String userId = principal.id();
Map<String, Object> myProfile = getMyProfile(userId);
String myGender = (String) myProfile.get("gender");
@@ -57,7 +57,7 @@ public class DiscoverController {
*/
@GetMapping("/users/{userId}")
public ApiResponse<Map<String, Object>> getProfile(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String userId) {
Map<String, Object> profile = discoverService.getPublicProfile(userId);
if (profile == null) {
@@ -66,18 +66,7 @@ public class DiscoverController {
return ApiResponse.ok(profile);
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
private Map<String, Object> getMyProfile(String userId) {
return userProfileMapper.findByUserId(userId)
@@ -1,10 +1,11 @@
package com.danyang.match.platform.home.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.home.dto.HomeDTO;
import com.danyang.match.platform.home.service.HomeApplicationService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
/**
@@ -17,28 +18,16 @@ import org.springframework.web.bind.annotation.*;
public class HomeController {
private final HomeApplicationService homeApplicationService;
private final JwtTokenService jwtTokenService;
/**
* 获取首页聚合数据
* GET /api/v1/home
*/
@GetMapping
public ApiResponse<HomeDTO> getHome(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<HomeDTO> getHome(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(homeApplicationService.getHome(userId));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,12 +1,13 @@
package com.danyang.match.platform.local_eligibility.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.local_eligibility.dto.LocalEligibilityDeclarationRequest;
import com.danyang.match.platform.local_eligibility.dto.LocalEligibilityEvidenceRequest;
import com.danyang.match.platform.local_eligibility.dto.LocalEligibilityStatusDTO;
import com.danyang.match.platform.local_eligibility.service.LocalEligibilityService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
/**
@@ -18,26 +19,14 @@ import org.springframework.web.bind.annotation.*;
public class LocalEligibilityController {
private final LocalEligibilityService localEligibilityService;
private final JwtTokenService jwtTokenService;
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
/**
* GET /api/v1/local-eligibility — 查询本地资格状态
*/
@GetMapping
public ApiResponse<LocalEligibilityStatusDTO> getStatus(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<LocalEligibilityStatusDTO> getStatus(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(localEligibilityService.getStatus(userId));
}
@@ -46,9 +35,9 @@ public class LocalEligibilityController {
*/
@PostMapping("/declaration")
public ApiResponse<LocalEligibilityStatusDTO> declare(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody LocalEligibilityDeclarationRequest req) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(localEligibilityService.declare(userId, req));
}
@@ -57,9 +46,9 @@ public class LocalEligibilityController {
*/
@PostMapping("/evidences")
public ApiResponse<LocalEligibilityStatusDTO> uploadEvidence(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody LocalEligibilityEvidenceRequest req) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(localEligibilityService.uploadEvidence(userId, req));
}
@@ -68,8 +57,8 @@ public class LocalEligibilityController {
*/
@PostMapping("/submit")
public ApiResponse<LocalEligibilityStatusDTO> submit(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(localEligibilityService.submitForReview(userId));
}
}
@@ -1,10 +1,11 @@
package com.danyang.match.platform.match.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.match.dto.RecommendationCandidateDTO;
import com.danyang.match.platform.match.service.RecommendationEngineService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -19,7 +20,6 @@ import java.util.Map;
public class RecommendationController {
private final RecommendationEngineService engineService;
private final JwtTokenService jwtTokenService;
/**
* 获取今日推荐
@@ -27,8 +27,8 @@ public class RecommendationController {
*/
@GetMapping
public ApiResponse<List<RecommendationCandidateDTO>> getRecommendations(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(engineService.getDailyRecommendations(userId));
}
@@ -39,10 +39,10 @@ public class RecommendationController {
*/
@PostMapping("/{targetUserId}/action")
public ApiResponse<Void> recordAction(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String targetUserId,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String action = body.get("action");
if (action == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "action不能为空");
@@ -57,23 +57,12 @@ public class RecommendationController {
*/
@GetMapping("/{targetUserId}/eligibility")
public ApiResponse<Map<String, Boolean>> checkEligibility(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String targetUserId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
boolean eligible = engineService.isStillEligible(userId, targetUserId);
return ApiResponse.ok(Map.of("eligible", eligible));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,11 @@
package com.danyang.match.platform.matchmaker.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.matchmaker.service.MatchmakerServiceService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -14,53 +16,55 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/operator/matchmaker")
@PreAuthorize("hasAuthority('matchmaker.view')")
@RequiredArgsConstructor
public class MatchmakerOperatorController {
private final MatchmakerServiceService matchmakerServiceService;
private final JwtTokenService jwtTokenService;
@GetMapping("/dashboard")
public ApiResponse<Map<String, Object>> getDashboard(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String operatorId = extractOperatorId(authHeader);
@AuthenticationPrincipal OperatorPrincipal principal) {
String operatorId = principal.id();
return ApiResponse.ok(matchmakerServiceService.getDashboard(operatorId));
}
@GetMapping("/pending")
public ApiResponse<List<Map<String, Object>>> getPending(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(matchmakerServiceService.getPendingServices());
}
@PostMapping("/services/{id}/assign")
@PreAuthorize("hasAuthority('matchmaker.assign')")
public ApiResponse<?> assignService(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String assignOperatorId = body.get("operatorId");
return ApiResponse.ok(matchmakerServiceService.assignService(id, assignOperatorId, null));
}
@PostMapping("/services/{id}/claim")
@PreAuthorize("hasAuthority('matchmaker.service')")
public ApiResponse<?> claimService(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(matchmakerServiceService.claimService(operatorId, id));
}
@GetMapping("/my-services")
public ApiResponse<?> getMyServices(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String operatorId = extractOperatorId(authHeader);
@AuthenticationPrincipal OperatorPrincipal principal) {
String operatorId = principal.id();
return ApiResponse.ok(matchmakerServiceService.getMyServices(operatorId));
}
@GetMapping("/services/{id}")
public ApiResponse<Map<String, Object>> getServiceDetail(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
Map<String, Object> detail = matchmakerServiceService.getServiceDetail(id);
if (detail == null) {
@@ -70,22 +74,24 @@ public class MatchmakerOperatorController {
}
@PostMapping("/services/{id}/recommend")
@PreAuthorize("hasAuthority('matchmaker.service')")
public ApiResponse<Map<String, Object>> recommend(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String targetUserId = body.get("targetUserId");
String reason = body.getOrDefault("reason", "");
return ApiResponse.ok(matchmakerServiceService.recommendPartner(id, operatorId, targetUserId, reason));
}
@PostMapping("/services/{id}/followup")
@PreAuthorize("hasAuthority('matchmaker.service')")
public ApiResponse<?> addFollowup(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String followupType = body.get("followupType");
String content = body.get("content");
String nextFollowupAt = body.get("nextFollowupAt");
@@ -93,21 +99,11 @@ public class MatchmakerOperatorController {
}
@PostMapping("/services/{id}/complete")
@PreAuthorize("hasAuthority('matchmaker.service')")
public ApiResponse<?> completeService(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(matchmakerServiceService.completeService(id));
}
private String extractOperatorId(String authHeader) {
return extractToken(authHeader);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.BusinessException(
"UNAUTHORIZED");
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,10 @@
package com.danyang.match.platform.matchmaker.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.matchmaker.service.MatchmakerServiceService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -18,7 +19,6 @@ import java.util.Map;
public class MatchmakerUserController {
private final MatchmakerServiceService matchmakerServiceService;
private final JwtTokenService jwtTokenService;
/**
* 申请红娘服务
@@ -27,9 +27,9 @@ public class MatchmakerUserController {
*/
@PostMapping
public ApiResponse<Map<String, Object>> apply(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String serviceType = body.get("serviceType");
String description = body.getOrDefault("description", "");
return ApiResponse.ok(matchmakerServiceService.applyService(userId, serviceType, description));
@@ -41,8 +41,8 @@ public class MatchmakerUserController {
*/
@GetMapping
public ApiResponse<List<Map<String, Object>>> getMyServices(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(matchmakerServiceService.getMyServices(userId));
}
@@ -52,9 +52,9 @@ public class MatchmakerUserController {
*/
@GetMapping("/{id}")
public ApiResponse<Map<String, Object>> getServiceDetail(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
Map<String, Object> detail = matchmakerServiceService.getServiceDetail(id);
if (detail == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.NOT_FOUND);
@@ -68,24 +68,13 @@ public class MatchmakerUserController {
*/
@GetMapping("/recommendations")
public ApiResponse<List<Map<String, Object>>> getRecommendations(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
if (userId == null) {
return ApiResponse.ok(List.of());
}
return ApiResponse.ok(matchmakerServiceService.getRecommendationsForUser(userId));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,14 +1,17 @@
package com.danyang.match.platform.messaging.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.messaging.dto.MessageDTO;
import com.danyang.match.platform.messaging.safety.MessageRiskService;
import com.danyang.match.platform.messaging.service.MessagingService;
import com.danyang.match.platform.photo.service.PhotoService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.http.MediaType;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
@@ -23,7 +26,6 @@ public class ChatController {
private final MessagingService messagingService;
private final MessageRiskService riskService;
private final JwtTokenService jwtTokenService;
private final PhotoService photoService;
/**
@@ -32,10 +34,10 @@ public class ChatController {
*/
@PostMapping(value = "/conversations/{id}/send", consumes = MediaType.APPLICATION_JSON_VALUE)
public ApiResponse<MessageDTO> sendTextMessage(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String content = body.get("content");
if (content == null || content.isBlank()) {
return ApiResponse.error(
@@ -61,9 +63,9 @@ public class ChatController {
*/
@PostMapping("/conversations/{id}/safety-tip")
public ApiResponse<Void> sendSafetyTip(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
messagingService.sendChatSafetyTip(id, userId);
return ApiResponse.ok();
}
@@ -74,9 +76,9 @@ public class ChatController {
*/
@PostMapping("/upload-token")
public ApiResponse<Map<String, Object>> getUploadToken(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String conversationId = body.get("conversationId");
// 复用 PhotoService 获取 MinIO presigned URL
return ApiResponse.ok(photoService.getUploadToken(userId, "chat"));
@@ -88,9 +90,9 @@ public class ChatController {
*/
@GetMapping("/files/{fileId}/url")
public ApiResponse<Map<String, Object>> getSignedUrl(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String fileId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(photoService.getSignedUrl(userId, fileId));
}
@@ -101,10 +103,10 @@ public class ChatController {
*/
@PostMapping("/conversations/{id}/send-image")
public ApiResponse<MessageDTO> sendImageMessage(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String fileKey = body.get("fileKey");
if (fileKey == null || fileKey.isBlank()) {
return ApiResponse.error(
@@ -115,16 +117,5 @@ public class ChatController {
return ApiResponse.ok(messagingService.sendImageMessage(userId, id, fileKey));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,12 @@
package com.danyang.match.platform.messaging.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.messaging.dto.ConversationDTO;
import com.danyang.match.platform.messaging.dto.MessageDTO;
import com.danyang.match.platform.messaging.service.MessagingService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -20,7 +21,6 @@ import java.util.Map;
public class MessageController {
private final MessagingService messagingService;
private final JwtTokenService jwtTokenService;
/**
* 获取我的会话列表
@@ -28,8 +28,8 @@ public class MessageController {
*/
@GetMapping("/conversations")
public ApiResponse<List<ConversationDTO>> getConversations(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(messagingService.getConversations(userId));
}
@@ -39,9 +39,9 @@ public class MessageController {
*/
@GetMapping("/conversations/{id}")
public ApiResponse<ConversationDTO> getConversation(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
ConversationDTO dto = messagingService.getConversation(userId, id);
if (dto == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.NOT_FOUND);
@@ -56,10 +56,10 @@ public class MessageController {
*/
@PostMapping("/conversations/{id}/send")
public ApiResponse<MessageDTO> sendMessage(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String content = body.get("content");
if (content == null || content.isBlank()) {
return ApiResponse.error(
@@ -76,11 +76,11 @@ public class MessageController {
*/
@GetMapping("/conversations/{id}/history")
public ApiResponse<Map<String, Object>> getHistory(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestParam(required = false) String cursor,
@RequestParam(defaultValue = "20") int limit) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(messagingService.getHistory(userId, id, cursor, limit));
}
@@ -91,10 +91,10 @@ public class MessageController {
*/
@PutMapping("/conversations/{id}/read")
public ApiResponse<Void> markAsRead(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String messageId = body.get("messageId");
messagingService.markAsRead(userId, id, messageId);
return ApiResponse.ok();
@@ -106,22 +106,11 @@ public class MessageController {
*/
@GetMapping("/unread-count")
public ApiResponse<Map<String, Object>> getUnreadCount(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
int count = messagingService.getTotalUnread(userId);
return ApiResponse.ok(Map.of("unreadCount", count));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -35,13 +35,13 @@ public class MessageRiskService {
"博彩", "赌博", "彩票", "六合彩",
"高收益", "稳赚", "内幕",
"刷单", "兼职", "快递费", "保证金",
"验证码", "密码", "身份证号",
"验证码", "密码", "身份证号", "加微信", "加你微信", "加我微信",
"诈骗", "骗子", "假冒"
);
// 普通风险关键词(仅记录,不提示)
private static final Set<String> MEDIUM_RISK_KEYWORDS = Set.of(
"电话", "加微信", "加QQ", "加群",
"电话", "加QQ", "加群",
"地址", "门牌", "小区", "楼栋",
"公司", "单位", "工作服"
);
@@ -1,10 +1,11 @@
package com.danyang.match.platform.notification.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.notification.dto.NotificationDTO;
import com.danyang.match.platform.notification.service.NotificationService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -19,7 +20,6 @@ import java.util.Map;
public class NotificationController {
private final NotificationService notificationService;
private final JwtTokenService jwtTokenService;
/**
* 获取通知列表
@@ -27,10 +27,10 @@ public class NotificationController {
*/
@GetMapping
public ApiResponse<Map<String, Object>> getNotifications(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int pageSize) {
String userId = extractUserId(authHeader);
String userId = principal.id();
List<NotificationDTO> list = notificationService.getNotifications(userId, page, pageSize);
int unreadCount = notificationService.getUnreadCount(userId);
Map<String, Object> result = new java.util.HashMap<>();
@@ -47,9 +47,9 @@ public class NotificationController {
*/
@PutMapping("/{id}/read")
public ApiResponse<Void> markAsRead(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
notificationService.markAsRead(userId, id);
return ApiResponse.ok();
}
@@ -60,8 +60,8 @@ public class NotificationController {
*/
@PutMapping("/read-all")
public ApiResponse<Map<String, Object>> markAllRead(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
int count = notificationService.markAllRead(userId);
return ApiResponse.ok(Map.of("markedCount", count));
}
@@ -72,21 +72,10 @@ public class NotificationController {
*/
@GetMapping("/unread-count")
public ApiResponse<Map<String, Object>> getUnreadCount(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(Map.of("unreadCount", notificationService.getUnreadCount(userId)));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,10 +1,12 @@
package com.danyang.match.platform.operator.controller;
import com.danyang.match.platform.operator.service.OperatorManagementService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.permission.entity.Operator;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -17,10 +19,10 @@ import java.util.Map;
@RestController
@RequestMapping("/operator/operators")
@RequiredArgsConstructor
@PreAuthorize("hasAuthority('permission.manage')")
public class OperatorController {
private final OperatorManagementService operatorService;
private final JwtTokenService jwtTokenService;
@GetMapping
public ApiResponse<Map<String, Object>> list(
@@ -42,27 +44,27 @@ public class OperatorController {
@PutMapping("/{id}/disable")
public ApiResponse<Void> disable(@PathVariable String id,
@RequestHeader("Authorization") String authHeader) {
operatorService.disableOperator(id, extractId(authHeader));
@AuthenticationPrincipal OperatorPrincipal principal) {
operatorService.disableOperator(id, principal.id());
return ApiResponse.ok();
}
@PutMapping("/{id}/enable")
public ApiResponse<Void> enable(@PathVariable String id,
@RequestHeader("Authorization") String authHeader) {
operatorService.enableOperator(id, extractId(authHeader));
@AuthenticationPrincipal OperatorPrincipal principal) {
operatorService.enableOperator(id, principal.id());
return ApiResponse.ok();
}
@PutMapping("/{id}/reset-password")
public ApiResponse<Void> resetPassword(@PathVariable String id,
@RequestBody Map<String, String> body,
@RequestHeader("Authorization") String authHeader) {
@AuthenticationPrincipal OperatorPrincipal principal) {
String newPassword = body.get("newPassword");
if (newPassword == null || newPassword.length() < 8) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "密码至少8位");
}
operatorService.resetPassword(id, newPassword, extractId(authHeader));
operatorService.resetPassword(id, newPassword, principal.id());
return ApiResponse.ok();
}
@@ -71,11 +73,4 @@ public class OperatorController {
return ApiResponse.ok(operatorService.listOrganizations());
}
private String extractId(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new RuntimeException("未认证");
}
String token = authHeader.substring(7);
return jwtTokenService.getUserIdFromToken(token);
}
}
@@ -2,7 +2,10 @@ package com.danyang.match.platform.organization.controller;
import com.danyang.match.platform.organization.service.OrganizationService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -14,12 +17,13 @@ import java.util.Map;
@RestController
@RequestMapping("/operator/organizations")
@RequiredArgsConstructor
@PreAuthorize("hasAuthority('organization.manage')")
public class OrganizationController {
private final OrganizationService organizationService;
@GetMapping
public ApiResponse<List<?>> list(@RequestHeader("Authorization") String authHeader) {
public ApiResponse<List<?>> list(@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(organizationService.listOrganizations());
}
@@ -29,7 +33,7 @@ public class OrganizationController {
}
@PostMapping
public ApiResponse<?> create(@RequestHeader("Authorization") String authHeader,
public ApiResponse<?> create(@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, Object> params) {
return ApiResponse.ok(organizationService.createOrganization(params));
}
@@ -43,22 +47,16 @@ public class OrganizationController {
@PutMapping("/{id}/disable")
public ApiResponse<Void> disable(@PathVariable String id,
@RequestHeader("Authorization") String authHeader) {
organizationService.disableOrganization(id, extractId(authHeader));
@AuthenticationPrincipal OperatorPrincipal principal) {
organizationService.disableOrganization(id, principal.id());
return ApiResponse.ok();
}
@PutMapping("/{id}/enable")
public ApiResponse<Void> enable(@PathVariable String id,
@RequestHeader("Authorization") String authHeader) {
organizationService.enableOrganization(id, extractId(authHeader));
@AuthenticationPrincipal OperatorPrincipal principal) {
organizationService.enableOrganization(id, principal.id());
return ApiResponse.ok();
}
private String extractId(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new RuntimeException("未认证");
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,14 @@
package com.danyang.match.platform.photo.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.photo.service.PhotoService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.http.MediaType;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -20,7 +23,6 @@ import java.util.Map;
public class PhotoController {
private final PhotoService photoService;
private final JwtTokenService jwtTokenService;
/**
* 获取上传凭证(供客户端直传到 MinIO)
@@ -28,9 +30,9 @@ public class PhotoController {
*/
@PostMapping("/upload-token")
public ApiResponse<Map<String, Object>> getUploadToken(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String photoType = body.getOrDefault("photoType", "lifestyle");
return ApiResponse.ok(photoService.getUploadToken(userId, photoType));
}
@@ -41,11 +43,11 @@ public class PhotoController {
*/
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponse<?> uploadPhoto(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestParam("file") MultipartFile file,
@RequestParam("photoType") String photoType,
@RequestParam(value = "sortOrder", defaultValue = "0") int sortOrder) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(photoService.uploadAndSave(userId, file, photoType, sortOrder));
}
@@ -55,9 +57,9 @@ public class PhotoController {
*/
@GetMapping(value = "/{fileKey}/url", produces = MediaType.APPLICATION_JSON_VALUE)
public ApiResponse<Map<String, Object>> getSignedUrl(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String fileKey) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(photoService.getSignedUrl(userId, fileKey));
}
@@ -67,8 +69,8 @@ public class PhotoController {
*/
@GetMapping
public ApiResponse<?> getPhotos(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(photoService.getPhotos(userId));
}
@@ -78,8 +80,8 @@ public class PhotoController {
*/
@GetMapping("/avatar")
public ApiResponse<?> getAvatar(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(photoService.getAvatar(userId));
}
@@ -89,9 +91,9 @@ public class PhotoController {
*/
@DeleteMapping("/{id}")
public ApiResponse<Void> deletePhoto(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
// 权限校验:只允许删除自己的照片
List<?> userPhotos = photoService.getPhotos(userId);
boolean owned = userPhotos.stream()
@@ -104,16 +106,5 @@ public class PhotoController {
return ApiResponse.ok();
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,10 @@
package com.danyang.match.platform.profile.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.profile.service.InterestService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -18,15 +19,14 @@ import java.util.Map;
public class InterestController {
private final InterestService interestService;
private final JwtTokenService jwtTokenService;
/**
* 获取兴趣
* GET /api/v1/profile/interests
*/
@GetMapping
public ApiResponse<List<String>> getInterests(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<List<String>> getInterests(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(interestService.getInterests(userId));
}
@@ -36,24 +36,13 @@ public class InterestController {
* Body: { "interests": ["travel", "reading"] }
*/
@PutMapping
public ApiResponse<Void> setInterests(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Void> setInterests(@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, List<String>> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
List<String> interests = body.get("interests");
interestService.setInterests(userId, interests);
return ApiResponse.ok();
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,10 @@
package com.danyang.match.platform.profile.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.profile.service.UserPreferenceService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -17,15 +18,14 @@ import java.util.Map;
public class PreferenceController {
private final UserPreferenceService preferenceService;
private final JwtTokenService jwtTokenService;
/**
* 获取择偶条件
* GET /api/v1/profile/preferences
*/
@GetMapping
public ApiResponse<Map<String, Object>> getPreferences(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Map<String, Object>> getPreferences(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(preferenceService.getPreference(userId));
}
@@ -34,23 +34,12 @@ public class PreferenceController {
* PUT /api/v1/profile/preferences
*/
@PutMapping
public ApiResponse<Void> savePreferences(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Void> savePreferences(@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> data) {
String userId = extractUserId(authHeader);
String userId = principal.id();
preferenceService.savePreference(userId, data);
return ApiResponse.ok();
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,10 @@
package com.danyang.match.platform.profile.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.profile.service.PrivacyService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -17,7 +18,6 @@ import java.util.Map;
public class PrivacyController {
private final PrivacyService privacyService;
private final JwtTokenService jwtTokenService;
/**
* 获取隐私设置
@@ -25,8 +25,8 @@ public class PrivacyController {
*/
@GetMapping
public ApiResponse<Map<String, Boolean>> getPrivacy(
@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(privacyService.getPrivacySettings(userId));
}
@@ -36,22 +36,11 @@ public class PrivacyController {
*/
@PutMapping
public ApiResponse<Map<String, Boolean>> updatePrivacy(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Boolean> settings) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(privacyService.updatePrivacySettings(userId, settings));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,12 @@
package com.danyang.match.platform.profile.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.profile.dto.PublicUserProfileDTO;
import com.danyang.match.platform.profile.dto.SelfProfileDTO;
import com.danyang.match.platform.profile.service.UserProfileService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -19,15 +20,14 @@ import java.util.Map;
public class ProfileController {
private final UserProfileService userProfileService;
private final JwtTokenService jwtTokenService;
/**
* 获取自己资料
* GET /api/v1/profile/me
*/
@GetMapping("/me")
public ApiResponse<SelfProfileDTO> getMyProfile(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<SelfProfileDTO> getMyProfile(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(userProfileService.getSelfProfile(userId));
}
@@ -36,9 +36,9 @@ public class ProfileController {
* PUT /api/v1/profile/me
*/
@PutMapping("/me")
public ApiResponse<Void> updateMyProfile(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Void> updateMyProfile(@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> fields) {
String userId = extractUserId(authHeader);
String userId = principal.id();
userProfileService.updateProfile(userId, fields);
return ApiResponse.ok();
}
@@ -48,9 +48,9 @@ public class ProfileController {
* PATCH /api/v1/profile/basic
*/
@PatchMapping("/basic")
public ApiResponse<?> updateBasicProfile(@RequestHeader("Authorization") String authHeader,
public ApiResponse<?> updateBasicProfile(@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Object> fields) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(userProfileService.updateBasicProfile(userId, fields));
}
@@ -59,8 +59,8 @@ public class ProfileController {
* GET /api/v1/profile/completion
*/
@GetMapping("/completion")
public ApiResponse<Map<String, Object>> getCompletion(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Map<String, Object>> getCompletion(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
int completion = userProfileService.getCompletionPercentage(userId);
Map<String, Object> result = Map.of("completion", completion);
return ApiResponse.ok(result);
@@ -72,9 +72,9 @@ public class ProfileController {
*/
@GetMapping("/{userId}")
public ApiResponse<PublicUserProfileDTO> getPublicProfile(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String userId) {
String currentUserId = extractUserId(authHeader);
String currentUserId = principal.id();
PublicUserProfileDTO profile = userProfileService.getPublicProfile(currentUserId, userId);
if (profile == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.NOT_FOUND, "用户不存在");
@@ -82,16 +82,5 @@ public class ProfileController {
return ApiResponse.ok(profile);
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,10 +1,12 @@
package com.danyang.match.platform.referral.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.referral.service.ReferralService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
@@ -21,7 +23,6 @@ import java.util.Map;
public class ReferralController {
private final ReferralService referralService;
private final JwtTokenService jwtTokenService;
/**
* P13-T03: 红娘转介用户到服务站预约
@@ -29,10 +30,11 @@ public class ReferralController {
* 仅 matchmaker 角色可调用
*/
@PostMapping("/service-station")
@PreAuthorize("hasAuthority('matchmaker.service')")
public ApiResponse<Map<String, Object>> referToServiceStation(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, String> body) {
String matchmakerId = extractUserId(authHeader);
String matchmakerId = principal.id();
String userId = body.get("userId");
String locationId = body.get("locationId");
String serviceType = body.getOrDefault("serviceType", "counselor_consultation");
@@ -48,10 +50,11 @@ public class ReferralController {
* POST /api/v1/referrals/activity
*/
@PostMapping("/activity")
@PreAuthorize("hasAuthority('matchmaker.service')")
public ApiResponse<Map<String, Object>> referToActivity(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, String> body) {
String matchmakerId = extractUserId(authHeader);
String matchmakerId = principal.id();
String userId = body.get("userId");
String activityId = body.get("activityId");
return ApiResponse.ok(referralService.referToActivity(matchmakerId, userId, activityId,
@@ -65,10 +68,10 @@ public class ReferralController {
*/
@PostMapping("/{id}/confirm")
public ApiResponse<Map<String, Object>> confirmReferral(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String targetType = body.getOrDefault("targetType", "service_station");
return ApiResponse.ok(referralService.confirmReferral(userId, id, targetType));
}
@@ -79,21 +82,10 @@ public class ReferralController {
*/
@GetMapping("/my-history")
public ApiResponse<List<Map<String, Object>>> getMyReferralHistory(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(referralService.getReferralHistory(userId));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,12 @@
package com.danyang.match.platform.relationship.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.relationship.dto.FateItemDTO;
import com.danyang.match.platform.relationship.dto.MatchDTO;
import com.danyang.match.platform.relationship.service.RelationshipService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -20,16 +21,15 @@ import java.util.Map;
public class RelationshipController {
private final RelationshipService relationshipService;
private final JwtTokenService jwtTokenService;
/**
* 表达喜欢
* POST /api/v1/relations/{targetUserId}/like
*/
@PostMapping("/{targetUserId}/like")
public ApiResponse<Map<String, Object>> like(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Map<String, Object>> like(@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String targetUserId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
try {
return ApiResponse.ok(relationshipService.like(userId, targetUserId));
} catch (RuntimeException e) {
@@ -42,9 +42,9 @@ public class RelationshipController {
* DELETE /api/v1/relations/{targetUserId}/like
*/
@DeleteMapping("/{targetUserId}/like")
public ApiResponse<Void> cancelLike(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Void> cancelLike(@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String targetUserId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
relationshipService.cancelLike(userId, targetUserId);
return ApiResponse.ok();
}
@@ -54,9 +54,9 @@ public class RelationshipController {
* POST /api/v1/relations/{targetUserId}/pass
*/
@PostMapping("/{targetUserId}/pass")
public ApiResponse<Void> pass(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Void> pass(@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String targetUserId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
relationshipService.pass(userId, targetUserId);
return ApiResponse.ok();
}
@@ -66,9 +66,9 @@ public class RelationshipController {
* POST /api/v1/relations/{targetUserId}/block
*/
@PostMapping("/{targetUserId}/block")
public ApiResponse<Void> block(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Void> block(@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String targetUserId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
relationshipService.block(userId, targetUserId);
return ApiResponse.ok();
}
@@ -78,9 +78,9 @@ public class RelationshipController {
* DELETE /api/v1/relations/{targetUserId}/block
*/
@DeleteMapping("/{targetUserId}/block")
public ApiResponse<Void> unblock(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Void> unblock(@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String targetUserId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
relationshipService.unblock(userId, targetUserId);
return ApiResponse.ok();
}
@@ -90,8 +90,8 @@ public class RelationshipController {
* GET /api/v1/relations/matches
*/
@GetMapping("/matches")
public ApiResponse<List<MatchDTO>> getMyMatches(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<List<MatchDTO>> getMyMatches(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(relationshipService.getMyMatches(userId));
}
@@ -100,9 +100,9 @@ public class RelationshipController {
* POST /api/v1/relations/matches/{targetUserId}/unmatch
*/
@PostMapping("/matches/{targetUserId}/unmatch")
public ApiResponse<Void> unmatch(@RequestHeader("Authorization") String authHeader,
public ApiResponse<Void> unmatch(@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String targetUserId) {
String userId = extractUserId(authHeader);
String userId = principal.id();
relationshipService.unmatch(userId, targetUserId);
return ApiResponse.ok();
}
@@ -112,8 +112,8 @@ public class RelationshipController {
* GET /api/v1/relations/fate/mutual
*/
@GetMapping("/fate/mutual")
public ApiResponse<List<FateItemDTO>> getMutual(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<List<FateItemDTO>> getMutual(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(relationshipService.getMutualList(userId));
}
@@ -122,8 +122,8 @@ public class RelationshipController {
* GET /api/v1/relations/fate/liked
*/
@GetMapping("/fate/liked")
public ApiResponse<List<FateItemDTO>> getLiked(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<List<FateItemDTO>> getLiked(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(relationshipService.getLikedList(userId));
}
@@ -132,8 +132,8 @@ public class RelationshipController {
* GET /api/v1/relations/fate/likers
*/
@GetMapping("/fate/likers")
public ApiResponse<List<FateItemDTO>> getLikers(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<List<FateItemDTO>> getLikers(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(relationshipService.getLikersList(userId));
}
@@ -143,9 +143,9 @@ public class RelationshipController {
*/
@PostMapping("/success/request")
public ApiResponse<Map<String, Object>> requestSuccess(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String targetUserId = body.get("targetUserId");
return ApiResponse.ok(relationshipService.requestSuccess(userId, targetUserId));
}
@@ -156,24 +156,13 @@ public class RelationshipController {
*/
@PostMapping("/success/confirm")
public ApiResponse<Void> confirmSuccess(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String targetUserId = body.get("targetUserId");
relationshipService.confirmSuccess(userId, targetUserId);
return ApiResponse.ok();
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,12 @@
package com.danyang.match.platform.relationship.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.relationship.service.RelationshipStatusService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.user.entity.User;
import com.danyang.match.platform.user.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -19,7 +20,6 @@ import java.util.Map;
public class RelationshipStatusController {
private final RelationshipStatusService relationshipStatusService;
private final JwtTokenService jwtTokenService;
private final UserMapper userMapper;
/**
@@ -27,8 +27,8 @@ public class RelationshipStatusController {
* GET /api/v1/relationship/status
*/
@GetMapping("/status")
public ApiResponse<Map<String, String>> getStatus(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Map<String, String>> getStatus(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
User user = userMapper.findById(userId).orElse(null);
String status = user != null ? user.getRelationshipStatus() : "searching";
Map<String, String> result = Map.of("relationshipStatus", status);
@@ -40,8 +40,8 @@ public class RelationshipStatusController {
* PUT /api/v1/relationship/pause
*/
@PutMapping("/pause")
public ApiResponse<Void> pause(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Void> pause(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
relationshipStatusService.pauseSearching(userId);
return ApiResponse.ok();
}
@@ -51,8 +51,8 @@ public class RelationshipStatusController {
* PUT /api/v1/relationship/resume
*/
@PutMapping("/resume")
public ApiResponse<Void> resume(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Void> resume(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
relationshipStatusService.resumeSearching(userId);
return ApiResponse.ok();
}
@@ -64,9 +64,9 @@ public class RelationshipStatusController {
*/
@PutMapping("/getting-to-know")
public ApiResponse<Void> setGettingToKnow(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, Boolean> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
boolean autoPause = body.getOrDefault("autoPause", false);
relationshipStatusService.setGettingToKnow(userId, autoPause);
return ApiResponse.ok();
@@ -79,9 +79,9 @@ public class RelationshipStatusController {
*/
@PostMapping("/success/request")
public ApiResponse<Map<String, Object>> requestSuccess(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String targetUserId = body.get("targetUserId");
if (targetUserId == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "targetUserId不能为空");
@@ -96,9 +96,9 @@ public class RelationshipStatusController {
*/
@PostMapping("/success/confirm")
public ApiResponse<Map<String, Object>> confirmSuccess(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String targetUserId = body.get("targetUserId");
if (targetUserId == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "targetUserId不能为空");
@@ -113,9 +113,9 @@ public class RelationshipStatusController {
*/
@PostMapping("/success/decline")
public ApiResponse<Void> declineSuccess(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String targetUserId = body.get("targetUserId");
relationshipStatusService.declineSuccess(userId, targetUserId);
return ApiResponse.ok();
@@ -126,22 +126,11 @@ public class RelationshipStatusController {
* PUT /api/v1/relationship/exit
*/
@PutMapping("/exit")
public ApiResponse<Void> exitService(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Void> exitService(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
relationshipStatusService.exitService(userId);
return ApiResponse.ok();
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,13 @@
package com.danyang.match.platform.review.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.review.dto.ReviewTaskDetailDTO;
import com.danyang.match.platform.review.dto.ReviewResultDTO;
import com.danyang.match.platform.review.service.ReviewService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -16,11 +18,11 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/operator/reviews")
@PreAuthorize("hasAuthority('review.view')")
@RequiredArgsConstructor
public class ReviewController {
private final ReviewService reviewService;
private final JwtTokenService jwtTokenService;
/**
* 获取待审核统计
@@ -28,14 +30,14 @@ public class ReviewController {
*/
@GetMapping
public ApiResponse<List<Map<String, Object>>> list(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestParam(defaultValue = "0") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize) {
return listPending(authHeader, page, pageSize);
return listPending(principal, page, pageSize);
}
@GetMapping("/stats")
public ApiResponse<Map<String, Long>> getStats(@RequestHeader("Authorization") String authHeader) {
public ApiResponse<Map<String, Long>> getStats(@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(reviewService.getPendingStats());
}
@@ -45,7 +47,7 @@ public class ReviewController {
*/
@GetMapping("/pending")
public ApiResponse<List<Map<String, Object>>> listPending(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer pageSize) {
// TODO: Phase 05 后续补充完整分页
@@ -57,10 +59,11 @@ public class ReviewController {
* POST /api/v1/operator/reviews/{taskId}/claim
*/
@PostMapping("/{taskId}/claim")
@PreAuthorize("hasAuthority('review.process')")
public ApiResponse<Map<String, Object>> claimTask(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String taskId) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
return reviewService.claimTask(operatorId, taskId)
.map(task -> ApiResponse.ok(java.util.Map.of("taskId", task.getId(), "status", (Object)task.getStatus())))
.orElse(ApiResponse.error(
@@ -74,7 +77,7 @@ public class ReviewController {
*/
@GetMapping("/{taskId}")
public ApiResponse<ReviewTaskDetailDTO> getTaskDetail(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String taskId) {
ReviewTaskDetailDTO detail = reviewService.getTaskDetail(taskId);
if (detail == null) {
@@ -88,11 +91,12 @@ public class ReviewController {
* POST /api/v1/operator/reviews/{taskId}/approve
*/
@PostMapping("/{taskId}/approve")
@PreAuthorize("hasAuthority('review.process')")
public ApiResponse<ReviewResultDTO> approve(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String taskId,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String comment = body.getOrDefault("comment", "");
return ApiResponse.ok(reviewService.approve(taskId, operatorId, comment));
}
@@ -102,11 +106,12 @@ public class ReviewController {
* POST /api/v1/operator/reviews/{taskId}/need-edit
*/
@PostMapping("/{taskId}/need-edit")
@PreAuthorize("hasAuthority('review.process')")
public ApiResponse<ReviewResultDTO> needEdit(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String taskId,
@RequestBody Map<String, Object> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String comment = (String) body.get("comment");
@SuppressWarnings("unchecked")
List<String> reasonCodes = (List<String>) body.getOrDefault("reasonCodes", List.of());
@@ -118,11 +123,12 @@ public class ReviewController {
* POST /api/v1/operator/reviews/{taskId}/reject
*/
@PostMapping("/{taskId}/reject")
@PreAuthorize("hasAuthority('review.process')")
public ApiResponse<ReviewResultDTO> reject(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String taskId,
@RequestBody Map<String, Object> body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
String comment = (String) body.get("comment");
@SuppressWarnings("unchecked")
List<String> reasonCodes = (List<String>) body.getOrDefault("reasonCodes", List.of());
@@ -139,16 +145,4 @@ public class ReviewController {
return ApiResponse.ok(List.of());
}
private String extractOperatorId(String authHeader) {
String token = extractToken(authHeader);
return token; // 从 JWT subject 提取
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,10 @@
package com.danyang.match.platform.review.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.review.service.ReviewService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -17,15 +18,14 @@ import java.util.Map;
public class UserReviewController {
private final ReviewService reviewService;
private final JwtTokenService jwtTokenService;
/**
* 获取用户当前审核状态
* GET /api/v1/review/status
*/
@GetMapping("/status")
public ApiResponse<Map<String, Object>> getReviewStatus(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Map<String, Object>> getReviewStatus(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
var history = reviewService.getReviewHistory(userId);
Map<String, Object> result = Map.of("history", history);
return ApiResponse.ok(result);
@@ -36,21 +36,10 @@ public class UserReviewController {
* POST /api/v1/review/resubmit
*/
@PostMapping("/resubmit")
public ApiResponse<Map<String, Object>> resubmit(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Map<String, Object>> resubmit(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(Map.of("taskId", reviewService.resubmit(userId).getId()));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return token;
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,12 @@
package com.danyang.match.platform.risk.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.risk.dto.ReportProcessDTO;
import com.danyang.match.platform.risk.dto.ReportSubmitDTO;
import com.danyang.match.platform.risk.service.ReportService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -19,7 +20,6 @@ import java.util.Map;
public class ReportController {
private final ReportService reportService;
private final JwtTokenService jwtTokenService;
/**
* 提交举报
@@ -27,9 +27,9 @@ public class ReportController {
*/
@PostMapping
public ApiResponse<Map<String, Object>> submitReport(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody ReportSubmitDTO body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(reportService.submitReport(userId, body));
}
@@ -38,21 +38,10 @@ public class ReportController {
* GET /api/v1/risk/reports/my
*/
@GetMapping("/my")
public ApiResponse<Object> getMyReports(@RequestHeader("Authorization") String authHeader) {
String userId = extractUserId(authHeader);
public ApiResponse<Object> getMyReports(@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(reportService.getMyReports(userId));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,10 +1,12 @@
package com.danyang.match.platform.risk.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.risk.dto.ReportProcessDTO;
import com.danyang.match.platform.risk.service.ReportService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -14,11 +16,11 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/operator/risk/reports")
@PreAuthorize("hasAuthority('risk.report.view')")
@RequiredArgsConstructor
public class ReportOperatorController {
private final ReportService reportService;
private final JwtTokenService jwtTokenService;
/**
* 获取举报列表(可带 status 筛选)
@@ -26,7 +28,7 @@ public class ReportOperatorController {
*/
@GetMapping
public ApiResponse<Object> listReports(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestParam(required = false) String status) {
if (status != null && !status.isEmpty()) {
return ApiResponse.ok(reportService.getReportsByStatus(status));
@@ -39,17 +41,17 @@ public class ReportOperatorController {
* GET /api/v1/operator/risk/reports/pending
*/
@GetMapping("/pending")
public ApiResponse<Object> getPendingReports(@RequestHeader("Authorization") String authHeader) {
public ApiResponse<Object> getPendingReports(@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(reportService.getPendingReports());
}
@GetMapping("/processing")
public ApiResponse<Object> getProcessingReports(@RequestHeader("Authorization") String authHeader) {
public ApiResponse<Object> getProcessingReports(@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(reportService.getReportsByStatus("processing"));
}
@GetMapping("/resolved")
public ApiResponse<Object> getResolvedReports(@RequestHeader("Authorization") String authHeader) {
public ApiResponse<Object> getResolvedReports(@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(reportService.getReportsByStatus("resolved"));
}
@@ -59,7 +61,7 @@ public class ReportOperatorController {
*/
@GetMapping("/{id}")
public ApiResponse<Map<String, Object>> getReportDetail(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(reportService.getReportDetail(id));
}
@@ -71,11 +73,12 @@ public class ReportOperatorController {
* "processNote": "...", "riskReasons": [...] }
*/
@PostMapping("/{id}/process")
@PreAuthorize("hasAuthority('risk.report.process')")
public ApiResponse<Map<String, Object>> processReport(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody ReportProcessDTO body) {
String operatorId = extractOperatorId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(reportService.processReport(id, body, operatorId));
}
@@ -84,7 +87,7 @@ public class ReportOperatorController {
* GET /api/v1/operator/risk/reports/stats
*/
@GetMapping("/stats")
public ApiResponse<Map<String, Object>> getReportStats(@RequestHeader("Authorization") String authHeader) {
public ApiResponse<Map<String, Object>> getReportStats(@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(reportService.getReportStats());
}
@@ -94,21 +97,9 @@ public class ReportOperatorController {
*/
@GetMapping("/users/{userId}")
public ApiResponse<Map<String, Object>> getUserRiskProfile(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String userId) {
return ApiResponse.ok(reportService.getUserRiskProfile(userId));
}
private String extractOperatorId(String authHeader) {
String token = extractToken(authHeader);
return token;
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,10 +1,12 @@
package com.danyang.match.platform.risk.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.risk.entity.RiskEvent;
import com.danyang.match.platform.risk.service.RiskReferralService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -20,17 +22,17 @@ import java.util.Map;
public class RiskController {
private final RiskReferralService riskReferralService;
private final JwtTokenService jwtTokenService;
/**
* P17-T04: 运营人员手动标记风险
* POST /api/v1/operator/risk/flag
*/
@PostMapping("/operator/flag")
@PreAuthorize("hasAuthority('risk.report.process')")
public ApiResponse<RiskEvent> manualFlagRisk(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, String> body) {
String operatorId = extractUserId(authHeader);
String operatorId = principal.id();
String userId = body.get("userId");
String eventType = body.getOrDefault("eventType", "manual_flag");
String note = body.getOrDefault("note", "");
@@ -42,22 +44,11 @@ public class RiskController {
* GET /api/v1/operator/risk/events/{userId}
*/
@GetMapping("/operator/events/{userId}")
@PreAuthorize("hasAuthority('risk.report.view')")
public ApiResponse<List<RiskEvent>> getRiskEvents(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String userId) {
return ApiResponse.ok(riskReferralService.getRiskEventsByUser(userId));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,11 +1,14 @@
package com.danyang.match.platform.service_appointment.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.service_appointment.dto.ServiceAppointmentDTO;
import com.danyang.match.platform.service_appointment.dto.ServiceAppointmentRequest;
import com.danyang.match.platform.service_appointment.service.ServiceAppointmentService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -21,31 +24,30 @@ import java.util.Map;
public class ServiceAppointmentController {
private final ServiceAppointmentService serviceAppointmentService;
private final JwtTokenService jwtTokenService;
// ---- 用户端 ----
@PostMapping
public ApiResponse<ServiceAppointmentDTO> create(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody ServiceAppointmentRequest request) {
// Use userId from request body (for operator-assisted creation) or extract from token
String userId = request.getUserId() != null ? request.getUserId() : extractUserId(authHeader);
// 用户端只能为当前登录用户创建;代办登记必须走独立运营权限端点。
String userId = principal.id();
return ApiResponse.ok(serviceAppointmentService.createAppointment(userId, request));
}
@GetMapping
public ApiResponse<List<ServiceAppointmentDTO>> getMyAppointments(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(serviceAppointmentService.getMyAppointments(userId));
}
@GetMapping("/{id}")
public ApiResponse<ServiceAppointmentDTO> getDetail(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
ServiceAppointmentDTO detail = serviceAppointmentService.getAppointmentDetail(id, userId);
if (detail == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.NOT_FOUND);
@@ -55,34 +57,37 @@ public class ServiceAppointmentController {
@PostMapping("/{id}/cancel")
public ApiResponse<ServiceAppointmentDTO> cancel(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String userId = extractUserId(authHeader);
String userId = principal.id();
String reason = body.getOrDefault("reason", "");
return ApiResponse.ok(serviceAppointmentService.cancelAppointment(id, userId, reason));
}
// ---- 后台端 ----
@PreAuthorize("hasAuthority('service_appointment.view')")
@GetMapping("/operator/list")
public ApiResponse<List<ServiceAppointmentDTO>> getPendingForOperator(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@RequestParam String operatorId) {
// 后台查询,仅运营人员可访问
return ApiResponse.ok(serviceAppointmentService.getMyAppointments(operatorId));
@AuthenticationPrincipal OperatorPrincipal principal) {
return ApiResponse.ok(serviceAppointmentService.getOperatorAppointments(principal.id()));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
@PreAuthorize("hasAuthority('service_appointment.confirm')")
@PostMapping("/operator/{id}/confirm")
public ApiResponse<ServiceAppointmentDTO> confirm(
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(serviceAppointmentService.confirmAppointment(id, principal.id()));
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
@PreAuthorize("hasAuthority('service_appointment.complete')")
@PostMapping("/operator/{id}/complete")
public ApiResponse<ServiceAppointmentDTO> complete(
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(serviceAppointmentService.completeAppointment(id, principal.id()));
}
}
@@ -19,4 +19,16 @@ public interface ServiceAppointmentMapper extends BaseMapper<ServiceAppointment>
@Select("SELECT * FROM service_appointments WHERE service_location_id = #{locationId} AND status = 'pending_confirmation' AND deleted_at IS NULL ORDER BY preferred_date ASC")
List<ServiceAppointment> findPendingByLocation(@Param("locationId") String locationId);
@Select("SELECT DISTINCT sa.* FROM service_appointments sa " +
"LEFT JOIN service_location_operators slo ON slo.service_location_id = sa.service_location_id " +
"WHERE sa.deleted_at IS NULL AND (sa.assigned_operator_id = #{operatorId} OR slo.operator_id = #{operatorId}) " +
"ORDER BY sa.created_at DESC")
List<ServiceAppointment> findAccessibleByOperator(@Param("operatorId") String operatorId);
@Select("SELECT EXISTS (SELECT 1 FROM service_appointments sa " +
"LEFT JOIN service_location_operators slo ON slo.service_location_id = sa.service_location_id " +
"WHERE sa.id = #{appointmentId} AND sa.deleted_at IS NULL " +
"AND (sa.assigned_operator_id = #{operatorId} OR slo.operator_id = #{operatorId}))")
boolean canAccessByOperator(@Param("appointmentId") String appointmentId,
@Param("operatorId") String operatorId);
}
@@ -8,6 +8,7 @@ import com.danyang.match.platform.service_appointment.mapper.ServiceAppointmentM
import com.danyang.match.platform.service_appointment.entity.ServiceAppointment;
import com.danyang.match.platform.service_appointment.mapper.ServiceLocationMapper;
import com.danyang.match.platform.shared.constant.Constants;
import com.danyang.match.platform.shared.security.DataScopeGuard;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -32,6 +33,7 @@ public class ServiceAppointmentService {
private final ServiceLocationMapper serviceLocationMapper;
private final NotificationService notificationService;
private final com.danyang.match.platform.risk.service.RiskReferralService riskReferralService;
private final DataScopeGuard dataScopeGuard;
/**
* 用户提交预约
@@ -91,12 +93,18 @@ public class ServiceAppointmentService {
.toList();
}
/** 获取当前服务站人员被分配的任务及其明确所属站点预约。 */
public List<ServiceAppointmentDTO> getOperatorAppointments(String operatorId) {
return appointmentMapper.findAccessibleByOperator(operatorId).stream()
.map(this::toDTO)
.toList();
}
/**
* 获取预约详情
*/
public ServiceAppointmentDTO getAppointmentDetail(String appointmentId, String viewerUserId) {
ServiceAppointment appt = appointmentMapper.selectById(appointmentId);
if (appt == null) return null;
if (appt == null || !viewerUserId.equals(appt.getUserId())) return null;
return toDTO(appt);
}
@@ -109,6 +117,7 @@ public class ServiceAppointmentService {
if (appt == null || !"submitted".equals(appt.getStatus())) {
throw new RuntimeException("预约不存在或状态不正确");
}
assertOperatorScope(appointmentId, operatorId);
appt.setStatus(Constants.APPOINTMENT_STATUS_CONFIRMED);
appt.setAssignedOperatorId(operatorId);
appt.setConfirmedStartAt(Instant.now());
@@ -132,6 +141,7 @@ public class ServiceAppointmentService {
if (appt == null || !"confirmed".equals(appt.getStatus())) {
throw new RuntimeException("预约状态不正确");
}
assertOperatorScope(appointmentId, operatorId);
appt.setStatus(Constants.APPOINTMENT_STATUS_COMPLETED);
appt.setUpdatedAt(Instant.now());
appointmentMapper.updateById(appt);
@@ -154,6 +164,12 @@ public class ServiceAppointmentService {
return toDTO(appt);
}
private void assertOperatorScope(String appointmentId, String operatorId) {
if (operatorId == null || !appointmentMapper.canAccessByOperator(appointmentId, operatorId)) {
dataScopeGuard.deny(operatorId, "service_appointment", appointmentId,
"只能处理所属服务站或分配给自己的预约任务");
}
}
private ServiceAppointmentDTO toDTO(ServiceAppointment appt) {
ServiceAppointmentDTO dto = new ServiceAppointmentDTO();
dto.setId(appt.getId());
@@ -2,9 +2,12 @@ package com.danyang.match.platform.service_program.controller;
import com.danyang.match.platform.service_program.dto.ServiceProgramDTO;
import com.danyang.match.platform.service_program.dto.ServiceProgramMemberDTO;
import com.danyang.match.platform.service_program.service.ServiceProgramService;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@@ -13,18 +16,17 @@ import java.util.Map;
@RequiredArgsConstructor
public class ServiceProgramController {
private final ServiceProgramService serviceProgramService;
private final JwtTokenService jwtTokenService;
@GetMapping
public ApiResponse<List<ServiceProgramDTO>> list(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestParam(required = false) String status) {
return ApiResponse.ok(serviceProgramService.getAllPrograms(status));
}
@GetMapping("/{id}")
public ApiResponse<ServiceProgramDTO> detail(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
ServiceProgramDTO d = serviceProgramService.getProgramDetail(id);
if (d == null) return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.NOT_FOUND);
@@ -34,74 +36,69 @@ public class ServiceProgramController {
// ---- 用户端 ----
@PostMapping("/{id}/apply")
public ApiResponse<ServiceProgramMemberDTO> apply(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String id) {
String userId = extractUserId(authHeader);
String userId = principal.id();
return ApiResponse.ok(serviceProgramService.applyToProgram(userId, id));
}
@GetMapping("/my")
public ApiResponse<List<ServiceProgramMemberDTO>> getMyApplications(
@RequestHeader(value = "Authorization", required = false) String authHeader) {
String userId = extractUserId(authHeader);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(serviceProgramService.getMyProgramMembers(userId));
}
// ---- 后台 ----
@PostMapping("/operator")
@PreAuthorize("hasAuthority('service_program.manage')")
public ApiResponse<ServiceProgramDTO> create(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody Map<String, Object> body) {
String operatorId = extractUserId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(serviceProgramService.createProgram(body, operatorId));
}
@PutMapping("/operator/{id}")
@PreAuthorize("hasAuthority('service_program.manage')")
public ApiResponse<ServiceProgramDTO> update(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id, @RequestBody Map<String, Object> body) {
return ApiResponse.ok(serviceProgramService.updateProgram(id, body));
}
@PostMapping("/operator/{id}/activate")
@PreAuthorize("hasAuthority('service_program.manage')")
public ApiResponse<ServiceProgramDTO> activate(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(serviceProgramService.activateProgram(id));
}
@GetMapping("/operator/{id}/members")
@PreAuthorize("hasAuthority('service_program.view')")
public ApiResponse<List<ServiceProgramMemberDTO>> getMembers(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
return ApiResponse.ok(serviceProgramService.getProgramMembers(id));
}
@PostMapping("/operator/members/{memberId}/approve")
@PreAuthorize("hasAuthority('service_program.manage')")
public ApiResponse<ServiceProgramMemberDTO> approveMember(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String memberId) {
String operatorId = extractUserId(authHeader);
String operatorId = principal.id();
return ApiResponse.ok(serviceProgramService.approveMember(memberId, operatorId));
}
@PostMapping("/operator/members/{memberId}/reject")
@PreAuthorize("hasAuthority('service_program.manage')")
public ApiResponse<ServiceProgramMemberDTO> rejectMember(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String memberId,
@RequestBody Map<String, String> body) {
return ApiResponse.ok(serviceProgramService.rejectMember(memberId, body.getOrDefault("reason", "")));
}
private String extractUserId(String authHeader) {
String token = extractToken(authHeader);
return jwtTokenService.getUserIdFromToken(token);
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -2,21 +2,30 @@ package com.danyang.match.platform.shared.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import com.danyang.match.platform.shared.filter.JwtAuthFilter;
import com.danyang.match.platform.shared.filter.PermissionFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public AuthorizationEventPublisher authorizationEventPublisher(ApplicationEventPublisher publisher) {
return new SpringAuthorizationEventPublisher(publisher);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
@@ -24,29 +33,27 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http,
JwtAuthFilter jwtAuthFilter,
PermissionFilter permissionFilter) throws Exception {
JwtAuthFilter jwtAuthFilter) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
// 公开接口(order=0 最高优先级)
.requestMatchers("/api/v1/auth/**").permitAll()
.requestMatchers("/api/v1/operator/auth/**").permitAll()
.requestMatchers("/api/v1/health/**").permitAll()
.requestMatchers("/api/v1/version").permitAll()
.requestMatchers("/api/v1/activities").permitAll()
.requestMatchers("/auth/wechat/login", "/auth/refresh", "/auth/operator/login").permitAll()
.requestMatchers("/health/**").permitAll()
.requestMatchers("/version").permitAll()
.requestMatchers("/activities").permitAll()
.requestMatchers("/auth/**").authenticated()
// 操作台接口需要认证
.requestMatchers("/api/v1/operator/**").authenticated()
.requestMatchers("/operator/**").hasRole("OPERATOR")
// 管理接口需要认证
.requestMatchers("/api/v1/admin/**").authenticated()
.requestMatchers("/admin/**").hasRole("OPERATOR")
// 其他所有用户接口需要认证(覆盖 /api/v1/** 中未匹配的)
.requestMatchers("/api/v1/**").authenticated()
.requestMatchers("/**").authenticated()
// 其他所有请求放行(避免 denyAll 导致未知路径也被拦截)
.anyRequest().permitAll()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(permissionFilter, JwtAuthFilter.class);
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@@ -104,6 +104,15 @@ public final class Constants {
public static final String REVIEW_RESULT_REJECTED = "rejected";
public static final String REVIEW_RESULT_NEED_EDIT = "need_edit";
// 红娘服务类型(与 V009 数据库约束一致)
public static final String MATCHMAKER_SERVICE_TYPE_RECOMMEND_PARTNER = "recommend_partner";
public static final String MATCHMAKER_SERVICE_TYPE_REVIEW_PREFERENCE = "review_preference";
public static final String MATCHMAKER_SERVICE_TYPE_CONTACT_INTENTION = "contact_intention";
public static final String MATCHMAKER_SERVICE_TYPE_JOIN_ACTIVITY = "join_activity";
public static final String MATCHMAKER_SERVICE_TYPE_NO_MATCH_HELP = "no_match_help";
public static final String MATCHMAKER_SERVICE_TYPE_CHAT_HELP = "chat_help";
public static final String MATCHMAKER_SERVICE_TYPE_OTHER = "other";
// 红娘服务状态
public static final String MATCHMAKER_SERVICE_STATUS_PENDING = "pending";
public static final String MATCHMAKER_SERVICE_STATUS_ASSIGNED = "assigned";
@@ -157,6 +166,12 @@ public final class Constants {
public static final String APPOINTMENT_STATUS_COMPLETED = "completed";
public static final String APPOINTMENT_STATUS_CANCELLED = "cancelled";
// 咨询服务类型(与 V022 数据库约束一致)
public static final String COUNSELING_TYPE_RELATIONSHIP_GUIDANCE = "relationship_guidance";
public static final String COUNSELING_TYPE_EMOTIONAL_SUPPORT = "emotional_support";
public static final String COUNSELING_TYPE_PREMARITAL_GUIDANCE = "premarital_guidance";
public static final String COUNSELING_TYPE_ANTI_FRAUD_GUIDANCE = "anti_fraud_guidance";
// 咨询状态
public static final String COUNSELING_STATUS_PENDING = "pending";
public static final String COUNSELING_STATUS_ASSIGNED = "assigned";
@@ -223,6 +238,11 @@ public final class Constants {
public static final String CHECKIN_STATUS_NOT_CHECKED_IN = "not_checked_in";
// 本地资格类型(与 V1.1 API 契约一致)
public static final String LOCAL_ELIGIBILITY_TYPE_HUKOU = "household_registration";
public static final String LOCAL_ELIGIBILITY_TYPE_WORK = "employment";
public static final String LOCAL_ELIGIBILITY_TYPE_RESIDENCE = "residence";
// 实名认证状态
public static final String VERIFICATION_STATUS_PENDING = "pending";
public static final String VERIFICATION_STATUS_SUBMITTED = "submitted";
@@ -233,6 +253,8 @@ public final class Constants {
// 风险等级
public static final String RISK_LEVEL_NORMAL = "normal";
/** 消息内容风控等级;账号风险画像使用 WATCH。 */
public static final String RISK_LEVEL_WARNING = "warning";
public static final String RISK_LEVEL_WATCH = "watch";
public static final String RISK_LEVEL_HIGH = "high";
public static final String RISK_LEVEL_CRITICAL = "critical";
@@ -244,6 +266,7 @@ public final class Constants {
// 风控动作状态
public static final String RISK_ACTION_STATUS_ACTIVE = "active";
public static final String RISK_ACTION_STATUS_RESOLVED = "resolved";
public static final String RISK_ACTION_STATUS_EXPIRED = "expired";
public static final String RISK_ACTION_STATUS_REVOKED = "revoked";
@@ -2,7 +2,10 @@ package com.danyang.match.platform.shared.controller;
import com.danyang.match.platform.shared.config.FeatureFlagConfig;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -13,6 +16,7 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/operator/feature-flags")
@PreAuthorize("hasAuthority('permission.manage')")
@RequiredArgsConstructor
public class FeatureFlagController {
@@ -34,7 +38,7 @@ public class FeatureFlagController {
*/
@PutMapping("/{flagName}")
public ApiResponse<Map<String, Object>> updateFlag(
@RequestHeader(value = "Authorization", required = false) String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String flagName,
@RequestBody Map<String, Boolean> body) {
boolean value = body.getOrDefault("value", false);
@@ -115,7 +115,8 @@ public class EncryptionUtil {
* 例:320***********1234
*/
public String maskIdNumber(String idNumber) {
if (idNumber == null || idNumber.length() < 10) {
if (idNumber == null) return null;
if (idNumber.length() < 10) {
return "****";
}
return idNumber.substring(0, 6) + "**********" + idNumber.substring(idNumber.length() - 4);
@@ -126,7 +127,8 @@ public class EncryptionUtil {
* 例:138****1234
*/
public String maskPhone(String phone) {
if (phone == null || phone.length() < 7) {
if (phone == null) return null;
if (phone.length() < 7) {
return "****";
}
return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
@@ -3,9 +3,13 @@ package com.danyang.match.platform.shared.exception;
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String message) {
super(message);
this.code = null;
public BusinessException(String code) {
super(java.util.Arrays.stream(com.danyang.match.platform.shared.enums.ApiResponseCode.values())
.filter(value -> value.getCode().equals(code))
.map(com.danyang.match.platform.shared.enums.ApiResponseCode::getDescription)
.findFirst()
.orElse(code));
this.code = code;
}
public BusinessException(String code, String message) {
@@ -47,6 +47,13 @@ public class GlobalExceptionHandler {
}
}
@Getter
public static class UnauthorizedException extends BusinessException {
public UnauthorizedException(String message) {
super(ApiResponseCode.UNAUTHORIZED, message);
}
}
@Getter
public static class PermissionDeniedException extends BusinessException {
public PermissionDeniedException() {
@@ -76,6 +83,13 @@ public class GlobalExceptionHandler {
return ApiResponse.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(UnauthorizedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ApiResponse<Void> handleUnauthorized(UnauthorizedException e) {
log.warn("Authentication failed: {}", e.getMessage());
return ApiResponse.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(PermissionDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ApiResponse<Void> handlePermissionDenied(PermissionDeniedException e) {
@@ -2,6 +2,9 @@ package com.danyang.match.platform.shared.filter;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.permission.service.PermissionService;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.shared.security.principal.PlatformPrincipal;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -9,12 +12,17 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
@Component
@RequiredArgsConstructor
@@ -51,7 +59,7 @@ public class JwtAuthFilter extends OncePerRequestFilter {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
sendUnauthorized(response);
chain.doFilter(request, response);
return;
}
@@ -63,8 +71,27 @@ public class JwtAuthFilter extends OncePerRequestFilter {
String subject = jwtTokenService.getUserIdFromToken(token);
String tokenType = jwtTokenService.parseToken(token).get("type", String.class);
boolean operatorToken = "operator_access".equals(tokenType);
List<String> permissions = operatorToken
? permissionService.getPermissions(subject)
: List.of();
PlatformPrincipal principal = operatorToken
? new OperatorPrincipal(subject, Set.copyOf(permissions))
: new UserPrincipal(subject);
var authorities = operatorToken
? Stream.concat(
Stream.of(new SimpleGrantedAuthority("ROLE_OPERATOR")),
permissions.stream().map(SimpleGrantedAuthority::new))
.toList()
: List.of(new SimpleGrantedAuthority("ROLE_USER"));
var authentication = UsernamePasswordAuthenticationToken.authenticated(principal, null, authorities);
var securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authentication);
SecurityContextHolder.setContext(securityContext);
// 临时保留请求属性,供尚未迁移的旧控制器兼容;新代码统一使用 Authentication Principal。
request.setAttribute("currentUserId", subject);
request.setAttribute("currentOperatorId", "operator_access".equals(tokenType) ? subject : null);
request.setAttribute("currentOperatorId", operatorToken ? subject : null);
request.setAttribute("currentUserToken", token);
chain.doFilter(request, response);
@@ -1,97 +0,0 @@
package com.danyang.match.platform.shared.filter;
import com.danyang.match.platform.permission.service.PermissionService;
import com.danyang.match.platform.shared.exception.BusinessException;
import com.danyang.match.platform.shared.enums.ApiResponseCode;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* 权限校验过滤器
* 检查当前 Operator 是否有访问该接口的权限
*/
@Component
@RequiredArgsConstructor
public class PermissionFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(PermissionFilter.class);
private static final AntPathMatcher pathMatcher = new AntPathMatcher();
private final PermissionService permissionService;
// 需要权限校验的路径前缀
private static final Map<String, String> PATH_PERMISSION_MAP = Map.of(
"/api/v1/admin/**", "super_admin",
"/api/v1/operator/**", "operator_login"
);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String operatorId = (String) request.getAttribute("currentOperatorId");
// 如果没有 Operator 信息(普通用户请求),跳过权限检查
if (operatorId == null) {
chain.doFilter(request, response);
return;
}
// 检查是否需要权限
boolean needsPermission = PATH_PERMISSION_MAP.keySet().stream()
.anyMatch(pattern -> pathMatcher.match(pattern, request.getRequestURI()));
if (!needsPermission) {
chain.doFilter(request, response);
return;
}
// Operator 登录接口本身不需要额外权限检查
if (request.getRequestURI().contains("/operator/auth/")) {
chain.doFilter(request, response);
return;
}
// 从 PATH_PERMISSION_MAP 获取路径对应的必需权限码
String requiredPermission = null;
for (Map.Entry<String, String> entry : PATH_PERMISSION_MAP.entrySet()) {
if (pathMatcher.match(entry.getKey(), request.getRequestURI())) {
requiredPermission = entry.getValue();
break;
}
}
if (requiredPermission == null) {
chain.doFilter(request, response);
return;
}
// 从 request attribute 获取 operatorId(由 JwtAuthFilter 设置)
if (operatorId == null) {
chain.doFilter(request, response);
return;
}
// 获取 Operator 的权限列表
List<String> permissions = permissionService.getPermissions(operatorId);
if (permissions == null || !permissions.contains(requiredPermission)) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("{\"success\":false,\"code\":\"FORBIDDEN\",\"message\":\"权限不足\",\"data\":null}");
return;
}
chain.doFilter(request, response);
}
}
@@ -0,0 +1,58 @@
package com.danyang.match.platform.shared.security;
import com.danyang.match.platform.audit.entity.AuditLog;
import com.danyang.match.platform.audit.mapper.AuditLogMapper;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.Instant;
import java.util.UUID;
/** 将服务端授权拒绝写入不可静默绕过的安全审计。 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthorizationAuditListener {
private final AuditLogMapper auditLogMapper;
@EventListener
public void onAuthorizationDenied(AuthorizationDeniedEvent<?> event) {
var authentication = event.getAuthentication().get();
if (authentication == null || !(authentication.getPrincipal() instanceof OperatorPrincipal principal)) {
return;
}
AuditLog audit = new AuditLog();
audit.setId(UUID.randomUUID().toString());
audit.setOperatorId(principal.id());
audit.setAction("AUTHORIZATION_DENIED");
audit.setResourceType("authorization");
audit.setResourceId(limit(String.valueOf(event.getObject()), 128));
audit.setCreatedAt(Instant.now());
if (RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes attributes) {
audit.setIp(attributes.getRequest().getRemoteAddr());
audit.setUserAgent(limit(attributes.getRequest().getHeader("User-Agent"), 512));
}
try {
auditLogMapper.insert(audit);
} catch (RuntimeException exception) {
log.error("写入授权拒绝审计失败: operatorId={}", principal.id(), exception);
}
}
private String limit(String value, int maxLength) {
if (value == null || value.length() <= maxLength) {
return value;
}
return value.substring(0, maxLength);
}
}
@@ -0,0 +1,37 @@
package com.danyang.match.platform.shared.security;
import com.danyang.match.platform.audit.entity.AuditLog;
import com.danyang.match.platform.audit.mapper.AuditLogMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.util.UUID;
/** Denies out-of-scope operator access and records the attempt before returning 403. */
@Slf4j
@Component
@RequiredArgsConstructor
public class DataScopeGuard {
private final AuditLogMapper auditLogMapper;
public void deny(String operatorId, String resourceType, String resourceId, String message) {
AuditLog audit = new AuditLog();
audit.setId(UUID.randomUUID().toString());
audit.setOperatorId(operatorId);
audit.setAction("DATA_SCOPE_DENIED");
audit.setResourceType(resourceType);
audit.setResourceId(resourceId);
audit.setCreatedAt(Instant.now());
try {
auditLogMapper.insert(audit);
} catch (RuntimeException exception) {
log.error("写入数据范围拒绝审计失败: operatorId={}, resourceType={}, resourceId={}",
operatorId, resourceType, resourceId, exception);
}
throw new AccessDeniedException(message);
}
}
@@ -0,0 +1,21 @@
package com.danyang.match.platform.shared.security.principal;
import java.util.Set;
/** 后台运营主体,权限集合只能由服务端数据库装载。 */
public record OperatorPrincipal(String id, Set<String> permissions) implements PlatformPrincipal {
public OperatorPrincipal {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("运营主体ID不能为空");
}
permissions = permissions == null ? Set.of() : Set.copyOf(permissions);
}
public OperatorPrincipal(String id) {
this(id, Set.of());
}
public boolean hasPermission(String permission) {
return permissions.contains(permission);
}
}
@@ -0,0 +1,6 @@
package com.danyang.match.platform.shared.security.principal;
/** 当前请求中已经通过 JWT 验证的主体。 */
public sealed interface PlatformPrincipal permits UserPrincipal, OperatorPrincipal {
String id();
}
@@ -0,0 +1,10 @@
package com.danyang.match.platform.shared.security.principal;
/** 普通用户主体。 */
public record UserPrincipal(String id) implements PlatformPrincipal {
public UserPrincipal {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("用户主体ID不能为空");
}
}
}
@@ -1,9 +1,11 @@
package com.danyang.match.platform.user.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.security.principal.OperatorPrincipal;
import com.danyang.match.platform.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -17,15 +19,15 @@ import java.util.Map;
public class UserController {
private final UserService userService;
private final JwtTokenService jwtTokenService;
/**
* 获取用户列表(分页+筛选)
* GET /api/v1/operator/users?keyword=&accountStatus=&profileStatus=&page=0&pageSize=20
*/
@PreAuthorize("hasAuthority('user.view')")
@GetMapping
public ApiResponse<Map<String, Object>> listUsers(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String accountStatus,
@RequestParam(required = false) String profileStatus,
@@ -38,12 +40,12 @@ public class UserController {
* 获取用户详情
* GET /api/v1/operator/users/{id}
*/
@PreAuthorize("hasAuthority('user.view')")
@GetMapping("/{id}")
public ApiResponse<Object> getUserDetail(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id) {
String operatorId = extractOperatorId(authHeader);
return ApiResponse.ok(userService.getUserDetail(id, operatorId));
return ApiResponse.ok(userService.getUserDetail(id, principal.id()));
}
/**
@@ -51,30 +53,18 @@ public class UserController {
* PUT /api/v1/operator/users/{id}/status
* Body: { "accountStatus": "banned" }
*/
@PreAuthorize("hasAuthority('user.status.change')")
@PutMapping("/{id}/status")
public ApiResponse<Void> updateAccountStatus(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
String operatorId = extractOperatorId(authHeader);
String newStatus = body.get("accountStatus");
if (newStatus == null) {
return ApiResponse.error(com.danyang.match.platform.shared.enums.ApiResponseCode.BAD_REQUEST, "accountStatus不能为空");
}
userService.updateAccountStatus(id, newStatus, operatorId);
userService.updateAccountStatus(id, newStatus, principal.id());
return ApiResponse.ok();
}
private String extractOperatorId(String authHeader) {
String token = extractToken(authHeader);
return token;
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}
@@ -1,9 +1,10 @@
package com.danyang.match.platform.verification.controller;
import com.danyang.match.platform.auth.token.JwtTokenService;
import com.danyang.match.platform.shared.security.principal.UserPrincipal;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.verification.service.IdentityVerificationService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@@ -18,7 +19,6 @@ import java.util.Map;
public class IdentityVerificationController {
private final IdentityVerificationService identityVerificationService;
private final JwtTokenService jwtTokenService;
/**
* 提交实名认证
@@ -28,11 +28,9 @@ public class IdentityVerificationController {
*/
@PostMapping("/submit")
public ApiResponse<Map<String, Object>> submitIdentity(
@RequestHeader("Authorization") String authHeader,
@AuthenticationPrincipal UserPrincipal principal,
@RequestBody Map<String, String> body) {
String token = extractToken(authHeader);
String userId = jwtTokenService.getUserIdFromToken(token);
String userId = principal.id();
String realName = body.get("realName");
String idNumber = body.get("idNumber");
@@ -57,10 +55,8 @@ public class IdentityVerificationController {
*/
@GetMapping("/status")
public ApiResponse<Map<String, Object>> getIdentityStatus(
@RequestHeader("Authorization") String authHeader) {
String token = extractToken(authHeader);
String userId = jwtTokenService.getUserIdFromToken(token);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
return ApiResponse.ok(identityVerificationService.getIdentityStatus(userId));
}
@@ -70,19 +66,10 @@ public class IdentityVerificationController {
*/
@PostMapping("/cancel")
public ApiResponse<Void> cancelIdentity(
@RequestHeader("Authorization") String authHeader) {
String token = extractToken(authHeader);
String userId = jwtTokenService.getUserIdFromToken(token);
@AuthenticationPrincipal UserPrincipal principal) {
String userId = principal.id();
identityVerificationService.cancelIdentity(userId);
return ApiResponse.ok();
}
private String extractToken(String authHeader) {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new com.danyang.match.platform.shared.exception.GlobalExceptionHandler.BusinessException(
com.danyang.match.platform.shared.enums.ApiResponseCode.UNAUTHORIZED);
}
return authHeader.substring(7);
}
}