轻松聘AI开发
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

178 lines
9.2 KiB

package com.mydreamjob.job;
import com.mydreamjob.common.api.ApiResponse;
import com.mydreamjob.common.exception.BusinessException;
import com.mydreamjob.common.exception.ErrorCode;
import com.mydreamjob.company.Company;
import com.mydreamjob.company.CompanyService;
import com.mydreamjob.job.dto.JobCreateRequest;
import com.mydreamjob.job.dto.JobSearchItem;
import com.mydreamjob.job.dto.JobStatusRequest;
import com.mydreamjob.job.dto.JobUpdateRequest;
import com.mydreamjob.security.CurrentUserAccessor;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 岗位管理接口
* 企业端:创建、编辑、状态流转(需 JWT + 企业归属校验)
* 公开端:岗位列表、详情(无需认证)
*/
@Tag(name = "岗位", description = "岗位创建、管理、查询")
@RestController
@RequestMapping("/jobs")
@RequiredArgsConstructor
public class JobController {
/** R01-A6:requestId 只来自过滤器规范化后的唯一产出点。 */
private final com.mydreamjob.common.logging.RequestIdAccessor requestIdAccessor;
private final JobService jobService;
private final CompanyService companyService;
private final CurrentUserAccessor currentUserAccessor;
// ── 企业端:创建岗位 ────────────────────────────────────────
@Operation(summary = "创建岗位", description = "仅企业账号可调用,新岗位默认 DRAFT 状态")
@PostMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> createJob(
@Valid @RequestBody JobCreateRequest req,
HttpServletRequest request) {
Long companyId = resolveCompanyId();
Map<String, Object> view = jobService.toView(jobService.createJob(companyId, req));
return ResponseEntity.ok(ApiResponse.ok(view, requestIdAccessor.get(request)));
}
// ── 企业端:编辑岗位 ────────────────────────────────────────
@Operation(summary = "更新岗位", description = "仅岗位所属企业可修改")
@PutMapping("/{jobId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateJob(
@PathVariable Long jobId,
@Valid @RequestBody JobUpdateRequest req,
HttpServletRequest request) {
Long companyId = resolveCompanyId();
Map<String, Object> view = jobService.toView(jobService.updateJob(companyId, jobId, req));
return ResponseEntity.ok(ApiResponse.ok(view, requestIdAccessor.get(request)));
}
// ── 企业端:状态流转 ────────────────────────────────────────
@Operation(summary = "发布/关闭岗位", description = "DRAFT→PUBLISHED,PUBLISHED→CLOSED;禁止非法回退")
@PatchMapping("/{jobId}/status")
public ResponseEntity<ApiResponse<Map<String, Object>>> updateStatus(
@PathVariable Long jobId,
@Valid @RequestBody JobStatusRequest req,
HttpServletRequest request) {
Long companyId = resolveCompanyId();
Map<String, Object> view = jobService.toView(jobService.transitionStatus(companyId, jobId, req));
return ResponseEntity.ok(ApiResponse.ok(view, requestIdAccessor.get(request)));
}
// ── 企业端:查询岗位详情 ────────────────────────────────────
@Operation(summary = "查询岗位详情", description = "企业可查看自己管理的岗位详情")
@GetMapping("/{jobId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getJob(
@PathVariable Long jobId,
HttpServletRequest request) {
Long companyId = resolveCompanyId();
Map<String, Object> view = jobService.toView(jobService.getCompanyJob(companyId, jobId));
return ResponseEntity.ok(ApiResponse.ok(view, requestIdAccessor.get(request)));
}
// ── 公开端:岗位列表 ────────────────────────────────────────
@Operation(summary = "查询已发布岗位列表", description = "仅返回 PUBLISHED 岗位,支持关键词、城市、用工类型、薪资筛选和分页")
@GetMapping
public ResponseEntity<ApiResponse<Map<String, Object>>> listJobs(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String city,
@RequestParam(required = false) String employmentType,
@RequestParam(required = false) Integer salaryMin,
@RequestParam(required = false) Integer salaryMax,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
HttpServletRequest request) {
if (size > 50) size = 50;
Page<Map<String, Object>> result =
jobService.listPublishedJobs(keyword, city, employmentType,
salaryMin, salaryMax, page, size);
Map<String, Object> data = new HashMap<>();
data.put("items", result.getContent());
data.put("total", result.getTotalElements());
data.put("page", result.getNumber());
data.put("size", result.getSize());
data.put("totalPages", result.getTotalPages());
return ResponseEntity.ok(ApiResponse.ok(data, requestIdAccessor.get(request)));
}
// ── 公开端:岗位详情 ────────────────────────────────────────
@Operation(summary = "查询岗位详情(公开)", description = "仅返回 PUBLISHED 岗位详情;CLOSED 也可查看详情")
@GetMapping("/public/{jobId}")
public ResponseEntity<ApiResponse<Map<String, Object>>> getPublicJob(
@PathVariable Long jobId,
HttpServletRequest request) {
Map<String, Object> view = jobService.getPublicJob(jobId);
return ResponseEntity.ok(ApiResponse.ok(view, requestIdAccessor.get(request)));
}
// ── 公开端:高级搜索 ────────────────────────────────────────
@Operation(summary = "岗位高级搜索", description = "仅返回 PUBLISHED 岗位,支持关键词、城市、用工类型、薪资、公司筛选、排序和分页")
@GetMapping("/search")
public ResponseEntity<ApiResponse<Map<String, Object>>> searchJobs(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String city,
@RequestParam(required = false) String employmentType,
@RequestParam(required = false) Integer salaryMin,
@RequestParam(required = false) Integer salaryMax,
@RequestParam(required = false) Long companyId,
@RequestParam(required = false) String sort,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
HttpServletRequest request) {
Page<JobSearchItem> result = jobService.searchJobs(
keyword, city, employmentType, salaryMin, salaryMax, companyId,
sort, page, size);
Map<String, Object> data = new HashMap<>();
data.put("items", result.getContent());
data.put("total", result.getTotalElements());
data.put("page", result.getNumber());
data.put("size", result.getSize());
data.put("totalPages", result.getTotalPages());
return ResponseEntity.ok(ApiResponse.ok(data, requestIdAccessor.get(request)));
}
// ── 公开端:筛选选项 ────────────────────────────────────────
@Operation(summary = "岗位筛选选项", description = "返回当前 PUBLISHED 岗位可用的城市、用工类型和薪资范围")
@GetMapping("/filters")
public ResponseEntity<ApiResponse<Map<String, Object>>> getFilters(
HttpServletRequest request) {
Map<String, Object> options = jobService.getFilterOptions();
return ResponseEntity.ok(ApiResponse.ok(options, requestIdAccessor.get(request)));
}
// ── 辅助方法 ────────────────────────────────────────────────
private Long resolveCompanyId() {
// 身份只来自 JWT/SecurityContext;非法主体统一为未登录语义(401),不抛 NumberFormatException
Long userId = currentUserAccessor.requireUserId();
Company company = companyService.findCompanyByOwnerUserId(userId);
if (company == null) {
throw new BusinessException(ErrorCode.COMPANY_NOT_FOUND);
}
return company.getId();
}
}