所遇到的问题及解决方法

  • 一、spring-boot集成spring-security
  • 1.导入依赖
  • 2.security的配置
  • 配置类
  • 页面与控制器
  • 在配置类中需要注入的账户信息获取的业务层类
  • mapper接口
  • 信息封装的类
  • 生成密文代码
  • 二、spring-boot集成pagehelper分页
  • 1.导入依赖
  • 2.配置
  • 分页请求包装类
  • 分页结果包装类
  • 分页工具类
  • 3.分页插件使用
  • 配置文件内容
  • mapper层
  • service层
  • web层
  • 页面
  • 三、spring-boot集成thymeleaf模板
  • 1.导入依赖
  • 2.使用
  • 四、spring-boot跨域问题
  • 五、spring-boot接收参数与返回数据问题
  • 1.接收参数
  • form表单与url参数同时存在需要注意的问题
  • form表单数据相对于接收的实体类不全时
  • 2.返回数据
  • 方法或类上使用@RestController注解
  • 方法或类上使用@Controller注解
  • 六、spring-boot监听器载入
  • session监听器类
  • session工具类
  • 配置监听器
  • springboot注册servlet、filter、listener、intercepter的方法总结
  • intercepter
  • filter
  • 注解实现
  • 注入RegistrationBean子类实现
  • servlet
  • 注解实现
  • 注入RegistrationBean子类实现
  • listener
  • 注解实现
  • 注入RegistrationBean子类实现
  • 七、其他小问题
  • spring-boot的favicon.ico显示问题
  • @RequestBody注解使用注意事项


一、spring-boot集成spring-security

1.导入依赖

<!--spring-security权限认证框架-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.security的配置

其功能很强大,只需要配置少量信息就可以,配置类如下

配置类

package com.zy.collect.config;

import com.zy.collect.service.impl.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * 安全模块即登录验证板块配置类
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(bCryptPasswordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
// 如果有允许匿名的url,填在下面
//                .antMatchers("/static/**").permitAll()
                .antMatchers("/admin/**")//控制路径指定
                .hasRole("ADMIN")//角色指定
                .and()
// 设置登陆页
                .formLogin().loginPage("/admin/login")
// 设置登陆成功页
                .defaultSuccessUrl("/admin/index?pageNum=1&pageSize=5",true)
// 设置登录失败页面
                .failureUrl("/admin/login?error=true")
                .permitAll()
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
                .and()
                .logout().permitAll();

// 关闭CSRF跨域
        http.csrf().disable();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
// 设置拦截忽略文件夹,可以对静态资源放行
//        web.ignoring().antMatchers("/css/**", "/js/**");
        web.ignoring().antMatchers("/static/**");
    }
}

注意:设置登录成功页面时切记带上后面的true参数,不然根据源码会将该参数定为false导致登录成功页面设置失败
虽然security模块有自身的登录页面但是样子不够特别,所有在配置类中可以自己定义登录页面,以及登录成功的页面还有登录失败的页面,一般情况下登录失败需要重新跳转到登录页面,故为了给提示,在配置失败页面url时加入error=true属性,方便控制器判断是否给出登录失败提示,登录页面 (主体) 与登录控制器如下:

页面与控制器

<form class="ui large form" method="post" action="#" th:action="@{/admin/login}">
    <div class="ui segment">
        <div class="field">
            <div class="ui left icon input">
                <i class="user icon"></i>
                <input type="text" name="username" placeholder="用户名">
            </div>
        </div>
        <div class="field">
            <div class="ui left icon input">
                <i class="lock icon"></i>
                <input type="password" name="password" placeholder="密码">
            </div>
        </div>
        <button class="ui fluid large teal submit button">登   录</button>
    </div>

    <div class="ui mini error message"></div>
    <div class="ui mini negative message" th:unless="${#strings.isEmpty(message)}" th:text="${message}">用户名或密码错误</div>

</form>
@Controller
@RequestMapping("/admin")
public class AdminLoginController {

    /**
     * 配合security进行登录验证
     * @param error
     * @param model
     * @return
     */
    @GetMapping("/login")
    public String loginPage(@RequestParam(name = "error",required = false) Boolean error, Model model){
        if(error != null && error){
            model.addAttribute("message","用户名或密码错误,请重新输入");
        }
        return "admin/login";
    }
}

其中在登录失败跳转过来后就会进入if语句使用Model存储message信息并在登录页面展示

在配置类中需要注入的账户信息获取的业务层类

package com.zy.collect.service.impl;

import com.zy.collect.domain.Admin;
import com.zy.collect.service.IAdminService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collection;

/**
 * 登录验证业务层实现类
 */
@Transactional
@Service
@Slf4j
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    IAdminService adminService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        Admin admin = adminService.findByName(s);
        log.info("admin:{}",admin);
        if(admin == null){
            throw new UsernameNotFoundException("用户不存在");
        }
        authorities.add(new SimpleGrantedAuthority(admin.getAdmin()));
        return new User(admin.getUsername(),admin.getPassword(),authorities);
    }
}

mapper接口

/**
     * 根据账号获取管理员信息
     * @param username
     * @return
     */
    @Select("select * from admin where username = #{username}")
    Admin findByUsernameAndPassword(String username);

信息封装的类

package com.zy.collect.domain;
//省略get与set方法
public class Admin {

    private Long id;
    private String username;    //账号
    private String password;    //密码
    private String admin;   //角色
}

可以看到我吧所需要的账号、密码、角色信息都放在了一个类,方便进行查找与修改。

在配置类中注入账户信息的时候采用了

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(bCryptPasswordEncoder);
    }

这代表密码采用加密的形式进行存储的。
因为管理员少,故我直接在test下,使用bCryptPasswordEncoder类直接生成加密后的密码然后存入数据库,如下:

生成密文代码

package com.zy.collect;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootTest
class CollectApplicationTests {

    @Autowired
    private BCryptPasswordEncoder pwdEncoder;

    @Test
    void contextLoads() {
        String password = "**********";//此处为需要设置的密码
        //生成密文
        String encode = pwdEncoder.encode(password);//此处生成密文
        System.out.println(encode);
        String encode1 = pwdEncoder.encode(password);//此处生成密文
        System.out.println(encode1);

        //验证密文
        boolean b = pwdEncoder.matches(password, encode);
        boolean b1 = pwdEncoder.matches(password, encode1);
        System.out.println(b);
        System.out.println(b1);
    }

}

如上spring-security登录验证功能集成完毕

二、spring-boot集成pagehelper分页

1.导入依赖

<!-- pagehelper分页 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.5</version>
</dependency>

2.配置

为了不耦合,采用自己写的类对分页请求与分页结果进行包装:

分页请求包装类

package com.zy.collect.util;

/**
 * 分页请求包装类
 */
//省略get与set方法
public class PageRequest {

    private Integer pageNum;    //当前页码
    private Integer pageSize;   //每页数量
}

分页结果包装类

package com.zy.collect.util;

import java.util.List;

/**
 * 分页返回结果包装类
 */
//省略get与set方法
public class PageResult {

    private Integer pageNum;    //当前页码
    private Integer pageSize;   //每页数量
    private Long totalSize; //记录总数
    private Integer totalPages; //页码总数
    private List<?> content;    //数据模型
}

分页工具类

package com.zy.collect.util;

import com.github.pagehelper.PageInfo;

/**
 * 分页查询相关工具类。
 */
public class PageUtils {

    public static PageResult getPageResult(PageRequest pageRequest, PageInfo<?> pageInfo){
        PageResult pageResult = new PageResult();
        pageResult.setPageNum(pageInfo.getPageNum());
        pageResult.setPageSize(pageInfo.getPageSize());
        pageResult.setTotalSize(pageInfo.getTotal());
        pageResult.setTotalPages(pageInfo.getPages());
        pageResult.setContent(pageInfo.getList());
        return pageResult;
    }
}

3.分页插件使用

配置文件内容

pagehelper:
    helperDialect: mysql
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql

mapper层

和普通的查询所有语句一样

/**
	 * 查询所有用户信息并分页
	 * @return
	 */
	@Select("select * from users")
	List<User> selectPage();

service层

/**
     * 分页查询用户信息
     * @param pageRequest 自定义,统一分页查询请求
     * @return
     */
    @Override
    public PageResult findPage(PageRequest pageRequest) {
        return PageUtils.getPageResult(pageRequest,getPageInfo(pageRequest));
    }

    /**
     * 分页插件载入
     * @param pageRequest
     * @return
     */
    private PageInfo<User> getPageInfo(PageRequest pageRequest){
        Integer pageNum = pageRequest.getPageNum();
        Integer pageSize = pageRequest.getPageSize();
        PageHelper.startPage(pageNum,pageSize);//必须在调用查询方法的紧上方
        List<User> users = userMapper.selectPage();
        return new PageInfo<User>(users);
    }

web层

/**
     * 首页
     * 所有用户信息分页展示
     * 每页5条用户信息
     * @param pageRequest
     * @param model
     * @return
     */
    @GetMapping("/index")
    public String index(PageRequest pageRequest,
                        Model model){
        PageResult page = adminService.findPage(pageRequest);
        model.addAttribute("page",page);
        return "admin/adminPage";
    }

调用该请求时,必须带上pageNum和pageSize属性,会自动封装到pageRequest参数里
例如:/admin/index?pageNum=1&pageSize=5
使用thymeleaf则为@{/admin/index(pageNum=1,pageSize=5)}

页面

正常接收就行,可以知道数据都在PageResult的content属性中,并且PageResult提供了许多分页页面所需的信息

三、spring-boot集成thymeleaf模板

1.导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2.使用

配置使用默认的即可,
然后再页面导入

xmlns:th="http://www.thymeleaf.org"

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
......
......

就可以使用其功能,非常方便

四、spring-boot跨域问题

当协议、域名、端口号有一样不同时就算是跨域,在springboot中有专门解决跨域访问的注解@CrossOrigin,只要在需要跨域的controller方法上加上此注解就可以实现跨域访问,也可以加在类上,代表该controller的所有方法均支持跨域访问

五、spring-boot接收参数与返回数据问题

1.接收参数

在web层controller方法接收的参数一般都来源自页面的form表单获取访问url后面的参数。问题如下:

form表单与url参数同时存在需要注意的问题

1.form表单与url参数的名称不能相同,如果相同,即使form表单没有给此参数赋值,在获取参数值时也会从url参数中获取该值,从而造成参数混乱,即使使用封装好的对象获取form数据依旧会存在该问题
2.不过或参数名一致,且二者都有值,会优先从form表单取出数据,既然如此,是不是可以使用url参数给定form表单的数据默认值?仅猜想!

form表单数据相对于接收的实体类不全时

1.实例类属性在form表单中,只是没有给定数据时,会返回一个空字符串,而不是null。
2.实体类属性不在form表单中,若属性类型为字符串则要额外注意,因为此时给属性会被赋值成‘null’,注意不是空,而只是一个普通字符串,但在后续使用时会报空指针异常。
针对以上两点,都需要进行字符串是否相等的判断,而不能只判空就使用

2.返回数据

方法或类上使用@RestController注解

使用该注解表明返回的是数据,而不需要页面渲染,所以一般使用Map集合储存数据即

Map<String, Object> map = new HashMap<>();

采用键值对的方式储存数据,这种数据对应着返回去的response.data.*数据,使用起来十分方便
使用注意数据类型与前端使用方式进行匹配

方法或类上使用@Controller注解

此方法的返回值就是一个页面获取一个控制器的处理地址,先会去匹配是否有对应页面,没有的话就会去匹配是否有对应控制器,都没有就会抛出异常。
1.在做页面跳转时使用转发的话可以使用Model进行携带数据的存储。使用重定向的话需要使用RedirectAttributes进行携带的数据的存储。
2.重定向使用attributes.addAttribute()时相当于拼接在重定向的url上,可以通过获取url参数的方式获取到该数据,使用attributes.addFlashAttribute()则只能通过Map获取

六、spring-boot监听器载入

此次使用了用于监听session生命周期的监听类,用来侦查session的创建与销毁

session监听器类

package com.zy.collect.servlet;

import com.zy.collect.context.MySessionContext;

import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
 * session监听器类
 */
public class MySessionListener implements HttpSessionListener {
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        MySessionContext.AddSession(httpSessionEvent.getSession());
    }
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        HttpSession session = httpSessionEvent.getSession();
        MySessionContext.DelSession(session);
    }
}

该类中使用了自定义工具类,实现session的储存、销毁与获取

session工具类

package com.zy.collect.context;

import javax.servlet.http.HttpSession;
import java.util.HashMap;

/**
 * session的Map集合类
 */
public class MySessionContext {

    private static HashMap<String,Object> map = new HashMap();

    /**
     * 保存session
     * @param session
     */
    public static synchronized void AddSession(HttpSession session) {
        if (session != null) {
            map.put(session.getId(), session);
        }
    }

    /**
     * 删除过期session
     * @param session
     */
    public static synchronized void DelSession(HttpSession session) {
        if (session != null) {
            map.remove(session.getId());
        }
    }

    /**
     * 根据id取出session
     * @param session_id
     * @return
     */
    public static synchronized HttpSession getSession(String session_id) {
        if (session_id == null)
            return null;
        return (HttpSession) map.get(session_id);
    }
}

虽然已经有了监听器,但监听器需要交给spring管理才能生效,采用通过创建RegistrationBean子类方式实现

配置监听器

package com.zy.collect.config;

import com.zy.collect.servlet.MySessionListener;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 监听器载入
 */
@Configuration
public class MyRegistConfig {

    @Bean
    public ServletListenerRegistrationBean myListener(){
        MySessionListener listener = new MySessionListener();
        return new ServletListenerRegistrationBean(listener);
    }
}

springboot注册servlet、filter、listener、intercepter的方法总结

对于servlet、filter、listener有目前我所知道的方法有两种通过注解或者通过注入RegistrationBean子类的方法实现。
对于intercepterspringMVC可以通过集成其配置类直接加进去,具体如下

intercepter

创建一个配置类实现WebMvcConfigurer 接口实现addInterceptors方法将拦截器配置进去,其中LoginInterceptor为自定义的拦截器

@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") //拦截所有请求,包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**","/sql","/findAll");  //不拦截路径
    }
}

filter

注解实现

想要通过注解进行配置,首先注解也需要装配,不然注解起不到效果,装配需要在启动类上加上@ServletComponentScan注解,servlet、listener注解配置同理!

package com.zy.admin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class BootWebAdminApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootWebAdminApplication.class, args);
    }
}

filter类

package com.zy.admin.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;


/**
 * 注入过滤器
 */
@Slf4j
@WebFilter(urlPatterns = {"/css/*","/images/*"})//需要该过滤器的路径
public class MyFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("MyFilter初始化完成");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("MyFilter工作");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        log.info("MyFilter销毁");
    }
}
注入RegistrationBean子类实现

不使用注解

@Bean
    public FilterRegistrationBean myFilter(){
        MyFilter myFilter = new MyFilter();
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));//需要该过滤器的路径
        return filterRegistrationBean;
    }

servlet

注解实现
package com.zy.admin.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 注入servlet
 */
@WebServlet(urlPatterns = "/my")//访问路径
public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("66666");
    }
}
注入RegistrationBean子类实现
@Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();
        return new ServletRegistrationBean(myServlet,"/my");//访问路径
    }

listener

注解实现
package com.zy.admin.servlet;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 * 注入监听器
 */
@Slf4j
@WebListener
public class MyServletContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("MyServletContextListener监听到项目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("MyServletContextListener监听到项目销毁");
    }
}
注入RegistrationBean子类实现
@Bean
    public ServletListenerRegistrationBean myListener(){
        MyServletContextListener listener = new MyServletContextListener();
        return new ServletListenerRegistrationBean(listener);
    }

七、其他小问题

spring-boot的favicon.ico显示问题

favicon.ico文件存放位置必须在配置的静态文件夹的根目录下。如果确认自己配置好着,可以再去target下对应目录看看有没有,没有的话说明配置还有问题,若此处还没有问题,那么可以就是 浏览器没有重新启动的问题了!!! 切记重新启动浏览器再测试。

@RequestBody注解使用注意事项

1.@RequestBody注解用来获取请求体中的数据,直接使用得到的是key=value&key=value…结构的数据,因此get方式不适用(get方式下@RequestBody获取不到任何数据)。
2.使用@RequestBody注解后,可以在方法中创建一个集合对象,前端提交的集合数据可以直接被注入到方法的集合对象中,而不需要创建一个pojo对象进行集合的封装。
3.如果想要将前端提交的json字符串自动封装到一个对象中,需要导入jackson的相关jar包,并使用@RequestBody注解。如果不导入该模块jar包则springboot使MappingJacksonHttpMessageConverter对json数据进行转换导致不能封装到对象中而且会报错!!!
4.使用@RequestBody 前后端参数要匹配个数不能少。字段名字要一样。
综上,@RequestBody注解的使用十分严格,对应那些form表单可以有参数不传值的就不需要用@RequestBody注解,而且多导入一个依赖也不是好选择,可以在不使用@RequestBody注解的时候直接用对象接收form的参数,只是不能保证接收对象的属性一定有值,从而要自己做许多判断。
可以理解为@RequestBody就是一种提交表单的规范,可以节省之后的数据处理的时间,不过使用时一定要注意使用条件。