2. spring-boot+thymeleaf(+vuejs)

上一篇其實是簡單入門,全是廢話郁岩,實戰(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ù)需要簡單定制

在上篇項目的基礎上修改目錄如下:


Paste_Image.png

修改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/taagmessages.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存儲的壓力并沒有想象中那么大,如果真有那么大也不是Mybatisjpa就可以解決的疹蛉,我寧愿花錢再買個服務器或者做做數(shù)據(jù)庫優(yōu)化活箕。考慮到使用spring-boot可款,我覺得Mybatis的設計邏輯并不契合育韩,相對來說克蚂,JPA更加方便,所以選用JPADAO層的工作筋讨,當然了埃叭,如果你厭倦了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飘千、isCredentialsNonExpiredisEnabled栈雳、getAuthorities重寫這幾個方法就可以根據(jù)自己的業(yè)務邏輯做更細致的權限管理护奈,即是簡單定制。
光說不練不是好選手哥纫,實際運行效果圖:

Paste_Image.png

登錄頁面:

Paste_Image.png

我知道實際項目肯定不是這樣霉旗,這里是最基礎的登陸示范,自定義登錄頁面只需要在修改上文提到的HttpSecurity即可,并不難厌秒,就當家庭作業(yè)了读拆。
權限user/info頁面
Paste_Image.png

最后附上github地址https://github.com/kaenry/spring-boot-magneto.git

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鸵闪,隨后出現(xiàn)的幾起案子檐晕,更是在濱河造成了極大的恐慌,老刑警劉巖蚌讼,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棉姐,死亡現(xiàn)場離奇詭異,居然都是意外死亡啦逆,警方通過查閱死者的電腦和手機伞矩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夏志,“玉大人乃坤,你說我怎么就攤上這事」得铮” “怎么了湿诊?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瘦材。 經(jīng)常有香客問我厅须,道長,這世上最難降的妖魔是什么食棕? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任朗和,我火速辦了婚禮,結果婚禮上簿晓,老公的妹妹穿的比我還像新娘眶拉。我一直安慰自己,他們只是感情好憔儿,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布忆植。 她就那樣靜靜地躺著,像睡著了一般谒臼。 火紅的嫁衣襯著肌膚如雪朝刊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天蜈缤,我揣著相機與錄音拾氓,去河邊找鬼。 笑死劫樟,一個胖子當著我的面吹牛痪枫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叠艳,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼奶陈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了附较?” 一聲冷哼從身側響起吃粒,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拒课,沒想到半個月后徐勃,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡早像,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年僻肖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卢鹦。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡臀脏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冀自,到底是詐尸還是另有隱情揉稚,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布熬粗,位于F島的核電站搀玖,受9級特大地震影響,放射性物質發(fā)生泄漏驻呐。R本人自食惡果不足惜灌诅,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望含末。 院中可真熱鬧延塑,春花似錦、人聲如沸答渔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沼撕。三九已至宋雏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間务豺,已是汗流浹背磨总。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留笼沥,地道東北人蚪燕。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓娶牌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親馆纳。 傳聞我的和親對象是個殘疾皇子诗良,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容