Commit 613f3bfd by zwb

招聘会演示平台搭建与简历导入

parent 71cbf619
Showing with 5877 additions and 0 deletions
package com.bkty.system.api;
import com.bkty.system.api.domain.bo.RemoteUserBo;
import com.bkty.system.api.domain.vo.RemoteUserVo;
import com.bkty.system.api.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import java.util.List;
/**
* 用户服务
*
* @author Lion Li
*/
public interface RemoteUserService {
/**
* 通过用户名查询用户信息
*
* @param username 用户名
* @return 结果
*/
LoginUser getUserInfo(String username) throws UserException;
/**
* 通过用户id查询用户信息
*
* @param userId 用户id
* @return 结果
*/
LoginUser getUserInfo(Long userId) throws UserException;
/**
* 通过手机号查询用户信息
*
* @param phonenumber 手机号
* @param tenantId 租户id
* @return 结果
*/
LoginUser getUserInfoByPhonenumber(String phonenumber, String tenantId) throws UserException;
/**
* 通过邮箱查询用户信息
*
* @param email 邮箱
* @param tenantId 租户id
* @return 结果
*/
LoginUser getUserInfoByEmail(String email, String tenantId) throws UserException;
/**
* 注册用户信息
*
* @param remoteUserBo 用户信息
* @return 结果
*/
Boolean registerUserInfo(RemoteUserBo remoteUserBo) throws UserException, ServiceException;
/**
* 通过userId查询用户账户
*
* @param userId 用户id
* @return 结果
*/
String selectUserNameById(Long userId);
/**
* 通过用户ID查询用户昵称
*
* @param userId 用户id
* @return 结果
*/
String selectNicknameById(Long userId);
/**
* 通过用户ID查询用户账户
*
* @param userIds 用户ID 多个用逗号隔开
* @return 用户名称
*/
String selectNicknameByIds(String userIds);
/**
* 通过用户ID查询用户手机号
*
* @param userId 用户id
* @return 用户手机号
*/
String selectPhonenumberById(Long userId);
/**
* 通过用户ID查询用户邮箱
*
* @param userId 用户id
* @return 用户邮箱
*/
String selectEmailById(Long userId);
/**
* 更新用户信息
*
* @param userId 用户ID
* @param ip IP地址
*/
void recordLoginInfo(Long userId, String ip);
/**
* 通过用户ID查询用户列表
*
* @param userIds 用户ids
* @return 用户列表
*/
List<RemoteUserVo> selectListByIds(List<Long> userIds);
/**
* 通过角色ID查询用户ID
*
* @param roleIds 角色ids
* @return 用户ids
*/
List<Long> selectUserIdsByRoleIds(List<Long> roleIds);
}
package com.bkty.system.api.domain.bo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 系统访问记录表 sys_logininfor
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class RemoteLogininforBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 访问ID
*/
private Long infoId;
/**
* 租户编号
*/
private String tenantId;
/**
* 用户账号
*/
private String userName;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录状态(0成功 1失败)
*/
private String status;
/**
* 提示消息
*/
private String msg;
/**
* 访问时间
*/
private Date loginTime;
/**
* 请求参数
*/
private Map<String, Object> params = new HashMap<>();
}
package com.bkty.system.api.domain.bo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 操作日志记录表 oper_log
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class RemoteOperLogBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
private Long operId;
/**
* 租户编号
*/
private String tenantId;
/**
* 模块标题
*/
private String title;
/**
* 业务类型(0其它 1新增 2修改 3删除)
*/
private Integer businessType;
/**
* 方法名称
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作类别(0其它 1后台用户 2手机端用户)
*/
private Integer operatorType;
/**
* 操作人员
*/
private String operName;
/**
* 部门名称
*/
private String deptName;
/**
* 请求URL
*/
private String operUrl;
/**
* 主机地址
*/
private String operIp;
/**
* 操作地点
*/
private String operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 操作状态(0正常 1异常)
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private Date operTime;
/**
* 消耗时间
*/
private Long costTime;
/**
* 请求参数
*/
private Map<String, Object> params = new HashMap<>();
}
package com.bkty.system.api.domain.bo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 社会化关系业务对象 sys_social
*
* @author Michelle.Chung
*/
@Data
@NoArgsConstructor
public class RemoteSocialBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
private Long id;
/**
* 的唯一ID
*/
private String authId;
/**
* 用户来源
*/
private String source;
/**
* 用户的授权令牌
*/
private String accessToken;
/**
* 用户的授权令牌的有效期,部分平台可能没有
*/
private int expireIn;
/**
* 刷新令牌,部分平台可能没有
*/
private String refreshToken;
/**
* 平台唯一id
*/
private String openId;
/**
* 用户的 ID
*/
private Long userId;
/**
* 平台的授权信息,部分平台可能没有
*/
private String accessCode;
/**
* 用户的 unionid
*/
private String unionId;
/**
* 授予的权限,部分平台可能没有
*/
private String scope;
/**
* 授权的第三方账号
*/
private String userName;
/**
* 授权的第三方昵称
*/
private String nickName;
/**
* 授权的第三方邮箱
*/
private String email;
/**
* 授权的第三方头像地址
*/
private String avatar;
/**
* 个别平台的授权信息,部分平台可能没有
*/
private String tokenType;
/**
* id token,部分平台可能没有
*/
private String idToken;
/**
* 小米平台用户的附带属性,部分平台可能没有
*/
private String macAlgorithm;
/**
* 小米平台用户的附带属性,部分平台可能没有
*/
private String macKey;
/**
* 用户的授权code,部分平台可能没有
*/
private String code;
/**
* Twitter平台用户的附带属性,部分平台可能没有
*/
private String oauthToken;
/**
* Twitter平台用户的附带属性,部分平台可能没有
*/
private String oauthTokenSecret;
}
package com.bkty.system.api.domain.bo;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.xss.Xss;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 用户信息业务对象 sys_user
*
* @author Michelle.Chung
*/
@Data
@NoArgsConstructor
public class RemoteUserBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 租户ID
*/
private String tenantId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户账号
*/
@Xss(message = "用户账号不能包含脚本字符")
@NotBlank(message = "用户账号不能为空")
@Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符")
private String userName;
/**
* 用户昵称
*/
@Xss(message = "用户昵称不能包含脚本字符")
@Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符")
private String nickName;
/**
* 用户类型(sys_user系统用户)
*/
private String userType;
/**
* 用户邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(min = 0, max = 50, message = "邮箱长度不能超过{max}个字符")
private String email;
/**
* 手机号码
*/
private String phonenumber;
/**
* 用户性别(0男 1女 2未知)
*/
private String sex;
/**
* 头像地址
*/
private Long avatar;
/**
* 密码
*/
private String password;
/**
* 帐号状态(0正常 1停用)
*/
private String status;
/**
* 最后登录IP
*/
private String loginIp;
/**
* 最后登录时间
*/
private Date loginDate;
/**
* 备注
*/
private String remark;
/**
* 数据权限 当前角色ID
*/
private Long roleId;
public RemoteUserBo(Long userId) {
this.userId = userId;
}
public boolean isSuperAdmin() {
return UserConstants.SUPER_ADMIN_ID.equals(this.userId);
}
}
package com.bkty.system.api.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 租户视图对象
*
* @author zhujie
*/
@Data
public class RemoteTenantVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* id
*/
private Long id;
/**
* 租户编号
*/
private String tenantId;
/**
* 联系人
*/
private String contactUserName;
/**
* 联系电话
*/
private String contactPhone;
/**
* 企业名称
*/
private String companyName;
/**
* 统一社会信用代码
*/
private String licenseNumber;
/**
* 地址
*/
private String address;
/**
* 域名
*/
private String domain;
/**
* 企业简介
*/
private String intro;
/**
* 备注
*/
private String remark;
/**
* 租户套餐编号
*/
private Long packageId;
/**
* 过期时间
*/
private Date expireTime;
/**
* 用户数量(-1不限制)
*/
private Long accountCount;
/**
* 租户状态(0正常 1停用)
*/
private String status;
}
package com.bkty.system.api.domain.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 用户
*
* @author Michelle.Chung
*/
@Data
@NoArgsConstructor
public class RemoteUserVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户类型(sys_user系统用户)
*/
private String userType;
/**
* 用户邮箱
*/
private String email;
/**
* 手机号码
*/
private String phonenumber;
/**
* 用户性别(0男 1女 2未知)
*/
private String sex;
/**
* 帐号状态(0正常 1停用)
*/
private String status;
/**
* 创建时间
*/
private Date createTime;
}
package com.bkty.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.domain.model.LoginBody;
import org.hibernate.validator.constraints.Length;
import static org.dromara.common.core.constant.UserConstants.*;
/**
* 用户注册对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RegisterBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
private String password;
/**
* 用户类型
*/
private String userType;
}
package org.dromara.common.core.annotation;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 自定义注解防止表单重复提交
*
* @author jiangxiaoge
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 间隔时间(ms),小于此时间视为重复提交
*/
int interval() default 5000;
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 提示消息 支持国际化 格式为 {code}
*/
String message() default "请误重复提交";
}
package org.dromara.common.core.aspectj;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.alibaba.fastjson2.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.core.annotation.RepeatSubmit;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.WarnException;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringContextHolder;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.dromara.common.core.constant.CacheConstants;
import java.time.Duration;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
/**
* 防止重复提交(参考美团GTIS防重系统)
*
* @author Lion Li
*/
@Aspect
public class RepeatSubmitAspect {
private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();
private static final RedisTemplate<String, String> CLIENT = SpringContextHolder.getBean(RedisTemplate.class);
@Before("@annotation(repeatSubmit)")
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
// 如果注解不为0 则使用注解数值
long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
if (interval < 1000) {
throw new WarnException("重复提交间隔时间不能小于'1'秒");
}
HttpServletRequest request = ServletUtils.getRequest();
String nowParams = argsArrayToString(point.getArgs());
// 请求地址(作为存放cache的key值)
String url = request.getRequestURI();
// 唯一值(没有消息头则使用请求地址)
String submitKey = Objects.toString(request.getHeader("Authorization"), "");
submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
// 唯一标识(指定key + url + 消息头)
String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
if (setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) {
KEY_CACHE.set(cacheRepeatKey);
} else {
throw new WarnException(repeatSubmit.message());
}
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
if (jsonResult instanceof R<?> r) {
try {
// 成功则不删除redis数据 保证在有效时间内无法重复提交
if (r.getCode() == R.ok().getCode()) {
return;
}
CLIENT.delete(KEY_CACHE.get());
} finally {
KEY_CACHE.remove();
}
}
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
CLIENT.delete(KEY_CACHE.get());
KEY_CACHE.remove();
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
StringJoiner params = new StringJoiner(" ");
if (ArrayUtil.isEmpty(paramsArray)) {
return params.toString();
}
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
params.add(JSON.toJSONString(o));
}
}
return params.toString();
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.values()) {
return value instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
/**
* 如果不存在则设置 并返回 true 如果存在则返回 false
*
* @param key 缓存的键值
* @param value 缓存的值
* @param duration 缓存的过期时间
* @return set成功或失败
*/
public boolean setObjectIfAbsent(final String key, final String value, final Duration duration) {
// 获取 ValueOperations
ValueOperations<String, String> valueOperations = CLIENT.opsForValue();
// 使用 setIfAbsent 来设置值,如果键不存在
Boolean isSet = CLIENT.opsForValue().setIfAbsent(key, value);
// 如果 setIfAbsent 返回 true,表示设置成功
if (Boolean.TRUE.equals(isSet)) {
// 设置过期时间
CLIENT.expire(key, duration);
return true;
}
return false; // 如果键已存在,则返回 false
}
}
package org.dromara.common.core.constant;
import cn.hutool.core.lang.RegexPool;
/**
* 常用正则表达式字符串
* <p>
* 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/
*
* @author Feng
*/
public interface RegexConstants extends RegexPool {
/**
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
*/
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
/**
* 权限标识必须符合 tool:build:list 格式,或者空字符串
*/
String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$";
/**
* 身份证号码(后6位)
*/
String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
/**
* QQ号码
*/
String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
/**
* 邮政编码
*/
String POSTAL_CODE = "^[1-9]\\d{5}$";
/**
* 注册账号
*/
String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
/**
* 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
*/
String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
/**
* 通用状态(0表示正常,1表示停用)
*/
String STATUS = "^[01]$";
}
package org.dromara.common.core.constant;
/**
* @author jiangxiaoge
* @description
* @data 2025/2/12
**/
public class RegionConstants {
public static final String REGION_JSON_ARRAY= """
[
{
"label": "北京",
"value": "11",
},
{
"label": "天津",
"value": "12",
},
{
"label": "河北",
"value": "13",
},
{
"label": "山西",
"value": "14",
},
{
"label": "内蒙古",
"value": "15",
},
{
"label": "辽宁",
"value": "21",
},
{
"label": "吉林",
"value": "22",
},
{
"label": "黑龙江",
"value": "23",
},
{
"label": "上海",
"value": "31",
},
{
"label": "江苏",
"value": "32",
},
{
"label": "浙江",
"value": "33",
},
{
"label": "安徽",
"value": "34",
},
{
"label": "福建",
"value": "35",
},
{
"label": "江西",
"value": "36",
},
{
"label": "山东",
"value": "37",
},
{
"label": "河南",
"value": "41",
},
{
"label": "湖北",
"value": "42",
},
{
"label": "湖南",
"value": "43",
},
{
"label": "广东",
"value": "44",
},
{
"label": "广西",
"value": "45",
},
{
"label": "海南",
"value": "46",
},
{
"label": "重庆",
"value": "50",
},
{
"label": "四川",
"value": "51",
},
{
"label": "贵州",
"value": "52",
},
{
"label": "云南",
"value": "53",
},
{
"label": "西藏",
"value": "54",
},
{
"label": "陕西",
"value": "61",
},
{
"label": "甘肃",
"value": "62",
},
{
"label": "青海",
"value": "63",
},
{
"label": "宁夏",
"value": "64",
},
{
"label": "新疆",
"value": "65",
},
{
"label": "台湾",
"value": "66",
},
{
"label": "香港",
"value": "67",
},
{
"label": "澳门",
"value": "68",
},
{
"label": "北京",
"value": "1101",
},
{
"label": "天津",
"value": "1201",
},
{
"label": "石家庄",
"value": "1301",
},
{
"label": "唐山",
"value": "1302",
},
{
"label": "秦皇岛",
"value": "1303",
},
{
"label": "邯郸",
"value": "1304",
},
{
"label": "邢台",
"value": "1305",
},
{
"label": "保定",
"value": "1306",
},
{
"label": "张家口",
"value": "1307",
},
{
"label": "承德",
"value": "1308",
},
{
"label": "沧州",
"value": "1309",
},
{
"label": "廊坊",
"value": "1310",
},
{
"label": "衡水",
"value": "1311",
},
{
"label": "太原",
"value": "1401",
},
{
"label": "大同",
"value": "1402",
},
{
"label": "阳泉",
"value": "1403",
},
{
"label": "长治",
"value": "1404",
},
{
"label": "晋城",
"value": "1405",
},
{
"label": "朔州",
"value": "1406",
},
{
"label": "晋中",
"value": "1407",
},
{
"label": "运城",
"value": "1408",
},
{
"label": "忻州",
"value": "1409",
},
{
"label": "临汾",
"value": "1410",
},
{
"label": "吕梁",
"value": "1411",
},
{
"label": "呼和浩特",
"value": "1501",
},
{
"label": "包头",
"value": "1502",
},
{
"label": "乌海",
"value": "1503",
},
{
"label": "赤峰",
"value": "1504",
},
{
"label": "通辽",
"value": "1505",
},
{
"label": "鄂尔多斯",
"value": "1506",
},
{
"label": "呼伦贝尔",
"value": "1507",
},
{
"label": "巴彦淖尔",
"value": "1508",
},
{
"label": "乌兰察布",
"value": "1509",
},
{
"label": "兴安盟",
"value": "1522",
},
{
"label": "锡林郭勒盟",
"value": "1525",
},
{
"label": "阿拉善盟",
"value": "1529",
},
{
"label": "沈阳",
"value": "2101",
},
{
"label": "大连",
"value": "2102",
},
{
"label": "鞍山",
"value": "2103",
},
{
"label": "抚顺",
"value": "2104",
},
{
"label": "本溪",
"value": "2105",
},
{
"label": "丹东",
"value": "2106",
},
{
"label": "锦州",
"value": "2107",
},
{
"label": "营口",
"value": "2108",
},
{
"label": "阜新",
"value": "2109",
},
{
"label": "辽阳",
"value": "2110",
},
{
"label": "盘锦",
"value": "2111",
},
{
"label": "铁岭",
"value": "2112",
},
{
"label": "朝阳",
"value": "2113",
},
{
"label": "葫芦岛",
"value": "2114",
},
{
"label": "长春",
"value": "2201",
},
{
"label": "吉林",
"value": "2202",
},
{
"label": "四平",
"value": "2203",
},
{
"label": "辽源",
"value": "2204",
},
{
"label": "通化",
"value": "2205",
},
{
"label": "白山",
"value": "2206",
},
{
"label": "松原",
"value": "2207",
},
{
"label": "白城",
"value": "2208",
},
{
"label": "延边朝鲜族自治州",
"value": "2224",
},
{
"label": "哈尔滨",
"value": "2301",
},
{
"label": "齐齐哈尔",
"value": "2302",
},
{
"label": "鸡西",
"value": "2303",
},
{
"label": "鹤岗",
"value": "2304",
},
{
"label": "双鸭山",
"value": "2305",
},
{
"label": "大庆",
"value": "2306",
},
{
"label": "伊春",
"value": "2307",
},
{
"label": "佳木斯",
"value": "2308",
},
{
"label": "七台河",
"value": "2309",
},
{
"label": "牡丹江",
"value": "2310",
},
{
"label": "黑河",
"value": "2311",
},
{
"label": "绥化",
"value": "2312",
},
{
"label": "大兴安岭地区",
"value": "2327",
},
{
"label": "上海",
"value": "3101",
},
{
"label": "南京",
"value": "3201",
},
{
"label": "无锡",
"value": "3202",
},
{
"label": "徐州",
"value": "3203",
},
{
"label": "常州",
"value": "3204",
},
{
"label": "苏州",
"value": "3205",
},
{
"label": "南通",
"value": "3206",
},
{
"label": "连云港",
"value": "3207",
},
{
"label": "淮安",
"value": "3208",
},
{
"label": "盐城",
"value": "3209",
},
{
"label": "扬州",
"value": "3210",
},
{
"label": "镇江",
"value": "3211",
},
{
"label": "泰州",
"value": "3212",
},
{
"label": "宿迁",
"value": "3213",
},
{
"label": "杭州",
"value": "3301",
},
{
"label": "宁波",
"value": "3302",
},
{
"label": "温州",
"value": "3303",
},
{
"label": "嘉兴",
"value": "3304",
},
{
"label": "湖州",
"value": "3305",
},
{
"label": "绍兴",
"value": "3306",
},
{
"label": "金华",
"value": "3307",
},
{
"label": "衢州",
"value": "3308",
},
{
"label": "舟山",
"value": "3309",
},
{
"label": "台州",
"value": "3310",
},
{
"label": "丽水",
"value": "3311",
},
{
"label": "合肥",
"value": "3401",
},
{
"label": "芜湖",
"value": "3402",
},
{
"label": "蚌埠",
"value": "3403",
},
{
"label": "淮南",
"value": "3404",
},
{
"label": "马鞍山",
"value": "3405",
},
{
"label": "淮北",
"value": "3406",
},
{
"label": "铜陵",
"value": "3407",
},
{
"label": "安庆",
"value": "3408",
},
{
"label": "黄山",
"value": "3410",
},
{
"label": "滁州",
"value": "3411",
},
{
"label": "阜阳",
"value": "3412",
},
{
"label": "宿州",
"value": "3413",
},
{
"label": "六安",
"value": "3415",
},
{
"label": "亳州",
"value": "3416",
},
{
"label": "池州",
"value": "3417",
},
{
"label": "宣城",
"value": "3418",
},
{
"label": "福州",
"value": "3501",
},
{
"label": "厦门",
"value": "3502",
},
{
"label": "莆田",
"value": "3503",
},
{
"label": "三明",
"value": "3504",
},
{
"label": "泉州",
"value": "3505",
},
{
"label": "漳州",
"value": "3506",
},
{
"label": "南平",
"value": "3507",
},
{
"label": "龙岩",
"value": "3508",
},
{
"label": "宁德",
"value": "3509",
},
{
"label": "南昌",
"value": "3601",
},
{
"label": "景德镇",
"value": "3602",
},
{
"label": "萍乡",
"value": "3603",
},
{
"label": "九江",
"value": "3604",
},
{
"label": "新余",
"value": "3605",
},
{
"label": "鹰潭",
"value": "3606",
},
{
"label": "赣州",
"value": "3607",
},
{
"label": "吉安",
"value": "3608",
},
{
"label": "宜春",
"value": "3609",
},
{
"label": "抚州",
"value": "3610",
},
{
"label": "上饶",
"value": "3611",
},
{
"label": "济南",
"value": "3701",
},
{
"label": "青岛",
"value": "3702",
},
{
"label": "淄博",
"value": "3703",
},
{
"label": "枣庄",
"value": "3704",
},
{
"label": "东营",
"value": "3705",
},
{
"label": "烟台",
"value": "3706",
},
{
"label": "潍坊",
"value": "3707",
},
{
"label": "济宁",
"value": "3708",
},
{
"label": "泰安",
"value": "3709",
},
{
"label": "威海",
"value": "3710",
},
{
"label": "日照",
"value": "3711",
},
{
"label": "莱芜",
"value": "3712",
},
{
"label": "临沂",
"value": "3713",
},
{
"label": "德州",
"value": "3714",
},
{
"label": "聊城",
"value": "3715",
},
{
"label": "滨州",
"value": "3716",
},
{
"label": "菏泽",
"value": "3717",
},
{
"label": "郑州",
"value": "4101",
},
{
"label": "开封",
"value": "4102",
},
{
"label": "洛阳",
"value": "4103",
},
{
"label": "平顶山",
"value": "4104",
},
{
"label": "安阳",
"value": "4105",
},
{
"label": "鹤壁",
"value": "4106",
},
{
"label": "新乡",
"value": "4107",
},
{
"label": "焦作",
"value": "4108",
},
{
"label": "濮阳",
"value": "4109",
},
{
"label": "许昌",
"value": "4110",
},
{
"label": "漯河",
"value": "4111",
},
{
"label": "三门峡",
"value": "4112",
},
{
"label": "南阳",
"value": "4113",
},
{
"label": "商丘",
"value": "4114",
},
{
"label": "信阳",
"value": "4115",
},
{
"label": "周口",
"value": "4116",
},
{
"label": "驻马店",
"value": "4117",
},
{
"label": "武汉",
"value": "4201",
},
{
"label": "黄石",
"value": "4202",
},
{
"label": "十堰",
"value": "4203",
},
{
"label": "宜昌",
"value": "4205",
},
{
"label": "襄阳",
"value": "4206",
},
{
"label": "鄂州",
"value": "4207",
},
{
"label": "荆门",
"value": "4208",
},
{
"label": "孝感",
"value": "4209",
},
{
"label": "荆州",
"value": "4210",
},
{
"label": "黄冈",
"value": "4211",
},
{
"label": "咸宁",
"value": "4212",
},
{
"label": "随州",
"value": "4213",
},
{
"label": "恩施土家族苗族自治州",
"value": "4228",
},
{
"label": "长沙",
"value": "4301",
},
{
"label": "株洲",
"value": "4302",
},
{
"label": "湘潭",
"value": "4303",
},
{
"label": "衡阳",
"value": "4304",
},
{
"label": "邵阳",
"value": "4305",
},
{
"label": "岳阳",
"value": "4306",
},
{
"label": "常德",
"value": "4307",
},
{
"label": "张家界",
"value": "4308",
},
{
"label": "益阳",
"value": "4309",
},
{
"label": "郴州",
"value": "4310",
},
{
"label": "永州",
"value": "4311",
},
{
"label": "怀化",
"value": "4312",
},
{
"label": "娄底",
"value": "4313",
},
{
"label": "湘西土家族苗族自治州",
"value": "4331",
},
{
"label": "广州",
"value": "4401",
},
{
"label": "韶关",
"value": "4402",
},
{
"label": "深圳",
"value": "4403",
},
{
"label": "珠海",
"value": "4404",
},
{
"label": "汕头",
"value": "4405",
},
{
"label": "佛山",
"value": "4406",
},
{
"label": "江门",
"value": "4407",
},
{
"label": "湛江",
"value": "4408",
},
{
"label": "茂名",
"value": "4409",
},
{
"label": "肇庆",
"value": "4412",
},
{
"label": "惠州",
"value": "4413",
},
{
"label": "梅州",
"value": "4414",
},
{
"label": "汕尾",
"value": "4415",
},
{
"label": "河源",
"value": "4416",
},
{
"label": "阳江",
"value": "4417",
},
{
"label": "清远",
"value": "4418",
},
{
"label": "东莞",
"value": "4419",
},
{
"label": "中山",
"value": "4420",
},
{
"label": "潮州",
"value": "4451",
},
{
"label": "揭阳",
"value": "4452",
},
{
"label": "云浮",
"value": "4453",
},
{
"label": "南宁",
"value": "4501",
},
{
"label": "柳州",
"value": "4502",
},
{
"label": "桂林",
"value": "4503",
},
{
"label": "梧州",
"value": "4504",
},
{
"label": "北海",
"value": "4505",
},
{
"label": "防城港",
"value": "4506",
},
{
"label": "钦州",
"value": "4507",
},
{
"label": "贵港",
"value": "4508",
},
{
"label": "玉林",
"value": "4509",
},
{
"label": "百色",
"value": "4510",
},
{
"label": "贺州",
"value": "4511",
},
{
"label": "河池",
"value": "4512",
},
{
"label": "来宾",
"value": "4513",
},
{
"label": "崇左",
"value": "4514",
},
{
"label": "海口",
"value": "4601",
},
{
"label": "三亚",
"value": "4602",
},
{
"label": "三沙",
"value": "4603",
},
{
"label": "儋州",
"value": "4604",
},
{
"label": "重庆",
"value": "5001",
},
{
"label": "成都",
"value": "5101",
},
{
"label": "自贡",
"value": "5103",
},
{
"label": "攀枝花",
"value": "5104",
},
{
"label": "泸州",
"value": "5105",
},
{
"label": "德阳",
"value": "5106",
},
{
"label": "绵阳",
"value": "5107",
},
{
"label": "广元",
"value": "5108",
},
{
"label": "遂宁",
"value": "5109",
},
{
"label": "内江",
"value": "5110",
},
{
"label": "乐山",
"value": "5111",
},
{
"label": "南充",
"value": "5113",
},
{
"label": "眉山",
"value": "5114",
},
{
"label": "宜宾",
"value": "5115",
},
{
"label": "广安",
"value": "5116",
},
{
"label": "达州",
"value": "5117",
},
{
"label": "雅安",
"value": "5118",
},
{
"label": "巴中",
"value": "5119",
},
{
"label": "资阳",
"value": "5120",
},
{
"label": "阿坝藏族羌族自治州",
"value": "5132",
},
{
"label": "甘孜藏族自治州",
"value": "5133",
},
{
"label": "凉山彝族自治州",
"value": "5134",
},
{
"label": "贵阳",
"value": "5201",
},
{
"label": "六盘水",
"value": "5202",
},
{
"label": "遵义",
"value": "5203",
},
{
"label": "安顺",
"value": "5204",
},
{
"label": "毕节",
"value": "5205",
},
{
"label": "铜仁",
"value": "5206",
},
{
"label": "黔西南布依族苗族自治州",
"value": "5223",
},
{
"label": "黔东南苗族侗族自治州",
"value": "5226",
},
{
"label": "黔南布依族苗族自治州",
"value": "5227",
},
{
"label": "昆明",
"value": "5301",
},
{
"label": "曲靖",
"value": "5303",
},
{
"label": "玉溪",
"value": "5304",
},
{
"label": "保山",
"value": "5305",
},
{
"label": "昭通",
"value": "5306",
},
{
"label": "丽江",
"value": "5307",
},
{
"label": "普洱",
"value": "5308",
},
{
"label": "临沧",
"value": "5309",
},
{
"label": "楚雄彝族自治州",
"value": "5323",
},
{
"label": "红河哈尼族彝族自治州",
"value": "5325",
},
{
"label": "文山壮族苗族自治州",
"value": "5326",
},
{
"label": "西双版纳傣族自治州",
"value": "5328",
},
{
"label": "大理白族自治州",
"value": "5329",
},
{
"label": "德宏傣族景颇族自治州",
"value": "5331",
},
{
"label": "怒江傈僳族自治州",
"value": "5333",
},
{
"label": "迪庆藏族自治州",
"value": "5334",
},
{
"label": "拉萨",
"value": "5401",
},
{
"label": "日喀则",
"value": "5402",
},
{
"label": "昌都",
"value": "5403",
},
{
"label": "林芝",
"value": "5404",
},
{
"label": "山南",
"value": "5405",
},
{
"label": "那曲地区",
"value": "5424",
},
{
"label": "阿里地区",
"value": "5425",
},
{
"label": "西安",
"value": "6101",
},
{
"label": "铜川",
"value": "6102",
},
{
"label": "宝鸡",
"value": "6103",
},
{
"label": "咸阳",
"value": "6104",
},
{
"label": "渭南",
"value": "6105",
},
{
"label": "延安",
"value": "6106",
},
{
"label": "汉中",
"value": "6107",
},
{
"label": "榆林",
"value": "6108",
},
{
"label": "安康",
"value": "6109",
},
{
"label": "商洛",
"value": "6110",
},
{
"label": "兰州",
"value": "6201",
},
{
"label": "嘉峪关",
"value": "6202",
},
{
"label": "金昌",
"value": "6203",
},
{
"label": "白银",
"value": "6204",
},
{
"label": "天水",
"value": "6205",
},
{
"label": "武威",
"value": "6206",
},
{
"label": "张掖",
"value": "6207",
},
{
"label": "平凉",
"value": "6208",
},
{
"label": "酒泉",
"value": "6209",
},
{
"label": "庆阳",
"value": "6210",
},
{
"label": "定西",
"value": "6211",
},
{
"label": "陇南",
"value": "6212",
},
{
"label": "临夏回族自治州",
"value": "6229",
},
{
"label": "甘南藏族自治州",
"value": "6230",
},
{
"label": "西宁",
"value": "6301",
},
{
"label": "海东",
"value": "6302",
},
{
"label": "海北藏族自治州",
"value": "6322",
},
{
"label": "黄南藏族自治州",
"value": "6323",
},
{
"label": "海南藏族自治州",
"value": "6325",
},
{
"label": "果洛藏族自治州",
"value": "6326",
},
{
"label": "玉树藏族自治州",
"value": "6327",
},
{
"label": "海西蒙古族藏族自治州",
"value": "6328",
},
{
"label": "银川",
"value": "6401",
},
{
"label": "石嘴山",
"value": "6402",
},
{
"label": "吴忠",
"value": "6403",
},
{
"label": "固原",
"value": "6404",
},
{
"label": "中卫",
"value": "6405",
},
{
"label": "乌鲁木齐",
"value": "6501",
},
{
"label": "克拉玛依",
"value": "6502",
},
{
"label": "吐鲁番",
"value": "6504",
},
{
"label": "哈密",
"value": "6505",
},
{
"label": "昌吉回族自治州",
"value": "6523",
},
{
"label": "博尔塔拉蒙古自治州",
"value": "6527",
},
{
"label": "巴音郭楞蒙古自治州",
"value": "6528",
},
{
"label": "阿克苏地区",
"value": "6529",
},
{
"label": "克孜勒苏柯尔克孜自治州",
"value": "6530",
},
{
"label": "喀什地区",
"value": "6531",
},
{
"label": "和田地区",
"value": "6532",
},
{
"label": "伊犁哈萨克自治州",
"value": "6540",
},
{
"label": "塔城地区",
"value": "6542",
},
{
"label": "阿勒泰地区",
"value": "6543",
},
{
"label": "台北",
"value": "6601",
},
{
"label": "高雄",
"value": "6602",
},
{
"label": "基隆",
"value": "6603",
},
{
"label": "台中",
"value": "6604",
},
{
"label": "台南",
"value": "6605",
},
{
"label": "新竹",
"value": "6606",
},
{
"label": "嘉义",
"value": "6607",
},
{
"label": "宜兰",
"value": "6608",
},
{
"label": "桃园",
"value": "6609",
},
{
"label": "苗栗",
"value": "6610",
},
{
"label": "彰化",
"value": "6611",
},
{
"label": "南投",
"value": "6612",
},
{
"label": "云林",
"value": "6613",
},
{
"label": "屏东",
"value": "6614",
},
{
"label": "台东",
"value": "6615",
},
{
"label": "花莲",
"value": "6616",
},
{
"label": "澎湖",
"value": "6617",
},
{
"label": "香港",
"value": "6701",
},
{
"label": "澳门",
"value": "6801",
},
]
""";
}
package org.dromara.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
/**
* @author jiangxiaoge
* @description 简历相关
* @data 2024/12/16
**/
public class ResumeEnum {
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum ResumeExtra {
ZYJN(0, "专业技能"),
YD(1, "个人优势"),
HJXX(2, "获奖信息"),
XQAH(3, "兴趣爱好");
private int code;
private String name;
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum ResumeOptimizationStatus {
NOT_OPTIMIZED(0, "未优化"),
OPTIMIZATION_FAILED(1, "优化失败"),
OPTIMIZATION_SUCCESSFUL(2, "优化成功"),
OPTIMIZING(3, "优化中");
private int code;
private String description;
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
public enum ResumeModel{
CUSTOM(0, "自定义模块", "custom", 9),
CLUB(1, "校园经历", "clubs", 7),//社团经历-校园经历
EDU(2, "教育经历", "edus", 0),
HOBBLES(3, "兴趣爱好", "hobbles", 6),
INTERNSHIP(4, "实习经历", "internships", 8),
WORK_EXP(5, "工作经历", "workExps", 1),
PROJECT(6, "项目经历", "projects",2),
SKILL(7, "专业技能", "skill", 3),
ADVANTAGE(8, "个人优势", "advantage", 4),
AWARDS(9, "荣誉奖项", "awards", 5),//获奖信息-荣誉奖项
BASE(10, "基本信息", "base", 99),
;
private int code;
private String name;
private String webCode;
private int sort;
public static String getWebCodeByCode(Integer code){
for (ResumeModel thisEnum : EnumSet.allOf(ResumeModel.class)) {
if (thisEnum.code == code){
return thisEnum.webCode;
}
}
return null;
}
public static List<Integer> getResumeModel(){
List<Integer> codeList = new ArrayList<>();
for (ResumeModel model : EnumSet.allOf(ResumeModel.class)) {
if (model != ResumeModel.CUSTOM && model != ResumeModel.BASE){
codeList.add(model.getCode());
}
}
return codeList;
}
}
}
package org.dromara.common.core.factory;
import cn.hutool.core.lang.PatternPool;
import org.dromara.common.core.constant.RegexConstants;
import java.util.regex.Pattern;
/**
* 正则表达式模式池工厂
* <p>初始化的时候将正则表达式加入缓存池当中</p>
* <p>提高正则表达式的性能,避免重复编译相同的正则表达式</p>
*
* @author 21001
*/
public class RegexPatternPoolFactory extends PatternPool {
/**
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
*/
public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
/**
* 身份证号码(后6位)
*/
public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
/**
* QQ号码
*/
public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
/**
* 邮政编码
*/
public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
/**
* 注册账号
*/
public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
/**
* 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
*/
public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
/**
* 通用状态(0表示正常,1表示停用)
*/
public static final Pattern STATUS = get(RegexConstants.STATUS);
}
package org.dromara.common.core.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.file.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.File;
/**
* 根据ip地址定位工具类,离线方式
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
*
* @author lishuyan
*/
@Slf4j
public class RegionUtils {
private static final Searcher SEARCHER;
static {
String fileName = "/ip2region.xdb";
File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
if (!FileUtils.exist(existFile)) {
ClassPathResource fileStream = new ClassPathResource(fileName);
if (ObjectUtil.isEmpty(fileStream.getStream())) {
throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
}
FileUtils.writeFromStream(fileStream.getStream(), existFile);
}
String dbPath = existFile.getPath();
// 1、从 dbPath 加载整个 xdb 到内存。
byte[] cBuff;
try {
cBuff = Searcher.loadContentFromFile(dbPath);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
}
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
try {
SEARCHER = Searcher.newWithBuffer(cBuff);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
}
}
/**
* 根据IP地址离线获取城市
*/
public static String getCityInfo(String ip) {
try {
ip = ip.trim();
// 3、执行查询
String region = SEARCHER.search(ip);
return region.replace("0|", "").replace("|0", "");
} catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ip);
return "未知";
}
}
}
package org.dromara.common.core.utils.reflect;
import cn.hutool.core.util.ReflectUtil;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.lang.reflect.Method;
/**
* 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
*
* @author Lion Li
*/
@SuppressWarnings("rawtypes")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ReflectUtils extends ReflectUtil {
private static final String SETTER_PREFIX = "set";
private static final String GETTER_PREFIX = "get";
/**
* 调用Getter方法.
* 支持多级,如:对象名.对象名.方法
*/
@SuppressWarnings("unchecked")
public static <E> E invokeGetter(Object obj, String propertyName) {
Object object = obj;
for (String name : StringUtils.split(propertyName, ".")) {
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
object = invoke(object, getterMethodName);
}
return (E) object;
}
/**
* 调用Setter方法, 仅匹配方法名。
* 支持多级,如:对象名.对象名.方法
*/
public static <E> void invokeSetter(Object obj, String propertyName, E value) {
Object object = obj;
String[] names = StringUtils.split(propertyName, ".");
for (int i = 0; i < names.length; i++) {
if (i < names.length - 1) {
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
object = invoke(object, getterMethodName);
} else {
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
Method method = getMethodByName(object.getClass(), setterMethodName);
invoke(object, method, value);
}
}
}
}
package org.dromara.common.core.utils.regex;
import cn.hutool.core.util.ReUtil;
import org.dromara.common.core.constant.RegexConstants;
/**
* 正则相关工具类
*
* @author Feng
*/
public final class RegexUtils extends ReUtil {
/**
* 从输入字符串中提取匹配的部分,如果没有匹配则返回默认值
*
* @param input 要提取的输入字符串
* @param regex 用于匹配的正则表达式,可以使用 {@link RegexConstants} 中定义的常量
* @param defaultInput 如果没有匹配时返回的默认值
* @return 如果找到匹配的部分,则返回匹配的部分,否则返回默认值
*/
public static String extractFromString(String input, String regex, String defaultInput) {
try {
String str = ReUtil.get(regex, input, 1);
return str == null ? defaultInput : str;
} catch (Exception e) {
return defaultInput;
}
}
}
package org.dromara.common.core.utils.regex;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.lang.Validator;
import org.dromara.common.core.factory.RegexPatternPoolFactory;
import java.util.regex.Pattern;
/**
* 正则字段校验器
* 主要验证字段非空、是否为满足指定格式等
*
* @author Feng
*/
public class RegexValidator extends Validator {
/**
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
*/
public static final Pattern DICTIONARY_TYPE = RegexPatternPoolFactory.DICTIONARY_TYPE;
/**
* 身份证号码(后6位)
*/
public static final Pattern ID_CARD_LAST_6 = RegexPatternPoolFactory.ID_CARD_LAST_6;
/**
* QQ号码
*/
public static final Pattern QQ_NUMBER = RegexPatternPoolFactory.QQ_NUMBER;
/**
* 邮政编码
*/
public static final Pattern POSTAL_CODE = RegexPatternPoolFactory.POSTAL_CODE;
/**
* 注册账号
*/
public static final Pattern ACCOUNT = RegexPatternPoolFactory.ACCOUNT;
/**
* 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符
*/
public static final Pattern PASSWORD = RegexPatternPoolFactory.PASSWORD;
/**
* 通用状态(0表示正常,1表示停用)
*/
public static final Pattern STATUS = RegexPatternPoolFactory.STATUS;
/**
* 检查输入的账号是否匹配预定义的规则
*
* @param value 要验证的账号
* @return 如果账号符合规则,返回 true;否则,返回 false。
*/
public static boolean isAccount(CharSequence value) {
return isMatchRegex(ACCOUNT, value);
}
/**
* 验证输入的账号是否符合规则,如果不符合,则抛出 ValidateException 异常
*
* @param value 要验证的账号
* @param errorMsg 验证失败时抛出的异常消息
* @param <T> CharSequence 的子类型
* @return 如果验证通过,返回输入的账号
* @throws ValidateException 如果验证失败
*/
public static <T extends CharSequence> T validateAccount(T value, String errorMsg) throws ValidateException {
if (!isAccount(value)) {
throw new ValidateException(errorMsg);
}
return value;
}
/**
* 检查输入的状态是否匹配预定义的规则
*
* @param value 要验证的状态
* @return 如果状态符合规则,返回 true;否则,返回 false。
*/
public static boolean isStatus(CharSequence value) {
return isMatchRegex(STATUS, value);
}
/**
* 验证输入的状态是否符合规则,如果不符合,则抛出 ValidateException 异常
*
* @param value 要验证的状态
* @param errorMsg 验证失败时抛出的异常消息
* @param <T> CharSequence 的子类型
* @return 如果验证通过,返回输入的状态
* @throws ValidateException 如果验证失败
*/
public static <T extends CharSequence> T validateStatus(T value, String errorMsg) throws ValidateException {
if (!isStatus(value)) {
throw new ValidateException(errorMsg);
}
return value;
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.store.redis;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.ConfigItem;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.*;
import org.apache.dubbo.metadata.MappingChangedEvent;
import org.apache.dubbo.metadata.MappingListener;
import org.apache.dubbo.metadata.MetadataInfo;
import org.apache.dubbo.metadata.ServiceNameMapping;
import org.apache.dubbo.metadata.report.identifier.*;
import org.apache.dubbo.metadata.report.support.AbstractMetadataReport;
import org.apache.dubbo.rpc.RpcException;
import redis.clients.jedis.*;
import redis.clients.jedis.params.SetParams;
import redis.clients.jedis.util.JedisClusterCRC16;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static org.apache.dubbo.common.constants.CommonConstants.*;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_RESPONSE;
import static org.apache.dubbo.metadata.MetadataConstants.META_DATA_STORE_TAG;
import static org.apache.dubbo.metadata.ServiceNameMapping.DEFAULT_MAPPING_GROUP;
import static org.apache.dubbo.metadata.ServiceNameMapping.getAppNames;
import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
/**
* RedisMetadataReport
*/
public class RedisMetadataReport extends AbstractMetadataReport {
private static final String REDIS_DATABASE_KEY = "database";
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RedisMetadataReport.class);
// protected , for test
protected JedisPool pool;
private Set<HostAndPort> jedisClusterNodes;
private int timeout;
private String password;
private final String root;
private final ConcurrentHashMap<String, MappingDataListener> mappingDataListenerMap = new ConcurrentHashMap<>();
private SetParams jedisParams = SetParams.setParams();
public RedisMetadataReport(URL url) {
super(url);
timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
password = url.getPassword();
this.root = url.getGroup(DEFAULT_ROOT);
if (url.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
// ttl default is twice the cycle-report time
jedisParams.ex(ONE_DAY_IN_MILLISECONDS * 2);
}
if (url.getParameter(CLUSTER_KEY, false)) {
jedisClusterNodes = new HashSet<>();
List<URL> urls = url.getBackupUrls();
for (URL tmpUrl : urls) {
jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort()));
}
} else {
int database = url.getParameter(REDIS_DATABASE_KEY, 0);
pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), timeout, password, database);
}
}
@Override
protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) {
this.storeMetadata(providerMetadataIdentifier, serviceDefinitions);
}
@Override
protected void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String value) {
this.storeMetadata(consumerMetadataIdentifier, value);
}
@Override
protected void doSaveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier, URL url) {
this.storeMetadata(serviceMetadataIdentifier, URL.encode(url.toFullString()));
}
@Override
protected void doRemoveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier) {
this.deleteMetadata(serviceMetadataIdentifier);
}
@Override
protected List<String> doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
String content = getMetadata(metadataIdentifier);
if (StringUtils.isEmpty(content)) {
return Collections.emptyList();
}
return new ArrayList<>(Arrays.asList(URL.decode(content)));
}
@Override
protected void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr) {
this.storeMetadata(subscriberMetadataIdentifier, urlListStr);
}
@Override
protected String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
return this.getMetadata(subscriberMetadataIdentifier);
}
@Override
public String getServiceDefinition(MetadataIdentifier metadataIdentifier) {
return this.getMetadata(metadataIdentifier);
}
private void storeMetadata(BaseMetadataIdentifier metadataIdentifier, String v) {
if (pool != null) {
storeMetadataStandalone(metadataIdentifier, v);
} else {
storeMetadataInCluster(metadataIdentifier, v);
}
}
private void storeMetadataInCluster(BaseMetadataIdentifier metadataIdentifier, String v) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
jedisCluster.set(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG, v, jedisParams);
} catch (Throwable e) {
String msg =
"Failed to put " + metadataIdentifier + " to redis cluster " + v + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void storeMetadataStandalone(BaseMetadataIdentifier metadataIdentifier, String v) {
try (Jedis jedis = pool.getResource()) {
jedis.set(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), v, jedisParams);
} catch (Throwable e) {
String msg = "Failed to put " + metadataIdentifier + " to redis " + v + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void deleteMetadata(BaseMetadataIdentifier metadataIdentifier) {
if (pool != null) {
deleteMetadataStandalone(metadataIdentifier);
} else {
deleteMetadataInCluster(metadataIdentifier);
}
}
private void deleteMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
jedisCluster.del(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
} catch (Throwable e) {
String msg = "Failed to delete " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void deleteMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
try (Jedis jedis = pool.getResource()) {
jedis.del(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
} catch (Throwable e) {
String msg = "Failed to delete " + metadataIdentifier + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMetadata(BaseMetadataIdentifier metadataIdentifier) {
if (pool != null) {
return getMetadataStandalone(metadataIdentifier);
} else {
return getMetadataInCluster(metadataIdentifier);
}
}
private String getMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
return jedisCluster.get(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
} catch (Throwable e) {
String msg = "Failed to get " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
try (Jedis jedis = pool.getResource()) {
return jedis.get(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
} catch (Throwable e) {
String msg = "Failed to get " + metadataIdentifier + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
/**
* Store class and application names using Redis hashes
* key: default 'dubbo:mapping'
* field: class (serviceInterface)
* value: application_names
* @param serviceInterface field(class)
* @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
* @param newConfigContent new application_names
* @param ticket previous application_names
* @return
*/
@Override
public boolean registerServiceAppMapping(
String serviceInterface, String defaultMappingGroup, String newConfigContent, Object ticket) {
try {
if (null != ticket && !(ticket instanceof String)) {
throw new IllegalArgumentException("redis publishConfigCas requires stat type ticket");
}
String pathKey = buildMappingKey(defaultMappingGroup);
return storeMapping(pathKey, serviceInterface, newConfigContent, (String) ticket);
} catch (Exception e) {
logger.warn(TRANSPORT_FAILED_RESPONSE, "", "", "redis publishConfigCas failed.", e);
return false;
}
}
private boolean storeMapping(String key, String field, String value, String ticket) {
if (pool != null) {
return storeMappingStandalone(key, field, value, ticket);
} else {
return storeMappingInCluster(key, field, value, ticket);
}
}
/**
* use 'watch' to implement cas.
* Find information about slot distribution by key.
*/
private boolean storeMappingInCluster(String key, String field, String value, String ticket) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
Jedis jedis = new Jedis(jedisCluster.getConnectionFromSlot(JedisClusterCRC16.getSlot(key)));
jedis.watch(key);
String oldValue = jedis.hget(key, field);
if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
Transaction transaction = jedis.multi();
transaction.hset(key, field, value);
List<Object> result = transaction.exec();
if (null != result) {
jedisCluster.publish(buildPubSubKey(), field);
return true;
}
} else {
jedis.unwatch();
}
jedis.close();
} catch (Throwable e) {
String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
return false;
}
/**
* use 'watch' to implement cas.
* Find information about slot distribution by key.
*/
private boolean storeMappingStandalone(String key, String field, String value, String ticket) {
try (Jedis jedis = pool.getResource()) {
jedis.watch(key);
String oldValue = jedis.hget(key, field);
if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
Transaction transaction = jedis.multi();
transaction.hset(key, field, value);
List<Object> result = transaction.exec();
if (null != result) {
jedis.publish(buildPubSubKey(), field);
return true;
}
}
jedis.unwatch();
} catch (Throwable e) {
String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
return false;
}
/**
* build mapping key
* @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
* @return
*/
private String buildMappingKey(String defaultMappingGroup) {
return this.root + GROUP_CHAR_SEPARATOR + defaultMappingGroup;
}
/**
* build pub/sub key
*/
private String buildPubSubKey() {
return buildMappingKey(DEFAULT_MAPPING_GROUP) + GROUP_CHAR_SEPARATOR + QUEUES_KEY;
}
/**
* get content and use content to complete cas
* @param serviceKey class
* @param group {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
*/
@Override
public ConfigItem getConfigItem(String serviceKey, String group) {
String key = buildMappingKey(group);
String content = getMappingData(key, serviceKey);
return new ConfigItem(content, content);
}
/**
* get current application_names
*/
private String getMappingData(String key, String field) {
if (pool != null) {
return getMappingDataStandalone(key, field);
} else {
return getMappingDataInCluster(key, field);
}
}
private String getMappingDataInCluster(String key, String field) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
return jedisCluster.hget(key, field);
} catch (Throwable e) {
String msg = "Failed to get " + key + ":" + field + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMappingDataStandalone(String key, String field) {
try (Jedis jedis = pool.getResource()) {
return jedis.hget(key, field);
} catch (Throwable e) {
String msg = "Failed to get " + key + ":" + field + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
/**
* remove listener. If have no listener,thread will dead
*/
@Override
public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
MappingDataListener mappingDataListener = mappingDataListenerMap.get(buildPubSubKey());
if (null != mappingDataListener) {
NotifySub notifySub = mappingDataListener.getNotifySub();
notifySub.removeListener(serviceKey, listener);
if (notifySub.isEmpty()) {
mappingDataListener.shutdown();
}
}
}
/**
* Start a thread and subscribe to {@link this#buildPubSubKey()}.
* Notify {@link MappingListener} if there is a change in the 'application_names' message.
*/
@Override
public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
MappingDataListener mappingDataListener =
ConcurrentHashMapUtils.computeIfAbsent(mappingDataListenerMap, buildPubSubKey(), k -> {
MappingDataListener dataListener = new MappingDataListener(buildPubSubKey());
dataListener.start();
return dataListener;
});
mappingDataListener.getNotifySub().addListener(serviceKey, listener);
return this.getServiceAppMapping(serviceKey, url);
}
@Override
public Set<String> getServiceAppMapping(String serviceKey, URL url) {
String key = buildMappingKey(DEFAULT_MAPPING_GROUP);
return getAppNames(getMappingData(key, serviceKey));
}
@Override
public MetadataInfo getAppMetadata(SubscriberMetadataIdentifier identifier, Map<String, String> instanceMetadata) {
String content = this.getMetadata(identifier);
return JsonUtils.toJavaObject(content, MetadataInfo.class);
}
@Override
public void publishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
this.storeMetadata(identifier, metadataInfo.getContent());
}
@Override
public void unPublishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
this.deleteMetadata(identifier);
}
// for test
public MappingDataListener getMappingDataListener() {
return mappingDataListenerMap.get(buildPubSubKey());
}
/**
* Listen for changes in the 'application_names' message and notify the listener.
*/
class NotifySub extends JedisPubSub {
private final Map<String, Set<MappingListener>> listeners = new ConcurrentHashMap<>();
public void addListener(String key, MappingListener listener) {
Set<MappingListener> listenerSet = listeners.computeIfAbsent(key, k -> new ConcurrentHashSet<>());
listenerSet.add(listener);
}
public void removeListener(String serviceKey, MappingListener listener) {
Set<MappingListener> listenerSet = this.listeners.get(serviceKey);
if (listenerSet != null) {
listenerSet.remove(listener);
if (listenerSet.isEmpty()) {
this.listeners.remove(serviceKey);
}
}
}
public Boolean isEmpty() {
return this.listeners.isEmpty();
}
@Override
public void onMessage(String key, String msg) {
logger.info("sub from redis:" + key + " message:" + msg);
String applicationNames = getMappingData(buildMappingKey(DEFAULT_MAPPING_GROUP), msg);
MappingChangedEvent mappingChangedEvent = new MappingChangedEvent(msg, getAppNames(applicationNames));
if (!CollectionUtils.isEmpty(listeners.get(msg))) {
for (MappingListener mappingListener : listeners.get(msg)) {
mappingListener.onEvent(mappingChangedEvent);
}
}
}
@Override
public void onPMessage(String pattern, String key, String msg) {
onMessage(key, msg);
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
super.onPSubscribe(pattern, subscribedChannels);
}
}
/**
* Subscribe application names change message.
*/
class MappingDataListener extends Thread {
private String path;
private final NotifySub notifySub = new NotifySub();
// for test
protected volatile boolean running = true;
public MappingDataListener(String path) {
this.path = path;
}
public NotifySub getNotifySub() {
return notifySub;
}
@Override
public void run() {
while (running) {
if (pool != null) {
try (Jedis jedis = pool.getResource()) {
jedis.subscribe(notifySub, path);
} catch (Throwable e) {
String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
} else {
try (JedisCluster jedisCluster = new JedisCluster(
jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
jedisCluster.subscribe(notifySub, path);
} catch (Throwable e) {
String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
}
}
public void shutdown() {
try {
running = false;
notifySub.unsubscribe(path);
} catch (Throwable e) {
String msg = "Failed to unsubscribe " + path + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
}
}
}
}
package org.dromara.common.dubbo.enumd;
import lombok.AllArgsConstructor;
/**
* 请求日志泛型
*
* @author Lion Li
*/
@AllArgsConstructor
public enum RequestLogEnum {
/**
* info 基础信息
*/
INFO,
/**
* param 参数信息
*/
PARAM,
/**
* full 全部
*/
FULL;
}
package org.dromara.common.redis.config.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.redisson.config.ReadMode;
import org.redisson.config.SubscriptionMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Redisson 配置属性
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties {
/**
* redis缓存key前缀
*/
private String keyPrefix;
/**
* 线程池数量,默认值 = 当前处理核数量 * 2
*/
private int threads;
/**
* Netty线程池数量,默认值 = 当前处理核数量 * 2
*/
private int nettyThreads;
/**
* 单机服务配置
*/
private SingleServerConfig singleServerConfig;
/**
* 集群服务配置
*/
private ClusterServersConfig clusterServersConfig;
@Data
@NoArgsConstructor
public static class SingleServerConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* 最小空闲连接数
*/
private int connectionMinimumIdleSize;
/**
* 连接池大小
*/
private int connectionPoolSize;
/**
* 连接空闲超时,单位:毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时,单位:毫秒
*/
private int timeout;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
}
@Data
@NoArgsConstructor
public static class ClusterServersConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* master最小空闲连接数
*/
private int masterConnectionMinimumIdleSize;
/**
* master连接池大小
*/
private int masterConnectionPoolSize;
/**
* slave最小空闲连接数
*/
private int slaveConnectionMinimumIdleSize;
/**
* slave连接池大小
*/
private int slaveConnectionPoolSize;
/**
* 连接空闲超时,单位:毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时,单位:毫秒
*/
private int timeout;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
/**
* 读取模式
*/
private ReadMode readMode;
/**
* 订阅模式
*/
private SubscriptionMode subscriptionMode;
}
}
package org.dromara.common.redis.handler;
import cn.hutool.http.HttpStatus;
import com.baomidou.lock.exception.LockFailureException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* Redis异常处理器
*
* @author AprilWind
*/
@Slf4j
@RestControllerAdvice
public class RedisExceptionHandler {
/**
* 分布式锁Lock4j异常
*/
@ExceptionHandler(LockFailureException.class)
public R<Void> handleLockFailureException(LockFailureException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("获取锁失败了'{}',发生Lock4j异常.", requestURI, e);
return R.fail(HttpStatus.HTTP_UNAVAILABLE, "业务处理中,请稍后再试...");
}
}
package org.dromara.common.redis.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.redisson.api.*;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* redis 工具类
*
* @author Lion Li
* @version 3.1.0 新增
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class RedisUtils {
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
/**
* 限流
*
* @param key 限流key
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔
* @return -1 表示失败
*/
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits();
} else {
return -1L;
}
}
/**
* 获取客户端实例
*/
public static RedissonClient getClient() {
return CLIENT;
}
/**
* 发布通道消息
*
* @param channelKey 通道key
* @param msg 发送数据
* @param consumer 自定义处理
*/
public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg);
consumer.accept(msg);
}
/**
* 发布消息到指定的频道
*
* @param channelKey 通道key
* @param msg 发送数据
*/
public static <T> void publish(String channelKey, T msg) {
RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg);
}
/**
* 订阅通道接收消息
*
* @param channelKey 通道key
* @param clazz 消息类型
* @param consumer 自定义处理
*/
public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
RTopic topic = CLIENT.getTopic(channelKey);
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public static <T> void setCacheObject(final String key, final T value) {
setCacheObject(key, value, false);
}
/**
* 缓存基本的对象,保留当前对象 TTL 有效期
*
* @param key 缓存的键值
* @param value 缓存的值
* @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
* @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
*/
public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
RBucket<T> bucket = CLIENT.getBucket(key);
if (isSaveTtl) {
try {
bucket.setAndKeepTTL(value);
} catch (Exception e) {
long timeToLive = bucket.remainTimeToLive();
if (timeToLive == -1) {
setCacheObject(key, value);
} else {
setCacheObject(key, value, Duration.ofMillis(timeToLive));
}
}
} else {
bucket.set(value);
}
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param duration 时间
*/
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
RBatch batch = CLIENT.createBatch();
RBucketAsync<T> bucket = batch.getBucket(key);
bucket.setAsync(value);
bucket.expireAsync(duration);
batch.execute();
}
/**
* 如果不存在则设置 并返回 true 如果存在则返回 false
*
* @param key 缓存的键值
* @param value 缓存的值
* @return set成功或失败
*/
public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
RBucket<T> bucket = CLIENT.getBucket(key);
return bucket.setIfAbsent(value, duration);
}
/**
* 如果存在则设置 并返回 true 如果存在则返回 false
*
* @param key 缓存的键值
* @param value 缓存的值
* @return set成功或失败
*/
public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
RBucket<T> bucket = CLIENT.getBucket(key);
return bucket.setIfExists(value, duration);
}
/**
* 注册对象监听器
* <p>
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addObjectListener(final String key, final ObjectListener listener) {
RBucket<T> result = CLIENT.getBucket(key);
result.addListener(listener);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public static boolean expire(final String key, final long timeout) {
return expire(key, Duration.ofSeconds(timeout));
}
/**
* 设置有效时间
*
* @param key Redis键
* @param duration 超时时间
* @return true=设置成功;false=设置失败
*/
public static boolean expire(final String key, final Duration duration) {
RBucket rBucket = CLIENT.getBucket(key);
return rBucket.expire(duration);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public static <T> T getCacheObject(final String key) {
RBucket<T> rBucket = CLIENT.getBucket(key);
return rBucket.get();
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public static <T> T getCacheObject(final String key, Class<T> clazz) {
RBucket<T> rBucket = CLIENT.getBucket(key);
return rBucket.get();
}
/**
* 获得key剩余存活时间
*
* @param key 缓存键值
* @return 剩余存活时间
*/
public static <T> long getTimeToLive(final String key) {
RBucket<T> rBucket = CLIENT.getBucket(key);
return rBucket.remainTimeToLive();
}
/**
* 删除单个对象
*
* @param key 缓存的键值
*/
public static boolean deleteObject(final String key) {
return CLIENT.getBucket(key).delete();
}
/**
* 删除集合对象
*
* @param collection 多个对象
*/
public static void deleteObject(final Collection collection) {
RBatch batch = CLIENT.createBatch();
collection.forEach(t -> {
batch.getBucket(t.toString()).deleteAsync();
});
batch.execute();
}
/**
* 检查缓存对象是否存在
*
* @param key 缓存的键值
*/
public static boolean isExistsObject(final String key) {
return CLIENT.getBucket(key).isExists();
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public static <T> boolean setCacheList(final String key, final List<T> dataList) {
RList<T> rList = CLIENT.getList(key);
return rList.addAll(dataList);
}
/**
* 追加缓存List数据
*
* @param key 缓存的键值
* @param data 待缓存的数据
* @return 缓存的对象
*/
public static <T> boolean addCacheList(final String key, final T data) {
RList<T> rList = CLIENT.getList(key);
return rList.add(data);
}
/**
* 注册List监听器
* <p>
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addListListener(final String key, final ObjectListener listener) {
RList<T> rList = CLIENT.getList(key);
rList.addListener(listener);
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public static <T> List<T> getCacheList(final String key) {
RList<T> rList = CLIENT.getList(key);
return rList.readAll();
}
/**
* 获得缓存的list对象(范围)
*
* @param key 缓存的键值
* @param form 起始下标
* @param to 截止下标
* @return 缓存键值对应的数据
*/
public static <T> List<T> getCacheListRange(final String key, int form, int to) {
RList<T> rList = CLIENT.getList(key);
return rList.range(form, to);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
RSet<T> rSet = CLIENT.getSet(key);
return rSet.addAll(dataSet);
}
/**
* 追加缓存Set数据
*
* @param key 缓存的键值
* @param data 待缓存的数据
* @return 缓存的对象
*/
public static <T> boolean addCacheSet(final String key, final T data) {
RSet<T> rSet = CLIENT.getSet(key);
return rSet.add(data);
}
/**
* 注册Set监听器
* <p>
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addSetListener(final String key, final ObjectListener listener) {
RSet<T> rSet = CLIENT.getSet(key);
rSet.addListener(listener);
}
/**
* 获得缓存的set
*
* @param key 缓存的key
* @return set对象
*/
public static <T> Set<T> getCacheSet(final String key) {
RSet<T> rSet = CLIENT.getSet(key);
return rSet.readAll();
}
/**
* 缓存Map
*
* @param key 缓存的键值
* @param dataMap 缓存的数据
*/
public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.putAll(dataMap);
}
}
/**
* 注册Map监听器
* <p>
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addMapListener(final String key, final ObjectListener listener) {
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.addListener(listener);
}
/**
* 获得缓存的Map
*
* @param key 缓存的键值
* @return map对象
*/
public static <T> Map<String, T> getCacheMap(final String key) {
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.getAll(rMap.keySet());
}
/**
* 获得缓存Map的key列表
*
* @param key 缓存的键值
* @return key列表
*/
public static <T> Set<String> getCacheMapKeySet(final String key) {
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.keySet();
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.put(hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public static <T> T getCacheMapValue(final String key, final String hKey) {
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.get(hKey);
}
/**
* 删除Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public static <T> T delCacheMapValue(final String key, final String hKey) {
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.remove(hKey);
}
/**
* 删除Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键
*/
public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
RBatch batch = CLIENT.createBatch();
RMapAsync<String, T> rMap = batch.getMap(key);
for (String hKey : hKeys) {
rMap.removeAsync(hKey);
}
batch.execute();
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
RMap<K, V> rMap = CLIENT.getMap(key);
return rMap.getAll(hKeys);
}
/**
* 设置原子值
*
* @param key Redis键
* @param value 值
*/
public static void setAtomicValue(String key, long value) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
atomic.set(value);
}
/**
* 获取原子值
*
* @param key Redis键
* @return 当前值
*/
public static long getAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.get();
}
/**
* 递增原子值
*
* @param key Redis键
* @return 当前值
*/
public static long incrAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.incrementAndGet();
}
/**
* 递减原子值
*
* @param key Redis键
* @return 当前值
*/
public static long decrAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.decrementAndGet();
}
/**
* 获得缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id)
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public static Collection<String> keys(final String pattern) {
Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
return stream.collect(Collectors.toList());
}
/**
* 删除缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id)
*
* @param pattern 字符串前缀
*/
public static void deleteKeys(final String pattern) {
CLIENT.getKeys().deleteByPattern(pattern);
}
/**
* 检查redis中是否存在key
*
* @param key 键
*/
public synchronized static Boolean hasKey(String key) {
RKeys rKeys = CLIENT.getKeys();
return rKeys.countExists(key) > 0;
}
}
package org.dromara.common.web.config;
import org.dromara.common.web.handler.GlobalExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 通用配置
*
* @author Lion Li
*/
@AutoConfiguration
public class ResourcesConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* 全局异常处理器
*/
@Bean
public GlobalExceptionHandler globalExceptionHandler() {
return new GlobalExceptionHandler();
}
}
package com.bkty.system.config;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.Timeout;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
/**
* RestTemplate
* @author jiangxiaoge
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate(httpRequestFactory());
}
// 创建一个不带服务发现的 RestTemplate
@Bean
public RestTemplate nonLoadBalancedRestTemplate() {
return new RestTemplate(httpRequestFactory());
}
@Bean
public HttpComponentsClientHttpRequestFactory httpRequestFactory() {
// 使用自定义 HttpClient
CloseableHttpClient httpClient = httpClient();
// 创建工厂并绑定 HttpClient
return new HttpComponentsClientHttpRequestFactory(httpClient);
}
@Bean
public CloseableHttpClient httpClient() {
// 配置连接池
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(2000); // 最大连接数
connectionManager.setDefaultMaxPerRoute(200); // 每个路由的最大连接数
// 配置超时
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(12)) // 连接超时
.setResponseTimeout(Timeout.ofSeconds(48)) // 响应超时
.build();
// 创建 HttpClient,并绑定连接池和超时配置
return HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
// 创建一个跳过 SSL 验证的 SSLContext
private SSLContext getSSLContext() {
try {
// 创建一个信任管理器,接受所有证书
TrustManager[] trustAllCertificates = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
// 使用自定义的信任管理器创建 SSLContext
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial((chain, authType) -> true) // 忽略 SSL 证书验证
.build();
// 使用该 SSLContext
return sslContext;
} catch (NoSuchAlgorithmException | java.security.KeyManagementException e) {
throw new RuntimeException("Error creating SSLContext", e);
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
}
}
package com.bkty.system.domain.dto;
import lombok.Data;
/**
* @author jiangxiaoge
* @description 简历列表item
* @data 2025/5/19
**/
@Data
public class ResumeListItemCache {
private Integer publicStatus;
private Long resumeId;
private String resumeName;
private Integer runType;
private String updateTime;
private Integer resumeScore;
// 使用状态 0=可用 1=作废
private Integer useStatus;
private String appraise;
//默认状态 0=默认 1=不默认
private Integer defaultStatus;
private String sex;
private String avatar;
}
package com.bkty.system.domain.dto;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* @author jiangxiaoge
* @description 创建简历dto
* @data 2024/12/16
**/
@Data
public class ResumeMakeDto {
/**简历名称*/
private String resumeName;
/**模版id*/
private String template;
/**平台*/
private int platform;
/**未来简历id*/
private Long platformResumeId;
/**未来简历自定义id*/
private Long platformCustomId;
/**简历id*/
private String resumeId;
/**模块id*/
private Long modelId;
/**个人简历基本信息*/
private Map<String, Object> bodyData;
private List<String> platformIdList;
/**简历模块排序列表*/
private Map<Long, Integer> modelSort;
private Integer runType;
/**简历发布状态 0.未发布 1.已发布*/
private Integer publicStatus;
private Long userId;
/**权益id*/
private String vipId;
/**模版ID*/
private String templateId;
/**模版名称*/
private String templateName;
}
package com.bkty.system.domain.dto;
import lombok.Data;
/**
* @author jiangxiaoge
* @description 简历模块优化建议缓存
* @data 2025/5/19
**/
@Data
public class ResumeModelOptimizationCache {
private Integer runStatus;
private String optimizationDesc;
private String updateTime;
public ResumeModelOptimizationCache() {
}
public ResumeModelOptimizationCache(Integer runStatus, String optimizationDesc, String updateTime) {
this.runStatus = runStatus;
this.optimizationDesc = optimizationDesc;
this.updateTime = updateTime;
}
}
package com.bkty.system.domain.dto;
import lombok.Data;
/**
* @author jiangxiaoge
* @description 简历缓存-简历列表
* @data 2025/1/8
**/
@Data
public class ResumeMsgCache {
/**简历id*/
private String id;
/**简历名称*/
private String resumeName;
/**更新时间*/
private String updateTime;
/**运行状态*/
private Integer runType;
}
package com.bkty.system.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
/**
* @author Herbert
* @description 简历模版表
* @data 2025/05/07
**/
@Data
@TableName("resume_template")
public class ResumeTemplate extends BaseEntity {
/**
* 主键id
*/
@TableId(type = IdType.ASSIGN_ID)
private String id;
/**
* 模版名称
*/
private String templateName;
/**
* 模版路径
*/
private String templatePath;
/**
* 模版别名
*/
private String templateAlias;
/**
* 模版预览地址
*/
private String templatePreview;
/**
* 平台ID
*/
private String platformId;
private static final long serialVersionUID = 1L;
}
package com.bkty.system.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.core.utils.Entity;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author Herbert
* @description 简历翻译记录表
* @data 2025/04/08
**/
@Data
@TableName("resume_translation_record")
public class ResumeTranslationRecord extends Entity {
/**主键ID*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**简历ID*/
private Long resumeId;
/**简历名称或文件名称*/
private String resumeName;
/**简历类型:选择已有简历-1;简历上传-2;*/
private String type;
/**智能体简历上传文件ID*/
private String agentFileId;
/**简历上传文件ID*/
private String fileId;
/**此参数仅在对话产生的消息中返回*/
private String chatId;
/**此消息所在的会话ID*/
private String conversationId;
/**编写此消息的智能体ID。此参数仅在对话产生的消息中返回*/
private String botId;
/**生成的html路径*/
private String pathHtml;
/**生成的pdf路径*/
private String pathPdf;
/**生成的png路径*/
private String pathPng;
/**智能体翻译后的内容*/
private String content;
/**对话的运行状态。取值为:created-对话已创建;in_progress-智能体正在处理中;completed-智能体已完成处理,本次对话结束;failed-对话失败;requires_action-对话中断,需要进一步处理;canceled-对话已取消*/
private String status;
/**删除标志(0代表存在 2代表删除)*/
@TableLogic
private String delFlag;
@Schema(description = "模版名称")
private String templateName;
@Schema(description = "源语言")
private String sourceLanguage;
@Schema(description = "目标语言")
private String targetLanguage;
}
package com.bkty.system.domain.vo;
import lombok.Data;
import org.dromara.common.core.annotation.JxgInitField;
/**
* @author jiangxiaoge
* @description
* @data 2024/12/17
**/
@Data
public class ResumeBase {
/**生日*/
@JxgInitField("Age")
private String birthday;
/**头像url*/
@JxgInitField("Avatar")
private String avatar;
/**城市*/
@JxgInitField("City")
private String city;
/**名族*/
@JxgInitField("Ethnic")
private String ethnic;
/**岗位*/
@JxgInitField("JobInt")
private String jobInt;
/**邮箱*/
@JxgInitField("Mail")
private String email;
/**名字*/
@JxgInitField("Name")
private String username;
/**薪资*/
@JxgInitField("Pay")
private String pay;
/**手机号*/
@JxgInitField("Phone")
private String phone;
/**政治面貌*/
@JxgInitField("PolStatus")
private String polStatus;
/**性别*/
@JxgInitField("Sex")
private String sex;
/**工作状态*/
@JxgInitField("Status")
private String workStatus;
/**微信号*/
@JxgInitField("Wechat")
private String wechat;
/**参加工作时间*/
@JxgInitField(value = "WorkTime")
private String workTime;
/**简历名称*/
@JxgInitField(value = "ResumeName")
private String resumeName;
}
package com.bkty.system.domain.vo;
import lombok.Data;
/**
* @author jiangxiaoge
* @description 简历基本信息vo
* @data 2024/12/17
**/
@Data
public class ResumeBaseVo {
private Long resumeId;
private String platformId;
private String resumeName;
private String updateTime;
private Integer runType;
private Integer publicStatus;
}
package com.bkty.system.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "根据模版生成pdf,html,png文件请求类")
public class ResumeByPdfVo {
@Schema(description = "html名称")
private String htmlName;
@Schema(description = "pdf名称")
private String pdfName;
@Schema(description = "png名称")
private String pngName;
@Schema(description = "png名称小图")
private String pngNameMin;
@Schema(description = "简历名称")
private String resumeName;
@Schema(description = "简历翻译记录ID")
private Long resumeTranslationRecordId;
@Schema(description = "用户名称")
private String userName;
}
package com.bkty.system.domain.vo;
import lombok.Data;
import org.dromara.common.core.annotation.JxgInitField;
/**
* @author jiangxiaoge
* @description
* @data 2024/12/17
**/
@Data
public class ResumeClub {
/**社团名称*/
@JxgInitField("Name")
private String clubName;
/**担任角色*/
@JxgInitField("RoleName")
private String roleName;
/**开始时间*/
@JxgInitField("StartTime")
private String startTime;
/**结束时间*/
@JxgInitField("EndTime")
private String endTime;
/**详情*/
@JxgInitField("Description")
private String description;
@JxgInitField("Performance")
private String performance;
}
package com.bkty.system.domain.vo;
import lombok.Data;
import org.dromara.common.core.annotation.JxgInitField;
/**
* @author jiangxiaoge
* @description
* @data 2024/12/17
**/
@Data
public class ResumeEdu {
/**学历*/
@JxgInitField("Degree")
private String degree;
/**开始时间*/
@JxgInitField("StartYear")
private String startTime;
/**结束时间*/
@JxgInitField("EndYear")
private String endTime;
/**专业*/
@JxgInitField("Major")
private String major;
/**学校*/
@JxgInitField("School")
private String school;
/**详情*/
@JxgInitField("Description")
private String description;
}
package com.bkty.system.domain.vo;
import lombok.Data;
/**
* @author jiangxiaoge
* @description
* @data 2024/12/17
**/
@Data
public class ResumeExtra {
/**数据类型 0.专业技能1.个人优势.获奖信息3.兴趣爱好*/
private Integer dataType;
/**详情*/
private String description;
private String infoName;
private String startTime;
private String endTime;
}
package com.bkty.system.domain.vo;
import org.dromara.common.core.annotation.JxgInitField;
import lombok.Data;
/**
* @author jiangxiaoge
* @description
* @data 2024/12/17
**/
@Data
public class ResumeInternship {
@JxgInitField("Corp")
private String internshipCorp;
/**实习岗位*/
@JxgInitField("Post")
private String internshipPost;
/**成果*/
@JxgInitField("Performance")
private String performance;
/**开始时间*/
@JxgInitField("StartTime")
private String startTime;
/**结束时间*/
@JxgInitField("EndTime")
private String endTime;
/**详情*/
@JxgInitField("Content")
private String description;
}
package com.bkty.system.domain.vo;
import lombok.Data;
/**
* @author jiangxiaoge
* @description 模块列表vo
* @data 2024/12/20
**/
@Data
public class ResumeModelVo {
private Long modelId;
private Integer modelType;
private Integer dataSort;
private int isShow;
private String modelName;
private String webCode;
}
package com.bkty.system.domain.vo;
import org.dromara.common.core.annotation.JxgInitField;
import lombok.Data;
/**
* @author jiangxiaoge
* @description
* @data 2024/12/17
**/
@Data
public class ResumeProject {
/**项目名称*/
@JxgInitField("Name")
private String projectName;
/**职位名称*/
@JxgInitField("RoleName")
private String roleName;
/**开始时间*/
@JxgInitField("StartTime")
private String startTime;
/**结束时间*/
@JxgInitField("EndTime")
private String endTime;
/**职位描述*/
@JxgInitField("Description")
private String description;
/**项目描述*/
@JxgInitField("Performance")
private String performance;
}
package com.bkty.system.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
/**
* @Desc:
* @author: Herbert
* @date: 2025/3/17 13:56
* @since: 17
*/
@Data
@Schema(description = "简历智作统计数据预处理返回类")
public class ResumeSmartCreationVo {
@Schema(description = "用户ID")
private String userId;
@Schema(description = "用户名称")
private String username;
@Schema(description = "手机号")
private String phone;
@Schema(description = "学号")
private String studentNumber;
@Schema(description = "学校ID")
private String schoolId;
@Schema(description = "简历id")
private String resumeId;
@Schema(description = "简历名称")
private String resumeName;
@Schema(description = "简历创建时间")
private Date resumeCreateTime;
@Schema(description = "简历更新时间")
private Date resumeUpdateTime;
@Schema(description = "简历评分")
private Integer resumeScore;
@Schema(description = "简历优化次数")
private Integer optimizeCounts;
@Schema(description = "下载次数")
private Integer downloadCounts;
@Schema(description = "简历是否删除(true:已删除,false:未删除)")
private Boolean resumeIsDeleted;
@Schema(description = "简历评价")
private String appraise;
@Schema(description = "模版名称")
private String templateName;
}
package com.bkty.system.domain.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author jiangxiaoge
* @description 个人简历
* @data 2024/12/17
**/
@Data
public class ResumeVo {
//基本信息
private ResumeBase base;
//社团信息
private Map<String, Object> clubs;
//教育经历
private Map<String, Object> edus;
//兴趣爱好
private Map<String, Object> hobbles;
//实习经历
private Map<String, Object> internships;
//项目经历
private Map<String, Object> projects;
//工作经验
private Map<String, Object> workExps;
private Map<String, Object> skill;
private Map<String, Object> advantage;
private Map<String, Object> awards;
private List<Map<String, Object>> custom = new ArrayList<>();
}
package com.bkty.system.domain.vo;
import org.dromara.common.core.annotation.JxgInitField;
import lombok.Data;
/**
* @author jiangxiaoge
* @description
* @data 2024/12/17
**/
@Data
public class ResumeWorkExp {
@JxgInitField("Content")
private String workContent;
/**公司名称*/
@JxgInitField("Corp")
private String corpName;
/**开始时间*/
@JxgInitField("StartTime")
private String startTime;
/**结束时间*/
@JxgInitField("EndTime")
private String endTime;
/**结束时间*/
@JxgInitField("Post")
private String postName;
/**工作成绩*/
@JxgInitField("Performance")
private String performance;
}
package com.bkty.system.dubbo;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bkty.system.api.RemoteUserService;
import com.bkty.system.api.domain.bo.RemoteUserBo;
import com.bkty.system.api.domain.vo.RemoteUserVo;
import com.bkty.system.api.model.LoginUser;
import com.bkty.system.domain.dto.SysUserDto;
import com.bkty.system.domain.entity.SysUser;
import com.bkty.system.domain.vo.SysUserVo;
import com.bkty.system.mapper.SysUserMapper;
import com.bkty.system.service.ISysPermissionService;
import com.bkty.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboService;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 用户服务
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
@DubboService
public class RemoteUserServiceImpl implements RemoteUserService {
private final ISysUserService userService;
private final SysUserMapper userMapper;
private final ISysPermissionService permissionService;
/**
* 通过用户名查询用户信息
*
* @param username 用户名
* @return 结果
*/
@Override
public LoginUser getUserInfo(String username) throws UserException {
System.out.println("进入查询用户方法");
SysUserVo sysUser = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(sysUser)) {
throw new UserException("user.not.exists", username);
}
if (UserStatus.DISABLE.getCode().equals(sysUser.getStatus())) {
throw new UserException("user.blocked", username);
}
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return buildLoginUser(sysUser);
}
/**
* 通过用户id查询用户信息
*
* @param userId 用户id
* @return 结果
*/
@Override
public LoginUser getUserInfo(Long userId) throws UserException {
SysUserVo sysUser = userMapper.selectVoById(userId);
if (ObjectUtil.isNull(sysUser)) {
throw new UserException("user.not.exists", "");
}
if (UserStatus.DISABLE.getCode().equals(sysUser.getStatus())) {
throw new UserException("user.blocked", sysUser.getUserName());
}
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return buildLoginUser(sysUser);
}
/**
* 通过手机号查询用户信息
*
* @param phonenumber 手机号
* @param tenantId 租户id
* @return 结果
*/
@Override
public LoginUser getUserInfoByPhonenumber(String phonenumber, String tenantId) throws UserException {
SysUserVo sysUser = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(sysUser)) {
throw new UserException("user.not.exists", phonenumber);
}
if (UserStatus.DISABLE.getCode().equals(sysUser.getStatus())) {
throw new UserException("user.blocked", phonenumber);
}
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return buildLoginUser(sysUser);
}
/**
* 通过邮箱查询用户信息
*
* @param email 邮箱
* @param tenantId 租户id
* @return 结果
*/
@Override
public LoginUser getUserInfoByEmail(String email, String tenantId) throws UserException {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
throw new UserException("user.not.exists", email);
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
throw new UserException("user.blocked", email);
}
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return buildLoginUser(user);
}
/**
* 注册用户信息
*
* @param remoteUserBo 用户信息
* @return 结果
*/
@Override
public Boolean registerUserInfo(RemoteUserBo remoteUserBo) throws UserException, ServiceException {
SysUserDto sysUserDto = MapstructUtils.convert(remoteUserBo, SysUserDto.class);
String username = sysUserDto.getUserName();
boolean exist = userMapper.exists(new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getUserName, sysUserDto.getUserName()));
if (exist) {
throw new UserException("user.register.save.error", username);
}
return userService.registerUser(sysUserDto, remoteUserBo.getTenantId());
}
/**
* 通过用户ID查询用户账户
*
* @param userId 用户ID
* @return 用户账户
*/
@Override
public String selectUserNameById(Long userId) {
return userService.selectUserNameById(userId);
}
/**
* 通过用户ID查询用户昵称
*
* @param userId 用户ID
* @return 用户昵称
*/
@Override
public String selectNicknameById(Long userId) {
return userService.selectNicknameById(userId);
}
/**
* 通过用户ID查询用户账户
*
* @param userIds 用户ID 多个用逗号隔开
* @return 用户账户
*/
@Override
public String selectNicknameByIds(String userIds) {
return userService.selectNicknameByIds(userIds);
}
/**
* 通过用户ID查询用户手机号
*
* @param userId 用户id
* @return 用户手机号
*/
@Override
public String selectPhonenumberById(Long userId) {
return userService.selectPhonenumberById(userId);
}
/**
* 通过用户ID查询用户邮箱
*
* @param userId 用户id
* @return 用户邮箱
*/
@Override
public String selectEmailById(Long userId) {
return userService.selectEmailById(userId);
}
/**
* 构建登录用户
*/
private LoginUser buildLoginUser(SysUserVo userVo) {
LoginUser loginUser = new LoginUser();
loginUser.setTenantId(userVo.getTenantId());
loginUser.setTenantName(userVo.getTenantName());
loginUser.setUserId(userVo.getUserId());
loginUser.setDeptId(userVo.getDeptId());
loginUser.setUsername(userVo.getUserName());
loginUser.setNickname(userVo.getNickName());
loginUser.setPassword(userVo.getPassword());
loginUser.setUserType(userVo.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(userVo.getUserId()));
loginUser.setRolePermission(permissionService.getRolePermission(userVo.getUserId()));
return loginUser;
}
/**
* 更新用户信息
*
* @param userId 用户ID
* @param ip IP地址
*/
@Override
public void recordLoginInfo(Long userId, String ip) {
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(ip);
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setUpdateBy(String.valueOf(userId));
DataPermissionHelper.ignore(() -> userMapper.updateById(sysUser));
}
/**
* 通过用户ID查询用户列表
*
* @param userIds 用户ids
* @return 用户列表
*/
@Override
public List<RemoteUserVo> selectListByIds(List<Long> userIds) {
List<SysUserVo> sysUserVos = userService.selectUserByIds(userIds, null);
return MapstructUtils.convert(sysUserVos, RemoteUserVo.class);
}
/**
* 通过角色ID查询用户ID
*
* @param roleIds 角色ids
* @return 用户ids
*/
@Override
public List<Long> selectUserIdsByRoleIds(List<Long> roleIds) {
return userService.selectUserIdsByRoleIds(roleIds);
}
}
package com.bkty.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bkty.system.domain.entity.ResumeTemplate;
import org.apache.ibatis.annotations.Mapper;
/**
* @author Herbert
* @description 简历模版表
* @data 2025/05/07
**/
@Mapper
public interface ResumeTemplateMapper extends BaseMapper<ResumeTemplate> {
}
package com.bkty.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bkty.system.domain.entity.ResumeTranslationRecord;
import org.apache.ibatis.annotations.Mapper;
/**
* @author Herbert
* @description 简历翻译记录表
* @data 2025/04/08
**/
@Mapper
public interface ResumeTranslationRecordMapper extends BaseMapper<ResumeTranslationRecord> {
}
package com.bkty.system.service.resume;
import com.bkty.system.domain.dto.ResumeListItemCache;
import com.bkty.system.domain.dto.ResumeMakeDto;
import com.bkty.system.domain.dto.ResumeModelOptimizationCache;
import com.bkty.system.domain.entity.FunctionResumeBase;
import com.bkty.system.domain.entity.FunctionResumeModel;
import com.bkty.system.domain.entity.FunctionResumeModelOptimization;
import com.bkty.system.domain.vo.ResumeBaseVo;
import com.bkty.system.domain.vo.ResumeModelVo;
import java.util.List;
import java.util.Map;
/**
* @author jiangxiaoge
* @description 简历缓存Service
* @data 2025/1/8
**/
public interface ResumeCacheService {
/**
* 根据用户的简历列表缓存
*/
void saveUserResumeListCache(FunctionResumeBase base, Long userId);
/**
* 查询用户简历信息缓存
* @param userId
* @return
*/
List<ResumeBaseVo> queryUserResumeCache(Long userId);
/**
* 删除简历基础信息缓存
* @param resumeId
* @param userId
*/
void delResumeCache(Long resumeId, Long userId);
/**
* 根据简历id保存简历模块列表缓存
* @param model
* @param resumeId
*/
void saveResumeModelListCache(FunctionResumeModel model, Long resumeId);
/**
* 查询简历模块信息缓存
* @param resumeId
* @return
*/
List<ResumeModelVo> queryResumeModelCache(Long resumeId);
/**
* 删除简历模块基础信息缓存
* @param modelId
* @param resumeId
*/
void delResumeModelCache(Long modelId, Long resumeId);
/**
* 保存简历模块具体数据
* @param modelId
* @param resumeId
* @param jsonData
*/
void saveResumeModelDataCache(Long modelId, Long resumeId, String jsonData);
/**
* 查询简历模块具体数据
* @param modelId
* @param resumeId
* @return
*/
String queryResumeModelDataCache(Long modelId, Long resumeId);
/**
* 删除简历模块具体数据
* @param modelId
* @param resumeId
*/
void delResumeModelDataCache(Long modelId, Long resumeId);
/**
* 修改模块排序缓存
* @param dto
*/
void updateModelSort(ResumeMakeDto dto);
/**
* 刷新模块优化缓存
* @param optimization
*/
void refreshModelOptimization(FunctionResumeModelOptimization optimization);
/**
* 查询缓存模块优化信息
* @param id
* @param resumeId
* @return
*/
Map<String, ResumeModelOptimizationCache> queryResumeModelOptimization(Long id, Long resumeId);
/**
* 查询简历列表缓存
* @param aLong
* @return
*/
List<ResumeListItemCache> queryResumeList(Long aLong);
/**
* 刷新简历列表缓存
* @param caches
* @param aLong
*/
void refreshResumeListCache(List<ResumeListItemCache> caches, Long aLong);
/**
* 简历缓存模版信息
*/
void saveResumeTemplateCache(Map<String,Object> templateMap, String resumeId);
void delResumeListCache(Long userId);
}
package com.bkty.system.service.resume.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson2.JSON;
import com.bkty.system.domain.dto.ResumeListItemCache;
import com.bkty.system.domain.dto.ResumeMakeDto;
import com.bkty.system.domain.dto.ResumeModelOptimizationCache;
import com.bkty.system.domain.entity.FunctionResumeBase;
import com.bkty.system.domain.entity.FunctionResumeModel;
import com.bkty.system.domain.entity.FunctionResumeModelOptimization;
import com.bkty.system.domain.vo.ResumeBaseVo;
import com.bkty.system.domain.vo.ResumeModelVo;
import com.bkty.system.service.resume.ResumeCacheService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.enums.ResumeEnum;
import org.dromara.common.core.utils.DateTimeWrapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author jiangxiaoge
* @description 简历缓存ServiceImpl
* @data 2025/1/8
**/
@Slf4j
@AllArgsConstructor
@Service
public class ResumeCacheServiceImpl implements ResumeCacheService {
private final RedisTemplate<String, String> redisTemplate;
/**简历基础信息列表key*/
private final static String REDIS_RESUME_LIST_KEY = "resume:msg:%s";
/**简历模块基础信息列表key*/
private final static String REDIS_RESUME_MODEL_LIST_KEY = "resume:model:msg:%s";
/**简历模块数据key*/
private final static String REDIS_RESUME_MODEL_DATA_KEY = "resume:model:data:%s:%s";
@Override
public void saveUserResumeListCache(FunctionResumeBase base, Long userId) {
ResumeBaseVo cache = new ResumeBaseVo();
DateTimeWrapper dateTimeWrapper = DateTimeWrapper.parseFromLocalDateTime(base.getUpdateTime().toInstant()
.atZone(ZoneId.of("Asia/Shanghai"))
.toLocalDateTime(), null);
cache.setResumeName(base.getResumeName());
cache.setResumeId(base.getId());
cache.setPlatformId(base.getPlatformId());
cache.setRunType(base.getRunType());
cache.setUpdateTime(dateTimeWrapper.toString());
String key = REDIS_RESUME_LIST_KEY.formatted(userId);
Set<String> resumeData = redisTemplate.opsForZSet().range(key, 0, -1);
if (CollectionUtil.isNotEmpty(resumeData)){
for (String resumeDatum : resumeData) {
ResumeBaseVo baseVo = JSON.parseObject(resumeDatum, ResumeBaseVo.class);
if (baseVo.getResumeId().equals(base.getId())){
redisTemplate.opsForZSet().remove(key, resumeDatum);
break;
}
}
redisTemplate.opsForZSet().add(key, JSON.toJSONString(cache), dateTimeWrapper.getDate().getTime());
}else {
redisTemplate.opsForZSet().add(key, JSON.toJSONString(cache), dateTimeWrapper.getDate().getTime());
}
}
@Override
public List<ResumeBaseVo> queryUserResumeCache(Long userId) {
String key = REDIS_RESUME_LIST_KEY.formatted(userId);
Set<String> resumeData = redisTemplate.opsForZSet().range(key, 0, -1);
if (CollectionUtil.isEmpty(resumeData) && resumeData != null){
List<ResumeBaseVo> voList = new ArrayList<>();
for (String resumeDatum : resumeData) {
ResumeBaseVo baseVo = JSON.parseObject(resumeDatum, ResumeBaseVo.class);
voList.add(baseVo);
}
return voList;
}
return List.of();
}
@Override
public void delResumeCache(Long resumeId, Long userId) {
String key = REDIS_RESUME_LIST_KEY.formatted(userId);
Set<String> resumeData = redisTemplate.opsForZSet().range(key, 0, -1);
if (CollectionUtil.isEmpty(resumeData) && resumeData != null){
for (String resumeDatum : resumeData) {
ResumeBaseVo baseVo = JSON.parseObject(resumeDatum, ResumeBaseVo.class);
if (baseVo.getResumeId().equals(resumeId)){
redisTemplate.opsForZSet().remove(key, resumeDatum);
break;
}
}
}
}
@Override
public void saveResumeModelListCache(FunctionResumeModel model, Long resumeId) {
String key = REDIS_RESUME_MODEL_LIST_KEY.formatted(resumeId);
redisTemplate.opsForHash().put(key, String.valueOf(model.getId()), JSON.toJSONString(model));
}
private ResumeModelVo initModelVo(FunctionResumeModel m) {
ResumeModelVo modelVo = new ResumeModelVo();
modelVo.setModelId(m.getId());
modelVo.setModelType(m.getModelType());
if (m.getIsShow() != null){
modelVo.setIsShow(m.getIsShow());
}
modelVo.setDataSort(m.getDataSort());
modelVo.setModelName(m.getModelName());
modelVo.setWebCode(ResumeEnum.ResumeModel.getWebCodeByCode(m.getModelType()));
return modelVo;
}
@Override
public List<ResumeModelVo> queryResumeModelCache(Long resumeId) {
String key = REDIS_RESUME_MODEL_LIST_KEY.formatted(resumeId);
List<Object> values = redisTemplate.opsForHash().values(key);
if (CollectionUtil.isNotEmpty(values)){
// 使用 fastjson 将 JSON 字符串转换成 ResumeModelVo 对象
List<FunctionResumeModel> collect = values.stream()
.map(value -> JSON.parseObject(value.toString(), FunctionResumeModel.class))
.toList();
List<ResumeModelVo> dataVoList = new ArrayList<>();
for (FunctionResumeModel model : collect) {
dataVoList.add(initModelVo(model));
}
return dataVoList;
}
return List.of();
}
@Override
public void delResumeModelCache(Long modelId, Long resumeId) {
String key = REDIS_RESUME_MODEL_LIST_KEY.formatted(resumeId);
redisTemplate.opsForHash().delete(key, String.valueOf(modelId));
}
@Override
public void saveResumeModelDataCache(Long modelId, Long resumeId, String jsonData) {
String key = REDIS_RESUME_MODEL_DATA_KEY.formatted(resumeId, modelId);
redisTemplate.opsForValue().set(key, jsonData);
}
@Override
public String queryResumeModelDataCache(Long modelId, Long resumeId) {
String key = REDIS_RESUME_MODEL_DATA_KEY.formatted(resumeId, modelId);
return redisTemplate.opsForValue().get(key);
}
@Override
public void delResumeModelDataCache(Long modelId, Long resumeId) {
String key = REDIS_RESUME_MODEL_DATA_KEY.formatted(resumeId, modelId);
redisTemplate.delete(key);
}
@Override
public void updateModelSort(ResumeMakeDto dto) {
String key = REDIS_RESUME_MODEL_LIST_KEY.formatted(dto.getResumeId());
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
if (MapUtil.isEmpty(entries)){
return;
}
if (CollectionUtil.isEmpty(dto.getModelSort())){
entries.forEach((k, v)->{
FunctionResumeModel modelVo = JSON.parseObject(v.toString(), FunctionResumeModel.class);
modelVo.setIsShow(1);
redisTemplate.opsForHash().put(key, k.toString(), JSON.toJSONString(modelVo));
});
}else {
Set<Long> modelIdSet = dto.getModelSort().keySet();
entries.forEach((k, v)->{
FunctionResumeModel modelVo = JSON.parseObject(v.toString(), FunctionResumeModel.class);
Long modelId = Long.valueOf(k.toString());
if (modelIdSet.contains(modelId)){
modelVo.setDataSort(dto.getModelSort().get(modelId));
modelVo.setIsShow(0);
} else {
modelVo.setIsShow(1);
}
redisTemplate.opsForHash().put(key, k.toString(), JSON.toJSONString(modelVo));
});
}
}
private final static String RESUME_MODEL_OPTIMIZATION_KEY = "resume:model:optimization:%s";
@Override
public void refreshModelOptimization(FunctionResumeModelOptimization optimization) {
String key = RESUME_MODEL_OPTIMIZATION_KEY.formatted(optimization.getResumeId());
//判断有没有这个key
if (Boolean.TRUE.equals(redisTemplate.hasKey(key))){
//hash类型删除这个元素
redisTemplate.opsForHash().delete(key, optimization.getModelCode());
}
//添加这个元素
ResumeModelOptimizationCache optimizationCache = new ResumeModelOptimizationCache();
optimizationCache.setRunStatus(optimization.getRunStatus());
optimizationCache.setOptimizationDesc(optimization.getOptimizationDesc());
optimizationCache.setUpdateTime(DateTimeWrapper.parseFromLocalDateTime(optimization.getUpdateTime().toInstant()
.atZone(ZoneId.of("Asia/Shanghai"))
.toLocalDateTime(), null).toString());
redisTemplate.opsForHash().put(key, optimization.getModelCode(), JSON.toJSONString(optimizationCache));
redisTemplate.expire(key, 30, TimeUnit.MINUTES);
}
@Override
public Map<String, ResumeModelOptimizationCache> queryResumeModelOptimization(Long id, Long resumeId) {
String key = RESUME_MODEL_OPTIMIZATION_KEY.formatted(resumeId);
if (Boolean.TRUE.equals(redisTemplate.hasKey(key))){
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
Map<String, ResumeModelOptimizationCache> result = new HashMap<>();
for (Map.Entry<Object, Object> entry : entries.entrySet()) {
String field = entry.getKey().toString();
ResumeModelOptimizationCache cache = JSON.parseObject(
entry.getValue().toString(),
ResumeModelOptimizationCache.class
);
result.put(field, cache);
}
return result;
}else {
return null;
}
}
private final static String REDIS_RESUME_LIST_KEY_V2 = "resume:list:v2:%s";
@Override
public List<ResumeListItemCache> queryResumeList(Long userId) {
String key = REDIS_RESUME_LIST_KEY_V2.formatted(userId);
if (Boolean.TRUE.equals(redisTemplate.hasKey(key))){
//过滤出resumeId不为空
return redisTemplate.opsForZSet().range(key, 0, -1).stream()
.filter(resumeData -> {
ResumeListItemCache resumeListItemCache = JSON.parseObject(resumeData, ResumeListItemCache.class);
return resumeListItemCache.getResumeId() != null;
}).map(resumeData -> JSON.parseObject(resumeData, ResumeListItemCache.class))
.collect(Collectors.toList());
} else {
//存入一个空数组
return null;
}
}
@Override
public void refreshResumeListCache(List<ResumeListItemCache> caches, Long userId) {
String key = REDIS_RESUME_LIST_KEY_V2.formatted(userId);
if (Boolean.TRUE.equals(redisTemplate.hasKey(key))){
redisTemplate.delete(key);
}
if (CollectionUtil.isNotEmpty(caches)){
for (ResumeListItemCache cache : caches) {
redisTemplate.opsForZSet().add(key, JSON.toJSONString(cache), DateTimeWrapper.parse(cache.getUpdateTime(), null).getDate().getTime());
redisTemplate.expire(key, 1, TimeUnit.DAYS);
}
}else {
redisTemplate.opsForZSet().add(key, JSON.toJSONString(new ResumeListItemCache()), System.currentTimeMillis());
redisTemplate.expire(key, 1, TimeUnit.DAYS);
}
}
private final static String REDIS_RESUME_TEMPLATE_KEY = "resumeTemplate:%s";
/**
* 简历缓存模版信息
*/
@Override
public void saveResumeTemplateCache(Map<String,Object> templateMap, String resumeId) {
String key = REDIS_RESUME_TEMPLATE_KEY.formatted(resumeId);
if (Boolean.TRUE.equals(redisTemplate.hasKey(key))){
redisTemplate.delete(key);
}
if (templateMap != null){
redisTemplate.opsForValue().set(key, JSON.toJSONString(templateMap));
}
}
@Override
public void delResumeListCache(Long userId) {
String key = REDIS_RESUME_LIST_KEY_V2.formatted(userId);
redisTemplate.delete(key);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment