Java开发:Spring Boot 实战教程

序言

随着技术的快速发展和数字化转型的深入推进,软件开发领域迎来了前所未有的变革。在众多开发框架中,Spring Boot凭借其“约定大于配置”的核心理念和快速开发的能力,迅速崭露头角,成为当今企业级应用开发的首选框架之一。

《Spring Boot实战教程》旨在为广大开发者提供一本系统、全面且实用的学习指南。本教程不仅深入解析了Spring Boot的核心特性和最佳实践,还通过大量的实战案例,帮助读者快速掌握Spring Boot的应用开发技巧,从而能够高效、稳定地构建出符合业务需求的Web应用。

一、创建Springboot项目

  1. 创建Maven工程
  2. 导入spring-boot-stater-web起步依赖
  3. 编写Controller
  4. 提供启动类

二、手动创建SpringBoot工程

在这里插入图片描述

在这里插入图片描述

三、编写配置文件application.properties

删除application.properties配置文件,新建application.yml或application.yaml配置文件【两者区别请自行查询】
在这里插入图片描述
在这里插入图片描述

四、编写Controller

在这里插入图片描述

启动项目后在控制台会显示配置的端口

在这里插入图片描述

可以根据需要将pom文件中的jdk17改为jdk8【注意mybatis等三方依赖库的版本也需要降低】

在这里插入图片描述

五、提供启动类

在这里插入图片描述

六、启动服务,在浏览器调用http://localhost:8080/hello

页面返回Hello World~表示调用成功,项目搭建正常

------------接下来就可以进行业务相关接口开发了------------

七、执行sql语句【在navicat、idea或者dos窗口执行sql语句】

-- 创建数据库
create database big_event;

-- 使用数据库
use big_event;

-- 用户表
create table user (
                      id int unsigned primary key auto_increment comment 'ID',
                      username varchar(20) not null unique comment '用户名',
                      password varchar(32)  comment '密码',
                      nickname varchar(10)  default '' comment '昵称',
                      email varchar(128) default '' comment '邮箱',
                      user_pic varchar(128) default '' comment '头像',
                      create_time datetime not null comment '创建时间',
                      update_time datetime not null comment '修改时间'
) comment '用户表';

-- 分类表
create table category(
                         id int unsigned primary key auto_increment comment 'ID',
                         category_name varchar(32) not null comment '分类名称',
                         category_alias varchar(32) not null comment '分类别名',
                         create_user int unsigned not null comment '创建人ID',
                         create_time datetime not null comment '创建时间',
                         update_time datetime not null comment '修改时间',
                         constraint fk_category_user foreign key (create_user) references user(id) -- 外键约束
);

-- 文章表
create table article(
                        id int unsigned primary key auto_increment comment 'ID',
                        title varchar(30) not null comment '文章标题',
                        content varchar(10000) not null comment '文章内容',
                        cover_img varchar(128) not null  comment '文章封面',
                        state varchar(3) default '草稿' comment '文章状态: 只能是[已发布] 或者 [草稿]',
                        category_id int unsigned comment '文章分类ID',
                        create_user int unsigned not null comment '创建人ID',
                        create_time datetime not null comment '创建时间',
                        update_time datetime not null comment '修改时间',
                        constraint fk_article_category foreign key (category_id) references category(id),-- 外键约束
                        constraint fk_article_user foreign key (create_user) references user(id) -- 外键约束
)

八、整合mysql

在这里插入图片描述

九、整合mybatis

推荐使用Mybatis-plus,而且建议只使用Mybatis-plus的Mapper规范,Service依旧按照Mybatis的规范【具体实施参照本文第三十二项】。
mybatis中文网:http://www.mybatis.cn/
mybatis的mapper.xml映射文件存放位置说明:
https://www.jb51.net/program/30707796h.htm

在这里插入图片描述

十、配置application.yml文件

server:
  port: 8080
  servlet:
    context-path: /big-event2

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/big_event?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
  redis:
    host: localhost
    port: 6379

#    下划线命名字段和驼峰命名属性的赋值问题【https://blog.csdn.net/Zong_0915/article/details/127453933】
mybatis:
  configuration:
    map-underscore-to-camel-case: true


十一、在包名下新建业务分类包

在这里插入图片描述

十二、通用实体类

返回结果实体类:

package com.source.bigevent2.pojo;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private Integer code;//状态码0成功;1失败
    private String msg;//提示信息
    private T data;//响应数据

    public static Result success() {
        return new Result(0, "操作成功", null);
    }

    public static <E> Result<E> success(E data) {
        return new Result<>(0, "操作成功", data);
    }

    public static Result<String> error(String msg) {
        return new Result<>(1, msg, null);
    }
}

分页返回结果实体类:

package com.source.bigevent2.pojo;

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

import java.util.List;

//分页返回结果对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean<T>{
    private Long total;//总条数
    private List<T> items;//当前页数据集合
}

十三、注册用户

首先创建对应的UserController、UserService、UserServiceImpl、UserMapper类

在这里插入图片描述

然后在每个类中编写对应的逻辑代码,项目中用到的实体类请到最后 附录一 中查询

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

如果只添加正则校验参数不合格时会被校验,并抛出异常,如下:

在这里插入图片描述

这时候可以添加全局异常捕获 【开发阶段不建议添加,添加后不容易定位出错问题】
【参考:https://huaweicloud.csdn.net/638754d5dacf622b8df8b03d.html】,如下:

在这里插入图片描述

十四、登录认证

令牌就是一段字符串
承载业务数据, 减少后续请求查询数据库的次数
防篡改, 保证信息的合法性和有效性
JWT
JWT依赖:

在这里插入图片描述

在这里插入图片描述

JWT详细步骤:

在这里插入图片描述

在这里插入图片描述

JWT工具类:

package com.source.bigevent.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Date;
import java.util.Map;

public class JwtUtil {

    private static final String KEY = "itheima";
	
	//接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
                .sign(Algorithm.HMAC256(KEY));
    }

	//接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

登录返回token

    @PostMapping("/login")
    public Result login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
        User user = userService.findByUserName(username);
        if (user == null) {
            return Result.error("用户名错误");
        }
        if (Md5Util.checkPassword(password, user.getPassword())) {
            Map<String, Object> claims = new HashMap<>();
            claims.put("userId", user.getId());
            claims.put("userName", username);
            String token = JwtUtil.genToken(claims);
            //使用redis对token进行进一步校验,登录成功存一份token到redis中
            redisTemplate.opsForValue().set("Token", token, 12, TimeUnit.HOURS);
            return Result.success(token);
        }
        return Result.error("密码错误");
    }

十五、拦截器认证Token

步骤一、使用拦截器统一验证令牌

在这里插入图片描述

步骤二、登录和注册接口需要放行

主要实现逻辑为WebConfig类【注意路径中有两个”/”】

在这里插入图片描述

十六、使用ThreadLocal进行局部变量存储

可以存储userId等,ThreadLocalUtil见 附录一

在这里插入图片描述

在这里插入图片描述

十七、获取用户信息

在这里插入图片描述

通过ThreadLocal可以获取到userName、userId

在这里插入图片描述

十八、更新用户信息

在这里插入图片描述

注意:如果方法的参数是实体类,那么不能存在与之并列的其他参数,也就是说参数只能有实体类一个参数。如果是入参形式为json则必须添加@RequestBody注解,否则无法解析传参数据。
实体类参数校验:实体类成员变量上添加注解【@NotNull、@NotEmpty、@Email】、接口参数的实体参数上添加@Validated注解

在这里插入图片描述

在这里插入图片描述

十九、更新用户头像

在这里插入图片描述
参数校验,必须是url地址

在这里插入图片描述

二十、修改密码

在这里插入图片描述

在这里插入图片描述

二十一、新增文章分类

在这里插入图片描述

二十二、文章分类列表

在这里插入图片描述

二十三、更新文章分类

在这里插入图片描述

在这里插入图片描述

二十四、新增文章【自定义校验】

在这里插入图片描述

参数校验

在这里插入图片描述

自定义校验 【@State】

在这里插入图片描述

package com.source.bigevent.pojo;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.source.bigevent.annotation.State;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
import java.time.LocalDateTime;

/**
 * 文章
 */
@Data
public class Article {
    @NotNull(groups = Edit.class)
    private Integer id;
    @NotEmpty
    private String title;
    @NotEmpty
    private String content;
    @URL
    private String coverImg;
    @State
    private String state;
    @NotNull
    private Integer categoryId;
    @NotNull(groups = Add.class)
    private Integer createUser;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

    public interface Add extends Default {
    }

    public interface Edit extends Default {
    }
}

二十五、文章列表【条件分页】

引入依赖pagehelper

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.4.6</version>
        </dependency>

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在resources文件夹下新建包名和mapper相同的目录并新建ArticelMapper.xml同名文件
–>new Directory -->com/source/bigevent/mapper

在这里插入图片描述

二十六、文件上传

文件上传分两种,一种是存在服务器磁盘,另一种是存在三方云服务器上。

在这里插入图片描述

在这里插入图片描述

A、将文件存在本地磁盘

在这里插入图片描述

B、三方文件存储服务器阿里云OSS
https://blog.csdn.net/m0_72853403/article/details/134611445
https://gitcode.csdn.net/65ec53451a836825ed7988ad.html

在这里插入图片描述

二十七、登录优化【Redis】

Springboot集成redis

在这里插入图片描述

令牌主动失效机制
A、 登录成功后,给浏览器响应令牌的同时,把该令牌存储到redis中【这里使用token的值做为key存入redis,避免所有用户使用同一个key来存取值】

在这里插入图片描述

B、 LoginInterceptor拦截器中,需要验证浏览器携带的令牌,并同时需要获取到redis中存储的与之相同的令牌

在这里插入图片描述

C、 当用户修改密码成功后,删除redis中存储的旧令牌

在这里插入图片描述

代码编辑完成后,启动redis安装包中的redis-server.exe即可使用redis

二十八、SpringBoot项目部署

在这里插入图片描述

A、如何生成jar包?
执行package命令即可
B、如何运行jar包?
Java –jar jar包位置
C、如何停止正在运行的服务?
Dos窗口中Ctrl+C
D、Jar包部署对服务器有什么要求?
必须有jre环境
E、启动redis
启动redis安装包中的redis-server.exe才可以使用redis

二十九、属性配置方式【优先级从低到高依次为ABCD】

A、 项目中resources目录下的application.yml

在这里插入图片描述
B、 Jar包所在目录下的application.yml【需要在该jar文件路径下运行cmd,然后执行java -jar big--.jar 配置文件才能生效】

在这里插入图片描述
C、 操作系统环境变量

在这里插入图片描述

D、 命令行参数

在这里插入图片描述

三十、application配置文件相关知识

application.yml / application.yaml格式如下:

在这里插入图片描述

yml配置信息书写与获取
三方依赖库技术参数信息书写【以redis为例】

在这里插入图片描述

自定义配置参数及获取:
方式一、通过@Value(“${键名}”)

在这里插入图片描述

方式二、通过@ConfigurationProperties(prefix=“前缀”)+@Value(“${键名}”)

在这里插入图片描述

三十一、驼峰命名格式导致实体类和数据库不一致

修改application.yml配置文件
参考:
https://www.cnblogs.com/ubiquitousShare/p/12507070.html
https://blog.csdn.net/weixin_45935633/article/details/104704042

在这里插入图片描述

三十二、Mybatis-plus使用

        <!-- mybatis-plus 是自己开发,并非官方的! -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

官网https://baomidou.com/
Mybatis-plus是为了让Mybatis更简单!
Mybatis-plus和Mybatis在项目中不建议同时存在 【会由于Mybatis版本不同而冲突】
Mybatis-plus支持Mybatis的Mapper、Service全部写法,如果之前使用的Mybatis,现切换成Mybatis-plus只需要将依赖改成Mybatis-plus、application.yml中mybatis属性名改成mybatis-plus,程序就可以正常运行。但是如果只是这样使用,就没有引入Mybatis-plus的必要了。

我们可以简化Mapper、Service,一些简单的查询方法Mybatis-plus已经为我们实现,我们只需要按照Mybatis的写法实现复杂的接口。

总结
1、实际开发中建议Service依旧遵循Mybatis规范,不使用Mybatis-plus的写法。只有Mapper按照Mybatis-plus规范编写,这样可以保证繁琐的逻辑依旧在Service中实现。
2、关于Wrapper推荐使用Wrappers.lambdaQuery().eq(User::getId,userId)
Wrappers.lambdaUpdate().eq(User::getId,userId)
3、关于逻辑删除
Mybatis-plus如果是3.3.0及以后,就只需要配置yaml即可。在3.3.0以前,除了yaml配置,还需要在对应的属性上加上@TableLogic注解。

建议:比较推荐使用Mybatis-plus,可以少写一些接口,而且通过Mybatis-plus的Wrapper查询条件可以简化sql语句。
Mybatis-plus使用教程可以参照:
1、https://blog.csdn.net/m0_67296957/article/details/131723536
2、https://blog.csdn.net/xuxuxux123/article/details/126451792
3、https://blog.csdn.net/qq_40649503/article/details/109522614

附录一

一、JWT工具类:

package com.source.bigevent.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Date;
import java.util.Map;

public class JwtUtil {

    private static final String KEY = "itheima";
	
	//接收业务数据,生成token并返回
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
                .sign(Algorithm.HMAC256(KEY));
    }

	//接收token,验证token,并返回业务数据
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }

}

二、ThreadLocal工具类

package com.source.bigevent.utils;

/**
 * ThreadLocal 工具类
 */
@SuppressWarnings("all")
public class ThreadLocalUtil {
    //提供ThreadLocal对象,
    private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();

    //根据键获取值
    public static <T> T get() {
        return (T) THREAD_LOCAL.get();
    }

    //存储键值对
    public static void set(Object value) {
        THREAD_LOCAL.set(value);
    }


    //清除ThreadLocal 防止内存泄漏
    public static void remove() {
        THREAD_LOCAL.remove();
    }
}

三、Md5工具类

package com.source.bigevent.utils;


import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Md5Util {
    /**
     * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字符串的md5校验值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判断字符串的md5校验码是否与一个已知的md5码相匹配
     *
     * @param password  要校验的字符串
     * @param md5PwdStr 已知的md5校验码
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }


    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
        // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

}

四、通用结果返回实体类

package com.source.bigevent.pojo;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private Integer code;//状态码,0-成功;1-失败
    private String message;//提示信息
    private T data;//响应数据

    public static <E> Result<E> success(E data) {
        return new Result<>(0, "操作成功", data);
    }

    public static Result success() {
        return new Result(0, "操作成功", null);
    }

    public static Result<String> error(String message) {
        return new Result<>(1, message, null);
    }
}

五、分页结果返回实体类

package com.source.bigevent.pojo;

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

import java.util.List;

//分页返回结果对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean <T>{
    private Long total;//总条数
    private List<T> items;//当前页数据集合
}

六、用户实体类

package com.source.bigevent.pojo;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用户实体类
 */
@Data
public class User {
    private Integer id;
    private String username;
    @JsonIgnore//对象转Json时忽略此字段
    private String password;
    private String nickname;
    private String email;
    private String userPic;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

七、文章分类实体类

package com.source.bigevent.pojo;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
import java.time.LocalDateTime;

/**
 * 分类
 */
@Data
public class Category {
    @NotNull(groups = Edit.class)
    private Integer id;
    @NotEmpty
    private String categoryName;
    @NotEmpty
    private String categoryAlias;
    private Integer createUser;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

    public interface Add extends Default {
    }

    public interface Edit extends Default {
    }
}

八、文章实体类

package com.source.bigevent.pojo;

import com.fasterxml.jackson.annotation.JsonFormat;
//import com.source.bigevent.annotation.State;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
import java.time.LocalDateTime;

/**
 * 文章
 */
@Data
public class Article {
    @NotNull(groups = Edit.class)
    private Integer id;
    @NotEmpty
    private String title;
    @NotEmpty
    private String content;
    @URL
    private String coverImg;
    //@State
    private String state;
    @NotNull
    private Integer categoryId;
    @NotNull(groups = Add.class)
    private Integer createUser;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

    public interface Add extends Default {
    }

    public interface Edit extends Default {
    }
}

附录二

一、SQL语句中获取当前时间的方法

在 SQL 中,获取当前时间的方法取决于你使用的数据库系统。不同的数据库系统提供了不同的函数来获取当前日期和时间。以下是一些常见数据库系统中获取当前时间的方法:

1、MySQL:

SELECT NOW();  
-- 或者  
SELECT CURRENT_TIMESTAMP;

2、PostgreSQL:

SELECT NOW();  
-- 或者  
SELECT CURRENT_TIMESTAMP;

3、SQL Server:

SELECT GETDATE();  
-- 或者  
SELECT CURRENT_TIMESTAMP;

4、Oracle:

SELECT SYSTIMESTAMP FROM DUAL;  
-- 或者  
SELECT CURRENT_TIMESTAMP FROM DUAL;

注意:SYSTIMESTAMP 返回带有时区信息的完整时间戳,而 CURRENT_TIMESTAMP 可能会根据会话的时区设置返回不同的值。

5、SQLite:

SELECT datetime('now');  
-- 或者  
SELECT CURRENT_TIMESTAMP;

注意:SQLite 的 CURRENT_TIMESTAMP 实际上是 datetime(‘now’) 的别名。

6、DB2:

SELECT CURRENT TIMESTAMP;  
-- 或者  
VALUES CURRENT TIMESTAMP;

在编写 SQL 语句时,请确保你使用的是与你的数据库系统兼容的函数。这些函数通常返回当前日期和时间的完整表示,包括年、月、日、时、分、秒,有时还包括时区信息(取决于函数和数据库配置)。如果你只需要日期部分,而不包括时间,你可以使用 CURDATE()(MySQL)、CURRENT_DATE(PostgreSQL、SQL Server、DB2)等函数(具体取决于数据库系统)。

  • 28
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
当前课程中博客项目的实战源码是我在 GitHub上开源项目 My-Blog,目前已有 3000 多个 star:本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 个人博客项目功能的讲解,通过本课程的学习,不仅仅让你掌握基本的 Spring Boot 开发能力以及 Spring Boot 项目的大部分开发使用场景,同时帮你提前甄别和处理掉将要遇到的技术难点,认真学完这个课程后,你将会对 Spring Boot 有更加深入而全面的了解,同时你也会得到一个大家都在使用的博客系统源码,你可以根据自己的需求和想法进行改造,也可以直接使用它来作为自己的个人网站,这个课程一定会给你带来巨大的收获。作者寄语本课程录制于 2020 年,代码基于 Spring Boot 2.x 版本。到目前为止,Spring Boot 技术栈也有一些版本升级,比如 Spring Boot 2.7 发版、Spring Boot 3.x 版本发布正式版本。对于这些情况,笔者会在本课程实战项目的开源仓库中创建不同的代码分支,保持实战项目的源码更新,保证读者朋友们不会学习过气的知识点。课程特色 课程内容紧贴 Spring Boot 技术栈,涵盖大部分 Spring Boot 使用场景。开发教程详细完整、文档资源齐全、实验过程循序渐进简单明了。实践项目页面美观且实用,交互效果完美。包含从零搭建项目、以及完整的后台管理系统和博客展示系统两个系统的功能开发流程。技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,对于提升你的市场竞争力有一定的帮助。实战项目预览    
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值