SpringBoot项目

目录

一、前后端界面搭建

1.项目前期准备

2.数据表介绍 

 3.构建SpringBoot项目

4、首页功能实现 

① 首页数据绑定语法 

② 公共跳转

5、用户登陆 

① 构建UserDto,定义mobile和password属性 

② 定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp) 

③ 在IUserService中定义Login方法,并返回JsonResponseBody 

④ 在UserServiceImpl实现类中要做五个事情 

⑤ 全局异常处理 

⑥ 自定义注解参数校验(JSR303) 

6、前端及数据库密码加密 

 7、登录令牌管理


一、前后端界面搭建

1.项目前期准备

技术点介绍

前端:Freemarker、jQuery

后端:SpringBoot、MybatisPlus、Lombok

中间件:Redis

2.数据表介绍 

用户表:t_user

商品表:t_goods

订单表:t_order

订单项表:t_order_item

数据源表:t_dict_type

数据项表:t_dict_data

 # 后续微服务秒杀项目所用
秒杀商品表:t_seckill_goods
秒杀订单表:t_seckill_order

 3.构建SpringBoot项目

1.创建项目,这里面我什么也没有勾, 完全按照我们下面的依赖就好了,

为什么呢?因为有些版本是不兼容的,可能同样的java代码,它会跟随这你的框架版本的不一样,而导致失效,报一些莫名其妙的错误。

2.创建SpringBoot项目并配置POM,涉及到的所有依赖

spring-boot-starter-freemarker
spring-boot-starter-web
mysql-connector-java 5.1.44
lombok
<!-- mybatis plus依赖 -->
mybatis-plus-boot-starter 3.4.0
mybatis-plus-generator 3.4.0
<!-- hariki连接池 -->
HikariCP
<!-- MD5依赖 -->
commons-codec
commons-lang3 3.6
<!-- valid验证依赖 -->
spring-boot-starter-validation
<!-- redis -->
spring-boot-starter-data-redis

pom.xml参考如下:

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.zking</groupId>
    <artifactId>testspbootpro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>testspbootpro</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--freemarker-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

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

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.44</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--junit-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- mybatis plus依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!-- mybatis-plus-generator依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.0</version>
        </dependency>

        <!--hariki-->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>

        <!-- MD5依赖 -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.6</version>
        </dependency>

        <!-- valid验证依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

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

        <!--commons-pool2 对象池依赖 2.0版本的lettuce需要-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!--spring-session将session借助于第三方存储(redis/mongodb等等),默认redis-->
        <!--<dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>-->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-easysdk</artifactId>
            <version>2.0.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3. 因为我们什么也没有勾,里面什么也没有,先把它转为yml版本的,然后配置application.yml

1)添加数据库及连接池配置
2)添加freemarker配置
3)添加mybatis-plus配置
4)添加logging日志配置

application.yml参考如下:

 server:
    port: 8081
    servlet:
        context-path: /
spring:
    datasource:
        url: jdbc:mysql://localhost:3306/spbootpro?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=UTF8
        driver-class-name: com.mysql.jdbc.Driver
        password: 123456
        username: root
        hikari:
            # 最小空闲连接数量
            minimum-idle: 5
            # 空闲连接存活最大时间,默认600000(10分钟)
            idle-timeout: 180000
            # 连接池最大连接数,默认是10
            maximum-pool-size: 10
            # 此属性控制从池返回的连接的默认自动提交行为,默认值:true
            auto-commit: true
            # 连接池名称
            pool-name: MyHikariCP
            # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
            max-lifetime: 1800000
            # 数据库连接超时时间,默认30秒,即30000
            connection-timeout: 30000
    freemarker:
        #设置编码格式
        charset: UTF-8
        #后缀
        suffix:
        #文档类型
        content-type: text/html
        #模板前端
        template-loader-path: classpath:/templates/
        #启用模板
        enabled: true
    mvc:
        static-path-pattern: /static/**
    redis:
        #服务端IP
        host: 192.168.29.128
        #端口
        port: 6379
        #密码
        password: 123456
        #选择数据库
        database: 0
        #超时时间
        timeout: 10000ms
        #Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问
        #Lettuce线程安全,Jedis线程非安全
        lettuce:
            pool:
                #最大连接数,默认8
                max-active: 8
                #最大连接阻塞等待时间,默认-1
                max-wait: 10000ms
                #最大空闲连接,默认8
                max-idle: 200
                #最小空闲连接,默认0
                min-idle: 5
#mybatis-plus配置
mybatis-plus:
    #所对应的 XML 文件位置
    mapper-locations: classpath*:/mapper/*Mapper.xml
    #别名包扫描路径
    type-aliases-package: com.jwj.spbootpro.model
    configuration:
        #驼峰命名规则
        map-underscore-to-camel-case: true
#日志配置
logging:
    level:
        com.jwj.spbootpro.mapper: debug

4. 启动类配置

package com.jwj.spbootpro;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@MapperScan({"/com.jwj.spbootpro.mapper"})
@EnableTransactionManagement
@SpringBootApplication
public class SpbootproApplication {

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

}

5.首页访问

 导入前端页面及页面对应的js/css/images文件

 把它copy到resource里面去,这里面的都是静态,死数据,等待这我们把这些数据变活。

 

我们来测试一下这个项目到底搭建成功没有,创建一个类IndexController.java

package com.jwj.spbootpro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author 敢敢
 * @site www.javajwj.com
 * @company xxx公司
 * @create  2022-11-05 15:21
 */
@Controller
public class IndexController {
    @RequestMapping("/")
    public String index(){
//        #后缀
//        suffix:
//        #模板前端
//        template-loader-path: classpath:/templates/
//        /templates+index.html+""  前缀+逻辑视图名+后缀
        return "index.xml";
    }
}

运行一下:访问http://localhost:8081/,出现界面如下,代表项目构建成功

目前我们看到的都是死数据,我们要把它变活。 

4、首页功能实现 

主要查询出 装饰摆件 和 墙式壁挂两个类别的上坪在首页进行展示 

 SELECT * FROM t_goods;
SELECT * FROM t_goods where goods_type = '01';
SELECT * FROM t_goods where goods_type = '07';

SELECT * FROM t_dict_type;
SELECT * FROM t_dict_data;

导入我们的Mybatis-plus生成器和工具类

导入模块生成类 

将自定义的代码生成模板放到templates目录下,覆盖Mybatis-plus默认的代码生成默认

为了生效,我们把target删除掉。

注意:在CodeGenerator.java表里面把表名改为你自己的表名

package com.jwj.spbootpro.generator;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CodeGenerator {

    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir") + "/spbootpro";
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("jwj");
        gc.setOpen(false);
        gc.setBaseColumnList(true);
        gc.setBaseResultMap(true);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/spbootpro?useUnicode=true&useSSL=false&characterEncoding=utf8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        //pc.setModuleName(scanner("模块名"));
        pc.setParent("com.jwj.spbootpro");
        //设置包名
        pc.setEntity("model");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mybatis-generator/mapper2.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录,自定义目录用");
                if (fileType == FileType.MAPPER) {
                    // 已经生成 mapper 文件判断存在,不想重新生成返回 false
                    return !new File(filePath).exists();
                }
                // 允许生成模板文件
                return true;
            }
        });
        */
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();

        // 配置自定义输出模板
        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        templateConfig.setMapper("templates/mybatis-generator/mapper2.java");
        templateConfig.setEntity("templates/mybatis-generator/entity2.java");
        templateConfig.setService("templates/mybatis-generator/service2.java");
        templateConfig.setServiceImpl("templates/mybatis-generator/serviceImpl2.java");
        templateConfig.setController("templates/mybatis-generator/controller2.java");
        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        strategy.setEntitySerialVersionUID(false);
        // 公共父类
        //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
        strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix("t_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }
}

 运行CodeGenerator.java里面的main方法,生成成功后的截图

生成出来的代码要被spring所接管

首页方法改造 IndexController.java 

package com.jwj.spbootpro.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jwj.spbootpro.model.Goods;
import com.jwj.spbootpro.service.IGoodsService;
import com.jwj.spbootpro.utils.DataUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

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

/**
 * @author 敢敢
 * @site www.javajwj.com
 * @company xxx公司
 * @create  2022-11-07 18:58
 */
@Controller
public class IndexController {
    @Autowired
    private IGoodsService goodsService;


    @RequestMapping("/")
    public String index(Model model){
//        摆件花艺
        List<Goods> goods01 = goodsService.list(new QueryWrapper<Goods>()
                .eq("goods_type", "01")
                .last("limit 6"));

//        壁挂北欧
        List<Goods> goods07 = goodsService.list(new QueryWrapper<Goods>()
                .eq("goods_type", "07")
                .last("limit 12"));

//        为了方便首页数据展示,方便摆放
        DataUtils<Goods> dataUtils = new DataUtils<>();
        Map<String, List<Goods>> gt01 = dataUtils.transfor(3,goods01);
        Map<String, List<Goods>> gt07 = dataUtils.transfor(4, goods07);
        model.addAttribute("gt01",gt01);
        model.addAttribute("gt07",gt07);
        return "index.html";
    }
}

① 首页数据绑定语法 

 1) list集合判空
<#if goods07?? && goods07?size gt 0>

2) 遍历map集合,获取所有的keys
<#list goods07?keys as key>

3) 根据key获取对应value值goods01[key]
<#list goods07[key] as g>

首页绑值  index.html        把所有的数据都变活

<!DOCTYPE html>
<html>
	<head lang="en">
		<#include "common/head.html">
		<link rel="stylesheet" type="text/css" href="css/public.css"/>
		<link rel="stylesheet" type="text/css" href="css/index.css" />
	</head>
	<body>
		<!------------------------------head------------------------------>
		<#include "common/top.html">

		<!-------------------------banner--------------------------->
		<div class="block_home_slider">
			<div id="home_slider" class="flexslider">
				<ul class="slides">
					<li>
						<div class="slide">
							<img src="img/banner2.jpg"/>
						</div>
					</li>
					<li>
						<div class="slide">
							<img src="img/banner1.jpg"/>
						</div>
					</li>
				</ul>
			</div>
		</div>
		
		<!------------------------------thImg------------------------------>
		<div class="thImg">
			<div class="clearfix">
				<a href="${ctx}/page/vase_proList.html"><img src="img/i1.jpg"/></a>
				<a href="${ctx}/page/proList.html"><img src="img/i2.jpg"/></a>
				<a href="#2"><img src="img/i3.jpg"/></a>
			</div>
		</div>
		
		<!------------------------------news------------------------------>
		<div class="news">
			<div class="wrapper">
				<h2><img src="img/ih1.jpg"/></h2>
				<div class="top clearfix">
					<a href="${ctx}/page/proDetail.html"><img src="img/n1.jpg"/><p></p></a>
					<a href="${ctx}/page/proDetail.html"><img src="img/n2.jpg"/><p></p></a>
					<a href="${ctx}/page/proDetail.html"><img src="img/n3.jpg"/><p></p></a>
				</div>
				<div class="bott clearfix">
					<a href="${ctx}/page/proDetail.html"><img src="img/n4.jpg"/><p></p></a>
					<a href="${ctx}/page/proDetail.html"><img src="img/n5.jpg"/><p></p></a>
					<a href="${ctx}/page/proDetail.html"><img src="img/n6.jpg"/><p></p></a>
				</div>
				<h2><img src="img/ih2.jpg"/></h2>
				<#if gt01?? && gt01?size gt 0>
<#--					遍历gt01中所有的key,是为了该key中的对象-->
					<#list gt01?keys as key>
						<div class="flower clearfix tran">
						<#list gt01[key] as g>
							<a href="proDetail.html" class="clearfix">
								<dl>
									<dt>
										<span class="abl"></span>
										<img src="${g.goodsImg}"/>
										<span class="abr"></span>
									</dt>
									<dd>${g.goodsName}</dd>
									<dd><span>¥ ${g.goodsPrice}</span></dd>
								</dl>
							</a>
						</#list>
						</div>
					</#list>
				</#if>

			</div>
		</div>
		
		<!------------------------------ad------------------------------>
		<a href="#" class="ad"><img src="img/ib1.jpg"/></a>
		
		<!------------------------------people------------------------------>
		<div class="people">
			<div class="wrapper">
				<h2><img src="img/ih3.jpg"/></h2>
				<#if gt07?? && gt07?size gt 0>
				<#list gt07?keys as key>
				<div class="pList clearfix tran">
					<#list gt07[key] as g>
						<a href="proDetail.html">
							<dl>
								<dt>
									<span class="abl"></span>
									<img src="${g.goodsImg}"/>
									<span class="abr"></span>
								</dt>
								<dd>${g.goodsName}</dd>
								<dd><span>¥${g.goodsPrice}</span></dd>
							</dl>
						</a>
					</#list>
				</div>
				</#list>
				</#if>
			</div>
		</div>

		<#include "common/footer.html"/>

		<script src="js/public.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/nav.js" type="text/javascript" charset="utf-8"></script>
		<script src="js/jquery.flexslider-min.js" type="text/javascript" charset="utf-8"></script>
		<script type="text/javascript">
			$(function() {
				$('#home_slider').flexslider({
					animation: 'slide',
					controlNav: true,
					directionNav: true,
					animationLoop: true,
					slideshow: true,
					slideshowSpeed:2000,
					useCSS: false
				});

			});
		</script>
	</body>
</html>

运行一下:

在去刷新一下界面,效果如图所示:

② 公共跳转

现在我们点什么都不能跳转,跳转失败,看效果如图所示:

1)创建公共跳转控制器PageController.java

package com.jwj.spbootpro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author 敢敢
 * @site www.javajwj.com
 * @company xxx公司
 * @create  2022-11-08 14:32
 */
@Controller
public class PageController {
    /**
     *
     * @param page 直接跳转页面(没有层级文件夹的情况)
     *             列如:http://localhost:8080/page/logiin.html
     * @return
     */
    @RequestMapping("/page/{page}")
    public String page(@PathVariable("page") String page){
        return page;
    }

    /**
     *
     * @param dir
     * @param page
     *        直接跳转页面(存在层级文件夹的情况)
     *        列如: http://localhost:8080/page/user/login.html
     * @return
     */
    @RequestMapping("/page/{dir}/{page}")
    public String page(@PathVariable("dir") String dir,
                       @PathVariable("page") String page){
        return dir + "/" + page;

    }
}

 以上公共跳转控制器配置后,所有页面效果都可以使用了,这里我就展示一个登录,如图所示:

5、用户登陆 

1)创建UserController类实现用户登录

1.1)构建UserDto,定义mobile和password属性

1.2)创建UserController类

1.3)定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp)
1.4)定义响应封装类JsonResponseBody和JsonResponseStatus

我们在js里面随便建一个包放我们的登录界面的js代码,我这里建的是others,里面建一个类叫login.js,在login.html里面引入

我们在写login.js方法的时候,要先确保能编译成功。

login.js

$(function () {
    alert(1);

    //给登录按钮添加点击事件
    $("#login").click(function () {
        let mobile = $("#mobile").val();
        let password = $("#password").val();

//    向后台发起登录ajax请求
       $.post("/user/toLogin",{
           mobile:mobile,
           password:password
       },function (res) {
           if(res.code!=200){
               alert(res.msg);
           }else
               //alert(rs.msg);
               location.href='/';
       },"json");
    });
});

① 构建UserDto,定义mobile和password属性 

定义UserDto.java接受前台传递的参数

package com.jwj.spbootpro.model.dto;

import com.jwj.spbootpro.validator.IsMobile;
import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class UserDto {
    @NotBlank(message = "手机号码不能为空!")
    @IsMobile
    private String mobile;
    @NotBlank(message = "密码不能为空!")
    private String password;
}

② 定义userLogin(UserDto userDto,HttpServletRequest req,HttpServletResponse resp) 

处理浏览器端的请求 UserController.java 

package com.jwj.spbootpro.controller;


import com.jwj.spbootpro.model.dto.UserDto;
import com.jwj.spbootpro.service.IUserService;
import com.jwj.spbootpro.utils.JsonResponseBody;
import com.sun.deploy.net.HttpResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * <p>
 * 用户信息表 前端控制器
 * </p>
 *
 * @author jwj
 * @since 2022-11-07
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private IUserService userService;

    /**
     * JsonResponseBody:返回json数据
     */
    @RequestMapping("/toLogin")
    public JsonResponseBody toLogin(@Valid UserDto userDto,
                                    HttpServletRequest request,
                                    HttpResponse response){
        return userService.toLogin(userDto,response);
    }
}

③ 在IUserService中定义Login方法,并返回JsonResponseBody 

IUserService.java  

package com.jwj.spbootpro.service;

import com.jwj.spbootpro.model.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jwj.spbootpro.model.dto.UserDto;
import com.jwj.spbootpro.utils.JsonResponseBody;
import com.sun.deploy.net.HttpResponse;

/**
 * <p>
 * 用户信息表 服务类
 * </p>
 *
 * @author jwj
 * @since 2022-11-07
 */
public interface IUserService extends IService<User> {

    JsonResponseBody toLogin(UserDto userDto, HttpResponse response);
}

④ 在UserServiceImpl实现类中要做五个事情 

4.1)判断mobile和password是否为空

4.2)判断mobile格式是否正确

4.3)根据用户手机号码查询用户是否存在
4.4)校验账号
4.5)校验密码 

⑤ 全局异常处理 

5.1)创建BusinessException
5.2)创建GlobalExceptionHandler
5.3)修改userLogin中的异常处理方式

⑥ 自定义注解参数校验(JSR303) 

6.1)创建自定义注解IsMobile
6.2)创建自定义校验规则类MobileValidator
6.3)在UserVo类的mobile属性中使用IsMobile注解 

自定义JSR303注解,完成服务端登录账户的验证  IsMobile.java

package com.jwj.spbootpro.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(
        validatedBy = {MobileValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsMobile {

    boolean required() default true;

    String message() default "手机号码格式错误!";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

MobileValidator.java

package com.jwj.spbootpro.validator;



import com.jwj.spbootpro.utils.ValidatorUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class MobileValidator implements ConstraintValidator<IsMobile,String> {

    private boolean required=false;

    @Override
    public void initialize(IsMobile constraintAnnotation) {
        this.required=constraintAnnotation.required();
    }

    @Override
    public boolean isValid(String mobile, ConstraintValidatorContext constraintValidatorContext) {
        if(!this.required)
            return false;
        return ValidatorUtils.isMobile(mobile);
    }
}

UserServiceImpl.java

package com.jwj.spbootpro.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jwj.spbootpro.exception.BusinessException;
import com.jwj.spbootpro.model.User;
import com.jwj.spbootpro.mapper.UserMapper;
import com.jwj.spbootpro.model.dto.UserDto;
import com.jwj.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jwj.spbootpro.utils.CookieUtils;
import com.jwj.spbootpro.utils.JsonResponseBody;
import com.jwj.spbootpro.utils.JsonResponseStatus;
import com.jwj.spbootpro.utils.MD5Utils;
import com.sun.deploy.net.HttpResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author jwj
 * @since 2022-11-07
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理


    @Override
    public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
        //        4.1)判断mobile和password是否为空(已由JSP303完成)
//        4.2)判断mobile格式是否正确(自定义验证注解)
//        4.3)根据用户手机号码查询用户对象信息
//        select * from t_user where mobile = ''    UserMapper.xml
        User user = userMapper.selectOne(new QueryWrapper<User>()
                .eq("id", userDto.getMobile()));
//        4.4)校验账号
//        判断用户对象是否存在
        if(user == null)
//            只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
            throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);

//        判断用户对象密码与输入密码是否一致
//        4.5)校验密码
        if(!user.getPassword().equals(pwd))
            throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
        
        return new JsonResponseBody<>();
    }
}

现在我们测试肯定是不成功的,因为我们的数据库加密了

测试多种情况

1.手机号为空

2.手机号为非法字符

3.密码为空

4.手机号不存在

5.手机号密码正确 

6、前端及数据库密码加密 

前端加密:防止客户端浏览器F12导致密码泄露

后端加密:防止数据库数据泄露导致密码泄露

MD5Utils.java

package com.jwj.spbootpro.utils;

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
 * MD5加密
 * 用户端:password=MD5(明文+固定Salt)
 * 服务端:password=MD5(用户输入+随机Salt)
 * 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。
 */
@Component
public class MD5Utils {

    //加密盐,与前端一致
    private static String salt="f1g2h3j4";

    /**
     * md5加密
     * @param src
     * @return
     */
    public static String md5(String src){
        return DigestUtils.md5Hex(src);
    }

    /**
     * 获取加密的盐
     * @return
     */
    public static String createSalt(){
        return UUID.randomUUID().toString().replace("-","");
    }

    /**
     * 将前端的明文密码通过MD5加密方式加密成后端服务所需密码
     * 注意:该步骤实际是在前端完成!!!
     * @param inputPass 明文密码
     * @return
     */
    public static String inputPassToFormpass(String inputPass){
//        123456
//        13123456f2
        //混淆固定盐salt,安全性更可靠
        String str=salt.charAt(1)+""+salt.charAt(5)+inputPass+salt.charAt(0)+""+salt.charAt(3);
        return md5(str);
    }

    /**
     * 将后端密文密码+随机salt生成数据库的密码
     * @param formPass
     * @param salt
     * @return
     */
    public static String formPassToDbPass(String formPass,String salt){
        //混淆固定盐salt,安全性更可靠
        String str=salt.charAt(7)+""+salt.charAt(4)+formPass+salt.charAt(1)+""+salt.charAt(5);
        return md5(str);
    }

    /**
     * 将用户输入的密码转换成数据库的密码
     * @param inputPass 明文密码
     * @param salt      盐
     * @return
     */
    public static String inputPassToDbPass(String inputPass,String salt){
        String formPass = inputPassToFormpass(inputPass);
        String dbPass = formPassToDbPass(formPass, salt);
        return dbPass;
    }

    public static void main(String[] args) {
        //d7aaa28e3b8e6c88352bd5e7c23829f9
        //5512a78a188b318c074a15f9b056a712
        String formPass = inputPassToFormpass("123456");
        System.out.println("前端加密密码:"+formPass);
        String salt = createSalt();
        System.out.println("后端加密随机盐:"+salt);
        String dbPass = formPassToDbPass(formPass, salt);
        System.out.println("后端加密密码:"+dbPass);
        System.out.println("-------------------------------------------");
        String dbPass1 = inputPassToDbPass("123456", salt);
        System.out.println("最终加密密码:"+dbPass1);
    }
}

运行里面的main方法,每次加密都会不一样。

 login.js变更如下

$(function () {
    alert(4);

    //给登录按钮添加点击事件
    $("#login").click(function () {
        let mobile = $("#mobile").val();
        let password = $("#password").val();

        //1.密码加密
        //1) 定义固定盐
        let salt='f1g2h3j4';
        //2) 固定盐混淆
        let temp=salt.charAt(1)+""+salt.charAt(5)+password+salt.charAt(0)+""+salt.charAt(3);
        //3) 使用MD5完成前端第一次加密
        let pwd=md5(temp);

//    向后台发起登录ajax请求
       $.post("/user/toLogin",{
           mobile:mobile,
           password:pwd
       },function (res) {
           if(res.code!=200){
               alert(res.msg);
           }else
                alert(res.msg);
               // location.href='/';
       },"json");
    });
});

UserServiceImpl .java 

package com.jwj.spbootpro.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jwj.spbootpro.exception.BusinessException;
import com.jwj.spbootpro.model.User;
import com.jwj.spbootpro.mapper.UserMapper;
import com.jwj.spbootpro.model.dto.UserDto;
import com.jwj.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jwj.spbootpro.utils.CookieUtils;
import com.jwj.spbootpro.utils.JsonResponseBody;
import com.jwj.spbootpro.utils.JsonResponseStatus;
import com.jwj.spbootpro.utils.MD5Utils;
import com.sun.deploy.net.HttpResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author jwj
 * @since 2022-11-07
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理


    @Override
    public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
        //        4.1)判断mobile和password是否为空(已由JSP303完成)
//        4.2)判断mobile格式是否正确(自定义验证注解)
//        4.3)根据用户手机号码查询用户对象信息
//        select * from t_user where mobile = ''    UserMapper.xml
        User user = userMapper.selectOne(new QueryWrapper<User>()
                .eq("id", userDto.getMobile()));
//        4.4)校验账号
//        判断用户对象是否存在
        if(user == null)
//            只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
            throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);

//        判断用户对象密码与输入密码是否一致
//        前台传递到后台的密码,要经过工具类md5加密一次,才有可能跟数据库密码匹配上
        String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());

//        4.5)校验密码
        if(!user.getPassword().equals(pwd))
            throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
        
        return new JsonResponseBody<>();
    }
}

 注意:要在login.html 里面引入md5.js

<!DOCTYPE html>
<html>
	<head>
		<#include "common/head.html">
		<link rel="stylesheet" type="text/css" href="css/public.css"/>
		<link rel="stylesheet" type="text/css" href="css/login.css"/>
		<script type="text/javascript" src="js/jquery-1.12.4.min.js"></script>
		<script type="text/javascript" src="js/md5.js"></script>
		<script type="text/javascript" src="js/others/login.js"></script>
	</head>
	<body>
		<!-------------------login-------------------------->
		<div class="login">
			<form action="#" method="post">
				<h1><a href="${ctx}/"><img src="img/temp/logo.png"></a></h1>
				<p></p>
				<div class="msg-warn hide"><b></b>公共场所不建议自动登录,以防账号丢失</div>
				<p><input style="font-size:14px;" type="text" id="mobile" value="" placeholder="昵称/邮箱/手机号"></p>
				<p><input style="font-size:14px;" type="password" id="password" value="" placeholder="密码"></p>
				<p><input type="button" id="login" value="登  录"></p>
				<p class="txt"><a class="" href="${ctx}/page/reg.html">免费注册</a><a href="${ctx}/page/forget.html">忘记密码?</a></p>
			</form>
		</div>
		
	</body>
</html>

泄露问题就解决了,如下:

 7、登录令牌管理

将登录的用户数据分别保留在客户端以及服务端

UserServiceImpl .java变更如下: 

package com.jwj.spbootpro.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jwj.spbootpro.exception.BusinessException;
import com.jwj.spbootpro.model.User;
import com.jwj.spbootpro.mapper.UserMapper;
import com.jwj.spbootpro.model.dto.UserDto;
import com.jwj.spbootpro.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jwj.spbootpro.service.IredisService;
import com.jwj.spbootpro.utils.CookieUtils;
import com.jwj.spbootpro.utils.JsonResponseBody;
import com.jwj.spbootpro.utils.JsonResponseStatus;
import com.jwj.spbootpro.utils.MD5Utils;
import com.sun.deploy.net.HttpResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author jwj
 * @since 2022-11-07
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;//在UserMapper类里面要被Spring所标记@Repository交给它管理
    @Autowired
    private IredisService redisService;

    @Override
    public JsonResponseBody toLogin(UserDto userDto, HttpServletRequest request, HttpServletResponse response) {
        //        4.1)判断mobile和password是否为空(已由JSP303完成)
//        4.2)判断mobile格式是否正确(自定义验证注解)
//        4.3)根据用户手机号码查询用户对象信息
//        select * from t_user where mobile = ''    UserMapper.xml
        User user = userMapper.selectOne(new QueryWrapper<User>()
                .eq("id", userDto.getMobile()));
//        4.4)校验账号
//        判断用户对象是否存在
        if(user == null)
//            只要抛出了这个异常,它就会被全局异常进行处理 GlobalExceptionHandler.java
            throw new BusinessException(JsonResponseStatus.USER_USERNAME_ERROR);

//        判断用户对象密码与输入密码是否一致
//        前台传递到后台的密码,要经过工具类md5加密一次,才有可能跟数据库密码匹配上
        String pwd = MD5Utils.formPassToDbPass(userDto.getPassword(), user.getSalt());

//        4.5)校验密码
        if(!user.getPassword().equals(pwd))
            throw new BusinessException(JsonResponseStatus.USER_PASSWORD_ERROR);
        //6.将登陆用户对象与token令牌进行绑定保存到cookie和redis
        //创建登陆令牌token
        String token= UUID.randomUUID().toString().replace("-","");
        //将token令牌保存到cookie中
        CookieUtils.setCookie(request,response,"token",token,7200);
        //将登陆token令牌与用户对象user绑定到redis中
        redisService.setUserToRedis(token,user);
        //将用户登陆的昵称设置到cookie中
        CookieUtils.setCookie(request,response,"nickname",user.getNickname());
        return new JsonResponseBody<>();
    }
}

在 service里面创建一个借口IredisService.java

package com.jwj.spbootpro.service;

import com.jwj.spbootpro.model.User;

/**
 * @author 敢敢
 * @site www.javajwj.com
 * @company xxx公司
 * @create  2022-11-08 17:29
 */
public interface IredisService {
    /**
     * 将用户对象user与登陆token令牌保存到redis
     * @param token 登陆令牌token
     * @param user  登陆用户对象user
     */
    void setUserToRedis(String token, User user);

    /**
     * 根据登陆token令牌到redis中获取对应的用户登陆对象信息
     * @param token 登陆令牌token
     * @return 登陆用户对象
     */
    User getUserByToken(String token);
}

实现RedisServiceImpl.java 方法

package com.jwj.spbootpro.service.impl;

import com.jwj.spbootpro.model.User;
import com.jwj.spbootpro.service.IredisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @author 敢敢
 * @site www.javajwj.com
 * @company xxx公司
 * @create  2022-11-08 17:31
 */
@Service
public class RedisServiceImpl implements IredisService {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public void setUserToRedis(String token, User user) {
        redisTemplate.opsForValue().set("user:"+token,user,7200L, TimeUnit.SECONDS);
    }

    @Override
    public User getUserByToken(String token) {
        return (User) redisTemplate.opsForValue().get("user:"+token);
    }
}

 运行结果如下:

项目示例基于spring boot 最新版本(2.1.9)实现,Spring BootSpring Cloud 学习示例,将持续更新…… 在基于Spring BootSpring Cloud 分布微服务开发过程中,根据实际项目环境,需要选择、集成符合项目需求的各种组件和积累各种解决方案。基于这样的背景下,我开源了本示例项目,方便大家快速上手Spring BootSpring Cloud 。 每个示例都带有详细的介绍文档、作者在使用过程中踩过的坑、解决方案及参考资料,方便快速上手为你提供学习捷径,少绕弯路,提高开发效率。 有需要写关于spring bootspring cloud示例,可以给我提issue哦 ## 项目介绍 spring boot demo 是一个Spring BootSpring Cloud的项目示例,根据市场主流的后端技术,共集成了30+个demo,未来将持续更新。该项目包含helloworld(快速入门)、web(ssh项目快速搭建)、aop(切面编程)、data-redis(redis缓存)、quartz(集群任务实现)、shiro(权限管理)、oauth2(四种认证模式)、shign(接口参数防篡改重放)、encoder(用户密码设计)、actuator(服务监控)、cloud-config(配置中心)、cloud-gateway(服务网关)、email(邮件发送)、cloud-alibaba(微服务全家桶)等模块 ### 开发环境 - JDK1.8 + - Maven 3.5 + - IntelliJ IDEA ULTIMATE 2019.1 - MySql 5.7 + ### Spring Boot 模块 模块名称|主要内容 ---|--- helloworld|[spring mvc,Spring Boot项目创建,单元测试](https://github.com/smltq/spring-boot-demo/blob/master/helloworld/HELP.md) web|[ssh项目,spring mvc,过滤器,拦截器,监视器,thymeleaf,lombok,jquery,bootstrap,mysql](https://github.com/smltq/spring-boot-demo/blob/master/web/HELP.md) aop|[aop,正则,前置通知,后置通知,环绕通知](https://github.com/smltq/spring-boot-demo/blob/master/aop/HELP.md) data-redis|[lettuce,redis,session redis,YAML配置,连接池,对象存储](https://github.com/smltq/spring-boot-demo/blob/master/data-redis/HELP.md) quartz|[Spring Scheduler,Quartz,分布式调度,集群,mysql持久化等](https://github.com/smltq/spring-boot-demo/blob/master/quartz/HELP.md) shiro|[授权、认证、加解密、统一异常处理](https://github.com/smltq/spring-boot-demo/blob/master/shiro/HELP.md) sign|[防篡改、防重放、文档自动生成](https://github.com/smltq/spring-boot-demo/blob/master/sign/HELP.md) security|[授权、认证、加解密、mybatis plus使用](https://github.com/smltq/spring-boot-demo/blob/master/security/HELP.md) mybatis-plus-generator|[基于mybatisplus代码自动生成](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-plus-generator) mybatis-plus-crud|[基于mybatisplus实现数据库增、册、改、查](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-plus-crud) encoder|[主流加密算法介绍、用户加密算法推荐](https://github.com/smltq/spring-boot-demo/blob/master/encoder/HELP.md) actuator|[autuator介绍](https://github.com/smltq/spring-boot-demo/blob/master/actuator/README.md) admin|[可视化服务监控、使用](https://github.com/smltq/spring-boot-demo/blob/master/admin/README.md) security-oauth2-credentials|[oauth2实现密码模式、客户端模式](https://github.com/smltq/spring-boot-demo/blob/master/security-oauth2-credentials/README.md) security-oauth2-auth-code|[基于spring boot实现oauth2授权模式](https://github.com/smltq/spring-boot-demo/blob/master/security-oauth2-auth-code/README.md) mybatis-multi-datasource|[mybatis、数据库集群、读写分离、读库负载均衡](https://github.com/smltq/spring-boot-demo/blob/master/mybatis-multi-datasource) template-thymeleaf|[thymeleaf实现应用国际化示例](https://github.com/smltq/spring-boot-demo/blob/master/template-thymeleaf) mq-redis|[redis之mq实现,发布订阅模式](https://github.com/smltq/spring-boot-demo/blob/master/mq-redis) email|[email实现邮件发送](https://github.com/smltq/spring-boot-demo/blob/master/email) jGit|[java调用git命令、jgit使用等](https://github.com/smltq/spring-boot-demo/blob/master/jGit) webmagic|[webmagic实现某电影网站爬虫示例](https://github.com/smltq/spring-boot-demo/blob/master/webmagic) netty|[基于BIO、NIO等tcp服务器搭建介绍](https://github.com/smltq/spring-boot-demo/blob/master/netty) ### Spring Cloud 模块 模块名称|主要内容 ---|--- cloud-oauth2-auth-code|[基于spring cloud实现oath2授权模式](https://github.com/smltq/spring-boot-demo/blob/master/cloud-oauth2-auth-code) cloud-gateway|[API主流网关、gateway快速上手](https://github.com/smltq/spring-boot-demo/blob/master/cloud-gateway) cloud-config|[配置中心(服务端、客户端)示例](https://github.com/smltq/spring-boot-demo/blob/master/cloud-config) cloud-feign|[Eureka服务注册中心、负载均衡、声明式服务调用](https://github.com/smltq/spring-boot-demo/blob/master/cloud-feign) cloud-hystrix|[Hystrix服务容错、异常处理、注册中心示例](https://github.com/smltq/spring-boot-demo/blob/master/cloud-hystrix) cloud-zuul|[zuul服务网关、过滤器、路由转发、服务降级、负载均衡](https://github.com/smltq/spring-boot-demo/blob/master/cloud-zuul) cloud-alibaba|[nacos服务中心、配置中心、限流等使用(系列示例整理中...)](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba) #### Spring Cloud Alibaba 模块 模块名称|主要内容 ---|--- nacos|[Spring Cloud Alibaba(一)如何使用nacos服务注册和发现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README1.md) config|[Spring Cloud Alibaba(二)配置中心多项目、多配置文件、分目录实现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README2.md) Sentinel|[Spring Cloud Alibaba(三)Sentinel之熔断降级](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README3.md) Dubbo|[Spring Cloud Alibaba(四)Spring Cloud与Dubbo的融合](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README4.md) RocketMQ|[Spring Cloud Alibaba(五)RocketMQ 异步通信实现](https://github.com/smltq/spring-boot-demo/blob/master/cloud-alibaba/README5.md) ### 其它 模块名称|主要内容 ---|--- leetcode|[力扣题解目录](https://github.com/smltq/spring-boot-demo/blob/master/leetcode) ## Spring Boot 概述 Spring Boot简化了基于Spring的应用开发,通过少量的代码就能创建一个独立的、产品级别的Spring应用。 Spring BootSpring平台及第三方库提供开箱即用的设置,这样你就可以有条不紊地开始。多数Spring Boot应用只需要很少的Spring配置。 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Sprin
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值