上一篇其實是簡單入門,全是廢話郁岩,實戰(zhàn)開始。友情提示:這篇文章有點長
目前沒有發(fā)現(xiàn)類似nodejs
里面init
功能的關于spring-boot
的工具,推薦還是去github上面clone
一個吧刹缝,方便快捷,也可使用start生成颈将,貢獻網(wǎng)址http://start.spring.io/梢夯。本文旨在這個目的構建一個倉庫供以后使用,目標:
- view層用
thymeleaf
替代jsp - 前端js框架采用
vuejs
- 添加國際化
- 修改banner
- DAO層采用JPA晴圾,配置數(shù)據(jù)庫
- 初始化數(shù)據(jù)
- 添加基礎權限認證并且能夠實現(xiàn)根據(jù)需要簡單定制
在上篇項目的基礎上修改目錄如下:
修改build.gradle
buildscript {
ext {
springBootVersion = '1.3.5.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'spring-boot'
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'war'
version = '0.1'
sourceCompatibility = 1.8
targetCompatibility = 1.8
[compileJava,compileTestJava]*.options*.encoding = 'UTF-8'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'org.springframework.boot:spring-boot-starter-security'
compile 'org.springframework.boot:spring-boot-starter-thymeleaf'
compile 'org.springframework.boot:spring-boot-devtools'
// compile 'mysql:mysql-connector-java:5.1.34'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
runtime 'org.hsqldb:hsqldb'
testCompile 'org.springframework.boot:spring-boot-starter-test'
// testRuntime 'org.hsqldb:hsqldb'
}
task wrapper(type: Wrapper) {
gradleVersion = '2.13'
}
//applicationDefaultJvmArgs = [ "-Xmx3550m","-Xms3550m","-Xmn2g","-Xss256k"]
使用hsqldb
只是用于方便測試颂砸,記得抹掉,環(huán)境采用jdk8
(因為最近在整react-native
不想切環(huán)境),application.properties
就一行代碼spring.profiles.active=dev
人乓,看字面意思應該就懂了梗醇,發(fā)布的時候記得改成對應名字即可比如pro
,開發(fā)環(huán)境配置文件application-dev.properties
:
# dev env
server.port=8090
# Thymeleaf view template config
# disable cache for dev
spring.thymeleaf.cache=false
# basic security
security.basic.enabled=false
# message resource config
# if true use system local and false to use baseName (e.g. 'messages')
spring.messages.fallback-to-system-locale=false
# datasource
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.hibernate.ddl-auto=update
#spring.datasource.continueOnError=true
#spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
#spring.jpa.database=MySQL
#spring.jpa.show-sql=true
#
#spring.datasource.url=jdbc:mysql://server:3306/dbname?useUnicode=true&characterEncoding=UTF-8&connectTimeout=60000&socketTimeout=60000&autoReconnect=true&autoReconnectForPools=true&failOverReadOnly=false
#spring.datasource.username=name
#spring.datasource.password=pass
#spring.datasource.driverClassName=com.mysql.jdbc.Driver
#spring.datasource.sqlScriptEncoding=utf-8
#
#spring.datasource.max-active=100
#spring.datasource.max-idle=8
#spring.datasource.min-idle=8
#spring.datasource.initial-size=30
#spring.datasource.validation-query=select 1
#spring.datasource.test-on-borrow=true
注釋的部分是舉例一般mysql
數(shù)據(jù)庫配置撒蟀,請不要忽視spring.datasource.url
后面的一堆參數(shù)叙谨,懂的朋友即懂,不懂的朋友一時半會也解釋不清保屯,大概意思就是保持數(shù)據(jù)庫連接池通暢不然會出現(xiàn)一個bug:跑得好好的項目不間斷時間莫名掛掉
手负,參數(shù)是需要修改的,請自行google姑尺。
templates/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="head">
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="stylesheet" />
<script src="http://cdnjs.cloudflare.com/ajax/libs/vue/1.0.24/vue.min.js"></script>
<title th:text="#{app.title}">Magneto</title>
</head>
<body>
<nav class="navbar navbar-inverse" th:fragment="header">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/" th:text="#{app.title}"></a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="/">Home <span class="sr-only">(current)</span></a></li>
<li><a href="/user/info">User</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div id="app" class="container">
<span th:text="#{app.title}"></span>
{{message}}
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Test Vue.js!'
}
})
</script>
</body>
</html>
使用vuejs
以及bootstrap
竟终,請自行更換。注意th:fragment
聲明模版塊切蟋,也可另新建文件比如layouts/head.html
统捶,舉例用法user/info.html
:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="index::head"></head>
<body>
<nav th:replace="index::header"></nav>
<div class="container">
<span th:text="${name}"></span>
</div>
</body>
</html>
先把次要的講完,banner.txt
可以替換彩蛋柄粹,好人做到底喘鸟,給你地址http://patorjk.com/software/taag,messages.properties
國際化app.title=Magneto
驻右,config/ServletInitializer.java
是給要war
的同學什黑,也可以在Application.java
中直接繼承SpringBootServletInitializer
,不然打出的war
包在tomcat底下是跑不起來的堪夭,而你根本不知道出錯在哪里愕把,這是個大坑,在spring-boot
以前的版本文檔里是沒有顯示的說明的森爽,坑了我很久恨豁。
數(shù)據(jù)庫持久層JPA
現(xiàn)在大部分同學用的是Mybatis
,而為什么我要在這里用上JPA
爬迟?我是這樣想的:Mybatis
的確對于可控的復雜的業(yè)務邏輯很擅長橘蜜,拋開其他不講,無論是效率還是從需求的角度來說的確比JPA
更加適用于現(xiàn)在復雜多變的項目業(yè)務需要雕旨,但是在中小項目里這種區(qū)別并不是那么的大扮匠,講道理捧请,現(xiàn)在NoSQL
怎么盛行凡涩,sql
存儲的壓力并沒有想象中那么大,如果真有那么大也不是Mybatis
或jpa
就可以解決的疹蛉,我寧愿花錢再買個服務器或者做做數(shù)據(jù)庫優(yōu)化活箕。考慮到使用spring-boot
可款,我覺得Mybatis
的設計邏輯并不契合育韩,相對來說克蚂,JPA
更加方便,所以選用JPA
做DAO
層的工作筋讨,當然了埃叭,如果你厭倦了hibernate
式的各種表連接的不痛快,集成Mybatis
也是很簡單的悉罕,參考這篇文章(這篇文章已經(jīng)夠長了遵岩,這里就不贅述了)
User
實體類:
@Entity
@DynamicUpdate
public class User implements Serializable, UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username;
private String password;
private String role;
...
UserRepo
繼承JPA
:
public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
通用service
接口ICommonService
public interface ICommonService<T> {
T save(T entity) throws Exception;
void delete(Long id) throws Exception;
void delete(T entity) throws Exception;
T findById(Long id);
T findBySample(T sample);
List<T> findAll();
List<T> findAll(T sample);
Page<T> findAll(PageRequest pageRequest);
Page<T> findAll(T sample, PageRequest pageRequest);
}
最終業(yè)務service
--UserService.java
@Service
public class UserService implements IUserService, UserDetailsService {
@Autowired
private UserRepo userRepo;
@Override
public User save(User entity) throws Exception {
return userRepo.save(entity);
}
@Override
public void delete(Long id) throws Exception {
userRepo.delete(id);
}
@Override
public void delete(User entity) throws Exception {
userRepo.delete(entity);
}
@Override
public User findById(Long id) {
return userRepo.findOne(id);
}
@Override
public User findBySample(User sample) {
return userRepo.findOne(whereSpec(sample));
}
@Override
public List<User> findAll() {
return userRepo.findAll();
}
@Override
public List<User> findAll(User sample) {
return userRepo.findAll(whereSpec(sample));
}
@Override
public Page<User> findAll(PageRequest pageRequest) {
return userRepo.findAll(pageRequest);
}
@Override
public Page<User> findAll(User sample, PageRequest pageRequest) {
return userRepo.findAll(whereSpec(sample), pageRequest);
}
private Specification<User> whereSpec(final User sample){
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (sample.getId()!=null){
predicates.add(cb.equal(root.<Long>get("id"), sample.getId()));
}
if (StringUtils.hasLength(sample.getUsername())){
predicates.add(cb.equal(root.<String>get("username"),sample.getUsername()));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User sample = new User();
sample.setUsername(username);
User user = findBySample(sample);
if( user == null ){
throw new UsernameNotFoundException(String.format("User with username=%s was not found", username));
}
return user;
}
}
whereSpec
方法是使用Specification
做通用封裝传睹,沒有使用泛型來更加通用,我覺得這樣已經(jīng)差不多了吧,要求不要太高蟆沫,看字面意思應該能懂是在做什么,不多說夺英,還是那句話--不懂的自己谷歌皿伺。大概生成的sql
可能是select u from user u where u.id=? and u.username=?
最難的權限部分
對于權限的詳細說明會在下面的文章里介紹,這里只取一般而言需要注冊登錄模塊的同學栈顷,集成這一部分是因為這是90%的項目都會使用的方式逆日,故為之。
spring-boot
采用spring-security
做權限的驗證工作萄凤,不了解的同學自己谷歌吧屏富。
基礎配置WebSecurityConfig.java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
// Configure spring security's authenticationManager with custom
// user details service
auth.userDetailsService(this.userService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
.and()
.httpBasic()
;
}
}
配置userDetailsService
將用戶管理轉交給我們自己,因為我覺得spring
自己的那套不一定適于用一般項目蛙卤,因為一般項目的User
表一般會和業(yè)務關系比較緊密狠半,設計初衷一定優(yōu)先考慮自己的業(yè)務而不是框架,HttpSecurity
做權限配置颤难,看字面意思應該就懂了神年,其他一般配置參考這篇文章。自己寫就需要碼更多代碼了行嗤,依次需要實現(xiàn)的接口如下:
UserService.java
繼承UserDetailsService
重寫loadUserByUsername
:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User sample = new User();
sample.setUsername(username);
User user = findBySample(sample);
if( user == null ){
throw new UsernameNotFoundException(String.format("User with username=%s was not found", username));
}
return user;
}
從代碼中不難看出已日,spring-security
內(nèi)部使用的user
是封裝過的UserDetails
,所以User.java
修改如下:
@Entity
@DynamicUpdate
public class User implements Serializable, UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username;
private String password;
private String role;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority(getRole()));
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
從代碼可以看出isAccountNonExpired
栅屏、isAccountNonLocked
飘千、isCredentialsNonExpired
、isEnabled
栈雳、getAuthorities
重寫這幾個方法就可以根據(jù)自己的業(yè)務邏輯做更細致的權限管理护奈,即是簡單定制。
光說不練不是好選手哥纫,實際運行效果圖:
登錄頁面:
我知道實際項目肯定不是這樣霉旗,這里是最基礎的登陸示范,自定義登錄頁面只需要在修改上文提到的
HttpSecurity
即可,并不難厌秒,就當家庭作業(yè)了读拆。權限user/info頁面
最后附上github地址https://github.com/kaenry/spring-boot-magneto.git