Merge branch 'main' of ssh://124.243.245.42:2222/zkr/shz-backend

This commit is contained in:
2026-07-20 11:30:24 +08:00
13 changed files with 419 additions and 13 deletions

View File

@@ -3,10 +3,14 @@ package com.ruoyi.cms.controller.cms;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.cms.domain.AppReviewJob;
import com.ruoyi.cms.domain.ResumeAccessLog;
import com.ruoyi.cms.domain.ResumeStatusDTO;
import com.ruoyi.cms.mapper.ResumeAccessLogMapper;
import com.ruoyi.cms.service.ICompanyService;
import com.ruoyi.cms.domain.vo.AppUserLky;
import com.ruoyi.cms.service.IAppReviewJobService;
import com.ruoyi.cms.util.DateValidateUtil;
@@ -35,7 +39,9 @@ import com.ruoyi.common.enums.BusinessType;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import com.ruoyi.common.core.domain.entity.AppUser;
import com.ruoyi.common.core.domain.entity.Company;
import com.ruoyi.cms.service.IAppUserService;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
@@ -54,6 +60,10 @@ public class CmsAppUserController extends BaseController
private IAppUserService appUserService;
@Autowired
private IAppReviewJobService appReviewJobService;
@Autowired
private ResumeAccessLogMapper resumeAccessLogMapper;
@Autowired
private ICompanyService companyService;
/**
* 查询APP用户列表
@@ -99,6 +109,35 @@ public class CmsAppUserController extends BaseController
return success(appUserService.selectAppUserByUserId(userId));
}
/**
* 获取用户完整简历信息(个人信息+教育+培训+工作经历+技能)
*/
@ApiOperation("获取用户完整简历信息")
@PreAuthorize("@ss.hasPermi('cms:appUser:query') or @ss.hasPermi('cms:hrTalentCollect:list')")
@GetMapping(value = "/resume/{userId}")
public AjaxResult getResume(@PathVariable("userId") Long userId)
{
return success(appUserService.getAppUserResume(userId));
}
/**
* 后台保存用户完整简历(个人信息+教育+培训+工作经历+技能)
* 复用 editRegisterUser 的保存逻辑
*/
@ApiOperation("后台保存用户完整简历")
@PostMapping("/saveResume")
@BussinessLog(title = "后台保存简历")
public AjaxResult saveResume(@RequestBody RegisterBody registerBody)
{
if (registerBody == null) {
return AjaxResult.error("入参不能为空!");
}
if (registerBody.getAppUser() == null || registerBody.getAppUser().getUserId() == null) {
return AjaxResult.error("用户信息或用户ID为空!");
}
return AjaxResult.success(appUserService.editRegisterUser(registerBody));
}
/**
* 新增APP用户
*/
@@ -361,4 +400,47 @@ public class CmsAppUserController extends BaseController
: "简历已设置成保密!";
return success(msg);
}
@ApiOperation("修改用户身份证号")
@Log(title = "APP用户", businessType = BusinessType.UPDATE)
@PostMapping("/updateIdCard")
public AjaxResult updateIdCard(@RequestBody AppUser appUser)
{
if (appUser.getUserId() == null) {
return AjaxResult.error("用户ID为空");
}
if (StringUtils.isBlank(appUser.getIdCard())) {
return AjaxResult.error("身份证号为空");
}
try {
appUserService.updateIdCard(appUser.getUserId(), appUser.getIdCard());
return success("身份证号修改成功");
} catch (RuntimeException e) {
return AjaxResult.error(e.getMessage());
}
}
@ApiOperation("重置用户密码(同步浪潮)")
@Log(title = "APP用户", businessType = BusinessType.UPDATE)
@PostMapping("/resetPassword")
public AjaxResult resetPassword(@RequestBody AppUser appUser)
{
if (appUser.getUserId() == null) {
return AjaxResult.error("用户ID为空");
}
if (StringUtils.isBlank(appUser.getPassword())) {
return AjaxResult.error("密码为空");
}
if (SecurityUtils.getlcUserid() == null) {
return error("门户会话失效,请重新在门户登录");
}
try {
// 使用当前管理员的浪潮身份调用浪潮重置密码接口
appUser.setLcUserid(SecurityUtils.getlcUserid());
appUserService.resetPassword(appUser);
return success("密码重置成功");
} catch (RuntimeException e) {
return AjaxResult.error(e.getMessage());
}
}
}

View File

@@ -0,0 +1,93 @@
package com.ruoyi.cms.controller.cms;
import java.util.List;
import com.ruoyi.cms.domain.ResumeAccessLog;
import com.ruoyi.cms.mapper.ResumeAccessLogMapper;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.Company;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.cms.service.ICompanyService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
/**
* 简历访问/下载记录
*/
@RestController
@RequestMapping("/cms/resumeAccessLog")
@Api(tags = "后台:简历访问记录")
public class ResumeAccessLogController extends BaseController
{
@Autowired
private ResumeAccessLogMapper resumeAccessLogMapper;
@Autowired
private ICompanyService companyService;
@ApiOperation("简历访问记录列表")
@PreAuthorize("@ss.hasPermi('cms:appUser:query')")
@GetMapping("/list")
public TableDataInfo list(ResumeAccessLog resumeAccessLog)
{
startPage();
QueryWrapper<ResumeAccessLog> qw = new QueryWrapper<>();
if (resumeAccessLog.getUserId() != null) {
qw.eq("user_id", resumeAccessLog.getUserId());
}
if (resumeAccessLog.getAccessBy() != null && !resumeAccessLog.getAccessBy().isEmpty()) {
qw.like("access_by", resumeAccessLog.getAccessBy());
}
qw.orderByDesc("access_time");
List<ResumeAccessLog> list = resumeAccessLogMapper.selectList(qw);
return getDataTable(list);
}
/**
* 记录简历下载操作(由前端 iframe 点击检测触发)
*/
@ApiOperation("记录简历下载")
@PostMapping("/download")
public AjaxResult logDownload(@RequestBody Map<String, Object> body, HttpServletRequest request)
{
try {
ResumeAccessLog log = new ResumeAccessLog();
Object userIdObj = body.get("userId");
if (userIdObj != null) {
log.setUserId(Long.valueOf(userIdObj.toString()));
}
log.setAccessBy(SecurityUtils.getUsername());
log.setAccessTime(DateUtils.getTime());
log.setIpAddress(IpUtils.getIpAddr(request));
log.setSourcePage("下载操作");
if (SecurityUtils.isLogin()) {
try {
Company company = companyService.queryCodeCompany(SecurityUtils.getIdcard());
if (company != null) {
log.setCompanyId(company.getCompanyId());
}
} catch (Exception e) {
// ignore
}
}
resumeAccessLogMapper.insert(log);
return success();
} catch (Exception e) {
return error("记录下载失败: " + e.getMessage());
}
}
}

View File

@@ -57,6 +57,11 @@ public class InterviewInvitation extends BaseEntity {
*/
@ApiModelProperty("面试方式")
private String interviewMethod;
/**
* 会议类型(shz_video/tencent_meeting),在线面试时使用
*/
@ApiModelProperty("会议类型")
private String meetingType;
/**
* 腾讯会议链接
*/

View File

@@ -0,0 +1,48 @@
package com.ruoyi.cms.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 简历访问/下载记录 resume_access_log
*
* @date 2026-07-19
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel("简历访问/下载记录")
@TableName(value = "resume_access_log")
public class ResumeAccessLog extends BaseEntity
{
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
@ApiModelProperty("主键ID")
private Long id;
@ApiModelProperty("被查看的求职者ID")
private Long userId;
@ApiModelProperty("招聘企业ID")
private Long companyId;
@ApiModelProperty("操作人")
private String accessBy;
@ApiModelProperty("访问时间")
private String accessTime;
@ApiModelProperty("来源页面")
private String sourcePage;
@ApiModelProperty("IP地址")
private String ipAddress;
}

View File

@@ -39,5 +39,7 @@ public interface AppUserMapper extends BaseMapper<AppUser>
int updateSysUserIdCard(@Param("userId") Long userId, @Param("idCard") String idCard);
int updateSysUserPassword(@Param("userId") Long userId, @Param("password") String password);
List<AppUserShow> selectUserApplyList(AppUser appUser);
}

View File

@@ -0,0 +1,11 @@
package com.ruoyi.cms.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.cms.domain.ResumeAccessLog;
/**
* 简历访问/下载记录 Mapper
*/
public interface ResumeAccessLogMapper extends BaseMapper<ResumeAccessLog>
{
}

View File

@@ -76,6 +76,13 @@ public interface IAppUserService
public AppUser getUserInfo();
/**
* 获取用户完整简历信息(个人信息+教育+培训+工作经历+技能)
* @param userId 用户ID
* @return 完整的用户简历数据
*/
public AppUser getAppUserResume(Long userId);
public int editRegisterUser(RegisterBody registerBody);
public List<AppUser> selectNoTmAppUserList(AppUser appUser);
@@ -89,4 +96,17 @@ public interface IAppUserService
void resetLcPsw(AppUser appUser);
void changeLcCompanyStatus(AppUser appUser);
/**
* 修改用户身份证号(同步更新 app_user 和 sys_user
* @param userId 用户ID
* @param idCard 新身份证号
*/
void updateIdCard(Long userId, String idCard);
/**
* 重置用户密码(先调浪潮接口,再同步更新本地 sys_user 表)
* @param appUser 包含 userId、password、lcUserid管理员浪潮身份
*/
void resetPassword(AppUser appUser);
}

View File

@@ -550,6 +550,33 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
return lky;
}
@Override
public AppUser getAppUserResume(Long userId) {
AppUser appUser = selectAppUserByUserId(userId);
if (appUser != null && appUser.getUserId() != null) {
// 查询工作经历
UserWorkExperiences workParam = new UserWorkExperiences();
workParam.setUserId(appUser.getUserId());
List<UserWorkExperiences> experiencesList = userWorkExperiencesMapper.getWorkExperiencesList(workParam);
appUser.setExperiencesList(experiencesList);
// 查询技能
AppSkill skillParam = new AppSkill();
skillParam.setUserId(appUser.getUserId());
List<AppSkill> skillList = appSkillMapper.getList(skillParam);
appUser.setAppSkillsList(skillList);
// 查询教育经历
List<AppUserEducation> educationsList = appUserEducationMapper.selectEducationListByUserId(appUser.getUserId());
appUser.setEducationsList(educationsList);
// 查询培训经历
List<AppUserTrain> trainsList = appUserTrainMapper.selectTrainListByUserId(appUser.getUserId());
appUser.setTrainsList(trainsList);
}
return appUser;
}
@Override
public AppUser getUserInfo() {
//查询用户信息
@@ -760,4 +787,91 @@ public class AppUserServiceImpl extends ServiceImpl<AppUserMapper,AppUser> imple
System.out.println(data);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateIdCard(Long userId, String idCard) {
// 1. 校验身份证格式
if (StringUtils.isBlank(idCard)) {
throw new RuntimeException("身份证号不能为空");
}
if (!idCard.matches(StringUtil.SFZ_VALID_REGEX)) {
throw new RuntimeException("身份证格式不正确");
}
// 2. 检查用户是否存在
AppUser appUser = appUserMapper.selectById(userId);
if (appUser == null) {
throw new RuntimeException("用户不存在");
}
// 3. 检查身份证唯一性
AppUser existing = appUserMapper.selectOne(
Wrappers.<AppUser>lambdaQuery()
.eq(AppUser::getIdCard, idCard)
.eq(AppUser::getDelFlag, "0")
.ne(AppUser::getUserId, userId)
.last("LIMIT 1"));
if (existing != null) {
throw new RuntimeException("该身份证号已被其他用户使用");
}
// 4. 更新 app_user 表
AppUser updateEntity = new AppUser();
updateEntity.setUserId(userId);
updateEntity.setIdCard(idCard);
appUserMapper.updateById(updateEntity);
// 5. 同步更新 sys_user 表
appUserMapper.updateSysUserIdCard(userId, idCard);
}
@Override
public void resetPassword(AppUser appUser) {
String password = appUser.getPassword();
// 1. 校验密码强度
validatePasswordStrength(password);
// 2. 查询目标用户信息
AppUser targetUser = appUserMapper.selectById(appUser.getUserId());
if (targetUser == null) {
throw new RuntimeException("用户不存在");
}
// 3. 获取管理员浪潮 token调用浪潮重置密码接口
String lcUserToken = redisCache.getCacheObject(CacheConstants.LC_JGD_TOKEN + appUser.getLcUserid());
if (StringUtils.isBlank(lcUserToken)) {
throw new RuntimeException("token已失效");
}
JSONObject pJson = new JSONObject();
pJson.put("userId", appUser.getUserId());
pJson.put("password", password);
String result = httpClientUtils.sendHttpPost(JG_RESET_PASSWORD_URL, lcUserToken, pJson.toJSONString());
JsonUtil.parsePortalResponse(result, "调用重置密码接口");
// 4. 同步更新本地 sys_user 表密码
SysUser sysUser = appUserMapper.selectSysUserIdcard(targetUser.getIdCard());
if (sysUser != null) {
String encryptedPassword = SecurityUtils.encryptPassword(password);
appUserMapper.updateSysUserPassword(sysUser.getUserId(), encryptedPassword);
}
}
/**
* 密码强度校验需包含大小写字母、数字和至少2个特殊字符长度8-20位
*/
private void validatePasswordStrength(String password) {
if (StringUtils.isBlank(password)) {
throw new RuntimeException("密码不能为空");
}
if (password.length() < 8 || password.length() > 20) {
throw new RuntimeException("密码长度需在8-20位之间");
}
if (!password.matches(".*[A-Z].*")) {
throw new RuntimeException("密码需包含大写字母");
}
if (!password.matches(".*[a-z].*")) {
throw new RuntimeException("密码需包含小写字母");
}
if (!password.matches(".*\\d.*")) {
throw new RuntimeException("密码需包含数字");
}
// 至少2个特殊字符非字母数字的字符
String specialChars = password.replaceAll("[a-zA-Z0-9]", "");
if (specialChars.length() < 2) {
throw new RuntimeException("密码需包含至少2个特殊字符");
}
}
}

View File

@@ -295,7 +295,10 @@ public class InterviewInvitationServiceImpl implements InterviewInvitationServic
content.append("面试时间:").append(invitation.getInterviewTime() != null ? invitation.getInterviewTime() : "待定").append("");
content.append("面试方式:").append(methodText);
if ("online".equals(invitation.getInterviewMethod())) {
content.append(",腾讯会议链接:").append(invitation.getMeetingLink() != null ? invitation.getMeetingLink() : "");
String meetingLabel = "shz_video".equals(invitation.getMeetingType()) ? "石河子视频会议链接"
: "tencent_meeting".equals(invitation.getMeetingType()) ? "腾讯会议链接"
: "会议链接";
content.append("").append(meetingLabel).append("").append(invitation.getMeetingLink() != null ? invitation.getMeetingLink() : "");
content.append(",会议密码:").append(invitation.getMeetingPassword() != null ? invitation.getMeetingPassword() : "");
} else {
content.append(",面试地点:").append(invitation.getInterviewLocation() != null ? invitation.getInterviewLocation() : "");

View File

@@ -34,10 +34,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="lcUserid" column="lc_userid" />
<result property="email" column="email" />
<result property="resumeStatus" column="resume_status" />
<result property="nation" column="nation" />
<result property="openid" column="openid" />
<result property="unionid" column="unionid" />
<result property="address" column="address" />
<result property="domicileAddress" column="domicile_address" />
<result property="userType" column="user_type" />
<result property="experience" column="experience" />
<result property="ytjPassword" column="ytj_password" />
</resultMap>
<sql id="selectAppUserVo">
select user_id, name, age, sex, birth_date, education, political_affiliation, phone, avatar, salary_min, salary_max, area, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark,job_title_id,is_recommend,id_card,work_experience,is_company_user,lc_userid,email,resume_status from app_user
select user_id, name, age, sex, birth_date, education, political_affiliation, phone, avatar, salary_min, salary_max, area, status, del_flag, login_ip, login_date, create_by, create_time, update_by, update_time, remark,job_title_id,is_recommend,id_card,work_experience,is_company_user,lc_userid,email,resume_status,nation,openid,unionid,address,domicile_address,user_type,experience,ytj_password from app_user
</sql>
<select id="selectAppUserList" parameterType="AppUser" resultMap="AppUserResult">
@@ -53,6 +61,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="avatar != null and avatar != ''"> and avatar = #{avatar}</if>
<if test="salaryMin != null and salaryMin != ''"> and salary_min = #{salaryMin}</if>
<if test="salaryMax != null and salaryMax != ''"> and salary_max = #{salaryMax}</if>
<if test="nation != null and nation != ''"> and nation = #{nation}</if>
<if test="area != null and area != ''"> and area = #{area}</if>
<if test="status != null and status != ''"> and status = #{status}</if>
<if test="loginIp != null and loginIp != ''"> and login_ip = #{loginIp}</if>
@@ -136,6 +145,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where user_id = #{userId}
</update>
<update id="updateSysUserPassword">
update sys_user
set password = #{password},
update_time = sysdate()
where user_id = #{userId}
</update>
<select id="selectUserApplyList" parameterType="AppUser" resultType="com.ruoyi.common.core.domain.entity.AppUserShow">
select a.user_id , a.name , a.age , a.sex , get_birth_date_from_id_card ( a.id_card ) as birth_date ,
a.education , a.political_affiliation , a.phone , a.avatar , c.min_salary salaryMin ,c.max_salary salaryMax,a.area ,a.id_card ,

View File

@@ -90,13 +90,13 @@
and htc.source_type = #{sourceType}
</if>
<if test="collectTags != null and collectTags != ''">
and htc.collect_tags like concat('%',#{collectTags},'%')
and htc.collect_tags like '%' || #{collectTags} || '%'
</if>
<if test="name != null and name != ''">
and au.name like concat('%',#{name},'%')
and au.name like '%' || #{name} || '%'
</if>
<if test="phone != null and phone != ''">
and au.phone like concat('%',#{phone},'%')
and au.phone like '%' || #{phone} || '%'
</if>
<if test="education != null and education != ''">
and au.education = #{education}