前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >2. spring-boot+thymeleaf(+vuejs)

2. spring-boot+thymeleaf(+vuejs)

作者头像
MasterVin
发布2018-08-30 10:20:56
1.5K0
发布2018-08-30 10:20:56
举报

上一篇其实是简单入门,全是废话,实战开始。友情提示:这篇文章有点长

目前没有发现类似nodejs里面init功能的关于spring-boot的工具,推荐还是去github上面clone一个吧,方便快捷,也可使用start生成,贡献网址http://start.spring.io/。本文旨在这个目的构建一个仓库供以后使用,目标:

  • view层用thymeleaf替代jsp
  • 前端js框架采用vuejs
  • 添加国际化
  • 修改banner
  • DAO层采用JPA,配置数据库
  • 初始化数据
  • 添加基础权限认证并且能够实现根据需要简单定制

在上篇项目的基础上修改目录如下:

Paste_Image.png

修改build.gradle

代码语言:javascript
复制
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只是用于方便测试,记得抹掉,环境采用jdk8(因为最近在整react-native不想切环境),application.properties就一行代码spring.profiles.active=dev,看字面意思应该就懂了,发布的时候记得改成对应名字即可比如pro,开发环境配置文件application-dev.properties

代码语言:javascript
复制
# 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数据库配置,请不要忽视spring.datasource.url后面的一堆参数,懂的朋友即懂,不懂的朋友一时半会也解释不清,大概意思就是保持数据库连接池通畅不然会出现一个bug:跑得好好的项目不间断时间莫名挂掉,参数是需要修改的,请自行google。 templates/index.html

代码语言:javascript
复制
<!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" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"/>
    <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

代码语言:javascript
复制
<!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=Magnetoconfig/ServletInitializer.java是给要war的同学,也可以在Application.java中直接继承SpringBootServletInitializer,不然打出的war包在tomcat底下是跑不起来的,而你根本不知道出错在哪里,这是个大坑,在spring-boot以前的版本文档里是没有显示的说明的,坑了我很久。


数据库持久层JPA

现在大部分同学用的是Mybatis,而为什么我要在这里用上JPA?我是这样想的:Mybatis的确对于可控的复杂的业务逻辑很擅长,抛开其他不讲,无论是效率还是从需求的角度来说的确比JPA更加适用于现在复杂多变的项目业务需要,但是在中小项目里这种区别并不是那么的大,讲道理,现在NoSQL怎么盛行,sql存储的压力并没有想象中那么大,如果真有那么大也不是Mybatisjpa就可以解决的,我宁愿花钱再买个服务器或者做做数据库优化。考虑到使用spring-boot,我觉得Mybatis的设计逻辑并不契合,相对来说,JPA更加方便,所以选用JPADAO层的工作,当然了,如果你厌倦了hibernate式的各种表连接的不痛快,集成Mybatis也是很简单的,参考这篇文章(这篇文章已经够长了,这里就不赘述了) User实体类:

代码语言:javascript
复制
@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

代码语言:javascript
复制
public interface UserRepo extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}

通用service接口ICommonService

代码语言:javascript
复制
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);
}

最终业务service--UserService.java

代码语言:javascript
复制
@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做通用封装,没有使用泛型来更加通用,我觉得这样已经差不多了吧,要求不要太高,看字面意思应该能懂是在做什么,不多说,还是那句话--不懂的自己谷歌。大概生成的sql可能是select u from user u where u.id=? and u.username=?


最难的权限部分

对于权限的详细说明会在下面的文章里介绍,这里只取一般而言需要注册登录模块的同学,集成这一部分是因为这是90%的项目都会使用的方式,故为之。 spring-boot采用spring-security做权限的验证工作,不了解的同学自己谷歌吧。 基础配置WebSecurityConfig.java

代码语言:javascript
复制
@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表一般会和业务关系比较紧密,设计初衷一定优先考虑自己的业务而不是框架,HttpSecurity做权限配置,看字面意思应该就懂了,其他一般配置参考这篇文章。自己写就需要码更多代码了,依次需要实现的接口如下: UserService.java继承UserDetailsService重写loadUserByUsername:

代码语言:javascript
复制
@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内部使用的user是封装过的UserDetails,所以User.java修改如下:

代码语言:javascript
复制
@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;
    }
}

从代码可以看出isAccountNonExpiredisAccountNonLockedisCredentialsNonExpiredisEnabledgetAuthorities重写这几个方法就可以根据自己的业务逻辑做更细致的权限管理,即是简单定制。 光说不练不是好选手,实际运行效果图:

Paste_Image.png

登录页面:

Paste_Image.png

我知道实际项目肯定不是这样,这里是最基础的登陆示范,自定义登录页面只需要在修改上文提到的HttpSecurity即可,并不难,就当家庭作业了。 权限user/info页面

Paste_Image.png

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.06.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据库持久层JPA
  • 最难的权限部分
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档