fix: repair admin page api contracts

This commit is contained in:
2026-09-10 21:31:38 +08:00
parent 7994e081a6
commit d6f5f25c35
30 changed files with 286 additions and 110 deletions
@@ -3,6 +3,10 @@ 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.activity.mapper.ActivityMapper;
import com.danyang.match.platform.activity.entity.Activity;
import com.danyang.match.platform.activity.entity.ActivityInterest;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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;
@@ -26,6 +30,25 @@ public class ActivityInterestController {
private final ActivityInterestService interestService;
private final RelationshipService relationshipService;
private final ActivityMapper activityMapper;
@GetMapping("/operator/activity-interest-summary")
@PreAuthorize("hasAuthority('activity.view')")
public ApiResponse<List<Map<String, Object>>> getActivityInterestSummary() {
return ApiResponse.ok(activityMapper.selectList(new LambdaQueryWrapper<Activity>()
.isNull(Activity::getDeletedAt).orderByDesc(Activity::getCreatedAt)).stream().map(activity -> {
Map<String, Object> item = new HashMap<>(interestService.getActivityInterestStats(activity.getId()));
item.put("activityTitle", activity.getTitle());
item.put("feedbackCount", 0);
return item;
}).toList());
}
@GetMapping("/operator/{id}/mutuals")
@PreAuthorize("hasAuthority('activity.view')")
public ApiResponse<List<ActivityInterest>> getMutualsForOperator(@PathVariable String id) {
return ApiResponse.ok(interestService.getMutualInterestsForOperator(id));
}
/**
* 提交活动反馈 + 表达意向
@@ -96,7 +96,7 @@ public class ActivityOperatorController {
@AuthenticationPrincipal OperatorPrincipal principal) {
List<Activity> activities = activityMapper.selectList(
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Activity>()
.eq(Activity::getDeletedAt, 0)
.isNull(Activity::getDeletedAt)
.orderByDesc(Activity::getCreatedAt)
);
return ApiResponse.ok(activities.stream().map(a -> {
@@ -207,6 +207,10 @@ public class ActivityInterestService {
return stats;
}
public List<ActivityInterest> getMutualInterestsForOperator(String activityId) {
return interestMapper.findMutualsByActivity(activityId);
}
// ==================== 私有辅助方法 ====================
private List<String> getApprovedParticipants(String activityId) {
@@ -0,0 +1,49 @@
package com.danyang.match.platform.banner.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.danyang.match.platform.banner.entity.HomeBanner;
import com.danyang.match.platform.banner.mapper.HomeBannerMapper;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.enums.ApiResponseCode;
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;
@RestController
@RequestMapping("/operator/banners")
@PreAuthorize("hasAuthority('banner.view')")
@RequiredArgsConstructor
public class HomeBannerOperatorController {
private final HomeBannerMapper bannerMapper;
@GetMapping
public ApiResponse<List<HomeBanner>> list() {
return ApiResponse.ok(bannerMapper.selectList(new LambdaQueryWrapper<HomeBanner>()
.isNull(HomeBanner::getDeletedAt).orderByAsc(HomeBanner::getSortOrder)));
}
@PostMapping
public ApiResponse<HomeBanner> create(@AuthenticationPrincipal OperatorPrincipal principal,
@RequestBody HomeBanner banner) {
banner.setId(null);
banner.setCreatedBy(principal.id());
banner.setUpdatedBy(principal.id());
bannerMapper.insert(banner);
return ApiResponse.ok(banner);
}
@PutMapping("/{id}")
public ApiResponse<HomeBanner> update(@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody HomeBanner banner) {
if (bannerMapper.selectById(id) == null) return ApiResponse.error(ApiResponseCode.NOT_FOUND);
banner.setId(id);
banner.setUpdatedBy(principal.id());
bannerMapper.updateById(banner);
return ApiResponse.ok(bannerMapper.selectById(id));
}
}
@@ -35,7 +35,7 @@ public class DiscoverService {
LambdaQueryWrapper<UserProfile> wrapper = new LambdaQueryWrapper<>();
wrapper.ne(UserProfile::getGender, myGender)
.eq(UserProfile::getProfileStatus, Constants.PROFILE_STATUS_APPROVED)
.eq(UserProfile::getDeletedAt, 0);
.isNull(UserProfile::getDeletedAt);
// 应用临时筛选条件
if (tempFilter.containsKey("minAge")) {
@@ -266,7 +266,8 @@ public class HomeApplicationService {
}
private boolean isProfileApproved(User user) {
String status = user.getProfileStatus();
String status = userProfileMapper.findByUserId(user.getId())
.map(UserProfile::getProfileStatus).orElse(null);
return Constants.PROFILE_STATUS_APPROVED.equals(status)
|| Constants.PROFILE_STATUS_COMPLETED.equals(status);
}
@@ -159,7 +159,7 @@ public class RecommendationEngineService {
wrapper.isNotNull(UserProfile::getGender)
.ne(UserProfile::getGender, getMyGender(userId))
.eq(UserProfile::getProfileStatus, Constants.PROFILE_STATUS_APPROVED)
.eq(UserProfile::getDeletedAt, 0);
.isNull(UserProfile::getDeletedAt);
// 排除当前用户已Block的人
List<String> blockedIds = relationMapper.findBlockedByUser(userId)
@@ -92,7 +92,7 @@ public class MessagingService {
new LambdaQueryWrapper<Message>()
.eq(Message::getSenderId, userId)
.eq(Message::getClientMessageId, clientMessageId)
.eq(Message::getDeletedAt, null));
.isNull(Message::getDeletedAt));
if (existing != null) {
return toMessageDTO(existing, userId);
}
@@ -16,6 +16,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
@@ -94,7 +95,7 @@ public class PhotoService {
photoMapper.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<UserPhoto>()
.eq(UserPhoto::getUserId, userId)
.eq(UserPhoto::getPhotoType, "avatar")
.set(UserPhoto::getDeletedAt, 1));
.set(UserPhoto::getDeletedAt, Instant.now()));
}
UserPhoto photo = new UserPhoto();
@@ -213,7 +214,7 @@ public class PhotoService {
public void deletePhoto(String photoId) {
photoMapper.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<UserPhoto>()
.eq(UserPhoto::getId, photoId)
.set(UserPhoto::getDeletedAt, 1));
.set(UserPhoto::getDeletedAt, Instant.now()));
log.info("照片已软删除: photoId={}", photoId);
}
@@ -43,5 +43,5 @@ public class UserProfile {
private Instant updatedAt;
@TableField("deleted_at")
private Integer deletedAt;
private Instant deletedAt;
}
@@ -110,7 +110,7 @@ public class RelationshipStatusService {
RelationshipSuccessRequest existing = successRequestMapper.selectOne(
new LambdaQueryWrapper<RelationshipSuccessRequest>()
.eq(RelationshipSuccessRequest::getMatchId, match.getId())
.eq(RelationshipSuccessRequest::getDeletedAt, 0)
.isNull(RelationshipSuccessRequest::getDeletedAt)
.orderByDesc(RelationshipSuccessRequest::getCreatedAt)
.last("LIMIT 1"));
@@ -177,7 +177,7 @@ public class RelationshipStatusService {
RelationshipSuccessRequest request = successRequestMapper.selectOne(
new LambdaQueryWrapper<RelationshipSuccessRequest>()
.eq(RelationshipSuccessRequest::getMatchId, match.getId())
.eq(RelationshipSuccessRequest::getDeletedAt, 0)
.isNull(RelationshipSuccessRequest::getDeletedAt)
.orderByDesc(RelationshipSuccessRequest::getCreatedAt)
.last("LIMIT 1"));
@@ -206,7 +206,7 @@ public class RelationshipStatusService {
.eq(RelationshipSuccessRequest::getMatchId, match.getId())
.eq(RelationshipSuccessRequest::getInitiatedBy, otherUserId)
.eq(RelationshipSuccessRequest::getStatus, Constants.SUCCESS_REQUEST_STATUS_CONFIRMED)
.eq(RelationshipSuccessRequest::getDeletedAt, 0)
.isNull(RelationshipSuccessRequest::getDeletedAt)
.last("LIMIT 1"));
// 5. 标记当前用户已确认
@@ -281,7 +281,7 @@ public class RelationshipStatusService {
RelationshipSuccessRequest request = successRequestMapper.selectOne(
new LambdaQueryWrapper<RelationshipSuccessRequest>()
.eq(RelationshipSuccessRequest::getMatchId, match.getId())
.eq(RelationshipSuccessRequest::getDeletedAt, 0)
.isNull(RelationshipSuccessRequest::getDeletedAt)
.orderByDesc(RelationshipSuccessRequest::getCreatedAt)
.last("LIMIT 1"));
@@ -90,4 +90,14 @@ public class ServiceAppointmentController {
return ApiResponse.ok(serviceAppointmentService.completeAppointment(id, principal.id()));
}
@PreAuthorize("hasAuthority('service_appointment.confirm')")
@PostMapping("/operator/{id}/cancel")
public ApiResponse<ServiceAppointmentDTO> cancelByOperator(
@AuthenticationPrincipal OperatorPrincipal principal,
@PathVariable String id,
@RequestBody Map<String, String> body) {
return ApiResponse.ok(serviceAppointmentService.cancelAppointmentByOperator(
id, principal.id(), body.getOrDefault("reason", "运营取消")));
}
}
@@ -0,0 +1,41 @@
package com.danyang.match.platform.service_appointment.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.danyang.match.platform.service_appointment.entity.ServiceLocation;
import com.danyang.match.platform.service_appointment.mapper.ServiceLocationMapper;
import com.danyang.match.platform.shared.dto.ApiResponse;
import com.danyang.match.platform.shared.enums.ApiResponseCode;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/operator/service-locations")
@PreAuthorize("hasAuthority('service_location.view')")
@RequiredArgsConstructor
public class ServiceLocationOperatorController {
private final ServiceLocationMapper locationMapper;
@GetMapping
public ApiResponse<List<ServiceLocation>> list() {
return ApiResponse.ok(locationMapper.selectList(new LambdaQueryWrapper<ServiceLocation>()
.isNull(ServiceLocation::getDeletedAt).orderByAsc(ServiceLocation::getName)));
}
@PostMapping
public ApiResponse<ServiceLocation> create(@RequestBody ServiceLocation location) {
location.setId(null);
locationMapper.insert(location);
return ApiResponse.ok(location);
}
@PutMapping("/{id}")
public ApiResponse<ServiceLocation> update(@PathVariable String id, @RequestBody ServiceLocation location) {
if (locationMapper.selectById(id) == null) return ApiResponse.error(ApiResponseCode.NOT_FOUND);
location.setId(id);
locationMapper.updateById(location);
return ApiResponse.ok(locationMapper.selectById(id));
}
}
@@ -164,6 +164,21 @@ public class ServiceAppointmentService {
return toDTO(appt);
}
@Transactional
public ServiceAppointmentDTO cancelAppointmentByOperator(String appointmentId, String operatorId, String reason) {
ServiceAppointment appt = appointmentMapper.selectById(appointmentId);
if (appt == null || Constants.APPOINTMENT_STATUS_COMPLETED.equals(appt.getStatus())
|| Constants.APPOINTMENT_STATUS_CANCELLED.equals(appt.getStatus())) {
throw new RuntimeException("预约不存在或状态不允许取消");
}
assertOperatorScope(appointmentId, operatorId);
appt.setStatus(Constants.APPOINTMENT_STATUS_CANCELLED);
appt.setCancelReason(reason);
appt.setUpdatedAt(Instant.now());
appointmentMapper.updateById(appt);
return toDTO(appt);
}
private void assertOperatorScope(String appointmentId, String operatorId) {
if (operatorId == null || !appointmentMapper.canAccessByOperator(appointmentId, operatorId)) {
dataScopeGuard.deny(operatorId, "service_appointment", appointmentId,
@@ -9,7 +9,7 @@ import java.util.UUID;
/**
* MyBatis-Plus 自动填充处理器
* 自动填充:createdAt、updatedAt、deletedAt、id(UUID)
* 自动填充:createdAt、updatedAt、id(UUID)
*/
@Component
public class AutoFillHandler implements MetaObjectHandler {
@@ -23,11 +23,10 @@ public class AutoFillHandler implements MetaObjectHandler {
}
this.strictInsertFill(metaObject, "createdAt", Instant.class, Instant.now());
this.strictInsertFill(metaObject, "updatedAt", Instant.class, Instant.now());
this.strictInsertFill(metaObject, "deletedAt", Integer.class, 0);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updatedAt", Instant.class, Instant.now());
}
}
}
@@ -20,7 +20,6 @@ public abstract class BaseEntity {
@TableField(fill = FieldFill.INSERT_UPDATE)
private Instant updatedAt;
// 软删除标记(NOT NULL, 0=未删除, 1=已删除)
// 注意:不使用 @TableLogic,由业务代码手动处理
private Integer deletedAt;
// 软删除时间;null 表示未删除。
private Instant deletedAt;
}
@@ -19,8 +19,6 @@ public class User extends BaseEntity {
@TableField("phone")
private String phone;
private String profileStatus;
@TableField("account_status")
private String accountStatus;
@@ -59,15 +59,28 @@ public class UserService {
query.eq(User::getAccountStatus, accountStatus);
}
if (profileStatus != null && !profileStatus.isBlank()) {
query.eq(User::getProfileStatus, profileStatus);
List<String> userIds = userProfileMapper.selectList(
new LambdaQueryWrapper<UserProfile>()
.eq(UserProfile::getProfileStatus, profileStatus)
.select(UserProfile::getUserId))
.stream().map(UserProfile::getUserId).toList();
query.in(User::getId, userIds.isEmpty()
? List.of("00000000-0000-0000-0000-000000000000") : userIds);
}
LambdaQueryWrapper<User> countQuery = new LambdaQueryWrapper<User>();
if (keyword != null && !keyword.isBlank()) countQuery.and(q -> q.like(User::getId, keyword).or().like(User::getWechatOpenid, keyword));
if (accountStatus != null && !accountStatus.isBlank()) countQuery.eq(User::getAccountStatus, accountStatus);
if (profileStatus != null && !profileStatus.isBlank()) countQuery.eq(User::getProfileStatus, profileStatus);
if (profileStatus != null && !profileStatus.isBlank()) {
List<String> userIds = userProfileMapper.selectList(
new LambdaQueryWrapper<UserProfile>()
.eq(UserProfile::getProfileStatus, profileStatus)
.select(UserProfile::getUserId))
.stream().map(UserProfile::getUserId).toList();
countQuery.in(User::getId, userIds.isEmpty()
? List.of("00000000-0000-0000-0000-000000000000") : userIds);
}
long totalLong = userMapper.selectCount(countQuery); int total = totalLong > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) totalLong;
long offsetLong = (page != null ? page : 0L) * (pageSize != null ? pageSize : 20L); int offset = offsetLong > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) offsetLong;
int limit = pageSize != null ? pageSize : 20;
List<User> users = userMapper.selectPage(
new Page((page != null ? page : 0), limit), query
@@ -0,0 +1,5 @@
ALTER TABLE organizations ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
ALTER TABLE user_profiles ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
ALTER TABLE content_categories ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
ALTER TABLE home_banners ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
ALTER TABLE service_locations ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;