SSO是公司一個(gè)已經(jīng)存在了若干年的項(xiàng)目,后端采用SpringMVC、MyBatis,數(shù)據(jù)庫(kù)使用MySQL,前端展示使用Freemark。今年,我們對(duì)該項(xiàng)目進(jìn)行了一次革命性的改進(jìn),改造成SpringCloud架構(gòu),并且把前后端分離,前端采用Vue框架。
公司主營(yíng)業(yè)務(wù):成都做網(wǎng)站、成都網(wǎng)站制作、移動(dòng)網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。成都創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。成都創(chuàng)新互聯(lián)公司推出旌陽(yáng)免費(fèi)做網(wǎng)站回饋大家。
SpringCloud的核心是SpringBoot,相比較于傳統(tǒng)的Spring,SpringCloud具有以下優(yōu)點(diǎn):
SSO項(xiàng)目改造前充斥著大量的配置文件,主要包含以下這些部分:
本文著重介紹以下幾個(gè)部分:
SpringMVC中,如果mvc:interceptors
配置的URL規(guī)則如下,則不會(huì)攔截靜態(tài)資源。
<mvc:mapping path="/*.do" />
但是如果配置的是:
<mvc:mapping path="/**" />
方案1: 在web.xml中配置<servlet-name>default</servlet-name>
,用defaultServlet
先處理請(qǐng)求如:
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.ico</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.gif</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</url-pattern>
</servlet-mapping>
方案2:使用<mvc:resources />
標(biāo)簽聲明靜態(tài)資源路徑
<mvc:resources mapping="/resources/js/**" location="/js/" />
<mvc:resources mapping="/resources/images/**" location="/images/" />
<mvc:resources mapping="/resources/css/**" location="/css/" />
方案3:使用mvc:default-servlet-handler/
標(biāo)簽
SpringBoot解決方案:繼承WebMvcConfigurerAdapter實(shí)現(xiàn)addResourceHandlers方法。
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/resource/")//sso靜態(tài)資源
.addResourceLocations("classpath:/META-INF/resources/")//swagger靜態(tài)資源
.setCachePeriod(0);//0表示不緩存
}
sso靜態(tài)資源文件路徑如圖:
SpringMVC配置文件內(nèi)容:
攔截任何請(qǐng)求并且初始化參數(shù),有些請(qǐng)求是不需要攔截的,有的請(qǐng)求登錄后不需要經(jīng)過(guò)權(quán)限校驗(yàn)直接放行。
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="自定義攔截器PermissionInterceptor">
<!-- 未登錄即可訪問(wèn)的地址 -->
<property name="excludeUrls">
<list><value>請(qǐng)求地址<value></list>
</property>
<!-- 只要登錄了就不需要攔截的資源 -->
<property name="LogInExcludeUrls">
<list><value>請(qǐng)求地址<value></list>
</property>
</bean>
</mvc:interceptor>
</mvc:interceptors>
SpringBoot中添加攔截器只需繼承WebMvcConfigurerAdapter,并重寫addInterceptors方法即可。
/*** 攔截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(permissionInterceptor).
addPathPatterns("/**");
super.addInterceptors(registry);
}
自定義的攔截器需要初始化一些參數(shù),因此需要在注冊(cè)攔截器之前注冊(cè),這里我們?cè)O(shè)置為懶加載。免登錄攔截的路徑,以及登錄后不需要判斷權(quán)限的路徑都寫在yml文件了,通過(guò)系統(tǒng)環(huán)境變量Environment獲取值。
@Autowired
@Lazy
private PermissionInterceptor permissionInterceptor;
@Autowired
private Environment environment;
/**
*
*/
@Bean
public PermissionInterceptor permissionInterceptor() {
PermissionInterceptor permissionInterceptor = new PermissionInterceptor();
List<String> excludeUrls = Arrays.asList(environment.getProperty("intercept.exclude.path").split(","));
List<String> commonUrls = Arrays.asList(environment.getProperty("intercept.login.exclude.path").split(","));
permissionInterceptor.setCommonUrls(commonUrls);
permissionInterceptor.setExcludeUrls(excludeUrls);
return permissionInterceptor;
}
A、數(shù)據(jù)源配置
數(shù)據(jù)源注入的三種情況:
【情況一】
【情況二】
【情況三】
pom.xml依賴:
<!-- 情況一、二 測(cè)試引入的依賴 -->
<!--<dependency>-->
<!--<groupId>com.alibaba</groupId>-->
<!--<artifactId>druid</artifactId>-->
<!--<version>${druid.version}</version>-->
<!--</dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>RELEASE</version>
</dependency>
yml配置:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 當(dāng)前數(shù)據(jù)源操作類型
driver-class-name: com.mysql.jdbc.Driver # mysql驅(qū)動(dòng)包
url: jdbc:mysql://yourURL # 數(shù)據(jù)庫(kù)名稱
username: yourusername
password: yourpassword
druid:
initial-size: 5 # 初始化大小
min-idle: 5 # 最小
max-active: 20 # 最大
max-wait: 60000 # 連接超時(shí)時(shí)間
time-between-eviction-runs-millis: 60000 # 配置間隔多久才進(jìn)行一次檢測(cè),檢測(cè)需要關(guān)閉的空閑連接,單位是毫秒
min-evictable-idle-time-millis: 300000 # 指定一個(gè)空閑連接最少空閑多久后可被清除,單位是毫秒
validationQuery: select 'x'
test-while-idle: true # 當(dāng)連接空閑時(shí),是否執(zhí)行連接測(cè)試
test-on-borrow: false # 當(dāng)從連接池借用連接時(shí),是否測(cè)試該連接
test-on-return: false # 在連接歸還到連接池時(shí)是否測(cè)試該連接
filters: config,wall,stat
B、MyBatis配置
通過(guò)引入mybatis-spring-boot-starter依賴,可以簡(jiǎn)單配置mybatis上手使用。
下面簡(jiǎn)單分析mybatis-starter的源碼以及如何配置mybatis。
先看mybatis-spring-boot-starter中mybatis-spring-boot-autoconfigure的spring.factories文件
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
可以看到自動(dòng)注入類是MybatisAutoConfiguration,我們從這個(gè)類入手分析可以知道,必須先創(chuàng)建好了數(shù)據(jù)源后,才會(huì)加載mybatis的sqlSessionFactory。
@EnableConfigurationProperties({MybatisProperties.class})注解指定了配置文件中
prefix = "mybatis" 那部分屬性有效,這部分屬性值將注入到已創(chuàng)建的SqlSessionFactoryBean中,最后生成SqlSessionFactory對(duì)象。
@Configuration
//當(dāng)SqlSessionFactory,SqlSessionFactoryBean存在的情況下加載當(dāng)前Bean
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
//當(dāng)指定數(shù)據(jù)源在容器中只有一個(gè)或者有多個(gè)但是只指定首選數(shù)據(jù)源
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
//當(dāng)數(shù)據(jù)源注入到Spring容器后才開始加載當(dāng)前Bean
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
private final MybatisProperties properties;
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
//設(shè)置mybatis配置文件所在路徑
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource
(this.properties.getConfigLocation())); }
}
//設(shè)置其他MyBatisProperties對(duì)象中有的屬性略....
return factory.getObject();
}
}
MybatisProperties含有的屬性:
@ConfigurationProperties(prefix = "mybatis" )
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
private String configLocation;
private String[] mapperLocations;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private String typeHandlersPackage;
private boolean checkConfigLocation = false;
private ExecutorType executorType;
private Properties configurationProperties;
@NestedConfigurationProperty
private Configuration configuration;
}
C、使用mybatis
application.yml
mybatis:
config-location: classpath:mybatis.xml # mybatis配置文件所在路徑
type-aliases-package: com.creditease.permission.model # 所有Entity別名類所在包
mapper-locations: classpath:mybatis/**/*.xml
從上面的MybatisProperties可以看出,mybatis可以指定一些configuration,比如自定義攔截器pageHelper。
mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
<plugin interceptor="com.creditease.permission.manager.MybatisInterceptor"></plugin>
</plugins>
</configuration>
@MapperScan("com.creditease.permission.dao")//mapper類所在目錄
public class SsoApplication {
public static void main(String[] args) {
SpringApplication.run(SsoApplication.class, args);
}
}
Spring事務(wù)有兩種處理方式:
用TransactionTemplate或者直接使用底層的PlatformTransactionManager將事務(wù)代碼寫在業(yè)務(wù)代碼中。
優(yōu)點(diǎn):可以在代碼塊中處理事務(wù),比較靈活。
缺點(diǎn):對(duì)代碼具有侵入性。
采用@Transactional注解或者基于配置文件方式,在方法前后進(jìn)行攔截。
優(yōu)點(diǎn):非侵入性不會(huì)污染代碼。
缺點(diǎn):事務(wù)只能在方法和類上控制,粒度較小。
A、使用@Transactional注解
非SpringBoot工程,需要在配置文件中加入配置:
<tx:annotation-driven/>
SpringBoot工程可以用@EnableTransactionManagement注解代替上面的配置內(nèi)容。
B、采用配置文件方式
之前的sso是基于配置的方式,配置代碼如下:
<aop:config>
<aop:pointcut expression="execution(public * com.creditease.permission.service.impl.*Impl.*(..))" id="pointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="query*" propagation="REQUIRED" read-only="true"/>
<tx:method name="find*" propagation="REQUIRED" read-only="true"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="modify*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
改造后的SpringBoot基于Java代碼:
@Aspect
@Configuration
public class TransactionAdviceConfig {
/**
* 指定切入點(diǎn)
*/
private static final String AOP_POINTCUT_EXPRESSION = "execution(public * com.creditease.permission.service.impl.*Impl.*(..))";
@Resource
DruidDataSource dataSource;
/**
* 指定處理事務(wù)的PlatformTransactionManager
* @return
*/
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
/**
* 指定切入點(diǎn)處理邏輯,執(zhí)行事務(wù)
* @return
*/
@Bean
public TransactionInterceptor txAdvice() {
DefaultTransactionAttribute txAttrRequired = new DefaultTransactionAttribute();
txAttrRequired.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
DefaultTransactionAttribute txAttrRequiredReadonly = new DefaultTransactionAttribute();
txAttrRequiredReadonly.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txAttrRequiredReadonly.setReadOnly(true);
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.addTransactionalMethod("query*", txAttrRequiredReadonly);
source.addTransactionalMethod("find*", txAttrRequiredReadonly);
source.addTransactionalMethod("save*", txAttrRequired);
source.addTransactionalMethod("delete*", txAttrRequired);
source.addTransactionalMethod("add*", txAttrRequired);
source.addTransactionalMethod("modify*", txAttrRequired);
return new TransactionInterceptor(transactionManager(), source);
}
/**
* Advisor組裝配置,將Advice的代碼邏輯注入到Pointcut位置
* @return
*/
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
一般編碼時(shí)有異常我們都會(huì)try-catch捕獲異常,有時(shí)為了區(qū)分不同的異常還會(huì)一次catch多個(gè)異常,大量的try-catch語(yǔ)句,這樣使得代碼也不夠優(yōu)雅;一個(gè)相同的異常處理寫多次代碼也比較冗余,所以引入全局的異常處理非常必要。
改造前的異常處理配置文件:
<!--定義異常處理頁(yè)面-->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="com.creditease.permissionapi.exception.NopermissionException">/permission/noSecurity</prop>
</props>
</property>
</bean>
使用SimpleMappingExceptionResolver類處理異常,設(shè)置自定義異常類型NopermissionException,以及異常發(fā)生后的請(qǐng)求路徑/permission/noSecurity。
SpringBoot中采用@RestControllerAdvice或者@ControllerAdvice設(shè)置全局異常類。這兩者區(qū)別類似于@Controller和@RestController注解。
SSO中定義了三種全局的異常處理:普通的Exception處理;自定的NopermissionException異常和參數(shù)校驗(yàn)異常。
全局異常處理代碼如下:
@Configuration
@Slf4j
@RestControllerAdvice
public class GlobalExceptionConfig {
//無(wú)權(quán)限處理
@ExceptionHandler(value = {NopermissionException.class})
public void noPermissionExceptionHandler(HttpServletRequest request, Exception ex, HttpServletResponse response, @Value("${sso.server.prefix}") String domain) throws IOException {
printLog(request,ex);
response.sendRedirect("跳轉(zhuǎn)到無(wú)權(quán)限頁(yè)面地址");
}
//參數(shù)校驗(yàn)處理
@ExceptionHandler(value = {BindException.class})
public ResultBody BindExceptionHandler(BindException bindException){
List<ObjectError> errors = bindException.getBindingResult().getAllErrors();
//這個(gè)ResultBody是一個(gè)返回結(jié)果對(duì)象,這里需要返回json,里面包含了狀態(tài)碼和提示信息
return ResultBody.buildFailureResult(errors.get(0).getDefaultMessage());
}
//所有未捕獲的異常處理邏輯
@ExceptionHandler(value = {Exception.class})
public ResultBody exceptionHandler(HttpServletRequest request,Exception ex){
printLog(request,ex);
return ResultBody.buildExceptionResult();
}
//將請(qǐng)求參數(shù)和異常打印出來(lái),結(jié)合@slf4j注解
public void printLog(HttpServletRequest request,Exception ex){
String parameters = JsonHelper.toString(request.getParameterMap());
log.error("url>>>:{},params>>>:{} ,printLog>>>:{}",request.getRequestURL(),parameters,ex);
}
}
@RestControllerAdvice結(jié)合@Validation,可以對(duì)Bean進(jìn)行校驗(yàn),校驗(yàn)不通過(guò)會(huì)拋出BindException異常。通過(guò)注解可以少寫if-else代碼,判斷請(qǐng)求的接口參數(shù)是否為空,提高代碼的美觀性。例如:
//常規(guī)做法
if(StringUtils.isEmpty(ssoSystem.getSysCode())
//SSO做法
//在Controller請(qǐng)求方法上添加@Valid注解
@RequestMapping(value = "/add", method = RequestMethod.POST)
public ResultBody add(@Valid @RequestBody SsoSystem ssoSystem) {
}
//在需要處理的SsoSystem Bean的屬性上加@NotNull注解
@NotNull(message = "系統(tǒng)編號(hào)不能為空")
private String sysCode;
當(dāng)sysCode傳入?yún)?shù)為空時(shí),就會(huì)拋出BindException被全局的異常處理類,捕獲處理返回json格式的參數(shù):
{
"resultCode":2,
"resultMsg":"系統(tǒng)編號(hào)不能為空",
"resultData":null
}
SpringBoot1.5默認(rèn)使用內(nèi)嵌tomcat8.5版本,而原來(lái)SpringMVC的SSO部署在tomcat7上。tomcat的升級(jí)對(duì)這次改造影響最明顯的就是cookie。
tomcat8后采用的cookie校驗(yàn)協(xié)議是Rfc6265CookieProcessor。該協(xié)議要求domain的命名必須遵循以下規(guī)則:
由于是兩個(gè)不同的應(yīng)用,必然會(huì)有兩個(gè)不同的端口。不同的端口就會(huì)有跨域問(wèn)題,SSO采用的方式是通過(guò)nginx區(qū)分來(lái)自前后端的請(qǐng)求,反向代理請(qǐng)求對(duì)應(yīng)到不同的服務(wù)去。
swagger是后端接口展示的插件,通過(guò)修改攔截器代碼,mock登錄對(duì)象免登錄,直接訪問(wèn)接口進(jìn)行前后端的調(diào)試。在swagger插件上可以看到具體接口請(qǐng)求路徑和參數(shù)、參數(shù)是否必須、返回值、接口統(tǒng)計(jì)信息等。
接口統(tǒng)計(jì)信息
之前是通過(guò)SpringMvc的modeAndview方式跳轉(zhuǎn)的,現(xiàn)在做了兩種處理:
注意:老代碼跳轉(zhuǎn)采用的是通過(guò)SpringMvc在return的頁(yè)面路徑前加redirect的形式,如:return "redirect:index",這樣默認(rèn)會(huì)在return的URL后加jessionID。
特別需要注意代碼中的相關(guān)校驗(yàn)路徑的地方。比如在這次改造過(guò)程中路徑修改會(huì)影響以下幾個(gè)方面。
作者: 黃玲峰
來(lái)源:宜信技術(shù)學(xué)院
名稱欄目:一個(gè)項(xiàng)目的SpringCloud微服務(wù)改造過(guò)程
本文地址:http://aaarwkj.com/article24/pchgje.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站改版、外貿(mào)建站、網(wǎng)站導(dǎo)航、面包屑導(dǎo)航、Google、網(wǎng)站設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)