SpringBoot

自动装配,最主要的功能,必须深刻理解

微服务是一种架构风格,就是一种架构

创建一个Springboot程序

在idea里直接创建即可,idea集成了springboot网站,也可以去spring官网下载

创建的时候导入springweb的依赖

用jar打包

配置文件application.properties可以更改端口号

spring.application.name=SpringBoot-01
#修改端口号
server.port=8081

在resources里设置bannner.txt可以自定义导入bannner

启动器

starter 都是用这个来启动的!,可以去官网找依赖

导入了很多类,但不一定都能使用,需要conditionONXXX里(用start导入了)都满足了才能使用。详细的解释如下:

总结:springboot的所有配置都是在启动的时候扫描并加载:spring.factories 所有的自动配置类都在这里面,但不一定生效,要判断条件是否成立,只要导入了对应的start,就有对应的启动器了,有了启动器,我们的自动装配就会生效,然后就配置成功!!!

1.springboot在启动的时候,在类路径下/META-INF /spring.factories获取指定的值;

2.将这些自动配置的类导入容器,自动配置就会生效,帮我们进行自动配置!

3.以前我们需要动手配置的东西,现在springboot帮我们做了!

4.整合javaEE,解决方案和自动装配的东西都在spring-boot-autoconfigure-2.2.0.RELEASE.jar这个包下

5.它会把所有需要导入的组件,以类名的方式返回,这些组件就会被添加到容器;

6.容器中也会存在非常多的xxxAutoConfiguration的文件(@Bean),就是这些类给容器中导入了这个场景需要的所有组件,并自动配置,@Configuration,JavaConfig!

7.有了自动配置类,免去了我们手动编写配置文件的工作!

配置文件

可以是以properties结尾的也可以以yaml结尾的,推荐用yaml结尾的

yaml基本格式:

key:空格value

可以保存一个对象,用{}(或缩进),也可以保存一个数组用[] 或-

用处

​ 通过yaml配置文件给实体类赋值,其实不止实体类,还可以给其他文件赋值

实体类这样写

重点是这个:

@ConfigurationProperties(prefix = "person") //导入yaml的配置
package com.li.springboot01.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component //注册bean
@ConfigurationProperties(prefix = "person") //导入yaml的配置
public class Person {



    private String name;

    private int age;

    private List<String> hobbies;

    private Map<String,Object> map;

    private Date date;
}

application.yaml

其中元素的值还可以用类似于${random.uuid}这种spring自带的一些元素来填充

server:
  port: 8081

person:
  name: 小李
  age: 18
  hobbies: [sing,dance,rap]
  map: {k1: v1}
  date: 2021/12/11

测试

package com.li.springboot01;

import com.li.springboot01.pojo.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringBoot01ApplicationTests {
    @Autowired//开启自动注入
    Person person;
    @Test
    void contextLoads() {
        System.out.println(person);
    }

}

松散绑定

在yaml中写first-name 在实体类里的属性名为firstName,也是可以完成赋值的,从-变成驼峰式,这就是松散绑定!

JSR303校验

限制输入的数据格式,格式错了会报错

1.导入依赖

<!--引入validation的场景启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.类上使用直接@Validated

3.限制格式(用注解)

如下图实际效果

@Validated//数据校验
public class Person {

    @Email//限定必须是邮箱格式,当然也可以在()里自己写格式
    private String name;

    private int age;

    private List<String> hobbies;

    private Map<String,Object> map;

    private Date date;
}

有诸如此类的格式,正则表达式可以实现一切

多环境配置

可以在不同目录下编写配置文件,可以在默认的配置文件里用spring.profiles.active = 来进行选择哪个配置生效后面跟-后面的文字即可

Spring Boot Web开发

1.静态资源导入

在springboot中可以使用以下方式处理静态资源

  • webjars(不推荐)localhost:8080/webjars/

  • public,static,/**,resources localhost:8080/(会去resources下的这三个目录里面查找)

    优先级:resources>static>public

2.首页定制

可以放在上面的三个目录里,命名为index.html

3.模板引擎

Thymeleaf 版本要3.x

thymeleaf常用命名空间,需要的兄弟请自取,顺便顶下我,让跟多的朋友看到:
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

<html lang="en" xmlns:th="http://www.thymeleaf.org" 
				xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
				xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

导入依赖

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>3.3.1</version>
</dependency>

结论:只要使用thymeleaf,只需要导入对应的依赖就可以了!我们将html放在我们的template目录下,里面包含了视图解析器

在html页面导入以下约束

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

语法,去网上搜着看和Vue类似

扩展SpringMVC

官方建议我们这样子做(一定要写) 静态资源过滤器,不然css等无法使用!

package com.li.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration //需要加上这个注解
public class MySpringMvcConfig  implements WebMvcConfigurer {
    
}

在里面实现方法!

员工管理系统

1.首页配置:所有的静态资源都由thyme leaf接管

​ url用@{}

2.页面国际化

实现步骤(图文)

1.在resource中创建资源包

必须在配置文件中注册

#真实的国际化配置文件的目录
spring.messages.basename=i18n.login

改写html,用#{}这样的格式写

实现国际化接口

package com.li.config;

import ch.qos.logback.core.util.StringUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import java.util.Locale;

public class MylocalResolve implements LocaleResolver {

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");

        Locale locale = Locale.getDefault();// 默认

        if(!StringUtils.isEmpty(l))
        {
            String[] s = l.split("_");

            locale = new Locale(s[0],s[1]);//不为空就改变
        }

        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {

    }
}

配置文件config里的方法都要注册

注册为Bean

   @Bean  //注册这个自定义的国际化解释器
    public LocaleResolver localeResolver()
    {
        return new MylocalResolve();
    }

名字必须为localeResolver(),这个是大坑!,找了一个小时的错才发现,springboot扫描到这个名字才能进入该方法!

简略步骤

1.我们需要配置i18n文件

2.我们如果需要在项目中进行按钮的自动切换,我们需要自定义一个组件localeResolver(切记名字一定是这个!!!!!)

3.记得将自己写的组件配置到spring容器@Bean

4.#{}

3.实现登录

1.改变表单

		<form class="form-signin" th:action="@{/login}">
			<img class="mb-4" th:src="@{img/bootstrap-solid.svg}" alt="" width="72"
				 height="72">
			<h1 class="h3 mb-3 font-weight-normal" th:text="#{loginin}"></h1>
<!--			如果返回值不为空,那就给他显示-->
			<p  style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
			<label class="sr-only"  th:text="#{username}">Username</label>
			<input type="text"  name="username" class="form-control" th:placeholder="#{username}"
				   required=""
				   autofocus="">
			<label class="sr-only"  th:text="#{password}">Password</label>
			<input type="password" name="password" class="form-control"
				   th:placeholder="#{password}"
				   required="">

2.编写控制类

package com.li.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MyLoginController {
    @RequestMapping("/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Model model){

        if(StringUtils.hasText(username) && "123456".equals(password))
        {
            return "redirect:/main.html";
        }

        else
            model.addAttribute("msg","账号或秘密输入错误");

        return "index";
    }
}

3.起别名(防止外部知道)

registry.addViewController("/main.html").setViewName("dashboard");
        //取别名,前面的是别名

4.配置拦截器,实现登录拦截

1.在config里编写自己的拦截器类

package com.li.config;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class MyInterceptor implements HandlerInterceptor {
    //定义拦截器
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Object session = request.getSession().getAttribute("session");

        if(session == null)
        {   //如果没有session那就拦截并返回错误信息
            request.setAttribute("msg","没有权限,请先登录!");
            request.getRequestDispatcher("/index.html").forward(request,response);//重定向到主页面
            return false;// 拦截
        }
        return true;//不然返回正确
    }


}

2.更改前面的登录controller,使其多一个传入参数session

有session不拦截,没session 拦截

package com.li.controller;

import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class MyLoginController {
    @RequestMapping("/user/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Model model, HttpSession session){

        if(StringUtils.hasText(username) && "123456".equals(password))
        {   session.setAttribute("session",username);
            return "redirect:/main.html";
        }
        else
        {
            model.addAttribute("msg","账号或秘密输入错误");

        return "index";
        }
    }
}

3.注册自己的拦截器registry,在config里实现方法,并定义过滤规则,注册在继承了webMvc的配置类里

    //注册一个自己的拦截器
//写清楚自己想要拦截的路径和放行的路径,然后务必写全!!!
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").excludePathPatterns(
                "/index.html","/","/user/login","/static/**");

        //设置拦截的目标
        //拦截了所有文件
        //其中放行了登录界面,主页面和静态资源!!!
    }

5.实现员工查询

当前端内容很多重复时,即公共部分很多为了提高复用性,可以使用

thymeleaf里的 th:fragment来提取公共部分,使用insert,replace,include来导入

具体用法:

侧边栏高亮设置

自己页面传输的参数

<!--侧边栏-->

<div th:insert="~{commons/commons::sidebar(active='main')}"></div>

公共部分的网页

     三元表达式来显示高亮-->
                        <a th:class="${active == 'main'?'nav-link active':'nav-link'} "
                           href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">
                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home">
                                <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
                                <polyline points="9 22 9 12 15 12 15 22"></polyline>
                            </svg>
                            仪表盘 <span class="sr-only">(current)</span>
                        </a>

将后端的数据在前端展示

<h2>员工信息</h2>
				<div class="table-responsive">
					<table class="table table-striped table-sm">
						<thead>
						<tr>
							<th>工号</th>
							<th>姓名</th>
							<th>邮箱</th>
							<th>性别</th>
							<th>部门</th>
							<th>生日</th>
							<th>操作</th>
						</tr>
						</thead>
						<tbody>
						<tr th:each="emp:${employee}">
							<td th:text="${emp.getId()}"></td>
							<td th:text="${emp.getLastName()}"></td>
							<td th:text="${emp.getEmail()}"></td>
							<td th:text="${emp.getGender()=='1'?'男':'女'}"></td>
							<td th:text="${emp.department.getDepartmentName()}"></td>
<!--							//设置时间格式-->
							<td
									th:text="${#dates.format(emp.getBirth(),'yyyy-MM-dd HH:mm:ss')}"></td>
						<td>
							<button class="btn-sm btn-primary">编辑</button>
							<button class="btn-sm btn-danger">删除</button>
						</td>
						</tr>
						</tbody>
					</table>
				</div>

事件(date的工具类)

<!-- false -->
<p th:text="${#strings.isEmpty(message)}"></p>
<!-- 2017-07-12 00:37:25 -->
<p th:text="${#dates.format(now, 'yyyy-MM-dd HH:mm:ss')}"></p>

6.增加员工的实现

增加页面

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org" >

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="">
  <meta name="author" content="">

  <title>Dashboard Template for Bootstrap</title>
  <!-- Bootstrap core CSS -->
  <link th:href="@{css/bootstrap.min.css}" rel="stylesheet">

  <!-- Custom styles for this template -->
  <link th:href="@{css/dashboard.css}" rel="stylesheet">
  <style type="text/css">
    /* Chart.js */

    @-webkit-keyframes chartjs-render-animation {
      from {
        opacity: 0.99
      }
      to {
        opacity: 1
      }
    }

    @keyframes chartjs-render-animation {
      from {
        opacity: 0.99
      }
      to {
        opacity: 1
      }
    }

    .chartjs-render-monitor {
      -webkit-animation: chartjs-render-animation 0.001s;
      animation: chartjs-render-animation 0.001s;
    }
  </style>
</head>

<body>
<!--	头横栏-->
<div th:insert="~{commons/commons :: headBar}"></div>

<div class="container-fluid">
  <div class="row">

    <div th:insert="~{commons/commons::sidebar(active='Cur.html')}"></div>
    <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<!--      添加员工页面-->
      <form th:action="@{/realAdd}" method="get">
        <div class="form-group">
          <label for="1" >工号</label>
          <input type="text"  id="1"
                name="id" >
        </div>
        <div class="form-group">
          <label for="2">名字</label>
          <input type="text" id="2" name="lastName">
        </div>

        <div class="form-group">
          <label for="3">邮箱</label>
          <input type="email"  placeholder="[email protected]" id="3" name="email">
        </div>
        <div class="form-group">
          <label>性别</label>
         <div class="form-check form-check-inline">
                <input class="form-check-input" type="radio" name="gender" value="1">
                <label class="form-check-label">男</label>
         </div>

          <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender" value="0">
            <label class="form-check-label">女</label>
          </div>
        </div>
        <div class="form-group">
          <label for="5">部门</label>
          <select id="5" name="department.id" >
              <option th:each="deps:${dep}" th:value="${deps.getId()}"
                      th:text="${deps.getDepartmentName()}">

              </option>

          </select>
        </div>

          <div class="form-group">
              <label for="26">生日</label>
              <input type="text" id="26" name="birth">
          </div>

       <button type="submit" class="btn btn-primary">添加</button>
      </form>

    </main>
  </div>
</div>

<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="asserts/js/popper.min.js"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>

<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js"></script>
<script>
  feather.replace()
</script>

<!-- Graphs -->
<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
<script>
  var ctx = document.getElementById("myChart");
  var myChart = new Chart(ctx, {
    type: 'line',
    data: {
      labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
      datasets: [{
        data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
        lineTension: 0,
        backgroundColor: 'transparent',
        borderColor: '#007bff',
        borderWidth: 4,
        pointBackgroundColor: '#007bff'
      }]
    },
    options: {
      scales: {
        yAxes: [{
          ticks: {
            beginAtZero: false
          }
        }]
      },
      legend: {
        display: false,
      }
    }
  });
</script>

</body>

</html>

控制类的填写

//    跳转到添加页面
    @RequestMapping("/toAdd")
    public String addEmp(Model model)
    {

        model.addAttribute("dep",departmentDao.getDepartment());
        return "emp/addEmp";
    }
//执行添加
    @RequestMapping("/realAdd")
    public String realAdd(Employee employee)
    {
        System.out.println("=================");
        System.out.println(employee);

        employeeDao.push(employee);

        return "redirect:/Cus";

    }

7.实现员工的修改

修改页面

<!DOCTYPE html>
<!-- saved from url=(0052)http://getbootstrap.com/docs/4.0/examples/dashboard/ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org" >

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="">
  <meta name="author" content="">

  <title>Dashboard Template for Bootstrap</title>
  <!-- Bootstrap core CSS -->
  <link th:href="@{css/bootstrap.min.css}" rel="stylesheet">

  <!-- Custom styles for this template -->
  <link th:href="@{css/dashboard.css}" rel="stylesheet">
  <style type="text/css">
    /* Chart.js */

    @-webkit-keyframes chartjs-render-animation {
      from {
        opacity: 0.99
      }
      to {
        opacity: 1
      }
    }

    @keyframes chartjs-render-animation {
      from {
        opacity: 0.99
      }
      to {
        opacity: 1
      }
    }

    .chartjs-render-monitor {
      -webkit-animation: chartjs-render-animation 0.001s;
      animation: chartjs-render-animation 0.001s;
    }
  </style>
</head>

<body>
<!--	头横栏-->
<div th:insert="~{commons/commons :: headBar}"></div>

<div class="container-fluid">
  <div class="row">

    <div th:insert="~{commons/commons::sidebar(active='Cur.html')}"></div>
    <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
      <!--      添加员工页面-->
      <form th:action="@{/update}" method="get">
        <div class="form-group">
          <label for="1" >工号</label>
          <input type="text"  id="1"
                 name="id" readonly th:value="${emp.getId()}">
        </div>
<!--        工号设置为可读-->
        <div class="form-group">
          <label for="2">名字</label>
          <input type="text" id="2" name="lastName" th:value="${emp.getLastName()}">
        </div>

        <div class="form-group">
          <label for="3">邮箱</label>
          <input type="email"  placeholder="[email protected]" id="3" name="email"
                 th:value="${emp.getEmail()}">
        </div>
        <div class="form-group">
          <label>性别</label>
          <div class="form-check form-check-inline" >
            <input class="form-check-input" type="radio" name="gender" value="1"
                   th:checked="${emp.getGender() eq '1'? 'checked':''}">
            <label class="form-check-label">男</label>
          </div>

          <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender" value="0"
                   th:checked="${emp.getGender() eq '0'? 'checked':''}">
            <label class="form-check-label">女</label>
          </div>
        </div>
        <div class="form-group">
          <label for="5">部门</label>
          <select id="5" name="department.id" >
            <option th:each="deps:${dep}"
                    th:selected="${deps.getId()==emp.getDepartment().getId()}"
                    th:value="${deps.getId()}"
                    th:text="${deps.getDepartmentName()}">
<!--          三元表达式来判断-->
            </option>

          </select>
        </div>

        <div class="form-group">
          <label for="26">生日</label>
          <input type="text" id="26" name="birth"
                 th:value="${#dates.format(emp.getBirth(), 'yyyy/MM/dd')}">
        </div>

        <button type="submit" class="btn btn-primary">修改</button>
      </form>

    </main>
  </div>
</div>

<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="asserts/js/jquery-3.2.1.slim.min.js"></script>
<script type="text/javascript" src="asserts/js/popper.min.js"></script>
<script type="text/javascript" src="asserts/js/bootstrap.min.js"></script>

<!-- Icons -->
<script type="text/javascript" src="asserts/js/feather.min.js"></script>
<script>
  feather.replace()
</script>

<!-- Graphs -->
<script type="text/javascript" src="asserts/js/Chart.min.js"></script>
<script>
  var ctx = document.getElementById("myChart");
  var myChart = new Chart(ctx, {
    type: 'line',
    data: {
      labels: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
      datasets: [{
        data: [15339, 21345, 18483, 24003, 23489, 24092, 12034],
        lineTension: 0,
        backgroundColor: 'transparent',
        borderColor: '#007bff',
        borderWidth: 4,
        pointBackgroundColor: '#007bff'
      }]
    },
    options: {
      scales: {
        yAxes: [{
          ticks: {
            beginAtZero: false
          }
        }]
      },
      legend: {
        display: false,
      }
    }
  });
</script>

</body>

</html>

特别注意单选框和下拉框的默认值选取

        <div class="form-group">
          <label>性别</label>
          <div class="form-check form-check-inline" >
            <input class="form-check-input" type="radio" name="gender" value="1"
                   th:checked="${emp.getGender() eq '1'? 'checked':''}">
            <label class="form-check-label">男</label>
          </div>

          <div class="form-check form-check-inline">
            <input class="form-check-input" type="radio" name="gender" value="0"
                   th:checked="${emp.getGender() eq '0'? 'checked':''}">
            <label class="form-check-label">女</label>
          </div>
        </div>
        <div class="form-group">
          <label for="5">部门</label>
          <select id="5" name="department.id" >
            <option th:each="deps:${dep}"
                    th:selected="${deps.getId()==emp.getDepartment().getId()}"
                    th:value="${deps.getId()}"
                    th:text="${deps.getDepartmentName()}">
<!--          三元表达式来判断-->
            </option>

          </select>
        </div>

一个用checked,一个用selected来,其实后面都是一个判断表达式

上面的性别选择可以写成 th:checked="${emp.getGender()=='1'}" 这样子,更加省力,不需要三元表达式

修改的controller层的方法实现

    //跳转到修改页面
    @RequestMapping("/toUpdate")
    public String toUpdate(@RequestParam("id") Integer id,Model model)
    {
        //根据前端的id查找这个员工
        Employee employeeById = employeeDao.getEmployeeById(id);
        model.addAttribute("dep",departmentDao.getDepartment());

        //将这个员工的信息发送给更新表
        model.addAttribute("emp",employeeById);
        return "emp/updateEmp";
    }

    //实现修改
    @RequestMapping("/update")
    public String realUpdate(Employee employee)
    {   //调用修改
        System.out.println(employee);

        employeeDao.updateEmployee(employee);

        return "redirect:/Cus";
    }

8.删除一个员工

dao层方法定义

 //删除一个员工的方法
    public void deleteEmp(@RequestParam("id") Integer id)
    {
        employee.remove(id);
    }

controller层方法调用

   @RequestMapping("/delete")

    public String deleteEmp(Integer id)
    {
        employeeDao.deleteEmp(id);

        return "redirect:/Cus";
    }

9.错误页面处理

在spring-boot中,只需要在template目录下创建一个error目录,里面放着各种错误命名的html即可,它会自动寻找,如404.html

10.注销功能的实现

定义实现的controller

    //注销功能
    @RequestMapping("/signOut")
    public String sgnOut(HttpSession session)
    {
        session.invalidate();//清除session

        return "redirect:main.html";
    }

如何开发一个网站

1.前端:用模板,bootstrap,layui ......

2.设计数据库

3.前端能够自动运行,独立化工程

4.数据接口如何对接:json,对象 all in one

5.前后端联调测试!

后台模板 X-admin

SpringData

springboot里有很多XXXtemplate,,都是它自己封装好的bean,可以拿来直接使用!!!

整合jdbc

1.添加依赖

2.编写配置文件

server:
  port: 8081

#配置jdbc数据源
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?useSSl=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver

3.编写控制类来测试

package com.example.springbootdata.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;

@Controller
public class MyJdbcController {
    @Autowired
    JdbcTemplate jdbcTemplate; //使用springboot自带的jdbc模板,并将数据源自动注入

    @ResponseBody
    @RequestMapping("/select")
    public String run(){

        String sql = "select * from mybatis.user"; //sql语句
		
        //执行语句
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);

        return maps.toString();
    }


}

默认数据源:HikariPool

Druid数据源 德鲁伊

默认数据源:HikariPool

1.导入依赖

  <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.20</version>
        </dependency>

<dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

2.修改yaml配置文件

server:
  port: 8081

#配置jdbc数据源
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?useSSl=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    #配置数据源类型 druid
    type: com.alibaba.druid.pool.DruidDataSource
    #最大连接池数量
    max-active: 20
    #初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
    initial-size: 10
    # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,
    # 并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
    max-wait: 60000
    #最小连接池数量
    min-idle: 5
    #有两个含义:
    #1: Destroy线程会检测连接的间隔时间
    #2: testWhileIdle的判断依据,详细看testWhileIdle属性的说明
    time-between-eviction-runs-millis: 60000
    #配置一个连接在池中最小生存的时间,单位是毫秒
    min-evictable-idle-time-millis: 180000
    #用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
    validation-query: select 'x'
    #连接有效性检查的超时时间 1 秒
    validation-query-timeout: 1
    #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
    test-on-borrow: false
    #设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过minEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查
    test-while-idle: true
    #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
    test-on-return: false
    #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
    pool-prepared-statements: true
    #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,
    # 不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
    max-open-prepared-statements: 20
    #数据库链接超过3分钟开始关闭空闲连接 秒为单位
    remove-abandoned-timeout: 1800
    #对于长时间不使用的连接强制关闭
    remove-abandoned: true
    #打开后,增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,
    # 每次检查强制验证连接有效性. 参考:https://github.com/alibaba/druid/wiki/KeepAlive_cn
    keep-alive: true
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    #是否超时关闭连接 默认为false ,若为true 就算数据库恢复连接,也无法连接上
    break-after-acquire-failure: false
    #设置获取连接出错时的自动重连次数
    connection-error-retry-attempts: 1
    #设置获取连接时的重试次数,-1为不重试
    not-full-fimeout-retry-count: 2
    #重连间隔时间 单位毫秒
    acquire-retry-delay: 10000
    # 设置获取连接出错时是否马上返回错误,true为马上返回
    fail-fast: true
    #属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
    #监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
    filters: stat,wall

3.编写配置类来实现

package com.example.springbootdata.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.jakarta.StatViewServlet;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;

@Configuration
public class MyDruidConfig {
//    绑定yaml配置文件
    @Bean
    @ConfigurationProperties("spring.datasource")
    public DataSource myDruidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    //因为springboot内置了servlet容器,然后没有web.xml文件,所以这里需要注册一个servlet
    public ServletRegistrationBean a(){
        ServletRegistrationBean<StatViewServlet> statViewServletServletRegistrationBean =
                new ServletRegistrationBean<>(new StatViewServlet(),"/druid/*");

        //后台需要有人登录,后台配置
        HashMap<String,String> initParams = new HashMap<>();

        //增加配置

        initParams.put("loginUsername","admin");
        initParams.put("loginPassword","123456");
        //把该配置加入进去
        statViewServletServletRegistrationBean.setInitParameters(initParams);

        return statViewServletServletRegistrationBean;
    }
}

整合Mybatis

整合包 mybatis-spring-boot-start

1.导入依赖

 <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

2.创建实体类pojo

package com.li.pojo;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;

    private String pwd;
}

3.编写Mapper

package com.li.mapper;

import com.li.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface UserMapper {
    List<User> getUser();
}

4.在resource下创建mybatis再创建mapper然后写mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.li.mapper.UserMapper">
    <select id="getUser" resultType="User">
        select * from mybatis.user
    </select>

</mapper>

5.在配置文件中导入配置

mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  type-aliases-package: com.li.pojo

SpringSecurity(安全)

shiro,springsecurity:很像,除了类不一样

AOP思想,有拦截器,记住我.....这些功能

Shiro

三大核心对象:

Subject 用户

SecurityManager 管理所有用户

Realm 连接数据

1.导入依赖

<dependencies>
<!--shiro核心-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>2.0.1</version>
        </dependency>
<!--        导入slf4j日志 下面三个包都是日志门面-->
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.28</version>
        </dependency>
<!--导入commons-logging包-->
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
        <!--导入log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>


    </dependencies>

2.编写两个resource是文件

2.1 log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

2.2 shiro.ini(这个是配置文件)

#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
# =============================================================================
# Quickstart INI Realm configuration
#
# For those that might not understand the references in this file, the
# definitions are all based on the classic Mel Brooks' film "Spaceballs". ;)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their assigned roles
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
# -----------------------------------------------------------------------------
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# 
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

3.编写运行类

Quickstart.java 要读懂

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.env.BasicIniEnvironment;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.lang.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        SecurityManager securityManager = new BasicIniEnvironment("classpath:shiro.ini").getSecurityManager();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum, so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        //登录操作
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        currentUser.logout();

        System.exit(0);
    }
}

大部分方法(Subject)

  • !currentUser.isAuthenticated()) 是否被认证
  • currentUser.login(token);登录
  • currentUser.getPrincipal() 获取当前用户的信息
  • currentUser.hasRole 是否有这个权限
  • currentUser.isPermitted 是否被允许
  • currentUser.logout(); 注销

都在上面那个java类里有体现了

整合Springboot

搭建环境

版本一定要兼容,不然一直报错,超级麻烦!!!,特别是shiro核心和整合包的版本一定要一致,不然就会报错!!!

1.导入依赖
<dependencies>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>2.0.1</version>
        </dependency>
        <!--导入Shiro整合springboot的包-->
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>2.0.1</version>
        </dependency>


        <!--导入shiro-web的包-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2.创建一个realm类对象,然后编写配置类

2.1编写realm对象,继承了AuthorizingRealm

package com.li.config;

import org.apache.catalina.realm.AuthenticatedUserRealm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("进入了授权");
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("进入了认证");
        return null;
    }
}

2.2 编写配置类config(必须定义)

package com.li.config;

import org.apache.catalina.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;

@Configuration
public class ShiroConfig{
    // 1. 创建ShiroFilterFactoryBean的Bean
    //1.ShiroFilterFactoryBean;
    @Bean
    public ShiroFilterFactoryBean getBean(@Qualifier("getManager")DefaultWebSecurityManager defaultWebSecurityManager){

        // 创建一个ShiroFilterFactoryBean对象
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

        // 设置ShiroFilterFactoryBean的SecurityManager属性
        bean.setSecurityManager(defaultWebSecurityManager);

        // 返回ShiroFilterFactoryBean对象
        return bean;
    }


    // 2. 创建一个DefaultWebSecurityManager的Bean
    //2.DefaultWebSecurityManager;
    @Bean
    public DefaultWebSecurityManager getManager(@Qualifier("getRealm") UserRealm realm){
        // 创建一个DefaultWebSecurityManager对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 设置DefaultWebSecurityManager的Realm属性为传入的realm
        defaultWebSecurityManager.setRealm(realm);
        // 返回创建的DefaultWebSecurityManager对象
        return defaultWebSecurityManager;

    }


    // 3. 定义一个Realm对象
    //3.Realm对象
    @Bean
    public UserRealm getRealm(){

        // 返回一个UserRealm对象
        return new UserRealm();
    }

}

实现拦截(必须是springboot2.x,三的话拦截不了)

package com.li.config;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    // 1. 创建一个ShiroFilterFactoryBean的Bean
    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 设置SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 定义拦截规则
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        filterChainDefinitionMap.put("/user/add", "authc"); // 登录页面无需认证
        filterChainDefinitionMap.put("/user/update", "authc"); // 登录页面无需认证
        /*
         anon(匿名):不需要身份验证即可访问。
        
        authc(身份验证):需要身份验证才能访问。
        
        authcBasic(基本身份验证):需要HTTP基本身份验证才能访问。
        
        user(用户):只有已记住的用户才能访问,或者已经身份验证的用户也可以。
        
        perms(权限):需要指定的权限才能访问。例如:"perms[user:create]" 表示需要user:create权限。
        
        roles(角色):需要指定的角色才能访问。例如:"roles[admin]" 表示需要admin角色。
        
        ssl(SSL):需要SSL连接才能访问。
        
        rest(REST):基于REST风格的URL权限控制,自动根据请求的方法(如GET、POST、DELETE等)进行权限控制。
        
        port(端口):访问的URL的端口必须匹配才允许访问。*/

        // 如果需要更复杂的权限控制,可以添加更多规则,例如:
        // filterChainDefinitionMap.put("/admin/**", "roles[admin]"); // admin目录下的所有资源需要admin角色

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        // 设置登录页面
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        // 设置未授权页面(可选)
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        return shiroFilterFactoryBean;
    }

    // 2. 创建一个DefaultWebSecurityManager的Bean
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("userRealm") Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    // 3. 定义一个Realm对象
    @Bean(name = "userRealm")
    public UserRealm userRealm() {
        return new UserRealm();
    }

    // 注意:UserRealm需要您自己实现,并且需要继承org.apache.shiro.realm.AuthorizingRealm
    // 在UserRealm中,您需要重写doGetAuthorizationInfo和doGetAuthenticationInfo方法
    // 来实现权限和身份验证逻辑
}

身份验证

重写认证方法

package com.li.config;

import org.apache.catalina.realm.AuthenticatedUserRealm;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


public class UserRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("进入了授权");
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //验证token,虚拟的建造数据
        String username = "root";
        String password = "123456";

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;//强转为用户的认证

        //账号认证自己做
        if(!token.getUsername().equals(username))
        {
            return null;//会自带抛出异常
        }
        //密码认证,shiro来做
        return new SimpleAuthenticationInfo("",password,"");
    }
}

控制器里编写登录验证的方法

 @RequestMapping("/login")
    public String login(String username, String password, Model model){
        Subject currentUser = SecurityUtils.getSubject();

        if (!currentUser.isAuthenticated()) {
            //如果还未认证,配置token
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);

            try {
                currentUser.login(token);
                return "index";
            }
            catch (UnknownAccountException uae)
            {
                    model.addAttribute("msg","账号不存在!");

                    return "login"; //错误就返回登录页面
            }
            catch (IncorrectCredentialsException ice)
            {
                model.addAttribute("msg","密码错误!");
                return "login";
            }
        }

        return "index"; //正确就进入主页面



    }

整合Mybatis

1.导入依赖

一定注意版本问题,报错了尝试降低版本

   <dependencies>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>2.0.1</version>
        </dependency>
        <!--导入Shiro整合springboot的包-->
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>2.0.1</version>
        </dependency>


        <!--导入shiro-web的包-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.20</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.experimental/spring-native -->
      
    </dependencies>

2.编写pojo
package com.li.pojo;


public class User {
    private int id;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }

    private String name;

    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

3.编写连接数据库和mybatis的配置 德鲁伊
server:
  port: 8081

#配置jdbc数据源
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?useSSl=true&useUnicode=true&characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    #配置数据源类型 druid
    type: com.alibaba.druid.pool.DruidDataSource
    #最大连接池数量
    max-active: 20
    #初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
    initial-size: 10
    # 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,
    # 并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
    max-wait: 60000
    #最小连接池数量
    min-idle: 5
    #有两个含义:
    #1: Destroy线程会检测连接的间隔时间
    #2: testWhileIdle的判断依据,详细看testWhileIdle属性的说明
    time-between-eviction-runs-millis: 60000
    #配置一个连接在池中最小生存的时间,单位是毫秒
    min-evictable-idle-time-millis: 180000
    #用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
    validation-query: select 'x'
    #连接有效性检查的超时时间 1 秒
    validation-query-timeout: 1
    #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
    test-on-borrow: false
    #设置从连接池获取连接时是否检查连接有效性,true时,如果连接空闲时间超过minEvictableIdleTimeMillis进行检查,否则不检查;false时,不检查
    test-while-idle: true
    #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
    test-on-return: false
    #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
    pool-prepared-statements: true
    #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,
    # 不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
    max-open-prepared-statements: 20
    #数据库链接超过3分钟开始关闭空闲连接 秒为单位
    remove-abandoned-timeout: 1800
    #对于长时间不使用的连接强制关闭
    remove-abandoned: true
    #打开后,增强timeBetweenEvictionRunsMillis的周期性连接检查,minIdle内的空闲连接,
    # 每次检查强制验证连接有效性. 参考:https://github.com/alibaba/druid/wiki/KeepAlive_cn
    keep-alive: true
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    #是否超时关闭连接 默认为false ,若为true 就算数据库恢复连接,也无法连接上
    break-after-acquire-failure: false
    #设置获取连接出错时的自动重连次数
    connection-error-retry-attempts: 1
    #设置获取连接时的重试次数,-1为不重试
    not-full-fimeout-retry-count: 2
    #重连间隔时间 单位毫秒
    acquire-retry-delay: 10000
    # 设置获取连接出错时是否马上返回错误,true为马上返回
    fail-fast: true
    #属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
    #监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
    filters: stat,wall
mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  type-aliases-package: com.li.pojo

4.编写Dao,service,controller层的方法,编写mapper.xml
5.整合shiro实现从数据库查找密码来完成登录认证
package com.li.config;

import com.li.pojo.User;
import com.li.service.UserServiceImp;
import org.apache.catalina.realm.AuthenticatedUserRealm;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserServiceImp userServiceImp;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("进入了授权");
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //真实的数据库连接
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;//强转为用户的认证
        User user = userServiceImp.getUserByName(token.getUsername());
        //账号认证自己做
        if (user == null)
        {   //查找不到人
            return null;//自动抛出异常
        }
        //密码认证,shiro来做
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
}

实现授权

先认证,再授权,通过认证传对象给授权

1.定义授权规则(user下的update路径有问题,所以改成了up,有问题是查找一下是否是命名问题,/**是指当前目录下的所有路由!)

 filterChainDefinitionMap.put("/user/up","perms[update:123]"); //必须授权了才能进来
        filterChainDefinitionMap.put("/user/add","perms[add:123]");

2.重写授权方法(给用户授权)

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        // 创建一个SimpleAuthorizationInfo对象用于存储授权信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 获取当前登录的用户对象
        Subject subject = SecurityUtils.getSubject();// 获取对象

        // 从用户对象中获取认证时传递过来的用户信息(User对象)
        User principal = (User) subject.getPrincipal();//接收认证传过来的对象

        // 打印用户信息
        System.out.println(principal);

        // 获取用户的权限字符串
        String perm = principal.getPerm();

        // 将权限字符串按照空格进行拆分,得到权限数组
        String[] s = perm.split(" "); //将权限分组

        // 遍历权限数组,将每个权限添加到SimpleAuthorizationInfo对象中
        for (int i = 0; i < s.length; i++) {
            System.out.println(s[i]);
            info.addStringPermission(s[i]);//全部权限授权
        }

        // 返回授权信息对象
        return info;
    }

3.用户是通过认证传输过来的

        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
//user就是被传输过来的

Swagger

号称世界上最流行的API框架

RestFulApi文档在线自动生成工具=》api文档与api定义同步更新

直接运行,可以在线测试api接口

支持多种语言:(Java,Php......)

在项目中使用

·需要springbox-swagger2

·springbox-ui

SpringBoot集成Swagger

1.导入依赖(springboot版本2.5.6)

<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>3.0.0</version>
</dependency>

2.配置swaggerConfig

package com.li.config;

import org.springframework.context.annotation.Configuration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
}

3.启动项目并访问

http://localhost:8081/swagger-ui.html

配置swagger

swagger的bean实例docket

改变apiinfo信息

package com.li.config;

import com.google.common.collect.Lists;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
    public static final Contact DEFAULT_CONTACT = new Contact("1", "sad", "d");
    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
    }

    public ApiInfo apiInfo(){

        return  new ApiInfo("小李", "Api Documentation", "1.0", "urn:tos", DEFAULT_CONTACT,
                "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());

    }

}

确定swagger的扫描路径和swagger作用的开发环境(测试,开发,发布......)

三个配置文件

主配置文件里确定环境

spring.application.name=swagger
spring.profiles.active=dev

#application.properties:默认配置
#application-dev.properties:开发环境
#application-test.properties:测试环境
#application-prod.properties:生产环境

#至于哪个具体的配置文件会被加载,需要在application.properties文件中通过spring.profiles.active属性来设置,其值对应{profile}值。

swagger配置类的编写

package com.li.config;

import com.google.common.collect.Lists;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.function.Predicate;

@Configuration
@EnableSwagger2 //开启swagger2
public class SwaggerConfig {
    public static final Contact DEFAULT_CONTACT = new Contact("1", "sad", "d");
    @Bean
    public Docket docket(Environment environment){

        Profiles profiles = Profiles.of("prod","test"); //判断现在的环境是否是需要开启swagger的环境
        boolean flag = environment.acceptsProfiles(profiles);

        System.out.println(flag);
        return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
                //在 select和build里定义要扫描的包或路径 apis ,如果是paths那就是过滤器,现在限制了只扫描 com.li.controller
                //这个路径下的包
                .apis(RequestHandlerSelectors.basePackage("com.li.controller") ).build().enable(flag);

//                enable决定是否开启swagger

    }

    public ApiInfo apiInfo(){

        return  new ApiInfo("小李", "Api Documentation", "1.0", "urn:tos", DEFAULT_CONTACT,
                "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList());

    }

}

只要接口里用到了实体类,swagger里就会识别到实体类,@ApiModel(),就是加上注释,一般用实体类上

try it out 和 execute 执行

任务

1.异步任务

在有线程的类上加上@Asyn这个注解

如下

package com.example.task.services;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
@Async
public class MyAsyn {

public void run() throws InterruptedException {
    Thread.sleep(3000);

    System.out.println("正在处理.....");
}

}

在启动类上加上@EnableAsyn这个注解

2.定时任务

两个接口

  • TaskExecutor
  • TaskScheduler

1.使用,首先在springboot的启动类里加入@EnableScheduling这个注解

通配符的作用(查看博客):

https://blog.csdn.net/jiguangdaye/article/details/122437768

2.编写定时任务

package com.example.task.services;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class MyScheduled {
    // 秒 分 时 日 月 周几 年  年一般省略,中间用空格隔开
    @Scheduled(cron = "0/2 * * * * ? ")
    //每两秒执行一次
    public void run(){
        System.out.println("正在执行定时任务");
    }


}

3.启动spring boot启动类,到达时间就会执行该任务

3.邮件发送

1.打开qq邮箱pop服务

2.导入spring boot 的mail start

<!--开启邮箱的服务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>

3.编写配置文件

spring.application.name=Task
server.port=8081
[email protected]
#开启服务后qq邮箱给的加密密码
spring.mail.password=cepvjjtnjusccief
#发送平台
spring.mail.host=smtp.qq.com
#开启邮箱认证
spring.mail.properties.smtp.ssl.enable=true

4.编写发送邮件的类和方法

下面的代码在测试类里编写了

package com.example.task;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMailMessage;
import org.springframework.mail.javamail.MimeMessageHelper;

import java.io.File;

@SpringBootTest
class TaskApplicationTests {
    @Autowired
JavaMailSender mailSender;
   @Test
    public void sendSimpleMail(){
       //发送一个简单的邮件

       SimpleMailMessage msg = new SimpleMailMessage();
       msg.setSubject("你好啊");//设置标题
       msg.setText("这是java的自动发送邮件");
       //发送给谁
       msg.setTo("[email protected]");
       //发送者是谁
       msg.setFrom("[email protected]");
       mailSender.send(msg);
   }

   @Test
    public void sendMimMail() throws MessagingException {//发送一个复杂的邮件

       MimeMessage msg = mailSender.createMimeMessage();
        //帮助创建这个复杂的邮件
       MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(msg,true);//支持多文件

       mimeMessageHelper.setSubject("自动发送学习笔记");

       mimeMessageHelper.setText("<h1 style=\"font-size: 4rem\">这是我的学习笔记</h1>");
        //添加附件
       mimeMessageHelper.addAttachment("python爬虫.md", new File("C:\\Users\\86187\\Desktop\\学习笔记\\python爬虫.md"));
       mimeMessageHelper.addAttachment("python学习.txt", new File("C:\\Users\\86187\\Desktop" +
               "\\学习笔记\\python学习.txt"));
       mimeMessageHelper.addAttachment("mybatis.md", new File("C:\\Users\\86187\\Desktop" +
               "\\学习笔记\\Mybatis配置文件.md"));
       mimeMessageHelper.addAttachment("spring.md", new File("C:\\Users\\86187\\Desktop" +
               "\\学习笔记\\Spring配置.md"));
       mimeMessageHelper.addAttachment("springMVC.md", new File("C:\\Users\\86187\\Desktop" +
               "\\学习笔记\\SpringMVC.md"));
       mimeMessageHelper.addAttachment("Springboot.md", new File("C:\\Users\\86187\\Desktop" +
               "\\学习笔记\\Springboot.md"));

        mimeMessageHelper.setTo("[email protected]");
        mimeMessageHelper.setFrom("[email protected]");
       mailSender.send(msg);
   }

}

分布式 dubbo+zookeeper+springboot

RPC(远程进程调度):序列化+通讯

Dubbo来进行RPC

zookeeper:注册中心(必须要)

dubbo-admin:监控后台,查看哪些服务被注册了,哪些服务被消费了,可以不要。

下载网址

https://github.com/DogerRain/dubbo-samples-test/tree/f1d29e599286c5779042ab87008f0377e72f8a11/doc/software

根目录下打包,跳过测试执行

mvn clean package -Dmaven.test.skip=true

访问网站

  • (http://localhost:7001/)

用户名和密码都是root

springboot集成dubbo看官方快速入门文档

2 - Dubbo Spring Boot Starter 开发微服务应用 | Apache Dubbo

一定要先启动zookeeper!!! 不然会报错,然后 使用的是jdk1.8