Commit eeaaf0e2 by tangyi

手机号登录

parent 8504f3a0
Version v3.0.0 (2019-6-23)
--------------------------
新功能:
# 增加短信验证码登录
* 增加消息中心服务
改进:
* 优化用户头像存储
* 从请求头里解析租户标识
Version v3.0.0 (2019-6-19) Version v3.0.0 (2019-6-19)
-------------------------- --------------------------
新功能:
* 重构exam-service * 重构exam-service
* 支持简答题和批改功能 * 支持简答题和批改功能
* 一些优化 * 一些优化
......
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
### 在线体验 ### 在线体验
- 前台:[http://118.25.138.130](http://118.25.138.130) - 前台:[http://it99.club](http://it99.club)
- 后台:[http://118.25.138.130:81](http://118.25.138.130:81) - 后台:[http://it99.club:81](http://it99.club:81)
默认账号: 默认账号:
......
...@@ -113,5 +113,18 @@ ...@@ -113,5 +113,18 @@
<artifactId>common-security</artifactId> <artifactId>common-security</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<!-- json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>${json.version}</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
package com.github.tangyi.common.core.cache; package com.github.tangyi.common.core.cache;
import com.github.tangyi.common.security.tenant.TenantContextHolder;
import com.github.tangyi.common.core.utils.SysUtil; import com.github.tangyi.common.core.utils.SysUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.cache.RedisCacheWriter;
import java.time.Duration;
import java.util.Map; import java.util.Map;
/** /**
...@@ -18,12 +22,39 @@ import java.util.Map; ...@@ -18,12 +22,39 @@ import java.util.Map;
@Slf4j @Slf4j
public class MultitenantCacheManager extends RedisCacheManager { public class MultitenantCacheManager extends RedisCacheManager {
private static final String SPLIT_FLAG = "#";
private static final int CACHE_LENGTH = 2;
public MultitenantCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, public MultitenantCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) { Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation); super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
} }
/** /**
* 扩展@Cacheable,支持配置失效时间
*
* @param name name
* @param cacheConfig cacheConfig
* @return RedisCache
*/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
if (StringUtils.isNotBlank(name) || !name.contains(SPLIT_FLAG))
return super.createRedisCache(name, cacheConfig);
String[] cacheArray = name.split(SPLIT_FLAG);
if (cacheArray.length < CACHE_LENGTH) {
return super.createRedisCache(name, cacheConfig);
}
String cacheName = cacheArray[0] + ":" + TenantContextHolder.getTenantCode();
if (cacheConfig != null) {
long cacheAge = Long.getLong(cacheArray[1], -1);
cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(cacheAge));
}
return super.createRedisCache(cacheName, cacheConfig);
}
/**
* 扩展cache name,加上tenantCode作为前缀 * 扩展cache name,加上tenantCode作为前缀
* *
* @param name name * @param name name
......
...@@ -9,26 +9,6 @@ package com.github.tangyi.common.core.constant; ...@@ -9,26 +9,6 @@ package com.github.tangyi.common.core.constant;
public class CommonConstant { public class CommonConstant {
/** /**
* 默认系统编号
*/
public static final String SYS_CODE = "EXAM";
/**
* 默认租户编号
*/
public static final String DEFAULT_TENANT_CODE = "gitee";
/**
* 租户编号
*/
public static final String TENANT_CODE = "tenantCode";
/**
* JSON 资源
*/
public static final String CONTENT_TYPE = "application/json; charset=utf-8";
/**
* 正常 * 正常
*/ */
public static final String STATUS_NORMAL = "0"; public static final String STATUS_NORMAL = "0";
......
package com.github.tangyi.common.core.utils; package com.github.tangyi.common.core.utils;
import com.github.tangyi.common.core.constant.CommonConstant; import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.common.core.tenant.TenantContextHolder; import com.github.tangyi.common.security.tenant.TenantContextHolder;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
...@@ -47,7 +47,7 @@ public class SysUtil { ...@@ -47,7 +47,7 @@ public class SysUtil {
* @return String * @return String
*/ */
public static String getSysCode() { public static String getSysCode() {
return CommonConstant.SYS_CODE; return SecurityConstant.SYS_CODE;
} }
/** /**
...@@ -60,7 +60,7 @@ public class SysUtil { ...@@ -60,7 +60,7 @@ public class SysUtil {
if (StringUtils.isBlank(tenantCode)) if (StringUtils.isBlank(tenantCode))
tenantCode = getCurrentUserTenantCode(); tenantCode = getCurrentUserTenantCode();
if (StringUtils.isBlank(tenantCode)) if (StringUtils.isBlank(tenantCode))
tenantCode = CommonConstant.DEFAULT_TENANT_CODE; tenantCode = SecurityConstant.DEFAULT_TENANT_CODE;
log.debug("租户code:{}", tenantCode); log.debug("租户code:{}", tenantCode);
return tenantCode; return tenantCode;
} }
...@@ -78,14 +78,14 @@ public class SysUtil { ...@@ -78,14 +78,14 @@ public class SysUtil {
if (details instanceof OAuth2AuthenticationDetails) { if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails) details; OAuth2AuthenticationDetails oAuth2AuthenticationDetails = (OAuth2AuthenticationDetails) details;
OAuth2AccessToken oAuth2AccessToken = resourceServerTokenServices.readAccessToken(oAuth2AuthenticationDetails.getTokenValue()); OAuth2AccessToken oAuth2AccessToken = resourceServerTokenServices.readAccessToken(oAuth2AuthenticationDetails.getTokenValue());
Object tenantObj = oAuth2AccessToken.getAdditionalInformation().get(CommonConstant.TENANT_CODE); Object tenantObj = oAuth2AccessToken.getAdditionalInformation().get(SecurityConstant.TENANT_CODE);
tenantCode = tenantObj == null ? "" : tenantObj.toString(); tenantCode = tenantObj == null ? "" : tenantObj.toString();
} else if (details instanceof WebAuthenticationDetails) { } else if (details instanceof WebAuthenticationDetails) {
// 未认证 // 未认证
Object requestObj = RequestContextHolder.getRequestAttributes(); Object requestObj = RequestContextHolder.getRequestAttributes();
if (requestObj != null) { if (requestObj != null) {
HttpServletRequest request = ((ServletRequestAttributes) requestObj).getRequest(); HttpServletRequest request = ((ServletRequestAttributes) requestObj).getRequest();
tenantCode = request.getParameter(CommonConstant.TENANT_CODE); tenantCode = request.getParameter(SecurityConstant.TENANT_CODE);
} }
} }
} catch (Exception e) { } catch (Exception e) {
......
package com.github.tangyi.common.core.utils.okhttp;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* 日志拦截器
*
* @author tangyi
* @date 2018-09-12 17:03
*/
public class LogInterceptor implements Interceptor {
private static Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
@Override
public Response intercept(Chain chain) throws IOException {
long t1 = System.nanoTime();
Request request = chain.request();
logger.debug(String.format("sending %s request %s%n%s", request.method(),
request.url(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.debug(String.format("received response for %s in %.1fms%n%s", response.request().url(),
(t2 - t1) / 1e6d, response.headers()));
return response;
}
}
package com.github.tangyi.common.core.utils.okhttp;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.internal.Util;
import okio.BufferedSink;
import okio.Okio;
import okio.Source;
import java.io.IOException;
import java.io.InputStream;
/**
* @author tangyi
* @version V1.0
* @date 2018-09-09 10:14
*/
public class OkHttpRequestBodyUtil {
/**
* @param
* @return
* @author tangyi
* @date 2018/9/9 10:22
*/
public static RequestBody create(final MediaType mediaType, final InputStream inputStream) {
return new RequestBody() {
@Override
public MediaType contentType() {
return mediaType;
}
@Override
public long contentLength() {
try {
return inputStream.available();
} catch (IOException e) {
return 0;
}
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(inputStream);
sink.writeAll(source);
} finally {
Util.closeQuietly(source);
}
}
};
}
}
package com.github.tangyi.common.core.utils.okhttp;
import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* SSLSocketFactory
*
* @author tangyi
* @date 2018-12-04 16:53
*/
public class SSLSocketClient {
/**
* 获取这个SSLSocketFactory
*
* @return SSLSocketFactory
* @author tangyi
* @date 2018/12/4 16:54
*/
public static SSLSocketFactory getSSLSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, getTrustManager(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取TrustManager
*
* @return TrustManager
* @author tangyi
* @date 2018/12/4 16:55
*/
private static TrustManager[] getTrustManager() {
return new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
}
/**
* 获取HostnameVerifier
*
* @return HostnameVerifier
* @author tangyi
* @date 2018/12/4 16:56
*/
public static HostnameVerifier getHostnameVerifier() {
return (requestedHost, remoteServerSession) -> requestedHost.equalsIgnoreCase(remoteServerSession.getPeerHost());
}
}
...@@ -40,11 +40,6 @@ public class UserVo extends BaseEntity<UserVo> { ...@@ -40,11 +40,6 @@ public class UserVo extends BaseEntity<UserVo> {
private String phone; private String phone;
/** /**
* 头像
*/
private String avatar;
/**
* 头像对应的附件id * 头像对应的附件id
*/ */
private String avatarId; private String avatarId;
......
...@@ -21,5 +21,11 @@ ...@@ -21,5 +21,11 @@
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId> <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency> </dependency>
<!--server-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>
package com.github.tangyi.common.security.config; package com.github.tangyi.common.security.config;
import com.github.tangyi.common.security.mobile.MobileSecurityConfigurer;
import com.github.tangyi.common.security.properties.FilterIgnorePropertiesConfig; import com.github.tangyi.common.security.properties.FilterIgnorePropertiesConfig;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -21,11 +22,20 @@ public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter ...@@ -21,11 +22,20 @@ public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter
private static final String RESOURCE_ID = "resource_id"; private static final String RESOURCE_ID = "resource_id";
/**
* 开放权限的URL
*/
private final FilterIgnorePropertiesConfig filterIgnorePropertiesConfig; private final FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;
/**
* 手机登录配置
*/
private final MobileSecurityConfigurer mobileSecurityConfigurer;
@Autowired @Autowired
public CustomResourceServerConfig(FilterIgnorePropertiesConfig filterIgnorePropertiesConfig) { public CustomResourceServerConfig(FilterIgnorePropertiesConfig filterIgnorePropertiesConfig, MobileSecurityConfigurer mobileSecurityConfigurer) {
this.filterIgnorePropertiesConfig = filterIgnorePropertiesConfig; this.filterIgnorePropertiesConfig = filterIgnorePropertiesConfig;
this.mobileSecurityConfigurer = mobileSecurityConfigurer;
} }
@Override @Override
...@@ -35,7 +45,6 @@ public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter ...@@ -35,7 +45,6 @@ public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter
@Override @Override
public void configure(HttpSecurity http) throws Exception { public void configure(HttpSecurity http) throws Exception {
// 忽略的url要包含/actuator/**
String[] ignores = new String[filterIgnorePropertiesConfig.getUrls().size()]; String[] ignores = new String[filterIgnorePropertiesConfig.getUrls().size()];
http http
.csrf().disable() .csrf().disable()
...@@ -44,5 +53,7 @@ public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter ...@@ -44,5 +53,7 @@ public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter
.antMatchers(filterIgnorePropertiesConfig.getUrls().toArray(ignores)).permitAll() .antMatchers(filterIgnorePropertiesConfig.getUrls().toArray(ignores)).permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
// 手机号登录
http.apply(mobileSecurityConfigurer);
} }
} }
package com.github.tangyi.common.security.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import com.github.tangyi.common.security.mobile.MobileLoginSuccessHandler;
import com.github.tangyi.common.security.mobile.MobileSecurityConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/**
* 手机号登录配置
*
* @author tangyi
* @date 2019/6/23 15:18
*/
@Configuration
public class MobileLoginConfig {
/**
* 配置手机号登录
* 采用懒加载是因为只有认证授权服务需要手机登录的相关配置
*
* @return MobileSecurityConfigurer
*/
@Bean
public MobileSecurityConfigurer mobileSecurityConfigurer(@Lazy PasswordEncoder encoder, @Lazy ClientDetailsService clientDetailsService,
@Lazy CustomUserDetailsService userDetailsService, @Lazy ObjectMapper objectMapper,
@Lazy AuthorizationServerTokenServices defaultAuthorizationServerTokenServices) {
MobileSecurityConfigurer mobileSecurityConfigurer = new MobileSecurityConfigurer();
mobileSecurityConfigurer.setMobileLoginSuccessHandler(mobileLoginSuccessHandler(encoder, clientDetailsService, objectMapper, defaultAuthorizationServerTokenServices));
mobileSecurityConfigurer.setUserDetailsService(userDetailsService);
return mobileSecurityConfigurer;
}
/**
* 手机登录成功后的处理
*
* @return AuthenticationSuccessHandler
*/
@Bean
public AuthenticationSuccessHandler mobileLoginSuccessHandler(PasswordEncoder encoder, ClientDetailsService clientDetailsService, ObjectMapper objectMapper,
AuthorizationServerTokenServices defaultAuthorizationServerTokenServices) {
return MobileLoginSuccessHandler.builder()
.objectMapper(objectMapper)
.clientDetailsService(clientDetailsService)
.passwordEncoder(encoder)
.defaultAuthorizationServerTokenServices(defaultAuthorizationServerTokenServices).build();
}
}
...@@ -30,4 +30,34 @@ public class SecurityConstant { ...@@ -30,4 +30,34 @@ public class SecurityConstant {
* 异常状态 * 异常状态
*/ */
public static final String ABNORMAL = "1"; public static final String ABNORMAL = "1";
/**
* 手机登录URL
*/
public static final String MOBILE_TOKEN_URL = "/mobile/token";
/**
* 租户编号请求头
*/
public static final String TENANT_CODE_HEADER = "Tenant-Code";
/**
* 默认系统编号
*/
public static final String SYS_CODE = "EXAM";
/**
* 默认租户编号
*/
public static final String DEFAULT_TENANT_CODE = "gitee";
/**
* 租户编号
*/
public static final String TENANT_CODE = "tenantCode";
/**
* JSON 资源
*/
public static final String CONTENT_TYPE = "application/json; charset=utf-8";
} }
package com.github.tangyi.auth.security; package com.github.tangyi.common.security.core;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
...@@ -21,4 +21,15 @@ public interface CustomUserDetailsService { ...@@ -21,4 +21,15 @@ public interface CustomUserDetailsService {
* @date 2019/05/28 21:06 * @date 2019/05/28 21:06
*/ */
UserDetails loadUserByUsernameAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException; UserDetails loadUserByUsernameAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException;
/**
* 根据社交账号和租户标识查询
*
* @param social social
* @param tenantCode tenantCode
* @return UserDetails
* @author tangyi
* @date 2019/06/22 21:08
*/
UserDetails loadUserBySocialAndTenantCode(String social, String tenantCode) throws UsernameNotFoundException;
} }
package com.github.tangyi.auth.security; package com.github.tangyi.common.security.filter;
import com.github.tangyi.common.core.constant.CommonConstant; import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.common.core.tenant.TenantContextHolder; import com.github.tangyi.common.security.tenant.TenantContextHolder;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*; import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
/** /**
* 获取请求里的租户code * 获取请求里的租户code
* *
* @author tangyi * @author tangyi
* @date 2019/5/28 22:53 * @date 2019/5/28 22:53
*/ */
@Slf4j @Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class TenantTokenFilter implements Filter { public class TenantTokenFilter implements Filter {
@Override @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String tenantCode = servletRequest.getParameter(CommonConstant.TENANT_CODE); HttpServletRequest request = (HttpServletRequest) servletRequest;
if (tenantCode != null) HttpServletResponse response = (HttpServletResponse) servletResponse;
// 获取请求头里的TENANT_CODE
String tenantCode = request.getHeader(SecurityConstant.TENANT_CODE_HEADER);
// 没有携带tenantCode则采用默认的tenantCode
if (tenantCode == null)
tenantCode = SecurityConstant.DEFAULT_TENANT_CODE;
TenantContextHolder.setTenantCode(tenantCode); TenantContextHolder.setTenantCode(tenantCode);
log.info("租户code:{}", tenantCode); log.info("租户code:{}", tenantCode);
filterChain.doFilter(servletRequest, servletResponse); filterChain.doFilter(request, response);
TenantContextHolder.clear();
} }
} }
package com.github.tangyi.common.security.mobile;
import com.github.tangyi.common.security.constant.SecurityConstant;
import lombok.Getter;
import lombok.Setter;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 手机登录filter
*
* @author tangyi
* @date 2019/6/22 21:15
*/
public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
@Getter
@Setter
private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
@Getter
@Setter
private boolean postOnly = true;
@Getter
@Setter
private AuthenticationEventPublisher eventPublisher;
@Getter
@Setter
private AuthenticationEntryPoint authenticationEntryPoint;
public MobileAuthenticationFilter() {
super(new AntPathRequestMatcher(SecurityConstant.MOBILE_TOKEN_URL, HttpMethod.POST.name()));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals(HttpMethod.POST.name()))
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
// 获取手机登录的参数
String mobile = obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
// 封装成token
MobileAuthenticationToken mobileAuthenticationToken = new MobileAuthenticationToken(mobile);
setDetails(request, mobileAuthenticationToken);
Authentication authResult = null;
try {
// 认证
authResult = this.getAuthenticationManager().authenticate(mobileAuthenticationToken);
logger.debug("Authentication success: " + authResult);
// 认证成功
eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (Exception failed) {
SecurityContextHolder.clearContext();
logger.debug("Authentication request failed: " + failed);
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
try {
authenticationEntryPoint.commence(request, response, new UsernameNotFoundException(failed.getMessage(), failed));
} catch (Exception e) {
logger.error("authenticationEntryPoint handle error:{}", failed);
}
}
return authResult;
}
private String obtainMobile(HttpServletRequest request) {
return request.getParameter(mobileParameter);
}
private void setDetails(HttpServletRequest request, MobileAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
package com.github.tangyi.common.security.mobile;
import com.github.tangyi.common.security.tenant.TenantContextHolder;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
/**
* @author tangyi
* @date 2019/6/22 21:00
*/
@Slf4j
@Data
public class MobileAuthenticationProvider implements AuthenticationProvider {
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private CustomUserDetailsService customUserDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MobileAuthenticationToken mobileAuthenticationToken = (MobileAuthenticationToken) authentication;
String principal = mobileAuthenticationToken.getPrincipal().toString();
UserDetails userDetails = customUserDetailsService.loadUserBySocialAndTenantCode(principal, TenantContextHolder.getTenantCode());
if (userDetails == null) {
log.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
}
MobileAuthenticationToken authenticationToken = new MobileAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationToken.setDetails(mobileAuthenticationToken.getDetails());
return authenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
return MobileAuthenticationToken.class.isAssignableFrom(authentication);
}
}
package com.github.tangyi.common.security.mobile;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 手机登录token
*
* @author tangyi
* @date 2019/6/22 15:27
*/
public class MobileAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
public MobileAuthenticationToken(String mobile) {
super(null);
this.principal = mobile;
setAuthenticated(false);
}
public MobileAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
package com.github.tangyi.common.security.mobile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.common.security.utils.SecurityUtil;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
/**
* 手机号登录成功,返回oauth token
*
* @author tangyi
* @date 2019/6/22 21:19
*/
@Slf4j
@Builder
public class MobileLoginSuccessHandler implements AuthenticationSuccessHandler {
private static final String BASIC_ = "Basic ";
private ObjectMapper objectMapper;
private PasswordEncoder passwordEncoder;
private ClientDetailsService clientDetailsService;
private AuthorizationServerTokenServices defaultAuthorizationServerTokenServices;
/**
* Called when a user has been successfully authenticated.
* 调用spring security oauth API 生成 oAuth2AccessToken
*
* @param request the request which caused the successful authentication
* @param response the response
* @param authentication the <tt>Authentication</tt> object which was created during
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header == null || !header.startsWith(BASIC_))
throw new UnapprovedClientAuthenticationException("请求头中client信息为空");
try {
String[] tokens = SecurityUtil.extractAndDecodeHeader(header);
assert tokens.length == 2;
String clientId = tokens[0];
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
// 校验secret
if (!passwordEncoder.matches(tokens[1], clientDetails.getClientSecret()))
throw new InvalidClientException("Given client ID does not match authenticated client");
TokenRequest tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "mobile");
//校验scope
new DefaultOAuth2RequestValidator().validateScope(tokenRequest, clientDetails);
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
OAuth2AccessToken oAuth2AccessToken = defaultAuthorizationServerTokenServices.createAccessToken(oAuth2Authentication);
log.info("获取token 成功:{}", oAuth2AccessToken.getValue());
response.setCharacterEncoding("utf-8");
response.setContentType(SecurityConstant.CONTENT_TYPE);
PrintWriter printWriter = response.getWriter();
printWriter.append(objectMapper.writeValueAsString(oAuth2AccessToken));
} catch (IOException e) {
throw new BadCredentialsException("Failed to decode basic authentication token");
}
}
}
package com.github.tangyi.common.security.mobile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 手机登录配置,配置
*
* @author tangyi
* @date 2019/6/22 21:26
*/
@Data
public class MobileSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private AuthenticationEventPublisher defaultAuthenticationEventPublisher;
private AuthenticationSuccessHandler mobileLoginSuccessHandler;
private CustomUserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) {
// 手机登录过滤器
MobileAuthenticationFilter mobileAuthenticationFilter = new MobileAuthenticationFilter();
mobileAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
mobileAuthenticationFilter.setAuthenticationSuccessHandler(mobileLoginSuccessHandler);
mobileAuthenticationFilter.setEventPublisher(defaultAuthenticationEventPublisher);
MobileAuthenticationProvider mobileAuthenticationProvider = new MobileAuthenticationProvider();
mobileAuthenticationProvider.setCustomUserDetailsService(userDetailsService);
// 增加手机登录的过滤器
http.authenticationProvider(mobileAuthenticationProvider).addFilterAfter(mobileAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
package com.github.tangyi.common.core.tenant; package com.github.tangyi.common.security.tenant;
/** /**
* ThreadLocal保存租户code * ThreadLocal保存租户code
......
package com.github.tangyi.common.security.utils; package com.github.tangyi.common.security.utils;
import com.github.tangyi.common.security.core.UserDetailsImpl; import com.github.tangyi.common.security.core.UserDetailsImpl;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import java.io.IOException;
import java.security.Principal; import java.security.Principal;
/** /**
...@@ -64,4 +66,26 @@ public class SecurityUtil { ...@@ -64,4 +66,26 @@ public class SecurityUtil {
public static Object getCurrentPrincipal() { public static Object getCurrentPrincipal() {
return SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} }
/**
* 从header 请求中的clientId/clientsecect
*
* @param header header中的参数
* @throws RuntimeException if the Basic header is not present or is not valid
* Base64
*/
public static String[] extractAndDecodeHeader(String header) throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Failed to decode basic authentication token");
}
String token = new String(decoded, "UTF8");
int delim = token.indexOf(":");
if (delim == -1)
throw new RuntimeException("Invalid basic authentication token");
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
} }
FROM anapsix/alpine-java:8_server-jre_unlimited FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com) MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/config-service.jar", "app.jar"] ARG JAR_FILE
ENV PROFILE native
ADD target/${JAR_FILE} /opt/app.jar
EXPOSE 8769 EXPOSE 8769
ENV JAVA_OPTS="-Xmx512m -Xms256m" ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Dspring.profiles.active=${PROFILE} -jar /opt/app.jar
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ] # JAVA_OPS配置,如:JAVA_OPS='-Xmx512m -Xms256m'
\ No newline at end of file \ No newline at end of file
...@@ -47,35 +47,26 @@ ...@@ -47,35 +47,26 @@
<!-- docker的maven插件 --> <!-- docker的maven插件 -->
<plugin> <plugin>
<groupId>com.spotify</groupId> <groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId> <artifactId>dockerfile-maven-plugin</artifactId>
<version>${docker.maven.verion}</version> <version>${docker.maven.verion}</version>
<executions> <executions>
<!--执行 mvn package 时 自动 执行 mvn docker:build--> <!--执行 mvn package 时 自动构建docker镜像并推送到仓库 -->
<execution> <execution>
<id>build-image</id> <id>default</id>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>build</goal> <goal>build</goal>
<goal>push</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<imageName>${docker.registry}/${docker.namespace}/${project.artifactId}</imageName> <repository>${docker.registry}/${docker.namespace}/${project.artifactId}</repository>
<imageTags> <tag>${project.version}</tag>
<imageTag>${project.version}</imageTag> <!-- 构建参数,指定jar包名称 -->
<imageTag>latest</imageTag> <buildArgs>
</imageTags> <JAR_FILE>${project.name}.jar</JAR_FILE>
<!-- 指定Dockerfile所在的路径 --> </buildArgs>
<dockerDirectory>${project.basedir}</dockerDirectory>
<baseImage>java</baseImage>
<entryPoint>["java", "-jar", "/${project.name}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.name}.jar</include>
</resource>
</resources>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
......
...@@ -85,6 +85,7 @@ ignore: ...@@ -85,6 +85,7 @@ ignore:
- /v1/user/findUserByUsername/** - /v1/user/findUserByUsername/**
- /v1/tenant/findTenantByTenantCode/** - /v1/tenant/findTenantByTenantCode/**
- /v1/user/checkExist/** - /v1/user/checkExist/**
- /v1/user/updatePassword
- /v1/menu/findMenuByRole/** - /v1/menu/findMenuByRole/**
- /v1/menu/findAllMenu - /v1/menu/findAllMenu
- /v1/code/** - /v1/code/**
...@@ -101,7 +102,6 @@ ignore: ...@@ -101,7 +102,6 @@ ignore:
- /health - /health
- /metrics/** - /metrics/**
- /loggers/** - /loggers/**
- /mobile/token
# 集群ID生成配置 # 集群ID生成配置
cluster: cluster:
......
...@@ -111,6 +111,7 @@ ignore: ...@@ -111,6 +111,7 @@ ignore:
- /v1/menu/findMenuByRole/** - /v1/menu/findMenuByRole/**
- /v1/menu/findAllMenu - /v1/menu/findAllMenu
- /v1/user/checkExist/** - /v1/user/checkExist/**
- /v1/user/updatePassword
- /v1/code/** - /v1/code/**
- /v1/attachment/download - /v1/attachment/download
- /v1/log/** - /v1/log/**
...@@ -125,7 +126,6 @@ ignore: ...@@ -125,7 +126,6 @@ ignore:
- /health - /health
- /metrics/** - /metrics/**
- /loggers/** - /loggers/**
- /mobile/token
# 集群ID生成配置 # 集群ID生成配置
cluster: cluster:
......
...@@ -62,10 +62,11 @@ swagger: ...@@ -62,10 +62,11 @@ swagger:
- user-service - user-service
- exam-service - exam-service
- auth-service - auth-service
- msc-service
# 演示环境 # 演示环境
preview: preview:
enabled: ${PREVIEW_ENABLED:true} enabled: ${PREVIEW_ENABLED:false}
ignores: ignores:
- api/auth # 授权服务 - api/auth # 授权服务
- resetPassword # 重置密码 - resetPassword # 重置密码
......
server:
port: 9000
spring:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
rabbitmq:
host: ${RABBIT_HOST:localhost}
port: ${RABBIT_PORT:5672}
username: ${RABBITMQ_DEFAULT_USER:guest}
password: ${RABBITMQ_DEFAULT_PASS:guest}
boot:
admin:
client:
url: http://${ADMIN_HOST:localhost}:${ADMIN_PORT:8085}/admin
username: ${ADMIN_USERNAME:admin}
password: ${ADMIN_PASSWORD:11}
instance:
service-base-url: http://${AUTH_SERVICE_HOST:localhost}:${server.port}
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: ALWAYS
sms:
appKey:
appSecret:
regionId: cn-hangzhou
domain: dysmsapi.aliyuncs.com
ignore:
urls:
- /
- /error
- /favicon.ico
- /**/*swagger*/**
- /v2/api-docs
- /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/**
- /**/*.css
- /**/*.js
- /social
- /signin
- /signup
- /info
- /health
- /metrics/**
- /loggers/**
logging:
level:
root: info
com.github.tangyi: debug
\ No newline at end of file
...@@ -89,11 +89,11 @@ fdfs: ...@@ -89,11 +89,11 @@ fdfs:
width: 150 width: 150
height: 150 height: 150
tracker-list: #TrackerList参数,支持多个 tracker-list: #TrackerList参数,支持多个
- ${FDFS_HOST:localhost}:${FDFS_PORT:22122} - ${FDFS_HOST:118.25.138.130}:${FDFS_PORT:22122}
# 系统配置 # 系统配置
sys: sys:
fdfsHttpHost: ${ATTACHMENT_HOST:http://localhost}:${ATTACHMENT_PORT:8080} # fastDfs的HTTP配置 fdfsHttpHost: ${ATTACHMENT_HOST:http://118.25.138.130}:${ATTACHMENT_PORT:8080} # fastDfs的HTTP配置
uploadUrl: api/user/v1/attachment/upload uploadUrl: api/user/v1/attachment/upload
defaultAvatar: https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80 defaultAvatar: https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80
...@@ -136,6 +136,7 @@ ignore: ...@@ -136,6 +136,7 @@ ignore:
- /v1/menu/findAllMenu - /v1/menu/findAllMenu
- /v1/user/register - /v1/user/register
- /v1/user/checkExist/** - /v1/user/checkExist/**
- /v1/user/updatePassword
- /v1/code/** - /v1/code/**
- /v1/attachment/download - /v1/attachment/download
- /v1/log/** - /v1/log/**
...@@ -150,7 +151,6 @@ ignore: ...@@ -150,7 +151,6 @@ ignore:
- /health - /health
- /metrics/** - /metrics/**
- /loggers/** - /loggers/**
- /mobile/token
- /bus/refresh - /bus/refresh
# 集群ID生成配置 # 集群ID生成配置
......
...@@ -70,7 +70,7 @@ services: ...@@ -70,7 +70,7 @@ services:
# 配置中心 # 配置中心
# --------------------------- # ---------------------------
config-service: config-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/config-service:latest image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/config-service:3.0.0
container_name: config-service container_name: config-service
env_file: docker-compose.env # 从文件中获取配置 env_file: docker-compose.env # 从文件中获取配置
restart: always restart: always
......
...@@ -4,7 +4,7 @@ services: ...@@ -4,7 +4,7 @@ services:
# 前台 # 前台
# --------------------------- # ---------------------------
spring-microservice-exam-web: spring-microservice-exam-web:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/spring-microservice-exam-web:latest image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/spring-microservice-exam-web:3.0.0
volumes: volumes:
# 挂载nginx的配置文件 # 挂载nginx的配置文件
- ./nginx.conf:/etc/nginx/nginx.conf - ./nginx.conf:/etc/nginx/nginx.conf
...@@ -20,7 +20,7 @@ services: ...@@ -20,7 +20,7 @@ services:
# 后台 # 后台
# --------------------------- # ---------------------------
spring-microservice-exam-ui: spring-microservice-exam-ui:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/spring-microservice-exam-ui:latest image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/spring-microservice-exam-ui:3.0.0
volumes: volumes:
# 挂载nginx的配置文件 # 挂载nginx的配置文件
- ./nginx.conf:/etc/nginx/nginx.conf - ./nginx.conf:/etc/nginx/nginx.conf
......
...@@ -4,7 +4,7 @@ services: ...@@ -4,7 +4,7 @@ services:
# api网关 # api网关
# --------------------------- # ---------------------------
gateway-service: gateway-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/gateway-service:latest image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/gateway-service:3.0.0
container_name: gateway-service container_name: gateway-service
env_file: docker-compose.env # 从文件中获取配置 env_file: docker-compose.env # 从文件中获取配置
restart: always restart: always
...@@ -17,7 +17,7 @@ services: ...@@ -17,7 +17,7 @@ services:
# 授权服务 # 授权服务
# --------------------------- # ---------------------------
auth-service: auth-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/auth-service:latest image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/auth-service:3.0.0
container_name: auth-service container_name: auth-service
env_file: docker-compose.env # 从文件中获取配置 env_file: docker-compose.env # 从文件中获取配置
restart: always restart: always
...@@ -30,7 +30,7 @@ services: ...@@ -30,7 +30,7 @@ services:
# 用户服务 # 用户服务
# --------------------------- # ---------------------------
user-service: user-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/user-service:latest image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/user-service:3.0.0
container_name: user-service container_name: user-service
env_file: docker-compose.env # 从文件中获取配置 env_file: docker-compose.env # 从文件中获取配置
restart: always restart: always
...@@ -43,7 +43,7 @@ services: ...@@ -43,7 +43,7 @@ services:
# 考试服务 # 考试服务
# --------------------------- # ---------------------------
exam-service: exam-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/exam-service:latest image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/exam-service:3.0.0
container_name: exam-service container_name: exam-service
env_file: docker-compose.env # 从文件中获取配置 env_file: docker-compose.env # 从文件中获取配置
restart: always restart: always
...@@ -56,7 +56,7 @@ services: ...@@ -56,7 +56,7 @@ services:
# 监控服务 # 监控服务
# --------------------------- # ---------------------------
monitor-service: monitor-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/monitor-service:latest image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/monitor-service:3.0.0
container_name: monitor-service container_name: monitor-service
env_file: docker-compose.env # 从文件中获取配置 env_file: docker-compose.env # 从文件中获取配置
restart: always restart: always
......
-- ----------------------------
-- 2019年6月21日14:32:59
-- ----------------------------
ALTER TABLE `microservice-user`.`sys_attachment`
ADD COLUMN `previewUrl` varchar(255) NULL COMMENT '预览地址' AFTER `busi_type`;
\ No newline at end of file
...@@ -5,7 +5,7 @@ DOCKERHOME=/spring-microservice-exam ...@@ -5,7 +5,7 @@ DOCKERHOME=/spring-microservice-exam
# 镜像名称前缀、标签 # 镜像名称前缀、标签
BASE_IMAGE_NAME=registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam BASE_IMAGE_NAME=registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam
BSEE_IMAGE_TAG=latest BSEE_IMAGE_TAG=3.0.0
# 各服务的镜像名称 # 各服务的镜像名称
CONFIG_SERVICE=$BASE_IMAGE_NAME/config-service:$BSEE_IMAGE_TAG CONFIG_SERVICE=$BASE_IMAGE_NAME/config-service:$BSEE_IMAGE_TAG
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
FROM anapsix/alpine-java:8_server-jre_unlimited FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com) MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/gateway-service.jar", "app.jar"] ARG JAR_FILE
ENV PROFILE native
ADD target/${JAR_FILE} /opt/app.jar
EXPOSE 8000 EXPOSE 8000
ENV JAVA_OPTS="-Xmx512m -Xms256m" ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Dspring.profiles.active=${PROFILE} -jar /opt/app.jar
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ] # JAVA_OPS配置,如:JAVA_OPS='-Xmx512m -Xms256m'
\ No newline at end of file \ No newline at end of file
...@@ -70,35 +70,26 @@ ...@@ -70,35 +70,26 @@
<!-- docker的maven插件 --> <!-- docker的maven插件 -->
<plugin> <plugin>
<groupId>com.spotify</groupId> <groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId> <artifactId>dockerfile-maven-plugin</artifactId>
<version>${docker.maven.verion}</version> <version>${docker.maven.verion}</version>
<executions> <executions>
<!--执行 mvn package 时 自动 执行 mvn docker:build--> <!--执行 mvn package 时 自动构建docker镜像并推送到仓库 -->
<execution> <execution>
<id>build-image</id> <id>default</id>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>build</goal> <goal>build</goal>
<goal>push</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<imageName>${docker.registry}/${docker.namespace}/${project.artifactId}</imageName> <repository>${docker.registry}/${docker.namespace}/${project.artifactId}</repository>
<imageTags> <tag>${project.version}</tag>
<imageTag>${project.version}</imageTag> <!-- 构建参数,指定jar包名称 -->
<imageTag>latest</imageTag> <buildArgs>
</imageTags> <JAR_FILE>${project.name}.jar</JAR_FILE>
<!-- 指定Dockerfile所在的路径 --> </buildArgs>
<dockerDirectory>${project.basedir}</dockerDirectory>
<baseImage>java</baseImage>
<entryPoint>["java", "-jar", "/${project.name}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.name}.jar</include>
</resource>
</resources>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
......
...@@ -25,6 +25,7 @@ public class AccessTokenJacksonSerializer extends StdSerializer<AccessToken> { ...@@ -25,6 +25,7 @@ public class AccessTokenJacksonSerializer extends StdSerializer<AccessToken> {
gen.writeStringField("refresh_token", accessToken.getJti()); gen.writeStringField("refresh_token", accessToken.getJti());
gen.writeStringField("scope", accessToken.getScope()); gen.writeStringField("scope", accessToken.getScope());
gen.writeStringField("token_type", accessToken.getTokenType()); gen.writeStringField("token_type", accessToken.getTokenType());
gen.writeStringField("tenant_code", accessToken.getTenantCode());
gen.writeEndObject(); gen.writeEndObject();
} }
} }
FROM anapsix/alpine-java:8_server-jre_unlimited FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com) MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/monitor-service.jar", "app.jar"] ARG JAR_FILE
ENV PROFILE native
ADD target/${JAR_FILE} /opt/app.jar
EXPOSE 8085 EXPOSE 8085
ENV JAVA_OPTS="-Xmx512m -Xms256m" ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Dspring.profiles.active=${PROFILE} -jar /opt/app.jar
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ] # JAVA_OPS配置,如:JAVA_OPS='-Xmx512m -Xms256m'
\ No newline at end of file \ No newline at end of file
...@@ -70,35 +70,26 @@ ...@@ -70,35 +70,26 @@
<!-- docker的maven插件 --> <!-- docker的maven插件 -->
<plugin> <plugin>
<groupId>com.spotify</groupId> <groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId> <artifactId>dockerfile-maven-plugin</artifactId>
<version>${docker.maven.verion}</version> <version>${docker.maven.verion}</version>
<executions> <executions>
<!--执行 mvn package 时 自动 执行 mvn docker:build--> <!--执行 mvn package 时 自动构建docker镜像并推送到仓库 -->
<execution> <execution>
<id>build-image</id> <id>default</id>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>build</goal> <goal>build</goal>
<goal>push</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<imageName>${docker.registry}/${docker.namespace}/${project.artifactId}</imageName> <repository>${docker.registry}/${docker.namespace}/${project.artifactId}</repository>
<imageTags> <tag>${project.version}</tag>
<imageTag>${project.version}</imageTag> <!-- 构建参数,指定jar包名称 -->
<imageTag>latest</imageTag> <buildArgs>
</imageTags> <JAR_FILE>${project.name}.jar</JAR_FILE>
<!-- 指定Dockerfile所在的路径 --> </buildArgs>
<dockerDirectory>${project.basedir}</dockerDirectory>
<baseImage>java</baseImage>
<entryPoint>["java", "-jar", "/${project.name}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.name}.jar</include>
</resource>
</resources>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
......
...@@ -72,9 +72,12 @@ ...@@ -72,9 +72,12 @@
<hutool.version>4.5.0</hutool.version> <hutool.version>4.5.0</hutool.version>
<jasypt.version>1.18</jasypt.version> <jasypt.version>1.18</jasypt.version>
<kaptcha.version>0.0.9</kaptcha.version> <kaptcha.version>0.0.9</kaptcha.version>
<json.version>20140107</json.version>
<okhttp.version>3.8.1</okhttp.version>
<aliyun.version>4.1.0</aliyun.version>
<!-- docker --> <!-- docker -->
<docker.maven.verion>1.0.0</docker.maven.verion> <docker.maven.verion>1.4.3</docker.maven.verion>
<docker.registry>registry.cn-hangzhou.aliyuncs.com</docker.registry> <docker.registry>registry.cn-hangzhou.aliyuncs.com</docker.registry>
<docker.namespace>spring-microservice-exam</docker.namespace> <docker.namespace>spring-microservice-exam</docker.namespace>
<scanner.maven.version>3.3.0.603</scanner.maven.version> <scanner.maven.version>3.3.0.603</scanner.maven.version>
...@@ -204,6 +207,13 @@ ...@@ -204,6 +207,13 @@
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<!-- msc-api -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>msc-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 管理中心插件 --> <!-- 管理中心插件 -->
<dependency> <dependency>
<groupId>de.codecentric</groupId> <groupId>de.codecentric</groupId>
......
FROM anapsix/alpine-java:8_server-jre_unlimited FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com) MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/auth-service.jar", "app.jar"] ARG JAR_FILE
ENV PROFILE native
ADD target/${JAR_FILE} /opt/app.jar
EXPOSE 8090 EXPOSE 8090
ENV JAVA_OPTS="-Xmx512m -Xms256m" ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Dspring.profiles.active=${PROFILE} -jar /opt/app.jar
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ] # JAVA_OPS配置,如:JAVA_OPS='-Xmx512m -Xms256m'
\ No newline at end of file \ No newline at end of file
...@@ -117,35 +117,26 @@ ...@@ -117,35 +117,26 @@
<!-- docker的maven插件 --> <!-- docker的maven插件 -->
<plugin> <plugin>
<groupId>com.spotify</groupId> <groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId> <artifactId>dockerfile-maven-plugin</artifactId>
<version>${docker.maven.verion}</version> <version>${docker.maven.verion}</version>
<executions> <executions>
<!--执行 mvn package 时 自动 执行 mvn docker:build--> <!--执行 mvn package 时 自动构建docker镜像并推送到仓库 -->
<execution> <execution>
<id>build-image</id> <id>default</id>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>build</goal> <goal>build</goal>
<goal>push</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<imageName>${docker.registry}/${docker.namespace}/${project.artifactId}</imageName> <repository>${docker.registry}/${docker.namespace}/${project.artifactId}</repository>
<imageTags> <tag>${project.version}</tag>
<imageTag>${project.version}</imageTag> <!-- 构建参数,指定jar包名称 -->
<imageTag>latest</imageTag> <buildArgs>
</imageTags> <JAR_FILE>${project.name}.jar</JAR_FILE>
<!-- 指定Dockerfile所在的路径 --> </buildArgs>
<dockerDirectory>${project.basedir}</dockerDirectory>
<baseImage>java</baseImage>
<entryPoint>["java", "-jar", "/${project.name}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.name}.jar</include>
</resource>
</resources>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
......
package com.github.tangyi.auth.config; package com.github.tangyi.auth.config;
import com.github.tangyi.auth.security.CustomTokenConverter; import com.github.tangyi.auth.security.CustomTokenConverter;
import com.github.tangyi.auth.security.TenantTokenFilter;
import com.github.tangyi.common.security.core.ClientDetailsServiceImpl; import com.github.tangyi.common.security.core.ClientDetailsServiceImpl;
import com.github.tangyi.common.security.exceptions.CustomOauthException; import com.github.tangyi.common.security.exceptions.CustomOauthException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -145,9 +144,7 @@ public class CustomAuthorizationServerConfigurer extends AuthorizationServerConf ...@@ -145,9 +144,7 @@ public class CustomAuthorizationServerConfigurer extends AuthorizationServerConf
.tokenKeyAccess("permitAll()") .tokenKeyAccess("permitAll()")
// 开启/oauth/check_token验证端口认证权限访问 // 开启/oauth/check_token验证端口认证权限访问
.checkTokenAccess("isAuthenticated()") .checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients() .allowFormAuthenticationForClients();
// 增加租户信息过滤器
.addTokenEndpointAuthenticationFilter(new TenantTokenFilter());
} }
} }
package com.github.tangyi.auth.config; package com.github.tangyi.auth.config;
import com.github.tangyi.auth.security.CustomUserDetailsAuthenticationProvider; import com.github.tangyi.auth.security.CustomUserDetailsAuthenticationProvider;
import com.github.tangyi.auth.security.CustomUserDetailsService; import com.github.tangyi.common.security.core.CustomUserDetailsService;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
...@@ -21,13 +20,13 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; ...@@ -21,13 +20,13 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
* @author tangyi * @author tangyi
* @date 2019-03-14 14:35 * @date 2019-03-14 14:35
*/ */
@AllArgsConstructor
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomUserDetailsService userDetailsService; @Autowired
private CustomUserDetailsService userDetailsService;
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
......
package com.github.tangyi.auth.controller; package com.github.tangyi.auth.controller;
import com.github.tangyi.common.core.constant.CommonConstant; import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.model.ResponseBean; import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.web.BaseController; import com.github.tangyi.common.core.web.BaseController;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices; import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/** /**
* Authentication管理 * Authentication管理
* *
...@@ -40,13 +43,16 @@ public class AuthenticationController extends BaseController { ...@@ -40,13 +43,16 @@ public class AuthenticationController extends BaseController {
/** /**
* 清除access_token * 清除access_token
* *
* @param accesstoken access_token * @param request request
* @return ReturnT * @return ReturnT
*/ */
@PostMapping("removeToken") @PostMapping("removeToken")
public ResponseBean<Boolean> removeToken(@RequestHeader("Authorization") String accesstoken) { public ResponseBean<Boolean> removeToken(HttpServletRequest request) {
if (accesstoken.startsWith(CommonConstant.AUTHORIZATION_BEARER)) String accessToken = request.getHeader("Authorization");
accesstoken = accesstoken.split(CommonConstant.AUTHORIZATION_BEARER)[1]; if (StringUtils.isBlank(accessToken))
return new ResponseBean<>(consumerTokenServices.revokeToken(accesstoken)); throw new CommonException("accessToken为空.");
if (accessToken.startsWith(CommonConstant.AUTHORIZATION_BEARER))
accessToken = accessToken.split(CommonConstant.AUTHORIZATION_BEARER)[1];
return new ResponseBean<>(consumerTokenServices.revokeToken(accessToken));
} }
} }
package com.github.tangyi.auth.security; package com.github.tangyi.auth.security;
import com.github.tangyi.common.core.constant.CommonConstant; import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.tenant.TenantContextHolder; import com.github.tangyi.common.security.tenant.TenantContextHolder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication;
......
package com.github.tangyi.auth.security; package com.github.tangyi.auth.security;
import com.github.tangyi.common.core.exceptions.TenantNotFoundException; import com.github.tangyi.common.core.exceptions.TenantNotFoundException;
import com.github.tangyi.common.core.tenant.TenantContextHolder; import com.github.tangyi.common.security.tenant.TenantContextHolder;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.InternalAuthenticationServiceException;
......
...@@ -6,6 +6,7 @@ import com.github.tangyi.common.core.constant.CommonConstant; ...@@ -6,6 +6,7 @@ import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.exceptions.TenantNotFoundException; import com.github.tangyi.common.core.exceptions.TenantNotFoundException;
import com.github.tangyi.common.core.vo.Role; import com.github.tangyi.common.core.vo.Role;
import com.github.tangyi.common.core.vo.UserVo; import com.github.tangyi.common.core.vo.UserVo;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import com.github.tangyi.common.security.core.GrantedAuthorityImpl; import com.github.tangyi.common.security.core.GrantedAuthorityImpl;
import com.github.tangyi.user.api.constant.MenuConstant; import com.github.tangyi.user.api.constant.MenuConstant;
import com.github.tangyi.user.api.feign.UserServiceClient; import com.github.tangyi.user.api.feign.UserServiceClient;
...@@ -61,6 +62,29 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService { ...@@ -61,6 +62,29 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
} }
/** /**
* 根据社交账号查询
*
* @param social social
* @param tenantCode tenantCode
* @return UserDetails
* @author tangyi
* @date 2019/06/22 21:08
*/
@Override
public UserDetails loadUserBySocialAndTenantCode(String social, String tenantCode) throws UsernameNotFoundException {
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());
}
/**
* 获取用户权限 * 获取用户权限
* *
* @param userVo userVo * @param userVo userVo
......
...@@ -25,3 +25,9 @@ encrypt: ...@@ -25,3 +25,9 @@ encrypt:
location: classpath:/jwt.jks location: classpath:/jwt.jks
alias: jwt alias: jwt
password: abc123 password: abc123
logging:
level:
root: info
com.github.tangyi: debug
\ No newline at end of file
FROM anapsix/alpine-java:8_server-jre_unlimited FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com) MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/exam-service.jar", "app.jar"] ARG JAR_FILE
ENV PROFILE native
ADD target/${JAR_FILE} /opt/app.jar
EXPOSE 8098 EXPOSE 8098
ENV JAVA_OPTS="-Xmx512m -Xms256m" ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Dspring.profiles.active=${PROFILE} -jar /opt/app.jar
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ] # JAVA_OPS配置,如:JAVA_OPS='-Xmx512m -Xms256m'
\ No newline at end of file \ No newline at end of file
...@@ -112,35 +112,26 @@ ...@@ -112,35 +112,26 @@
<!-- docker的maven插件 --> <!-- docker的maven插件 -->
<plugin> <plugin>
<groupId>com.spotify</groupId> <groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId> <artifactId>dockerfile-maven-plugin</artifactId>
<version>${docker.maven.verion}</version> <version>${docker.maven.verion}</version>
<executions> <executions>
<!--执行 mvn package 时 自动 执行 mvn docker:build--> <!--执行 mvn package 时 自动构建docker镜像并推送到仓库 -->
<execution> <execution>
<id>build-image</id> <id>default</id>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>build</goal> <goal>build</goal>
<goal>push</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<imageName>${docker.registry}/${docker.namespace}/${project.artifactId}</imageName> <repository>${docker.registry}/${docker.namespace}/${project.artifactId}</repository>
<imageTags> <tag>${project.version}</tag>
<imageTag>${project.version}</imageTag> <!-- 构建参数,指定jar包名称 -->
<imageTag>latest</imageTag> <buildArgs>
</imageTags> <JAR_FILE>${project.name}.jar</JAR_FILE>
<!-- 指定Dockerfile所在的路径 --> </buildArgs>
<dockerDirectory>${project.basedir}</dockerDirectory>
<baseImage>java</baseImage>
<entryPoint>["java", "-jar", "/${project.name}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.name}.jar</include>
</resource>
</resources>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
......
package com.github.tangyi.exam.mq; package com.github.tangyi.exam.mq;
import com.github.tangyi.common.core.constant.MqConstant; import com.github.tangyi.common.core.constant.MqConstant;
import com.github.tangyi.common.core.tenant.TenantContextHolder; import com.github.tangyi.common.security.tenant.TenantContextHolder;
import com.github.tangyi.exam.api.constants.ExamExaminationRecordConstant; import com.github.tangyi.exam.api.constants.ExamExaminationRecordConstant;
import com.github.tangyi.exam.api.module.Answer; import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.api.module.ExaminationRecord; import com.github.tangyi.exam.api.module.ExaminationRecord;
......
FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com)
ARG JAR_FILE
ENV PROFILE native
ADD target/${JAR_FILE} /opt/app.jar
EXPOSE 9000
ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Dspring.profiles.active=${PROFILE} -jar /opt/app.jar
# JAVA_OPS配置,如:JAVA_OPS='-Xmx512m -Xms256m'
\ No newline at end of file
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.tangyi</groupId>
<artifactId>service-api-impl</artifactId>
<version>3.0.0</version>
</parent>
<artifactId>msc-service</artifactId>
<name>${project.artifactId}</name>
<description>消息中心服务</description>
<dependencies>
<!-- common-security -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>common-security</artifactId>
</dependency>
<!-- common-log -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>common-log</artifactId>
</dependency>
<!-- web 服务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 配置客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- 管理中心插件 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!-- aliyun -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>${aliyun.version}</version>
</dependency>
<!-- msc-api -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>msc-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<finalName>${project.name}</finalName>
</configuration>
</plugin>
<!-- 打包时跳过test插件,不运行test测试用例 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!-- docker的maven插件 -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>${docker.maven.verion}</version>
<executions>
<!--执行 mvn package 时 自动构建docker镜像并推送到仓库 -->
<execution>
<id>default</id>
<phase>package</phase>
<goals>
<goal>build</goal>
<goal>push</goal>
</goals>
</execution>
</executions>
<configuration>
<repository>${docker.registry}/${docker.namespace}/${project.artifactId}</repository>
<tag>${project.version}</tag>
<!-- 构建参数,指定jar包名称 -->
<buildArgs>
<JAR_FILE>${project.name}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
package com.github.tangyi.msc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MscServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MscServiceApplication.class, args);
}
}
package com.github.tangyi.msc.config;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
/**
* Swagger配置
*
* @author tangyi
* @date 2019/3/26 16:26
*/
@Configuration
@EnableSwagger2
@EnableWebMvc
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());
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build()
.globalOperationParameters(parameterList);
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger API")
.description("https://gitee.com/wells2333/spring-microservice-exam")
.termsOfServiceUrl("https://gitee.com/wells2333/spring-microservice-exam")
.contact(new Contact("tangyi", "https://gitee.com/wells2333/spring-microservice-exam", "1633736729@qq.com"))
.version("2.0")
.build();
}
/**
* 显示swagger-ui.html文档展示页,还必须注入swagger资源:
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
package com.github.tangyi.msc.controller;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.msc.api.dto.SmsDto;
import com.github.tangyi.msc.service.SmsService;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 发送短信接口
*
* @author tangyi
* @date 2019/6/22 12:59
*/
@Slf4j
@AllArgsConstructor
@Api("发送短信")
@RestController
@RequestMapping(value = "/v1/sms")
public class SmsController extends BaseController {
private final SmsService smsService;
/**
* 发送短信
*
* @param smsDto smsDto
* @return ResponseBean
* @author tangyi
* @date 2019/06/22 13:12
*/
@PostMapping("sendSms")
public ResponseBean<Object> sendSms(@RequestBody SmsDto smsDto) {
log.info("发送短信给{},发送内容:{}", smsDto.getReceiver(), smsDto.getContent());
// TODO 发送逻辑
String result = smsService.sendSms(smsDto);
log.info("发送短信成功,返回内容:{}", result);
return new ResponseBean<>(result);
}
}
package com.github.tangyi.msc.properties;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 短信相关配置
*
* @author tangyi
* @date 2019/6/22 13:31
*/
@Data
@Configuration
@RefreshScope
@ConditionalOnExpression("!'${sms}'.isEmpty()")
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
/**
* appKey
*/
private String appKey;
/**
* appSecret
*/
private String appSecret;
/**
* regionId
*/
private String regionId;
/**
* domain
*/
private String domain;
}
package com.github.tangyi.msc.service;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.github.tangyi.msc.api.dto.SmsDto;
import com.github.tangyi.msc.properties.SmsProperties;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author tangyi
* @date 2019/6/22 13:23
*/
@Slf4j
@AllArgsConstructor
@Service
public class SmsService {
private final SmsProperties smsProperties;
/**
* 发送短信
*
* @param smsDto smsDto
* @return String
* @author tangyi
* @date 2019/06/22 13:28
*/
public String sendSms(SmsDto smsDto) {
DefaultProfile profile = DefaultProfile.getProfile(smsProperties.getRegionId(), smsProperties.getAppKey(), smsProperties.getAppSecret());
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setDomain(smsProperties.getDomain());
request.putQueryParameter("RegionId", smsProperties.getRegionId());
request.putQueryParameter("PhoneNumbers", smsDto.getReceiver());
try {
CommonResponse response = client.getCommonResponse(request);
log.info("发送结果:{}", response.getData());
return response.getData();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return null;
}
}
# bootstrap.yml文件中的内容不能放到application.yml中,否则config部分无法被加载
# 因为config部分的配置先于application.yml被加载,而bootstrap.yml中的配置会先于application.yml加载
spring:
application:
name: msc-service
cloud:
# 使用consul作为注册中心
consul:
host: ${CONSUL_HOST:localhost}
port: ${CONSUL_PORT:8500}
# 使用配置服务中心的配置信息
config:
fail-fast: true # 在某些情况下,如果服务无法连接到配置服务器,则可能希望启动服务失败,客户端将以异常停止
retry:
max-attempts: 5 # 配置客户端重试,默认行为是重试6次,初始退避间隔为1000ms,指数乘数为1.1
discovery:
# 默认false,设为true表示使用注册中心中的配置服务(服务发现)而不自己指定配置服务的地址(即uri)
enabled: true
# 指向配置中心在consul注册的服务名称(即:spring.application.name)
service-id: config-service
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="false">
<springProperty scop="context" name="spring.application.name" source="spring.application.name"
defaultValue="spring-microservice-exam"/>
<property name="log.path" value="logs/${spring.application.name}"/>
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{MM-dd HH:mm:ss.SSS} %-5level [%logger{50}] - %msg%n</pattern>
</encoder>
</appender>
<!-- Log file debug output -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
</appender>
<!-- Log file error output -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
<appender-ref ref="error"/>
</root>
</configuration>
...@@ -16,5 +16,6 @@ ...@@ -16,5 +16,6 @@
<module>auth-service</module> <module>auth-service</module>
<module>user-service</module> <module>user-service</module>
<module>exam-service</module> <module>exam-service</module>
<module>msc-service</module>
</modules> </modules>
</project> </project>
FROM anapsix/alpine-java:8_server-jre_unlimited FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com) MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/user-service.jar", "app.jar"] ARG JAR_FILE
ENV PROFILE native
ADD target/${JAR_FILE} /opt/app.jar
EXPOSE 8095 EXPOSE 8095
ENV JAVA_OPTS="-Xmx512m -Xms256m" ENTRYPOINT java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -Duser.timezone=Asia/Shanghai -Dfile.encoding=UTF-8 -Dspring.profiles.active=${PROFILE} -jar /opt/app.jar
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ] # JAVA_OPS配置,如:JAVA_OPS='-Xmx512m -Xms256m'
\ No newline at end of file \ No newline at end of file
...@@ -112,35 +112,26 @@ ...@@ -112,35 +112,26 @@
<!-- docker的maven插件 --> <!-- docker的maven插件 -->
<plugin> <plugin>
<groupId>com.spotify</groupId> <groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId> <artifactId>dockerfile-maven-plugin</artifactId>
<version>${docker.maven.verion}</version> <version>${docker.maven.verion}</version>
<executions> <executions>
<!--执行 mvn package 时 自动 执行 mvn docker:build--> <!--执行 mvn package 时 自动构建docker镜像并推送到仓库 -->
<execution> <execution>
<id>build-image</id> <id>default</id>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>build</goal> <goal>build</goal>
<goal>push</goal>
</goals> </goals>
</execution> </execution>
</executions> </executions>
<configuration> <configuration>
<imageName>${docker.registry}/${docker.namespace}/${project.artifactId}</imageName> <repository>${docker.registry}/${docker.namespace}/${project.artifactId}</repository>
<imageTags> <tag>${project.version}</tag>
<imageTag>${project.version}</imageTag> <!-- 构建参数,指定jar包名称 -->
<imageTag>latest</imageTag> <buildArgs>
</imageTags> <JAR_FILE>${project.name}.jar</JAR_FILE>
<!-- 指定Dockerfile所在的路径 --> </buildArgs>
<dockerDirectory>${project.basedir}</dockerDirectory>
<baseImage>java</baseImage>
<entryPoint>["java", "-jar", "/${project.name}.jar"]</entryPoint>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.name}.jar</include>
</resource>
</resources>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
......
...@@ -11,7 +11,6 @@ import com.github.tangyi.common.core.vo.AttachmentVo; ...@@ -11,7 +11,6 @@ import com.github.tangyi.common.core.vo.AttachmentVo;
import com.github.tangyi.common.core.web.BaseController; import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.common.log.annotation.Log; import com.github.tangyi.common.log.annotation.Log;
import com.github.tangyi.user.api.module.Attachment; import com.github.tangyi.user.api.module.Attachment;
import com.github.tangyi.user.config.SysConfig;
import com.github.tangyi.user.service.AttachmentService; import com.github.tangyi.user.service.AttachmentService;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import io.swagger.annotations.*; import io.swagger.annotations.*;
...@@ -48,8 +47,6 @@ public class AttachmentController extends BaseController { ...@@ -48,8 +47,6 @@ public class AttachmentController extends BaseController {
private final AttachmentService attachmentService; private final AttachmentService attachmentService;
private final SysConfig sysConfig;
/** /**
* 根据ID获取 * 根据ID获取
* *
...@@ -244,11 +241,6 @@ public class AttachmentController extends BaseController { ...@@ -244,11 +241,6 @@ public class AttachmentController extends BaseController {
public ResponseBean<String> getPreviewUrl(@PathVariable String id) { public ResponseBean<String> getPreviewUrl(@PathVariable String id) {
Attachment attachment = new Attachment(); Attachment attachment = new Attachment();
attachment.setId(id); attachment.setId(id);
attachment = attachmentService.get(attachment); return new ResponseBean<>(attachmentService.getPreviewUrl(attachment));
if (attachment == null)
throw new CommonException("附件不存在.");
String preview = sysConfig.getFdfsHttpHost() + "/" + attachment.getFastFileId();
log.debug("预览地址:{}", preview);
return new ResponseBean<>(preview);
} }
} }
...@@ -229,7 +229,7 @@ public class UserController extends BaseController { ...@@ -229,7 +229,7 @@ public class UserController extends BaseController {
@ApiOperation(value = "更新用户信息", notes = "根据用户id更新用户的基本信息、角色信息") @ApiOperation(value = "更新用户信息", notes = "根据用户id更新用户的基本信息、角色信息")
@ApiImplicitParam(name = "userDto", value = "用户实体user", required = true, dataType = "UserDto") @ApiImplicitParam(name = "userDto", value = "用户实体user", required = true, dataType = "UserDto")
@Log("修改用户") @Log("修改用户")
public ResponseBean<Boolean> updateUser(@RequestBody @Valid UserDto userDto) { public ResponseBean<Boolean> updateUser(@RequestBody UserDto userDto) {
try { try {
return new ResponseBean<>(userService.updateUser(userDto)); return new ResponseBean<>(userService.updateUser(userDto));
} catch (Exception e) { } catch (Exception e) {
...@@ -250,7 +250,7 @@ public class UserController extends BaseController { ...@@ -250,7 +250,7 @@ public class UserController extends BaseController {
@ApiOperation(value = "更新用户基本信息", notes = "根据用户id更新用户的基本信息") @ApiOperation(value = "更新用户基本信息", notes = "根据用户id更新用户的基本信息")
@ApiImplicitParam(name = "userDto", value = "用户实体user", required = true, dataType = "UserDto") @ApiImplicitParam(name = "userDto", value = "用户实体user", required = true, dataType = "UserDto")
@Log("更新用户基本信息") @Log("更新用户基本信息")
public ResponseBean<Boolean> updateInfo(@RequestBody @Valid UserDto userDto) { public ResponseBean<Boolean> updateInfo(@RequestBody UserDto userDto) {
// 新密码不为空 // 新密码不为空
if (StringUtils.isNotEmpty(userDto.getNewPassword())) { if (StringUtils.isNotEmpty(userDto.getNewPassword())) {
if (!encoder.matches(userDto.getOldPassword(), userDto.getPassword())) { if (!encoder.matches(userDto.getOldPassword(), userDto.getPassword())) {
...@@ -264,6 +264,58 @@ public class UserController extends BaseController { ...@@ -264,6 +264,58 @@ public class UserController extends BaseController {
} }
/** /**
* 修改密码
*
* @param userDto userDto
* @return ResponseBean
* @author tangyi
* @date 2019/06/21 20:09
*/
@PutMapping("updatePassword")
@ApiOperation(value = "修改用户密码", notes = "修改用户密码")
@ApiImplicitParam(name = "userDto", value = "用户实体user", required = true, dataType = "UserDto")
@Log("更新用户密码")
public ResponseBean<Boolean> updatePassword(@RequestBody UserDto userDto) {
if (StringUtils.isBlank(userDto.getUsername()))
throw new CommonException("用户名不能为空.");
if (StringUtils.isBlank(userDto.getTenantCode()))
throw new CommonException("租户编码不能为空.");
UserVo userVo = userService.selectUserVoByUsername(userDto.getUsername(), userDto.getTenantCode());
UserDto newUserDto = new UserDto();
newUserDto.setId(userVo.getId());
newUserDto.setUsername(userVo.getUsername());
newUserDto.setPassword(userVo.getPassword());
newUserDto.setOldPassword(userDto.getOldPassword());
newUserDto.setNewPassword(userDto.getNewPassword());
// 新密码不为空
if (StringUtils.isNotEmpty(newUserDto.getNewPassword())) {
if (!encoder.matches(newUserDto.getOldPassword(), newUserDto.getPassword())) {
return new ResponseBean<>(Boolean.FALSE, "新旧密码不匹配");
} else {
// 新旧密码一致,修改密码
newUserDto.setPassword(encoder.encode(newUserDto.getNewPassword()));
}
}
return new ResponseBean<>(userService.update(newUserDto) > 0);
}
/**
* 更新头像
*
* @param userDto userDto
* @return ResponseBean
* @author tangyi
* @date 2019/06/21 18:08
*/
@PutMapping("updateAvatar")
@ApiOperation(value = "更新用户头像", notes = "根据用户id更新用户的头像信息")
@ApiImplicitParam(name = "userDto", value = "用户实体user", required = true, dataType = "UserDto")
@Log("更新用户头像")
public ResponseBean<Boolean> updateAvatar(@RequestBody UserDto userDto) {
return new ResponseBean<>(userService.updateAvatar(userDto) > 0);
}
/**
* 删除用户 * 删除用户
* *
* @param id id * @param id id
...@@ -465,7 +517,7 @@ public class UserController extends BaseController { ...@@ -465,7 +517,7 @@ public class UserController extends BaseController {
@ApiOperation(value = "检查用户是否存在", notes = "检查用户名是否存在") @ApiOperation(value = "检查用户是否存在", notes = "检查用户名是否存在")
@ApiImplicitParam(name = "username", value = "用户name", required = true, dataType = "String", paramType = "path") @ApiImplicitParam(name = "username", value = "用户name", required = true, dataType = "String", paramType = "path")
@GetMapping("checkExist/{username}") @GetMapping("checkExist/{username}")
public ResponseBean<Boolean> checkUsernameIsExist(@PathVariable("username") @NotBlank String username, @RequestParam @NotBlank String tenantCode) { public ResponseBean<Boolean> checkUsernameIsExist(@PathVariable("username") @NotBlank String username, @RequestHeader(SecurityConstant.TENANT_CODE_HEADER) String tenantCode) {
boolean exist = Boolean.FALSE; boolean exist = Boolean.FALSE;
if (StringUtils.isNotEmpty(username)) if (StringUtils.isNotEmpty(username))
exist = userService.selectUserVoByUsername(username, tenantCode) != null; exist = userService.selectUserVoByUsername(username, tenantCode) != null;
......
...@@ -5,11 +5,14 @@ import com.github.tangyi.common.core.service.CrudService; ...@@ -5,11 +5,14 @@ import com.github.tangyi.common.core.service.CrudService;
import com.github.tangyi.common.core.utils.FileUtil; import com.github.tangyi.common.core.utils.FileUtil;
import com.github.tangyi.common.core.utils.SysUtil; import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.user.api.module.Attachment; import com.github.tangyi.user.api.module.Attachment;
import com.github.tangyi.user.config.SysConfig;
import com.github.tangyi.user.mapper.AttachmentMapper; import com.github.tangyi.user.mapper.AttachmentMapper;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
...@@ -28,6 +31,33 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment> ...@@ -28,6 +31,33 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment>
private final FastDfsService fastDfsService; private final FastDfsService fastDfsService;
private final SysConfig sysConfig;
/**
* 根据id查询
*
* @param attachment attachment
* @return Attachment
*/
@Cacheable(value = "attachment", key = "#attachment.id")
@Override
public Attachment get(Attachment attachment) {
return super.get(attachment);
}
/**
* 根据id更新
*
* @param attachment attachment
* @return int
*/
@Override
@Transactional
@CacheEvict(value = "attachment", key = "#attachment.id")
public int update(Attachment attachment) {
return super.update(attachment);
}
/** /**
* 上传 * 上传
* *
...@@ -43,7 +73,6 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment> ...@@ -43,7 +73,6 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment>
inputStream = file.getInputStream(); inputStream = file.getInputStream();
long attachSize = file.getSize(); long attachSize = file.getSize();
String fastFileId = fastDfsService.uploadFile(inputStream, attachSize, FileUtil.getFileNameEx(file.getOriginalFilename())); String fastFileId = fastDfsService.uploadFile(inputStream, attachSize, FileUtil.getFileNameEx(file.getOriginalFilename()));
log.debug("fastFileId:{}", fastFileId);
if (StringUtils.isBlank(fastFileId)) if (StringUtils.isBlank(fastFileId))
throw new CommonException("上传失败!"); throw new CommonException("上传失败!");
Attachment newAttachment = new Attachment(); Attachment newAttachment = new Attachment();
...@@ -85,13 +114,41 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment> ...@@ -85,13 +114,41 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment>
*/ */
@Override @Override
@Transactional @Transactional
@CacheEvict(value = "attachment", key = "#attachment.id")
public int delete(Attachment attachment) { public int delete(Attachment attachment) {
try {
fastDfsService.deleteFile(attachment.getGroupName(), attachment.getFastFileId()); fastDfsService.deleteFile(attachment.getGroupName(), attachment.getFastFileId());
return super.delete(attachment); return super.delete(attachment);
} catch (Exception e) {
log.error(e.getMessage(), e);
} }
return -1;
/**
* 批量删除
*
* @param ids ids
* @return int
*/
@Override
@Transactional
@CacheEvict(value = "attachment", allEntries = true)
public int deleteAll(String[] ids) {
return super.deleteAll(ids);
}
/**
* 获取附件的预览地址
*
* @param attachment attachment
* @return String
* @author tangyi
* @date 2019/06/21 17:45
*/
public String getPreviewUrl(Attachment attachment) {
attachment = this.get(attachment);
if (attachment == null)
throw new CommonException("附件不存在.");
String preview = attachment.getPreviewUrl();
if (StringUtils.isBlank(preview))
preview = sysConfig.getFdfsHttpHost() + "/" + attachment.getFastFileId();
log.debug("id为:{}的附件的预览地址:{}", attachment.getId(), preview);
return preview;
} }
} }
package com.github.tangyi.user.service; package com.github.tangyi.user.service;
import com.github.tangyi.common.core.constant.CommonConstant; import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.service.CrudService; import com.github.tangyi.common.core.service.CrudService;
import com.github.tangyi.common.core.utils.IdGen; import com.github.tangyi.common.core.utils.IdGen;
import com.github.tangyi.common.core.utils.SysUtil; import com.github.tangyi.common.core.utils.SysUtil;
...@@ -9,13 +10,17 @@ import com.github.tangyi.common.security.constant.SecurityConstant; ...@@ -9,13 +10,17 @@ import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.user.api.constant.MenuConstant; import com.github.tangyi.user.api.constant.MenuConstant;
import com.github.tangyi.user.api.dto.UserDto; import com.github.tangyi.user.api.dto.UserDto;
import com.github.tangyi.user.api.dto.UserInfoDto; import com.github.tangyi.user.api.dto.UserInfoDto;
import com.github.tangyi.user.api.module.Attachment;
import com.github.tangyi.user.api.module.Menu; import com.github.tangyi.user.api.module.Menu;
import com.github.tangyi.user.api.module.User; import com.github.tangyi.user.api.module.User;
import com.github.tangyi.user.api.module.UserRole; import com.github.tangyi.user.api.module.UserRole;
import com.github.tangyi.user.config.SysConfig;
import com.github.tangyi.user.mapper.UserMapper; import com.github.tangyi.user.mapper.UserMapper;
import com.github.tangyi.user.mapper.UserRoleMapper; import com.github.tangyi.user.mapper.UserRoleMapper;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
...@@ -34,6 +39,7 @@ import java.util.concurrent.TimeUnit; ...@@ -34,6 +39,7 @@ import java.util.concurrent.TimeUnit;
* @date 2018-08-25 16:17 * @date 2018-08-25 16:17
*/ */
@AllArgsConstructor @AllArgsConstructor
@Slf4j
@Service @Service
public class UserService extends CrudService<UserMapper, User> { public class UserService extends CrudService<UserMapper, User> {
...@@ -45,6 +51,10 @@ public class UserService extends CrudService<UserMapper, User> { ...@@ -45,6 +51,10 @@ public class UserService extends CrudService<UserMapper, User> {
private final RedisTemplate redisTemplate; private final RedisTemplate redisTemplate;
private final AttachmentService attachmentService;
private final SysConfig sysConfig;
/** /**
* 新增用户 * 新增用户
* *
...@@ -85,7 +95,7 @@ public class UserService extends CrudService<UserMapper, User> { ...@@ -85,7 +95,7 @@ public class UserService extends CrudService<UserMapper, User> {
userVo = userMapper.selectUserVoByUsername(userVo.getUsername(), tenantCode); userVo = userMapper.selectUserVoByUsername(userVo.getUsername(), tenantCode);
UserInfoDto user = new UserInfoDto(); UserInfoDto user = new UserInfoDto();
if (userVo != null) { if (userVo != null) {
user.setUser(userVo); BeanUtils.copyProperties(userVo, user);
// 用户角色 // 用户角色
List<String> roles = new ArrayList<>(); List<String> roles = new ArrayList<>();
// 用户权限 // 用户权限
...@@ -109,6 +119,8 @@ public class UserService extends CrudService<UserMapper, User> { ...@@ -109,6 +119,8 @@ public class UserService extends CrudService<UserMapper, User> {
roles.add(role.getRoleCode()); roles.add(role.getRoleCode());
}); });
} }
// 头像信息
this.initUserAvatar(user, userVo);
user.setRoles(roles.toArray(new String[0])); user.setRoles(roles.toArray(new String[0]));
user.setPermissions(permissions.toArray(new String[0])); user.setPermissions(permissions.toArray(new String[0]));
} }
...@@ -163,6 +175,34 @@ public class UserService extends CrudService<UserMapper, User> { ...@@ -163,6 +175,34 @@ public class UserService extends CrudService<UserMapper, User> {
} }
/** /**
* 更新头像
*
* @param userDto userDto
* @return int
* @author tangyi
* @date 2019/06/21 18:14
*/
@Transactional
@CacheEvict(value = "user", key = "#userDto.username")
public int updateAvatar(UserDto userDto) {
User user = new User();
user.setId(userDto.getId());
user = this.get(user);
if (user == null)
throw new CommonException("用户不存在.");
// 先删除旧头像
if (StringUtils.isNotBlank(user.getAvatarId())) {
Attachment attachment = new Attachment();
attachment.setId(user.getAvatarId());
attachment = attachmentService.get(attachment);
if (attachment != null)
attachmentService.delete(attachment);
}
user.setAvatarId(userDto.getAvatarId());
return super.update(user);
}
/**
* 根据用户名查找 * 根据用户名查找
* *
* @param username username * @param username username
...@@ -212,4 +252,27 @@ public class UserService extends CrudService<UserMapper, User> { ...@@ -212,4 +252,27 @@ public class UserService extends CrudService<UserMapper, User> {
public Integer userCount(UserVo userVo) { public Integer userCount(UserVo userVo) {
return this.dao.userCount(userVo); return this.dao.userCount(userVo);
} }
/**
* 初始化用户头像信息
*
* @param userInfoDto userInfoDto
* @param userVo userVo
* @author tangyi
* @date 2019/06/21 17:49
*/
private void initUserAvatar(UserInfoDto userInfoDto, UserVo userVo) {
try {
// 附件id不为空,获取对应的预览地址,否则获取配置默认头像地址
if (StringUtils.isNotBlank(userVo.getAvatarId())) {
Attachment attachment = new Attachment();
attachment.setId(userVo.getAvatarId());
userInfoDto.setAvatarUrl(attachmentService.getPreviewUrl(attachment));
} else {
userInfoDto.setAvatarUrl(sysConfig.getDefaultAvatar());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
} }
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<result column="busi_id" property="busiId"/> <result column="busi_id" property="busiId"/>
<result column="busi_module" property="busiModule"/> <result column="busi_module" property="busiModule"/>
<result column="busi_type" property="busiType"/> <result column="busi_type" property="busiType"/>
<result column="preview_url" property="previewUrl"/>
<result column="creator" property="creator"/> <result column="creator" property="creator"/>
<result column="create_date" property="createDate"/> <result column="create_date" property="createDate"/>
<result column="modifier" property="modifier"/> <result column="modifier" property="modifier"/>
...@@ -28,6 +29,7 @@ ...@@ -28,6 +29,7 @@
a.busi_id, a.busi_id,
a.busi_module, a.busi_module,
a.busi_type, a.busi_type,
a.preview_url,
a.creator, a.creator,
a.create_date, a.create_date,
a.modifier, a.modifier,
...@@ -89,6 +91,7 @@ ...@@ -89,6 +91,7 @@
busi_id, busi_id,
busi_module, busi_module,
busi_type, busi_type,
preview_url,
creator, creator,
create_date, create_date,
modifier, modifier,
...@@ -105,6 +108,7 @@ ...@@ -105,6 +108,7 @@
#{busiId}, #{busiId},
#{busiModule}, #{busiModule},
#{busiType}, #{busiType},
#{previewUrl},
#{creator}, #{creator},
#{createDate}, #{createDate},
#{modifier}, #{modifier},
...@@ -138,6 +142,9 @@ ...@@ -138,6 +142,9 @@
<if test="busiType != null"> <if test="busiType != null">
busi_type = #{busiType}, busi_type = #{busiType},
</if> </if>
<if test="previewUrl != null">
preview_url = #{previewUrl},
</if>
<if test="delFlag != null"> <if test="delFlag != null">
del_flag = #{delFlag}, del_flag = #{delFlag},
</if> </if>
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
<result column="password" property="password"/> <result column="password" property="password"/>
<result column="salt" property="salt"/> <result column="salt" property="salt"/>
<result column="phone" property="phone"/> <result column="phone" property="phone"/>
<result column="avatar" property="avatar"/>
<result column="avatar_id" property="avatarId"/> <result column="avatar_id" property="avatarId"/>
<result column="email" property="email"/> <result column="email" property="email"/>
<result column="sex" property="sex"/> <result column="sex" property="sex"/>
...@@ -32,7 +31,6 @@ ...@@ -32,7 +31,6 @@
<result column="password" property="password"/> <result column="password" property="password"/>
<result column="salt" property="salt"/> <result column="salt" property="salt"/>
<result column="phone" property="phone"/> <result column="phone" property="phone"/>
<result column="avatar" property="avatar"/>
<result column="avatar_id" property="avatarId"/> <result column="avatar_id" property="avatarId"/>
<result column="email" property="email"/> <result column="email" property="email"/>
<result column="sex" property="sex"/> <result column="sex" property="sex"/>
...@@ -69,7 +67,6 @@ ...@@ -69,7 +67,6 @@
a.password, a.password,
a.salt, a.salt,
a.phone, a.phone,
a.avatar,
a.avatar_id, a.avatar_id,
a.email, a.email,
a.sex, a.sex,
...@@ -119,7 +116,6 @@ ...@@ -119,7 +116,6 @@
`user`.password, `user`.password,
`user`.salt, `user`.salt,
`user`.phone, `user`.phone,
`user`.avatar,
`user`.avatar_id, `user`.avatar_id,
`user`.email, `user`.email,
`user`.sex, `user`.sex,
...@@ -195,7 +191,6 @@ ...@@ -195,7 +191,6 @@
password, password,
salt, salt,
phone, phone,
avatar,
avatar_id, avatar_id,
email, email,
sex, sex,
...@@ -217,7 +212,6 @@ ...@@ -217,7 +212,6 @@
#{password}, #{password},
#{salt}, #{salt},
#{phone}, #{phone},
#{avatar},
#{avatarId}, #{avatarId},
#{email}, #{email},
#{sex}, #{sex},
...@@ -253,9 +247,6 @@ ...@@ -253,9 +247,6 @@
<if test="phone != null"> <if test="phone != null">
phone = #{phone} , phone = #{phone} ,
</if> </if>
<if test="avatar != null">
avatar = #{avatar} ,
</if>
<if test="avatarId != null"> <if test="avatarId != null">
avatar_id = #{avatarId} , avatar_id = #{avatarId} ,
</if> </if>
......
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.tangyi</groupId>
<artifactId>service-api</artifactId>
<version>3.0.0</version>
</parent>
<artifactId>msc-api</artifactId>
<name>${project.artifactId}</name>
<description>消息中心服务api</description>
<dependencies>
<!-- common-core -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>common-core</artifactId>
</dependency>
<!-- common-feign -->
<dependency>
<groupId>com.github.tangyi</groupId>
<artifactId>common-feign</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package com.github.tangyi.msc.api.constant;
/**
* @author tangyi
* @date 2019/6/22 13:18
*/
public class SmsConstant {
}
package com.github.tangyi.msc.api.dto;
import lombok.Data;
import java.io.Serializable;
/**
* @author tangyi
* @date 2019/6/22 13:07
*/
@Data
public class SmsDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 接收人
*/
private String receiver;
/**
* 发送内容
*/
private String content;
}
...@@ -16,5 +16,6 @@ ...@@ -16,5 +16,6 @@
<module>user-api</module> <module>user-api</module>
<module>exam-api</module> <module>exam-api</module>
<module>auth-api</module> <module>auth-api</module>
<module>msc-api</module>
</modules> </modules>
</project> </project>
package com.github.tangyi.user.api.dto; package com.github.tangyi.user.api.dto;
import com.github.tangyi.common.core.vo.UserVo;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
...@@ -14,10 +13,67 @@ public class UserInfoDto implements Serializable { ...@@ -14,10 +13,67 @@ public class UserInfoDto implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private String id;
/**
* 姓名
*/
private String name;
/**
* 用户名
*/
private String username;
/**
* 电话号码
*/
private String phone;
/**
* 头像对应的附件id
*/
private String avatarId;
/**
* 头像地址
*/
private String avatarUrl;
/**
* 邮箱
*/
private String email;
/**
* 性别
*/
private String sex;
/**
* 生日
*/
private String born;
/**
* 部门名称
*/
private String deptName;
/**
* 部门ID
*/
private String deptId;
/**
* 备注
*/
private String remark;
/** /**
* 用户信息 * 状态,0-启用,1-禁用
*/ */
private UserVo user; private String status;
/** /**
* 权限信息 * 权限信息
......
...@@ -140,4 +140,16 @@ public interface UserServiceClient { ...@@ -140,4 +140,16 @@ public interface UserServiceClient {
*/ */
@GetMapping("/v1/tenant/findTenantByTenantCode/{tenantCode}") @GetMapping("/v1/tenant/findTenantByTenantCode/{tenantCode}")
Tenant findTenantByTenantCode(@PathVariable("tenantCode") String tenantCode); Tenant findTenantByTenantCode(@PathVariable("tenantCode") String tenantCode);
/**
* 根据社交账号获取用户详细信息
*
* @param social social
* @param tenantCode 租户标识
* @return UserVo
* @author tangyi
* @date 2019/06/22 21:10
*/
@GetMapping("/v1/user/findUserBySocial/{social}")
UserVo findUserBySocial(@PathVariable("social") String social, @RequestParam("tenantCode") String tenantCode);
} }
...@@ -162,6 +162,19 @@ public class UserServiceClientFallbackImpl implements UserServiceClient { ...@@ -162,6 +162,19 @@ public class UserServiceClientFallbackImpl implements UserServiceClient {
return null; return null;
} }
/**
* 根据社交账号获取用户详细信息
*
* @param social social
* @param tenantCode 租户标识
* @return UserVo
*/
@Override
public UserVo findUserBySocial(String social, String tenantCode) {
log.error("feign 根据社交账号获取用户详细信息失败, {}, {}, {}", social, tenantCode, throwable);
return null;
}
public Throwable getThrowable() { public Throwable getThrowable() {
return throwable; return throwable;
} }
......
...@@ -51,4 +51,9 @@ public class Attachment extends BaseEntity<Attachment> { ...@@ -51,4 +51,9 @@ public class Attachment extends BaseEntity<Attachment> {
* 业务模块 * 业务模块
*/ */
private String busiModule; private String busiModule;
/**
* 预览地址
*/
private String previewUrl;
} }
...@@ -6,7 +6,6 @@ import lombok.Data; ...@@ -6,7 +6,6 @@ import lombok.Data;
import javax.validation.constraints.Email; import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import java.util.List; import java.util.List;
/** /**
...@@ -31,8 +30,6 @@ public class User extends BaseEntity<User> { ...@@ -31,8 +30,6 @@ public class User extends BaseEntity<User> {
@Pattern(regexp = "^\\d{11}$", message = "请输入11位手机号") @Pattern(regexp = "^\\d{11}$", message = "请输入11位手机号")
private String phone; private String phone;
private String avatar;
private String avatarId; private String avatarId;
@Email(message = "邮箱格式不正确") @Email(message = "邮箱格式不正确")
......
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