Compare commits
12 Commits
e0cb4493e3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c248f8e48 | |||
| 231aef7d74 | |||
| 79309ffa28 | |||
| a798c82835 | |||
| 310d99cfc3 | |||
| f318e44481 | |||
| e3ed3e94d4 | |||
| 4e2d9009c1 | |||
| e4384b003f | |||
| 28179e2a4d | |||
| f9af836285 | |||
| 98a838295d |
@@ -26,8 +26,8 @@ public class LoginController {
|
||||
private LoginService loginService;
|
||||
|
||||
@PostMapping("/sms/sendCode")
|
||||
public boolean sendCode(@RequestParam @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") String mobileNumber) {
|
||||
return loginService.sendCode(mobileNumber);
|
||||
public void sendCode(@RequestParam @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") String mobileNumber) {
|
||||
loginService.sendCode(mobileNumber);
|
||||
}
|
||||
|
||||
@PostMapping("/login/smsLogin")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.jiayunet.controller;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jiayunet.pojo.vo.RouteMenuVo;
|
||||
import org.jiayunet.service.RouteMenuService;
|
||||
import org.jiayunet.tool.UserSecurityTool;
|
||||
@@ -15,6 +16,7 @@ import java.util.List;
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/route")
|
||||
@AllArgsConstructor
|
||||
@@ -24,10 +26,16 @@ public class RouteMenuController {
|
||||
|
||||
/**
|
||||
* 获取当前用户有效路由菜单(树形结构)
|
||||
* <p>支持未登录访问,未登录时返回全量菜单但仅openAccess标记为可用</p>
|
||||
*/
|
||||
@GetMapping("/menu")
|
||||
public List<RouteMenuVo> getUserRoutes() {
|
||||
Long userId = UserSecurityTool.getUserId();
|
||||
Long userId = 0L;
|
||||
try {
|
||||
userId = UserSecurityTool.getUserId();
|
||||
} catch (Exception e) {
|
||||
log.debug("未登录用户访问路由菜单");
|
||||
}
|
||||
return routeMenuService.getUserRoutes(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.jiayunet.pojo.dto.job;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -41,6 +42,15 @@ public class JobDetailDto {
|
||||
/** 要求的行业经验名称 */
|
||||
private String requiredIndustryName;
|
||||
|
||||
/** 发布日期 */
|
||||
private Instant expireAt;
|
||||
|
||||
/** 脚本名字 */
|
||||
private String pyname;
|
||||
|
||||
/** 招聘分类 0=社招 1=校招 2=实习 3=其他 */
|
||||
private Integer recruitCategory;
|
||||
|
||||
// ========== 公司信息 ==========
|
||||
/** 公司ID */
|
||||
private Long companyId;
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.jiayunet.pojo.dto.job;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -54,6 +55,15 @@ public class JobDto {
|
||||
/** 岗位状态(0=有效 1=已下架 2=已过期) */
|
||||
private Integer status;
|
||||
|
||||
/** 发布日期 */
|
||||
private Instant expireAt;
|
||||
|
||||
/** 脚本名字 */
|
||||
private String pyname;
|
||||
|
||||
/** 招聘分类 0=社招 1=校招 2=实习 3=其他 */
|
||||
private Integer recruitCategory;
|
||||
|
||||
/** 匹配总分(0-100) */
|
||||
private Integer matchScore;
|
||||
|
||||
|
||||
@@ -30,4 +30,9 @@ public class JobIntentionDto {
|
||||
* 就业类型:0=全职,1=实习
|
||||
*/
|
||||
private Integer employmentType;
|
||||
|
||||
/**
|
||||
* 招聘分类: 0=社招, 1=校招, 2=实习, 3=其他
|
||||
*/
|
||||
private Integer recruitCategory;
|
||||
}
|
||||
|
||||
@@ -30,4 +30,9 @@ public class JobIntentionParam {
|
||||
* 就业类型:0=全职,1=实习
|
||||
*/
|
||||
private Integer employmentType;
|
||||
|
||||
/**
|
||||
* 招聘分类: 0=社招, 1=校招, 2=实习, 3=其他
|
||||
*/
|
||||
private Integer recruitCategory;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ public class JobQueryParam extends PageParam {
|
||||
/** 排除岗位ID列表(用于推荐时排除已推荐过的) */
|
||||
private List<Long> excludeJobIds;
|
||||
|
||||
/** 招聘分类 0=社招 1=校招 2=实习 3=其他 */
|
||||
private Integer recruitCategory;
|
||||
|
||||
/** 岗位状态过滤(0=有效 1=已下架 2=已过期,可多选,null或空=查所有) */
|
||||
private List<Integer> statusFilter;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ public class JobIntentionService {
|
||||
po.setRegionCodes(param.getRegionCodes() != null ? param.getRegionCodes() : java.util.Collections.emptyList());
|
||||
po.setIndustryIds(param.getIndustryIds() != null ? param.getIndustryIds() : java.util.Collections.emptyList());
|
||||
po.setEmploymentType(param.getEmploymentType());
|
||||
po.setRecruitCategory(param.getRecruitCategory());
|
||||
po.setCreateTime(Instant.now());
|
||||
po.setUpdateTime(Instant.now());
|
||||
intentionMapper.insert(po);
|
||||
@@ -63,6 +64,7 @@ public class JobIntentionService {
|
||||
po.setRegionCodes(param.getRegionCodes() != null ? param.getRegionCodes() : java.util.Collections.emptyList());
|
||||
po.setIndustryIds(param.getIndustryIds() != null ? param.getIndustryIds() : java.util.Collections.emptyList());
|
||||
po.setEmploymentType(param.getEmploymentType());
|
||||
po.setRecruitCategory(param.getRecruitCategory());
|
||||
po.setUpdateTime(Instant.now());
|
||||
intentionMapper.updateById(po);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public class JobService {
|
||||
List<UserJobDislike> dislikes = userJobDislikeMapper.selectList(new LambdaQueryWrapper<UserJobDislike>().eq(UserJobDislike::getUserId, userId));
|
||||
|
||||
// 3. 提取排除列表
|
||||
List<Long> excludeJobIds = new ArrayList<>(dislikes.stream().map(UserJobDislike::getJobId).filter(Objects::nonNull).distinct().collect(Collectors.toList()));
|
||||
List<Long> excludeJobIds = dislikes.stream().map(UserJobDislike::getJobId).filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (param.getExcludeJobIds() != null && !param.getExcludeJobIds().isEmpty()) {
|
||||
excludeJobIds.addAll(param.getExcludeJobIds());
|
||||
}
|
||||
@@ -127,7 +127,7 @@ public class JobService {
|
||||
// 6. 执行分页查询
|
||||
Page<JobListItemVo> pageParam = param.toPage();
|
||||
pageParam.setSearchCount(false);
|
||||
Page<JobListItemVo> page = jobMapper.selectJobPage(pageParam, param.getJobIds(),param.getStatusFilter(), param.getKeyword(), expandedRegionCodes, expandedCategoryIds, expandedIndustryIds, param.getEmploymentType(), excludeJobIds, excludeCompanyIds, excludeRegionCodes, excludeIndustryIds);
|
||||
Page<JobListItemVo> page = jobMapper.selectJobPage(pageParam, param.getJobIds(),param.getStatusFilter(), param.getKeyword(), expandedRegionCodes, expandedCategoryIds, expandedIndustryIds, param.getEmploymentType(), excludeJobIds, excludeCompanyIds, excludeRegionCodes, excludeIndustryIds, param.getRecruitCategory());
|
||||
|
||||
// 7. 查询收藏状态
|
||||
List<Long> jobIds = page.getRecords().stream().map(JobListItemVo::getId).collect(Collectors.toList());
|
||||
@@ -292,6 +292,9 @@ public class JobService {
|
||||
dto.setTags(job.getTags());
|
||||
dto.setSkillTags(job.getSkillTags());
|
||||
dto.setSourceUrl(job.getSourceUrl());
|
||||
dto.setExpireAt(job.getExpireAt());
|
||||
dto.setPyname(job.getPyname());
|
||||
dto.setRecruitCategory(job.getRecruitCategory());
|
||||
dto.setCategoryName(categoryName);
|
||||
dto.setRequiredIndustryName(requiredIndustryName);
|
||||
dto.setCompanyId(company.getId());
|
||||
@@ -611,7 +614,7 @@ public class JobService {
|
||||
|
||||
/**
|
||||
* 求职助手岗位推荐
|
||||
* <p>1. 查求职意向构造筛选条件(无意向则不设条件) 2. 排除已推荐的+已投递的 3. 取前35条候选 4. AI精筛返回8-10个</p>
|
||||
* <p>1. 查求职意向构造筛选条件(无意向则不设条件) 2. 排除已推荐的+已投递的 3. 取2000条候选池 4. 随机取100条 5. AI精筛返回8-10个</p>
|
||||
*/
|
||||
public JobAgentRecommendDto recommendJobs(JobAgentRecommendParam param, Long userId) {
|
||||
StopWatch sw = new StopWatch("recommendJobs");
|
||||
@@ -624,12 +627,13 @@ public class JobService {
|
||||
// 2. 构造查询参数
|
||||
JobQueryParam queryParam = new JobQueryParam();
|
||||
queryParam.setPageNum(1);
|
||||
queryParam.setPageSize(100);
|
||||
queryParam.setPageSize(2000);
|
||||
if (intention != null) {
|
||||
queryParam.setCategoryIds(intention.getCategoryIds());
|
||||
queryParam.setRegionCodes(intention.getRegionCodes());
|
||||
queryParam.setIndustryIds(intention.getIndustryIds());
|
||||
queryParam.setEmploymentType(intention.getEmploymentType());
|
||||
queryParam.setRecruitCategory(intention.getRecruitCategory());
|
||||
}
|
||||
|
||||
// 3. 合并排除列表:入参传的 + 已投递/待投递的
|
||||
@@ -643,7 +647,7 @@ public class JobService {
|
||||
applications.forEach(a -> excludeIds.add(a.getJobId()));
|
||||
queryParam.setExcludeJobIds(excludeIds);
|
||||
|
||||
// 4. 查询候选岗位(完整列表数据)
|
||||
// 4. 查询候选池(2000条)
|
||||
sw.start("查候选岗位");
|
||||
PageResult<JobDto> candidates = listJobs(queryParam, userId);
|
||||
sw.stop();
|
||||
@@ -654,27 +658,34 @@ public class JobService {
|
||||
return dto;
|
||||
}
|
||||
|
||||
// 5. 批量查岗位详情(title + description + requirement)
|
||||
// 5. 随机取100条作为AI候选
|
||||
List<JobDto> candidateList = new ArrayList<>(candidates.getList());
|
||||
Collections.shuffle(candidateList);
|
||||
if (candidateList.size() > 100) {
|
||||
candidateList = candidateList.subList(0, 100);
|
||||
}
|
||||
|
||||
// 6. 批量查岗位详情(title + description + requirement)
|
||||
sw.start("查岗位详情");
|
||||
List<Long> candidateJobIds = candidates.getList().stream().map(JobDto::getId).collect(Collectors.toList());
|
||||
List<Long> candidateJobIds = candidateList.stream().map(JobDto::getId).collect(Collectors.toList());
|
||||
List<Job> jobDetails = jobMapper.selectList(new LambdaQueryWrapper<Job>().in(Job::getId, candidateJobIds).select(Job::getId, Job::getTitle, Job::getDescription, Job::getRequirement));
|
||||
Map<Long, Job> jobDetailMap = jobDetails.stream().collect(Collectors.toMap(Job::getId, j -> j));
|
||||
sw.stop();
|
||||
|
||||
// 6. 构造别名映射(短序号 → 真实ID)和候选岗位映射
|
||||
// 7. 构造别名映射(短序号 → 真实ID)和候选岗位映射
|
||||
Map<Integer, Long> aliasToRealId = new HashMap<>();
|
||||
Map<Long, JobDto> candidateMap = new HashMap<>();
|
||||
int seq = 1;
|
||||
for (JobDto dto : candidates.getList()) {
|
||||
for (JobDto dto : candidateList) {
|
||||
aliasToRealId.put(seq, dto.getId());
|
||||
candidateMap.put(dto.getId(), dto);
|
||||
seq++;
|
||||
}
|
||||
|
||||
// 7. 构造AI输入(用短序号替代19位雪花ID,减少token消耗)
|
||||
// 8. 构造AI输入(用短序号替代19位雪花ID,减少token消耗)
|
||||
StringBuilder jobInfo = new StringBuilder();
|
||||
seq = 1;
|
||||
for (JobDto dto : candidates.getList()) {
|
||||
for (JobDto dto : candidateList) {
|
||||
Job detail = jobDetailMap.get(dto.getId());
|
||||
jobInfo.append("ID:").append(seq++)
|
||||
.append("\n标题:").append(dto.getTitle())
|
||||
@@ -692,7 +703,7 @@ public class JobService {
|
||||
"只返回JSON,不要其他内容。";
|
||||
String userMessage = "【用户偏好(仅供参考)】\n" + preferenceInfo + "\n\n【候选岗位】\n" + jobInfo;
|
||||
|
||||
// 8. 调用AI
|
||||
// 9. 调用AI
|
||||
sw.start("AI调用");
|
||||
String aiResponse = aiChatAbility.chat("job-recommend", systemPrompt, userMessage);
|
||||
String json = AiResponseCleanTool.clean(aiResponse);
|
||||
@@ -700,7 +711,7 @@ public class JobService {
|
||||
|
||||
log.info("recommendJobs耗时统计:\n{}", sw.prettyPrint());
|
||||
|
||||
// 9. 解析AI返回,别名映射回真实ID,过滤出选中的岗位
|
||||
// 10. 解析AI返回,别名映射回真实ID,过滤出选中的岗位
|
||||
try {
|
||||
JsonNode root = HttpTool.objectMapper.readTree(json);
|
||||
String summary = root.path("summary").asText("为你精选了合适的岗位");
|
||||
|
||||
@@ -66,16 +66,16 @@ public class LoginService {
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* <p>1. 生成6位随机验证码 2. 委托SmsService发送</p>
|
||||
* <p>1. 生成6位随机验证码 2. 委托SmsService发送,失败抛异常</p>
|
||||
*/
|
||||
public boolean sendCode(String mobileNumber) {
|
||||
public void sendCode(String mobileNumber) {
|
||||
Assert.hasText(mobileNumber, "手机号不能为空");
|
||||
|
||||
UniversalSmsVariable variable = new UniversalSmsVariable();
|
||||
String code = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
|
||||
variable.setCode(code);
|
||||
|
||||
return smsService.send(mobileNumber, SmsTemplateEnum.UNIVERSAL, variable);
|
||||
smsService.send(mobileNumber, SmsTemplateEnum.UNIVERSAL, variable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,7 @@ public class MemberProductService {
|
||||
@Autowired
|
||||
private MessageService messageService;
|
||||
|
||||
//@Autowired
|
||||
@Autowired
|
||||
private WxNativePayAbility wxNativePayAbility;
|
||||
|
||||
@Autowired
|
||||
@@ -151,6 +151,7 @@ public class MemberProductService {
|
||||
* 微信Native支付下单
|
||||
*/
|
||||
private String prepayWechat(String orderNo, MemberProduct product) {
|
||||
|
||||
PayWechatFlow flow = new PayWechatFlow();
|
||||
flow.setOrderType("member");
|
||||
flow.setOrderNo(orderNo);
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.time.Instant;
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
//@Service
|
||||
@Service
|
||||
@Primary
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -23,14 +23,14 @@ email:
|
||||
# 微信支付
|
||||
wx_pay:
|
||||
# status close:关闭 open:打开
|
||||
status: close
|
||||
app_id: your_app_id
|
||||
merchant_id: ${MERCHANT_ID:your_merchant_id}
|
||||
private_key: ${WX_PRIVATE_KEY:your_private_key}
|
||||
merchant_serial_number: ${MERCHANT_SERIAL_NUMBER:your_merchant_serial_number}
|
||||
api_v3_key: ${API_V3_KEY:your_api_v3_key}
|
||||
public_key: ${WX_PUBLIC_KEY:your_public_key}
|
||||
public_key_id: ${PUBLIC_KEY_ID:your_public_key_id}
|
||||
status: open
|
||||
app_id: wx12cee9ea7c6a2069
|
||||
merchant_id: ${MERCHANT_ID:1374348102}
|
||||
private_key: ${WX_PRIVATE_KEY:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQcL1GGv5kr4KVacUwUAZzz7Zh2wsn5rJZyTm8MiRlR3P/3kkiZqT4gHkyhLjfS80LJh4vm5mslY2N4ZeJR0KNmRX4Zl17AzIcxpfp1790Dsbc6YBJw5UCaalJRmf8Wg4Dy9VfcUpOTwfxNz2QsiL18mtrEk69oF/7V/E1rlI1zYRoi+zei12LftYnBATKIW3/yqKZpCmPHHjG2l5wmrUDlOfEZhKaFc4wTyXJOum6kGwBFv+QxsYH7qDaTfUFGqww1n0wX5ItTDqUN/ETNgANkRMp3TUyd65ch1eWUA26Hy693e+GZ6wdSzam7xl8m6nsiVM143NBrW1PNvEZ5mFhAgMBAAECggEAF5/vk/aTRs4tusmWM/eeA5pTTZwKE6euJ0cLaC02utPybdm/6EpCAHhvRC6O1aA9yTm0yiiUrl71AFPFXSXagToMp27SwZKHJ4PKzBJL/Nd/6qH8jF/Cj9dx/IAiYRPD6uI7eOnaRi3m9IwSSkgmdkHXsvcMPanjtxUsnaz4XGiOLN9JjrAM/vk4lrgzQaDqagxPkM1+mXuFYB6RJsd8IS2tOBwUIVGkadSLZcqtEJw29Bku/QtsJiBE/Xk7kCY+Le8Jrtw2QH0BthbfZuKQvGChulK6BBqaVaEZDohO82rn3Fovqcag2V0za1FoJa1sAHKkQMfTajQZiWHNFf/IwQKBgQD/VcPNfqPG7ilPxFOaEQoV4iACA6WxEWwiqixsouW2WZ4iXJZd4OczbCAqkd9QjJ3CZtZJMS8Zmyx9W1o26Tre/y9v4A0vYmb8ldSsxoCIbTh4Un4+tT9KeXhznfV+bOq7eqZNY8UJ8pEw99WdriOLS0EtoyChiN48vIVT08k8HQKBgQDQ+7WQ5okHjYrAs3Is5nCshRpfnfQDn6qV87ztWIG/6o54AZ1hRZjVGRH9NB2aK0jWiHbr7ELZufGXgt+UbbLq9wV/QYF+nZDvlZi0LcLaOvlpP7Z9fcAYVVxpSlTbRSUsSrqYwyjQw0NwOrhQIzJxsXUzp5MdMo23e9UYqZ3PFQKBgQDUcYd4cuXRnlbBB5iHl6XMj1gSVPaHXPeb3/sWaHK4RdhMvrxdX8L+Bfi9cqFbY6PfG7EvNLz7kSBzLI7jISraX3gYnVJbGoSVpBRPrNlEqZtSgVCI3ETMskSF7edUSUsEsgesXFaU8D3dgFc72qe4PeT9E70Sqc4+upw0IwJMAQKBgDQD8pU2TDUNDdQY1fpHARRvrLjP38RnIVZhbEzHEsRp5TwJatqjL/8aVlfneNa+n0qGfNML4ze+CFvlvzB6fWXuRff+nfSd001Otsth+HXI5/tCWHGzsvRbirzKO4S4GSCFGRyctrP/ZfGxK98GY6/Ys4s/0mYaBxv8PnX9AcJhAoGAF6AWpeB1+IlNj+M2FZAZigGBsIsS4PMTOp5oBHqjh4RpPJa8haESaNMDbAkLIlBwWsqzchQQOvVap8hv9ft8cyoQ/995xADQHJTSn5wkZzpDXQiNPwRoxmd852Kln0WQ88XmbbgZfeYMlDN3ovA3JC3NNBZRflAmVYAQrKsF+fM=}
|
||||
merchant_serial_number: ${MERCHANT_SERIAL_NUMBER:50425CBE34C658C6E9A2F4F9DD0E85DC5F00DDD9}
|
||||
api_v3_key: ${API_V3_KEY:Youlibao201508Youlibao2015082022}
|
||||
public_key: ${WX_PUBLIC_KEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw7q/wZEL3ecfOGD9DfhEEoSS8TsLm+PLqHe5YbYfuRsk+Umdv7zfpncPcXa7xjXCiy9Y5bSFH0U6qnz0IChkl8hbYcwNQ6WWDFfSuYBsglPZcbDbDCk8cMRSmbusHKBfKTSJS5JBzaD3G2d7b9QkTOejOJNb4eYC7JkjTC/cPiWZztFtDP3tIismqvg6UHPw4AkHc+YX2yPF+e6sVlsPbn2xwzKZW+NU+JkQTi2uUeC1icSeF0uDVxvK3SZMQqkhw75h8zBhAZ+F9B30u4ExYBJtIMubaruSUERUFLb2JiQK4tECqX9lJa43nfXP3pHw6tDJicoA4yJ5AypkWI2+xwIDAQAB}
|
||||
public_key_id: ${PUBLIC_KEY_ID:PUB_KEY_ID_0113743481022025060400112207001600}
|
||||
notify_domain: ${API_NOTIFY_DOMAIN:http://127.0.0.1:8080/api}
|
||||
|
||||
# 支付宝支付
|
||||
@@ -79,7 +79,7 @@ app:
|
||||
|
||||
#开放接口
|
||||
ignore:
|
||||
urls: "/public/**,/job/list,/job/detail"
|
||||
urls: "/public/**,/job/list,/job/detail,/route/menu"
|
||||
|
||||
# 简历配置
|
||||
resume:
|
||||
|
||||
@@ -23,15 +23,15 @@ email:
|
||||
# 微信支付
|
||||
wx_pay:
|
||||
# status close:关闭 open:打开
|
||||
status: close
|
||||
app_id: your_app_id
|
||||
merchant_id: ${MERCHANT_ID:your_merchant_id}
|
||||
private_key: ${WX_PRIVATE_KEY:your_private_key}
|
||||
merchant_serial_number: ${MERCHANT_SERIAL_NUMBER:your_merchant_serial_number}
|
||||
api_v3_key: ${API_V3_KEY:your_api_v3_key}
|
||||
public_key: ${WX_PUBLIC_KEY:your_public_key}
|
||||
public_key_id: ${PUBLIC_KEY_ID:your_public_key_id}
|
||||
notify_domain: ${API_NOTIFY_DOMAIN:http://127.0.0.1:8080/api}
|
||||
status: open
|
||||
app_id: wx12cee9ea7c6a2069
|
||||
merchant_id: ${MERCHANT_ID:1374348102}
|
||||
private_key: ${WX_PRIVATE_KEY:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQcL1GGv5kr4KVacUwUAZzz7Zh2wsn5rJZyTm8MiRlR3P/3kkiZqT4gHkyhLjfS80LJh4vm5mslY2N4ZeJR0KNmRX4Zl17AzIcxpfp1790Dsbc6YBJw5UCaalJRmf8Wg4Dy9VfcUpOTwfxNz2QsiL18mtrEk69oF/7V/E1rlI1zYRoi+zei12LftYnBATKIW3/yqKZpCmPHHjG2l5wmrUDlOfEZhKaFc4wTyXJOum6kGwBFv+QxsYH7qDaTfUFGqww1n0wX5ItTDqUN/ETNgANkRMp3TUyd65ch1eWUA26Hy693e+GZ6wdSzam7xl8m6nsiVM143NBrW1PNvEZ5mFhAgMBAAECggEAF5/vk/aTRs4tusmWM/eeA5pTTZwKE6euJ0cLaC02utPybdm/6EpCAHhvRC6O1aA9yTm0yiiUrl71AFPFXSXagToMp27SwZKHJ4PKzBJL/Nd/6qH8jF/Cj9dx/IAiYRPD6uI7eOnaRi3m9IwSSkgmdkHXsvcMPanjtxUsnaz4XGiOLN9JjrAM/vk4lrgzQaDqagxPkM1+mXuFYB6RJsd8IS2tOBwUIVGkadSLZcqtEJw29Bku/QtsJiBE/Xk7kCY+Le8Jrtw2QH0BthbfZuKQvGChulK6BBqaVaEZDohO82rn3Fovqcag2V0za1FoJa1sAHKkQMfTajQZiWHNFf/IwQKBgQD/VcPNfqPG7ilPxFOaEQoV4iACA6WxEWwiqixsouW2WZ4iXJZd4OczbCAqkd9QjJ3CZtZJMS8Zmyx9W1o26Tre/y9v4A0vYmb8ldSsxoCIbTh4Un4+tT9KeXhznfV+bOq7eqZNY8UJ8pEw99WdriOLS0EtoyChiN48vIVT08k8HQKBgQDQ+7WQ5okHjYrAs3Is5nCshRpfnfQDn6qV87ztWIG/6o54AZ1hRZjVGRH9NB2aK0jWiHbr7ELZufGXgt+UbbLq9wV/QYF+nZDvlZi0LcLaOvlpP7Z9fcAYVVxpSlTbRSUsSrqYwyjQw0NwOrhQIzJxsXUzp5MdMo23e9UYqZ3PFQKBgQDUcYd4cuXRnlbBB5iHl6XMj1gSVPaHXPeb3/sWaHK4RdhMvrxdX8L+Bfi9cqFbY6PfG7EvNLz7kSBzLI7jISraX3gYnVJbGoSVpBRPrNlEqZtSgVCI3ETMskSF7edUSUsEsgesXFaU8D3dgFc72qe4PeT9E70Sqc4+upw0IwJMAQKBgDQD8pU2TDUNDdQY1fpHARRvrLjP38RnIVZhbEzHEsRp5TwJatqjL/8aVlfneNa+n0qGfNML4ze+CFvlvzB6fWXuRff+nfSd001Otsth+HXI5/tCWHGzsvRbirzKO4S4GSCFGRyctrP/ZfGxK98GY6/Ys4s/0mYaBxv8PnX9AcJhAoGAF6AWpeB1+IlNj+M2FZAZigGBsIsS4PMTOp5oBHqjh4RpPJa8haESaNMDbAkLIlBwWsqzchQQOvVap8hv9ft8cyoQ/995xADQHJTSn5wkZzpDXQiNPwRoxmd852Kln0WQ88XmbbgZfeYMlDN3ovA3JC3NNBZRflAmVYAQrKsF+fM=}
|
||||
merchant_serial_number: ${MERCHANT_SERIAL_NUMBER:50425CBE34C658C6E9A2F4F9DD0E85DC5F00DDD9}
|
||||
api_v3_key: ${API_V3_KEY:Youlibao201508Youlibao2015082022}
|
||||
public_key: ${WX_PUBLIC_KEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw7q/wZEL3ecfOGD9DfhEEoSS8TsLm+PLqHe5YbYfuRsk+Umdv7zfpncPcXa7xjXCiy9Y5bSFH0U6qnz0IChkl8hbYcwNQ6WWDFfSuYBsglPZcbDbDCk8cMRSmbusHKBfKTSJS5JBzaD3G2d7b9QkTOejOJNb4eYC7JkjTC/cPiWZztFtDP3tIismqvg6UHPw4AkHc+YX2yPF+e6sVlsPbn2xwzKZW+NU+JkQTi2uUeC1icSeF0uDVxvK3SZMQqkhw75h8zBhAZ+F9B30u4ExYBJtIMubaruSUERUFLb2JiQK4tECqX9lJa43nfXP3pHw6tDJicoA4yJ5AypkWI2+xwIDAQAB}
|
||||
public_key_id: ${PUBLIC_KEY_ID:PUB_KEY_ID_0113743481022025060400112207001600}
|
||||
notify_domain: ${API_NOTIFY_DOMAIN:http://8.148.237.97:10202/api}
|
||||
|
||||
# 支付宝支付
|
||||
alipay:
|
||||
@@ -79,7 +79,7 @@ app:
|
||||
|
||||
#开放接口
|
||||
ignore:
|
||||
urls: "/public/**,/job/list,/job/detail"
|
||||
urls: "/public/**,/job/list,/job/detail,/route/menu"
|
||||
|
||||
# 简历配置
|
||||
resume:
|
||||
|
||||
@@ -79,7 +79,7 @@ app:
|
||||
|
||||
#开放接口
|
||||
ignore:
|
||||
urls: "/public/**,/job/list,/job/detail"
|
||||
urls: "/public/**,/job/list,/job/detail,/route/menu"
|
||||
|
||||
# 简历配置
|
||||
resume:
|
||||
|
||||
@@ -15,7 +15,7 @@ public enum SmsTemplateEnum {
|
||||
/**
|
||||
* 通用验证码
|
||||
*/
|
||||
UNIVERSAL("油梨科技", "SMS_501981064", 5, UniversalSmsVariable.class);
|
||||
UNIVERSAL("油梨科技", "SMS_501981064", 5,1, UniversalSmsVariable.class);
|
||||
|
||||
/**
|
||||
* 模板签名
|
||||
@@ -34,6 +34,13 @@ public enum SmsTemplateEnum {
|
||||
*/
|
||||
private final Integer effectiveTime;
|
||||
|
||||
/**
|
||||
* 重发限制时间,单位分钟。发送后需间隔该时间才允许重发。
|
||||
* NULL 或 <= 0:不限制重复发送
|
||||
* > 0:发送后需等待指定分钟后才可重新发送
|
||||
*/
|
||||
private final Integer retryTime;
|
||||
|
||||
/**
|
||||
* 短信参数对象类
|
||||
*/
|
||||
|
||||
@@ -19,5 +19,5 @@ public interface JobMapper extends CommonMapper<Job> {
|
||||
/**
|
||||
* 分页查询岗位列表
|
||||
*/
|
||||
Page<JobListItemVo> selectJobPage(Page<JobListItemVo> page, @Param("jobIds") List<Long> jobIds, @Param("statusFilter") List<Integer> statusFilter, @Param("keyword") String keyword, @Param("regionCodes") List<String> regionCodes, @Param("categoryIds") List<Long> categoryIds, @Param("industryIds") List<Long> industryIds, @Param("employmentType") Integer employmentType, @Param("excludeJobIds") List<Long> excludeJobIds, @Param("excludeCompanyIds") List<Long> excludeCompanyIds, @Param("excludeRegionCodes") List<String> excludeRegionCodes, @Param("excludeIndustryIds") List<Long> excludeIndustryIds);
|
||||
Page<JobListItemVo> selectJobPage(Page<JobListItemVo> page, @Param("jobIds") List<Long> jobIds, @Param("statusFilter") List<Integer> statusFilter, @Param("keyword") String keyword, @Param("regionCodes") List<String> regionCodes, @Param("categoryIds") List<Long> categoryIds, @Param("industryIds") List<Long> industryIds, @Param("employmentType") Integer employmentType, @Param("excludeJobIds") List<Long> excludeJobIds, @Param("excludeCompanyIds") List<Long> excludeCompanyIds, @Param("excludeRegionCodes") List<String> excludeRegionCodes, @Param("excludeIndustryIds") List<Long> excludeIndustryIds, @Param("recruitCategory") Integer recruitCategory);
|
||||
}
|
||||
|
||||
@@ -77,12 +77,18 @@ public class Job {
|
||||
/** 爬虫原始数据ID,用于去重 */
|
||||
private String sourceId;
|
||||
|
||||
/** 招聘分类 0=校招 1=实习 2=社招 3=其他 */
|
||||
/** 内容哈希,用于去重续命 */
|
||||
private String contentHash;
|
||||
|
||||
/** 招聘分类 0=社招 1=校招 2=实习 3=其他 */
|
||||
private Integer recruitCategory;
|
||||
|
||||
/** 发布日期 */
|
||||
private Instant expireAt;
|
||||
|
||||
/** 脚本名字 */
|
||||
private String pyname;
|
||||
|
||||
/** 状态 0=上架 1=下架 2=已失效 */
|
||||
private Integer status;
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ public class UserJobIntention {
|
||||
/** 工作类型 0=全职 1=实习 */
|
||||
private Integer employmentType;
|
||||
|
||||
/** 招聘分类: 0=社招, 1=校招, 2=实习, 3=其他 */
|
||||
private Integer recruitCategory;
|
||||
|
||||
/** 创建时间 */
|
||||
private Instant createTime;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.Data;
|
||||
import org.jiayunet.config.LongListTypeHandler;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -73,4 +74,13 @@ public class JobListItemVo {
|
||||
|
||||
/** 岗位状态(0=有效 1=已下架 2=已过期) */
|
||||
private Integer status;
|
||||
|
||||
/** 发布日期 */
|
||||
private Instant expireAt;
|
||||
|
||||
/** 脚本名字 */
|
||||
private String pyname;
|
||||
|
||||
/** 招聘分类 0=社招 1=校招 2=实习 3=其他 */
|
||||
private Integer recruitCategory;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.jiayunet.constant.SmsTemplateEnum;
|
||||
import org.jiayunet.constant.sms.SmsVariableAllows;
|
||||
import org.jiayunet.constant.sms.UniversalSmsVariable;
|
||||
import org.jiayunet.exception.BusinessException;
|
||||
import org.jiayunet.exception.BusinessExpCodeEnum;
|
||||
import org.jiayunet.sms.AliYunSmsAbility;
|
||||
import org.jiayunet.tool.server.RedisServerTool;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -41,33 +43,26 @@ public class SmsService {
|
||||
* @param phone 目标手机号,不能为空
|
||||
* @param template 短信模板枚举,必须与 {@code variable} 的类型匹配
|
||||
* @param variable 短信参数对象,可能是 {@link UniversalSmsVariable}(验证码)或其他实现
|
||||
* @return {@code true} 表示短信发送成功,{@code false} 表示发送失败或前置参数校验未通过
|
||||
* @throws BusinessException 发送失败时抛出业务异常
|
||||
*/
|
||||
public boolean send(String phone, SmsTemplateEnum template, SmsVariableAllows variable) {
|
||||
try {
|
||||
Assert.hasText(phone, "手机号不能为空");
|
||||
Assert.notNull(template, "短信模板不能为空");
|
||||
Assert.notNull(variable, "短信参数不能为空");
|
||||
public void send(String phone, SmsTemplateEnum template, SmsVariableAllows variable) {
|
||||
Assert.hasText(phone, "手机号不能为空");
|
||||
Assert.notNull(template, "短信模板不能为空");
|
||||
Assert.notNull(variable, "短信参数不能为空");
|
||||
|
||||
// 校验参数类型是否匹配
|
||||
if (!template.getVariableClass().isInstance(variable)) {
|
||||
log.error("短信参数类型不匹配: template={}, expectedClass={}, actualClass={}",
|
||||
template.name(), template.getVariableClass().getName(), variable.getClass().getName());
|
||||
return false;
|
||||
}
|
||||
// 校验参数类型是否匹配
|
||||
if (!template.getVariableClass().isInstance(variable)) {
|
||||
log.error("短信参数类型不匹配: template={}, expectedClass={}, actualClass={}", template.name(), template.getVariableClass().getName(), variable.getClass().getName());
|
||||
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "短信发送失败");
|
||||
}
|
||||
|
||||
// 判断是否激活验证码模式
|
||||
boolean isVerifyMode = isVerifyCodeMode(template, variable);
|
||||
// 判断是否激活验证码模式
|
||||
boolean isVerifyMode = isVerifyCodeMode(template, variable);
|
||||
|
||||
if (isVerifyMode) {
|
||||
return sendVerifyCode(phone, template, (UniversalSmsVariable) variable);
|
||||
} else {
|
||||
return sendNormalSms(phone, template, variable);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("发送短信异常: phone={}, template={}", phone, template.name(), e);
|
||||
return false;
|
||||
if (isVerifyMode) {
|
||||
sendVerifyCode(phone, template, (UniversalSmsVariable) variable);
|
||||
} else {
|
||||
sendNormalSms(phone, template, variable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +159,7 @@ public class SmsService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 发送验证码短信。
|
||||
*
|
||||
* 在发送前先检查 Redis 中是否已有未过期的验证码,防止重复发送。
|
||||
@@ -173,15 +168,24 @@ public class SmsService {
|
||||
* @param phone 目标手机号,不能为空
|
||||
* @param template 短信模板枚举,必须包含验证码相关配置(如有效时间)
|
||||
* @param variable {@link UniversalSmsVariable},其中必须包含非空的 {@code code}
|
||||
* @return {@code true} 表示短信发送及缓存成功,{@code false} 表示发送被拦截或失败
|
||||
* @throws BusinessException 发送被拦截或失败时抛出业务异常
|
||||
*/
|
||||
private boolean sendVerifyCode(String phone, SmsTemplateEnum template, UniversalSmsVariable variable) {
|
||||
private void sendVerifyCode(String phone, SmsTemplateEnum template, UniversalSmsVariable variable) {
|
||||
String key = buildVerifyCodeKey(template, phone);
|
||||
|
||||
// 检查是否重复发送
|
||||
// 检查是否允许重发
|
||||
if (redisServerTool.hasKey(key)) {
|
||||
log.warn("验证码尚未过期,请勿重复发送: phone={}, template={}", phone, template.name());
|
||||
return false;
|
||||
if (template.getRetryTime() != null && template.getRetryTime() > 0) {
|
||||
// 配置了重发间隔且大于0:判断已过时间是否超过 retryTime
|
||||
Long remainSeconds = redisServerTool.getExpire(key);
|
||||
long elapsedSeconds = template.getEffectiveTime() * 60L - (remainSeconds != null ? remainSeconds : 0L);
|
||||
if (elapsedSeconds < template.getRetryTime() * 60L) {
|
||||
log.warn("重发间隔未到,请{}分钟后再试: phone={}, template={}", template.getRetryTime(), phone, template.name());
|
||||
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "请勿重复发送");
|
||||
}
|
||||
// 已超过重发间隔,允许重新发送,后续会覆盖旧验证码
|
||||
}
|
||||
// retryTime 为 null 或 <= 0 时不限制重复发送
|
||||
}
|
||||
|
||||
// 转换参数对象为 Map<String, String>
|
||||
@@ -195,30 +199,34 @@ public class SmsService {
|
||||
variableMap
|
||||
);
|
||||
|
||||
// 发送成功后存储验证码
|
||||
if (success) {
|
||||
redisServerTool.set(key, variable.getCode(), template.getEffectiveTime(), TimeUnit.MINUTES);
|
||||
log.info("验证码已存储: phone={}, template={}, effectiveTime={}分钟",
|
||||
phone, template.name(), template.getEffectiveTime());
|
||||
if (!success) {
|
||||
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "短信发送失败,请稍后重试");
|
||||
}
|
||||
|
||||
return success;
|
||||
// 发送成功后存储验证码
|
||||
redisServerTool.set(key, variable.getCode(), template.getEffectiveTime(), TimeUnit.MINUTES);
|
||||
log.info("验证码已存储: phone={}, template={}, effectiveTime={}分钟",
|
||||
phone, template.name(), template.getEffectiveTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送普通短信
|
||||
*/
|
||||
private boolean sendNormalSms(String phone, SmsTemplateEnum template, SmsVariableAllows variable) {
|
||||
private void sendNormalSms(String phone, SmsTemplateEnum template, SmsVariableAllows variable) {
|
||||
// 转换参数对象为 Map<String, String>
|
||||
Map<String, String> variableMap = objectMapper.convertValue(variable, new com.fasterxml.jackson.core.type.TypeReference<Map<String, String>>() {});
|
||||
|
||||
// 直接发送短信
|
||||
return aliYunSmsAbility.sendSms(
|
||||
boolean success = aliYunSmsAbility.sendSms(
|
||||
phone,
|
||||
template.getSignName(),
|
||||
template.getTemplateCode(),
|
||||
variableMap
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "短信发送失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<result column="required_major_ids" property="requiredMajorIds" typeHandler="org.jiayunet.config.LongListTypeHandler"/>
|
||||
<result column="major_sensitivity" property="majorSensitivity"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="expire_at" property="expireAt"/>
|
||||
<result column="pyname" property="pyname"/>
|
||||
<result column="recruit_category" property="recruitCategory"/>
|
||||
<result column="company_id" property="companyId"/>
|
||||
<result column="company_name" property="companyName"/>
|
||||
<result column="company_short_name" property="companyShortName"/>
|
||||
@@ -36,6 +39,9 @@
|
||||
j.required_major_ids,
|
||||
j.major_sensitivity,
|
||||
j.status,
|
||||
j.expire_at,
|
||||
j.pyname,
|
||||
j.recruit_category,
|
||||
c.id AS company_id,
|
||||
c.name AS company_name,
|
||||
c.short_name AS company_short_name,
|
||||
@@ -126,6 +132,10 @@
|
||||
<if test="employmentType != null">
|
||||
AND j.employment_type = #{employmentType}
|
||||
</if>
|
||||
<!-- 筛选条件:招聘分类 -->
|
||||
<if test="recruitCategory != null">
|
||||
AND j.recruit_category = #{recruitCategory}
|
||||
</if>
|
||||
ORDER BY j.id DESC
|
||||
</select>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user