对齐 boot 与 cloud 的代码

This commit is contained in:
YunaiV 2023-07-27 13:00:52 +08:00
parent 21b3744544
commit 859cf74a6c
25 changed files with 63 additions and 95 deletions

View File

@ -102,7 +102,7 @@
系统内置多种多种业务功能,可以用于快速你的业务系统: 系统内置多种多种业务功能,可以用于快速你的业务系统:
![功能分层](https://static.iocoder.cn/ruoyi-vue-pro-biz.png) ![功能分层](https://static.iocoder.cn/ruoyi-vue-pro-biz.png?imageView2/2/format/webp)
* 系统功能 * 系统功能
* 基础设施 * 基础设施

View File

@ -216,10 +216,9 @@
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 --> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
<version>${dynamic-datasource.version}</version> <version>${dynamic-datasource.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.yulichang</groupId> <groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
<version>${mybatis-plus-join-boot-starter.version}</version> <version>${mybatis-plus-join-boot-starter.version}</version>
</dependency> </dependency>

View File

@ -64,9 +64,8 @@
<dependency> <dependency>
<groupId>com.github.yulichang</groupId> <groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -6,10 +6,10 @@ import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.toolkit.Db; import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.github.yulichang.base.MPJBaseMapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.Collection; import java.util.Collection;
@ -17,10 +17,8 @@ import java.util.List;
/** /**
* MyBatis Plus BaseMapper 的基础上拓展提供更多的能力 * MyBatis Plus BaseMapper 的基础上拓展提供更多的能力
* <p>
* 为什么继承 MPJBaseMapper 接口支持 MyBatis Plus 多表 Join 的能力
*/ */
public interface BaseMapperX<T> extends MPJBaseMapper<T> { public interface BaseMapperX<T> extends BaseMapper<T> {
default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) { default PageResult<T> selectPage(PageParam pageParam, @Param("ew") Wrapper<T> queryWrapper) {
// MyBatis Plus 查询 // MyBatis Plus 查询
@ -46,18 +44,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)); return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
} }
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
SFunction<T, ?> field3, Object value3) {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
.eq(field3, value3));
}
default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
SFunction<T, ?> field3, Object value3, SFunction<T, ?> field4, Object value4) {
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)
.eq(field3, value3).eq(field4, value4));
}
default Long selectCount() { default Long selectCount() {
return selectCount(new QueryWrapper<T>()); return selectCount(new QueryWrapper<T>());
} }
@ -117,26 +103,8 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
update(update, new QueryWrapper<>()); update(update, new QueryWrapper<>());
} }
/**
* 根据ID 批量更新适合大量数据更新
*
* @param entities 实体们
*/
default void updateBatch(Collection<T> entities) {
Db.updateBatchById(entities);
}
default void updateBatch(Collection<T> entities, int size) { default void updateBatch(Collection<T> entities, int size) {
Db.updateBatchById(entities, size); Db.updateBatchById(entities, size);
} }
/**
* 批量修改插入, 会根据实体的主键是否为空更新还是修改默认为 1000
*
* @param entities 实体们
*/
default void saveOrUpdateBatch(Collection<T> entities){
Db.saveOrUpdateBatch(entities);
}
} }

View File

@ -60,7 +60,7 @@ public class YudaoWebSecurityConfigurerAdapter {
/** /**
* 自定义的权限映射 Bean * 自定义的权限映射 Bean
* *
* @see #configure(HttpSecurity) * @see #filterChain(HttpSecurity)
*/ */
@Resource @Resource
private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers; private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;
@ -79,7 +79,7 @@ public class YudaoWebSecurityConfigurerAdapter {
/** /**
* 配置 URL 的安全配置 * 配置 URL 的安全配置
* <p> *
* anyRequest | 匹配所有请求路径 * anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问 * access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问 * anonymous | 匿名可以访问
@ -141,7 +141,6 @@ public class YudaoWebSecurityConfigurerAdapter {
// 添加 Token Filter // 添加 Token Filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build(); return httpSecurity.build();
} }

View File

@ -40,6 +40,7 @@ public class BaseDbAndRedisUnitTest {
// Redis 配置类 // Redis 配置类
RedisTestConfiguration.class, // Redis 测试配置类用于启动 RedisServer RedisTestConfiguration.class, // Redis 测试配置类用于启动 RedisServer
// RedisAutoConfiguration.class, // Spring Redis 自动配置类
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类 YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动高配置类 RedissonAutoConfiguration.class, // Redisson 自动高配置类
}) })

View File

@ -52,7 +52,8 @@ public class YudaoSwaggerAutoConfiguration {
// 接口信息 // 接口信息
.info(buildInfo(properties)) .info(buildInfo(properties))
// 接口安全配置 // 接口安全配置
.components(new Components().securitySchemes(securitySchemas)); .components(new Components().securitySchemes(securitySchemas))
.addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION));
securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key))); securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));
return openAPI; return openAPI;
} }

View File

@ -47,7 +47,7 @@ public class BpmTaskController {
@Parameter(name = "processInstanceId", description = "流程实例的编号", required = true) @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:task:query')") @PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId( public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
@RequestParam("processInstanceId") String processInstanceId) { @RequestParam("processInstanceId") String processInstanceId) {
return success(taskService.getTaskListByProcessInstanceId(processInstanceId)); return success(taskService.getTaskListByProcessInstanceId(processInstanceId));
} }

View File

@ -23,10 +23,10 @@ import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgn
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
/** /**
* {@link BpmUserGroupServiceImpl} 的单元测试类 * {@link BpmUserGroupServiceImpl} 的单元测试类
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@Import(BpmUserGroupServiceImpl.class) @Import(BpmUserGroupServiceImpl.class)
public class BpmUserGroupServiceTest extends BaseDbUnitTest { public class BpmUserGroupServiceTest extends BaseDbUnitTest {

View File

@ -149,7 +149,7 @@ public class AuthController {
@Parameter(name = "redirectUri", description = "回调路径") @Parameter(name = "redirectUri", description = "回调路径")
}) })
public CommonResult<String> socialLogin(@RequestParam("type") Integer type, public CommonResult<String> socialLogin(@RequestParam("type") Integer type,
@RequestParam("redirectUri") String redirectUri) { @RequestParam("redirectUri") String redirectUri) {
return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri)); return CommonResult.success(socialUserService.getAuthorizeUrl(type, redirectUri));
} }

View File

@ -8,7 +8,7 @@ import lombok.NoArgsConstructor;
import java.util.Set; import java.util.Set;
@Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表") @Schema(description = "管理后台 - 登录用户的权限信息 Response VO额外包括用户信息和角色列表")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@ -37,7 +37,7 @@ public class AuthPermissionInfoRespVO {
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码") @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String nickname; private String nickname;
@Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://www.iocoder.cn/xx.jpg") @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.jpg")
private String avatar; private String avatar;
} }

View File

@ -11,9 +11,9 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
/** /**
* 邮件日志 Base VO提供给添加修改详细的子 VO 使用 * 邮件日志 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成 * 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/ */
@Data @Data
public class MailLogBaseVO { public class MailLogBaseVO {

View File

@ -40,6 +40,3 @@ tenant-id: {{adminTenentId}}
GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10 GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10
Authorization: Bearer {{token}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} tenant-id: {{adminTenentId}}
###

View File

@ -20,7 +20,7 @@ public class PermissionAssignRoleDataScopeReqVO {
// TODO 这里要多一个枚举校验 // TODO 这里要多一个枚举校验
private Integer dataScope; private Integer dataScope;
@Schema(description = "部门编号列表,只有范围类型为 DEPT_CUSTOM 时,该字段才需要", example = "1,3,5") @Schema(description = "部门编号列表只有范围类型为 DEPT_CUSTOM 时,该字段才需要", example = "1,3,5")
private Set<Long> dataScopeDeptIds = Collections.emptySet(); // 兜底 private Set<Long> dataScopeDeptIds = Collections.emptySet(); // 兜底
} }

View File

@ -7,9 +7,9 @@ import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
/** /**
* 敏感词 Base VO提供给添加修改详细的子 VO 使用 * 敏感词 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成 * 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/ */
@Data @Data
public class SensitiveWordBaseVO { public class SensitiveWordBaseVO {

View File

@ -7,9 +7,9 @@ import javax.validation.constraints.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
/** /**
* 租户 Base VO提供给添加修改详细的子 VO 使用 * 租户 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成 * 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/ */
@Data @Data
public class TenantBaseVO { public class TenantBaseVO {

View File

@ -25,7 +25,7 @@ public class UserProfileUpdateReqVO {
@Length(min = 11, max = 11, message = "手机号长度必须 11 位") @Length(min = 11, max = 11, message = "手机号长度必须 11 位")
private String mobile; private String mobile;
@Schema(description = "用户性别-参见 SexEnum 枚举类", example = "1") @Schema(description = "用户性别参见 SexEnum 枚举类", example = "1")
private Integer sex; private Integer sex;
} }

View File

@ -29,7 +29,7 @@ public class UserExportReqVO {
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime; private LocalDateTime[] createTime;
@Schema(description = "部门编号,同时筛选子部门", example = "1024") @Schema(description = "部门编号同时筛选子部门", example = "1024")
private Long deptId; private Long deptId;
} }

View File

@ -6,7 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 用户分页时的信息 Response VO,相比用户基本信息来说,会多部门信息") @Schema(description = "管理后台 - 用户分页时的信息 Response VO相比用户基本信息来说,会多部门信息")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.system.controller.admin.user.vo.user; package cn.iocoder.yudao.module.system.controller.admin.user.vo.user;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@ -13,9 +15,9 @@ public class UserUpdateStatusReqVO {
@NotNull(message = "角色编号不能为空") @NotNull(message = "角色编号不能为空")
private Long id; private Long id;
@Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "状态见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空") @NotNull(message = "状态不能为空")
// @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}") @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
private Integer status; private Integer status;
} }

View File

@ -197,8 +197,8 @@ public class MenuServiceImpl implements MenuService {
return Collections.emptyList(); return Collections.emptyList();
} }
return menuCache.values().stream().filter(menu -> menuIds.contains(menu.getId()) return menuCache.values().stream().filter(menu -> menuIds.contains(menu.getId())
&& menuTypes.contains(menu.getType()) && menuTypes.contains(menu.getType())
&& menusStatuses.contains(menu.getStatus())) && menusStatuses.contains(menu.getStatus()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@ -297,7 +297,7 @@ public class AdminUserServiceImpl implements AdminUserService {
} }
private void validateUserForCreateOrUpdate(Long id, String username, String mobile, String email, private void validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
Long deptId, Set<Long> postIds) { Long deptId, Set<Long> postIds) {
// 关闭数据权限避免因为没有数据权限查询不到数据进而导致唯一校验不正确 // 关闭数据权限避免因为没有数据权限查询不到数据进而导致唯一校验不正确
DataPermissionUtils.executeIgnore(() -> { DataPermissionUtils.executeIgnore(() -> {
// 校验用户存在 // 校验用户存在

View File

@ -148,7 +148,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
); );
} }
@Test @Test
public void testLogin_success() { public void testLogin_success() {
// 准备参数 // 准备参数
AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o -> AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->
@ -174,9 +174,9 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
assertPojoEquals(accessTokenDO, loginRespVO); assertPojoEquals(accessTokenDO, loginRespVO);
// 校验调用参数 // 校验调用参数
verify(loginLogService).createLoginLog( verify(loginLogService).createLoginLog(
argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
&& o.getResult().equals(LoginResultEnum.SUCCESS.getResult()) && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())
&& o.getUserId().equals(user.getId())) && o.getUserId().equals(user.getId()))
); );
verify(socialUserService).bindSocialUser(eq(new SocialUserBindReqDTO( verify(socialUserService).bindSocialUser(eq(new SocialUserBindReqDTO(
user.getId(), UserTypeEnum.ADMIN.getValue(), user.getId(), UserTypeEnum.ADMIN.getValue(),
@ -317,8 +317,8 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
assertServiceException(() -> authService.validateCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR, "就是不对"); assertServiceException(() -> authService.validateCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR, "就是不对");
// 校验调用参数 // 校验调用参数
verify(loginLogService).createLoginLog( verify(loginLogService).createLoginLog(
argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType()) argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())
&& o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult())) && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))
); );
} }
@ -349,8 +349,9 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
// 调用 // 调用
authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType()); authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());
// 校验调用参数 // 校验调用参数
verify(loginLogService).createLoginLog(argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType()) verify(loginLogService).createLoginLog(
&& o.getResult().equals(LoginResultEnum.SUCCESS.getResult())) argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())
&& o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))
); );
// 调用并校验 // 调用并校验

View File

@ -25,10 +25,10 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
/** /**
* {@link MailLogServiceImpl} 的单元测试类 * {@link MailLogServiceImpl} 的单元测试类
* *
* @author 芋道源码 * @author 芋道源码
*/ */
@Import(MailLogServiceImpl.class) @Import(MailLogServiceImpl.class)
public class MailLogServiceImplTest extends BaseDbUnitTest { public class MailLogServiceImplTest extends BaseDbUnitTest {

View File

@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
class MailSendServiceImplTest extends BaseMockitoUnitTest { public class MailSendServiceImplTest extends BaseMockitoUnitTest {
@InjectMocks @InjectMocks
private MailSendServiceImpl mailSendService; private MailSendServiceImpl mailSendService;
@ -278,16 +278,17 @@ class MailSendServiceImplTest extends BaseMockitoUnitTest {
// mock 方法发送邮件 // mock 方法发送邮件
String messageId = randomString(); String messageId = randomString();
mailUtilMock.when(() -> MailUtil.send(argThat(mailAccount -> { mailUtilMock.when(() -> MailUtil.send(
assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom()); argThat(mailAccount -> {
assertTrue(mailAccount.isAuth()); assertEquals("芋艿 <7685@qq.com>", mailAccount.getFrom());
assertEquals(account.getUsername(), mailAccount.getUser()); assertTrue(mailAccount.isAuth());
assertEquals(account.getPassword(), mailAccount.getPass()); assertEquals(account.getUsername(), mailAccount.getUser());
assertEquals(account.getHost(), mailAccount.getHost()); assertEquals(account.getPassword(), mailAccount.getPass());
assertEquals(account.getPort(), mailAccount.getPort()); assertEquals(account.getHost(), mailAccount.getHost());
assertEquals(account.getSslEnable(), mailAccount.isSslEnable()); assertEquals(account.getPort(), mailAccount.getPort());
return true; assertEquals(account.getSslEnable(), mailAccount.isSslEnable());
}), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true))) return true;
}), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true)))
.thenReturn(messageId); .thenReturn(messageId);
// 调用 // 调用