forked from baskbuddy/zhangshun
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
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();
|
|
}
|
|
|
|
}
|
|
|