SpringBoot+MyBatis搭建SSM基本骨架(Redis + Swagger+自定义配置 +mysql )

一些技巧

IDEA 添加 mybatis mapper的xml文件的自动提示功能
idea springboot的yml自动提示补全
PDMan + Navicat Premium + MySQL 数据库设计和工具

SSM架构

image.png

表现层主要有controller作为外部接口访问和业务层service接口的连接;业务层处于中间层次,进行接口访问后的后续逻辑处理,并通过service调用持久层mybatis的mapper,而持久层的mapper具体的SQL操作在xml文件之中实现,最后操作mysql数据库。

image.png

SSM程序执行流程

yuque_diagram.jpg

业务逻辑:
Controller-->service接口-->serviceImpl-->mapper(dao)接口-->mapperImpl(daoImpl)-->mapper-->db
其中impl为模板代码,通过注解简化,在编译的时候自动生成注解实现类。

基本架构

application.yml 指定服务端口

server:
  port: 5088
  compression:
    enabled: true

pom.xml基本配置



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.5.RELEASE
         
    
    com.springboot
    demo
    0.0.1-SNAPSHOT
    demo
    Demo project for Spring Boot

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        


        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


一、Controller 外部层

controller层即控制层。
controller层的功能为请求和响应控制。
controller层负责前后端交互,接受前端请求,调用service层,接收service层返回的数据,最后返回具体的页面和数据到客户端。

RestController注解

Spring注解:@Controller和@RestController的区别

RequestMapping注解

@RequestMapping可以作用在类上,也可以作用在方法上。
@GetMapping其实就是@RequestMapping和Get的集合:
@GetMapping(value = “hello”) 等价于@RequestMapping(value = “hello”, method = RequestMethod.GET)

RequestParam注解

@RequestParam:将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解)

语法:@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)
value:参数名
required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。
defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值

例子代码:

@RestController
@RequestMapping(value = "/user")
public class UserController {
    @Autowired
    private UserService mUserService;

    @RequestMapping(value = "/registration",method = RequestMethod.POST)
    public Object registration(@RequestParam(value = "username") String username, @RequestParam(value = "password") String password, @RequestParam(value = "memberLevelId",defaultValue = "4") int memberLevelId, @RequestParam(value = "phone") String phone ){
        System.out.printf("username:"+username+",password:"+password +",memberLevelID:"+memberLevelId+",phone:"+phone);
        mUserService.addUser(username,password,memberLevelId,phone, DateUtil.currentDate());
        return "registration success! ";
    }
}

二、Service逻辑层

service层即业务逻辑层。
service层的作用为完成功能设计。
service层调用dao层接口,接收dao层返回的数据(封装为对象Model层),完成项目的基本功能设计。
存放业务逻辑处理,也是一些关于数据库处理的操作,但不是直接和数据库打交道,他有接口还有接口的实现方法,在接口的实现方法中需要导入mapper层,mapper层是直接跟数据库打交道的,他也是个接口,只有方法名字,具体实现在mapper.xml文件里,service是供我们使用的方法。

Repository注解

SpringBoot @Repository解析

例子代码:

@Repository
public class UserService {
    @Autowired
    private UserMapper mUserMapper;
    public void addUser(String username,String password,int memberLevelId,String phone,String createTime){
        mUserMapper.addUser(username, password, memberLevelId, phone, createTime);
    }
}

Autowired注解

SpringBoot @Autowired解析
autowired有4种模式,byName、byType、constructor、autodectect

@Autowired在何处使用

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
CONSTRUCTOR:构造
METHOD:方法
PARAMETER:参数
FIELD:字段
ANNOTATION_TYPE:注解

@Autowired参数

Autowired注解,只有一个required元素,默认是true,也是就是说这个值能改为false。true和false的意义不同。
require=ture 时,表示解析被标记的字段或方法,一定有对应的bean存在。
require=false 时,表示解析被标记的字段或方法,没有对应的bean存在不会报错。

public @interface Autowired {
    /**
     * Declares whether the annotated dependency is required.
     * 

Defaults to {@code true}. */ boolean required() default true; }

三、Mapper层(Dao层)

不管是什么框架,我们很多时候都会与数据库进行交互。如果遇到一个场景我们都要去写SQL语句,那么我们的代码就会很冗余。所以,我们就想到了把数据库封装一下,让我们的数据库的交道看起来像和一个对象打交道,这个对象通常就是DAO。当我们操作这个对象的时候,这个对象会自动产生SQL语句来和数据库进行交互,我们就只需要使用DAO就行了。

我们经常说的DAO = Data Access Object = 数据存取对象。通常网络上我们说的Dao不仅包含了数据库的Model数据结构还包含了对应的数据操作方法,但是有一些却把它当成纯粹的model,所以更倾向于称为Mapper层。

通常我们在Mapper层里面写接口(Mapper层包括了数据对象既model),里面有与数据打交道的方法。SQL语句通常写在mapper文件里面的。

优点:结构清晰,Dao层的数据源配置以及相关的有关数据库连接的参数都在Spring配置文件中进行配置

总结:
mapper层即数据持久层,也被称为dao层。
mapper层的作用为访问数据库,向数据库发送sql语句,完成数据的增删改查任务。
现在用mybatis逆向工程生成的mapper层。对数据库进行数据持久化操作,他的方法语句是直接针对数据库操作的,而service层是针对我们controller,也就是针对我们使用者。service的impl是把mapper和service进行整合的文件。

集成MySQL

MySQL配置

pom.xml添加

        
        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            mysql
            mysql-connector-java
            runtime
        

在application.yml中添加:

spring:
  #配置数据源
  datasource:
    url: jdbc:mysql://localhost:3306/mytest?characterEncoding=utf8&useSSL=true&serverTimezone=UTC
    username: root #数据库用户名
    password: root #数据库密码

mysql相关工具使用

PDMan + Navicat Premium + MySQL 数据库设计和工具
Navicat 连接MySQL数据库出现Authentication plugin 'caching_sha2_password' cannot be loaded的解决方案
Navicat中如何复制数据库
Navicat:数据库可视化

集成MyBatis

mybatis配置

在application.yml中添加:

mybatis:
  mapper-locations: classpath:mybatis/*.xml  # 注意:一定要对应mapper映射xml文件的所在路径
  type-aliases-package: com.springboot.demo.entity # 注意:对应实体类的路径
  configuration:
    map-underscore-to-camel-case: true # 数据库字段下划线自动转驼峰

在pom.xml依赖中添加

                
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.1.3
        

DemoApplication添加MapperScan注解

@MapperScan("com.springboot.demo")
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

UserMapper 接口添加数据库操作外部方法

@Repository
public interface UserMapper {
    /**
     * @param username 用户名
     * @param password 密码
     * @param memberLevelId 会员级别
     * @param phone 手机号码
     */
    void addUser(String username,String password,int memberLevelId,String phone,String createTime);
}

Mapper创建xml,实现底层SQL的执行操作




    
        INSERT INTO t_user ( user_name, pwd, imooc_id, order_id, create_time )
            VALUES
                ( #{userName}, #{password}, #{imoocId}, #{orderId}, #{createTime} )
                ON DUPLICATE KEY UPDATE
                pwd=VALUES(pwd)
                ,user_name=VALUES(user_name);
    

四、Model层

model层即数据库实体层,也被称为entity层,pojo层。存放我们的实体类,与数据库中的属性值基本保持一致。比如service层返回controller层的数据,封装为对象model,更加符合面向对象编程的习惯。

乱码错误

之前建立项目是utf8,但是不知道怎么操作的application.yml有一次变成乱码,于是用GBK重载,注释的中文恢复了,但是执行代码发现报错

Caused by: org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input l

其实应该是文件的格式问题造成的解析错误,改变改文件的解析格式为utf8,或者新建一个application.yml文件覆盖,里面乱码的中文,重新输入即可。

对于PostMan的使用教程,可以查看postman使用教程

image.png

报错,500, "error": "Internal Server Error",,查看服务端log,create_time字段出问题。


image.png

UserService的addUser 调用UserMapper


image.png

而UserMapper在UserMapper.xml使用了create_time对应不上UserMapper.java的createTime报错,将UserMapper.xml的create_time改为createTime即可
image.png

重新请求postman


image.png

更改请求时的密码,发现并没有新注册一个账户,而是直接update数据库,证明我们的mapper的SQL语句是正确的

自定义配置

@Configuration底层是含有@Component ,所以@Configuration 具有和 @Component 的作用。
@Configuration可理解为用spring的时候xml里面的标签。
@Configuration注解可以达到在Spring中使用xml配置文件的作用。
@Bean可理解为用spring的时候xml里面的标签

@Configuration注解

@Bean注解

BCrypt实现用户信息加密

密码的存储,传输使用明文是比较不安全的做法,所以我们通过集成BCrypt实现用户信息加密。
pom.xml添加配置



    org.springframework.boot
    spring-boot-starter-security

添加SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //定义所有要控制的路径
        .antMatchers("/**")
                //允许所有人访问上述路径
        .permitAll()
        .and().csrf().disable();
        super.configure(http);
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return  new BCryptPasswordEncoder();
    }
}
image.png

自定义配置类入口WebSecurityConfigurerAdapter

HttpSecurity 使用了builder 的构建方式来灵活制定访问策略。最早基于 XML 标签对 HttpSecurity 进行配置。现在大部分使用 javaConfig方式。常用的方法解读如下:

方法 说明
openidLogin() 用于基于 OpenId 的验证
headers() 将安全标头添加到响应,比如说简单的 XSS 保护
cors() 配置跨域资源共享( CORS )
sessionManagement() 允许配置会话管理
portMapper() 允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee() 配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509() 配置基于x509的认证
rememberMe 允许配置“记住我”的验证
authorizeRequests() 允许基于使用HttpServletRequest限制访问
requestCache() 允许配置请求缓存
exceptionHandling() 允许配置错误处理
securityContext() 在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi() 将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf() 添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout() 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous() 允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin() 指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login() 根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel() 配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic() 配置 Http Basic 验证
addFilterBefore() 在指定的Filter类之前添加过滤器
addFilterAt() 在指定的Filter类的位置添加过滤器
addFilterAfter() 在指定的Filter类的之后添加过滤器
and() 连接以上策略的连接器,用来组合安全策略。实际上就是"而且"的意思

我们的项目
authorizeRequests():允许基于使用HttpServletRequest限制访问
antMatchers("/**"):定义所有要控制的路径

/* 是拦截所有的文件夹,不包含子文件夹
/** 是拦截所有的文件夹及里面的子文件夹
也能够对不同路径设置不同的配置
 .antMatchers("/resources/**", "/signup", "/about").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")

antMatchers("/**").permitAll() 表示项目根目录以及子目录全部都 permitAll 允许所有人访问上述路径

在项目中添加了Security模块后,在发送post请求时会失败,出现以下日志:

Invalid CSRF token found for ..

在Security的默认拦截器里,默认会开启CSRF处理,判断请求是否携带了token,如果没有就拒绝访问。并且,在请求为(GET|HEAD|TRACE|OPTIONS)时,则不会开启,可参考如下源码:

// 先从tokenRepository中加载token  
  CsrfToken csrfToken = tokenRepository.loadToken(request);  
  final boolean missingToken = csrfToken == null;  
  // 如果为空,则tokenRepository生成新的token,并保存到tokenRepository中  
  if(missingToken) {  
      CsrfToken generatedToken = tokenRepository.generateToken(request);  
      // 默认的SaveOnAccessCsrfToken方法,记录tokenRepository,  
      // tokenRepository,response,获取token时先将token同步保存到tokenRepository中  
      csrfToken = new SaveOnAccessCsrfToken(tokenRepository, request, response, generatedToken);  
  }  
  // 将token写入request的attribute中,方便页面上使用  
  request.setAttribute(CsrfToken.class.getName(), csrfToken);  
  request.setAttribute(csrfToken.getParameterName(), csrfToken);  

  // 如果不需要csrf验证的请求,则直接下传请求(requireCsrfProtectionMatcher是默认的对象,对符合^(GET|HEAD|TRACE|OPTIONS)$的请求不验证)  
  if(!requireCsrfProtectionMatcher.matches(request)) {  
      filterChain.doFilter(request, response);  
      return;  
  }  

  // 从用户请求中获取token信息  
  String actualToken = request.getHeader(csrfToken.getHeaderName());  
  if(actualToken == null) {  
      actualToken = request.getParameter(csrfToken.getParameterName());  
  }  
  // 验证,如果相同,则下传请求,如果不同,则抛出异常  
  if(!csrfToken.getToken().equals(actualToken)) {  
      if(logger.isDebugEnabled()) {  
          logger.debug("Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request));  
      }  
      if(missingToken) {  
          accessDeniedHandler.handle(request, response, new MissingCsrfTokenException(actualToken));  
      } else {  
          accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken));  
      }  
      return;  
  }  

  filterChain.doFilter(request, response);  

所以 csrf().disable()关掉csrf

整合Swagger文档

        
        
            io.springfox
            springfox-swagger2
            2.9.2
        
        
            io.springfox
            springfox-swagger-ui
            2.9.2
        

application

@MapperScan("com.springboot.demo")
@SpringBootApplication
@EnableSwagger2
public class DemoApplication {

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

}

添加配置类

添加一个swagger 配置类,在工程下新建 config 包并添加一个 SwaggerConfig 配置类。

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    public static final String VERSION ="1.0.1";
    public  static final String AUTHOR="spring boot demo";


    public Docket createRestApi(){
        return  new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com\\springboot\\demo\\controller"))
                .paths(PathSelectors.any())
                .build()
                .ignoredParameterTypes(ApiIgnore.class);

    }

    private ApiInfo apiInfo() {

        return new ApiInfoBuilder()
                .title("API 文档")
                .description("SpringBoot demo API 文档")
                .version(VERSION)
                .contact(new Contact(AUTHOR,"https://www.baidu.com","[email protected]"))
                .build();
    }
}

Swagger 注解

@Api:用在请求的类上,表示对类的说明
    tags="说明该类的作用,可以在UI界面上看到的注解"
    value="该参数没什么意义,在UI界面上也看到,所以不需要配置"

@ApiOperation:用在请求的方法上,说明方法的用途、作用
    value="说明方法的用途、作用"
    notes="方法的备注说明"

@ApiImplicitParams:用在请求的方法上,表示一组参数说明
    @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
        name:参数名
        value:参数的汉字说明、解释
        required:参数是否必须传
        paramType:参数放在哪个地方
            · header --> 请求参数的获取:@RequestHeader
            · query --> 请求参数的获取:@RequestParam
            · path(用于restful接口)--> 请求参数的获取:@PathVariable
            · body(不常用)
            · form(不常用)
        dataType:参数类型,默认String,其它值dataType="Integer"
        defaultValue:参数的默认值

@ApiResponses:用在请求的方法上,表示一组响应
    @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
        code:数字,例如400
        message:信息,例如"请求参数没填好"
        response:抛出异常的类

@ApiModel:用于响应类上,表示一个返回响应数据的信息
            (这种一般用在post创建的时候,使用@RequestBody这样的场景,
            请求参数无法使用@ApiImplicitParam注解进行描述的时候)
    @ApiModelProperty:用在属性上,描述响应类的属性

示例

@Api:用在请求的类上,说明该类的作用

@RestController
@RequestMapping(value = "/user")
@Api(tags = {"Member"})
public class UserController {

@ApiOperation(value = "登录")
@ApiParam("登录账号")

    @ApiOperation(value = "注册")
    @RequestMapping(value = "/registration",method = RequestMethod.POST)
    public ResponseEntity registration(@RequestParam(value = "username") @ApiParam("用户名") String username,
                               @RequestParam(value = "password") @ApiParam("密码") String password,
                               @RequestParam(value = "memberLevelId",defaultValue = "4") @ApiParam("会员等级") int memberLevelId,
                               @RequestParam(value = "phone") @ApiParam("手机号码") String phone ){
//        System.out.printf("username:"+username+",password:"+password +",memberLevelID:"+memberLevelId+",phone:"+phone);
        mUserService.addUser(username, bCryptPasswordEncoder.encode(password),memberLevelId,phone, DateUtil.currentDate());
        return ResponseEntity.successMessage("registration success! ");
    }
@ApiOperation(value="用户注册",notes="手机号、密码都是必输项,年龄随边填,但必须是数字")

@ApiImplicitParams:用在请求的方法上,包含一组参数说明

@ApiImplicitParams({
    @ApiImplicitParam(name="mobile",value="手机号",required=true,paramType="form"),
    @ApiImplicitParam(name="password",value="密码",required=true,paramType="form"),
    @ApiImplicitParam(name="age",value="年龄",required=true,paramType="form",dataType="Integer")
})

@ApiResponses:用于请求的方法上,表示一组响应

@ApiOperation(value = "select1请求",notes = "多个参数,多种的查询参数类型")
@ApiResponses({
     @ApiResponse(code=400,message="请求参数没填好"),
     @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对")
 })

@ApiModel:用于响应类上,表示一个返回响应数据的信息

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
@ApiModel(description= "返回响应数据")
public class RestMessage implements Serializable{
    @ApiModelProperty(value = "是否成功")
    private boolean success=true;
    @ApiModelProperty(value = "返回对象")
    private Object data;
    @ApiModelProperty(value = "错误编号")
    private Integer errCode;
    @ApiModelProperty(value = "错误信息")
    private String message;
    /* getter/setter */
}

访问swagger接口文档界面

运行项目之后,打开浏览器,访问:http://localhost:5088/swagger-ui.html,进入swagger接口文档界面。

image.png

测试接口

展开member的/user/login登录接口,点击Try it out,就可以输入参数并点击执行。

image.png

输入参数并点击执行


image.png

看到返回信息,提示密码错误,程序正常


image.png

整合 Redis

pom.xml 集成服务

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        

Redis 工具类

public class UserRedisUtil {


    public static String BOARDING_PASS = "boarding-pass";
    /**
     * 将用户信息保存到redis
     * @param redisTemplate
     * @param session
     * @param userEntity
     */
    public static void addUser(StringRedisTemplate redisTemplate, HttpSession session, UserEntity userEntity){
        //用户session写入redis
        redisTemplate.opsForValue().set(getKey(session),JsonUtil.toJsonString(userEntity));
    }

    /**
     * 将用户信息从redis中移除
     * @param redisTemplate
     * @param session
     */
    public static void removeUser(StringRedisTemplate redisTemplate,HttpSession session){
        session.invalidate();
        //将用户从redis中移除
        redisTemplate.delete(getKey(session));
    }


    public  static UserEntity getUser(StringRedisTemplate redisTemplate, HttpServletRequest request){
        //检测redis是否含有session
        UserEntity userEntity = null;
        String userJson = redisTemplate.opsForValue().get(getBoardingPass(request));
        if(userJson != null){
            userEntity = JsonUtil.fromJson(userJson,UserEntity.class);
        }
        return  userEntity;
    }



    /**
     * 获取redis存储的key
     * @param session
     * @return
     */
    public static String getKey(HttpSession session){
        return session.getId();
    }


    public static String getBoardingPass(HttpServletRequest request){
        String pass =request.getHeader(BOARDING_PASS);
        return pass != null ?pass : "no-pass";
    }
}

你可能感兴趣的:(SpringBoot+MyBatis搭建SSM基本骨架(Redis + Swagger+自定义配置 +mysql ))