Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/yudao-cloud into master-jdk25

# Conflicts:
#	yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperX.java
This commit is contained in:
YunaiV
2026-08-30 19:43:03 +08:00
79 changed files with 1191 additions and 385 deletions
+1
View File
@@ -716,6 +716,7 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (117, 11, '支付宝 Wap 网站支付', 'alipay_wap', 'pay_channel_code', 0, 'primary', '', '支付宝 Wap 网站支付', '1', '2021-12-03 10:42:26', '1', '2023-07-19 20:09:16', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (118, 12, '支付宝 App 支付', 'alipay_app', 'pay_channel_code', 0, 'primary', '', '支付宝 App 支付', '1', '2021-12-03 10:42:55', '1', '2023-07-19 20:09:20', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (119, 14, '支付宝扫码支付', 'alipay_qr', 'pay_channel_code', 0, 'primary', '', '支付宝扫码支付', '1', '2021-12-03 10:43:10', '1', '2023-07-19 20:09:28', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3449, 15, '支付宝小程序支付', 'alipay_lite', 'pay_channel_code', 0, 'primary', '', '支付宝小程序支付', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (120, 10, '通知成功', '10', 'pay_notify_status', 0, 'success', '', '通知成功', '1', '2021-12-03 11:02:41', '1', '2023-07-19 10:08:19', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (121, 20, '通知失败', '20', 'pay_notify_status', 0, 'danger', '', '通知失败', '1', '2021-12-03 11:02:59', '1', '2023-07-19 10:08:21', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (122, 0, '等待通知', '0', 'pay_notify_status', 0, 'info', '', '未通知', '1', '2021-12-03 11:03:10', '1', '2023-07-19 10:08:24', '0');
+1 -1
View File
@@ -817,6 +817,7 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (117, 11, '支付宝 Wap 网站支付', 'alipay_wap', 'pay_channel_code', 0, 'primary', '', '支付宝 Wap 网站支付', '1', '2021-12-03 10:42:26', '1', '2023-07-19 20:09:16', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (118, 12, '支付宝 App 支付', 'alipay_app', 'pay_channel_code', 0, 'primary', '', '支付宝 App 支付', '1', '2021-12-03 10:42:55', '1', '2023-07-19 20:09:20', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (119, 14, '支付宝扫码支付', 'alipay_qr', 'pay_channel_code', 0, 'primary', '', '支付宝扫码支付', '1', '2021-12-03 10:43:10', '1', '2023-07-19 20:09:28', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3449, 15, '支付宝小程序支付', 'alipay_lite', 'pay_channel_code', 0, 'primary', '', '支付宝小程序支付', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (120, 10, '通知成功', '10', 'pay_notify_status', 0, 'success', '', '通知成功', '1', '2021-12-03 11:02:41', '1', '2023-07-19 10:08:19', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (121, 20, '通知失败', '20', 'pay_notify_status', 0, 'danger', '', '通知失败', '1', '2021-12-03 11:02:59', '1', '2023-07-19 10:08:21', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (122, 0, '等待通知', '0', 'pay_notify_status', 0, 'info', '', '未通知', '1', '2021-12-03 11:03:10', '1', '2023-07-19 10:08:24', '0');
@@ -6195,4 +6196,3 @@ COMMIT;
DROP SEQUENCE IF EXISTS yudao_demo03_student_seq;
CREATE SEQUENCE yudao_demo03_student_seq
START 10;
+2 -1
View File
@@ -817,6 +817,7 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (117, 11, '支付宝 Wap 网站支付', 'alipay_wap', 'pay_channel_code', 0, 'primary', '', '支付宝 Wap 网站支付', '1', '2021-12-03 10:42:26', '1', '2023-07-19 20:09:16', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (118, 12, '支付宝 App 支付', 'alipay_app', 'pay_channel_code', 0, 'primary', '', '支付宝 App 支付', '1', '2021-12-03 10:42:55', '1', '2023-07-19 20:09:20', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (119, 14, '支付宝扫码支付', 'alipay_qr', 'pay_channel_code', 0, 'primary', '', '支付宝扫码支付', '1', '2021-12-03 10:43:10', '1', '2023-07-19 20:09:28', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3449, 15, '支付宝小程序支付', 'alipay_lite', 'pay_channel_code', 0, 'primary', '', '支付宝小程序支付', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (120, 10, '通知成功', '10', 'pay_notify_status', 0, 'success', '', '通知成功', '1', '2021-12-03 11:02:41', '1', '2023-07-19 10:08:19', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (121, 20, '通知失败', '20', 'pay_notify_status', 0, 'danger', '', '通知失败', '1', '2021-12-03 11:02:59', '1', '2023-07-19 10:08:21', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (122, 0, '等待通知', '0', 'pay_notify_status', 0, 'info', '', '未通知', '1', '2021-12-03 11:03:10', '1', '2023-07-19 10:08:24', '0');
@@ -1663,7 +1664,7 @@ COMMIT;
DROP SEQUENCE IF EXISTS system_dict_data_seq;
CREATE SEQUENCE system_dict_data_seq
START 3449;
START 3450;
-- ----------------------------
-- Table structure for system_dict_type
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -817,6 +817,7 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (117, 11, '支付宝 Wap 网站支付', 'alipay_wap', 'pay_channel_code', 0, 'primary', '', '支付宝 Wap 网站支付', '1', '2021-12-03 10:42:26', '1', '2023-07-19 20:09:16', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (118, 12, '支付宝 App 支付', 'alipay_app', 'pay_channel_code', 0, 'primary', '', '支付宝 App 支付', '1', '2021-12-03 10:42:55', '1', '2023-07-19 20:09:20', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (119, 14, '支付宝扫码支付', 'alipay_qr', 'pay_channel_code', 0, 'primary', '', '支付宝扫码支付', '1', '2021-12-03 10:43:10', '1', '2023-07-19 20:09:28', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3449, 15, '支付宝小程序支付', 'alipay_lite', 'pay_channel_code', 0, 'primary', '', '支付宝小程序支付', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (120, 10, '通知成功', '10', 'pay_notify_status', 0, 'success', '', '通知成功', '1', '2021-12-03 11:02:41', '1', '2023-07-19 10:08:19', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (121, 20, '通知失败', '20', 'pay_notify_status', 0, 'danger', '', '通知失败', '1', '2021-12-03 11:02:59', '1', '2023-07-19 10:08:21', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (122, 0, '等待通知', '0', 'pay_notify_status', 0, 'info', '', '未通知', '1', '2021-12-03 11:03:10', '1', '2023-07-19 10:08:24', '0');
@@ -1663,7 +1664,7 @@ COMMIT;
DROP SEQUENCE IF EXISTS system_dict_data_seq;
CREATE SEQUENCE system_dict_data_seq
START 3449;
START 3450;
-- ----------------------------
-- Table structure for system_dict_type
+2 -1
View File
@@ -769,6 +769,7 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (117, 11, '支付宝 Wap 网站支付', 'alipay_wap', 'pay_channel_code', 0, 'primary', '', '支付宝 Wap 网站支付', '1', to_date('2021-12-03 10:42:26', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 20:09:16', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (118, 12, '支付宝 App 支付', 'alipay_app', 'pay_channel_code', 0, 'primary', '', '支付宝 App 支付', '1', to_date('2021-12-03 10:42:55', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 20:09:20', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (119, 14, '支付宝扫码支付', 'alipay_qr', 'pay_channel_code', 0, 'primary', '', '支付宝扫码支付', '1', to_date('2021-12-03 10:43:10', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 20:09:28', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3449, 15, '支付宝小程序支付', 'alipay_lite', 'pay_channel_code', 0, 'primary', '', '支付宝小程序支付', '1', to_date('2026-08-18 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-08-18 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (120, 10, '通知成功', '10', 'pay_notify_status', 0, 'success', '', '通知成功', '1', to_date('2021-12-03 11:02:41', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 10:08:19', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (121, 20, '通知失败', '20', 'pay_notify_status', 0, 'danger', '', '通知失败', '1', to_date('2021-12-03 11:02:59', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 10:08:21', 'SYYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (122, 0, '等待通知', '0', 'pay_notify_status', 0, 'info', '', '未通知', '1', to_date('2021-12-03 11:03:10', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-07-19 10:08:24', 'SYYYY-MM-DD HH24:MI:SS'), '0');
@@ -1614,7 +1615,7 @@ COMMIT;
-- @formatter:on
CREATE SEQUENCE system_dict_data_seq
START WITH 3449;
START WITH 3450;
-- ----------------------------
-- Table structure for system_dict_type
+1
View File
@@ -817,6 +817,7 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (117, 11, '支付宝 Wap 网站支付', 'alipay_wap', 'pay_channel_code', 0, 'primary', '', '支付宝 Wap 网站支付', '1', '2021-12-03 10:42:26', '1', '2023-07-19 20:09:16', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (118, 12, '支付宝 App 支付', 'alipay_app', 'pay_channel_code', 0, 'primary', '', '支付宝 App 支付', '1', '2021-12-03 10:42:55', '1', '2023-07-19 20:09:20', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (119, 14, '支付宝扫码支付', 'alipay_qr', 'pay_channel_code', 0, 'primary', '', '支付宝扫码支付', '1', '2021-12-03 10:43:10', '1', '2023-07-19 20:09:28', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3449, 15, '支付宝小程序支付', 'alipay_lite', 'pay_channel_code', 0, 'primary', '', '支付宝小程序支付', '1', '2026-08-18 00:00:00', '1', '2026-08-18 00:00:00', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (120, 10, '通知成功', '10', 'pay_notify_status', 0, 'success', '', '通知成功', '1', '2021-12-03 11:02:41', '1', '2023-07-19 10:08:19', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (121, 20, '通知失败', '20', 'pay_notify_status', 0, 'danger', '', '通知失败', '1', '2021-12-03 11:02:59', '1', '2023-07-19 10:08:21', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (122, 0, '等待通知', '0', 'pay_notify_status', 0, 'info', '', '未通知', '1', '2021-12-03 11:03:10', '1', '2023-07-19 10:08:24', '0');
+1
View File
@@ -2258,6 +2258,7 @@ GO
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (118, 12, N'支付宝 App 支付', N'alipay_app', N'pay_channel_code', 0, N'primary', N'', N'支付宝 App 支付', N'1', N'2021-12-03 10:42:55', N'1', N'2023-07-19 20:09:20', N'0')
GO
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (119, 14, N'支付宝扫码支付', N'alipay_qr', N'pay_channel_code', 0, N'primary', N'', N'支付宝扫码支付', N'1', N'2021-12-03 10:43:10', N'1', N'2023-07-19 20:09:28', N'0')
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3449, 15, N'支付宝小程序支付', N'alipay_lite', N'pay_channel_code', 0, N'primary', N'', N'支付宝小程序支付', N'1', N'2026-08-18 00:00:00', N'1', N'2026-08-18 00:00:00', N'0')
GO
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (120, 10, N'通知成功', N'10', N'pay_notify_status', 0, N'success', N'', N'通知成功', N'1', N'2021-12-03 11:02:41', N'1', N'2023-07-19 10:08:19', N'0')
GO
@@ -5,8 +5,6 @@ package cn.iocoder.yudao.framework.datasource.core.enums;
*
* 通过在方法上,使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解,设置使用的数据源。
* 注意,默认是 {@link #MASTER} 数据源
*
* 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html
*/
public interface DataSourceEnum {
@@ -173,7 +173,7 @@ public class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {
@Override
public LambdaQueryWrapperX<T> and(boolean condition, Consumer<LambdaQueryWrapper<T>> consumer) {
super.and(condition,consumer);
super.and(condition, consumer);
return this;
}
@@ -14,6 +14,7 @@ import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@@ -73,6 +74,19 @@ public class YudaoSecurityAutoConfiguration {
return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);
}
/**
* Token 过滤器由 Spring Security FilterChain 管理,禁止 Spring Boot 再注册为 Servlet 全局 Filter
*
* @see <a href="https://github.com/YunaiV/ruoyi-vue-pro/issues/1198">Issue #1198</a>
*/
@Bean
public FilterRegistrationBean<TokenAuthenticationFilter> tokenAuthenticationFilterRegistration(
TokenAuthenticationFilter filter) {
FilterRegistrationBean<TokenAuthenticationFilter> registration = new FilterRegistrationBean<>(filter);
registration.setEnabled(false);
return registration;
}
@Bean("ss") // 使用 Spring Security 的缩写,方便使用
public SecurityFrameworkService securityFrameworkService(PermissionCommonApi permissionApi) {
return new SecurityFrameworkServiceImpl(permissionApi);
@@ -17,17 +17,19 @@ import jakarta.validation.constraints.NotNull;
public class WebProperties {
@NotNull(message = "APP API 不能为空")
@Valid
private Api appApi = new Api("/app-api", "**.controller.app.**");
@NotNull(message = "Admin API 不能为空")
@Valid
private Api adminApi = new Api("/admin-api", "**.controller.admin.**");
@NotNull(message = "Admin UI 不能为空")
@Valid
private Ui adminUi;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Valid
public static class Api {
/**
@@ -53,7 +55,6 @@ public class WebProperties {
}
@Data
@Valid
public static class Ui {
/**
@@ -313,6 +313,11 @@ public class GlobalExceptionHandler {
// 忽略日志,避免影响主流程
}
}
// ServiceException 的错误码可能为成功码,直接调用 CommonResult.error 会抛出异常,此处统一按系统异常处理。
// 详见 Issue #1196:https://github.com/YunaiV/ruoyi-vue-pro/issues/1196
if (CommonResult.isSuccess(ex.getCode())) {
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
}
return CommonResult.error(ex.getCode(), ex.getMessage());
}
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.d
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -12,6 +13,7 @@ import jakarta.annotation.Resource;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -91,4 +93,19 @@ public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmT
return deptApi.getDept(startUser.getDeptId()).getCheckedData();
}
/**
* 获取发起人的部门(优先使用流程变量中的发起人编号)
*
* @param startUserId 发起人 Id(流程变量不存在时使用)
* @param processVariables 流程变量
* @return 发起人的部门
*/
protected DeptRespDTO getStartUserDept(Long startUserId, Map<String, Object> processVariables) {
Long processStartUserId = FlowableUtils.getProcessInstanceStartUserId(processVariables);
if (processStartUserId != null && processStartUserId > 0) {
startUserId = processStartUserId;
}
return getStartUserDept(startUserId);
}
}
@@ -49,7 +49,7 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBp
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
// 获取发起人的 multi 部门负责人
DeptRespDTO dept = super.getStartUserDept(startUserId);
DeptRespDTO dept = super.getStartUserDept(startUserId, processInstance.getProcessVariables());
if (dept == null) {
return new HashSet<>();
}
@@ -60,7 +60,7 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBp
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
int level = Integer.parseInt(param); // 参数是部门的层级
DeptRespDTO dept = super.getStartUserDept(startUserId);
DeptRespDTO dept = super.getStartUserDept(startUserId, processVariables);
if (dept == null) {
return new HashSet<>();
}
@@ -48,19 +48,20 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTask
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
// 获取发起人的部门负责人
return getStartUserDeptLeader(startUserId, param);
return getStartUserDeptLeader(startUserId, param, processInstance.getProcessVariables());
}
@Override
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
// 获取发起人的部门负责人
return getStartUserDeptLeader(startUserId, param);
return getStartUserDeptLeader(startUserId, param, processVariables);
}
private Set<Long> getStartUserDeptLeader(Long startUserId, String param) {
private Set<Long> getStartUserDeptLeader(Long startUserId, String param,
Map<String, Object> processVariables) {
int level = Integer.parseInt(param); // 参数是部门的层级
DeptRespDTO dept = super.getStartUserDept(startUserId);
DeptRespDTO dept = super.getStartUserDept(startUserId, processVariables);
if (dept == null) {
return new HashSet<>();
}
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -133,6 +134,17 @@ public class FlowableUtils {
return (Integer) processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
}
/**
* 获得流程实例的发起用户 ID
*
* @param processVariables 流程实例的 variables
* @return 发起用户 ID
*/
public static Long getProcessInstanceStartUserId(Map<String, Object> processVariables) {
return processVariables == null ? null : Convert.toLong(
processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID));
}
/**
* 获得流程实例的审批原因
*
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@@ -13,6 +14,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -42,12 +45,16 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMo
String param = "2";
// mock 方法(获得流程发起人)
Long startUserId = 1L;
Long processVariableStartUserId = 2L;
ProcessInstance processInstance = mock(ProcessInstance.class);
DelegateExecution execution = mock(DelegateExecution.class);
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
Map<String, Object> processVariables = new HashMap<>();
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, processVariableStartUserId);
when(processInstance.getProcessVariables()).thenReturn(processVariables);
// mock 方法(获取发起人的 multi 部门负责人)
mockGetStartUserDept(startUserId);
mockGetStartUserDept(processVariableStartUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
@@ -61,12 +68,14 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMo
String param = "2";
// mock 方法
Long startUserId = 1L;
mockGetStartUserDept(startUserId);
Long processVariableStartUserId = 2L;
Map<String, Object> processVariables = new HashMap<>();
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, processVariableStartUserId);
mockGetStartUserDept(processVariableStartUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
startUserId, null, null);
// 断言
startUserId, null, processVariables);
assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
}
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@@ -13,6 +14,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -42,12 +45,16 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategyTest extends BaseMockito
String param = "2";
// mock 方法(获得流程发起人)
Long startUserId = 1L;
Long processVariableStartUserId = 2L;
ProcessInstance processInstance = mock(ProcessInstance.class);
DelegateExecution execution = mock(DelegateExecution.class);
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
Map<String, Object> processVariables = new HashMap<>();
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, processVariableStartUserId);
when(processInstance.getProcessVariables()).thenReturn(processVariables);
// mock 方法(获取发起人的部门负责人)
mockGetStartUserDeptLeader(startUserId);
mockGetStartUserDeptLeader(processVariableStartUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
@@ -61,12 +68,14 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategyTest extends BaseMockito
String param = "2";
// mock 方法
Long startUserId = 1L;
mockGetStartUserDeptLeader(startUserId);
Long processVariableStartUserId = 2L;
Map<String, Object> processVariables = new HashMap<>();
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, processVariableStartUserId);
mockGetStartUserDeptLeader(processVariableStartUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
startUserId, null, null);
// 断言
startUserId, null, processVariables);
assertEquals(Sets.newLinkedHashSet(1001L), userIds);
}
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
@@ -22,6 +23,29 @@ import static org.junit.jupiter.api.Assertions.assertNull;
*/
class FlowableUtilsTest {
@Test
public void testGetProcessInstanceStartUserId() {
// 准备参数
Map<String, Object> processVariables = new HashMap<>();
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, "1");
// 调用
Long startUserId = FlowableUtils.getProcessInstanceStartUserId(processVariables);
// 断言
assertEquals(1L, startUserId);
}
@Test
public void testGetProcessInstanceStartUserId_empty() {
// 准备参数
Map<String, Object> processVariables = new HashMap<>();
// 调用 & 断言
assertNull(FlowableUtils.getProcessInstanceStartUserId(processVariables));
assertNull(FlowableUtils.getProcessInstanceStartUserId(null));
}
@Test
public void testGetSummary_customSummary_parseDbFormFields() {
// 准备参数:模拟 DB 中 form_fields 字段,列表里每个元素都是一个 form-create 字段 JSON。
@@ -102,7 +102,7 @@ public class FmsAccountUserServiceImpl implements FmsAccountUserService {
FmsAccountUserUpdateReqVO.Member::getUserId, FmsAccountUserUpdateReqVO.Member::getLevel);
Set<Long> memberUserIds = new HashSet<>(memberLevelMap.keySet());
memberUserIds.removeAll(founderUserIds);
adminUserApi.validateUserList(memberUserIds);
adminUserApi.validateUserList(memberUserIds).checkError();
// 2.1 移出取消授权的成员
Set<Long> currentMemberUserIds = convertSet(accountUsers, FmsAccountUserDO::getUserId,
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.fms.dal.mysql.config.FmsAccountUserMapper;
import cn.iocoder.yudao.module.fms.enums.config.FmsAccountUserLevelEnum;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -22,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -38,6 +41,11 @@ public class FmsAccountUserServiceImplTest extends BaseDbUnitTest {
@MockitoBean
private AdminUserApi adminUserApi;
@BeforeEach
public void setUp() {
when(adminUserApi.validateUserList(anyCollection())).thenReturn(success(true));
}
@Test
public void testCreateAccountOwner_firstAccountSet() {
// 准备参数
@@ -312,7 +312,7 @@ public class HrmAttendanceGroupServiceImpl implements HrmAttendanceGroupService
private void validateAttendanceGroup(HrmAttendanceGroupSaveReqVO reqVO) {
// 1. 校验适用部门存在
deptApi.validateDeptList(reqVO.getDeptIds());
deptApi.validateDeptList(reqVO.getDeptIds()).checkError();
// 2. 校验适用员工存在
employeeService.validateEmployeeListExists(reqVO.getEmployeeIds());
}
@@ -628,7 +628,7 @@ public class HrmEmployeeServiceImpl implements HrmEmployeeService {
throw exception(EMPLOYEE_CREATE_USER_LIST_DUPLICATE);
}
}
adminUserApi.validateUserList(userIds);
adminUserApi.validateUserList(userIds).checkError();
Map<Long, AdminUserRespDTO> userMap = convertMap(
adminUserApi.getUserList(userIds).getCheckedData(), AdminUserRespDTO::getId);
// 1.2 构建并校验全部员工档案,避免部分数据校验失败时已经执行写库
@@ -1179,7 +1179,7 @@ public class HrmEmployeeServiceImpl implements HrmEmployeeService {
private void validateDeptAndLeader(Long employeeId, Long deptId, Long leaderEmployeeId) {
// 1. 校验部门是否存在
if (deptId != null) {
deptApi.validateDeptList(Collections.singleton(deptId));
deptApi.validateDeptList(Collections.singleton(deptId)).checkError();
}
// 2. 校验直属上级链路不存在自引用或环路
if (leaderEmployeeId == null) {
@@ -1417,7 +1417,7 @@ public class HrmEmployeeServiceImpl implements HrmEmployeeService {
try {
notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO()
.setUserId(employee.getUserId()).setTemplateCode(templateCode)
.setTemplateParams(Collections.singletonMap("employeeName", employee.getName())));
.setTemplateParams(Collections.singletonMap("employeeName", employee.getName()))).checkError();
return true;
} catch (RuntimeException ex) {
log.warn("[sendEmployeeMessage][employeeId({}) userId({}) templateCode({}) 发送失败]",
@@ -536,7 +536,7 @@ public class HrmInsuranceMonthEmployeeRecordServiceImpl implements HrmInsuranceM
notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO()
.setUserId(employee.getUserId())
.setTemplateCode(MessageTemplateConstants.INSURANCE_MONTH_RECORD_CREATED)
.setTemplateParams(templateParams));
.setTemplateParams(templateParams)).checkError();
}
}
@@ -350,7 +350,7 @@ public class HrmPerformanceAssessmentProcessServiceImpl
notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO()
.setUserId(handler.getUserId())
.setTemplateCode(MessageTemplateConstants.PERFORMANCE_TASK_PENDING)
.setTemplateParams(templateParams));
.setTemplateParams(templateParams)).checkError();
}
@Override
@@ -386,7 +386,7 @@ public class HrmPerformanceAssessmentProcessServiceImpl
notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO()
.setUserId(employee.getUserId())
.setTemplateCode(MessageTemplateConstants.PERFORMANCE_PROCESS_RESULT)
.setTemplateParams(templateParams));
.setTemplateParams(templateParams)).checkError();
}
}
@@ -555,7 +555,7 @@ public class HrmPerformancePlanServiceImpl implements HrmPerformancePlanService
employeeService.validateEmployeeListExists(employeeIds);
}
if (CollUtil.isNotEmpty(deptIds)) {
deptApi.validateDeptList(deptIds);
deptApi.validateDeptList(deptIds).checkError();
}
}
@@ -351,7 +351,7 @@ public class HrmRecruitInterviewServiceImpl implements HrmRecruitInterviewServic
templateParams.put("interviewTime", INTERVIEW_TIME_FORMATTER.format(recruitInterview.getInterviewTime()));
userIds.forEach(userId -> notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO()
.setUserId(userId).setTemplateCode(MessageTemplateConstants.RECRUIT_INTERVIEW_ARRANGED)
.setTemplateParams(templateParams)));
.setTemplateParams(templateParams)).checkError());
}
}
@@ -146,7 +146,7 @@ public class HrmRecruitPostServiceImpl implements HrmRecruitPostService {
// 1. 校验职位类型和用人部门存在
recruitPostTypeService.validateRecruitPostTypeExists(reqVO.getPostTypeId());
if (reqVO.getDeptId() != null) {
deptApi.validateDeptList(Collections.singleton(reqVO.getDeptId()));
deptApi.validateDeptList(Collections.singleton(reqVO.getDeptId())).checkError();
}
// 2. 校验招聘负责人和面试官存在
@@ -221,7 +221,7 @@ public class HrmSalaryGroupServiceImpl implements HrmSalaryGroupService {
// 1. 校验适用部门及其上下级部门未归属其他薪资组
Set<Long> selectedDeptIds = CollUtil.isEmpty(deptIds)
? Collections.emptySet() : new HashSet<>(deptIds);
deptApi.validateDeptList(selectedDeptIds);
deptApi.validateDeptList(selectedDeptIds).checkError();
List<HrmSalaryGroupDO> otherSalaryGroups = convertList(salaryGroupMapper.selectListByIdDesc(),
salaryGroup -> salaryGroup, salaryGroup -> ObjUtil.notEqual(salaryGroup.getId(), id));
if (CollUtil.isNotEmpty(selectedDeptIds)) {
@@ -256,7 +256,7 @@ public class HrmSalarySlipServiceImpl implements HrmSalarySlipService {
templateParams.put("realSalary", employeeRecord.getRealPaySalary().toPlainString());
notifyMessageSendApi.sendSingleMessageToAdmin(new NotifySendSingleToUserReqDTO()
.setUserId(employee.getUserId()).setTemplateCode(MessageTemplateConstants.SALARY_SLIP_SENT)
.setTemplateParams(templateParams));
.setTemplateParams(templateParams)).checkError();
}
}
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.hrm.enums.attendance.config.HrmAttendanceMisscard
import cn.iocoder.yudao.module.hrm.service.employee.info.HrmEmployeeService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
@@ -22,13 +23,23 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link HrmAttendanceGroupServiceImpl} 的单元测试类
@@ -49,6 +60,11 @@ public class HrmAttendanceGroupServiceImplTest extends BaseMockitoUnitTest {
@Mock
private DeptApi deptApi;
@BeforeEach
public void setUp() {
lenient().when(deptApi.validateDeptList(anyCollection())).thenReturn(success(true));
}
@Test
public void testGetAttendanceGroupMap_employeeAndDeptAndDefaultGroup() {
// mock 数据
@@ -68,6 +68,7 @@ import static java.util.Arrays.asList;
import static java.util.Collections.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.*;
@@ -128,6 +129,9 @@ public class HrmEmployeeServiceImplTest extends BaseDbUnitTest {
HrmEmployeeChangeRecordCreateReqVO reqVO = invocation.getArgument(0);
return BeanUtils.toBean(reqVO, HrmEmployeeChangeRecordDO.class).setId(randomLongId());
});
lenient().when(deptApi.validateDeptList(anyCollection())).thenReturn(success(true));
lenient().when(adminUserApi.validateUserList(anyCollection())).thenReturn(success(true));
lenient().when(notifyMessageSendApi.sendSingleMessageToAdmin(any())).thenReturn(success(randomLongId()));
}
@Test
@@ -25,6 +25,7 @@ import cn.iocoder.yudao.module.hrm.service.insurance.config.HrmInsuranceSchemeSe
import cn.iocoder.yudao.module.hrm.service.insurance.employee.HrmInsuranceEmployeeInfoService;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -37,6 +38,8 @@ import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.INSURANCE_EMPLOYEE_SCHEME_NOT_CONFIGURED;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.INSURANCE_MONTH_EMPLOYEE_NOT_ELIGIBLE;
@@ -73,6 +76,11 @@ public class HrmInsuranceMonthEmployeeRecordServiceImplTest extends BaseDbUnitTe
@MockitoBean
private NotifyMessageSendApi notifyMessageSendApi;
@BeforeEach
public void setUp() {
when(notifyMessageSendApi.sendSingleMessageToAdmin(any())).thenReturn(success(randomLongId()));
}
@Test
public void testCreateMonthEmployeeRecordList_success() {
// mock 数据
@@ -43,6 +43,7 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.context.annotation.Import;
@@ -57,6 +58,7 @@ import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.PERFORMANCE_STAGE_NO_PERMISSION;
@@ -70,6 +72,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -111,6 +114,11 @@ public class HrmPerformanceAssessmentProcessServiceImplTest extends BaseDbUnitTe
@MockitoBean
private HrmPerformanceAssessmentActionRecordService assessmentActionRecordService;
@BeforeEach
public void setUp() {
lenient().when(notifyMessageSendApi.sendSingleMessageToAdmin(any())).thenReturn(success(randomLongId()));
}
@Test
public void testInitializeAssessmentStages_unboundEmployee_fail() {
// mock 数据
@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.hrm.service.employee.info.HrmEmployeeService;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.context.annotation.Import;
@@ -27,6 +28,7 @@ import java.util.Map;
import java.util.function.Consumer;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
@@ -68,6 +70,11 @@ public class HrmRecruitInterviewServiceImplTest extends BaseDbUnitTest {
@MockitoBean
private NotifyMessageSendApi notifyMessageSendApi;
@BeforeEach
public void setUp() {
when(notifyMessageSendApi.sendSingleMessageToAdmin(any())).thenReturn(success(randomLongId()));
}
@Test
public void testCreateRecruitInterview_success() {
// mock 数据
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.hrm.enums.recruit.post.HrmRecruitPostStatusEnum;
import cn.iocoder.yudao.module.hrm.service.employee.info.HrmEmployeeService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -25,6 +26,7 @@ import java.util.Map;
import java.util.function.Consumer;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.getDateTimeRange;
@@ -44,6 +46,7 @@ import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -68,6 +71,11 @@ public class HrmRecruitPostServiceImplTest extends BaseDbUnitTest {
@MockitoBean
private DeptApi deptApi;
@BeforeEach
public void setUp() {
when(deptApi.validateDeptList(anyCollection())).thenReturn(success(true));
}
@Test
public void testCreateRecruitPost_success() {
// 准备参数
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.hrm.service.employee.info.HrmEmployeeService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -32,10 +33,10 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.ArgumentMatchers.argThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
/**
* {@link HrmSalaryGroupServiceImpl} 的单元测试类
@@ -58,6 +59,11 @@ public class HrmSalaryGroupServiceImplTest extends BaseDbUnitTest {
@MockitoBean
private DeptApi deptApi;
@BeforeEach
public void setUp() {
when(deptApi.validateDeptList(anyCollection())).thenReturn(success(true));
}
@Test
public void testCreateSalaryGroup_success() {
// 准备参数
@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.hrm.enums.salary.slip.HrmSalarySlipReadStatusEnum;
import cn.iocoder.yudao.module.hrm.enums.salary.slip.HrmSalarySlipTemplateOptionTypeEnum;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@@ -27,6 +28,7 @@ import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SALARY_SLIP_ALREADY_SENT;
import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SALARY_SLIP_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -37,6 +39,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
/**
* {@link HrmSalarySlipServiceImpl} 的单元测试类
@@ -56,6 +59,12 @@ public class HrmSalarySlipServiceImplTest extends BaseDbUnitTest {
@MockitoBean
private NotifyMessageSendApi notifyMessageSendApi;
@BeforeEach
public void setUp() {
when(notifyMessageSendApi.sendSingleMessageToAdmin(org.mockito.ArgumentMatchers.any()))
.thenReturn(success(randomLongId()));
}
@Test
public void testCreateSalarySlipList_success() {
// mock 数据
@@ -35,7 +35,6 @@ public class AppFileController {
@Operation(summary = "上传文件")
@Parameter(name = "file", description = "文件附件", required = true,
schema = @Schema(type = "string", format = "binary"))
@PermitAll
public CommonResult<String> uploadFile(@Valid AppFileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile();
byte[] content = IoUtil.readBytes(file.getInputStream());
@@ -57,7 +56,6 @@ public class AppFileController {
@PostMapping("/create")
@Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件")
@PermitAll
public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
return success(fileService.createFile(createReqVO));
}
@@ -173,6 +173,12 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
* @return 节点地址
*/
private String buildPresignerEndpoint() {
// 阿里云 OSS、七牛云的自定义域名仅用于文件访问,不能作为 S3 签名节点。
// Presigner 必须使用服务商提供的 endpoint,否则生成的签名无法用于直传。
if (StrUtil.contains(config.getEndpoint(), S3FileClientConfig.ENDPOINT_ALIYUN)
|| StrUtil.contains(config.getEndpoint(), S3FileClientConfig.ENDPOINT_QINIU)) {
return buildEndpoint();
}
// 补全 domain
if (StrUtil.isEmpty(config.getDomain())) {
config.setDomain(buildDomain());
@@ -69,6 +69,42 @@ public class S3FileClientTest {
assertFalse(result.contains("%25E4%25B8%25AD"));
}
@Test
public void testPresignPutUrl_aliyunUsesEndpointInsteadOfCustomDomain() {
S3FileClientConfig config = new S3FileClientConfig();
config.setAccessKey("access-key");
config.setAccessSecret("access-secret");
config.setBucket("test-bucket");
config.setDomain("https://custom.example.com");
config.setEndpoint("oss-cn-beijing.aliyuncs.com");
config.setEnablePathStyleAccess(false);
config.setEnablePublicAccess(false);
S3FileClient client = new S3FileClient(0L, config);
client.init();
String result = client.presignPutUrl("avatar.jpg");
assertTrue(result.startsWith("https://test-bucket.oss-cn-beijing.aliyuncs.com/"));
}
@Test
public void testPresignPutUrl_qiniuUsesEndpointInsteadOfCustomDomain() {
S3FileClientConfig config = new S3FileClientConfig();
config.setAccessKey("access-key");
config.setAccessSecret("access-secret");
config.setBucket("test-bucket");
config.setDomain("https://custom.example.com");
config.setEndpoint("s3-cn-south-1.qiniucs.com");
config.setEnablePathStyleAccess(false);
config.setEnablePublicAccess(false);
S3FileClient client = new S3FileClient(0L, config);
client.init();
String result = client.presignPutUrl("avatar.jpg");
assertTrue(result.startsWith("https://test-bucket.s3-cn-south-1.qiniucs.com/"));
}
@Test
@Disabled // MinIO,如果要集成测试,可以注释本行
public void testMinIO() throws Exception {
@@ -66,6 +66,8 @@ public interface ErrorCodeConstants {
ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
ErrorCode OTA_FIRMWARE_DELETE_FAIL_TASK_IN_PROGRESS = new ErrorCode(1_050_008_002,
"删除 OTA 固件失败,原因:存在进行中的升级任务");
// ========== OTA 升级任务相关 1-050-008-100 ==========
@@ -105,4 +107,4 @@ public interface ErrorCodeConstants {
// ========== IoT 告警记录 1-050-014-000 ==========
ErrorCode ALERT_RECORD_NOT_EXISTS = new ErrorCode(1_050_014_000, "IoT 告警记录不存在");
}
}
@@ -83,10 +83,10 @@ public class IotDeviceMessageController {
@PostMapping("/send")
@Operation(summary = "发送消息", description = "可用于设备模拟")
@PreAuthorize("@ss.hasPermission('iot:device:message-end')")
@PreAuthorize("@ss.hasPermission('iot:device:message-send')")
public CommonResult<Boolean> sendDeviceMessage(@Valid @RequestBody IotDeviceMessageSendReqVO sendReqVO) {
deviceMessageService.sendDeviceMessage(BeanUtils.toBean(sendReqVO, IotDeviceMessage.class));
return success(true);
}
}
}
@@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
@@ -47,6 +48,15 @@ public class IotOtaFirmwareController {
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除 OTA 固件")
@Parameter(name = "id", description = "固件编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('iot:ota-firmware:delete')")
public CommonResult<Boolean> deleteOtaFirmware(@RequestParam("id") Long id) {
otaFirmwareService.deleteOtaFirmware(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得 OTA 固件")
@PreAuthorize("@ss.hasPermission('iot:ota-firmware:query')")
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@@ -16,6 +17,12 @@ public interface IotOtaTaskMapper extends BaseMapperX<IotOtaTaskDO> {
IotOtaTaskDO::getName, name);
}
default Long selectCountByFirmwareIdAndStatus(Long firmwareId, Integer status) {
return selectCount(new LambdaQueryWrapper<IotOtaTaskDO>()
.eq(IotOtaTaskDO::getFirmwareId, firmwareId)
.eq(IotOtaTaskDO::getStatus, status));
}
default PageResult<IotOtaTaskDO> selectPage(IotOtaTaskPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskDO>()
.eqIfPresent(IotOtaTaskDO::getFirmwareId, pageReqVO.getFirmwareId())
@@ -35,6 +35,13 @@ public interface IotOtaFirmwareService {
*/
void updateOtaFirmware(@Valid IotOtaFirmwareUpdateReqVO updateReqVO);
/**
* 删除 OTA 固件信息
*
* @param id 固件编号
*/
void deleteOtaFirmware(Long id);
/**
* 获取 OTA 固件信息
*
@@ -10,6 +10,8 @@ import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwa
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaFirmwareMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskMapper;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum;
import cn.iocoder.yudao.module.iot.service.product.IotProductService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -23,6 +25,7 @@ import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_DELETE_FAIL_TASK_IN_PROGRESS;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_NOT_EXISTS;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE;
@@ -38,6 +41,8 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
@Resource
private IotOtaFirmwareMapper otaFirmwareMapper;
@Resource
private IotOtaTaskMapper otaTaskMapper;
@Lazy
@Resource
private IotProductService productService;
@@ -74,6 +79,19 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
otaFirmwareMapper.updateById(updateObj);
}
@Override
public void deleteOtaFirmware(Long id) {
// 1.1 校验存在
validateFirmwareExists(id);
// 1.2 校验是否有进行中的升级任务
if (otaTaskMapper.selectCountByFirmwareIdAndStatus(id, IotOtaTaskStatusEnum.IN_PROGRESS.getStatus()) > 0) {
throw exception(OTA_FIRMWARE_DELETE_FAIL_TASK_IN_PROGRESS);
}
// 2. 删除
otaFirmwareMapper.deleteById(id);
}
@Override
public IotOtaFirmwareDO getOtaFirmware(Long id) {
return otaFirmwareMapper.selectById(id);
@@ -137,6 +137,8 @@ public interface ErrorCodeConstants {
ErrorCode CAL_TEAM_SHIFT_NOT_EXISTS = new ErrorCode(1_040_201_200, "班组排班记录不存在");
ErrorCode CAL_TEAM_SHIFT_GENERATE_TEAM_NOT_ENOUGH = new ErrorCode(1_040_201_201, "班组数量不满足轮班方式要求");
ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH = new ErrorCode(1_040_201_202, "班次数量不满足轮班方式要求");
ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_COUNT_INVALID = new ErrorCode(1_040_201_203, "按天倒班必须填写合法的倒班天数");
ErrorCode CAL_TEAM_SHIFT_GENERATE_SHIFT_METHOD_INVALID = new ErrorCode(1_040_201_204, "不支持的倒班方式:{}");
// ========== MES 日历排班-排班计划(1-040-202-000) ==========
ErrorCode CAL_PLAN_NOT_EXISTS = new ErrorCode(1_040_202_000, "排班计划不存在");
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.ibatis.annotations.Mapper;
/**
@@ -31,4 +32,11 @@ public interface MesCalPlanMapper extends BaseMapperX<MesCalPlanDO> {
return selectOne(MesCalPlanDO::getCode, code);
}
default void updateWithShiftFieldCleanup(MesCalPlanDO plan, boolean clearShiftMethod, boolean clearShiftCount) {
update(plan, Wrappers.<MesCalPlanDO>lambdaUpdate()
.eq(MesCalPlanDO::getId, plan.getId())
.set(clearShiftMethod, MesCalPlanDO::getShiftMethod, null)
.set(clearShiftCount, MesCalPlanDO::getShiftCount, null));
}
}
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.cal.plan.vo.MesCalPlanSaveRe
import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
import cn.iocoder.yudao.module.mes.dal.mysql.cal.plan.MesCalPlanMapper;
import cn.iocoder.yudao.module.mes.enums.cal.MesCalPlanStatusEnum;
import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum;
import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamShiftService;
import cn.hutool.core.util.ObjUtil;
@@ -35,6 +36,7 @@ public class MesCalPlanServiceImpl implements MesCalPlanService {
@Resource
private MesCalPlanMapper planMapper;
@Resource
@Lazy
private MesCalPlanShiftService planShiftService;
@@ -75,10 +77,20 @@ public class MesCalPlanServiceImpl implements MesCalPlanService {
// 1.3 校验编码唯一
validatePlanCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
// 2. 更新
// 2.1 倒班字段联动清理:单白班不倒班,倒班方式非按天时不需要倒班天数
updateReqVO.setStatus(null); // 不允许通过 update 修改状态,状态变更走专用接口
boolean clearShiftMethod = MesCalShiftTypeEnum.SINGLE.getType().equals(updateReqVO.getShiftType());
boolean clearShiftCount = clearShiftMethod || updateReqVO.getShiftMethod() == null
|| !MesCalShiftMethodEnum.DAY.getMethod().equals(updateReqVO.getShiftMethod());
if (clearShiftMethod) {
updateReqVO.setShiftMethod(null);
}
if (clearShiftCount) {
updateReqVO.setShiftCount(null);
}
// 2.2 更新
MesCalPlanDO updateObj = BeanUtils.toBean(updateReqVO, MesCalPlanDO.class);
planMapper.updateById(updateObj);
planMapper.updateWithShiftFieldCleanup(updateObj, clearShiftMethod, clearShiftCount);
}
@Override
@@ -25,6 +25,8 @@ import java.util.ArrayList;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_COUNT_INVALID;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_METHOD_INVALID;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_TEAM_NOT_ENOUGH;
@@ -39,6 +41,7 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
@Resource
private MesCalTeamShiftMapper teamShiftMapper;
@Resource
@Lazy
private MesCalPlanService planService;
@@ -77,8 +80,10 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
for (int i = 0; i < days; i++) {
LocalDate currentDate = startDate.plusDays(i);
LocalDateTime currentDay = currentDate.atStartOfDay();
// 2.2.1 根据倒班方式计算 shiftIndex
shiftIndex = calculateShiftIndex(shiftIndex, i, currentDate, startDate, plan);
// 2.2.1 根据倒班方式计算 shiftIndex 单白班不倒班,跳过
if (!MesCalShiftTypeEnum.SINGLE.getType().equals(plan.getShiftType())) {
shiftIndex = calculateShiftIndex(shiftIndex, i, currentDate, startDate, plan);
}
// 2.2.2 根据轮班方式生成排班记录,收集到列表
buildRecordsForDay(allRecords, planId, currentDay, shiftIndex, plan.getShiftType(), teams, shifts);
}
@@ -118,11 +123,15 @@ public class MesCalTeamShiftServiceImpl implements MesCalTeamShiftService {
}
} else if (MesCalShiftMethodEnum.DAY.getMethod().equals(plan.getShiftMethod())) {
// 按天轮班:到了指定轮班天数的倍数
if (dayOffset % plan.getShiftCount() == 0) {
Integer shiftCount = plan.getShiftCount();
if (shiftCount == null || shiftCount <= 0) {
throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_COUNT_INVALID);
}
if (dayOffset % shiftCount == 0) {
return shiftIndex + 1;
}
} else {
throw new IllegalArgumentException("不支持的倒班方式: " + plan.getShiftMethod());
throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_METHOD_INVALID, plan.getShiftMethod());
}
return shiftIndex;
}
@@ -29,6 +29,7 @@ public enum PayChannelEnum implements ArrayValuable<String> {
ALIPAY_APP("alipay_app", "支付宝App 支付"),
ALIPAY_QR("alipay_qr", "支付宝扫码支付"),
ALIPAY_BAR("alipay_bar", "支付宝条码支付"),
ALIPAY_LITE("alipay_lite", "支付宝小程序支付"),
MOCK("mock", "模拟支付"),
@@ -136,7 +136,7 @@ public class PayOrderDO extends BaseDO {
/**
* 渠道用户编号
*
* 例如说,微信 openid、支付宝账号
* 例如说,微信 openid、支付宝 buyerId(buyerOpenId)
*/
private String channelUserId;
/**
@@ -54,6 +54,7 @@ public class PayClientFactoryImpl implements PayClientFactory {
clientClass.put(ALIPAY_APP, AlipayAppPayClient.class);
clientClass.put(ALIPAY_PC, AlipayPcPayClient.class);
clientClass.put(ALIPAY_BAR, AlipayBarPayClient.class);
clientClass.put(ALIPAY_LITE, AlipayLitePayClient.class);
// 钱包支付客户端
clientClass.put(WALLET, WalletPayClient.class);
// Mock 支付客户端
@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -92,9 +93,12 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
status = PayOrderStatusEnum.REFUND.getStatus();
}
Assert.notNull(status, (Supplier<Throwable>) () -> {
throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body));
throw new IllegalArgumentException(CharSequenceUtil.format("body({}) 的 trade_status 不正确", body));
});
return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")),
// https://opendocs.alipay.com/open/00dn78?pathHash=fef00e6d#%E5%BC%82%E6%AD%A5%E9%80%9A%E7%9F%A5%E5%8F%82%E6%95%B0
// buyer_id(buyer_open_id)买家支付宝用户号,seller_id 卖家支付宝用户号
String channelUserId = CharSequenceUtil.nullToDefault(bodyObj.get("buyer_open_id"), bodyObj.get("buyer_id"));
return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), channelUserId, parseTime(params.get("gmt_payment")),
bodyObj.get("out_trade_no"), body);
}
@@ -0,0 +1,86 @@
package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeCreateModel;
import com.alipay.api.request.AlipayTradeCreateRequest;
import com.alipay.api.response.AlipayTradeCreateResponse;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE;
/**
* 支付宝【小程序】的 PayClient 实现类
*
* 文档:<a href="https://opendocs.alipay.com/mini/6039ed0c_alipay.trade.create">统一收单交易创建</a>
*
* @author graypxl
*/
public class AlipayLitePayClient extends AbstractAlipayPayClient {
public static final String BUYER_ID_KEY = "buyer_id";
public static final String BUYER_OPEN_ID_KEY = "buyer_open_id";
public AlipayLitePayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_LITE.getCode(), config);
}
@Override
public PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
AlipayTradeCreateModel model = new AlipayTradeCreateModel();
model.setOutTradeNo(reqDTO.getOutTradeNo());
model.setSubject(reqDTO.getSubject());
model.setBody(reqDTO.getBody());
model.setTotalAmount(formatAmount(reqDTO.getPrice()));
model.setTimeExpire(formatTime(reqDTO.getExpireTime()));
model.setProductCode("JSAPI_PAY");
model.setOpAppId(config.getAppId());
applyBuyerIdentity(model, reqDTO);
AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
request.setBizModel(model);
request.setNotifyUrl(reqDTO.getNotifyUrl());
AlipayTradeCreateResponse response;
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
response = client.certificateExecute(request);
} else {
response = client.execute(request);
}
if (!response.isSuccess()) {
return buildClosedPayOrderRespDTO(reqDTO, response);
}
return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), response.getTradeNo(),
reqDTO.getOutTradeNo(), response);
}
static void applyBuyerIdentity(AlipayTradeCreateModel model, PayOrderUnifiedReqDTO reqDTO) {
String buyerId = MapUtil.getStr(reqDTO.getChannelExtras(), BUYER_ID_KEY);
String buyerOpenId = MapUtil.getStr(reqDTO.getChannelExtras(), BUYER_OPEN_ID_KEY);
if (StrUtil.isBlank(buyerOpenId)) {
buyerOpenId = MapUtil.getStr(reqDTO.getChannelExtras(), "openid");
}
if (isAlipayUserId(buyerId)) {
model.setBuyerId(buyerId);
} else if (StrUtil.isNotBlank(buyerOpenId)) {
model.setBuyerOpenId(buyerOpenId);
} else if (StrUtil.isNotBlank(buyerId)) {
model.setBuyerOpenId(buyerId);
} else {
throw invalidParamException("支付请求的 buyer_open_id 不能为空!");
}
}
static boolean isAlipayUserId(String value) {
return StrUtil.isNotBlank(value) && value.startsWith("2088") && value.length() <= 28
&& StrUtil.isNumeric(value);
}
}
@@ -6,7 +6,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
@@ -63,18 +62,17 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
WxPayConfig payConfig = new WxPayConfig();
BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent", "publicKeyContent");
payConfig.setTradeType(tradeType);
// weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决
if (Objects.equals(config.getApiVersion(), API_VERSION_V2)) {
payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
payConfig.setKeyContent(Base64.decode(config.getKeyContent()));
} else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) {
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
payConfig.setPrivateKeyContent(StrUtil.utf8Bytes(config.getPrivateKeyContent()));
// 参考 https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/ICUE53 和 https://t.zsxq.com/ODR5V
if (StrUtil.isNotBlank(config.getPublicKeyContent())) {
payConfig.setPublicKeyPath(FileUtils.createTempFile(config.getPublicKeyContent()).getPath());
payConfig.setPublicKeyContent(StrUtil.utf8Bytes(config.getPublicKeyContent()));
}
// 特殊:强制使用微信公钥模式,避免灰度期间的问题!!!
payConfig.setStrictlyNeedWechatPaySerial(true);
// 特殊:weixin-java-pay 只有配置 publicKeyPath 后,再开启 fullPublicKeyModel,才会使用 PublicCertificateVerifier
// 特殊:weixin-java-pay 只有配置 publicKeyContent 后,再开启 fullPublicKeyModel,才会使用 PublicCertificateVerifier
// 对应 https://t.zsxq.com/5Q9lO 帖子
payConfig.setFullPublicKeyModel(true);
}
@@ -0,0 +1,201 @@
package cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.module.pay.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.module.pay.framework.pay.core.enums.PayOrderDisplayModeEnum;
import cn.iocoder.yudao.module.pay.framework.pay.core.client.exception.PayClientException;
import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradeCreateModel;
import com.alipay.api.request.AlipayTradeCreateRequest;
import com.alipay.api.response.AlipayTradeCreateResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.InjectMocks;
import java.util.HashMap;
import java.util.Map;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.CLOSED;
import static cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum.WAITING;
import static cn.iocoder.yudao.module.pay.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.when;
/**
* {@link AlipayLitePayClient} 单元测试
*
* @author graypxl
*/
public class AlipayLitePayClientTest extends AbstractAlipayClientTest {
@InjectMocks
private AlipayLitePayClient client = new AlipayLitePayClient(randomLongId(), config);
@Override
@BeforeEach
public void setUp() {
setClient(client);
}
@Test
@DisplayName("支付宝小程序支付:buyer_open_id 下单成功")
public void testUnifiedOrder_buyerOpenId() throws AlipayApiException {
String buyerOpenId = "060d4dhFytu7VF5haNiDacG7b-n5toPeevhdLa07Kz9ass8";
String tradeNo = randomString();
AlipayTradeCreateResponse response = randomPojo(AlipayTradeCreateResponse.class, o -> {
o.setSubCode("");
o.setTradeNo(tradeNo);
});
when(defaultAlipayClient.execute(argThat(assertRequest(config.getAppId(), null, buyerOpenId))))
.thenReturn(response);
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
reqDTO.setChannelExtras(Map.of(AlipayLitePayClient.BUYER_OPEN_ID_KEY, buyerOpenId));
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
assertEquals(WAITING.getStatus(), resp.getStatus());
assertEquals(reqDTO.getOutTradeNo(), resp.getOutTradeNo());
assertNull(resp.getChannelOrderNo());
assertNull(resp.getChannelUserId());
assertNull(resp.getSuccessTime());
assertEquals(PayOrderDisplayModeEnum.APP.getMode(), resp.getDisplayMode());
assertEquals(tradeNo, resp.getDisplayContent());
assertSame(response, resp.getRawData());
assertNull(resp.getChannelErrorCode());
assertNull(resp.getChannelErrorMsg());
}
@Test
@DisplayName("支付宝小程序支付:buyer_id 下单成功")
public void testUnifiedOrder_buyerId() throws AlipayApiException {
String buyerId = "2088123456789012";
AlipayTradeCreateResponse response = randomPojo(AlipayTradeCreateResponse.class, o -> o.setSubCode(""));
when(defaultAlipayClient.execute(argThat(assertRequest(config.getAppId(), buyerId, null))))
.thenReturn(response);
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
reqDTO.setChannelExtras(Map.of(AlipayLitePayClient.BUYER_ID_KEY, buyerId));
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
assertEquals(WAITING.getStatus(), resp.getStatus());
assertEquals(reqDTO.getOutTradeNo(), resp.getOutTradeNo());
assertEquals(PayOrderDisplayModeEnum.APP.getMode(), resp.getDisplayMode());
assertSame(response, resp.getRawData());
}
@Test
@DisplayName("支付宝小程序支付:兼容 openid 参数")
public void testUnifiedOrder_openidAlias() throws AlipayApiException {
String buyerOpenId = randomString();
AlipayTradeCreateResponse response = randomPojo(AlipayTradeCreateResponse.class, o -> o.setSubCode(""));
when(defaultAlipayClient.execute(argThat(assertRequest(config.getAppId(), null, buyerOpenId))))
.thenReturn(response);
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
reqDTO.setChannelExtras(Map.of("openid", buyerOpenId));
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
assertEquals(WAITING.getStatus(), resp.getStatus());
assertEquals(reqDTO.getOutTradeNo(), resp.getOutTradeNo());
assertEquals(PayOrderDisplayModeEnum.APP.getMode(), resp.getDisplayMode());
assertSame(response, resp.getRawData());
}
@Test
@DisplayName("支付宝小程序支付:证书模式下单成功")
public void testUnifiedOrder_certificateMode() throws AlipayApiException {
config.setMode(MODE_CERTIFICATE);
String buyerOpenId = randomString();
AlipayTradeCreateResponse response = randomPojo(AlipayTradeCreateResponse.class, o -> o.setSubCode(""));
when(defaultAlipayClient.certificateExecute(argThat(assertRequest(config.getAppId(), null, buyerOpenId))))
.thenReturn(response);
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
reqDTO.setChannelExtras(Map.of(AlipayLitePayClient.BUYER_OPEN_ID_KEY, buyerOpenId));
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
assertEquals(WAITING.getStatus(), resp.getStatus());
assertEquals(reqDTO.getOutTradeNo(), resp.getOutTradeNo());
assertEquals(PayOrderDisplayModeEnum.APP.getMode(), resp.getDisplayMode());
assertSame(response, resp.getRawData());
}
@Test
@DisplayName("支付宝小程序支付:渠道返回失败")
public void testUnifiedOrder_channelFailed() throws AlipayApiException {
String subCode = randomString();
String subMsg = randomString();
AlipayTradeCreateResponse response = randomPojo(AlipayTradeCreateResponse.class, o -> {
o.setSubCode(subCode);
o.setSubMsg(subMsg);
});
when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeCreateRequest>) request -> true)))
.thenReturn(response);
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
reqDTO.setChannelExtras(Map.of(AlipayLitePayClient.BUYER_OPEN_ID_KEY, randomString()));
PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
assertEquals(CLOSED.getStatus(), resp.getStatus());
assertEquals(reqDTO.getOutTradeNo(), resp.getOutTradeNo());
assertNull(resp.getChannelOrderNo());
assertNull(resp.getChannelUserId());
assertNull(resp.getSuccessTime());
assertNull(resp.getDisplayMode());
assertNull(resp.getDisplayContent());
assertSame(response, resp.getRawData());
assertEquals(subCode, resp.getChannelErrorCode());
assertEquals(subMsg, resp.getChannelErrorMsg());
}
@Test
@DisplayName("支付宝小程序支付:买家标识为空")
public void testUnifiedOrder_buyerEmpty() {
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
reqDTO.setChannelExtras(new HashMap<>());
assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO));
}
@Test
@DisplayName("支付宝小程序支付:抛出业务异常")
public void testUnifiedOrder_throwServiceException() throws AlipayApiException {
when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeCreateRequest>) request -> true)))
.thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
reqDTO.setChannelExtras(Map.of(AlipayLitePayClient.BUYER_OPEN_ID_KEY, randomString()));
assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO));
}
@Test
@DisplayName("支付宝小程序支付:抛出系统异常")
public void testUnifiedOrder_throwPayException() throws AlipayApiException {
when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeCreateRequest>) request -> true)))
.thenThrow(new RuntimeException("系统异常"));
PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
reqDTO.setChannelExtras(Map.of(AlipayLitePayClient.BUYER_OPEN_ID_KEY, randomString()));
assertThrows(PayClientException.class, () -> client.unifiedOrder(reqDTO));
}
private static ArgumentMatcher<AlipayTradeCreateRequest> assertRequest(String appId, String buyerId,
String buyerOpenId) {
return request -> {
AlipayTradeCreateModel model = (AlipayTradeCreateModel) request.getBizModel();
assertEquals("JSAPI_PAY", model.getProductCode());
assertEquals(appId, model.getOpAppId());
assertEquals(buyerId, model.getBuyerId());
assertEquals(buyerOpenId, model.getBuyerOpenId());
return true;
};
}
}
@@ -1,13 +1,13 @@
package cn.iocoder.yudao.module.system.controller.admin.socail;
package cn.iocoder.yudao.module.system.controller.admin.social;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientRespVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientRespVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
import cn.iocoder.yudao.module.system.service.social.SocialClientService;
import io.swagger.v3.oas.annotations.Operation;
@@ -1,14 +1,14 @@
package cn.iocoder.yudao.module.system.controller.admin.socail;
package cn.iocoder.yudao.module.system.controller.admin.social;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserBindReqVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserRespVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserUnbindReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.user.SocialUserBindReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.user.SocialUserRespVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.user.SocialUserUnbindReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import io.swagger.v3.oas.annotations.Operation;
@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.socail.vo.client;
package cn.iocoder.yudao.module.system.controller.admin.social.vo.client;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.socail.vo.client;
package cn.iocoder.yudao.module.system.controller.admin.social.vo.client;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.socail.vo.client;
package cn.iocoder.yudao.module.system.controller.admin.social.vo.client;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.socail.vo.user;
package cn.iocoder.yudao.module.system.controller.admin.social.vo.user;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.socail.vo.user;
package cn.iocoder.yudao.module.system.controller.admin.social.vo.user;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.socail.vo.user;
package cn.iocoder.yudao.module.system.controller.admin.social.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.system.controller.admin.socail.vo.user;
package cn.iocoder.yudao.module.system.controller.admin.social.vo.user;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.social;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
import org.apache.ibatis.annotations.Mapper;
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.social;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import org.apache.ibatis.annotations.Mapper;
@@ -166,7 +166,7 @@ public class MailTemplateServiceImpl implements MailTemplateService {
// 提取 <pre> 标签内的内容
String innerContent = matcher.group(1);
// 返回 div 标签包裹的内容
matcher.appendReplacement(sb, "<div>" + innerContent + "</div>");
matcher.appendReplacement(sb, Matcher.quoteReplacement("<div>" + innerContent + "</div>"));
}
matcher.appendTail(sb);
return sb.toString();
@@ -207,7 +207,8 @@ public class MailTemplateServiceImpl implements MailTemplateService {
String codeBlock = matcher.group(1);
// 为代码块添加样式
String replacement = "<pre style=\"background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto;\"><code>" + codeBlock + "</code></pre>";
matcher.appendReplacement(sb, replacement);
// 代码块内容可能包含 $ 或 \\,需要 quoteReplacement,否则会抛 IllegalArgumentException: Illegal group reference
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(sb);
return sb.toString();
@@ -6,8 +6,8 @@ import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderNotifyConfirmReceiveReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import jakarta.validation.Valid;
@@ -26,8 +26,8 @@ import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderNotifyConfirmReceiveReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
import jakarta.validation.Valid;
@@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
@@ -398,4 +398,18 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
assertFalse(formattedContent.contains("{activationLink}"));
}
@Test
public void testFormatMailTemplateContent_dollarInCodeBlock() {
// 准备参数
Map<String, Object> params = new HashMap<>();
// 测试代码块中包含 $ 与 \\(历史上 appendReplacement 会抛 IllegalArgumentException: Illegal group reference)
String content = "<pre><code>echo $HOME && ls \\root</code></pre>";
// 调用,并断言内容原样保留
String result = mailTemplateService.formatMailTemplateContent(content, params);
assertTrue(result.contains("echo $HOME && ls \\root"));
assertTrue(result.contains("<div><code>"));
}
}
@@ -12,8 +12,8 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaOrderUploadShippingInfoReqDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.client.SocialClientSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.social.vo.user.SocialUserPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;