This commit is contained in:
cxh 2024-12-28 16:52:27 +08:00
commit 5be1d3e19e
43 changed files with 1132 additions and 119 deletions

View File

@ -34,6 +34,7 @@ MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
# 本框架与RuoYi的功能差异

View File

@ -36,7 +36,7 @@
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.34</lombok.version>
<bouncycastle.version>1.76</bouncycastle.version>
<justauth.version>1.16.6</justauth.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>

View File

@ -14,6 +14,7 @@ import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
@ -60,6 +61,7 @@ public class SysLoginService {
private final ISysSocialService sysSocialService;
private final ISysRoleService roleService;
private final ISysDeptService deptService;
private final ISysPostService postService;
private final SysUserMapper userMapper;
@ -148,21 +150,24 @@ public class SysLoginService {
*/
public LoginUser buildLoginUser(SysUserVo user) {
LoginUser loginUser = new LoginUser();
Long userId = user.getUserId();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setUserId(userId);
loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName());
loginUser.setNickname(user.getNickName());
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
loginUser.setRolePermission(permissionService.getRolePermission(userId));
if (ObjectUtil.isNotNull(user.getDeptId())) {
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
}
List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
List<SysPostVo> posts = postService.selectPostsByUserId(userId);
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
return loginUser;
}

View File

@ -5,9 +5,17 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.XcxLoginBody;
import org.dromara.common.core.domain.model.XcxLoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
@ -39,12 +47,24 @@ public class XcxAuthStrategy implements IAuthStrategy {
// 多个小程序识别使用
String appid = loginBody.getAppid();
// todo 以下自行实现
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key openid
String openid = "";
AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
.clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥")
.ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
AuthCallback authCallback = new AuthCallback();
authCallback.setCode(xcxCode);
AuthResponse<AuthUser> resp = authRequest.login(authCallback);
String openid, unionId;
if (resp.ok()) {
AuthToken token = resp.getData().getToken();
openid = token.getOpenId();
// 微信小程序只有关联到微信开放平台下之后才能获取到 unionId因此unionId不一定能返回
unionId = token.getUnionId();
} else {
throw new ServiceException(resp.getMsg());
}
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
SysUserVo user = loadUserByOpenid(openid);
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setTenantId(user.getTenantId());

View File

@ -200,7 +200,7 @@ justauth:
redirect-uri: ${justauth.address}/social-callback?source=maxkey
topiam:
# topiam 服务器地址
server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
server-url: http://127.0.0.1:1898/api/v1/authorize/y0q************spq***********8ol
client-id: 449c4*********937************759
client-secret: ac7***********1e0************28d
redirect-uri: ${justauth.address}/social-callback?source=topiam

View File

@ -139,6 +139,8 @@ tenant:
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
# 自定义配置 是否全局开启逻辑删除 关闭后 所有逻辑删除功能将失效
enableLogicDelete: true
# 多包名使用 例如 org.dromara.**.mapper,org.xxx.**.mapper
mapperPackage: org.dromara.**.mapper
# 对应的 XML 文件位置

View File

@ -2,7 +2,7 @@
<configuration>
<property name="log.path" value="./logs"/>
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->

View File

@ -0,0 +1,37 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 部门
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class DeptDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 部门ID
*/
private Long deptId;
/**
* 父部门ID
*/
private Long parentId;
/**
* 部门名称
*/
private String deptName;
}

View File

@ -0,0 +1,46 @@
package org.dromara.common.core.domain.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 岗位
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class PostDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 岗位ID
*/
private Long postId;
/**
* 部门id
*/
private Long deptId;
/**
* 岗位编码
*/
private String postCode;
/**
* 岗位名称
*/
private String postName;
/**
* 岗位类别编码
*/
private String postCategory;
}

View File

@ -1,8 +1,9 @@
package org.dromara.common.core.domain.model;
import org.dromara.common.core.domain.dto.RoleDTO;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO;
import java.io.Serial;
import java.io.Serializable;
@ -111,6 +112,11 @@ public class LoginUser implements Serializable {
*/
private List<RoleDTO> roles;
/**
* 岗位对象
*/
private List<PostDTO> posts;
/**
* 数据权限 当前角色ID
*/

View File

@ -0,0 +1,146 @@
package org.dromara.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/*
* 日期格式
* "yyyy"4位数的年份例如2023年表示为"2023"
* "yy"2位数的年份例如2023年表示为"23"
* "MM"2位数的月份取值范围为01到12例如7月表示为"07"
* "M"不带前导零的月份取值范围为1到12例如7月表示为"7"
* "dd"2位数的日期取值范围为01到31例如22日表示为"22"
* "d"不带前导零的日期取值范围为1到31例如22日表示为"22"
* "EEEE"星期的全名例如星期三表示为"Wednesday"
* "E"星期的缩写例如星期三表示为"Wed"
* "DDD" "D"一年中的第几天取值范围为001到366例如第200天表示为"200"
* 时间格式
* "HH"24小时制的小时数取值范围为00到23例如下午5点表示为"17"
* "hh"12小时制的小时数取值范围为01到12例如下午5点表示为"05"
* "mm"分钟数取值范围为00到59例如30分钟表示为"30"
* "ss"秒数取值范围为00到59例如45秒表示为"45"
* "SSS"毫秒数取值范围为000到999例如123毫秒表示为"123"
*/
/**
* 日期格式与时间格式枚举
*/
@Getter
@AllArgsConstructor
public enum FormatsType {
/**
* 例如2023年表示为"23"
*/
YY("yy"),
/**
* 例如2023年表示为"2023"
*/
YYYY("yyyy"),
/**
* 例例如2023年7月可以表示为 "2023-07"
*/
YYYY_MM("yyyy-MM"),
/**
* 例如日期 "2023年7月22日" 可以表示为 "2023-07-22"
*/
YYYY_MM_DD("yyyy-MM-dd"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分"则可以表示为 "2023-07-22 15:30"
*/
YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分45秒"则可以表示为 "2023-07-22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
/**
* 例如下午3点30分45秒表示为 "15:30:45"
*/
HH_MM_SS("HH:mm:ss"),
/**
* 例例如2023年7月可以表示为 "2023/07"
*/
YYYY_MM_SLASH("yyyy/MM"),
/**
* 例如日期 "2023年7月22日" 可以表示为 "2023/07/22"
*/
YYYY_MM_DD_SLASH("yyyy/MM/dd"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分45秒"则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分45秒"则可以表示为 "2023/07/22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
/**
* 例例如2023年7月可以表示为 "2023.07"
*/
YYYY_MM_DOT("yyyy.MM"),
/**
* 例如日期 "2023年7月22日" 可以表示为 "2023.07.22"
*/
YYYY_MM_DD_DOT("yyyy.MM.dd"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分"则可以表示为 "2023.07.22 15:30"
*/
YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
/**
* 例如当前时间如果是 "2023年7月22日下午3点30分45秒"则可以表示为 "2023.07.22 15:30:45"
*/
YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
/**
* 例如2023年7月可以表示为 "202307"
*/
YYYYMM("yyyyMM"),
/**
* 例如2023年7月22日可以表示为 "20230722"
*/
YYYYMMDD("yyyyMMdd"),
/**
* 例如2023年7月22日下午3点可以表示为 "2023072215"
*/
YYYYMMDDHH("yyyyMMddHH"),
/**
* 例如2023年7月22日下午3点30分可以表示为 "202307221530"
*/
YYYYMMDDHHMM("yyyyMMddHHmm"),
/**
* 例如2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
*/
YYYYMMDDHHMMSS("yyyyMMddHHmmss");
/**
* 时间格式
*/
private final String timeFormat;
public static FormatsType getFormatsType(String str) {
for (FormatsType value : values()) {
if (StringUtils.contains(str, value.getTimeFormat())) {
return value;
}
}
throw new RuntimeException("'FormatsType' not found By " + str);
}
}

View File

@ -1,5 +1,9 @@
package org.dromara.common.core.service;
import org.dromara.common.core.domain.dto.DeptDTO;
import java.util.List;
/**
* 通用 部门服务
*
@ -15,4 +19,19 @@ public interface DeptService {
*/
String selectDeptNameByIds(String deptIds);
/**
* 根据部门ID查询部门负责人
*
* @param deptId 部门ID用于指定需要查询的部门
* @return 返回该部门的负责人ID
*/
Long selectDeptLeaderById(Long deptId);
/**
* 查询部门
*
* @return 部门列表
*/
List<DeptDTO> selectDeptsByList();
}

View File

@ -0,0 +1,10 @@
package org.dromara.common.core.service;
/**
* 通用 岗位服务
*
* @author AprilWind
*/
public interface PostService {
}

View File

@ -0,0 +1,10 @@
package org.dromara.common.core.service;
/**
* 通用 角色服务
*
* @author AprilWind
*/
public interface RoleService {
}

View File

@ -82,4 +82,13 @@ public interface UserService {
* @return 用户
*/
List<UserDTO> selectUsersByDeptIds(List<Long> deptIds);
/**
* 通过岗位ID查询用户
*
* @param postIds 岗位ids
* @return 用户
*/
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
}

View File

@ -3,16 +3,15 @@ package org.dromara.common.core.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.dromara.common.core.enums.FormatsType;
import org.dromara.common.core.exception.ServiceException;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 时间工具类
@ -21,86 +20,137 @@ import java.util.Date;
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
public static final String YYYY = "yyyy";
public static final String YYYY_MM = "yyyy-MM";
public static final String YYYY_MM_DD = "yyyy-MM-dd";
public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
private static final String[] PARSE_PATTERNS = {
"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
"yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
"yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
/**
* 获取当前Date型日期
* 获取当前日期和时间
*
* @return Date() 当前日期
* @return 当前日期和时间的 Date 对象表示
*/
public static Date getNowDate() {
return new Date();
}
/**
* 获取当前日期, 默认格式为yyyy-MM-dd
* 获取当前日期的字符串表示格式为YYYY-MM-DD
*
* @return String
* @return 当前日期的字符串表示
*/
public static String getDate() {
return dateTimeNow(YYYY_MM_DD);
return dateTimeNow(FormatsType.YYYY_MM_DD);
}
/**
* 获取当前日期的字符串表示格式为yyyyMMdd
*
* @return 当前日期的字符串表示
*/
public static String getCurrentDate() {
return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat());
}
/**
* 获取当前日期的路径格式字符串格式为"yyyy/MM/dd"
*
* @return 当前日期的路径格式字符串
*/
public static String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat());
}
/**
* 获取当前时间的字符串表示格式为YYYY-MM-DD HH:MM:SS
*
* @return 当前时间的字符串表示
*/
public static String getTime() {
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS);
}
/**
* 获取当前时间的字符串表示格式为 "HH:MM:SS"
*
* @return 当前时间的字符串表示格式为 "HH:MM:SS"
*/
public static String getTimeWithHourMinuteSecond() {
return dateTimeNow(FormatsType.HH_MM_SS);
}
/**
* 获取当前日期和时间的字符串表示格式为YYYYMMDDHHMMSS
*
* @return 当前日期和时间的字符串表示
*/
public static String dateTimeNow() {
return dateTimeNow(YYYYMMDDHHMMSS);
return dateTimeNow(FormatsType.YYYYMMDDHHMMSS);
}
public static String dateTimeNow(final String format) {
/**
* 获取当前日期和时间的指定格式的字符串表示
*
* @param format 日期时间格式例如"YYYY-MM-DD HH:MM:SS"
* @return 当前日期和时间的字符串表示
*/
public static String dateTimeNow(final FormatsType format) {
return parseDateToStr(format, new Date());
}
public static String dateTime(final Date date) {
return parseDateToStr(YYYY_MM_DD, date);
/**
* 将指定日期格式化为 YYYY-MM-DD 格式的字符串
*
* @param date 要格式化的日期对象
* @return 格式化后的日期字符串
*/
public static String formatDate(final Date date) {
return parseDateToStr(FormatsType.YYYY_MM_DD, date);
}
public static String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
/**
* 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串
*
* @param date 要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String formatDateTime(final Date date) {
return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date);
}
public static Date dateTime(final String format, final String ts) {
/**
* 将指定日期按照指定格式进行格式化
*
* @param format 要使用的日期时间格式例如"YYYY-MM-DD HH:MM:SS"
* @param date 要格式化的日期对象
* @return 格式化后的日期时间字符串
*/
public static String parseDateToStr(final FormatsType format, final Date date) {
return new SimpleDateFormat(format.getTimeFormat()).format(date);
}
/**
* 将指定格式的日期时间字符串转换为 Date 对象
*
* @param format 要解析的日期时间格式例如"YYYY-MM-DD HH:MM:SS"
* @param ts 要解析的日期时间字符串
* @return 解析后的 Date 对象
* @throws RuntimeException 如果解析过程中发生异常
*/
public static Date parseDateTime(final FormatsType format, final String ts) {
try {
return new SimpleDateFormat(format).parse(ts);
return new SimpleDateFormat(format.getTimeFormat()).parse(ts);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* 日期路径 即年// 如2018/08/08
*/
public static String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}
/**
* 日期路径 即年// 如20180808
*/
public static String dateTime() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}
/**
* 日期型字符串转化为日期 格式
* 将对象转换为日期对象
*
* @param str 要转换的对象通常是字符串
* @return 转换后的日期对象如果转换失败或输入为null则返回null
*/
public static Date parseDate(Object str) {
if (str == null) {
@ -115,6 +165,8 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
/**
* 获取服务器启动时间
*
* @return 服务器启动时间的 Date 对象表示
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
@ -122,35 +174,66 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
}
/**
* 计算相差天数
* 计算两个日期之间的天数差以毫秒为单位
*
* @param date1 第一个日期
* @param date2 第二个日期
* @return 两个日期之间的天数差的绝对值
*/
public static int differentDaysByMillisecond(Date date1, Date date2) {
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
}
/**
* 计算两个时间差
* 计算两个日期之间的时间差并以天小时和分钟的格式返回
*
* @param endDate 结束日期
* @param nowDate 当前日期
* @return 表示时间差的字符串格式为"天 小时 分钟"
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "" + hour + "小时" + min + "分钟";
long diffInMillis = endDate.getTime() - nowDate.getTime();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
return String.format("%d天 %d小时 %d分钟", day, hour, min);
}
/**
* 增加 LocalDateTime ==> Date
* 计算两个时间点的差值小时分钟当值为0时不显示该单位
*
* @param endDate 结束时间
* @param nowDate 当前时间
* @return 时间差字符串格式为 "x天 x小时 x分钟 x秒"若为 0 则不显示
*/
public static String getTimeDifference(Date endDate, Date nowDate) {
long diffInMillis = endDate.getTime() - nowDate.getTime();
long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
// 构建时间差字符串条件是值不为0才显示
StringBuilder result = new StringBuilder();
if (day > 0) {
result.append(String.format("%d天 ", day));
}
if (hour > 0) {
result.append(String.format("%d小时 ", hour));
}
if (min > 0) {
result.append(String.format("%d分钟 ", min));
}
if (sec > 0) {
result.append(String.format("%d秒", sec));
}
return result.length() > 0 ? result.toString().trim() : "0秒";
}
/**
* LocalDateTime 对象转换为 Date 对象
*
* @param temporalAccessor 要转换的 LocalDateTime 对象
* @return 转换后的 Date 对象
*/
public static Date toDate(LocalDateTime temporalAccessor) {
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
@ -158,11 +241,46 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
}
/**
* 增加 LocalDate ==> Date
* LocalDate 对象转换为 Date 对象
*
* @param temporalAccessor 要转换的 LocalDate 对象
* @return 转换后的 Date 对象
*/
public static Date toDate(LocalDate temporalAccessor) {
LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 校验日期范围
*
* @param startDate 开始日期
* @param endDate 结束日期
* @param maxValue 最大时间跨度的限制值
* @param unit 时间跨度的单位可选择 "DAYS""HOURS" "MINUTES"
*/
public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) {
// 校验结束日期不能早于开始日期
if (endDate.before(startDate)) {
throw new ServiceException("结束日期不能早于开始日期");
}
// 计算时间跨度
long diffInMillis = endDate.getTime() - startDate.getTime();
// 根据单位转换时间跨度
long diff = switch (unit) {
case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis);
case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis);
case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis);
default -> throw new IllegalArgumentException("不支持的时间单位");
};
// 校验时间跨度不超过最大限制
if (diff > maxValue) {
throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
}
}
}

View File

@ -15,7 +15,7 @@ public class SqlUtil {
/**
* 定义常用的 sql关键字
*/
public static String SQL_REGEX = "and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/**
* 仅支持字母数字下划线空格逗号小数点支持多个字段排序

View File

@ -0,0 +1,24 @@
package org.dromara.common.excel.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 批注
* @author guzhouyanyu
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelNotation {
/**
* col index
*/
int index() default -1;
/**
* 批注内容
*/
String value() default "";
}

View File

@ -0,0 +1,26 @@
package org.dromara.common.excel.annotation;
import org.apache.poi.ss.usermodel.IndexedColors;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 是否必填
* @author guzhouyanyu
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelRequired {
/**
* col index
*/
int index() default -1;
/**
* 字体颜色
*/
IndexedColors fontColor() default IndexedColors.RED;
}

View File

@ -0,0 +1,135 @@
package org.dromara.common.excel.handler;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.metadata.data.DataFormatData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.StyleUtil;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* 批注必填
*
* @author guzhouyanyu
*/
public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
/**
* 批注
*/
private final Map<Integer, String> notationMap;
/**
* 头列字体颜色
*/
private final Map<Integer, Short> headColumnMap;
public DataWriteHandler(Class<?> clazz) {
notationMap = getNotationMap(clazz);
headColumnMap = getRequiredMap(clazz);
}
@Override
public void afterCellDispose(CellWriteHandlerContext context) {
if (CollUtil.isEmpty(notationMap) && CollUtil.isEmpty(headColumnMap)) {
return;
}
WriteCellData<?> cellData = context.getFirstCellData();
WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
DataFormatData dataFormatData = new DataFormatData();
// 单元格设置为文本格式
dataFormatData.setIndex((short) 49);
writeCellStyle.setDataFormatData(dataFormatData);
if (context.getHead()) {
Cell cell = context.getCell();
WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
Sheet sheet = writeSheetHolder.getSheet();
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
Drawing<?> drawing = sheet.createDrawingPatriarch();
// 设置标题字体样式
WriteFont headWriteFont = new WriteFont();
// 加粗
headWriteFont.setBold(true);
if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getColumnIndex())) {
// 设置字体颜色
headWriteFont.setColor(headColumnMap.get(cell.getColumnIndex()));
}
writeCellStyle.setWriteFont(headWriteFont);
CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
cell.setCellStyle(cellStyle);
if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getColumnIndex())) {
// 批注内容
String notationContext = notationMap.get(cell.getColumnIndex());
// 创建绘图对象
Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
comment.setString(new XSSFRichTextString(notationContext));
cell.setCellComment(comment);
}
}
}
/**
* 获取必填列
*/
private static Map<Integer, Short> getRequiredMap(Class<?> clazz) {
Map<Integer, Short> requiredMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
// 检查 fields 数组是否为空
if (fields.length == 0) {
return requiredMap;
}
Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
for (int i = 0; i < filteredFields.length; i++) {
Field field = filteredFields[i];
if (!field.isAnnotationPresent(ExcelRequired.class)) {
continue;
}
ExcelRequired excelRequired = field.getAnnotation(ExcelRequired.class);
int columnIndex = excelRequired.index() == -1 ? i : excelRequired.index();
requiredMap.put(columnIndex, excelRequired.fontColor().getIndex());
}
return requiredMap;
}
/**
* 获取批注
*/
private static Map<Integer, String> getNotationMap(Class<?> clazz) {
Map<Integer, String> notationMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
// 检查 fields 数组是否为空
if (fields.length == 0) {
return notationMap;
}
Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
for (int i = 0; i < filteredFields.length; i++) {
Field field = filteredFields[i];
if (!field.isAnnotationPresent(ExcelNotation.class)) {
continue;
}
ExcelNotation excelNotation = field.getAnnotation(ExcelNotation.class);
int columnIndex = excelNotation.index() == -1 ? i : excelNotation.index();
notationMap.put(columnIndex, excelNotation.value());
}
return notationMap;
}
}

View File

@ -18,6 +18,7 @@ import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
import org.dromara.common.excel.core.*;
import org.dromara.common.excel.handler.DataWriteHandler;
import java.io.IOException;
import java.io.InputStream;
@ -191,6 +192,7 @@ public class ExcelUtil {
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.registerWriteHandler(new DataWriteHandler(clazz))
.sheet(sheetName);
if (merge) {
// 合并处理器
@ -211,7 +213,7 @@ public class ExcelUtil {
* @param data 模板需要的数据
* @param response 响应体
*/
public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
try {
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
@ -230,20 +232,21 @@ public class ExcelUtil {
* @param data 模板需要的数据
* @param os 输出流
*/
public static void exportTemplate(List<Object> data, String templatePath, OutputStream os) {
public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
// 单表多数据导出 模板格式为 {.属性}
for (Object d : data) {
for (T d : data) {
excelWriter.fill(d, writeSheet);
}
excelWriter.finish();
@ -299,6 +302,9 @@ public class ExcelUtil {
* @param os 输出流
*/
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
@ -307,9 +313,6 @@ public class ExcelUtil {
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@ -333,6 +336,9 @@ public class ExcelUtil {
* @param os 输出流
*/
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
@ -340,9 +346,6 @@ public class ExcelUtil {
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
for (int i = 0; i < data.size(); i++) {
WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
for (Map.Entry<String, Object> map : data.get(i).entrySet()) {

View File

@ -32,7 +32,7 @@ public class BigNumberSerializer extends NumberSerializer {
@Override
public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// 超出范围 序列化字符串
// 超出范围 序列化字符串
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
super.serialize(value, gen, provider);
} else {

View File

@ -2,6 +2,7 @@ package org.dromara.common.mybatis.config;
import cn.hutool.core.net.NetUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@ -13,6 +14,7 @@ import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.aspect.DataPermissionAspect;
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
@ -105,6 +107,14 @@ public class MybatisPlusConfig {
return new MybatisExceptionHandler();
}
/**
* 初始化表对象处理器
*/
@Bean
public PostInitTableInfoHandler postInitTableInfoHandler() {
return new PlusPostInitTableInfoHandler();
}
/**
* PaginationInnerInterceptor 分页插件自动识别数据库类型
* https://baomidou.com/pages/97710a/

View File

@ -119,4 +119,9 @@ public class PageQuery implements Serializable {
return (pageNum - 1) * pageSize;
}
public PageQuery(Integer pageSize, Integer pageNum) {
this.pageSize = pageSize;
this.pageNum = pageNum;
}
}

View File

@ -3,6 +3,7 @@ package org.dromara.common.mybatis.handler;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
@ -28,9 +29,7 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.*;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@ -130,7 +129,9 @@ public class PlusDataPermissionHandler {
joinStr = " " + dataPermission.joinStr() + " ";
}
LoginUser user = DataPermissionHelper.getVariable("user");
StandardEvaluationContext context = new StandardEvaluationContext();
Object defaultValue = "-1";
NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue);
context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue));
context.setBeanResolver(beanResolver);
DataPermissionHelper.getContext().forEach(context::setVariable);
Set<String> conditions = new HashSet<>();
@ -257,4 +258,64 @@ public class PlusDataPermissionHandler {
return getDataPermission(mapperId) == null;
}
/**
* 对所有null变量找不到的变量返回默认值
*/
@AllArgsConstructor
private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext {
private final Object defaultValue;
@Override
public Object lookupVariable(String name) {
Object obj = super.lookupVariable(name);
// 如果读取到的值是 null则返回默认值
if (obj == null) {
return defaultValue;
}
return obj;
}
}
/**
* 对所有null变量找不到的变量返回默认值 委托模式 将不需要处理的方法委托给原处理器
*/
@AllArgsConstructor
private static class NullSafePropertyAccessor implements PropertyAccessor {
private final PropertyAccessor delegate;
private final Object defaultValue;
@Override
public Class<?>[] getSpecificTargetClasses() {
return delegate.getSpecificTargetClasses();
}
@Override
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
return delegate.canRead(context, target, name);
}
@Override
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
TypedValue value = delegate.read(context, target, name);
// 如果读取到的值是 null则返回默认值
if (value.getValue() == null) {
return new TypedValue(defaultValue);
}
return value;
}
@Override
public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
return delegate.canWrite(context, target, name);
}
@Override
public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
delegate.write(context, target, name, newValue);
}
}
}

View File

@ -0,0 +1,27 @@
package org.dromara.common.mybatis.handler;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import org.apache.ibatis.session.Configuration;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
/**
* 修改表信息初始化方式
* 目前用于全局修改是否使用逻辑删除
*
* @author Lion Li
*/
public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler {
@Override
public void postTableInfo(TableInfo tableInfo, Configuration configuration) {
String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true");
// 只有关闭时 统一设置false 为true时mp自动判断不处理
if (!Convert.toBool(flag)) {
ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false);
}
}
}

View File

@ -0,0 +1,165 @@
package org.dromara.common.redis.utils;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.RIdGenerator;
import org.redisson.api.RedissonClient;
import java.time.Duration;
/**
* 发号器工具类
*
* @author 秋辞未寒
* @date 2024-12-10
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SequenceUtils {
/**
* 默认初始值
*/
public static final Long DEFAULT_INIT_VALUE = 1L;
/**
* 默认步长
*/
public static final Long DEFAULT_STEP_VALUE = 1L;
/**
* 默认过期时间-
*/
public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
/**
* 默认过期时间-分钟
*/
public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
/**
* 获取Redisson客户端实例
*/
private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
/**
* 获取ID生成器
*
* @param key 业务key
* @param expireTime 过期时间
* @param initValue ID初始值
* @param stepValue ID步长
* @return ID生成器
*/
private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
if (initValue == null || initValue <= 0) {
initValue = DEFAULT_INIT_VALUE;
}
if (stepValue == null || stepValue <= 0) {
stepValue = DEFAULT_STEP_VALUE;
}
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
// 设置过期时间
idGenerator.expire(expireTime);
// 设置初始值和步长
idGenerator.tryInit(initValue, stepValue);
return idGenerator;
}
/**
* 获取指定业务key的唯一id
*
* @param key 业务key
* @param expireTime 过期时间
* @param initValue ID初始值
* @param stepValue ID步长
* @return 唯一id
*/
public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
}
/**
* 获取指定业务key的唯一id字符串
*
* @param key 业务key
* @param expireTime 过期时间
* @param initValue ID初始值
* @param stepValue ID步长
* @return 唯一id
*/
public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
return String.valueOf(nextId(key, expireTime, initValue, stepValue));
}
/**
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
*
* @param key 业务key
* @param expireTime 过期时间
* @return 唯一id
*/
public static long nextId(String key, Duration expireTime) {
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
}
/**
* 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
*
* @param key 业务key
* @param expireTime 过期时间
* @return 唯一id
*/
public static String nextIdStr(String key, Duration expireTime) {
return String.valueOf(nextId(key, expireTime));
}
/**
* 获取 yyyyMMdd 开头的唯一id
*
* @return 唯一id
*/
public static String nextIdDate() {
return nextIdDate("");
}
/**
* 获取 prefix + yyyyMMdd 开头的唯一id
*
* @param prefix 业务前缀
* @return 唯一id
*/
public static String nextIdDate(String prefix) {
// 前缀+日期 构建 prefixKey
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
// 获取下一个id
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
// 返回完整id
return StringUtils.format("{}{}", prefixKey, nextId);
}
/**
* 获取 yyyyMMddHHmmss 开头的唯一id
*
* @return 唯一id
*/
public static String nextIdDateTime() {
return nextIdDateTime("");
}
/**
* 获取 prefix + yyyyMMddHHmmss 开头的唯一id
*
* @param prefix 业务前缀
* @return 唯一id
*/
public static String nextIdDateTime(String prefix) {
// 前缀+日期时间 构建 prefixKey
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
// 获取下一个id
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
// 返回完整id
return StringUtils.format("{}{}", prefixKey, nextId);
}
}

View File

@ -88,6 +88,13 @@ public class LoginHelper {
return Convert.toLong(getExtra(USER_KEY));
}
/**
* 获取用户id
*/
public static String getUserIdStr() {
return Convert.toStr(getExtra(USER_KEY));
}
/**
* 获取用户账户
*/

View File

@ -80,12 +80,12 @@ public enum SensitiveStrategy {
FIRST_MASK(DesensitizedUtil::firstMask),
/**
* 清空为null
* 清空为""
*/
CLEAR(s -> DesensitizedUtil.clear()),
/**
* 清空为""
* 清空为null
*/
CLEAR_TO_NULL(s -> DesensitizedUtil.clearToNull());

View File

@ -30,7 +30,7 @@ public class AuthMaxKeyRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String body = doPostAuthorizationCode(authCallback.getCode());
Dict object = JsonUtils.parseMap(body);
// oauth/token 验证异常
@ -51,7 +51,7 @@ public class AuthMaxKeyRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String body = doGetUserInfo(authToken);
Dict object = JsonUtils.parseMap(body);
// oauth/token 验证异常

View File

@ -1,7 +1,10 @@
package org.dromara.common.social.topiam;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.xkcoding.http.support.HttpHeader;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.cache.AuthStateCache;
@ -41,7 +44,7 @@ public class AuthTopIamRequest extends AuthDefaultRequest {
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
public AuthToken getAccessToken(AuthCallback authCallback) {
String body = doPostAuthorizationCode(authCallback.getCode());
Dict object = JsonUtils.parseMap(body);
checkResponse(object);
@ -55,7 +58,7 @@ public class AuthTopIamRequest extends AuthDefaultRequest {
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
public AuthUser getUserInfo(AuthToken authToken) {
String body = doGetUserInfo(authToken);
Dict object = JsonUtils.parseMap(body);
checkResponse(object);
@ -70,6 +73,16 @@ public class AuthTopIamRequest extends AuthDefaultRequest {
.build();
}
@Override
protected String doPostAuthorizationCode(String code) {
HttpRequest request = HttpRequest.post(source.accessToken())
.header("Authorization", "Basic " + Base64.encode("%s:%s".formatted(config.getClientId(), config.getClientSecret())))
.form("grant_type", "authorization_code")
.form("code", code)
.form("redirect_uri", config.getRedirectUri());
HttpResponse response = request.execute();
return response.body();
}
@Override
protected String doGetUserInfo(AuthToken authToken) {
@ -86,7 +99,7 @@ public class AuthTopIamRequest extends AuthDefaultRequest {
.build();
}
public static void checkResponse(Dict object) {
private static void checkResponse(Dict object) {
// oauth/token 验证异常
if (object.containsKey("error")) {
throw new AuthException(object.getStr("error_description"));

View File

@ -1,5 +1,6 @@
package org.dromara.common.sse.core;
import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.sse.dto.SseMessageDto;
@ -65,7 +66,7 @@ public class SseEmitterManager {
*/
public void disconnect(Long userId, String token) {
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
if (emitters != null) {
if (MapUtil.isNotEmpty(emitters)) {
try {
SseEmitter sseEmitter = emitters.get(token);
sseEmitter.send(SseEmitter.event().comment("disconnected"));
@ -73,6 +74,8 @@ public class SseEmitterManager {
} catch (Exception ignore) {
}
emitters.remove(token);
} else {
USER_TOKEN_EMITTERS.remove(userId);
}
}
@ -93,7 +96,7 @@ public class SseEmitterManager {
*/
public void sendMessage(Long userId, String message) {
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
if (emitters != null) {
if (MapUtil.isNotEmpty(emitters)) {
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
try {
entry.getValue().send(SseEmitter.event()
@ -103,6 +106,8 @@ public class SseEmitterManager {
emitters.remove(entry.getKey());
}
}
} else {
USER_TOKEN_EMITTERS.remove(userId);
}
}

View File

@ -16,7 +16,14 @@ import org.dromara.common.sse.dto.SseMessageDto;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SseMessageUtils {
private final static SseEmitterManager MANAGER = SpringUtils.getBean(SseEmitterManager.class);
private final static Boolean SSE_ENABLE = SpringUtils.getProperty("sse.enabled", Boolean.class, true);
private static SseEmitterManager MANAGER;
static {
if (isEnable() && MANAGER == null) {
MANAGER = SpringUtils.getBean(SseEmitterManager.class);
}
}
/**
* 向指定的WebSocket会话发送消息
@ -25,6 +32,9 @@ public class SseMessageUtils {
* @param message 要发送的消息内容
*/
public static void sendMessage(Long userId, String message) {
if (!isEnable()) {
return;
}
MANAGER.sendMessage(userId, message);
}
@ -34,6 +44,9 @@ public class SseMessageUtils {
* @param message 要发送的消息内容
*/
public static void sendMessage(String message) {
if (!isEnable()) {
return;
}
MANAGER.sendMessage(message);
}
@ -43,6 +56,9 @@ public class SseMessageUtils {
* @param sseMessageDto 要发布的SSE消息对象
*/
public static void publishMessage(SseMessageDto sseMessageDto) {
if (!isEnable()) {
return;
}
MANAGER.publishMessage(sseMessageDto);
}
@ -52,7 +68,17 @@ public class SseMessageUtils {
* @param message 要发布的消息内容
*/
public static void publishAll(String message) {
if (!isEnable()) {
return;
}
MANAGER.publishAll(message);
}
/**
* 是否开启
*/
public static Boolean isEnable() {
return SSE_ENABLE;
}
}

View File

@ -4,7 +4,7 @@
<contextName>logback</contextName>
<property name="log.path" value="./logs/ruoyi-monitor-admin"/>
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
@ -31,4 +31,4 @@
<appender-ref ref="file"/>
</root>
</configuration>
</configuration>

View File

@ -2,7 +2,7 @@
<configuration>
<property name="log.path" value="./logs/ruoyi-snailjob-server" />
<property name="console.log.pattern"
value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>

View File

@ -2,6 +2,8 @@ package org.dromara.demo.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.demo.domain.TestDemo;
@ -36,30 +38,35 @@ public class TestDemoVo implements Serializable {
/**
* 部门id
*/
@ExcelRequired
@ExcelProperty(value = "部门id")
private Long deptId;
/**
* 用户id
*/
@ExcelRequired
@ExcelProperty(value = "用户id")
private Long userId;
/**
* 排序号
*/
@ExcelRequired
@ExcelProperty(value = "排序号")
private Integer orderNum;
/**
* key键
*/
@ExcelNotation(value = "测试key")
@ExcelProperty(value = "key键")
private String testKey;
/**
*
*/
@ExcelNotation(value = "测试value")
@ExcelProperty(value = "")
private String value;

View File

@ -98,8 +98,8 @@ public class SysProfileController extends BaseController {
if (BCrypt.checkpw(bo.getNewPassword(), password)) {
return R.fail("新密码不能与旧密码相同");
}
if (userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(bo.getNewPassword())) > 0) {
int rows = DataPermissionHelper.ignore(() -> userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(bo.getNewPassword())));
if (rows > 0) {
return R.ok();
}
return R.fail("修改密码异常,请联系管理员");
@ -121,7 +121,8 @@ public class SysProfileController extends BaseController {
}
SysOssVo oss = ossService.upload(avatarfile);
String avatar = oss.getUrl();
if (userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId())) {
boolean updateSuccess = DataPermissionHelper.ignore(() -> userService.updateUserAvatar(LoginHelper.getUserId(), oss.getOssId()));
if (updateSuccess) {
AvatarVo avatarVo = new AvatarVo();
avatarVo.setImgUrl(avatar);
return R.ok(avatarVo);

View File

@ -25,6 +25,14 @@ public interface ISysPostService {
*/
List<SysPostVo> selectPostList(SysPostBo post);
/**
* 查询用户所属岗位组
*
* @param userId 用户ID
* @return 岗位ID
*/
List<SysPostVo> selectPostsByUserId(Long userId);
/**
* 查询所有岗位
*

View File

@ -38,7 +38,7 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
* @param roleId 角色Id
* @return 部门Id组
*/
@Cacheable(cacheNames = CacheNames.SYS_ROLE_CUSTOM, key = "#roleId")
@Cacheable(cacheNames = CacheNames.SYS_ROLE_CUSTOM, key = "#roleId", condition = "#roleId != null")
@Override
public String getRoleCustom(Long roleId) {
if (ObjectUtil.isNull(roleId)) {
@ -60,7 +60,7 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
* @param deptId 部门Id
* @return 部门Id组
*/
@Cacheable(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, key = "#deptId")
@Cacheable(cacheNames = CacheNames.SYS_DEPT_AND_CHILD, key = "#deptId", condition = "#deptId != null")
@Override
public String getDeptAndChild(Long deptId) {
if (ObjectUtil.isNull(deptId)) {

View File

@ -1,5 +1,6 @@
package org.dromara.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.tree.Tree;
@ -10,6 +11,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.dto.DeptDTO;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.DeptService;
import org.dromara.common.core.utils.*;
@ -67,8 +69,6 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
*/
@Override
public List<Tree<Long>> selectDeptTreeList(SysDeptBo bo) {
// 只查询未禁用部门
bo.setStatus(SystemConstants.NORMAL);
LambdaQueryWrapper<SysDept> lqw = buildQueryWrapper(bo);
List<SysDeptVo> depts = baseMapper.selectDeptList(lqw);
return buildDeptTreeSelect(depts);
@ -176,6 +176,31 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
return String.join(StringUtils.SEPARATOR, list);
}
/**
* 根据部门ID查询部门负责人
*
* @param deptId 部门ID用于指定需要查询的部门
* @return 返回该部门的负责人ID
*/
@Override
public Long selectDeptLeaderById(Long deptId) {
SysDeptVo vo = SpringUtils.getAopProxy(this).selectDeptById(deptId);
return vo.getLeader();
}
/**
* 查询部门
*
* @return 部门列表
*/
@Override
public List<DeptDTO> selectDeptsByList() {
List<SysDeptVo> list = baseMapper.selectDeptList(new LambdaQueryWrapper<SysDept>()
.select(SysDept::getDeptId, SysDept::getDeptName, SysDept::getParentId)
.eq(SysDept::getStatus, SystemConstants.NORMAL));
return BeanUtil.copyToList(list, DeptDTO.class);
}
/**
* 根据ID查询所有子部门数正常状态
*

View File

@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.PostService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
@ -34,7 +35,7 @@ import java.util.List;
*/
@RequiredArgsConstructor
@Service
public class SysPostServiceImpl implements ISysPostService {
public class SysPostServiceImpl implements ISysPostService, PostService {
private final SysPostMapper baseMapper;
private final SysDeptMapper deptMapper;
@ -57,6 +58,17 @@ public class SysPostServiceImpl implements ISysPostService {
return baseMapper.selectVoList(buildQueryWrapper(post));
}
/**
* 查询用户所属岗位组
*
* @param userId 用户ID
* @return 岗位ID
*/
@Override
public List<SysPostVo> selectPostsByUserId(Long userId) {
return baseMapper.selectPostsByUserId(userId);
}
/**
* 根据查询条件构建查询包装器
*

View File

@ -17,6 +17,7 @@ import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.RoleService;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
@ -47,7 +48,7 @@ import java.util.*;
*/
@RequiredArgsConstructor
@Service
public class SysRoleServiceImpl implements ISysRoleService {
public class SysRoleServiceImpl implements ISysRoleService, RoleService {
private final SysRoleMapper baseMapper;
private final SysRoleMenuMapper roleMenuMapper;
@ -351,7 +352,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
private int insertRoleMenu(SysRoleBo role) {
int rows = 1;
// 新增用户与角色管理
List<SysRoleMenu> list = new ArrayList<SysRoleMenu>();
List<SysRoleMenu> list = new ArrayList<>();
for (Long menuId : role.getMenuIds()) {
SysRoleMenu rm = new SysRoleMenu();
rm.setRoleId(role.getRoleId());
@ -372,7 +373,7 @@ public class SysRoleServiceImpl implements ISysRoleService {
private int insertRoleDept(SysRoleBo role) {
int rows = 1;
// 新增角色与部门数据权限管理
List<SysRoleDept> list = new ArrayList<SysRoleDept>();
List<SysRoleDept> list = new ArrayList<>();
for (Long deptId : role.getDeptIds()) {
SysRoleDept rd = new SysRoleDept();
rd.setRoleId(role.getRoleId());

View File

@ -696,4 +696,27 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
.in(SysUser::getDeptId, deptIds));
return BeanUtil.copyToList(list, UserDTO.class);
}
/**
* 通过岗位ID查询用户
*
* @param postIds 岗位ids
* @return 用户
*/
@Override
public List<UserDTO> selectUsersByPostIds(List<Long> postIds) {
if (CollUtil.isEmpty(postIds)) {
return List.of();
}
// 通过岗位ID获取用户岗位信息
List<SysUserPost> userPosts = userPostMapper.selectList(
new LambdaQueryWrapper<SysUserPost>().in(SysUserPost::getPostId, postIds));
// 获取用户ID列表
Set<Long> userIds = StreamUtils.toSet(userPosts, SysUserPost::getUserId);
return selectListByIds(new ArrayList<>(userIds));
}
}