Merge branch 'feature/multi-module' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/flowable

 Conflicts:
	pom.xml
	yudao-module-bpm/yudao-module-bpm-activiti/src/main/java/cn/iocoder/yudao/adminserver/modules/bpm/framework/activiti/config/BpmActivitiConfiguration.java
This commit is contained in:
YunaiV 2022-01-29 10:06:00 +08:00
commit 6aca4ae9fd
127 changed files with 1554 additions and 1248 deletions

View File

@ -2,6 +2,10 @@
"local": {
"baseUrl": "http://127.0.0.1:48080/api",
"userServerUrl": "http://127.0.0.1:28080/api",
"token": "test1"
"token": "test1",
"userApi": "http://127.0.0.1:48080/app-api",
"userToken": "test1",
"userTenentId": "1"
}
}

View File

@ -13,6 +13,7 @@
<module>yudao-admin-server</module>
<module>yudao-user-server</module>
<module>yudao-core-service</module>
<module>yudao-module-member</module>
<module>yudao-module-bpm</module>
</modules>
@ -21,13 +22,14 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>1.3.0-snapshot</revision>
<revision>1.4.0-snapshot</revision>
<!-- Maven 相关 -->
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<!-- 看看咋放到 bom 里 -->
<lombok.version>1.18.20</lombok.version>
<mapstruct.version>1.4.1.Final</mapstruct.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -55,6 +57,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<!-- TODO 看看咋放到 bom 里 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>

View File

@ -13,10 +13,21 @@
<packaging>jar</packaging>
<name>yudao-admin-server</name>
<description>管理后台 Server提供其 API 接口</description>
<description>
后端 Server 的主项目,通过引入需要 yudao-module-xxx 的依赖,
从而实现提供 RESTful API 给 yudao-ui-admin、yudao-ui-user 等前端项目。
本质上来说,它就是个空壳(容器)!
</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
<!-- TODO 芋艿:多模块 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-member-impl</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>

View File

@ -4,7 +4,8 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${yudao.info.base-package} ${yudao.core-service.base-package}
@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}", "${yudao.core-service.base-package}"})
@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}", "${yudao.core-service.base-package}",
"${yudao.info.member-package}"}) // TODO 芋艿重构
public class AdminServerApplication {
public static void main(String[] args) {

View File

@ -23,19 +23,28 @@ public class SecurityConfiguration {
public Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer() {
return registry -> {
// 验证码的接口
registry.antMatchers(api("/system/captcha/**")).anonymous();
registry.antMatchers(buildAdminApi("/system/captcha/**")).anonymous();
// 获得租户编号的接口
registry.antMatchers(api("/system/tenant/get-id-by-name")).anonymous();
registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).anonymous();
// Spring Boot Admin Server 的安全配置
registry.antMatchers(adminSeverContextPath).anonymous()
.antMatchers(adminSeverContextPath + "/**").anonymous();
// 短信回调 API
registry.antMatchers(api("/system/sms/callback/**")).anonymous();
registry.antMatchers(buildAdminApi("/system/sms/callback/**")).anonymous();
// 设置 App API 无需认证
registry.antMatchers(buildAppApi("/**")).permitAll();
};
}
private String api(String url) {
return webProperties.getApiPrefix() + url;
private String buildAdminApi(String url) {
// TODO 芋艿多模块
return webProperties.getAdminApi().getPrefix() + url;
}
private String buildAppApi(String url) {
// TODO 芋艿多模块
return webProperties.getAppApi().getPrefix() + url;
}
}

View File

@ -26,6 +26,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.context.annotation.Lazy;
@ -60,8 +61,6 @@ import static java.util.Collections.singleton;
@Slf4j
public class SysAuthServiceImpl implements SysAuthService {
private static final UserTypeEnum USER_TYPE_ENUM = UserTypeEnum.ADMIN;
@Resource
@Lazy // 延迟加载因为存在相互依赖的问题
private AuthenticationManager authenticationManager;
@ -83,7 +82,6 @@ public class SysAuthServiceImpl implements SysAuthService {
@Resource
private SysSocialCoreService socialService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 获取 username 对应的 SysUserDO
@ -157,7 +155,8 @@ public class SysAuthServiceImpl implements SysAuthService {
try {
// 调用 Spring Security AuthenticationManager#authenticate(...) 方法使用账号密码进行认证
// 在其内部会调用到 loadUserByUsername 方法获取 User 信息
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
username, password, getUserType()));
// org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
} catch (BadCredentialsException badCredentialsException) {
this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.BAD_CREDENTIALS);
@ -216,7 +215,7 @@ public class SysAuthServiceImpl implements SysAuthService {
// 如果未绑定 SysSocialUserDO 用户则无法自动登录进行报错
String unionId = socialService.getAuthUserUnionId(authUser);
List<SysSocialUserDO> socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, USER_TYPE_ENUM);
List<SysSocialUserDO> socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, getUserType());
if (CollUtil.isEmpty(socialUsers)) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
@ -232,7 +231,7 @@ public class SysAuthServiceImpl implements SysAuthService {
LoginUser loginUser = this.buildLoginUser(user);
// 绑定社交用户更新
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM);
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, getUserType());
// 缓存登录用户到 Redis 返回 sessionId 编号
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
@ -248,7 +247,7 @@ public class SysAuthServiceImpl implements SysAuthService {
LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword());
// 绑定社交用户新增
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM);
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, getUserType());
// 缓存登录用户到 Redis 返回 sessionId 编号
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
@ -261,7 +260,7 @@ public class SysAuthServiceImpl implements SysAuthService {
Assert.notNull(authUser, "授权用户不为空");
// 绑定社交用户新增
socialService.bindSocialUser(userId, reqVO.getType(), authUser, USER_TYPE_ENUM);
socialService.bindSocialUser(userId, reqVO.getType(), authUser, getUserType());
}
@Override
@ -277,12 +276,17 @@ public class SysAuthServiceImpl implements SysAuthService {
this.createLogoutLog(loginUser.getId(), loginUser.getUsername());
}
@Override
public UserTypeEnum getUserType() {
return UserTypeEnum.ADMIN;
}
private void createLogoutLog(Long userId, String username) {
SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO();
reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType());
reqDTO.setTraceId(TracerUtils.getTraceId());
reqDTO.setUserId(userId);
reqDTO.setUserType(USER_TYPE_ENUM.getValue());
reqDTO.setUserType(getUserType().getValue());
reqDTO.setUsername(username);
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(ServletUtils.getClientIP());

View File

@ -24,6 +24,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@ -48,7 +49,7 @@ public class SysUserServiceImpl implements SysUserService {
@Value("${sys.user.init-password:yudaoyuanma}")
private String userInitPassword;
@Resource
@Resource(name = "sysUserMapper") // userMapper 存在重名
private SysUserMapper userMapper;
@Resource

View File

@ -55,6 +55,19 @@ spring:
username: root
password: 123456
activiti:
#1.false:默认值activiti启动时对比数据库表中保存的版本如果不匹配。将抛出异常
#2.true:启动时会对数据库中所有表进行更新操作,如果表存在,不做处理,反之,自动创建表
#3.create_drop:启动时自动创建表,关闭时自动删除表
#4.drop_create:启动时,删除旧表,再创建新表
database-schema-update: true
#activiti7默认不生成历史信息表需手动设置开启
db-history-used: true
check-process-definitions: true
#full保存历史数据的最高级别可保存全部流程相关细节包括流程流转各节点参数
history-level: full
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
@ -161,6 +174,18 @@ logging:
cn.iocoder.yudao.coreservice.modules.system.dal.mysql: debug
cn.iocoder.yudao.coreservice.modules.tool.dal.mysql: debug
--- #################### 微信公众号相关配置 ####################
wx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档
mp:
# 公众号配置(必填)
app-id: wx041349c6f39b268b
secret: 5abee519483bc9f8cb37ce280e814bd0
# 存储配置,解决 AccessToken 的跨节点的共享
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wx # Redis Key 的前缀 TODO 芋艿:解决下 Redis key 管理的配置
http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置

View File

@ -48,11 +48,17 @@ yudao:
info:
version: 1.0.0
base-package: cn.iocoder.yudao.adminserver
member-package: cn.iocoder.yudao.module.member
core-service:
base-package: cn.iocoder.yudao.coreservice
web:
api-prefix: /api
controller-package: ${yudao.info.base-package}
admin-api:
prefix: /api
controller: ${yudao.info.base-package}
app-api:
prefix: /app-api
controller: cn.iocoder.yudao.module.member.controller.app
swagger:
title: 管理后台
description: 提供管理员管理的所有功能
@ -72,7 +78,13 @@ yudao:
- cn.iocoder.yudao.adminserver.modules.bpm.enums.BpmErrorCodeConstants
tenant: # 多租户相关配置项
tables: # 配置需要开启多租户的表;如果实体已经继承 TenantBaseDO 类,则无需重复配置
url:
url: ## TODO 芋艿:迁移到 web 配置项下,
admin-ui: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址
sms-code: # 短信验证码相关的配置项
expire-times: 10m
send-frequency: 1m
send-maximum-quantity-per-day: 10
begin-code: 9999 # 这里配置 9999 的原因是,测试方便。
end-code: 9999 # 这里配置 9999 的原因是,测试方便。
debug: false

View File

@ -9,7 +9,6 @@ import cn.iocoder.yudao.coreservice.modules.infra.dal.dataobject.file.InfFileDO;
*/
public interface InfFileCoreService {
/**
* 保存文件并返回文件的访问路径
*

View File

@ -1,6 +0,0 @@
/**
* 提供 POJO 类的实体转换
*
* 目前使用 MapStruct 框架
*/
package cn.iocoder.yudao.coreservice.modules.member.convert;

View File

@ -1,10 +0,0 @@
package cn.iocoder.yudao.coreservice.modules.member.dal.mysql.user;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MbrUserCoreMapper extends BaseMapperX<MbrUserDO> {
}

View File

@ -1,7 +0,0 @@
/**
* member 包下我们放会员业务.
* 例如说会员中心等等
*
* 缩写mbr
*/
package cn.iocoder.yudao.coreservice.modules.member;

View File

@ -1,19 +0,0 @@
package cn.iocoder.yudao.coreservice.modules.member.service.user;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
/**
* 前台用户 Core Service 接口
*
* @author 芋道源码
*/
public interface MbrUserCoreService {
/**
* 通过用户 ID 查询用户
*
* @param id 用户ID
* @return 用户对象信息
*/
MbrUserDO getUser(Long id);
}

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.coreservice.modules.member.service.user.impl;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.coreservice.modules.member.dal.mysql.user.MbrUserCoreMapper;
import cn.iocoder.yudao.coreservice.modules.member.service.user.MbrUserCoreService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* User Core Service 实现类
*
* @author 芋道源码
*/
@Service
@Slf4j
public class MbrUserCoreServiceImpl implements MbrUserCoreService {
@Resource
private MbrUserCoreMapper userCoreMapper;
@Override
public MbrUserDO getUser(Long id) {
return userCoreMapper.selectById(id);
}
}

View File

@ -2,8 +2,6 @@ package cn.iocoder.yudao.coreservice.modules.system.service.sms.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.coreservice.modules.member.service.user.MbrUserCoreService;
import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.sms.SysSmsTemplateDO;
import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO;
import cn.iocoder.yudao.coreservice.modules.system.mq.message.sms.SysSmsSendMessage;
@ -43,8 +41,6 @@ public class SysSmsCoreServiceImpl implements SysSmsCoreService {
@Resource
private SysUserCoreService sysUserCoreService;
@Resource
private MbrUserCoreService mbrUserCoreService;
@Resource
private SysSmsTemplateCoreService smsTemplateCoreService;
@Resource
private SysSmsLogCoreService smsLogCoreService;
@ -72,10 +68,11 @@ public class SysSmsCoreServiceImpl implements SysSmsCoreService {
public Long sendSingleSmsToMember(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {
// 如果 mobile 为空则加载用户编号对应的手机号
if (StrUtil.isEmpty(mobile)) {
MbrUserDO user = mbrUserCoreService.getUser(userId);
if (user != null) {
mobile = user.getMobile();
}
// MbrUserDO user = mbrUserCoreService.getUser(userId);
// if (user != null) {
// mobile = user.getMobile();
// }
// TODO 芋艿重构
}
// 执行发送
return this.sendSingleSms(mobile, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);

View File

@ -1,7 +0,0 @@
/**
* tool 包下我们放研发工具提升研发效率与质量
* 例如说代码生成器接口文档等等
*
* 缩写tool
*/
package cn.iocoder.yudao.coreservice.modules.tool;

View File

@ -14,7 +14,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>1.3.0-snapshot</revision>
<revision>1.4.0-snapshot</revision>
<!-- 统一依赖管理 -->
<spring.boot.version>2.4.12</spring.boot.version>
<!-- Web 相关 -->

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.hutool.core.lang.Matcher;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -22,4 +24,8 @@ public enum UserTypeEnum {
*/
private final String name;
public static UserTypeEnum valueOf(Integer value) {
return ArrayUtil.firstMatch(userType -> userType.getValue().equals(value), UserTypeEnum.values());
}
}

View File

@ -149,6 +149,7 @@ public class OperateLogAspect {
private static void fillUserFields(OperateLogCreateReqDTO operateLogDTO) {
operateLogDTO.setUserId(WebFrameworkUtils.getLoginUserId());
operateLogDTO.setUserType(WebFrameworkUtils.getLoginUserType());
}
private static void fillModuleFields(OperateLogCreateReqDTO operateLogDTO,

View File

@ -21,6 +21,9 @@ public class OperateLogCreateReqDTO {
@ApiModelProperty(value = "用户编号", required = true, example = "1024")
@NotNull(message = "用户编号不能为空")
private Long userId;
@ApiModelProperty(value = "用户类型", required = true, example = "1")
@NotNull(message = "用户类型不能为空")
private Integer userType;
@ApiModelProperty(value = "操作模块", required = true, example = "订单")
@NotEmpty(message = "操作模块不能为空")

View File

@ -15,7 +15,8 @@ import org.springframework.context.annotation.Configuration;
* @author 芋道源码
*/
@Configuration
@MapperScan(value = {"${yudao.info.base-package}", "${yudao.core-service.base-package}"}, annotationClass = Mapper.class,
@MapperScan(value = {"${yudao.info.base-package}", "${yudao.core-service.base-package}", "${yudao.info.member-package}"},
annotationClass = Mapper.class,
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载目前仅用于单元测试
public class YudaoMybatisAutoConfiguration {

View File

@ -1,12 +1,14 @@
package cn.iocoder.yudao.framework.security.config;
import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@ -20,6 +22,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.annotation.Resource;
import java.util.List;
/**
* Spring Security 自动配置类主要用于相关组件的配置
@ -29,7 +32,7 @@ import javax.annotation.Resource;
*
* @author 芋道源码
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SecurityProperties.class)
public class YudaoSecurityAutoConfiguration {
@ -64,8 +67,8 @@ public class YudaoSecurityAutoConfiguration {
* 退出处理类 Bean
*/
@Bean
public LogoutSuccessHandler logoutSuccessHandler(SecurityAuthFrameworkService securityFrameworkService) {
return new LogoutSuccessHandlerImpl(securityProperties, securityFrameworkService);
public LogoutSuccessHandler logoutSuccessHandler(MultiUserDetailsAuthenticationProvider authenticationProvider) {
return new LogoutSuccessHandlerImpl(securityProperties, authenticationProvider);
}
/**
@ -83,9 +86,19 @@ public class YudaoSecurityAutoConfiguration {
* Token 认证过滤器 Bean
*/
@Bean
public JWTAuthenticationTokenFilter authenticationTokenFilter(SecurityAuthFrameworkService securityFrameworkService,
public JWTAuthenticationTokenFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
GlobalExceptionHandler globalExceptionHandler) {
return new JWTAuthenticationTokenFilter(securityProperties, securityFrameworkService, globalExceptionHandler);
return new JWTAuthenticationTokenFilter(securityProperties, authenticationProvider, globalExceptionHandler);
}
/**
* 身份验证的 Provider Bean通过它实现账号 + 密码的认证
*/
@Bean
public MultiUserDetailsAuthenticationProvider authenticationProvider(
List<SecurityAuthFrameworkService> securityFrameworkServices,
WebProperties webProperties, PasswordEncoder passwordEncoder) {
return new MultiUserDetailsAuthenticationProvider(securityFrameworkServices, webProperties, passwordEncoder);
}
/**

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.security.config;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.web.config.WebProperties;
@ -35,16 +36,8 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
@Resource
private WebProperties webProperties;
/**
* 自定义用户认证逻辑
*/
@Resource
private SecurityAuthFrameworkService userDetailsService;
/**
* Spring Security 加密器
*/
@Resource
private PasswordEncoder passwordEncoder;
private MultiUserDetailsAuthenticationProvider authenticationProvider;
/**
* 认证失败处理类 Bean
*/
@ -65,13 +58,15 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
*/
@Resource
private JWTAuthenticationTokenFilter authenticationTokenFilter;
/**
* 自定义的权限映射 Bean
*
* @see #configure(HttpSecurity)
*/
@Resource
private Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry> authorizeRequestsCustomizer;
private Customizer<ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry>
authorizeRequestsCustomizer;
/**
* 由于 Spring Security 创建 AuthenticationManager 对象时没声明 @Bean 注解导致无法被注入
@ -89,8 +84,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
auth.authenticationProvider(authenticationProvider);
}
/**
@ -123,16 +117,16 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
// 一堆自定义的 Spring Security 处理器
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler).and()
.logout().logoutUrl(api("/logout")).logoutSuccessHandler(logoutSuccessHandler); // 登出
.logout().logoutUrl(buildAdminApi("/logout")).logoutSuccessHandler(logoutSuccessHandler); // 登出
// 设置每个请求的权限 全局共享规则
httpSecurity.authorizeRequests()
// 登录的接口可匿名访问
.antMatchers(api("/login")).anonymous()
.antMatchers(buildAdminApi("/login")).anonymous()
// 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
// 文件的获取接口可匿名访问
.antMatchers(api("/infra/file/get/**")).anonymous()
.antMatchers(buildAdminApi("/infra/file/get/**")).anonymous()
// Swagger 接口文档
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
@ -143,11 +137,11 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
.antMatchers("/actuator/**").anonymous()
// Druid 监控 TODO 芋艿等对接了 druid admin 在调整下
.antMatchers("/druid/**").anonymous()
// oAuth2 auth2/login/gitee
.antMatchers(api("/auth2/login/**")).anonymous()
.antMatchers(api("/auth2/authorization/**")).anonymous()
// oAuth2 auth2/login/gitee TODO 芋艿貌似可以删除
.antMatchers(buildAdminApi("/auth2/login/**")).anonymous()
.antMatchers(buildAdminApi("/auth2/authorization/**")).anonymous()
.antMatchers("/api/callback/**").anonymous()
// 设置每个请求的权限 每个项目的自定义规则
// 设置每个请求的权限 每个项目的自定义规则 TODO 芋艿改造成多个方便每个模块自定义规则
.and().authorizeRequests(authorizeRequestsCustomizer)
// 设置每个请求的权限 兜底规则必须认证
.authorizeRequests().anyRequest().authenticated()
@ -156,8 +150,14 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
private String api(String url) {
return webProperties.getApiPrefix() + url;
private String buildAdminApi(String url) {
// TODO 芋艿多模块
return webProperties.getAdminApi().getPrefix() + url;
}
private String buildAppApi(String url) {
// TODO 芋艿多模块
return webProperties.getAppApi().getPrefix() + url;
}
}

View File

@ -0,0 +1,149 @@
package cn.iocoder.yudao.framework.security.core.authentication;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 支持多用户类型的 AuthenticationProvider 实现类
*
* 为什么不用 {@link org.springframework.security.authentication.ProviderManager}
* 原因是需要每个用户类型实现对应的 {@link AuthenticationProvider} + authentication略显麻烦实际也是可以实现的
*
* 另外额外支持 verifyTokenAndRefresh 校验令牌logout 登出mockLogin 模拟登陆等操作
* 实际上它就是 {@link SecurityAuthFrameworkService} 定义的三个接口
* 因为需要支持多种类型所以需要根据请求的 URL判断出对应的用户类型从而使用对应的 SecurityAuthFrameworkService 是吸纳
*
* @see cn.iocoder.yudao.framework.common.enums.UserTypeEnum
* @author 芋道源码
*/
public class MultiUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private final Map<UserTypeEnum, SecurityAuthFrameworkService> services = new HashMap<>();
private final WebProperties properties;
private final PasswordEncoder passwordEncoder;
public MultiUserDetailsAuthenticationProvider(List<SecurityAuthFrameworkService> serviceList,
WebProperties properties, PasswordEncoder passwordEncoder) {
serviceList.forEach(service -> services.put(service.getUserType(), service));
this.properties = properties;
this.passwordEncoder = passwordEncoder;
}
// ========== AuthenticationProvider 相关 ==========
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 执行用户的加载
return selectService(authentication).loadUserByUsername(username);
}
private SecurityAuthFrameworkService selectService(UsernamePasswordAuthenticationToken authentication) {
// 第一步获得用户类型
UserTypeEnum userType = getUserType(authentication);
// 第二步获得 SecurityAuthFrameworkService
SecurityAuthFrameworkService service = services.get(userType);
Assert.notNull(service, "用户类型({}) 找不到 SecurityAuthFrameworkService 实现类", userType);
return service;
}
private UserTypeEnum getUserType(UsernamePasswordAuthenticationToken authentication) {
Assert.isInstanceOf(MultiUsernamePasswordAuthenticationToken.class, authentication);
MultiUsernamePasswordAuthenticationToken multiAuthentication = (MultiUsernamePasswordAuthenticationToken) authentication;
UserTypeEnum userType = multiAuthentication.getUserType();
Assert.notNull(userType, "用户类型不能为空");
return userType;
}
@Override // copy DaoAuthenticationProvider additionalAuthenticationChecks 方法
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 校验 credentials
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
// 校验 password
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
// ========== SecurityAuthFrameworkService 相关 ==========
/**
* 校验 token 的有效性并获取用户信息
* 通过后刷新 token 的过期时间
*
* @param request 请求
* @param token token
* @return 用户信息
*/
public LoginUser verifyTokenAndRefresh(HttpServletRequest request, String token) {
return selectService(request).verifyTokenAndRefresh(token);
}
/**
* 模拟指定用户编号的 LoginUser
*
* @param request 请求
* @param userId 用户编号
* @return 登录用户
*/
public LoginUser mockLogin(HttpServletRequest request, Long userId) {
return selectService(request).mockLogin(userId);
}
/**
* 基于 token 退出登录
*
* @param request 请求
* @param token token
*/
public void logout(HttpServletRequest request, String token) {
selectService(request).logout(token);
}
private SecurityAuthFrameworkService selectService(HttpServletRequest request) {
// 第一步获得用户类型
UserTypeEnum userType = getUserType(request);
// 第二步获得 SecurityAuthFrameworkService
SecurityAuthFrameworkService service = services.get(userType);
Assert.notNull(service, "URI({}) 用户类型({}) 找不到 SecurityAuthFrameworkService 实现类",
request.getRequestURI(), userType);
return service;
}
private UserTypeEnum getUserType(HttpServletRequest request) {
if (request.getRequestURI().startsWith(properties.getAdminApi().getPrefix())) {
return UserTypeEnum.ADMIN;
}
if (request.getRequestURI().startsWith(properties.getAppApi().getPrefix())) {
return UserTypeEnum.MEMBER;
}
throw new IllegalArgumentException(StrUtil.format("URI({}) 找不到匹配的用户类型", request.getRequestURI()));
}
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.framework.security.core.authentication;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import lombok.Getter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 支持多用户的 UsernamePasswordAuthenticationToken 实现类
*
* @author 芋道源码
*/
@Getter
public class MultiUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
/**
* 用户类型
*/
private UserTypeEnum userType;
public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(principal, credentials);
}
public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials, UserTypeEnum userType) {
super(principal, credentials);
this.userType = userType;
}
public MultiUsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities, UserTypeEnum userType) {
super(principal, credentials, authorities);
this.userType = userType;
}
}

View File

@ -2,17 +2,15 @@ package cn.iocoder.yudao.framework.security.core.filter;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -25,12 +23,12 @@ import java.io.IOException;
*
* @author 芋道源码
*/
@AllArgsConstructor
@RequiredArgsConstructor
public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
private final SecurityProperties securityProperties;
private final SecurityAuthFrameworkService authService;
private final MultiUserDetailsAuthenticationProvider authenticationProvider;
private final GlobalExceptionHandler globalExceptionHandler;
@ -42,10 +40,10 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
if (StrUtil.isNotEmpty(token)) {
try {
// 验证 token 有效性
LoginUser loginUser = authService.verifyTokenAndRefresh(token);
LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
// 模拟 Login 功能方便日常开发调试
if (loginUser == null) {
loginUser = this.mockLoginUser(token);
loginUser = this.mockLoginUser(request, token);
}
// 设置当前用户
if (loginUser != null) {
@ -67,10 +65,11 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
*
* 注意在线上环境下一定要关闭该功能
*
* @param request 请求
* @param token 模拟的 token格式为 {@link SecurityProperties#getTokenSecret()} + 用户编号
* @return 模拟的 LoginUser
*/
private LoginUser mockLoginUser(String token) {
private LoginUser mockLoginUser(HttpServletRequest request, String token) {
if (!securityProperties.getMockEnable()) {
return null;
}
@ -79,7 +78,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
return null;
}
Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
return authService.mockLogin(userId);
return authenticationProvider.mockLogin(request, userId);
}
}

View File

@ -1,53 +0,0 @@
/*
* MIT License
* Copyright (c) 2020-2029 YongWu zheng (dcenter.top and gitee.com/pcore and github.com/ZeroOrInfinity)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package cn.iocoder.yudao.framework.security.core.handler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author weir
*/
public class AbstractSignUpUrlAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private RequestCache requestCache = new HttpSessionRequestCache();
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
if (requestCache.getRequest(request, response) != null) {
requestCache.getRequest(request, response);
}
super.onAuthenticationSuccess(request,response,authentication);
}
@Override
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
}

View File

@ -2,16 +2,14 @@ package cn.iocoder.yudao.framework.security.core.handler;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import lombok.AllArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -26,14 +24,14 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
private final SecurityProperties securityProperties;
private final SecurityAuthFrameworkService securityFrameworkService;
private final MultiUserDetailsAuthenticationProvider authenticationProvider;
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
// 执行退出
String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader());
if (StrUtil.isNotBlank(token)) {
securityFrameworkService.logout(token);
authenticationProvider.logout(request, token);
}
// 返回成功
ServletUtils.writeJSON(response, CommonResult.success(null));

View File

@ -1,10 +1,11 @@
package cn.iocoder.yudao.framework.security.core.service;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* Security 框架 Auth Service 接口定义 security 组件需要的功能
* Security 框架 Auth Service 接口定义不同用户类型的 {@link UserTypeEnum} 需要实现的方法
*
* @author 芋道源码
*/
@ -34,4 +35,11 @@ public interface SecurityAuthFrameworkService extends UserDetailsService {
*/
void logout(String token);
/**
* 获得用户类型每个用户类型对应一个 SecurityAuthFrameworkService 实现类
*
* @return 用户类型
*/
UserTypeEnum getUserType();
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.apilog.core.filter;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@ -25,6 +26,8 @@ import java.io.IOException;
import java.util.Date;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.*;
/**
* API 访问日志 Filter
*
@ -42,7 +45,8 @@ public class ApiAccessLogFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// 只过滤 API 请求的地址
return !request.getRequestURI().startsWith(webProperties.getApiPrefix());
return !StrUtil.startWithAny(request.getRequestURI(), webProperties.getAppApi().getPrefix(),
webProperties.getAppApi().getPrefix());
}
@Override
@ -73,7 +77,7 @@ public class ApiAccessLogFilter extends OncePerRequestFilter {
this.buildApiAccessLogDTO(accessLog, request, beginTime, queryString, requestBody, ex);
apiAccessLogFrameworkService.createApiAccessLogAsync(accessLog);
} catch (Throwable th) {
log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), JsonUtils.toJsonString(accessLog), th);
log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th);
}
}
@ -99,7 +103,7 @@ public class ApiAccessLogFilter extends OncePerRequestFilter {
accessLog.setApplicationName(applicationName);
accessLog.setRequestUrl(request.getRequestURI());
Map<String, Object> requestParams = MapUtil.<String, Object>builder().put("query", queryString).put("body", requestBody).build();
accessLog.setRequestParams(JsonUtils.toJsonString(requestParams));
accessLog.setRequestParams(toJsonString(requestParams));
accessLog.setRequestMethod(request.getMethod());
accessLog.setUserAgent(ServletUtils.getUserAgent(request));
accessLog.setUserIp(ServletUtil.getClientIP(request));

View File

@ -5,6 +5,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ConfigurationProperties(prefix = "yudao.web")
@ -12,26 +14,37 @@ import javax.validation.constraints.NotNull;
@Data
public class WebProperties {
/**
* API 前缀实现所有 Controller 提供的 RESTFul API 的统一前缀
*
*
* 意义通过该前缀避免 SwaggerActuator 意外通过 Nginx 暴露出来给外部带来安全性问题
* 这样Nginx 只需要配置转发到 /api/* 的所有接口即可
*
* @see YudaoWebAutoConfiguration#configurePathMatch(PathMatchConfigurer)
*/
@NotNull(message = "API 前缀不能为空")
private String apiPrefix;
@NotNull(message = "APP API 不能为空")
private Api appApi;
@NotNull(message = "Admin API 不能为空")
private Api adminApi;
/**
* Controller 所在包
*
* 主要目的是给该 Controller 设置指定的 {@link #apiPrefix}
*
* 因为我们有多个 modules 包里会包含 Controller所以只需要写到 cn.iocoder.yudao 这样的层级
*/
@NotNull(message = "Controller 所在包不能为空")
private String controllerPackage;
@Data
@Valid
public static class Api {
/**
* API 前缀实现所有 Controller 提供的 RESTFul API 的统一前缀
*
*
* 意义通过该前缀避免 SwaggerActuator 意外通过 Nginx 暴露出来给外部带来安全性问题
* 这样Nginx 只需要配置转发到 /api/* 的所有接口即可
*
* @see YudaoWebAutoConfiguration#configurePathMatch(PathMatchConfigurer)
*/
@NotEmpty(message = "API 前缀不能为空")
private String prefix;
/**
* Controller 所在包
*
* 主要目的是给该 Controller 设置指定的 {@link #prefix}
*
* 因为我们有多个 modules 包里会包含 Controller所以只需要写到 cn.iocoder.yudao 这样的层级
*/
@NotEmpty(message = "Controller 所在包不能为空")
private String controller;
}
}

View File

@ -38,10 +38,19 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// 设置 API 前缀仅仅匹配 controller 包下的
configurer.addPathPrefix(webProperties.getApiPrefix(), clazz ->
clazz.isAnnotationPresent(RestController.class)
&& clazz.getPackage().getName().startsWith(webProperties.getControllerPackage())); // 仅仅匹配 controller
configurePathMatch(configurer, webProperties.getAdminApi());
configurePathMatch(configurer, webProperties.getAppApi());
}
/**
* 设置 API 前缀仅仅匹配 controller 包下的
*
* @param configurer 配置
* @param api API 配置
*/
private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class)
&& clazz.getPackage().getName().startsWith(api.getController())); // 仅仅匹配 controller
}
@Bean

View File

@ -55,6 +55,11 @@ public class WebFrameworkUtils {
return (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);
}
public static Integer getLoginUserType() {
HttpServletRequest request = getRequest();
return getLoginUserType(request);
}
public static Long getLoginUserId() {
HttpServletRequest request = getRequest();
return getLoginUserId(request);

View File

@ -10,10 +10,17 @@ import cn.iocoder.yudao.coreservice.modules.bpm.api.group.BpmUserGroupServiceApi
import cn.iocoder.yudao.coreservice.modules.system.service.dept.SysDeptCoreService;
import cn.iocoder.yudao.coreservice.modules.system.service.permission.SysPermissionCoreService;
import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreService;
import cn.iocoder.yudao.adminserver.modules.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
import org.activiti.api.runtime.shared.identity.UserGroupManager;
import org.activiti.core.common.spring.identity.ActivitiUserGroupManagerImpl;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import java.util.Collections;
import java.util.List;
@ -27,7 +34,15 @@ import static org.activiti.spring.boot.ProcessEngineAutoConfiguration.BEHAVIOR_F
public class BpmActivitiConfiguration {
/**
* BPM 模块的 ProcessEngineConfigurationConfigurer 实现类主要设置各种监听器用户组管理
* 空用户组的 Bean
*/
@Bean
public UserGroupManager userGroupManager() {
return new EmptyUserGroupManager();
}
/**
* BPM 模块的 ProcessEngineConfigurationConfigurer 实现类主要设置各种监听器
*/
@Bean
public ProcessEngineConfigurationConfigurer bpmProcessEngineConfigurationConfigurer(
@ -35,8 +50,6 @@ public class BpmActivitiConfiguration {
return configuration -> {
// 注册监听器例如说 BpmActivitiEventListener
configuration.setEventListeners(Collections.singletonList(taskActivitiEventListener));
// 用户组
configuration.setUserGroupManager(new EmptyUserGroupManager());
};
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>yudao-module-member-api</module>
<module>yudao-module-member-impl</module>
</modules>
<artifactId>yudao-module-member</artifactId>
<packaging>pom</packaging>
<name>${artifactId}</name>
<description>
member 模块,我们放会员业务。
例如说:会员中心等等
</description>
</project>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-member</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-member-api</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<description>
member 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,4 @@
/**
* member API 定义暴露给其它模块的 API
*/
package cn.iocoder.yudao.module.member.api;

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.member.api.user;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
/**
* 会员用户的 API 接口
*
* @author 芋道源码
*/
public interface UserApi {
/**
* 获得会员用户信息
*
* @param id 用户编号
* @return 用户信息
*/
UserRespDTO getUser(Long id);
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.member.api.user.dto;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
/**
* 用户信息 Response DTO
*
* @author 芋道源码
*/
public class UserRespDTO {
/**
* 用户ID
*/
private Long id;
/**
* 用户昵称
*/
private String nickname;
/**
* 帐号状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
/**
* 手机
*/
private String mobile;
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-member</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-member-impl</artifactId>
<packaging>jar</packaging>
<name>${artifactId}</name>
<description>
member 模块,我们放会员业务。
例如说:会员中心等等
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-member-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-core-service</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-sms</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mq</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 工具类相关 -->
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.member.api;

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.member.api.user;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
import cn.iocoder.yudao.module.member.convert.user.UserConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import cn.iocoder.yudao.module.member.service.user.UserService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* 会员用户的 API 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class UserApiImpl implements UserApi {
@Resource
private UserService userService;
@Override
public UserRespDTO getUser(Long id) {
UserDO user = userService.getUser(id);
return UserConvert.INSTANCE.convert2(user);
}
}

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.member.controller.admin.address;

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.member.controller.admin.user;

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.member.controller.app.address;

View File

@ -1,6 +1,7 @@
### 请求 /login 接口 => 成功
POST {{userServerUrl}}/login
POST {{userApi}}/login
Content-Type: application/json
tenant-id: {{userTenentId}}
{
"mobile": "15601691300",

View File

@ -1,14 +1,13 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth;
package cn.iocoder.yudao.module.member.controller.app.auth;
import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import com.alibaba.fastjson.JSON;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
import cn.iocoder.yudao.module.member.service.auth.AuthService;
import cn.iocoder.yudao.module.member.service.sms.SysSmsCodeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@ -18,7 +17,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -26,15 +24,16 @@ import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getCli
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getUserAgent;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "认证")
@Api(tags = "APP 端 - 认证")
@RestController
@RequestMapping("/")
@Validated
@Slf4j
public class SysAuthController {
public class AppAuthController {
@Resource
private SysAuthService authService;
private AuthService authService;
@Resource
private SysSmsCodeService smsCodeService;
@Resource
@ -42,35 +41,31 @@ public class SysAuthController {
@PostMapping("/login")
@ApiOperation("使用手机 + 密码登录")
public CommonResult<SysAuthLoginRespVO> login(@RequestBody @Valid SysAuthLoginReqVO reqVO) {
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
String token = authService.login(reqVO, getClientIP(), getUserAgent());
// 返回结果
return success(SysAuthLoginRespVO.builder().token(token).build());
return success(AppAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/sms-login")
@ApiOperation("使用手机 + 验证码登录")
public CommonResult<SysAuthLoginRespVO> smsLogin(@RequestBody @Valid SysAuthSmsLoginReqVO reqVO) {
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {
String token = authService.smsLogin(reqVO, getClientIP(), getUserAgent());
// 返回结果
return success(SysAuthLoginRespVO.builder().token(token).build());
return success(AppAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/send-sms-code")
@ApiOperation(value = "发送手机验证码",notes = "不检测该手机号是否已被注册")
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid SysAuthSendSmsReqVO reqVO) {
@ApiOperation(value = "发送手机验证码")
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSendSmsReqVO reqVO) {
smsCodeService.sendSmsCode(reqVO.getMobile(), reqVO.getScene(), getClientIP());
return success(true);
}
@PostMapping("/send-sms-new-code")
@ApiOperation(value = "发送手机验证码",notes = "检测该手机号是否已被注册,用于修改手机时使用")
public CommonResult<Boolean> sendSmsNewCode(@RequestBody @Valid SysAuthSendSmsReqVO reqVO) {
smsCodeService.sendSmsNewCode(reqVO);
return success(true);
}
@GetMapping("/send-sms-code-login")
@GetMapping("/send-sms-code-login") // TODO 芋艿post 比较合理
@ApiOperation(value = "向已登录用户发送验证码",notes = "修改手机时验证原手机号使用")
public CommonResult<Boolean> sendSmsCodeLogin() {
smsCodeService.sendSmsCodeLogin(getLoginUserId());
@ -80,7 +75,8 @@ public class SysAuthController {
@PostMapping("/reset-password")
@ApiOperation(value = "重置密码", notes = "用户忘记密码时使用")
@PreAuthenticated
public CommonResult<Boolean> resetPassword(@RequestBody @Valid MbrAuthResetPasswordReqVO reqVO) {
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) {
authService.resetPassword(reqVO);
return success(true);
}
@ -88,20 +84,11 @@ public class SysAuthController {
@PostMapping("/update-password")
@ApiOperation(value = "修改用户密码",notes = "用户修改密码时使用")
@PreAuthenticated
public CommonResult<Boolean> updatePassword(@RequestBody @Valid MbrAuthUpdatePasswordReqVO reqVO) {
public CommonResult<Boolean> updatePassword(@RequestBody @Valid AppAuthUpdatePasswordReqVO reqVO) {
authService.updatePassword(getLoginUserId(), reqVO);
return success(true);
}
@PostMapping("/check-sms-code")
@ApiOperation(value = "校验验证码是否正确")
@PreAuthenticated
public CommonResult<Boolean> checkSmsCode(@RequestBody @Valid SysAuthSmsLoginReqVO reqVO) {
// TODO @宋天check 的时候不应该使用 useSmsCode 这样验证码就直接被使用了另外check 开头的方法更多是校验的逻辑不会有 update 数据的动作这点在方法命名上也是要注意的
smsCodeService.useSmsCode(reqVO.getMobile(),SysSmsSceneEnum.CHECK_CODE_BY_SMS.getScene(),reqVO.getCode(),getClientIP());
return success(true);
}
// ========== 社交登录相关 ==========
@GetMapping("/social-auth-redirect")
@ -115,32 +102,33 @@ public class SysAuthController {
return CommonResult.success(socialService.getAuthorizeUrl(type, redirectUri));
}
@PostMapping("/social-login")
@ApiOperation("社交登录,使用 code 授权码")
public CommonResult<SysAuthLoginRespVO> socialLogin(@RequestBody @Valid MbrAuthSocialLoginReqVO reqVO) {
public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) {
String token = authService.socialLogin(reqVO, getClientIP(), getUserAgent());
return success(SysAuthLoginRespVO.builder().token(token).build());
return success(AppAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/social-login2")
@ApiOperation("社交登录,使用 手机号 + 手机验证码")
public CommonResult<SysAuthLoginRespVO> socialLogin2(@RequestBody @Valid MbrAuthSocialLogin2ReqVO reqVO) {
@OperateLog(enable = false) // 避免 Post 请求被记录操作日志
public CommonResult<AppAuthLoginRespVO> socialLogin2(@RequestBody @Valid AppAuthSocialLogin2ReqVO reqVO) {
String token = authService.socialLogin2(reqVO, getClientIP(), getUserAgent());
return success(SysAuthLoginRespVO.builder().token(token).build());
return success(AppAuthLoginRespVO.builder().token(token).build());
}
@PostMapping("/social-bind")
@ApiOperation("社交绑定,使用 code 授权码")
public CommonResult<Boolean> socialBind(@RequestBody @Valid MbrAuthSocialBindReqVO reqVO) {
@PreAuthenticated
public CommonResult<Boolean> socialBind(@RequestBody @Valid AppAuthSocialBindReqVO reqVO) {
authService.socialBind(getLoginUserId(), reqVO);
return CommonResult.success(true);
}
@DeleteMapping("/social-unbind")
@ApiOperation("取消社交绑定")
public CommonResult<Boolean> socialUnbind(@RequestBody MbrAuthSocialUnbindReqVO reqVO) {
@PreAuthenticated
public CommonResult<Boolean> socialUnbind(@RequestBody AppAuthSocialUnbindReqVO reqVO) {
socialService.unbindSocialUser(getLoginUserId(), reqVO.getType(), reqVO.getUnionId(), UserTypeEnum.MEMBER);
return CommonResult.success(true);
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.module.member.enums.sms.SysSmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
// TODO 芋艿code review 相关逻辑
@ApiModel("APP 端 - 校验验证码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthCheckCodeReqVO {
@ApiModelProperty(value = "手机号", example = "15601691234")
@NotBlank(message = "手机号不能为空")
@Mobile
private String mobile;
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotBlank(message = "手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@ApiModelProperty(value = "发送场景", example = "1", notes = "对应 MbrSmsSceneEnum 枚举")
@NotNull(message = "发送场景不能为空")
@InEnum(SysSmsSceneEnum.class)
private Integer scene;
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import io.swagger.annotations.ApiModel;
@ -11,12 +11,12 @@ import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
@ApiModel("手机 + 密码登录 Request VO")
@ApiModel("APP 端 - 手机 + 密码登录 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthLoginReqVO {
public class AppAuthLoginReqVO {
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
@NotEmpty(message = "手机号不能为空")

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -7,12 +7,12 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@ApiModel("手机密码登录 Response VO")
@ApiModel("APP 端 - 手机密码登录 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthLoginRespVO {
public class AppAuthLoginRespVO {
@ApiModelProperty(value = "token", required = true, example = "yudaoyuanma")
private String token;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
@ -8,15 +9,17 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@ApiModel("重置密码 Request VO")
// TODO 芋艿code review 相关逻辑
@ApiModel("APP 端 - 重置密码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthResetPasswordReqVO {
public class AppAuthResetPasswordReqVO {
@ApiModelProperty(value = "新密码", required = true, example = "buzhidao")
@NotEmpty(message = "新密码不能为空")
@ -29,4 +32,9 @@ public class MbrAuthResetPasswordReqVO {
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String code;
@ApiModelProperty(value = "手机号",required = true,example = "15878962356")
@NotBlank(message = "手机号不能为空")
@Mobile
private String mobile;
}

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.module.member.enums.sms.SysSmsSceneEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -10,10 +10,10 @@ import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
@ApiModel("发送手机验证码 Response VO")
@ApiModel("APP 端 - 发送手机验证码 Response VO")
@Data
@Accessors(chain = true)
public class SysAuthSendSmsReqVO {
public class AppAuthSendSmsReqVO {
@ApiModelProperty(value = "手机号", example = "15601691234")
@Mobile

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import io.swagger.annotations.ApiModel;
@ -12,12 +12,12 @@ import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@ApiModel("手机 + 验证码登录 Request VO")
@ApiModel("APP 端 - 手机 + 验证码登录 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SysAuthSmsLoginReqVO {
public class AppAuthSmsLoginReqVO {
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
@NotEmpty(message = "手机号不能为空")

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
@ -12,12 +12,12 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("社交登录 Request VO使用 code 授权码")
@ApiModel("APP 端 - 社交绑定 Request VO使用 code 授权码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSocialLoginReqVO {
public class AppAuthSocialBindReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysSocialTypeEnum.class)

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
@ -14,12 +14,12 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@ApiModel("社交登录 Request VO使用 code 授权码 + 账号密码")
@ApiModel("APP 端 - 社交登录 Request VO使用 code 授权码 + 账号密码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSocialLogin2ReqVO {
public class AppAuthSocialLogin2ReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysSocialTypeEnum.class)

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
@ -12,12 +12,12 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("社交绑定 Request VO使用 code 授权码")
@ApiModel("APP 端 - 社交登录 Request VO使用 code 授权码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSocialBindReqVO {
public class AppAuthSocialLoginReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysSocialTypeEnum.class)

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
@ -12,12 +12,12 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("取消社交绑定 Request VO使用 code 授权码")
@ApiModel("APP 端 - 取消社交绑定 Request VO使用 code 授权码")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthSocialUnbindReqVO {
public class AppAuthSocialUnbindReqVO {
@ApiModelProperty(value = "社交平台的类型", required = true, example = "10", notes = "参见 SysUserSocialTypeEnum 枚举值")
@InEnum(SysSocialTypeEnum.class)

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -11,12 +11,13 @@ import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
@ApiModel("修改密码 Request VO")
// TODO 芋艿code review 相关逻辑
@ApiModel("APP 端 - 修改密码 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrAuthUpdatePasswordReqVO {
public class AppAuthUpdatePasswordReqVO {
@ApiModelProperty(value = "用户旧密码", required = true, example = "123456")
@NotBlank(message = "旧密码不能为空")

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.member.controller.app;

View File

@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.member.controller.app.user;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserInfoRespVO;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
import cn.iocoder.yudao.module.member.convert.user.UserConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import cn.iocoder.yudao.module.member.service.user.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.member.enums.MemberErrorCodeConstants.FILE_IS_EMPTY;
@Api(tags = "APP 端 - 用户个人中心")
@RestController
@RequestMapping("/member/user")
@Validated
@Slf4j
public class AppUserController {
@Resource
private UserService userService;
@PutMapping("/update-nickname")
@ApiOperation("修改用户昵称")
@PreAuthenticated
public CommonResult<Boolean> updateUserNickname(@RequestParam("nickname") String nickname) {
userService.updateUserNickname(getLoginUserId(), nickname);
return success(true);
}
@PutMapping("/update-avatar")
@ApiOperation("修改用户头像")
@PreAuthenticated
public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws IOException {
if (file.isEmpty()) {
throw exception(FILE_IS_EMPTY);
}
String avatar = userService.updateUserAvatar(getLoginUserId(), file.getInputStream());
return success(avatar);
}
@GetMapping("/get")
@ApiOperation("获得基本信息")
@PreAuthenticated
public CommonResult<AppUserInfoRespVO> getUserInfo() {
UserDO user = userService.getUser(getLoginUserId());
return success(UserConvert.INSTANCE.convert(user));
}
@PostMapping("/update-mobile")
@ApiOperation(value = "修改用户手机")
@PreAuthenticated
public CommonResult<Boolean> updateMobile(@RequestBody @Valid AppUserUpdateMobileReqVO reqVO) {
userService.updateUserMobile(getLoginUserId(), reqVO);
return success(true);
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.member.controller.user.vo;
package cn.iocoder.yudao.module.member.controller.app.user.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -6,11 +6,11 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ApiModel("用户个人信息 Response VO")
@ApiModel("APP 端 - 用户个人信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MbrUserInfoRespVO {
public class AppUserInfoRespVO {
@ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
private String nickname;

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.userserver.modules.member.controller.user.vo;
package cn.iocoder.yudao.module.member.controller.app.user.vo;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
@ -12,12 +13,12 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
@ApiModel("修改手机 Request VO")
@ApiModel("APP 端 - 修改手机 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MbrUserUpdateMobileReqVO {
public class AppUserUpdateMobileReqVO {
@ApiModelProperty(value = "手机验证码", required = true, example = "1024")
@NotEmpty(message = "手机验证码不能为空")
@ -27,9 +28,22 @@ public class MbrUserUpdateMobileReqVO {
@ApiModelProperty(value = "手机号",required = true,example = "15823654487")
@NotBlank(message = "手机号不能为空")
// TODO @宋天手机校验直接使用 @Mobile
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误")
@Mobile
private String mobile;
@ApiModelProperty(value = "原手机验证码", required = true, example = "1024")
@NotEmpty(message = "原手机验证码不能为空")
@Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位")
@Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字")
private String oldCode;
// TODO @芋艿oldMobile 应该不用传递
@ApiModelProperty(value = "原手机号",required = true,example = "15823654487")
@NotBlank(message = "手机号不能为空")
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
@Mobile
private String oldMobile;
}

View File

@ -0,0 +1,6 @@
/**
* 提供 RESTful API 给前端
* 1. admin 提供给管理后台 yudao-ui-admin 前端项目
* 2. app 提供给用户 APP yudao-ui-app 前端项目
*/
package cn.iocoder.yudao.module.member.controller;

View File

@ -1,21 +1,21 @@
package cn.iocoder.yudao.userserver.modules.system.convert.auth;
package cn.iocoder.yudao.module.member.convert.auth;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface SysAuthConvert {
public interface AuthConvert {
SysAuthConvert INSTANCE = Mappers.getMapper(SysAuthConvert.class);
AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);
@Mapping(source = "mobile", target = "username")
LoginUser convert0(MbrUserDO bean);
LoginUser convert0(UserDO bean);
default LoginUser convert(MbrUserDO bean) {
default LoginUser convert(UserDO bean) {
// 目的为了设置 UserTypeEnum.MEMBER.getValue()
return convert0(bean).setUserType(UserTypeEnum.MEMBER.getValue());
}

View File

@ -3,4 +3,4 @@
*
* 目前使用 MapStruct 框架
*/
package cn.iocoder.yudao.userserver.modules.member.convert;
package cn.iocoder.yudao.module.member.convert;

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.member.convert.user;
import cn.iocoder.yudao.module.member.api.user.dto.UserRespDTO;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserInfoRespVO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
AppUserInfoRespVO convert(UserDO bean);
UserRespDTO convert2(UserDO bean);
}

View File

@ -1,9 +1,8 @@
package cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms;
package cn.iocoder.yudao.module.member.dal.dataobject.sms;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import lombok.experimental.Accessors;
import java.util.Date;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user;
package cn.iocoder.yudao.module.member.dal.dataobject.user;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
@ -10,7 +10,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.Date;
/**
* 会员中心的用户 DO
* 会员用户 DO
*
* uk_mobile 索引基于 {@link #mobile} 字段
*
@ -22,7 +22,7 @@ import java.util.Date;
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MbrUserDO extends TenantBaseDO {
public class UserDO extends TenantBaseDO {
/**
* 用户ID

View File

@ -1,10 +1,11 @@
package cn.iocoder.yudao.userserver.modules.system.dal.mysql.sms;
package cn.iocoder.yudao.module.member.dal.mysql.sms;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
import cn.iocoder.yudao.module.member.dal.dataobject.sms.SysSmsCodeDO;
import org.apache.ibatis.annotations.Mapper;
// TODO @芋艿拿到 system 模块下
@Mapper
public interface SysSmsCodeMapper extends BaseMapperX<SysSmsCodeDO> {
@ -13,14 +14,15 @@ public interface SysSmsCodeMapper extends BaseMapperX<SysSmsCodeDO> {
*
* @param mobile 手机号
* @param scene 发送场景选填
* @param code 验证码 选填
* @return 手机验证码
*/
default SysSmsCodeDO selectLastByMobile(String mobile, Integer scene) {
default SysSmsCodeDO selectLastByMobile(String mobile,String code,Integer scene) {
return selectOne(new QueryWrapperX<SysSmsCodeDO>()
.eq("mobile", mobile)
.eqIfPresent("scene", scene)
.eqIfPresent("code", code)
.orderByDesc("id")
.last("LIMIT 1"));
}
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.member.dal.mysql.user;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 会员 User Mapper
*
* @author 芋道源码
*/
@Mapper
public interface UserMapper extends BaseMapperX<UserDO> {
default UserDO selectByMobile(String mobile) {
return selectOne(UserDO::getMobile, mobile);
}
}

View File

@ -0,0 +1,9 @@
/**
* DAL = Data Access Layer 数据访问层
* 1. data object数据对象
* 2. redisRedis CRUD 操作
* 3. mysqlMySQL CRUD 操作
*
* 其中MySQL 的表以 mbr_ 作为前缀
*/
package cn.iocoder.yudao.module.member.dal;

View File

@ -0,0 +1,4 @@
/**
* 占位后续有类后可以删除避免 package 无法提交到 Git
*/
package cn.iocoder.yudao.module.member.dal.redis;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.member.enums;
package cn.iocoder.yudao.module.member.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
@ -7,11 +7,12 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
*
* member 系统使用 1-004-000-000
*/
public interface MbrErrorCodeConstants {
public interface MemberErrorCodeConstants {
// ==========用户相关 1004001000============
ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在");
// ==========文件相关 1004002000 ===========
ErrorCode FILE_IS_EMPTY = new ErrorCode(1004002000, "文件为空");
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.enums;
package cn.iocoder.yudao.module.member.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
@ -24,9 +24,9 @@ public interface SysErrorCodeConstants {
ErrorCode USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1005001004, "超过每日短信发送数量");
ErrorCode USER_SMS_CODE_SEND_TOO_FAST = new ErrorCode(1005001005, "短信发送过于频率");
ErrorCode USER_SMS_CODE_IS_EXISTS = new ErrorCode(1005001006, "手机号已被使用");
ErrorCode USER_SMS_CODE_IS_UNUSED = new ErrorCode(1005001006, "验证码未被使用");
// ========== 用户模块 1005002000 ==========
ErrorCode USER_NOT_EXISTS = new ErrorCode(1005002001, "用户不存在");
ErrorCode USER_CODE_FAILED = new ErrorCode(1005002002, "验证码不匹配");
ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1005002003, "密码校验失败");
}

View File

@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.member.enums.sms;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户短信验证码发送场景的枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum SysSmsSceneEnum implements IntArrayValuable {
LOGIN_BY_SMS(1,SysSmsTemplateCodeConstants.USER_SMS_LOGIN, "手机号登陆"),
CHANGE_MOBILE_BY_SMS(2,SysSmsTemplateCodeConstants.USER_SMS_UPDATE_MOBILE, "更换手机号"),
FORGET_MOBILE_BY_SMS(3,SysSmsTemplateCodeConstants.USER_SMS_RESET_PASSWORD, "忘记密码"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SysSmsSceneEnum::getScene).toArray();
/**
* 验证那场景编号
*/
private final Integer scene;
/**
* 模版编码
*/
private final String code;
/**
* 描述
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
public static String getCodeByScene(Integer scene){
for (SysSmsSceneEnum value : values()) {
if (value.getScene().equals(scene)){
return value.getCode();
}
}
return null;
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.member.enums.sms;
/**
* yudao-user-server 使用到的短信模板的 Code 编码的枚举
*
* @author 芋道源码
*/
public interface SysSmsTemplateCodeConstants {
/**
* 前台用户短信登录
*/
String USER_SMS_LOGIN = "user-sms-login";
/**
* 用户忘记密码
*/
String USER_SMS_RESET_PASSWORD = "user-sms-reset-password";
/**
* 用户更新手机号
*/
String USER_SMS_UPDATE_MOBILE = "user-sms-update-mobile";
}

View File

@ -0,0 +1,6 @@
/**
* 属于 yudao-module-member-impl 的封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.member.framework;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.framework.sms;
package cn.iocoder.yudao.module.member.framework.sms;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.modules.system.framework.sms;
package cn.iocoder.yudao.module.member.framework.sms;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.member;

View File

@ -1,18 +1,18 @@
package cn.iocoder.yudao.userserver.modules.system.service.auth;
package cn.iocoder.yudao.module.member.service.auth;
import cn.iocoder.yudao.framework.security.core.service.SecurityAuthFrameworkService;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
import javax.validation.Valid;
/**
* 用户前台的认证 Service 接口
* 会员的认证 Service 接口
*
* 提供用户的账号密码登录token 的校验等认证相关的功能
*
* @author 芋道源码
*/
public interface SysAuthService extends SecurityAuthFrameworkService {
public interface AuthService extends SecurityAuthFrameworkService {
/**
* 手机 + 密码登录
@ -22,7 +22,7 @@ public interface SysAuthService extends SecurityAuthFrameworkService {
* @param userAgent 用户 UA
* @return 身份令牌使用 JWT 方式
*/
String login(@Valid SysAuthLoginReqVO reqVO, String userIp, String userAgent);
String login(@Valid AppAuthLoginReqVO reqVO, String userIp, String userAgent);
/**
* 手机 + 验证码登陆
@ -32,7 +32,7 @@ public interface SysAuthService extends SecurityAuthFrameworkService {
* @param userAgent 用户 UA
* @return 身份令牌使用 JWT 方式
*/
String smsLogin(@Valid SysAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
String smsLogin(@Valid AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent);
/**
@ -43,7 +43,7 @@ public interface SysAuthService extends SecurityAuthFrameworkService {
* @param userAgent 用户 UA
* @return 身份令牌使用 JWT 方式
*/
String socialLogin(@Valid MbrAuthSocialLoginReqVO reqVO, String userIp, String userAgent);
String socialLogin(@Valid AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent);
/**
* 社交登录使用 手机号 + 手机验证码
@ -53,7 +53,7 @@ public interface SysAuthService extends SecurityAuthFrameworkService {
* @param userAgent 用户 UA
* @return 身份令牌使用 JWT 方式
*/
String socialLogin2(@Valid MbrAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
String socialLogin2(@Valid AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent);
/**
* 社交绑定使用 code 授权码
@ -61,25 +61,19 @@ public interface SysAuthService extends SecurityAuthFrameworkService {
* @param userId 用户编号
* @param reqVO 绑定信息
*/
void socialBind(Long userId, @Valid MbrAuthSocialBindReqVO reqVO);
void socialBind(Long userId, @Valid AppAuthSocialBindReqVO reqVO);
/**
* 修改用户密码
* @param userId 用户id
* @param userReqVO 用户请求实体类
*/
void updatePassword(Long userId, @Valid MbrAuthUpdatePasswordReqVO userReqVO);
void updatePassword(Long userId, AppAuthUpdatePasswordReqVO userReqVO);
/**
* 忘记密码
* @param userReqVO 用户请求实体类
*/
void resetPassword(MbrAuthResetPasswordReqVO userReqVO);
void resetPassword(AppAuthResetPasswordReqVO userReqVO);
/**
* 检测手机与验证码是否匹配
* @param phone 手机号
* @param code 验证码
*/
void checkIfMobileMatchCodeAndDeleteCode(String phone,String code);
}

View File

@ -1,8 +1,7 @@
package cn.iocoder.yudao.userserver.modules.system.service.auth.impl;
package cn.iocoder.yudao.module.member.service.auth;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO;
import cn.iocoder.yudao.coreservice.modules.system.enums.logger.SysLoginLogTypeEnum;
import cn.iocoder.yudao.coreservice.modules.system.enums.logger.SysLoginResultEnum;
@ -15,18 +14,18 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.userserver.modules.member.dal.mysql.user.MbrUserMapper;
import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*;
import cn.iocoder.yudao.userserver.modules.system.convert.auth.SysAuthConvert;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import cn.iocoder.yudao.framework.security.core.authentication.MultiUsernamePasswordAuthenticationToken;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import cn.iocoder.yudao.module.member.dal.mysql.user.UserMapper;
import cn.iocoder.yudao.module.member.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.module.member.service.sms.SysSmsCodeService;
import cn.iocoder.yudao.module.member.service.user.UserService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
@ -40,31 +39,28 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.*;
import static cn.iocoder.yudao.module.member.enums.SysErrorCodeConstants.*;
/**
* Auth Service 实现类
* 会员的认证 Service 接口
*
* @author 芋道源码
*/
@Service
@Slf4j
public class SysAuthServiceImpl implements SysAuthService {
private static final UserTypeEnum USER_TYPE_ENUM = UserTypeEnum.MEMBER;
public class AuthServiceImpl implements AuthService {
@Resource
@Lazy // 延迟加载因为存在相互依赖的问题
private AuthenticationManager authenticationManager;
@Resource
private MbrUserService userService;
private UserService userService;
@Resource
private SysSmsCodeService smsCodeService;
@Resource
@ -74,28 +70,24 @@ public class SysAuthServiceImpl implements SysAuthService {
@Resource
private SysSocialCoreService socialService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private MbrUserMapper userMapper;
private static final UserTypeEnum userTypeEnum = UserTypeEnum.MEMBER;
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
// 获取 username 对应的 SysUserDO
MbrUserDO user = userService.getUserByMobile(mobile);
UserDO user = userService.getUserByMobile(mobile);
if (user == null) {
throw new UsernameNotFoundException(mobile);
}
// 创建 LoginUser 对象
return SysAuthConvert.INSTANCE.convert(user);
return AuthConvert.INSTANCE.convert(user);
}
@Override
public String login(SysAuthLoginReqVO reqVO, String userIp, String userAgent) {
public String login(AppAuthLoginReqVO reqVO, String userIp, String userAgent) {
// 使用手机 + 密码进行登录
LoginUser loginUser = this.login0(reqVO.getMobile(), reqVO.getPassword());
@ -105,80 +97,77 @@ public class SysAuthServiceImpl implements SysAuthService {
@Override
@Transactional
public String smsLogin(SysAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
public String smsLogin(AppAuthSmsLoginReqVO reqVO, String userIp, String userAgent) {
// 校验验证码
smsCodeService.useSmsCode(reqVO.getMobile(), SysSmsSceneEnum.LOGIN_BY_SMS.getScene(),
reqVO.getCode(), userIp);
// 获得获得注册用户
MbrUserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
UserDO user = userService.createUserIfAbsent(reqVO.getMobile(), userIp);
Assert.notNull(user, "获取用户失败,结果为空");
// 执行登陆
this.createLoginLog(user.getMobile(), SysLoginLogTypeEnum.LOGIN_SMS, SysLoginResultEnum.SUCCESS);
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
// 缓存登录用户到 Redis 返回 sessionId 编号
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
}
@Override
public String socialLogin(MbrAuthSocialLoginReqVO reqVO, String userIp, String userAgent) {
public String socialLogin(AppAuthSocialLoginReqVO reqVO, String userIp, String userAgent) {
// 使用 code 授权码进行登录
AuthUser authUser = socialService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
org.springframework.util.Assert.notNull(authUser, "授权用户不为空");
// 如果未绑定 SysSocialUserDO 用户则无法自动登录进行报错
String unionId = socialService.getAuthUserUnionId(authUser);
List<SysSocialUserDO> socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, USER_TYPE_ENUM);
List<SysSocialUserDO> socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId, getUserType());
if (CollUtil.isEmpty(socialUsers)) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
// 自动登录
MbrUserDO user = userService.getUser(socialUsers.get(0).getUserId());
UserDO user = userService.getUser(socialUsers.get(0).getUserId());
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
this.createLoginLog(user.getMobile(), SysLoginLogTypeEnum.LOGIN_SOCIAL, SysLoginResultEnum.SUCCESS);
// 创建 LoginUser 对象
LoginUser loginUser = SysAuthConvert.INSTANCE.convert(user);
LoginUser loginUser = AuthConvert.INSTANCE.convert(user);
// 绑定社交用户更新
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM);
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, getUserType());
// 缓存登录用户到 Redis 返回 sessionId 编号
return userSessionCoreService.createUserSession(loginUser, userIp, userAgent);
}
@Override
public String socialLogin2(MbrAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
public String socialLogin2(AppAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
AuthUser authUser = socialService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
org.springframework.util.Assert.notNull(authUser, "授权用户不为空");
// 使用手机号手机验证码登录
SysAuthSmsLoginReqVO loginReqVO = SysAuthSmsLoginReqVO
.builder()
.mobile(reqVO.getMobile())
.code(reqVO.getSmsCode())
.build();
AppAuthSmsLoginReqVO loginReqVO = AppAuthSmsLoginReqVO.builder()
.mobile(reqVO.getMobile()).code(reqVO.getSmsCode()).build();
String sessionId = this.smsLogin(loginReqVO, userIp, userAgent);
LoginUser loginUser = userSessionCoreService.getLoginUser(sessionId);
// 绑定社交用户新增
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, USER_TYPE_ENUM);
socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser, getUserType());
return sessionId;
}
@Override
public void socialBind(Long userId, MbrAuthSocialBindReqVO reqVO) {
public void socialBind(Long userId, AppAuthSocialBindReqVO reqVO) {
// 使用 code 授权码进行登录
AuthUser authUser = socialService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
org.springframework.util.Assert.notNull(authUser, "授权用户不为空");
// 绑定社交用户新增
socialService.bindSocialUser(userId, reqVO.getType(), authUser, USER_TYPE_ENUM);
socialService.bindSocialUser(userId, reqVO.getType(), authUser, getUserType());
}
private LoginUser login0(String username, String password) {
@ -188,7 +177,8 @@ public class SysAuthServiceImpl implements SysAuthService {
try {
// 调用 Spring Security AuthenticationManager#authenticate(...) 方法使用账号密码进行认证
// 在其内部会调用到 loadUserByUsername 方法获取 User 信息
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
authentication = authenticationManager.authenticate(new MultiUsernamePasswordAuthenticationToken(
username, password, getUserType()));
} catch (BadCredentialsException badCredentialsException) {
this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
@ -208,7 +198,7 @@ public class SysAuthServiceImpl implements SysAuthService {
private void createLoginLog(String mobile, SysLoginLogTypeEnum logTypeEnum, SysLoginResultEnum loginResult) {
// 获得用户
MbrUserDO user = userService.getUserByMobile(mobile);
UserDO user = userService.getUserByMobile(mobile);
// 插入登录日志
SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO();
reqDTO.setLogType(logTypeEnum.getType());
@ -246,10 +236,11 @@ public class SysAuthServiceImpl implements SysAuthService {
return;
}
// 重新加载 MbrUserDO 信息
MbrUserDO user = userService.getUser(loginUser.getId());
// 重新加载 UserDO 信息
UserDO user = userService.getUser(loginUser.getId());
if (user == null || CommonStatusEnum.DISABLE.getStatus().equals(user.getStatus())) {
throw exception(AUTH_TOKEN_EXPIRED); // 校验 token 用户被禁用的情况下也认为 token 过期方便前端跳转到登录界面
// 校验 token 用户被禁用的情况下也认为 token 过期方便前端跳转到登录界面
throw exception(AUTH_TOKEN_EXPIRED);
}
// 刷新 LoginUser 缓存
@ -258,8 +249,8 @@ public class SysAuthServiceImpl implements SysAuthService {
@Override
public LoginUser mockLogin(Long userId) {
// 获取用户编号对应的 MbrUserDO
MbrUserDO user = userService.getUser(userId);
// 获取用户编号对应的 UserDO
UserDO user = userService.getUser(userId);
if (user == null) {
throw new UsernameNotFoundException(String.valueOf(userId));
}
@ -268,7 +259,7 @@ public class SysAuthServiceImpl implements SysAuthService {
this.createLoginLog(user.getMobile(), SysLoginLogTypeEnum.LOGIN_MOCK, SysLoginResultEnum.SUCCESS);
// 创建 LoginUser 对象
return SysAuthConvert.INSTANCE.convert(user);
return AuthConvert.INSTANCE.convert(user);
}
@Override
@ -285,46 +276,35 @@ public class SysAuthServiceImpl implements SysAuthService {
}
@Override
public void updatePassword(Long userId, @Valid MbrAuthUpdatePasswordReqVO reqVO) {
public UserTypeEnum getUserType() {
return UserTypeEnum.MEMBER;
}
@Override
public void updatePassword(Long userId, AppAuthUpdatePasswordReqVO reqVO) {
// 检验旧密码
MbrUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
UserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());
// 更新用户密码
// TODO @宋天不要更新整个对象哈
userDO.setPassword(passwordEncoder.encode(reqVO.getPassword()));
userMapper.updateById(userDO);
UserDO mbrUserDO = UserDO.builder().id(userDO.getId())
.password(passwordEncoder.encode(reqVO.getPassword())).build();
userMapper.updateById(mbrUserDO);
}
@Override
public void resetPassword(MbrAuthResetPasswordReqVO reqVO) {
// 根据验证码取出手机号并查询用户
String mobile = stringRedisTemplate.opsForValue().get(reqVO.getCode());
MbrUserDO userDO = userMapper.selectByMobile(mobile);
if (userDO == null){
throw exception(USER_NOT_EXISTS);
}
// TODO @芋艿 这一步没必要检验验证码与手机是否匹配因为是根据验证码去redis中查找手机号然后根据手机号查询用户
// 也就是说 即便黑客以其他方式将验证码发送到自己手机上最终还是会根据手机号查询用户然后进行重置密码的操作不存在安全问题
public void resetPassword(AppAuthResetPasswordReqVO reqVO) {
// 检验用户是否存在
UserDO userDO = checkUserIfExists(reqVO.getMobile());
// TODO @宋天这块微信在讨论下哈~~~
// 校验验证码
smsCodeService.useSmsCode(userDO.getMobile(), SysSmsSceneEnum.FORGET_MOBILE_BY_SMS.getScene(), reqVO.getCode(),getClientIP());
// 使用验证码
smsCodeService.useSmsCode(reqVO.getMobile(),SysSmsSceneEnum.FORGET_MOBILE_BY_SMS.getScene(), reqVO.getCode(),
getClientIP());
// 更新密码
userDO.setPassword(passwordEncoder.encode(reqVO.getPassword()));
userMapper.updateById(userDO);
}
@Override
public void checkIfMobileMatchCodeAndDeleteCode(String phone, String code) {
// 检验用户手机与验证码是否匹配
String mobile = stringRedisTemplate.opsForValue().get(code);
if (!phone.equals(mobile)){
throw exception(USER_CODE_FAILED);
}
// 销毁redis中此验证码
stringRedisTemplate.delete(code);
UserDO mbrUserDO = UserDO.builder().build();
mbrUserDO.setId(userDO.getId());
mbrUserDO.setPassword(passwordEncoder.encode(reqVO.getPassword()));
userMapper.updateById(mbrUserDO);
}
/**
@ -332,11 +312,11 @@ public class SysAuthServiceImpl implements SysAuthService {
*
* @param id 用户 id
* @param oldPassword 旧密码
* @return MbrUserDO 用户实体
* @return MemberUserDO 用户实体
*/
@VisibleForTesting
public MbrUserDO checkOldPassword(Long id, String oldPassword) {
MbrUserDO user = userMapper.selectById(id);
public UserDO checkOldPassword(Long id, String oldPassword) {
UserDO user = userMapper.selectById(id);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
@ -347,12 +327,20 @@ public class SysAuthServiceImpl implements SysAuthService {
return user;
}
public UserDO checkUserIfExists(String mobile) {
UserDO user = userMapper.selectByMobile(mobile);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
return user;
}
private void createLogoutLog(Long userId, String username) {
SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO();
reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType());
reqDTO.setTraceId(TracerUtils.getTraceId());
reqDTO.setUserId(userId);
reqDTO.setUserType(USER_TYPE_ENUM.getValue());
reqDTO.setUserType(getUserType().getValue());
reqDTO.setUsername(username);
reqDTO.setUserAgent(ServletUtils.getUserAgent());
reqDTO.setUserIp(getClientIP());

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.userserver.modules.system.service.sms;
package cn.iocoder.yudao.module.member.service.sms;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSendSmsReqVO;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.module.member.dal.dataobject.sms.SysSmsCodeDO;
import cn.iocoder.yudao.module.member.enums.sms.SysSmsSceneEnum;
/**
* 短信验证码 Service 接口
@ -15,33 +15,37 @@ public interface SysSmsCodeService {
/**
* 创建短信验证码并进行发送
*
* @param mobile 手机号
* @param scene 发送场景 {@link SysSmsSceneEnum}
* @param mobile 手机号
* @param scene 发送场景 {@link SysSmsSceneEnum}
* @param createIp 发送 IP
*/
void sendSmsCode(@Mobile String mobile, Integer scene, String createIp);
/**
* 发送短信验证码并检测手机号是否已被注册
* @param reqVO 请求实体
*/
void sendSmsNewCode(SysAuthSendSmsReqVO reqVO);
/**
* 验证短信验证码并进行使用
* 如果正确则将验证码标记成已使用
* 如果错误则抛出 {@link ServiceException} 异常
*
* @param mobile 手机号
* @param scene 发送场景
* @param code 验证码
* @param scene 发送场景
* @param code 验证码
* @param usedIp 使用 IP
*/
void useSmsCode(@Mobile String mobile, Integer scene, String code, String usedIp);
/**
* 根据用户id发送验证码
*
* @param userId 用户id
*/
void sendSmsCodeLogin(Long userId);
/**
* 检查验证码是否有效
* @param mobile 手机
* @param code 验证码
* @param scene 使用场景
* @return 验证码记录
*/
SysSmsCodeDO checkCodeIsExpired(String mobile, String code, Integer scene);
}

View File

@ -0,0 +1,137 @@
package cn.iocoder.yudao.module.member.service.sms;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.coreservice.modules.system.service.sms.SysSmsCoreService;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.member.dal.dataobject.sms.SysSmsCodeDO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import cn.iocoder.yudao.module.member.dal.mysql.sms.SysSmsCodeMapper;
import cn.iocoder.yudao.module.member.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.module.member.framework.sms.SmsCodeProperties;
import cn.iocoder.yudao.module.member.service.user.UserService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Date;
import static cn.hutool.core.util.RandomUtil.randomInt;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.member.enums.SysErrorCodeConstants.*;
/**
* 短信验证码 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class SysSmsCodeServiceImpl implements SysSmsCodeService {
@Resource
private SmsCodeProperties smsCodeProperties;
@Resource
private SysSmsCodeMapper smsCodeMapper;
@Resource
private UserService userService;
@Resource
private SysSmsCoreService smsCoreService;
@Override
public void sendSmsCode(String mobile, Integer scene, String createIp) {
// 创建验证码
String code = this.createSmsCode(mobile, scene, createIp);
// 获取发送模板
String codeTemplate = SysSmsSceneEnum.getCodeByScene(scene);
// 如果是更换手机号发送验证码则需要检测手机号是否被注册
if (SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene().equals(scene)){
this.checkMobileIsRegister(mobile,scene);
}
// 发送验证码
smsCoreService.sendSingleSmsToMember(mobile, null, codeTemplate,
MapUtil.of("code", code));
}
public void checkMobileIsRegister(String mobile, Integer scene) {
// 检测手机号是否已被使用
UserDO user = userService.getUserByMobile(mobile);
if (user != null) {
throw ServiceExceptionUtil.exception(USER_SMS_CODE_IS_EXISTS);
}
// 发送短信
this.sendSmsCode(mobile,scene,getClientIP());
}
private String createSmsCode(String mobile, Integer scene, String ip) {
// 校验是否可以发送验证码不用筛选场景
SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null);
if (lastSmsCode != null) {
if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限
throw ServiceExceptionUtil.exception(USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
}
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
< smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
throw ServiceExceptionUtil.exception(USER_SMS_CODE_SEND_TOO_FAST);
}
// TODO 芋艿提升每个 IP 每天可发送数量
// TODO 芋艿提升每个 IP 每小时可发送数量
}
// 创建验证码记录
String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
SysSmsCodeDO newSmsCode = SysSmsCodeDO.builder().mobile(mobile).code(code)
.scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
.createIp(ip).used(false).build();
smsCodeMapper.insert(newSmsCode);
return code;
}
@Override
public void useSmsCode(String mobile, Integer scene, String code, String usedIp) {
// 检测验证码是否有效
SysSmsCodeDO lastSmsCode = this.checkCodeIsExpired(mobile, code, scene);
// 判断验证码是否已被使用
if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {
throw ServiceExceptionUtil.exception(USER_SMS_CODE_USED);
}
// 使用验证码
smsCodeMapper.updateById(SysSmsCodeDO.builder().id(lastSmsCode.getId())
.used(true).usedTime(new Date()).usedIp(usedIp).build());
}
@Override
public void sendSmsCodeLogin(Long userId) {
UserDO user = userService.getUser(userId);
if (user == null){
throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
}
// 发送验证码
this.sendSmsCode(user.getMobile(),SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), getClientIP());
}
@Override
public SysSmsCodeDO checkCodeIsExpired(String mobile, String code, Integer scene) {
// 校验验证码
SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile,code,scene);
// 若验证码不存在抛出异常
if (lastSmsCode == null) {
throw ServiceExceptionUtil.exception(USER_SMS_CODE_NOT_FOUND);
}
if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
>= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
throw ServiceExceptionUtil.exception(USER_SMS_CODE_EXPIRED);
}
return lastSmsCode;
}
}

View File

@ -1,18 +1,18 @@
package cn.iocoder.yudao.userserver.modules.member.service.user;
package cn.iocoder.yudao.module.member.service.user;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserInfoRespVO;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import java.io.InputStream;
/**
* 前台用户 Service 接口
* 会员用户 Service 接口
*
* @author 芋道源码
*/
public interface MbrUserService {
public interface UserService {
/**
* 通过手机查询用户
@ -20,7 +20,7 @@ public interface MbrUserService {
* @param mobile 手机
* @return 用户对象
*/
MbrUserDO getUserByMobile(String mobile);
UserDO getUserByMobile(String mobile);
/**
* 基于手机号创建用户
@ -30,7 +30,7 @@ public interface MbrUserService {
* @param registerIp 注册 IP
* @return 用户对象
*/
MbrUserDO createUserIfAbsent(@Mobile String mobile, String registerIp);
UserDO createUserIfAbsent(@Mobile String mobile, String registerIp);
/**
* 更新用户的最后登陆信息
@ -46,14 +46,14 @@ public interface MbrUserService {
* @param id 用户ID
* @return 用户对象信息
*/
MbrUserDO getUser(Long id);
UserDO getUser(Long id);
/**
* 修改用户昵称
* @param userId 用户id
* @param nickname 用户新昵称
*/
void updateNickname(Long userId, String nickname);
void updateUserNickname(Long userId, String nickname);
/**
* 修改用户头像
@ -61,21 +61,13 @@ public interface MbrUserService {
* @param inputStream 头像文件
* @return 头像url
*/
String updateAvatar(Long userId, InputStream inputStream);
/**
* 根据用户id获取用户头像与昵称
*
* @param userId 用户id
* @return 用户响应实体类
*/
MbrUserInfoRespVO getUserInfo(Long userId);
String updateUserAvatar(Long userId, InputStream inputStream);
/**
* 修改手机
* @param userId 用户id
* @param reqVO 请求实体
*/
void updateMobile(Long userId, MbrUserUpdateMobileReqVO reqVO);
void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);
}

View File

@ -0,0 +1,147 @@
package cn.iocoder.yudao.module.member.service.user;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.sms.SysSmsCodeDO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import cn.iocoder.yudao.module.member.dal.mysql.user.UserMapper;
import cn.iocoder.yudao.module.member.enums.SysErrorCodeConstants;
import cn.iocoder.yudao.module.member.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.module.member.service.sms.SysSmsCodeService;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.InputStream;
import java.util.Date;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.member.enums.MemberErrorCodeConstants.USER_NOT_EXISTS;
/**
* 会员 User Service 实现类
*
* @author 芋道源码
*/
@Service
@Valid
@Slf4j
public class UserServiceImpl implements UserService {
@Resource
private UserMapper memberUserMapper;
@Resource
private InfFileCoreService fileCoreService;
@Resource
private SysSmsCodeService smsCodeService;
@Resource
private PasswordEncoder passwordEncoder;
@Override
public UserDO getUserByMobile(String mobile) {
return memberUserMapper.selectByMobile(mobile);
}
@Override
public UserDO createUserIfAbsent(String mobile, String registerIp) {
// 用户已经存在
UserDO user = memberUserMapper.selectByMobile(mobile);
if (user != null) {
return user;
}
// 用户不存在则进行创建
return this.createUser(mobile, registerIp);
}
private UserDO createUser(String mobile, String registerIp) {
// 生成密码
String password = IdUtil.fastSimpleUUID();
// 插入用户
UserDO user = new UserDO();
user.setMobile(mobile);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setPassword(passwordEncoder.encode(password)); // 加密密码
user.setRegisterIp(registerIp);
memberUserMapper.insert(user);
return user;
}
@Override
public void updateUserLogin(Long id, String loginIp) {
memberUserMapper.updateById(new UserDO().setId(id)
.setLoginIp(loginIp).setLoginDate(new Date()));
}
@Override
public UserDO getUser(Long id) {
return memberUserMapper.selectById(id);
}
@Override
public void updateUserNickname(Long userId, String nickname) {
UserDO user = this.checkUserExists(userId);
// 仅当新昵称不等于旧昵称时进行修改
if (nickname.equals(user.getNickname())){
return;
}
UserDO userDO = new UserDO();
userDO.setId(user.getId());
userDO.setNickname(nickname);
memberUserMapper.updateById(userDO);
}
@Override
public String updateUserAvatar(Long userId, InputStream avatarFile) {
this.checkUserExists(userId);
// 创建文件
String avatar = fileCoreService.createFile(IdUtil.fastUUID(), IoUtil.readBytes(avatarFile));
// 更新头像路径
memberUserMapper.updateById(UserDO.builder().id(userId).avatar(avatar).build());
return avatar;
}
@Transactional(rollbackFor = Exception.class)
public void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO) {
// 检测用户是否存在
checkUserExists(userId);
// 校验旧手机和旧验证码
SysSmsCodeDO sysSmsCodeDO = smsCodeService.checkCodeIsExpired(reqVO.getOldMobile(), reqVO.getOldCode(),
SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene());
// 判断旧 code 是否未被使用如果是抛出异常
if (Boolean.FALSE.equals(sysSmsCodeDO.getUsed())){
throw ServiceExceptionUtil.exception(SysErrorCodeConstants.USER_SMS_CODE_IS_UNUSED);
}
// 使用新验证码
smsCodeService.useSmsCode(reqVO.getMobile(), SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(),
reqVO.getCode(),getClientIP());
// 更新用户手机
memberUserMapper.updateById(UserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
}
@VisibleForTesting
public UserDO checkUserExists(Long id) {
if (id == null) {
return null;
}
UserDO user = memberUserMapper.selectById(id);
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
return user;
}
}

View File

@ -1,20 +1,18 @@
package cn.iocoder.yudao.userserver.modules.system.service;
package cn.iocoder.yudao.module.member.service.auth;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService;
import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService;
import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.userserver.BaseDbAndRedisUnitTest;
import cn.iocoder.yudao.userserver.modules.member.dal.mysql.user.MbrUserMapper;
import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.MbrAuthResetPasswordReqVO;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.MbrAuthUpdatePasswordReqVO;
import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService;
import cn.iocoder.yudao.userserver.modules.system.service.auth.impl.SysAuthServiceImpl;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.AppAuthResetPasswordReqVO;
import cn.iocoder.yudao.module.member.controller.app.auth.vo.AppAuthUpdatePasswordReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import cn.iocoder.yudao.module.member.dal.mysql.user.UserMapper;
import cn.iocoder.yudao.module.member.service.sms.SysSmsCodeService;
import cn.iocoder.yudao.module.member.service.user.UserService;
import cn.iocoder.yudao.module.member.test.BaseDbAndRedisUnitTest;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
@ -23,7 +21,6 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle;
@ -35,17 +32,17 @@ import static org.mockito.Mockito.when;
// TODO @芋艿单测的 review等逻辑都达成一致后
/**
* {@link SysAuthService} 的单元测试类
* {@link AuthService} 的单元测试类
*
* @author 宋天
*/
@Import({SysAuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
@Import({AuthServiceImpl.class, YudaoRedisAutoConfiguration.class})
public class SysAuthServiceTest extends BaseDbAndRedisUnitTest {
@MockBean
private AuthenticationManager authenticationManager;
@MockBean
private MbrUserService userService;
private UserService userService;
@MockBean
private SysSmsCodeService smsCodeService;
@MockBean
@ -59,21 +56,21 @@ public class SysAuthServiceTest extends BaseDbAndRedisUnitTest {
@MockBean
private PasswordEncoder passwordEncoder;
@Resource
private MbrUserMapper mbrUserMapper;
private UserMapper mbrUserMapper;
@Resource
private SysAuthServiceImpl authService;
private AuthServiceImpl authService;
@Test
public void testUpdatePassword_success(){
// 准备参数
MbrUserDO userDO = randomMbrUserDO();
UserDO userDO = randomUserDO();
mbrUserMapper.insert(userDO);
// 新密码
String newPassword = randomString();
// 请求实体
MbrAuthUpdatePasswordReqVO reqVO = MbrAuthUpdatePasswordReqVO.builder()
AppAuthUpdatePasswordReqVO reqVO = AppAuthUpdatePasswordReqVO.builder()
.oldPassword(userDO.getPassword())
.password(newPassword)
.build();
@ -84,14 +81,14 @@ public class SysAuthServiceTest extends BaseDbAndRedisUnitTest {
when(passwordEncoder.encode(newPassword)).thenReturn(newPassword);
// 更新用户密码
authService.updatePassword(userDO.getId(),reqVO);
authService.updatePassword(userDO.getId(), reqVO);
assertEquals(mbrUserMapper.selectById(userDO.getId()).getPassword(),newPassword);
}
@Test
public void testResetPassword_success(){
// 准备参数
MbrUserDO userDO = randomMbrUserDO();
UserDO userDO = randomUserDO();
mbrUserMapper.insert(userDO);
// 随机密码
@ -99,17 +96,15 @@ public class SysAuthServiceTest extends BaseDbAndRedisUnitTest {
// 随机验证码
String code = randomNumbers(4);
MbrAuthResetPasswordReqVO reqVO = MbrAuthResetPasswordReqVO.builder()
.password(password)
.code(code)
.build();
// 放入code+手机号
stringRedisTemplate.opsForValue().set(code,userDO.getMobile(),10, TimeUnit.MINUTES);
// mock
when(passwordEncoder.encode(password)).thenReturn(password);
// 更新用户密码
AppAuthResetPasswordReqVO reqVO = new AppAuthResetPasswordReqVO();
reqVO.setMobile(userDO.getMobile());
reqVO.setPassword(password);
reqVO.setCode(code);
authService.resetPassword(reqVO);
assertEquals(mbrUserMapper.selectById(userDO.getId()).getPassword(),password);
}
@ -118,12 +113,12 @@ public class SysAuthServiceTest extends BaseDbAndRedisUnitTest {
// ========== 随机对象 ==========
@SafeVarargs
private static MbrUserDO randomMbrUserDO(Consumer<MbrUserDO>... consumers) {
Consumer<MbrUserDO> consumer = (o) -> {
private static UserDO randomUserDO(Consumer<UserDO>... consumers) {
Consumer<UserDO> consumer = (o) -> {
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
o.setPassword(randomString());
};
return randomPojo(MbrUserDO.class, ArrayUtils.append(consumer, consumers));
return randomPojo(UserDO.class, ArrayUtils.append(consumer, consumers));
}

View File

@ -1,19 +1,18 @@
package cn.iocoder.yudao.userserver.modules.member.service;
package cn.iocoder.yudao.module.member.service.user;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService;
import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.userserver.BaseDbAndRedisUnitTest;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO;
import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO;
import cn.iocoder.yudao.userserver.modules.member.dal.mysql.user.MbrUserMapper;
import cn.iocoder.yudao.userserver.modules.member.service.user.impl.MbrUserServiceImpl;
import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSendSmsReqVO;
import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.userserver.modules.system.service.auth.impl.SysAuthServiceImpl;
import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
import cn.iocoder.yudao.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
import cn.iocoder.yudao.module.member.dal.dataobject.sms.SysSmsCodeDO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.UserDO;
import cn.iocoder.yudao.module.member.dal.mysql.user.UserMapper;
import cn.iocoder.yudao.module.member.enums.sms.SysSmsSceneEnum;
import cn.iocoder.yudao.module.member.service.auth.AuthServiceImpl;
import cn.iocoder.yudao.module.member.service.sms.SysSmsCodeService;
import cn.iocoder.yudao.module.member.test.BaseDbAndRedisUnitTest;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
@ -32,24 +31,24 @@ import static org.mockito.Mockito.*;
// TODO @芋艿单测的 review等逻辑都达成一致后
/**
* {@link MbrUserServiceImpl} 的单元测试类
* {@link UserServiceImpl} 的单元测试类
*
* @author 宋天
*/
@Import({MbrUserServiceImpl.class, YudaoRedisAutoConfiguration.class})
@Import({UserServiceImpl.class, YudaoRedisAutoConfiguration.class})
public class MbrUserServiceImplTest extends BaseDbAndRedisUnitTest {
@Resource
private MbrUserServiceImpl mbrUserService;
private UserServiceImpl mbrUserService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private MbrUserMapper userMapper;
private UserMapper userMapper;
@MockBean
private SysAuthServiceImpl authService;
private AuthServiceImpl authService;
@MockBean
private InfFileCoreService fileCoreService;
@ -63,35 +62,24 @@ public class MbrUserServiceImplTest extends BaseDbAndRedisUnitTest {
@Test
public void testUpdateNickName_success(){
// mock 数据
MbrUserDO userDO = randomMbrUserDO();
UserDO userDO = randomUserDO();
userMapper.insert(userDO);
// 随机昵称
String newNickName = randomString();
// 调用接口修改昵称
mbrUserService.updateNickname(userDO.getId(),newNickName);
mbrUserService.updateUserNickname(userDO.getId(),newNickName);
// 查询新修改后的昵称
String nickname = mbrUserService.getUser(userDO.getId()).getNickname();
// 断言
assertEquals(newNickName,nickname);
}
@Test
public void testGetUserInfo_success(){
// mock 数据
MbrUserDO userDO = randomMbrUserDO();
userMapper.insert(userDO);
// 查询用户昵称与头像
MbrUserInfoRespVO userInfo = mbrUserService.getUserInfo(userDO.getId());
System.out.println(userInfo);
}
@Test
public void testUpdateAvatar_success(){
// mock 数据
MbrUserDO dbUser = randomMbrUserDO();
UserDO dbUser = randomUserDO();
userMapper.insert(dbUser);
// 准备参数
@ -102,7 +90,7 @@ public class MbrUserServiceImplTest extends BaseDbAndRedisUnitTest {
String avatar = randomString();
when(fileCoreService.createFile(anyString(), eq(avatarFileBytes))).thenReturn(avatar);
// 调用
String str = mbrUserService.updateAvatar(userId, avatarFile);
String str = mbrUserService.updateUserAvatar(userId, avatarFile);
// 断言
assertEquals(avatar, str);
}
@ -111,28 +99,28 @@ public class MbrUserServiceImplTest extends BaseDbAndRedisUnitTest {
public void updateMobile_success(){
// mock数据
String oldMobile = randomNumbers(11);
MbrUserDO userDO = randomMbrUserDO();
UserDO userDO = randomUserDO();
userDO.setMobile(oldMobile);
userMapper.insert(userDO);
// 验证旧手机
sysSmsCodeService.sendSmsCodeLogin(userDO.getId());
// 验证旧手机验证码是否正确
sysSmsCodeService.useSmsCode(oldMobile,SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(),"123","1.1.1.1");
// 验证新手机
SysAuthSendSmsReqVO smsReqVO = new SysAuthSendSmsReqVO();
smsReqVO.setMobile(oldMobile);
smsReqVO.setScene(SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene());
sysSmsCodeService.sendSmsNewCode(smsReqVO);
// 旧手机和旧验证码
SysSmsCodeDO codeDO = new SysSmsCodeDO();
String oldCode = RandomUtil.randomString(4);
codeDO.setMobile(userDO.getMobile());
codeDO.setCode(oldCode);
codeDO.setScene(SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene());
codeDO.setUsed(Boolean.FALSE);
when(sysSmsCodeService.checkCodeIsExpired(codeDO.getMobile(),codeDO.getCode(),codeDO.getScene())).thenReturn(codeDO);
// 更新手机号
String newMobile = randomNumbers(11);
String code = randomNumbers(4);
MbrUserUpdateMobileReqVO reqVO = new MbrUserUpdateMobileReqVO();
String newCode = randomNumbers(4);
AppUserUpdateMobileReqVO reqVO = new AppUserUpdateMobileReqVO();
reqVO.setMobile(newMobile);
reqVO.setCode(code);
mbrUserService.updateMobile(userDO.getId(),reqVO);
reqVO.setCode(newCode);
reqVO.setOldMobile(oldMobile);
reqVO.setOldCode(oldCode);
mbrUserService.updateUserMobile(userDO.getId(),reqVO);
assertEquals(mbrUserService.getUser(userDO.getId()).getMobile(),newMobile);
}
@ -140,11 +128,11 @@ public class MbrUserServiceImplTest extends BaseDbAndRedisUnitTest {
// ========== 随机对象 ==========
@SafeVarargs
private static MbrUserDO randomMbrUserDO(Consumer<MbrUserDO>... consumers) {
Consumer<MbrUserDO> consumer = (o) -> {
private static UserDO randomUserDO(Consumer<UserDO>... consumers) {
Consumer<UserDO> consumer = (o) -> {
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
};
return randomPojo(MbrUserDO.class, ArrayUtils.append(consumer, consumers));
return randomPojo(UserDO.class, ArrayUtils.append(consumer, consumers));
}
}

View File

@ -1,9 +1,8 @@
package cn.iocoder.yudao.userserver;
package cn.iocoder.yudao.module.member.test;
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
import cn.iocoder.yudao.userserver.config.RedisTestConfiguration;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import org.redisson.spring.starter.RedissonAutoConfiguration;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver;
package cn.iocoder.yudao.module.member.test;
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.userserver.config;
package cn.iocoder.yudao.module.member.test;
import com.github.fppt.jedismock.RedisServer;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;

View File

@ -42,3 +42,8 @@ mybatis:
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao.module.member.dal.mysql
core-service:
base-package: cn.iocoder.yudao.module.member.dal.mysql # TODO 芋艿:要清理掉

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -90,15 +90,6 @@
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- TODO @宋天junit 已经在 yudao-spring-boot-starter-test 啦,不用在引入哈 -->
<!--单元测试相关-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!-- 工具类相关 -->
</dependencies>

Some files were not shown because too many files have changed in this diff Show More