package com.tourguide.api.admin; import com.tourguide.common.api.ApiResponse; import com.tourguide.common.api.PageResponse; import com.tourguide.common.exception.BusinessException; import com.tourguide.modules.user.dto.*; import com.tourguide.modules.user.service.GuideAutoDisableService; import com.tourguide.modules.user.service.GuideRegistrationDocumentStorageService; import com.tourguide.modules.user.service.GuideUserService; import com.tourguide.modules.user.service.MiniAvatarStorageService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; import java.time.LocalDate; import java.util.List; /** * 管理后台导游账号管理接口。 * 数据保存于 guide_user 独立表,支持资料维护、停启、白名单及自动禁用规则配置。 */ @RestController @RequestMapping("/api/admin/guide-users") public class AdminGuideUserController { private final GuideUserService userService; private final GuideAutoDisableService autoDisableService; private final MiniAvatarStorageService avatarStorageService; private final GuideRegistrationDocumentStorageService documentStorageService; public AdminGuideUserController(GuideUserService userService, GuideAutoDisableService autoDisableService, MiniAvatarStorageService avatarStorageService, GuideRegistrationDocumentStorageService documentStorageService) { this.userService = userService; this.autoDisableService = autoDisableService; this.avatarStorageService = avatarStorageService; this.documentStorageService = documentStorageService; } /** * 分页查询导游账号,支持基础关键字、状态、日期、评级、团队类型和白名单筛选。 * * @param page 页码,从 1 开始 * @param size 每页记录数 * @param keyword 导游姓名、手机号等关键字 * @param status 账号启用状态 * @param startDate 注册日期起始值,包含当天 * @param endDate 注册日期结束值,包含当天 * @param ratingLevel 导游评级筛选条件 * @param teamType 团队归属类型筛选条件 * @param whitelisted 是否加入自动禁用白名单 * @return 导游账号分页结果 */ @GetMapping public ApiResponse> page(@RequestParam(defaultValue = "1") long page, @RequestParam(defaultValue = "10") long size, @RequestParam(required = false) String keyword, @RequestParam(required = false) Integer status, @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate, @RequestParam(required = false) String ratingLevel, @RequestParam(required = false) String teamType, @RequestParam(required = false) Boolean whitelisted) { return ApiResponse.success(userService.page(page, size, keyword, status, startDate, endDate, ratingLevel, teamType, whitelisted)); } /** * 查询指定导游的基础资料、团队归属和景区服务配置。 * * @param id 导游账号主键 * @return 导游完整详情 */ @GetMapping("/{id}") public ApiResponse detail(@PathVariable Long id) { return ApiResponse.success(userService.detail(id)); } /** * 更新指定导游的后台可维护资料及业务配置。 * * @param id 导游账号主键 * @param request 导游资料和配置更新参数 * @return 更新后的导游详情 */ @PutMapping("/{id}") public ApiResponse updateDetail(@PathVariable Long id, @Valid @RequestBody GuideUserDetailUpdateRequest request) { return ApiResponse.success(userService.updateDetail(id, request)); } /** * 上传并更新指定导游头像。 * * @param id 导游账号主键 * @param file JPG、PNG 或 WebP 图片,最大 5MB * @return 更新后的导游基础信息 */ @PostMapping(value = "/{id}/avatar", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResponse updateAvatar(@PathVariable Long id, @RequestPart("file") MultipartFile file) { String avatar = avatarStorageService.store(file); return ApiResponse.success(userService.updateAvatarByAdmin(id, avatar)); } /** * 上传并更新指定导游的证件图片。 * * @param id 导游账号主键 * @param documentType 证件字段名:idCardFrontUrl、idCardBackUrl、guideCertificateUrl 或 qualificationCertificateUrl * @param file JPG、PNG 或 WebP 图片,最大 5MB * @return 上传后保存的证件图片地址 */ @PostMapping(value = "/{id}/documents/{documentType}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ApiResponse uploadDocument(@PathVariable Long id, @PathVariable String documentType, @RequestPart("file") MultipartFile file) { if (!userService.supportsDocumentType(documentType)) { throw new BusinessException(400, "不支持的证件类型"); } String documentUrl = documentStorageService.store(file); return ApiResponse.success(new GuideRegistrationDocumentResponse( userService.updateDocumentByAdmin(id, documentType, documentUrl))); } @PostMapping("/{id}/scenic-configs") public ApiResponse> addScenicConfig( @PathVariable Long id, @Valid @RequestBody GuideScenicConfigCreateRequest request) { return ApiResponse.success(userService.addScenicConfig(id, request.getScenicSpotId())); } @DeleteMapping("/{id}/scenic-configs/{configId}") public ApiResponse> deleteScenicConfig( @PathVariable Long id, @PathVariable Long configId) { return ApiResponse.success(userService.deleteScenicConfig(id, configId)); } /** * 由后台直接创建导游账号,适用于无需走入驻审核流程的运营录入场景。 * * @param request 导游账号创建参数 * @return 新建导游的基础信息 */ @PostMapping public ApiResponse create(@Valid @RequestBody GuideUserCreateRequest request) { return ApiResponse.success(userService.create(request)); } /** * 启用或禁用指定导游账号,禁用后导游无法继续使用导游端受保护接口。 * 自动禁用的导游必须先加入自动禁用白名单后才能手动启用。 * * @param id 导游账号主键 * @param request 目标状态,1 为启用、0 为禁用 * @return 无业务数据的成功响应 */ @PutMapping("/{id}/status") public ApiResponse updateStatus(@PathVariable Long id, @Valid @RequestBody UserStatusRequest request) { userService.updateStatus(id, request.getStatus()); if (request.getStatus() == GuideUserService.STATUS_ENABLED) { autoDisableService.disableGuideIfEligible(id); } return ApiResponse.success(); } /** * 设置导游是否进入自动禁用白名单,白名单导游不受自动禁用规则影响。 * * @param id 导游账号主键 * @param request 白名单启用状态 * @return 无业务数据的成功响应 */ @PutMapping("/{id}/whitelist") public ApiResponse updateWhitelist(@PathVariable Long id, @Valid @RequestBody GuideWhitelistRequest request) { userService.updateWhitelist(id, request.getEnabled()); if (!request.getEnabled()) { autoDisableService.disableGuideIfEligible(id); } return ApiResponse.success(); } /** 配置导游是否可直接接取精讲服务,独立于自动禁用白名单。 */ @PutMapping("/{id}/premium-permission") public ApiResponse updatePremiumPermission(@PathVariable Long id, @Valid @RequestBody GuidePremiumPermissionRequest request) { userService.updatePremiumPermission(id, request.getEnabled()); return ApiResponse.success(); } /** * 查询当前导游自动禁用规则配置,供后台设置页面回显。 * * @return 自动禁用开关、阈值及相关规则参数 */ @GetMapping("/auto-disable/config") public ApiResponse autoDisableConfig() { return ApiResponse.success(userService.getAutoDisableConfig()); } /** * 更新导游自动禁用规则,后续自动处理任务按最新配置执行。 * * @param request 自动禁用开关和阈值配置 * @return 保存后的自动禁用配置 */ @PutMapping("/auto-disable/config") public ApiResponse updateAutoDisableConfig( @Valid @RequestBody GuideAutoDisableConfigRequest request) { GuideAutoDisableConfigResponse config = userService.updateAutoDisableConfig(request); autoDisableService.synchronizeGuideStatuses(); return ApiResponse.success(config); } }