springbootdemo(spring-boot + velociyt + mybatis + shiro)

端午节前一周重新研究了一波当前开发系统的架构,参考诸多文档资料写了一个springboot+veloctiy+mybatis+shiro的demo,并将学习过程记录在此。后续若有时间,将会对此demo丰富升级,将后续学习的东西都填充入本demo。
https://github.com/2500284064/springboot-demo

按照时间顺序记录demo开发过程:

springboot+velocity集成

1、构建合适的文件目录
目录格式:
--springdemo
    --src
        --main
            --java
                --com.example
                    --config           //存放配置类
                    --controller       //控制器
                    --db               //数据库entity 和mapper接口
                    --service          //服务层
                    --Starter.java     //启动类
            --resources
                --assets               //静态资源
                --mapper               //mybatis mapper.xml目录
                --templates            //veloctiy模板
                --application.yml
    --pom.xml

文件目录层次如上述格式所示,其中pom.xml和src同在根目录之下,src目录至java目录和 resources目录是spring代码已经写死的目录结构。而java目录下的目录结构可随意构建,不过个人习惯将项目的groupId作为java下的包目录名。建议将启动类放在最外层包的根目录下,原因是spring默认将启动类所属的包作为扫描的目录,当然也可以自己配置扫描包目录。

此外,采用velocity模板语言时,默认的vm文件根目录为resources/templates。此目录可在application.yml中配置。(application.yml是项目的配置文件,默认在resources根目录下)

ps:后续将com.example目录称之为java根目录,resources称之为资源根目录

2、pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>

<!--spring-boot-starter-parent 采取保守策略,可以修改为最新的jdk-->
<properties>
    <java.version>1.8</java.version>
</properties>

<!--可提供诸多默认配置,支持属性覆盖-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.3.RELEASE</version>
</parent>

<!-- Additional lines to be added here... -->

<dependencies>
    <!--提供web支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--velocity支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-velocity</artifactId>
    </dependency>

</dependencies>

<build>
    <plugins>
        <!--编译插件,打包成可执行jar-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <fork>true</fork>
            </configuration>
            <version>1.4.0.RELEASE</version>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

springboot+velocity的项目搭建只需要上述的依赖。依赖的作用已经在注解中说明。

3、配置application.yml
server:
    port: 8001      #端口号
    session:
      timeout: 6000 #second    

spring:
    aop:
      auto: true
      proxy-target-class: true

    velocity:
      cache: false
      charset: UTF-8
      expose-spring-macro-helpers: true
      properties:                       #配置宏所在文件,自行google
        velocimacro.library: macro/macro.vm
        velocimacro.library.autoreload: true
      resource-loader-path: classpath:/templates/   #vm文件扫描目录
      suffix: .vm
4、配置启动类和velocityConfig配置类

之前已经说过,建议启动类在java根目录下,启动类的写法如无特殊需求其实很固定:

@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackages = "com.example")    //此时由于主类在根目录下,所以默认的扫描目录即为com.example目录
//上面三个注解可用@SpringBootApplication代替

//@MapperScan(value = "com.example.db.mapper")        此注解被MapperScannerConfig类替代
@EnableTransactionManagement(proxyTargetClass = true)  //支持事务管理
public class ApiStarter {

    public static void main(String[] args) throws Exception {
        SpringApplication app = new SpringApplication(ApiStarter.class);
        //此处可对app进行自定义设置
        app.run(args);
    }
}   

velocityConfig配置类用于配置VelocityLayoutViewResolver类解析velocity模板。同时可配置默认布局,
所谓布局,是指项目中后台返回的页面将会作为html嵌入设置的布局页面中引用$screen_content的位置,采用布局的好处的可以统一诸如外部js,css和页面头部尾部等公用的部分。此外布局可在后台返回时自主设置。

@Configuration
public class VelocityConfig {
    /*name property is chooseable*/
    /*配置布局,默认布局文件夹为layout, 且若不使用VelocityProperties 参数则无法使用布局*/
    @Bean(name = "velocityResolver")
    public VelocityLayoutViewResolver velocityLayoutViewResolver(VelocityProperties properties){
        VelocityLayoutViewResolver resolver = new VelocityLayoutViewResolver();
        properties.applyToViewResolver(resolver);
        resolver.setLayoutUrl("layout/default.vm");
        return resolver;
    }
}
5、撰写基本Controller和login页面(login页面后面集成shiro用作测试录)
/*IndexController:*/
@Controller
public class IndexController {
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login(){
        return "login";
    }
}

login.vm: 登录页面,后续用作shiro的登录测试页面

<script>
$(function () {
    $("#signIn").on("click", function () {
        $("#loginForm").submit();
    })
})
</script>
<div class="page-content" style="background-image: url('/assets/images/earth-banner.jpg'); background-repeat: no-repeat; height: 415px">
    <div class="login-panel">
        <form id="loginForm" action="/login" method="post">
            <div class="errorMsg">$!{errorMsg}</div>
            <input type="text" name="userName" value="" placeholder="账号"/>
            <input type="password" name="password" value="" placeholder="密码"/>
            <input type="button" class="btn btn-primary btn-sm" id="signIn" value="登录"/>
        </form>
    </div>
</div>

至此就可以运行测试了:启动项目后,在浏览器访问:localhost:8001/login, 若返回登录页面则成功。

集成mybatis

ORM层目前使用的较多的技术有hibernate何mybatis两种,mybatis相对来说更加灵活,这里指的灵活,很大一部分在于mybatis的SQL语言需要手写高级SQL语句,手写SQL语句的好处在于可以根据需要进行优化,诸如仅仅查询特定的字段等。当然这也表明了mybatis相对更加简陋。

1、配置pom.xml

本项目使用的是oracle数据库,因此引入oracle依赖。

    <!--oracle-->
    <!--连接oracle必要依赖-->
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc7</artifactId>
        <version>12.1.0.1</version>
    </dependency>

    <!--mybatis start-->
    <!--整合mybatis必要依赖-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.1.1</version>
    </dependency>
    <!--mybatis end-->
2、配置application.yml

配置数据源,在spring下配置如下datasource(也可以在mybatis的配置文件中配置):

datasource:
  url: jdbc:oracle:thin:@localhost:1521:orcl
  username: username
  password: password
  driver-class-name: oracle.jdbc.driver.OracleDriver

配置mybatis配置,包括管理sql的xml文件目录,entity目录,和配置文件目录:

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.db.entity
  config-location: classpath:mybatis-config.xml

此处,type-aliases-package 属性可以不配置,mapper-locations属性若不配置的话,则默认的sql.xml扫描目录即为mapperScanner的扫描路径。

config-location指明mybatis配置文件在resources根目录下。

3、配置mapperScanner

创建config文件 MybatisMapperScanConfig:

/**
 *
 * 配置mapper接口的目录,若application配置文件中未配置mybatis.mapper-location属性,
 * 则默认在相同目录(即可在resources目录下,也可java目录下)下扫描查找mapper.xml文件
 */
@Configuration
public class MybatisMapperScanConfig {

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer mapperScanner = new MapperScannerConfigurer();
        mapperScanner.setBasePackage("com.example.db.mapper");
        return mapperScanner;
    }
}

此Java类配置非必需,最简单的方法是在启动类添加注解 @MapperScan(value = “com.example.db.mapper”)

4、自动生成数据库实体类、mapper接口和mapper.xml

通过上面3步骤,mybatis的配置基本完成,mybatis 提供了 generator自动生成需要获取的数据库实体类、mapper接口和mapper.xml,只需要在pom中引入依赖再配置生成规则即可。

pom中配置插件:

        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.2</version>
            <configuration>
                <!--此处声明了生成规则配置文件的路径-->
                <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
                <verbose>true</verbose>
                <overwrite>true</overwrite>
            </configuration>
            <executions>
                <execution>
                    <id>Generate MyBatis Artifacts</id>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
            </executions>
            <dependencies>
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
            </dependencies>
        </plugin>

在pom引入的generator插件中配置好生成规则文件路径名称,并创建该配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration >
  <properties resource="generatorConfig.properties"/>

  <!-- 指定数据连接驱动jar地址 -->  
  <classPathEntry location="${classPath}" />

  <!-- 一个数据库一个context -->  
  <context id="pis-dev" targetRuntime="MyBatis3">
    <commentGenerator>  
        <property name="suppressAllComments" value="false"/><!-- 是否取消注释 -->  
        <property name="suppressDate" value="true" /> <!-- 是否生成注释代时间戳-->  
    </commentGenerator>  

    <jdbcConnection driverClass="${jdbc.driver}"
        connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}" >
    </jdbcConnection>

    <!-- 类型转换 -->  
    <javaTypeResolver>  
        <!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->  
        <property name="forceBigDecimals" value="false"/>  
    </javaTypeResolver>  

    <javaModelGenerator targetPackage="com.example.db.entity" targetProject="${db.project}/src/main/java" >
       <property name="enableSubPackages" value="true"/>  
       <property name="trimStrings" value="true"/>
    </javaModelGenerator>

    <sqlMapGenerator targetPackage="mapper" targetProject="${db.project}/src/main/resources">
      <property name="enableSubPackages" value="true"/> 
    </sqlMapGenerator>

    <javaClientGenerator targetPackage="com.example.db.mapper" targetProject="${db.project}/src/main/java" type="XMLMAPPER" >
      <property name="enableSubPackages" value="true" />
    </javaClientGenerator>

        <!-- schema即为数据库名 tableName为对应的数据库表 domainObjectName是要生成的实体类 enable*ByExample 是否生成 example类   -->  

        <!-- 忽略列,不生成bean 字段 -->  
        <!-- <ignoreColumn column="FRED" />   -->

        <!-- 指定列的java数据类型 -->  
        <!-- <columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />   -->

    <table  tableName="sys_right"  domainObjectName="Right" enableCountByExample="false"
            enableDeleteByExample="false" enableSelectByExample="false"  enableUpdateByExample="false" >
    </table>

  </context>

</generatorConfiguration>

配置很好理解,不赘述,本项目中将配置文件中需要用到的参数提取出来,其实可直接写在配置文件中,不过不推荐。

配置完毕后,即可运行mvn clean package命令,mybatis将会自动根据配置生成实体类等。

pS:生成规则的配置中,生成实体类,mapper.xml等文件的目录应当与mapperScanner和mapper-locations对应。且生成完毕后,需要将pom中的插件注释,否则每次运行项目均会再次生成

Shiro集成

本测试项目之所以选择Shiro作为权限控制,主要原因是实际工作项目用到的即为Shiro,所以想借着此demo深入了解学习shiro的使用。

spring其实自带了spring-security作安全管理,这两者实现的功能基本一致,相对来说shiro更为简单一些,好用易学,也是java官方所推荐的。

本demo配置实现方法:

1、引入依赖

pom中引入依赖的jar.其中shiro-core是所有使用shiro必须的,shiro-spring提供对spring框架的支持。

<dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.3.2</version>
    </dependency>
2、实现Realm

Realm是shiro中唯一需要自己实现的类,用来实现具体的登录验证,权限赋予的功能。
Realm继承自抽象类AutorizingRealm, AutorizingRealm中已经实现了大多数代码,具体需要开发人员编写的即为上面所说的登录验证(重写doGetAuthenticationInfo方法),权限赋予(重写doGetAuthorizationInfo方法)。

eg:

/*登录认证*/
@Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken aToken){
    //根据用户输入的用户名获取数据库中的用户信息。
    UsernamePasswordToken token = (UsernamePasswordToken) aToken;
    User user = userService.selectUserByUserName(token.getUsername());
    if(user != null){
        //将查询到的用户和密码存放到 authenticationInfo用于后面的权限判断
        //(第二个参数为数据库中的密码,将用来与输入的密码匹配,
        //第一个参数为存入的登录用户信息,之后可以在其他地方获取当前登录用户信息)。
        //第三个参数随便。
        AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user,user.getPassword(), "realmName") ;
        return authenticationInfo ;
    }
    return null;
}

/*权限认证,执行完登录认证后将进行权限赋予*/
@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal){
    //此处入参principal即为上一方法返回值的第一个参数:登录用户User
    User user = (User) principal.getPrimaryPrincipal();
    logger.debug("userName = " + user.getUserName());

    if(StringUtils.isEmpty(user.getId())) return null;

    /*添加角色和权限*/
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

    List<Role> roles = userService.selectRolesByUserId(user.getId());
    List<Right> rights = userService.selectRightsByUserId(user.getId());

    info.setRoles(roles.stream().map( a -> a.getRoleName()).collect(Collectors.toSet()));
    info.setStringPermissions(rights.stream().map( a -> a.getPermission()).collect(Collectors.toSet()));

    return info;
}
3、ShiroConfig

自己实现了登录认证和权限赋予之后,还需要对Shiro进行其他配置,如ShiroFilterFactoryBean和SecurityManager,这两者是必须配置的。

ShiroFilterFactoryBean用来处理资源拦截问题,ShiroFilterFactoryBean中必须注入SecurityManager,这是ShiroFilter的核心安全接口。

下面直接贴出demo的配置,(主要了解ShiroFilterFactoryBean的配置即可,PS:ShiroFilterFactoryBean依赖于SecurityManager)

@Configuration
public class ShiroConfig {
    private static Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
     * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter() {

        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        //Shiro的核心安全接口,这个属性是必须的
        shiroFilter.setSecurityManager(securityManager());
        shiroFilter.setLoginUrl("/login");                 /*要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面*/
        shiroFilter.setSuccessUrl("/index");               /*登录成功页面*/
        shiroFilter.setUnauthorizedUrl("/forbidden");      /*无权限转页面*/

        /*定义shiro过滤链 Map结构 * Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的*/
        Map<String, String> filterChainDefinitionMapping = new HashMap<String, String>();
        /*anon 静态资源,匿名访问*/
        filterChainDefinitionMapping.put("/favicon.ico", "anon");
        filterChainDefinitionMapping.put("/forbidden", "anon");
        filterChainDefinitionMapping.put("/assets/**", "anon");
        filterChainDefinitionMapping.put("/webjars/**", "anon");
        filterChainDefinitionMapping.put("/v2/api-docs", "anon");

        filterChainDefinitionMapping.put("/login", "authc");  //需要认证
        filterChainDefinitionMapping.put("/logout", "logout");  //退出登录,返回登录页面
        filterChainDefinitionMapping.put("/**", "user");   //判断登陆状态

        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMapping);

        Map<String, Filter> filters = new HashMap<String, Filter>();
        filters.put("anon", new AnonymousFilter());
        filters.put("authc", new FormAuthenticationFilter());
        filters.put("logout", new LogoutFilter());
        filters.put("user", new UserFilter());
        shiroFilter.setFilters(filters);

        return shiroFilter;
    }


    @Bean
    public RememberMeManager rememberMeManager() {
        logger.debug("create remember me manager.");

        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        simpleCookie.setHttpOnly(true);
        simpleCookie.setMaxAge(2592000);

        CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
        rememberMeManager.setCipherKey(org.apache.shiro.codec.Base64.decode("Pis2016%KyEe^!#/"));
        rememberMeManager.setCookie(simpleCookie);
        return rememberMeManager;
    }

    /*指定名字,防止与spring cache 冲突*/
    @Bean(name = "shiroCacheManager")
    public CacheManager cacheManager() {
        return new MemoryConstrainedCacheManager();
    }


    @Bean(name = "securityManager")
    public org.apache.shiro.mgt.SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //注入缓存管理器;
        securityManager.setCacheManager(cacheManager());
        securityManager.setRealm(realm());
        securityManager.setRememberMeManager(rememberMeManager());
        SecurityUtils.setSecurityManager(securityManager);
        return securityManager;
    }



    @Bean(name = "realm")
    @DependsOn("lifecycleBeanPostProcessor")
    public ShiroRealm realm() {
        ShiroRealm realm = new ShiroRealm();
//      Md5加密
//      realm.setCredentialsMatcher(new HashedCredentialsMatcher(Md5Hash.ALGORITHM_NAME));
        return realm;
    }


    /*shiro生命周期处理器*/
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }


    /*开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),
    需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
    * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能*/
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    @Bean
    @ConditionalOnMissingBean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(securityManager);
        return aasa;
    }

    @Bean
    /*设置基于表单的身份验证过滤器*/
    public FormAuthenticationFilter formAuthenticationFilter(){
        FormAuthenticationFilter filter = new FormAuthenticationFilter();
        filter.setFailureKeyAttribute("shiroLoginFailure");
        filter.setUsernameParam("userName");
        filter.setPasswordParam("password");
        filter.setRememberMeParam("rememberMe");
        return filter;
    }

}
4、登录

配置完成后,只需要访问ShiroFilter配置的LoginUrl(默认为login.jsp),就会自动获取传入的参数username和password(必须全小写,如需要自定义,可配置基于表单的身份验证过滤器FormAuthenticationFilter)封装为UsernamePasswordToken,传递个Realm的登录验证方法处理。

而实际的login方法中只需要获取错误信息分别处理即可。

//login的实现不需要自己实现,在ShiroConfig中的Shiro过滤器声明登录URL,则会在此url被请求时,
//自动获取 username 和 password 参数,作为登录账号和密码
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String signInBak(HttpServletRequest request, ModelMap model){
    String errorClassName = (String)request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
    logger.info("errorClassName {}", errorClassName);

    if(errorClassName != null) {
        if (errorClassName.endsWith("UnknownAccountException") ||
                errorClassName.endsWith("IncorrectCredentialsException")) {
            model.addAttribute("errorMsg", "账号或密码错误");
        } else {
            model.addAttribute("errorMsg", "未知错误,请联系管理员.");
        }
    }
    return "/login";
}

如果不需要访问login自动shiro验证,可以手动登录,只需要SecurityUtils.getSubject()获取到subject,然后调用subject.login(token)方法, 传入UsernamePasswordToken作为参数即可手动登录验证。

总结

至此,demo框架已经完成,这篇随笔基本记录了实现的过程,贴入了很多代码,也比较混乱,暂且先记着,加深学习印象。后续将会另起一到两篇记录日志管理和缓存管理的添加,待续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值