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.
221 lines
9.3 KiB
221 lines
9.3 KiB
package com.mydreamjob.company;
|
|
|
|
import com.mydreamjob.auth.User;
|
|
import com.mydreamjob.auth.UserRepository;
|
|
import com.mydreamjob.common.exception.BusinessException;
|
|
import com.mydreamjob.common.exception.ErrorCode;
|
|
import com.mydreamjob.company.dto.CompanyCreateRequest;
|
|
import com.mydreamjob.company.dto.CompanyPublicView;
|
|
import com.mydreamjob.company.dto.CompanyUpdateRequest;
|
|
import com.mydreamjob.job.JobService;
|
|
import com.mydreamjob.job.dto.CompanyManagedJobItem;
|
|
import com.mydreamjob.job.dto.JobSearchItem;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.data.domain.Page;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* 企业管理 Service
|
|
* 角色校验优先——所有写操作在业务逻辑前先校验角色。
|
|
*/
|
|
@Slf4j
|
|
@Service
|
|
public class CompanyService {
|
|
|
|
private final com.mydreamjob.security.UserRoleGuard userRoleGuard;
|
|
private final CompanyRepository companyRepository;
|
|
private final UserRepository userRepository;
|
|
private final JobService jobService;
|
|
|
|
public CompanyService(CompanyRepository companyRepository, UserRepository userRepository,
|
|
JobService jobService,
|
|
com.mydreamjob.security.UserRoleGuard userRoleGuard) {
|
|
this.companyRepository = companyRepository;
|
|
this.userRepository = userRepository;
|
|
this.jobService = jobService;
|
|
this.userRoleGuard = userRoleGuard;
|
|
}
|
|
|
|
// ── 角色校验(最先执行)───────────────────────────────────
|
|
|
|
public void assertCompanyRole(Long userId) {
|
|
userRoleGuard.requireCompany(userId);
|
|
}
|
|
|
|
public void assertStudentRole(Long userId) {
|
|
userRoleGuard.requireStudent(userId);
|
|
}
|
|
|
|
// ── 业务方法 ──────────────────────────────────────────────
|
|
|
|
/**
|
|
* 查询当前用户绑定的企业资料(含角色校验:非 COMPANY 返回 403)
|
|
*/
|
|
public Company findCompanyByOwnerUserId(Long userId) {
|
|
assertCompanyRole(userId);
|
|
return companyRepository.findByOwnerUserId(userId).orElse(null);
|
|
}
|
|
|
|
/**
|
|
* 创建企业资料(仅 COMPANY 角色,已存在时 409)
|
|
*/
|
|
@Transactional
|
|
public Company createCompany(Long userId, CompanyCreateRequest req) {
|
|
assertCompanyRole(userId);
|
|
if (companyRepository.findByOwnerUserId(userId).isPresent()) {
|
|
throw new BusinessException(ErrorCode.COMPANY_ALREADY_EXISTS);
|
|
}
|
|
var industry = com.mydreamjob.industry.IndustryCatalog.selection(req.getIndustryCode(), req.getIndustry());
|
|
Company company = Company.builder()
|
|
.ownerUserId(userId)
|
|
.name(req.getName())
|
|
.logoUrl(req.getLogoUrl())
|
|
.description(req.getDescription())
|
|
.industry(industry.name()).industryCode(industry.code())
|
|
.companySize(normalizeCompanySize(req.getCompanySize()))
|
|
.website(req.getWebsite())
|
|
.address(req.getAddress())
|
|
.verified(false)
|
|
.build();
|
|
return companyRepository.save(company);
|
|
}
|
|
|
|
/**
|
|
* 更新企业资料(幂等:不传字段则保留原值)
|
|
*/
|
|
@Transactional
|
|
public Company updateCompany(Long userId, CompanyUpdateRequest req) {
|
|
assertCompanyRole(userId);
|
|
Company company = companyRepository.findByOwnerUserIdForUpdate(userId)
|
|
.orElseThrow(() -> new BusinessException(ErrorCode.COMPANY_NOT_FOUND));
|
|
|
|
var industry = com.mydreamjob.industry.IndustryCatalog.selection(req.getIndustryCode(), req.getIndustry());
|
|
if (req.getName() != null) company.setName(req.getName());
|
|
if (req.getLogoUrl() != null) company.setLogoUrl(req.getLogoUrl());
|
|
if (req.getDescription() != null) company.setDescription(req.getDescription());
|
|
if (req.getIndustry() != null || req.getIndustryCode() != null) {
|
|
company.setIndustry(industry.name());
|
|
company.setIndustryCode(industry.code());
|
|
}
|
|
// companySize:null/缺省保持原值;其余交给与创建共用的同一套归一化规则。
|
|
if (req.getCompanySize() != null) {
|
|
company.setCompanySize(normalizeCompanySize(req.getCompanySize()));
|
|
}
|
|
if (req.getWebsite() != null) company.setWebsite(req.getWebsite());
|
|
if (req.getAddress() != null) company.setAddress(req.getAddress());
|
|
|
|
return companyRepository.save(company);
|
|
}
|
|
|
|
/** 企业规模长度上限,与请求 DTO 的 @Size(max = 40) 保持一致。 */
|
|
private static final int MAX_COMPANY_SIZE_LENGTH = 40;
|
|
|
|
/**
|
|
* companySize 统一归一化——创建与更新**共用同一套规则**,避免两套逻辑漂移。
|
|
*
|
|
* <ul>
|
|
* <li>{@code null} / 缺省 → {@code null}(创建即存 SQL NULL;更新由调用方先判 null 以「保持原值」)</li>
|
|
* <li>空串或纯空白 → {@code null}(清空)</li>
|
|
* <li>非空 → 去掉两端空白后保存(如 {@code " 100-499人 "} → {@code "100-499人"})</li>
|
|
* <li>超过 40 字符 → 抛参数错误,**不截断**</li>
|
|
* </ul>
|
|
*/
|
|
private static String normalizeCompanySize(String value) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
String trimmed = value.trim();
|
|
if (trimmed.isEmpty()) {
|
|
return null;
|
|
}
|
|
if (trimmed.length() > MAX_COMPANY_SIZE_LENGTH) {
|
|
throw new BusinessException(ErrorCode.BAD_REQUEST);
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
/**
|
|
* 将企业 Entity 转为 API 视图(排除敏感字段)
|
|
*/
|
|
public Map<String, Object> toView(Company company) {
|
|
if (company == null) return null;
|
|
Map<String, Object> view = new HashMap<>();
|
|
view.put("id", company.getId());
|
|
view.put("ownerUserId", company.getOwnerUserId());
|
|
view.put("name", company.getName());
|
|
view.put("logoUrl", company.getLogoUrl());
|
|
view.put("description", company.getDescription());
|
|
view.put("industry", company.getIndustry());
|
|
view.put("industryCode", company.getIndustryCode());
|
|
view.put("companySize", company.getCompanySize());
|
|
view.put("website", company.getWebsite());
|
|
view.put("address", company.getAddress());
|
|
view.put("verified", company.getVerified());
|
|
view.put("createdAt", company.getCreatedAt());
|
|
return view;
|
|
}
|
|
|
|
// ── 公开端:企业详情 ────────────────────────────────────────
|
|
|
|
/**
|
|
* 查询企业公开信息(匿名可访问)。
|
|
* 仅返回 6 个公开字段,不泄露 ownerUserId、联系方式、审核信息等。
|
|
*/
|
|
public CompanyPublicView getPublicCompany(Long companyId) {
|
|
Company company = companyRepository.findById(companyId)
|
|
.orElseThrow(() -> new BusinessException(ErrorCode.COMPANY_NOT_FOUND));
|
|
return toPublicView(company);
|
|
}
|
|
|
|
/**
|
|
* 将企业 Entity 转为公开视图(仅 6 个字段)。
|
|
* 不修改现有 toView() 方法。
|
|
*/
|
|
public CompanyPublicView toPublicView(Company company) {
|
|
return CompanyPublicView.builder()
|
|
.id(company.getId())
|
|
.name(company.getName())
|
|
.logoUrl(company.getLogoUrl())
|
|
.description(company.getDescription())
|
|
.industry(company.getIndustry())
|
|
.industryCode(company.getIndustryCode())
|
|
.companySize(company.getCompanySize())
|
|
.verified(company.getVerified())
|
|
.build();
|
|
}
|
|
|
|
// ── 公开端:企业岗位列表 ────────────────────────────────────
|
|
|
|
/**
|
|
* 查询企业公开岗位列表(匿名可访问)。
|
|
* 先确认企业存在,再复用已验收的 JobService.searchJobs。
|
|
*/
|
|
public Page<JobSearchItem> getCompanyJobs(Long companyId, String sort, int page, int size) {
|
|
companyRepository.findById(companyId)
|
|
.orElseThrow(() -> new BusinessException(ErrorCode.COMPANY_NOT_FOUND));
|
|
return jobService.searchJobs(
|
|
null, // keyword
|
|
null, // city
|
|
null, // employmentType
|
|
null, // salaryMin
|
|
null, // salaryMax
|
|
companyId, // companyId
|
|
sort, // sort
|
|
page, // page
|
|
size // size
|
|
);
|
|
}
|
|
|
|
/** 当前企业自身岗位管理列表;身份和企业归属均由服务端解析。 */
|
|
public Page<CompanyManagedJobItem> getMyJobs(Long userId, String status,
|
|
String keyword, String city,
|
|
int page, int size) {
|
|
Company company = findCompanyByOwnerUserId(userId);
|
|
if (company == null) throw new BusinessException(ErrorCode.COMPANY_NOT_FOUND);
|
|
return jobService.listCompanyJobs(company.getId(), status, keyword, city, page, size);
|
|
}
|
|
}
|
|
|