序言
随着技术的快速发展和数字化转型的深入推进,软件开发领域迎来了前所未有的变革。在众多开发框架中,Spring Boot凭借其“约定大于配置”的核心理念和快速开发的能力,迅速崭露头角,成为当今企业级应用开发的首选框架之一。
《Spring Boot实战教程》旨在为广大开发者提供一本系统、全面且实用的学习指南。本教程不仅深入解析了Spring Boot的核心特性和最佳实践,还通过大量的实战案例,帮助读者快速掌握Spring Boot的应用开发技巧,从而能够高效、稳定地构建出符合业务需求的Web应用。
1、为何选择Spring Boot?
在众多的开发框架中,Spring Boot以其独特的优势赢得了开发者的青睐。首先,Spring Boot简化了Spring应用的初始搭建和开发过程,通过提供大量的默认配置和自动化配置,使开发者能够快速地启动和运行应用。其次,Spring Boot支持各种主流的开发工具和平台,如Maven、Gradle、IntelliJ IDEA等,方便开发者根据自己的习惯和需求选择最适合的开发环境。最后,Spring Boot拥有庞大的社区支持和丰富的生态系统,开发者可以轻松找到所需的库和插件,实现各种复杂的功能需求。
2、本教程的特点
系统全面:本教程从Spring Boot的基础知识讲起,逐步深入到高级特性和实战应用,涵盖了Spring Boot的各个方面。
实战导向:通过大量的实战案例和示例代码,帮助读者更好地理解和掌握Spring Boot的应用开发技巧。
图文并茂:教程中穿插了大量的图表和示意图,使内容更加生动直观,易于理解。
深入浅出:对于复杂的概念和技术,本教程采用深入浅出、循序渐进的讲解方式,使读者能够轻松掌握。
3、本教程的内容结构
本教程共分为以下几个部分:
基础篇:介绍Spring Boot的基本概念、发展历程和核心特性,以及如何搭建一个基础的Spring Boot项目。
进阶篇:深入解析Spring Boot的高级特性和最佳实践,包括数据访问、Web开发、安全控制、性能优化等方面。
实战篇:通过多个实战案例,展示如何使用Spring Boot构建符合业务需求的Web应用,包括电商网站、企业门户、社交应用等。
高级篇:介绍Spring Boot与微服务架构、云计算、容器化等技术的结合应用,以及如何进行Spring Boot应用的测试和部署。
4、结语
《Spring Boot实战教程》不仅是一本学习指南,更是一本实战手册。通过本教程的学习,你将能够全面掌握Spring Boot的应用开发技巧,并能够在实际项目中灵活运用这些技巧,构建出高效、稳定且符合业务需求的Web应用。让我们一起踏上Spring Boot的实战之旅吧!
一、创建Springboot项目
- 创建Maven工程
- 导入spring-boot-stater-web起步依赖
- 编写Controller
- 提供启动类
二、手动创建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
十、配置application.yml文件
十一、在包名下新建业务分类包
十二、通用实体类
返回结果实体类:
packagecom.source.bigevent2.pojo;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;@Data@NoArgsConstructor@AllArgsConstructorpublicclassResult<T>{privateInteger code;//状态码0成功;1失败privateString msg;//提示信息privateT data;//响应数据publicstaticResultsuccess(){returnnewResult(0,"操作成功",null);}publicstatic<E>Result<E>success(E data){returnnewResult<>(0,"操作成功", data);}publicstaticResult<String>error(String msg){returnnewResult<>(1, msg,null);}}
分页返回结果实体类:
packagecom.source.bigevent2.pojo;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;importjava.util.List;//分页返回结果对象@Data@NoArgsConstructor@AllArgsConstructorpublicclassPageBean<T>{privateLong total;//总条数privateList<T> items;//当前页数据集合}
十三、注册用户
首先创建对应的UserController、UserService、UserServiceImpl、UserMapper类
然后在每个类中编写对应的逻辑代码,项目中用到的实体类请到最后 附录一 中查询
如果只添加正则校验参数不合格时会被校验,并抛出异常,如下:
这时候可以添加全局异常捕获 【开发阶段不建议添加,添加后不容易定位出错问题】
【参考:https://huaweicloud.csdn.net/638754d5dacf622b8df8b03d.html】,如下:
十四、登录认证
令牌就是一段字符串
承载业务数据, 减少后续请求查询数据库的次数
防篡改, 保证信息的合法性和有效性
JWT
JWT依赖:
JWT详细步骤:
JWT工具类:
packagecom.source.bigevent.utils;importcom.auth0.jwt.JWT;importcom.auth0.jwt.algorithms.Algorithm;importjava.util.Date;importjava.util.Map;publicclassJwtUtil{privatestaticfinalStringKEY="itheima";//接收业务数据,生成token并返回publicstaticStringgenToken(Map<String,Object> claims){returnJWT.create().withClaim("claims", claims).withExpiresAt(newDate(System.currentTimeMillis()+1000*60*60*12)).sign(Algorithm.HMAC256(KEY));}//接收token,验证token,并返回业务数据publicstaticMap<String,Object>parseToken(String token){returnJWT.require(Algorithm.HMAC256(KEY)).build().verify(token).getClaim("claims").asMap();}}
登录返回token
@PostMapping("/login")publicResultlogin(@Pattern(regexp ="^\\S{5,16}$")String username,@Pattern(regexp ="^\\S{5,16}$")String password){User user = userService.findByUserName(username);if(user ==null){returnResult.error("用户名错误");}if(Md5Util.checkPassword(password, user.getPassword())){Map<String,Object> claims =newHashMap<>();
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);returnResult.success(token);}returnResult.error("密码错误");}
十五、拦截器认证Token
步骤一、使用拦截器统一验证令牌
步骤二、登录和注册接口需要放行
主要实现逻辑为WebConfig类【注意路径中有两个”/”】
十六、使用ThreadLocal进行局部变量存储
可以存储userId等,ThreadLocalUtil见 附录一
十七、获取用户信息
通过ThreadLocal可以获取到userName、userId
十八、更新用户信息
注意:如果方法的参数是实体类,那么不能存在与之并列的其他参数,也就是说参数只能有实体类一个参数。如果是入参形式为json则必须添加@RequestBody注解,否则无法解析传参数据。
实体类参数校验:实体类成员变量上添加注解【@NotNull、@NotEmpty、@Email】、接口参数的实体参数上添加@Validated注解
十九、更新用户头像
参数校验,必须是url地址
二十、修改密码
二十一、新增文章分类
二十二、文章分类列表
二十三、更新文章分类
二十四、新增文章【自定义校验】
参数校验
自定义校验 【@State】
packagecom.source.bigevent.pojo;importcom.fasterxml.jackson.annotation.JsonFormat;importcom.source.bigevent.annotation.State;importlombok.Data;importorg.hibernate.validator.constraints.URL;importjavax.validation.constraints.NotEmpty;importjavax.validation.constraints.NotNull;importjavax.validation.groups.Default;importjava.time.LocalDateTime;/**
* 文章
*/@DatapublicclassArticle{@NotNull(groups =Edit.class)privateInteger id;@NotEmptyprivateString title;@NotEmptyprivateString content;@URLprivateString coverImg;@StateprivateString state;@NotNullprivateInteger categoryId;@NotNull(groups =Add.class)privateInteger createUser;@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss")privateLocalDateTime createTime;@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss")privateLocalDateTime updateTime;publicinterfaceAddextendsDefault{}publicinterfaceEditextendsDefault{}}
二十五、文章列表【条件分页】
引入依赖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
附录一
一、JWT工具类:
packagecom.source.bigevent.utils;importcom.auth0.jwt.JWT;importcom.auth0.jwt.algorithms.Algorithm;importjava.util.Date;importjava.util.Map;publicclassJwtUtil{privatestaticfinalStringKEY="itheima";//接收业务数据,生成token并返回publicstaticStringgenToken(Map<String,Object> claims){returnJWT.create().withClaim("claims", claims).withExpiresAt(newDate(System.currentTimeMillis()+1000*60*60*12)).sign(Algorithm.HMAC256(KEY));}//接收token,验证token,并返回业务数据publicstaticMap<String,Object>parseToken(String token){returnJWT.require(Algorithm.HMAC256(KEY)).build().verify(token).getClaim("claims").asMap();}}
二、ThreadLocal工具类
packagecom.source.bigevent.utils;/**
* ThreadLocal 工具类
*/@SuppressWarnings("all")publicclassThreadLocalUtil{//提供ThreadLocal对象,privatestaticfinalThreadLocalTHREAD_LOCAL=newThreadLocal();//根据键获取值publicstatic<T>Tget(){return(T)THREAD_LOCAL.get();}//存储键值对publicstaticvoidset(Object value){THREAD_LOCAL.set(value);}//清除ThreadLocal 防止内存泄漏publicstaticvoidremove(){THREAD_LOCAL.remove();}}
三、Md5工具类
packagecom.source.bigevent.utils;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;publicclassMd5Util{/**
* 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
*/protectedstaticchar hexDigits[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};protectedstaticMessageDigest 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
*/publicstaticStringgetMD5String(String s){returngetMD5String(s.getBytes());}/**
* 判断字符串的md5校验码是否与一个已知的md5码相匹配
*
* @param password 要校验的字符串
* @param md5PwdStr 已知的md5校验码
* @return
*/publicstaticbooleancheckPassword(String password,String md5PwdStr){String s =getMD5String(password);return s.equals(md5PwdStr);}publicstaticStringgetMD5String(byte[] bytes){
messagedigest.update(bytes);returnbufferToHex(messagedigest.digest());}privatestaticStringbufferToHex(byte bytes[]){returnbufferToHex(bytes,0, bytes.length);}privatestaticStringbufferToHex(byte bytes[],int m,int n){StringBuffer stringbuffer =newStringBuffer(2* n);int k = m + n;for(int l = m; l < k; l++){appendHexPair(bytes[l], stringbuffer);}return stringbuffer.toString();}privatestaticvoidappendHexPair(byte bt,StringBuffer stringbuffer){char c0 = hexDigits[(bt &0xf0)>>4];// 取字节中高 4 位的数字转换, >>>// 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同char c1 = hexDigits[bt &0xf];// 取字节中低 4 位的数字转换
stringbuffer.append(c0);
stringbuffer.append(c1);}}
四、通用结果返回实体类
packagecom.source.bigevent.pojo;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;@Data@NoArgsConstructor@AllArgsConstructorpublicclassResult<T>{privateInteger code;//状态码,0-成功;1-失败privateString message;//提示信息privateT data;//响应数据publicstatic<E>Result<E>success(E data){returnnewResult<>(0,"操作成功", data);}publicstaticResultsuccess(){returnnewResult(0,"操作成功",null);}publicstaticResult<String>error(String message){returnnewResult<>(1, message,null);}}
五、分页结果返回实体类
packagecom.source.bigevent.pojo;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;importjava.util.List;//分页返回结果对象@Data@NoArgsConstructor@AllArgsConstructorpublicclassPageBean<T>{privateLong total;//总条数privateList<T> items;//当前页数据集合}
六、用户实体类
packagecom.source.bigevent.pojo;importcom.fasterxml.jackson.annotation.JsonIgnore;importlombok.Data;importjava.time.LocalDateTime;/**
* 用户实体类
*/@DatapublicclassUser{privateInteger id;privateString username;@JsonIgnore//对象转Json时忽略此字段privateString password;privateString nickname;privateString email;privateString userPic;privateLocalDateTime createTime;privateLocalDateTime updateTime;}
七、文章分类实体类
packagecom.source.bigevent.pojo;importcom.fasterxml.jackson.annotation.JsonFormat;importlombok.Data;importjavax.validation.constraints.NotEmpty;importjavax.validation.constraints.NotNull;importjavax.validation.groups.Default;importjava.time.LocalDateTime;/**
* 分类
*/@DatapublicclassCategory{@NotNull(groups =Edit.class)privateInteger id;@NotEmptyprivateString categoryName;@NotEmptyprivateString categoryAlias;privateInteger createUser;@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss")privateLocalDateTime createTime;@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss")privateLocalDateTime updateTime;publicinterfaceAddextendsDefault{}publicinterfaceEditextendsDefault{}}
八、文章实体类
packagecom.source.bigevent.pojo;importcom.fasterxml.jackson.annotation.JsonFormat;//import com.source.bigevent.annotation.State;importlombok.Data;importorg.hibernate.validator.constraints.URL;importjavax.validation.constraints.NotEmpty;importjavax.validation.constraints.NotNull;importjavax.validation.groups.Default;importjava.time.LocalDateTime;/**
* 文章
*/@DatapublicclassArticle{@NotNull(groups =Edit.class)privateInteger id;@NotEmptyprivateString title;@NotEmptyprivateString content;@URLprivateString coverImg;//@StateprivateString state;@NotNullprivateInteger categoryId;@NotNull(groups =Add.class)privateInteger createUser;@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss")privateLocalDateTime createTime;@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss")privateLocalDateTime updateTime;publicinterfaceAddextendsDefault{}publicinterfaceEditextendsDefault{}}
附录二
一、SQL语句中获取当前时间的方法
在 SQL 中,获取当前时间的方法取决于你使用的数据库系统。不同的数据库系统提供了不同的函数来获取当前日期和时间。以下是一些常见数据库系统中获取当前时间的方法:
1、MySQL:
SELECTNOW();-- 或者
SELECTCURRENT_TIMESTAMP;
2、PostgreSQL:
SELECTNOW();-- 或者
SELECTCURRENT_TIMESTAMP;
3、SQL Server:
SELECTGETDATE();-- 或者
SELECTCURRENT_TIMESTAMP;
4、Oracle:
SELECTSYSTIMESTAMPFROMDUAL;-- 或者
SELECTCURRENT_TIMESTAMPFROMDUAL;
注意:SYSTIMESTAMP 返回带有时区信息的完整时间戳,而 CURRENT_TIMESTAMP 可能会根据会话的时区设置返回不同的值。
5、SQLite:
SELECTdatetime('now');-- 或者
SELECTCURRENT_TIMESTAMP;
注意:SQLite 的 CURRENT_TIMESTAMP 实际上是 datetime(‘now’) 的别名。
6、DB2:
SELECTCURRENTTIMESTAMP;-- 或者
VALUESCURRENTTIMESTAMP;
在编写 SQL 语句时,请确保你使用的是与你的数据库系统兼容的函数。这些函数通常返回当前日期和时间的完整表示,包括年、月、日、时、分、秒,有时还包括时区信息(取决于函数和数据库配置)。如果你只需要日期部分,而不包括时间,你可以使用 CURDATE()(MySQL)、CURRENT_DATE(PostgreSQL、SQL Server、DB2)等函数(具体取决于数据库系统)。
版权归原作者 android157 所有, 如有侵权,请联系我们删除。