Commit 131f4c8e by tangyi

优化

parent eeaaf0e2
Version v3.0.0 (2019-7-2)
--------------------------
改进:
* 优化swagger ui配置,增加租户标识请求头
* 完善手机号登录
Version v3.0.0 (2019-6-23)
--------------------------
......
......@@ -126,7 +126,12 @@ public class CommonConstant {
/**
* 保存code的前缀
*/
public static final String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY";
public static final String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY_";
/**
* 验证码长度
*/
public static final String CODE_SIZE = "4";
/**
* Bearer
......@@ -138,5 +143,15 @@ public class CommonConstant {
*/
public static final String GRANT_TYPE_PASSWORD = "password";
/**
* 手机号类型
*/
public static final String GRANT_TYPE_MOBILE = "mobile";
/**
* 租户编号请求头
*/
public static final String TENANT_CODE_HEADER = "Tenant-Code";
}
......@@ -22,4 +22,9 @@ public class ServiceConstant {
* 授权服务名称
*/
public static final String AUTH_SERVICE = "auth-service";
/**
* 消息中心服务名称
*/
public static final String MSC_SERVICE = "msc-service";
}
package com.github.tangyi.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 登录类型
*
* @author tangyi
* @date 2019/07/02 09:45
*/
@Getter
@AllArgsConstructor
public enum LoginType {
/**
* 账号密码登录
*/
PWD("PWD", "账号密码登录"),
/**
* 验证码登录
*/
SMS("SMS", "验证码登录"),
/**
* QQ登录
*/
QQ("QQ", "QQ登录"),
/**
* 微信登录
*/
WECHAT("WX", "微信登录");
/**
* 类型
*/
private String type;
/**
* 描述
*/
private String description;
}
......@@ -22,6 +22,11 @@ public class SecurityConstant {
public static final int DEFAULT_IMAGE_EXPIRE = 60;
/**
* 默认短信验证码过期时间
*/
public static final int DEFAULT_SMS_EXPIRE = 5 * 60;
/**
* 正常状态
*/
public static final String NORMAL = "0";
......
......@@ -33,7 +33,6 @@ public class TenantTokenFilter implements Filter {
if (tenantCode == null)
tenantCode = SecurityConstant.DEFAULT_TENANT_CODE;
TenantContextHolder.setTenantCode(tenantCode);
log.info("租户code:{}", tenantCode);
filterChain.doFilter(request, response);
TenantContextHolder.clear();
}
......
......@@ -82,6 +82,8 @@ ignore:
- /csrf
- /actuator/**
- /hystrix.sender
- /v1/sms/**
- /v1/user/findUserBySocial/**
- /v1/user/findUserByUsername/**
- /v1/tenant/findTenantByTenantCode/**
- /v1/user/checkExist/**
......
......@@ -106,7 +106,9 @@ ignore:
- /csrf
- /actuator/**
- /hystrix.sender
- /v1/sms/**
- /v1/user/findUserByUsername/**
- /v1/user/findUserBySocial/**
- /v1/tenant/findTenantByTenantCode/**
- /v1/menu/findMenuByRole/**
- /v1/menu/findAllMenu
......
......@@ -75,6 +75,7 @@ preview:
- updateInfo
- attachment
- api/exam # 考试服务
- api/msc
# 开启网关token转换
gateway:
......
......@@ -2,9 +2,9 @@ server:
port: 8085
turbine:
appConfig: consul,auth-service,exam-service,user-service,gateway-service
appConfig: consul,auth-service,exam-service,user-service,gateway-service,msc-service
aggregator:
clusterConfig: CONSUL,AUTH-SERVICE,EXAM-SERVICE,USER-SERVICE,GATEWAY-SERVICE
clusterConfig: CONSUL,AUTH-SERVICE,EXAM-SERVICE,USER-SERVICE,GATEWAY-SERVICE,MSC-SERVICE
spring:
security:
......
......@@ -28,8 +28,8 @@ management:
show-details: ALWAYS
sms:
appKey:
appSecret:
appKey: test
appSecret: test
regionId: cn-hangzhou
domain: dysmsapi.aliyuncs.com
......@@ -43,27 +43,19 @@ ignore:
- /csrf
- /actuator/**
- /hystrix.sender
- /v1/user/findUserByUsername/**
- /v1/tenant/findTenantByTenantCode/**
- /v1/user/checkExist/**
- /v1/user/updatePassword
- /v1/menu/findMenuByRole/**
- /v1/menu/findAllMenu
- /v1/code/**
- /v1/attachment/download
- /v1/log/**
- /authentication/**
- /v1/authentication/**
- /v1/sms/**
- /**/*.css
- /**/*.js
- /social
- /signin
- /signup
- /info
- /health
- /metrics/**
- /loggers/**
# 集群ID生成配置
cluster:
workId: ${CLUSTER_WORKID:1}
dataCenterId: ${CLUSTER_DATA_CENTER_ID:1}
logging:
level:
root: info
......
......@@ -130,7 +130,10 @@ ignore:
- /csrf
- /actuator/**
- /hystrix.sender
- /v1/sms/**
- /v1/mobile/**
- /v1/user/findUserByUsername/**
- /v1/user/findUserBySocial/**
- /v1/tenant/findTenantByTenantCode/**
- /v1/menu/findMenuByRole/**
- /v1/menu/findAllMenu
......
......@@ -69,8 +69,8 @@ public class TokenRequestGlobalFilter implements GlobalFilter, Ordered {
if (HttpMethod.POST.matches(request.getMethodValue())
&& StrUtil.containsAnyIgnoreCase(uri.getPath(), GatewayConstant.OAUTH_TOKEN_URL, GatewayConstant.REGISTER, GatewayConstant.MOBILE_TOKEN_URL)) {
String grantType = request.getQueryParams().getFirst(GatewayConstant.GRANT_TYPE);
// 授权类型为密码模式
if (CommonConstant.GRANT_TYPE_PASSWORD.equals(grantType) || GatewayConstant.GRANT_TYPE_REFRESH_TOKEN.equals(grantType)) {
// 授权类型为密码、手机号模式
if (CommonConstant.GRANT_TYPE_PASSWORD.equals(grantType) || CommonConstant.GRANT_TYPE_MOBILE.equals(grantType) || GatewayConstant.GRANT_TYPE_REFRESH_TOKEN.equals(grantType)) {
// 装饰器
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
......
......@@ -2,6 +2,7 @@ package com.github.tangyi.gateway.filters;
import cn.hutool.core.util.StrUtil;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.enums.LoginType;
import com.github.tangyi.common.core.exceptions.InvalidValidateCodeException;
import com.github.tangyi.common.core.exceptions.ValidateCodeExpiredException;
import com.github.tangyi.gateway.constants.GatewayConstant;
......@@ -40,10 +41,10 @@ public class ValidateCodeFilter implements GlobalFilter, Ordered {
if (HttpMethod.POST.matches(request.getMethodValue())
&& StrUtil.containsAnyIgnoreCase(uri.getPath(), GatewayConstant.OAUTH_TOKEN_URL, GatewayConstant.REGISTER, GatewayConstant.MOBILE_TOKEN_URL)) {
String grantType = request.getQueryParams().getFirst(GatewayConstant.GRANT_TYPE);
// 授权类型为密码模式、注册才校验验证码
if (CommonConstant.GRANT_TYPE_PASSWORD.equals(grantType) || StrUtil.containsAnyIgnoreCase(uri.getPath(), GatewayConstant.REGISTER)) {
// 授权类型为密码模式、手机号、注册才校验验证码
if (CommonConstant.GRANT_TYPE_PASSWORD.equals(grantType) || CommonConstant.GRANT_TYPE_MOBILE.equals(grantType) || StrUtil.containsAnyIgnoreCase(uri.getPath(), GatewayConstant.REGISTER)) {
// 校验验证码
checkCode(request);
checkCode(request, getLoginType(grantType));
}
}
return chain.filter(exchange);
......@@ -58,18 +59,23 @@ public class ValidateCodeFilter implements GlobalFilter, Ordered {
* 校验验证码
*
* @param serverHttpRequest serverHttpRequest
* @param loginType loginType
* @throws InvalidValidateCodeException
*/
private void checkCode(ServerHttpRequest serverHttpRequest) throws InvalidValidateCodeException {
private void checkCode(ServerHttpRequest serverHttpRequest, LoginType loginType) throws InvalidValidateCodeException {
MultiValueMap<String, String> params = serverHttpRequest.getQueryParams();
// 租户标识
String tenantCode = serverHttpRequest.getHeaders().getFirst("Tenant-Code");
// 验证码
String code = params.getFirst("code");
if (StrUtil.isBlank(code))
throw new InvalidValidateCodeException("请输入验证码");
throw new InvalidValidateCodeException("请输入验证码.");
// 获取随机码
String randomStr = params.getFirst("randomStr");
// 随机数为空,则获取手机号
if (StrUtil.isBlank(randomStr))
randomStr = params.getFirst("mobile");
String key = CommonConstant.DEFAULT_CODE_KEY + randomStr;
String key = tenantCode + ":" + CommonConstant.DEFAULT_CODE_KEY + loginType.getType() + "@" + randomStr;
// 验证码过期
if (!redisTemplate.hasKey(key))
throw new ValidateCodeExpiredException(GatewayConstant.EXPIRED_ERROR);
......@@ -83,9 +89,22 @@ public class ValidateCodeFilter implements GlobalFilter, Ordered {
}
if (!StrUtil.equals(saveCode, code)) {
redisTemplate.delete(key);
throw new InvalidValidateCodeException("验证码错误,请重新输入");
throw new InvalidValidateCodeException("验证码错误.");
}
redisTemplate.delete(key);
}
/**
* 获取登录类型
*
* @param grantType grantType
* @return LoginType
*/
private LoginType getLoginType(String grantType) {
if (CommonConstant.GRANT_TYPE_PASSWORD.equals(grantType))
return LoginType.PWD;
if (CommonConstant.GRANT_TYPE_MOBILE.equals(grantType))
return LoginType.SMS;
return LoginType.PWD;
}
}
......@@ -62,4 +62,9 @@ public class AccessToken implements Serializable {
* 租户标识
*/
private String tenantCode;
/**
* 登录类型
*/
private String loginType;
}
......@@ -34,15 +34,9 @@ public class SwaggerConfig implements WebMvcConfigurer {
@Bean
public Docket createRestApi() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
List<Parameter> parameterList = new ArrayList<>();
tokenBuilder.name("Authorization")
.defaultValue("去其他请求中获取heard中token参数")
.description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
parameterList.add(tokenBuilder.build());
parameterList.add(authorizationParameter());
parameterList.add(tenantCodeParameter());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
......@@ -52,6 +46,36 @@ public class SwaggerConfig implements WebMvcConfigurer {
.globalOperationParameters(parameterList);
}
/**
* Authorization 请求头
* @return Parameter
*/
private Parameter authorizationParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.defaultValue("bearer authorization参数")
.description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
return tokenBuilder.build();
}
/**
* Tenant-Code 请求头
* @return Parameter
*/
private Parameter tenantCodeParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Tenant-Code")
.defaultValue("租户标识")
.description("租户标识")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
return tokenBuilder.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger API")
......
package com.github.tangyi.auth.security;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.enums.LoginType;
import com.github.tangyi.common.security.tenant.TenantContextHolder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
......@@ -20,12 +21,17 @@ public class CustomTokenConverter extends JwtAccessTokenConverter {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
if (authentication.getOAuth2Request().getGrantType().equalsIgnoreCase(CommonConstant.GRANT_TYPE_PASSWORD)) {
final Map<String, Object> additionalInfo = new HashMap<>();
// 加入tenantCode
additionalInfo.put("tenantCode", TenantContextHolder.getTenantCode());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
final Map<String, Object> additionalInfo = new HashMap<>();
String grantType = authentication.getOAuth2Request().getGrantType();
// 加入tenantCode
additionalInfo.put("tenantCode", TenantContextHolder.getTenantCode());
// 加入登录类型,用户名/手机号
if (grantType.equalsIgnoreCase(CommonConstant.GRANT_TYPE_PASSWORD)) {
additionalInfo.put("loginType", LoginType.PWD.getType());
} else if (grantType.equalsIgnoreCase(CommonConstant.GRANT_TYPE_MOBILE)) {
additionalInfo.put("loginType", LoginType.SMS.getType());
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return super.enhance(accessToken, authentication);
}
}
......@@ -49,12 +49,7 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
*/
@Override
public UserDetails loadUserByUsernameAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException, TenantNotFoundException {
if (StringUtils.isBlank(tenantCode))
throw new TenantNotFoundException("租户code不能为空.");
// 先获取租户信息
Tenant tenant = userServiceClient.findTenantByTenantCode(tenantCode);
if (tenant == null)
throw new TenantNotFoundException("租户不存在.");
Tenant tenant = this.validateTenantCode(tenantCode);
UserVo userVo = userServiceClient.findUserByUsername(username, tenantCode);
if (userVo == null)
throw new UsernameNotFoundException("用户名不存在.");
......@@ -72,16 +67,27 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
*/
@Override
public UserDetails loadUserBySocialAndTenantCode(String social, String tenantCode) throws UsernameNotFoundException {
Tenant tenant = this.validateTenantCode(tenantCode);
UserVo userVo = userServiceClient.findUserBySocial(social, tenantCode);
if (userVo == null)
throw new UsernameNotFoundException("用户手机号未注册.");
return new CustomUserDetails(social, userVo.getPassword(), CommonConstant.STATUS_NORMAL.equals(userVo.getStatus()), getAuthority(userVo), userVo.getTenantCode());
}
/**
* 校验租户标识
*
* @param tenantCode tenantCode
* @return Tenant
*/
private Tenant validateTenantCode(String tenantCode) throws TenantNotFoundException {
if (StringUtils.isBlank(tenantCode))
throw new TenantNotFoundException("租户code不能为空.");
// 先获取租户信息
Tenant tenant = userServiceClient.findTenantByTenantCode(tenantCode);
if (tenant == null)
throw new TenantNotFoundException("租户不存在.");
UserVo userVo = userServiceClient.findUserBySocial(social, tenantCode);
if (userVo == null)
throw new UsernameNotFoundException("用户名不存在.");
return new CustomUserDetails(social, userVo.getPassword(), CommonConstant.STATUS_NORMAL.equals(userVo.getStatus()), getAuthority(userVo), userVo.getTenantCode());
return tenant;
}
/**
......
......@@ -34,15 +34,9 @@ public class SwaggerConfig implements WebMvcConfigurer {
@Bean
public Docket createRestApi() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
List<Parameter> parameterList = new ArrayList<>();
tokenBuilder.name("Authorization")
.defaultValue("去其他请求中获取heard中token参数")
.description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
parameterList.add(tokenBuilder.build());
parameterList.add(authorizationParameter());
parameterList.add(tenantCodeParameter());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
......@@ -52,6 +46,36 @@ public class SwaggerConfig implements WebMvcConfigurer {
.globalOperationParameters(parameterList);
}
/**
* Authorization 请求头
* @return Parameter
*/
private Parameter authorizationParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.defaultValue("bearer authorization参数")
.description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
return tokenBuilder.build();
}
/**
* Tenant-Code 请求头
* @return Parameter
*/
private Parameter tenantCodeParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Tenant-Code")
.defaultValue("租户标识")
.description("租户标识")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
return tokenBuilder.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger API")
......
......@@ -2,8 +2,20 @@ package com.github.tangyi.msc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@SpringBootApplication
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
// 扫描api包里的FeignClient
@EnableFeignClients(basePackages = {"com.github.tangyi"})
@ComponentScan(basePackages = {"com.github.tangyi"})
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableCircuitBreaker
public class MscServiceApplication {
public static void main(String[] args) {
......
......@@ -34,15 +34,9 @@ public class SwaggerConfig implements WebMvcConfigurer {
@Bean
public Docket createRestApi() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
List<Parameter> parameterList = new ArrayList<>();
tokenBuilder.name("Authorization")
.defaultValue("去其他请求中获取heard中token参数")
.description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
parameterList.add(tokenBuilder.build());
parameterList.add(authorizationParameter());
parameterList.add(tenantCodeParameter());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
......@@ -52,6 +46,36 @@ public class SwaggerConfig implements WebMvcConfigurer {
.globalOperationParameters(parameterList);
}
/**
* Authorization 请求头
* @return Parameter
*/
private Parameter authorizationParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.defaultValue("bearer authorization参数")
.description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
return tokenBuilder.build();
}
/**
* Tenant-Code 请求头
* @return Parameter
*/
private Parameter tenantCodeParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Tenant-Code")
.defaultValue("租户标识")
.description("租户标识")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
return tokenBuilder.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger API")
......
# bootstrap.yml文件中的内容不能放到application.yml中,否则config部分无法被加载
# 因为config部分的配置先于application.yml被加载,而bootstrap.yml中的配置会先于application.yml加载
# bootstrap.yml文件中的内容不能放到application.yml中,否则config部分无法被加载
# 因为config部分的配置先于application.yml被加载,而bootstrap.yml中的配置会先于application.yml加载
spring:
application:
name: msc-service
cloud:
# 使用consul作为注册中心
# 使用consul作为注册中心
consul:
host: ${CONSUL_HOST:localhost}
port: ${CONSUL_PORT:8500}
# 使用配置服务中心的配置信息
config:
fail-fast: true # 在某些情况下,如果服务无法连接到配置服务器,则可能希望启动服务失败,客户端将以异常停止
fail-fast: true # 在某些情况下,如果服务无法连接到配置服务器,则可能希望启动服务失败,客户端将以异常停止
retry:
max-attempts: 5 # 配置客户端重试,默认行为是重试6次,初始退避间隔为1000ms,指数乘数为1.1
max-attempts: 5 # 配置客户端重试,默认行为是重试6次,初始退避间隔为1000ms,指数乘数为1.1
discovery:
# 默认false,设为true表示使用注册中心中的配置服务(服务发现)而不自己指定配置服务的地址(即uri)
# 默认false,设为true表示使用注册中心中的配置服务(服务发现)而不自己指定配置服务的地址(即uri)
enabled: true
# 指向配置中心在consul注册的服务名称(即:spring.application.name)
# 指向配置中心在consul注册的服务名称(即:spring.application.name)
service-id: config-service
\ No newline at end of file
......@@ -36,6 +36,12 @@
<artifactId>exam-api</artifactId>
</dependency>
<!-- msc-api -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>msc-api</artifactId>
</dependency>
<!-- web 服务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
......
......@@ -34,15 +34,9 @@ public class SwaggerConfig implements WebMvcConfigurer {
@Bean
public Docket createRestApi() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
List<Parameter> parameterList = new ArrayList<>();
tokenBuilder.name("Authorization")
.defaultValue("去其他请求中获取heard中token参数")
.description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
parameterList.add(tokenBuilder.build());
parameterList.add(authorizationParameter());
parameterList.add(tenantCodeParameter());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
......@@ -52,6 +46,36 @@ public class SwaggerConfig implements WebMvcConfigurer {
.globalOperationParameters(parameterList);
}
/**
* Authorization 请求头
* @return Parameter
*/
private Parameter authorizationParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.defaultValue("bearer authorization参数")
.description("令牌")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
return tokenBuilder.build();
}
/**
* Tenant-Code 请求头
* @return Parameter
*/
private Parameter tenantCodeParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Tenant-Code")
.defaultValue("租户标识")
.description("租户标识")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
return tokenBuilder.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger API")
......
package com.github.tangyi.user.controller;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.user.service.MobileService;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
/**
* 手机管理Controller
*
* @author tangyi
* @date 2019/07/02 09:34
*/
@Slf4j
@AllArgsConstructor
@Api("手机管理")
@RestController
@RequestMapping("/v1/mobile")
public class MobileController extends BaseController {
private final MobileService mobileService;
/**
* 发送短信
*
* @param mobile mobile
* @param tenantCode tenantCode
* @return ResponseBean
* @author tangyi
* @date 2019/07/02 09:49:05
*/
@GetMapping("sendSms/{mobile}")
public ResponseBean<Boolean> sendSms(@PathVariable String mobile, @RequestHeader(SecurityConstant.TENANT_CODE_HEADER) String tenantCode) {
return mobileService.sendSms(mobile, tenantCode);
}
}
......@@ -31,6 +31,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
......@@ -38,7 +39,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.security.Principal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
......@@ -89,9 +89,9 @@ public class UserController extends BaseController {
*/
@GetMapping("info")
@ApiOperation(value = "获取用户信息", notes = "获取当前登录用户详细信息")
public ResponseBean<UserInfoDto> user(Principal principal) {
public ResponseBean<UserInfoDto> user(OAuth2Authentication authentication) {
UserVo userVo = new UserVo();
userVo.setUsername(principal.getName());
userVo.setUsername(authentication.getName());
userVo.setTenantCode(SysUtil.getTenantCode());
return new ResponseBean<>(userService.findUserInfo(userVo));
}
......@@ -114,6 +114,23 @@ public class UserController extends BaseController {
}
/**
* 根据用户手机号获取用户详细信息
*
* @param social social
* @param tenantCode tenantCode
* @return UserVo
*/
@GetMapping("/findUserBySocial/{social}")
@ApiOperation(value = "获取用户信息", notes = "根据用户手机号获取用户详细信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "social", value = "用户手机号", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "tenantCode", value = "租户标识", required = true, dataType = "String"),
})
public UserVo findUserBySocial(@PathVariable String social, @RequestParam String tenantCode) {
return userService.selectUserVoBySocial(social, tenantCode);
}
/**
* 获取分页数据
*
* @param pageNum pageNum
......
package com.github.tangyi.user.controller;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.user.service.UserService;
import com.google.code.kaptcha.Producer;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
......@@ -44,18 +40,19 @@ public class ValidateCodeController extends BaseController {
* @date 2018/9/14 20:13
*/
@ApiOperation(value = "生成验证码", notes = "生成验证码")
@ApiImplicitParam(name = "random", value = "随机数", required = true, dataType = "String", paramType = "path")
@ApiImplicitParams({
@ApiImplicitParam(name = "random", value = "随机数", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "tenantCode", value = "租户标识", required = true, dataType = "String")
})
@GetMapping("/{random}")
public void produceCode(@PathVariable String random, HttpServletResponse response) throws Exception {
if (StringUtils.isEmpty(random))
throw new CommonException("随机码不能为空!");
public void produceCode(@PathVariable String random, @RequestParam String tenantCode, HttpServletResponse response) throws Exception {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
// 生成文字验证码
String text = producer.createText();
// 生成图片验证码
BufferedImage image = producer.createImage(text);
userService.saveImageCode(random, text);
userService.saveImageCode(tenantCode, random, text);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "JPEG", out);
IOUtils.closeQuietly(out);
......
......@@ -27,6 +27,15 @@ public interface UserMapper extends CrudMapper<User> {
UserVo selectUserVoByUsername(@Param("username") String username, @Param("tenantCode") String tenantCode);
/**
* 通过用户手机号查询用户信息(含有角色信息)
*
* @param social 用户手机号
* @param tenantCode 租户标识
* @return userVo
*/
UserVo selectUserVoBySocial(@Param("social") String social, @Param("tenantCode") String tenantCode);
/**
* 查询用户数量
*
* @param userVo userVo
......
package com.github.tangyi.user.service;
import cn.hutool.core.util.RandomUtil;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.enums.LoginType;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.vo.UserVo;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.msc.api.constant.SmsConstant;
import com.github.tangyi.msc.api.dto.SmsDto;
import com.github.tangyi.msc.api.feign.MscServiceClient;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 手机管理Service
*
* @author tangyi
* @date 2019/07/02 09:35
*/
@Slf4j
@AllArgsConstructor
@Service
public class MobileService {
private final UserService userService;
private final RedisTemplate redisTemplate;
private final MscServiceClient mscServiceClient;
/**
* 发送短信
*
* @param mobile mobile
* @param tenantCode tenantCode
* @return ResponseBean
* @author tangyi
* @date 2019/07/02 09:36:52
*/
public ResponseBean<Boolean> sendSms(String mobile, String tenantCode) {
UserVo userVo = userService.selectUserVoBySocial(mobile, tenantCode);
if (userVo == null) {
log.info("手机号未注册:{}", mobile);
return new ResponseBean<>(Boolean.FALSE, "手机号未注册.");
}
Object codeObj = redisTemplate.opsForValue().get(CommonConstant.DEFAULT_CODE_KEY + mobile);
if (codeObj != null) {
log.info("手机号验证码未过期:{},{}", mobile, codeObj);
return new ResponseBean<>(Boolean.FALSE, "手机号未注册.");
}
String code = RandomUtil.randomNumbers(Integer.parseInt(CommonConstant.CODE_SIZE));
log.debug("手机号生成验证码成功:{},{}", mobile, code);
redisTemplate.opsForValue().set(tenantCode + ":" + CommonConstant.DEFAULT_CODE_KEY + LoginType.SMS.getType() + "@" + mobile
, code, SecurityConstant.DEFAULT_SMS_EXPIRE, TimeUnit.SECONDS);
// 调用消息中心服务,发送短信验证码
SmsDto smsDto = new SmsDto();
smsDto.setReceiver(mobile);
smsDto.setContent(String.format(SmsConstant.SMS_TEMPLATE, code));
mscServiceClient.sendSms(smsDto);
return new ResponseBean<>(Boolean.TRUE, code);
}
}
package com.github.tangyi.user.service;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.enums.LoginType;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.service.CrudService;
import com.github.tangyi.common.core.utils.IdGen;
......@@ -90,40 +91,42 @@ public class UserService extends CrudService<UserMapper, User> {
* @date 2018/9/11 23:44
*/
public UserInfoDto findUserInfo(UserVo userVo) {
String tenantCode = userVo.getTenantCode();
String tenantCode = userVo.getTenantCode(), username = userVo.getUsername();
// 根据用户名查询用户信息
userVo = userMapper.selectUserVoByUsername(userVo.getUsername(), tenantCode);
userVo = userMapper.selectUserVoByUsername(username, tenantCode);
if (userVo == null)
userVo = userMapper.selectUserVoBySocial(username, tenantCode);
if (userVo == null)
throw new CommonException("查询用户信息失败.");
UserInfoDto user = new UserInfoDto();
if (userVo != null) {
BeanUtils.copyProperties(userVo, user);
// 用户角色
List<String> roles = new ArrayList<>();
// 用户权限
List<String> permissions = new ArrayList<>();
// 根据角色获取权限
if (CollectionUtils.isNotEmpty(userVo.getRoleList())) {
userVo.getRoleList().stream()
// 过滤普通用户角色
.filter(role -> !SecurityConstant.BASE_ROLE.equals(role.getRoleName()))
.forEach(role -> {
// 根据角色查找菜单
List<Menu> menuList = menuService.findMenuByRole(role.getRoleCode(), tenantCode);
if (CollectionUtils.isNotEmpty(menuList)) {
menuList.stream()
// 获取权限菜单
.filter(menu -> MenuConstant.MENU_TYPE_PERMISSION.equals(menu.getType()))
// 获取权限
.forEach(menu -> permissions.add(menu.getPermission()));
}
// 保存角色code
roles.add(role.getRoleCode());
});
}
// 头像信息
this.initUserAvatar(user, userVo);
user.setRoles(roles.toArray(new String[0]));
user.setPermissions(permissions.toArray(new String[0]));
BeanUtils.copyProperties(userVo, user);
// 用户角色
List<String> roles = new ArrayList<>();
// 用户权限
List<String> permissions = new ArrayList<>();
// 根据角色获取权限
if (CollectionUtils.isNotEmpty(userVo.getRoleList())) {
userVo.getRoleList().stream()
// 过滤普通用户角色
.filter(role -> !SecurityConstant.BASE_ROLE.equals(role.getRoleName()))
.forEach(role -> {
// 根据角色查找菜单
List<Menu> menuList = menuService.findMenuByRole(role.getRoleCode(), tenantCode);
if (CollectionUtils.isNotEmpty(menuList)) {
menuList.stream()
// 获取权限菜单
.filter(menu -> MenuConstant.MENU_TYPE_PERMISSION.equals(menu.getType()))
// 获取权限
.forEach(menu -> permissions.add(menu.getPermission()));
}
// 保存角色code
roles.add(role.getRoleCode());
});
}
// 头像信息
this.initUserAvatar(user, userVo);
user.setRoles(roles.toArray(new String[0]));
user.setPermissions(permissions.toArray(new String[0]));
return user;
}
......@@ -215,15 +218,28 @@ public class UserService extends CrudService<UserMapper, User> {
}
/**
* 根据用户手机号查找
*
* @param social social
* @param tenantCode tenantCode
* @return UserVo
*/
@Cacheable(value = "user", key = "#social")
public UserVo selectUserVoBySocial(String social, String tenantCode) {
return userMapper.selectUserVoBySocial(social, tenantCode);
}
/**
* 保存验证码
*
* @param random random
* @param imageCode imageCode
* @param tenantCode tenantCode
* @param random random
* @param imageCode imageCode
* @author tangyi
* @date 2018/9/14 20:12
*/
public void saveImageCode(String random, String imageCode) {
redisTemplate.opsForValue().set(CommonConstant.DEFAULT_CODE_KEY + random, imageCode, SecurityConstant.DEFAULT_IMAGE_EXPIRE, TimeUnit.SECONDS);
public void saveImageCode(String tenantCode, String random, String imageCode) {
redisTemplate.opsForValue().set(tenantCode + ":" + CommonConstant.DEFAULT_CODE_KEY + LoginType.PWD.getType() + "@" + random, imageCode, SecurityConstant.DEFAULT_IMAGE_EXPIRE, TimeUnit.SECONDS);
}
/**
......
......@@ -152,6 +152,11 @@
WHERE `user`.username = #{username} and `user`.tenant_code = #{tenantCode} and `user`.del_flag = 0
</select>
<select id="selectUserVoBySocial" resultMap="userVoResultMap">
<include refid="selectUserVo"/>
WHERE `user`.phone = #{social} and `user`.tenant_code = #{tenantCode} and `user`.del_flag = 0
</select>
<select id="get" resultMap="userResultMap">
SELECT
<include refid="userColumns"/>
......
......@@ -5,4 +5,9 @@ package com.github.tangyi.msc.api.constant;
* @date 2019/6/22 13:18
*/
public class SmsConstant {
/**
* 短信模板
*/
public static final String SMS_TEMPLATE = "验证码:%s,本验证码有效时间5分钟,请勿告知其他人。";
}
package com.github.tangyi.msc.api.feign;
import com.github.tangyi.common.core.constant.ServiceConstant;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.feign.config.CustomFeignConfig;
import com.github.tangyi.msc.api.dto.SmsDto;
import com.github.tangyi.msc.api.feign.factory.MscServiceClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 消息中心服务
*
* @author tangyi
* @date 2019/07/02 16:04
*/
@FeignClient(value = ServiceConstant.MSC_SERVICE, configuration = CustomFeignConfig.class, fallbackFactory = MscServiceClientFallbackFactory.class)
public interface MscServiceClient {
/**
* 发送短信
*
* @param smsDto smsDto
* @return ResponseBean
* @author tangyi
* @date 2019/07/02 16:07:27
*/
@PostMapping("/v1/sms/sendSms")
ResponseBean<?> sendSms(@RequestBody SmsDto smsDto);
}
package com.github.tangyi.msc.api.feign.factory;
import com.github.tangyi.msc.api.feign.MscServiceClient;
import com.github.tangyi.msc.api.feign.fallback.MscServiceClientFallbackImpl;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 消息中心服务断路器工厂
*
* @author tangyi
* @date 2019/07/02 16:08
*/
@Component
public class MscServiceClientFallbackFactory implements FallbackFactory<MscServiceClient> {
@Override
public MscServiceClient create(Throwable throwable) {
MscServiceClientFallbackImpl mscServiceClientFallback = new MscServiceClientFallbackImpl();
mscServiceClientFallback.setThrowable(throwable);
return mscServiceClientFallback;
}
}
package com.github.tangyi.msc.api.feign.fallback;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.msc.api.dto.SmsDto;
import com.github.tangyi.msc.api.feign.MscServiceClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 消息中心服务断路器
*
* @author tangyi
* @date 2019/07/02 16:09
*/
@Slf4j
@Component
public class MscServiceClientFallbackImpl implements MscServiceClient {
private Throwable throwable;
@Override
public ResponseBean<?> sendSms(SmsDto smsDto) {
log.error("feign 发送短信失败:{}, {}, {}", smsDto.getReceiver(), smsDto.getContent(), throwable);
return null;
}
public Throwable getThrowable() {
return throwable;
}
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment