学习过一个项目,记录一些基本知识点。

  程序仓库:https://gitee.com/juncaoit/basic-springboot

 

目录:

  1.springboot 的官网

  2.@Controller与@RestController的区别

  3.springboot的目录结构

  4.同个⽂件的加载顺序

  5.解压后的jar包的目录

  6.对外接口使用统一的json格式

  7.JackSon处理字段

  8.添加配置文件

  9.单元测试

  10.MockMvc

  11.全局异常处理

  12.拦截器

  13.监听器

  14.拦截器

  15.FreeMarker

 

1.springboot 的官网

  https://spring.io/projects/spring-boot#overview

 

2.@Controller与@RestController的区别

@Controller 作⽤:⽤于标记这个类是⼀个控制器,返回⻚⾯的时候使⽤;如果要返回JSON,则需要在接⼝上使⽤@ResponseBody才可以

@RestController 作⽤:⽤于标记这个类是⼀个控制器,返回JSON数据的时候使⽤,如果使⽤这个注解,则接⼝返回数据会被序列化为JSON
所以:@RestController = @Controller+@ResponseBody

 

3.springboot的目录结构

  

springboot后端框架代码结构 springboot后端开发_spring

 

 

 

src/main/java:存放代码
src/main/resources
static: 存放静态⽂件,⽐如 css、js、image, (访问⽅式 http://localhost:8080/js/main.js)
templates:存放静态⻚⾯jsp,html,tpl
config:存放配置⽂件,application.properties
resources:

 

4.同个⽂件的加载顺序

  静态资源⽂件 Spring Boot 默认会挨个从:

  META/resources > resources > static > public ⾥⾯找是否存在相应的资源

  默认配置 spring.resources.static-locations = classpath:/METAINF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/

 

  如何添加新的目录,则修改appliction.properties

#文件加载内容
spring.resources.static-locations=classpath:/META-INF/resources/, classpath:/resources/, classpath:/static/, classpath:/public/, classpath:/templates/

 

  启动之后进行访问页面:

http://localhost:8080/success.html

  

springboot后端框架代码结构 springboot后端开发_spring_02

 

 

 

 

5.解压后的jar包的目录

  org:spring使用的

  META-INF:指定main函数的位置,告诉虚拟机入口位置

  BOOT-INF:自己的项目下的class与lib

  

springboot后端框架代码结构 springboot后端开发_springboot后端框架代码结构_03

 

 

 

6.对外接口使用统一的json格式

  对外转换:

package com.jun.xiaod.utils;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class JsonData<T> {

    private int code;

    private T data;

    private String msg;

    public JsonData(int code, T data) {
        this.code = code;
        this.data = data;
    }

    public static <T> JsonData<T> buildSuccess(T data) {
        return new JsonData<T>(0, data, "");
    }

    public static <T> JsonData<T> buildFail(String msg) {
        return new JsonData<T>(-1, null, msg);
    }

    public static <T> JsonData<T> buildFail(int code, String msg) {
        return new JsonData<T>(code, null, msg);
    }
}

  

  同时,序列化与反序列化工具:

package com.jun.xiaod.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonUtils {
    /**
     * 序列化
     */
    public static <T> String toJsonString(T t) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.writeValueAsString(t);
    }

    /**
     * 反序列化
     */
    public static <T> T read(String jsonStr) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(jsonStr, new TypeReference<T>(){});
    }
}

 

7.JackSon处理字段

  在返回的VO中添加注解:

jackson处理相关⾃动:

指定字段不返回:@JsonIgnore
指定⽇期格式:@JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
空字段不返回:@JsonInclude(Include.NON_NULL)
指定别名:@JsonProperty

 

  举例:

package com.jun.xiaod.domain;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Video {
    private int id;

    /**
     * 视频标题
     */
    private String title;

    /**
     * 概述
     */
    private String summary;

    /**
     * 封面图
     */
    private String coverImg;

    /**
     * 价格,分
     */
    private int price;

    /**
     * 创建时间
     */
    @JsonFormat(pattern="yyyy-MM-dd hh:mm:ss",locale="zh",timezone="GMT+8")
    @JsonProperty("create_time")
    private Date createTime;

    /**
     * 默认8.7,最高10分
     */
    private Double point;

}

  

  效果:

  其中,video.setCreateTime(new Date());

{
    "code": 0,
    "data": [
        {
            "id": 1,
            "title": "java",
            "summary": null,
            "coverImg": null,
            "price": 0,
            "point": null,
            "create_time": "2022-05-06 09:57:54"
        }
    ],
    "msg": ""
}

 

8.添加配置文件

  

springboot后端框架代码结构 springboot后端开发_ide_04

 

  其中,配置文件如下:

#微信支付配置
wxpay.appid=w123456
wxpay.secret=safemn
wx.mechid=121212

 

  其他地方引用使用:

@Resource
    private WxConfig wxConfig;

 

9.单元测试

<!-- spring test框架 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

 

  使用:

  在controller的测试类中进行继承。

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {VideoApplication.class})
public class BaseTest {
}

 

10.MockMvc

  增加类注解 @AutoConfigureMockMvc

  注⼊⼀个MockMvc类

  相关API :

    perform执⾏⼀个RequestBuilder请求

    andExpect:添加ResultMatcher->MockMvcResultMatchers验证规则

    andReturn:最后返回相应的MvcResult->Response

package com.jun.xioad.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.jun.xiaod.controller.VideoController;
import com.jun.xiaod.domain.Video;
import com.jun.xiaod.utils.JsonData;
import com.jun.xiaod.utils.JsonUtils;
import com.jun.xioad.BaseTest;
import junit.framework.TestCase;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import javax.annotation.Resource;
import java.sql.*;
import java.util.List;

@Slf4j
@AutoConfigureMockMvc
public class VideoControllerTest extends BaseTest {
    @Resource
    private VideoController videoController;
    @Resource
    private MockMvc mockMvc;

    @Test
    public void testList() throws JsonProcessingException {
        JsonData<List<Video>> videoList = videoController.getVideoList();
        log.info("videoList=={}", JsonUtils.toJsonString(videoList));
        TestCase.assertEquals(5, videoList.getData().size());
        TestCase.assertTrue(videoList.getData().size() > 0);
    }

    /**
     * mockMvc的使用
     */
    @Test
    public void testListMvc() throws Exception {
        MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/app/v1/test/get/config"))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andReturn();
        int status = mvcResult.getResponse().getStatus();
        log.info("ststua={}", status);
    }


}

 

11.全局异常处理

   统⼀的错误⻚⾯或者错误码

   对⽤户更友好

package com.jun.xiaod.handler;

import com.jun.xiaod.utils.JsonData;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice
public class CustomExtHandler {
    @ExceptionHandler(value = Exception.class)
    JsonData<String> handle(Exception e, HttpServletRequest request) {
        return JsonData.buildFail(-2, "网络正忙,请稍后重试");
    }
}

 

  思路:

  类添加注解

    @ControllerAdvice,如果需要返回json数据,则⽅法需要加@ResponseBody

    @RestControllerAdvice, 默认返回json数据,⽅法不需要加@ResponseBody

  ⽅法添加处理器 捕获全局异常,处理所有不可知的异常

    @ExceptionHandler(value=Exception.class)

 

12.拦截器

package com.jun.xiaod.filter;

import com.jun.xiaod.domain.User;
import com.jun.xiaod.service.impl.UserServiceImpl;
import com.jun.xiaod.utils.JsonData;
import com.jun.xiaod.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;

@Slf4j
@WebFilter(urlPatterns = "/api/v1/pri/*", filterName = "loginFilter")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("doFilter");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 先从请求头中进行获取,再从参数中进行获取
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            token = request.getParameter("token");
        }
        // 存在token
        if (!StringUtils.isEmpty(token)) {
            // 为了验证这里,需要先进行登陆,然后使用登陆之后的token获取user
            User user = UserServiceImpl.sessionMap.get(token);
            if (Objects.nonNull(user)) {
                filterChain.doFilter(servletRequest, servletResponse);
            } else {
                renderJson(response, JsonUtils.toJsonString(JsonData.buildFail(-2, "登陆失败,没有登陆过")));
            }
        } else {
            renderJson(response, JsonUtils.toJsonString(JsonData.buildFail(-3, "登陆失败,无token")));
        }

    }

    /**
     * 输出流输出
     */
    private void renderJson(HttpServletResponse response, String json) {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        try (PrintWriter writer = response.getWriter()) {
            writer.println(json);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

  使用地方:

@Slf4j
@RestController
@RequestMapping("/api/v1/pri/order")
public class VideoOrderController {
    @Resource
    private VideoServiceImpl videoService;

    @PostMapping("/save")
    public JsonData<String> saveVideo() {
        log.info("/save");
        return JsonData.buildSuccess("下单成功");
    }
}

 

  思路:

  启动类⾥⾯增加 @ServletComponentScan,进⾏扫描

  新建⼀个Filter类,implements Filter,并实现对应的接⼝

  @WebFilter 标记⼀个类为filter,被spring进⾏扫描。urlPatterns:拦截规则,⽀持正则,控制chain.doFilter的⽅法的调⽤,来实现是否通过放⾏。不放⾏,web应⽤resp.sendRedirect("/index.html") 或者 返回json字符串

 

  效果:

  

springboot后端框架代码结构 springboot后端开发_springboot后端框架代码结构_05

 

 

13.监听器

  常见的监听器

    ServletContextListener 应⽤启动监听

    HttpSessionLisener 会话监听

    ServletRequestListener 请求监听

/**
 * 应用启动监听器
 */
@Slf4j
@WebListener
public class ApplicationListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        log.info("ApplicationListener init");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        log.info("ApplicationListener destory");
    }
}

 

14.拦截器

  注册拦截器

package com.jun.xiaod.config;

import com.jun.xiaod.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 拦截器啥的
 * 不依赖容器
 */
@Configuration
public class CustomerWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/api/v1/**");
        WebMvcConfigurer.super.addInterceptors(registry);
    }

    public LoginInterceptor getLoginInterceptor() {
        return new LoginInterceptor();
    }
}

 

  定义拦截器:

package com.jun.xiaod.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginInterceptor preHandle ");
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("LoginInterceptor postHandle ");
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("LoginInterceptor afterCompletion ");
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

 

15.FreeMarker

 pom

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

 

配置文件

#freemarker
spring.freemarker.cache=false
spring.freemarker.charset=utf-8
spring.freemarker.allow-request-override=false
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
spring.freemarker.suffix=.ftl
spring.freemarker.template-loader-path=classpath:/templates/

 

html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h2>payId:${setting.payId}</h2>
</body>
</html>

 

后端

@Controller
@RequestMapping("/api/v1/freemarker")
public class FreemarkerController {
    @Resource
    private WxConfig wxConfig;

    @GetMapping("/get/config")
    public String getConfig(ModelMap modelMap) {
        modelMap.addAttribute("setting", wxConfig);
        // 不用添加后缀
        return "fm/user";
    }
}