springboot
能夠說是如今作javaweb
開發最火的技術,我在基於springboot
搭建項目的過程當中,踩過很多坑,發現整合框架時並不是僅僅引入starter
那麼簡單。html
要作到簡單,易用,擴展性更好,還需作很多二次封裝,因而便寫了個基於springboot
的web項目腳手架,對一些經常使用的框架進行整合,並進行了簡單的二次封裝。前端
項目名baymax
取自動畫片超能陸戰隊裏面的大白,大白是一個醫護充氣機器人,但願這個項目你能像大白同樣貼心,能夠減小你的工做量。java
github https://github.com/zhaoguhong/baymaxmysql
web模塊是開發web項目必不可少的一個模塊git
maven 依賴github
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
對於先後端分離項目,推薦直接使用@RestController
註解
須要注意的是,不建議直接用RequstMapping註解而且不指定方法類型的寫法,推薦使用GetMaping
或者PostMaping
之類的註解web
@SpringBootApplication @RestController public class BaymaxApplication { public static void main(String[] args) { SpringApplication.run(BaymaxApplication.class, args); } @GetMapping("/test") public String test() { return "hello baymax"; } }
spring 對單元測試也提供了很好的支持ajax
maven 依賴redis
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
添加 @RunWith(SpringRunner.class)
和 @SpringBootTest
便可進行測試spring
@RunWith(SpringRunner.class) @SpringBootTest public class WebTest { }
對於Controller
層的接口,能夠直接用MockMvc
進行測試
@RunWith(SpringRunner.class) @SpringBootTest public class WebTest { @Autowired private WebApplicationContext context; private MockMvc mvc; @Before public void setUp() throws Exception { mvc = MockMvcBuilders.webAppContextSetup(context).build(); } @Test public void testValidation() throws Exception { mvc.perform(MockMvcRequestBuilders.get("/test")) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.content().string("hello baymax")); } }
actuator 是 spring 提供的應用監控功能,經常使用的配置項以下
# actuator端口 默認應用端口 management.server.port=8082 # 加載全部的端點 默認只加載 info,health management.endpoints.web.exposure.include=* # actuator路徑前綴,默認 /actuator management.endpoints.web.base-path=/actuator
lombok能夠在編譯期生成對應的java代碼,使代碼看起來更簡潔,同時減小開發工做量
用lombok後的實體類
@Data public class Demo { private Long id; private String userName; private Integer age; }
須要注意,@Data
包含 @ToString、@Getter、@Setter、@EqualsAndHashCode、@RequiredArgsConstructor
,RequiredArgsConstructor 並非無參構造,無參構造的註解是NoArgsConstructor
RequiredArgsConstructor
會生成 會生成一個包含常量(final),和標識了@NotNull的變量 的構造方法
把表中的基礎字段抽離出來一個BaseEntity,全部的實體類都繼承該類
/** * 實體類基礎類 */ @Data public abstract class BaseEntity implements Serializable { /** * 主鍵id */ private Long id; /** * 建立人 */ private Long createdBy; /** * 建立時間 */ private Date createdTime; /** * 更新人 */ private Long updatedBy; /** * 更新時間 */ private Date updatedTime; /** * 是否刪除 */ private Integer isDeleted; }
先後端分離項目基本上都是ajax調用,因此封裝一個統一的返回對象有利於前端統一處理
/** * 用於 ajax 請求的響應工具類 */ @Data public class ResponseResult<T> { // 未登陸 public static final String UN_LOGIN_CODE = "401"; // 操做失敗 public static final String ERROR_CODE = "400"; // 服務器內部執行錯誤 public static final String UNKNOWN_ERROR_CODE = "500"; // 操做成功 public static final String SUCCESS_CODE = "200"; // 響應信息 private String msg; // 響應code private String code; // 操做成功,響應數據 private T data; public ResponseResult(String code, String msg, T data) { this.msg = msg; this.code = code; this.data = data; } }
返回給前端的值用ResponseResult
包裝一下
/** * 測試成功的 ResponseResult */ @GetMapping("/successResult") public ResponseResult<List<Demo>> test() { List<Demo> demos = demoMapper.getDemos(); return ResponseResult.success(demos); } /** * 測試失敗的 ResponseResult */ @GetMapping("/errorResult") public ResponseResult<List<Demo>> demo() { return ResponseResult.error("操做失敗"); }
spring其實封裝了ResponseEntity 處理響應,ResponseEntity 包含 狀態碼,頭部信息,響應體 三部分
/** * 測試請求成功 * @return */ @GetMapping("/responseEntity") public ResponseEntity<String> responseEntity() { return ResponseEntity.ok("請求成功"); } /** * 測試服務器內部錯誤 * @return */ @GetMapping("/InternalServerError") public ResponseEntity<String> responseEntityerror() { return new ResponseEntity<>("出錯了", HttpStatus.INTERNAL_SERVER_ERROR); }
爲了方便異常處理,定義一套異常體系,BaymaxException 作爲全部自定義異常的父類
// 項目全部自定義異常的父類 public class BaymaxException extends RuntimeException // 業務異常 該異常的信息會返回給用戶 public class BusinessException extends BaymaxException // 用戶未登陸異常 public class NoneLoginException extends BaymaxException
對全部的異常處理後再返回給前端
@RestControllerAdvice public class GlobalControllerExceptionHandler { /** * 業務異常 */ @ExceptionHandler(value = {BusinessException.class}) public ResponseResult<?> handleBusinessException(BusinessException ex) { String msg = ex.getMessage(); if (StringUtils.isBlank(msg)) { msg = "操做失敗"; } return ResponseResult.error(msg); } /** * 處理未登陸異常 */ @ExceptionHandler(value = {NoneLoginException.class}) public ResponseResult<?> handleNoneLoginException(NoneLoginException ex) { return ResponseResult.unLogin(); }
對於未知的異常,保存到數據庫,方便後續排錯
須要說明是的,若是項目訪問量比較大,推薦用 ELK 這種成熟的日誌分析系統,不推薦日誌保存到關係型數據庫
@Autowired private ExceptionLogMapper exceptionLogMapper; /** * 處理未知的錯誤 */ @ExceptionHandler(value = {Exception.class}) public ResponseResult<Long> handleunknownException(Exception ex) { ExceptionLog log = new ExceptionLog(new Date(), ExceptionUtils.getStackTrace(ex)); exceptionLogMapper.insert(log); ResponseResult<Long> result = ResponseResult.unknownError("服務器異常:" + log.getId()); result.setData(log.getId()); return result; }
對外開一個異常日誌查詢接口/anon/exception/{異常日誌id}
,方便查詢
@RestController @RequestMapping("/anon/exception") public class ExceptionController { @Autowired private ExceptionLogMapper exceptionLogMapper; @GetMapping(value = "/{id}") public String getDemo(@PathVariable(value = "id") Long id) { return exceptionLogMapper.selectByPrimaryKey(id).getException(); } }
JSR 303定義了一系列的 Bean Validation 規範,Hibernate Validator 是 Bean Validation 的實現,並進行了擴展
spring boot 使用 也很是方便
public class Demo extends BaseEntity{ @NotBlank(message = "用戶名不容許爲空") private String userName; @NotBlank private String title; @NotNull private Integer age; }
參數前面添加@Valid註解便可
@PostMapping("/add") public ResponseResult<String> add(@RequestBody @Valid Demo demo) { demoMapper.insert(demo); return ResponseResult.success(); }
對於校驗結果能夠每一個方法單獨處理,若是不處理,會拋出有異常,能夠對校驗的異常作全局處理
在 GlobalControllerExceptionHandler
添加
/** * 處理校驗異常 */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseResult<?> handleValidationException(MethodArgumentNotValidException ex) { BindingResult result = ex.getBindingResult(); if (result.hasErrors()) { StringJoiner joiner = new StringJoiner(","); List<ObjectError> errors = result.getAllErrors(); errors.forEach(error -> { FieldError fieldError = (FieldError) error; joiner.add(fieldError.getField() + " " + error.getDefaultMessage()); }); return ResponseResult.error(joiner.toString()); } else { return ResponseResult.error("操做失敗"); } }
spring boot 的默認使用的日誌是logback
,web模塊依賴的有日誌 starter
,因此這裏不用再引入依賴,詳細配置
Actuator 組件提供了日誌相關接口,能夠查詢日誌級別或者動態修改日誌級別
// 查看全部包/類的日誌級別 /actuator/loggers // 查看指定包/類日誌級別 get 請求 /actuator/loggers/com.zhaoguhong.baymax.demo.controller.DemoController //修改日誌級別 post 請求 參數 {"configuredLevel":"debug"} /actuator/loggers/com.zhaoguhong.baymax.demo.controller.DemoController
添加一個日誌切面,方便記錄方法執行的入參和出參
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface LogAspect { /** * 日誌描述 */ String value() default ""; /** * 日誌級別 */ String level() default "INFO"; }
使用時直接添加到方法上便可
swagger 是一個很好用的文檔生成工具
maven 依賴
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency>
相關配置
@Configuration @EnableSwagger2 public class SwaggerConfig { @Value("${swagger.enable:false}") private boolean swaggerEnable; //文檔訪問前綴 public static final String ACCESS_PREFIX = "/swagger-resources/**,/swagger-ui.html**,/webjars/**,/v2/**"; @Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) // 設置是否開啓swagger,生產環境關閉 .enable(swaggerEnable) .select() // 當前包路徑 .apis(RequestHandlerSelectors.basePackage("com.zhaoguhong.baymax")) .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build(); } // 構建api文檔的詳細信息 private ApiInfo apiInfo() { return new ApiInfoBuilder() // 頁面標題 .title("接口文檔") // 建立人 .contact(new Contact("孤鴻", "https://github.com/zhaoguhong/baymax", "")) // 版本號 .version("1.0") // 描述 .description("大白的接口文檔") .build(); } }
爲文檔加一個開關
#是否開啓swagger文檔,生產環境關閉 swagger.enable=true
而後就能夠愉快的在寫代碼的同時寫文檔了
@PostMapping("/add") @ApiOperation(value = "新增 demo") public ResponseResult<String> add(@RequestBody @Valid Demo demo) { demoMapper.insert(demo); return ResponseResult.success(); }
@ApiModel("示例") public class Demo extends BaseEntity{ @ApiModelProperty("用戶名") private String userName; @ApiModelProperty("標題") private String title; @ApiModelProperty("年齡") private Integer age; }
訪問 localhost:8080/swagger-ui.html
就能夠看到效果了
springboot1.X的數據庫鏈接池是tomcat鏈接池,springboot2默認的數據庫鏈接池由Tomcat換成 HikariCP,HikariCP
是一個高性能的JDBC鏈接池,號稱最快的鏈接池
Druid 是阿里巴巴數據庫事業部出品,爲監控而生的數據庫鏈接池,這裏選取Druid
做爲項目的數據庫鏈接池
maven 依賴
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency>
設置用戶名密碼
spring.datasource.druid.stat-view-servlet.login-username=admin spring.datasource.druid.stat-view-servlet.login-password=123456
而後就能夠訪問 localhost:8080/druid
看監控信息了
maven 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
spring對jdbc作了封裝和抽象,最經常使用的是 jdbcTemplate
和 NamedParameterJdbcTemplate
兩個類,前者使用佔位符,後者使用命名參數,我在jdbcDao
作了一層簡單的封裝,提供統一的對外接口
jdbcDao
主要方法以下:
// 佔位符 find(String sql, Object... args) // 佔位符,手動指定映射mapper find(String sql, Object[] args, RowMapper<T> rowMapper) // 命名參數 find(String sql, Map<String, ?> paramMap) // 命名參數,手動指定映射mapper find(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper) //springjdbc 原queryForMap方法,若是沒查詢到會拋異常,此處若是沒有查詢到,返回null queryForMap(String sql, Object... args) queryForMap(String sql, Map<String, ?> paramMap) // 分頁查詢 find(Page<T> page, String sql, Map<String, ?> parameters, RowMapper<?> mapper) // 分頁查詢 find(Page<T> page, String sql, RowMapper<T> mapper, Object... args)
jpa
是 java
持久化的標準,spring data jpa
使操做數據庫變得更方便,須要說明的 spring data jpa
自己並非jpa的實現,它默認使用的 provider
是 hibernate
maven 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
我把通用的方法抽取了出來了,封裝了一個BaseRepository,使用時,直接繼承該接口便可
public interface DemoRepository extends BaseRepository<Demo> { }
BaseRepository
主要方法以下
// 新增,會對建立時間,建立人自動賦值 void saveEntity(T entity) // 更新,會對更新時間,更新人自動賦值 void updateEntity(T entity) // 邏輯刪除 void deleteEntity(T entity) // 批量保存 void saveEntites(Collection<T> entitys) // 批量更新 void updateEntites(Collection<T> entitys) // 批量邏輯刪除 void deleteEntites(Collection<T> entitys) // 根據id獲取實體,會過濾掉邏輯刪除的 T getById(Long id)
若是想使用傳統的sql形式,能夠直接使用JpaDao,爲了方便使用,我儘可能使JpaDao和JdbcDao的接口保持統一
JpaDao
主要方法以下
// 佔位符 例如:from Demo where id =? find(String sql, Object... args) // 命名參數 find(String sql, Map<String, ?> paramMap) // 分頁 find(Page<T> page, String hql, Map<String, ?> parameters) // 分頁 find(Page<T> page, String hql, Object... parameters)
Redis 是性能極佳key-value數據庫,經常使用來作緩存,
java 中經常使用的客戶端有 Jedis
和 Lettuce
, spring data redis
是基於 Lettuce
作的二次封裝
maven 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
爲了在redis讀起來更方便,更改序列化方式
@Configuration public class RedisConfig { /** * 設置序列化方式 */ @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer()); return redisTemplate; } @Bean public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() { ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 將類名稱序列化到json串中 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class); jackson2JsonRedisSerializer.setObjectMapper(om); return jackson2JsonRedisSerializer; } }
spring cache 抽象出了一組緩存接口,經過註解的方式使用,能夠很方便的配置其具體實現,詳細配置
這裏使用redis作爲緩存 provider, 默認的value序列化方式是JDK,爲方便查看,能夠修改成用json序列化
有時會有設置redis key
前綴的需求,默認是這樣的
static CacheKeyPrefix simple() { // 在 cacheName 後面添加 "::" return name -> name + "::"; }
spring boot 提供的有配置前綴的屬性
spring.cache.redis.key-prefix= # Key prefix.
但這是一個坑,這樣寫的效果實際這樣的,會把cacheName
幹掉,顯然不是咱們想要的
CacheKeyPrefix cacheKeyPrefix = (cacheName) -> prefix;
咱們想要的是把前綴加在最前面,保留cacheName
CacheKeyPrefix cacheKeyPrefix = (cacheName) -> keyPrefix + "::" + cacheName + "::";
參考org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
,聲明 RedisCacheManager
@Configuration @EnableConfigurationProperties(CacheProperties.class) @EnableCaching public class SpringCacheConfig { @Autowired private CacheProperties cacheProperties; @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheManagerBuilder builder = RedisCacheManager .builder(redisConnectionFactory) .cacheDefaults(determineConfiguration()); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { builder.initialCacheNames(new LinkedHashSet<>(cacheNames)); } return builder.build(); } private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration() { Redis redisProperties = this.cacheProperties.getRedis(); org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration .defaultCacheConfig(); // 修改序列化爲json config = config.serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(jackson2JsonRedisSerializer())); if (redisProperties.getTimeToLive() != null) { config = config.entryTtl(redisProperties.getTimeToLive()); } if (redisProperties.getKeyPrefix() != null) { // 重寫前綴拼接方式 config = config.computePrefixWith((cacheName) -> redisProperties.getKeyPrefix() + "::" + cacheName + "::"); } if (!redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); } if (!redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); } return config; } // 省略 jackson2JsonRedisSerializer() }
MongoDB 是文檔型數據庫,使用 spring data mogodb
能夠很方便對mogodb進行操做
maven 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>
sprign data mogodb
提供了 MongoTemplate
對mogodb進行操做,我在該類的基礎上又擴展了一下,能夠自定義本身的方法
@Configuration public class MongoDbConfig { /** * 擴展本身的mogoTemplate */ @Bean public MyMongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) { return new MyMongoTemplate(mongoDbFactory, converter); } }
我擴展了一個分頁的方法,能夠根據本身的狀況擴展其它方法
// mogodb 分頁 public <T> Page<T> find(Page<T> page, Query query, Class<T> entityClass)
maven 依賴
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency>
經常使用配置以下
# 配置文件位置 classpath後面要加*,否則後面通配符無論用 mybatis.mapperLocations=classpath*:com/zhaoguhong/baymax/*/mapper/*Mapper.xml # 開啓駝峯命名自動映射 mybatis.configuration.map-underscore-to-camel-case=true
dao層直接用接口,簡潔,方便
@Mapper public interface DemoMapper { /** * 註解方式 */ @Select("SELECT * FROM demo WHERE user_name = #{userName}") List<Demo> findByUserName(@Param("userName") String userName); /** * xml方式 */ List<Demo> getDemos(); }
須要注意,xml的namespace必須是mapper類的全限定名,這樣才能夠創建dao接口與xml的關係
<mapper namespace="com.zhaoguhong.baymax.demo.dao.DemoMapper"> <select id="getDemos" resultType="com.zhaoguhong.baymax.demo.entity.Demo"> select * from demo </select> </mapper>
mybatis 的單表增刪改查寫起來很囉嗦,通用mapper很好的解決了這個問題
maven 依賴
<dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>1.2.4</version> </dependency>
經常使用配置以下
# 通用mapper 多個接口時用逗號隔開 mapper.mappers=com.zhaoguhong.baymax.mybatis.MyMapper mapper.not-empty=false mapper.identity=MYSQL
定義本身的 MyMapper
方便擴展,MyMapper
接口 中封裝了通用的方法,和jpa
的BaseRepository
相似,這裏再也不贅述
聲明mapper
須要加Mapper
註解,還稍顯麻煩,能夠用掃描的方式
@Configuration @tk.mybatis.spring.annotation.MapperScan(basePackages = "com.zhaoguhong.baymax.**.dao") public class MybatisConfig { }
使用時直接繼承MyMapper接口便可
public interface DemoMapper extends MyMapper<Demo>{ }
pagehelper是一個很好用的mybatis的分頁插件
maven 依賴
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.3</version> </dependency>
經常使用配置以下
#pagehelper #指定數據庫類型 pagehelper.helperDialect=mysql #分頁合理化參數 pagehelper.reasonable=true
使用分頁
PageHelper.startPage(1, 5); List<Demo> demos = demoMapper.selectAll();
pagehelper 還有好多玩法,能夠參考這裏
pagehelper 雖然好用,但項目中有本身的分頁對象,因此單獨寫一個攔截器,把他們整合到一塊兒
這個地方要特別注意插件的順序不要搞錯
@Configuration // 設置mapper掃描的包 @tk.mybatis.spring.annotation.MapperScan(basePackages = "com.zhaoguhong.baymax.**.dao") @Slf4j public class MybatisConfig { @Autowired private List<SqlSessionFactory> sqlSessionFactoryList; /** * 添加自定義的分頁插件,pageHelper 的分頁插件PageInterceptor是用@PostConstruct添加的,自定義的應該在其後面添加 * 真正執行時順序是反過來,先執行MyPageInterceptor,再執行 PageInterceptor * * 因此要保證 PageHelperAutoConfiguration 先執行 */ @Autowired public void addPageInterceptor(PageHelperAutoConfiguration pageHelperAutoConfiguration) { MyPageInterceptor interceptor = new MyPageInterceptor(); for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) { sqlSessionFactory.getConfiguration().addInterceptor(interceptor); log.info("註冊自定義分頁插件成功"); } } }
在使用時只須要傳入自定義的分頁對象便可
Page<Demo> page = new Page<>(1, 10); demos = demoMapper.getDemos(page);
安全模塊是項目中必不可少的一環,經常使用的安全框架有shiro
和spring security
,shiro相對輕量級,使用很是靈活,spring security
相對功能更完善,並且能夠和spring 無縫銜接。這裏選取spring security
作爲安全框架
maven 依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
繼承WebSecurityConfigurerAdapter類就能夠進行配置了
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SecurityProperties securityProperties; @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // 設置能夠匿名訪問的url .antMatchers(securityProperties.getAnonymousArray()).permitAll() // 其它全部請求都要認證 .anyRequest().authenticated() .and() .formLogin() // 自定義登陸頁 .loginPage(securityProperties.getLoginPage()) // 自定義登陸請求路徑 .loginProcessingUrl(securityProperties.getLoginProcessingUrl()) .permitAll() .and() .logout() .permitAll(); // 禁用CSRF http.csrf().disable(); } @Override public void configure(WebSecurity web) throws Exception { String[] ignoringArray = securityProperties.getIgnoringArray(); // 忽略的資源,直接跳過spring security權限校驗 if (ArrayUtils.isNotEmpty(ignoringArray)) { web.ignoring().antMatchers(ignoringArray); } } /** * * 聲明密碼加密方式 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) // 配置密碼加密方式,也能夠不指定,默認就是BCryptPasswordEncoder .passwordEncoder(passwordEncoder()); } }
實現UserDetailsService
接口,定義本身的UserDetailsService
@Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override @Transactional public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsernameAndIsDeleted(username, SystemConstants.UN_DELETED); if (user == null) { throw new UsernameNotFoundException("username Not Found"); } return user; } }
配置項
#匿名訪問的url,多個用逗號分隔 security.anonymous=/test #忽略的資源,直接跳過spring security權限校驗,通常是用作靜態資源,多個用逗號分隔 security.ignoring=/static/**,/images/** #自定義登陸頁面 security.loginPage=/login.html #自定義登陸請求路徑 security.loginProcessingUrl=/login
爲了方面使用,封裝一個上下文對象 ContextHolder
// 獲取當前線程HttpServletRequest getRequest() // 獲取當前線程HttpServletResponse getResponse() // 獲取當前HttpSession getHttpSession() setSessionAttribute(String key, Serializable entity) getSessionAttribute(String key) setRequestAttribute(String key, Object entity) getRequestAttribute(String key) // 獲取 ApplicationContext getApplicationContext() //根據beanId獲取spring bean getBean(String beanId) // 獲取當前登陸用戶 getLoginUser() // 獲取當前登陸用戶 id getLoginUserId() // 獲取當前登陸用戶 爲空則拋出異常 getRequiredLoginUser() // 獲取當前登陸用戶id, 爲空則拋出異常 getRequiredLoginUserId()
單點登陸系統(SSO,single sign-on)指的的,多個系統,共用一套用戶體系,只要登陸其中一個系統,訪問其餘系統不須要從新登陸
CAS(Central Authentication Service)是耶魯大學的一個開源項目,是比較流行的單獨登陸解決方案。在CAS中,只負責登陸的系統被稱爲服務端,其它全部系統被稱爲客戶端
跳轉到sso認證中心進行統一登出,cas 會通知全部客戶端進行登出
maven 依賴
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-cas</artifactId> </dependency>
spring security 對 cas 作了很好的封裝,在使用的過程當中,只須要定義好對應的登陸fifter和登出fifter便可,整合cas的代碼我寫在了WebSecurityConfig
類中
相關屬性配置
#是否開啓單點登陸 cas.enable = true #服務端地址 cas.serverUrl= #客戶端地址 cas.clientUrl= #登陸地址 cas.loginUrl=${cas.serverUrl}/login #服務端登出地址 cas.serverLogoutUrl=${cas.serverUrl}/logout #單點登陸成功回調地址 cas.clientCasUrl=${cas.clientUrl}/login/cas
由於要使用freeMarker解析模板,因此也要引入freeMarker依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> </dependency>
相關配置
#郵件 #設置郵箱主機,163郵箱爲smtp.163.com,qq爲smtp.qq.com spring.mail.host = smtp.163.com spring.mail.username = #受權碼 spring.mail.password = #默認的郵件發送人 mail.sender =
封裝一個 MailService
// 根據相關配置發送郵件 void sendMail(MailModel mailModel); // 發送簡單的郵件 void sendSimleMail(String to, String subject, String content); // 發送html格式的郵件 void sendHtmlMail(String to, String subject, String content); // 發送帶附件的郵件 void sendAttachmentMail(String to, String subject, String content, String path); // 發送帶附件的html格式郵件 void sendAttachmentHtmlMail(String to, String subject, String content, String path); // 根據模版發送簡單郵件 void sendMailByTemplate(String to, String subject, String templateName, Map<String, Object> params); }
設置阿里雲鏡像,加快下載速度
修改 setting.xml,在 mirrors 節點上,添加
<mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror>
也能夠在項目 pom.xml 文件添加 ,僅當前項目有效
<repositories> <repository> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> <updatePolicy>daily</updatePolicy> </releases> <snapshots> <enabled>false</enabled> <checksumPolicy>warn</checksumPolicy> </snapshots> <layout>default</layout> </repository> </repositories>
springboot
自動配置的源碼,再作定製化處理