第1章 初识SpringBoot ,开发社区首页
1.1 课程介绍
1.2 搭建开发环境
1.创建community项目
添加基本的依赖
pom.xml
<!--启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--web启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
编写配置文件
application.yml
server:
port: 8080
servlet:
context-path: /community
启动项目,输入http://localhost:8080/community
测试项目启动
2.导入数据库
评论表
CREATE TABLE `comment` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) DEFAULT NULL,
`entity_type` INT(11) DEFAULT NULL,
`entity_id` INT(11) DEFAULT NULL,
`target_id` INT(11) DEFAULT NULL,
`content` TEXT,
`status` INT(11) DEFAULT NULL,
`create_time` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_user_id` (`user_id`) /*!80000 INVISIBLE */,
KEY `index_entity_id` (`entity_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
帖子表
CREATE TABLE `discuss_post` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(45) DEFAULT NULL,
`title` VARCHAR(100) DEFAULT NULL,
`content` TEXT,
`type` INT(11) DEFAULT NULL COMMENT '0-普通; 1-置顶;',
`status` INT(11) DEFAULT NULL COMMENT '0-正常; 1-精华; 2-拉黑;',
`create_time` TIMESTAMP NULL DEFAULT NULL,
`comment_count` INT(11) DEFAULT NULL,
`score` DOUBLE DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_user_id` (`user_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
登录凭证表
CREATE TABLE `login_ticket` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`ticket` VARCHAR(45) NOT NULL,
`status` INT(11) DEFAULT '0' COMMENT '0-有效; 1-无效;',
`expired` TIMESTAMP NOT NULL,
PRIMARY KEY (`id`),
KEY `index_ticket` (`ticket`(20))
) ENGINE=INNODB DEFAULT CHARSET=utf8;
消息表
CREATE TABLE `message` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`from_id` INT(11) DEFAULT NULL,
`to_id` INT(11) DEFAULT NULL,
`conversation_id` VARCHAR(45) NOT NULL,
`content` TEXT,
`status` INT(11) DEFAULT NULL COMMENT '0-未读;1-已读;2-删除;',
`create_time` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_from_id` (`from_id`),
KEY `index_to_id` (`to_id`),
KEY `index_conversation_id` (`conversation_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
用户表
CREATE TABLE `user` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) DEFAULT NULL,
`password` VARCHAR(50) DEFAULT NULL,
`salt` VARCHAR(50) DEFAULT NULL,
`email` VARCHAR(100) DEFAULT NULL,
`type` INT(11) DEFAULT NULL COMMENT '0-普通用户; 1-超级管理员; 2-版主;',
`status` INT(11) DEFAULT NULL COMMENT '0-未激活; 1-已激活;',
`activation_code` VARCHAR(100) DEFAULT NULL,
`header_url` VARCHAR(200) DEFAULT NULL,
`create_time` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_username` (`username`(20)),
KEY `index_email` (`email`(20))
) ENGINE=INNODB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
3.使用Mybatis
导入依赖
pom.xml
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
编写配置文件
application.yml
spring:
# 数据源配置
datasource:
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/community?serverTimezone=UTC
type: com.zaxxer.hikari.HikariDataSource
hikari: # 连接池配置
maximum-pool-size: 15
minimum-idle: 5
idle-timeout: 30000
# mybatis配置
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.comunity.entity
configuration:
use-generated-keys: true # 自动生成主键
map-underscore-to-camel-case: true # 驼峰命名映射
编写实体类
/**
* 用户表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String username;
private String password;
private String salt; //用来加密
private String email;
private int type;
private int status;
private String activationCode; //激活码
private String headerUrl;
private Date createTime;
}
UserMapper
@Mapper
public interface UserMapper {
// 通过id查询用户
User selectById(int id);
// 通过username查询用户
User selectByName(String username);
// 通过邮箱查询用户
User selectByEmail(String email);
// 新增用户
int insertUser(User user);
// 修改用户状态
int updateStatus(int id,int status);
// 修改用户头像
int updateHeader(int id,String headerUrl);
// 修改用户密码
int updatePassword(int id,String password);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.community.dao.UserMapper">
<sql id="selectFields">
id,username,password,salt,email,type,status,activation_code,header_url,create_time
</sql>
<sql id="insertFields">
username,password,salt,email,type,status,activation_code,header_url,create_time
</sql>
<select id="selectById" resultType="User">
select
<include refid="selectFields"></include>
from user
where id = #{id}
</select>
<select id="selectByName" resultType="User">
select
<include refid="selectFields"></include>
from user
where username = #{username}
</select>
<select id="selectByEmail" resultType="User">
select
<include refid="selectFields"></include>
from user
where email = #{email}
</select>
<!--mybatis自动生成id,通过keyProperty将id填入user 不然为空-->
<insert id="insertUser" parameterType="User" keyProperty="id">
insert into user(<include refid="insertFields"></include>)
values(#{username},#{password},#{salt},#{email},#{type},#{status},#{activationCode},#{headerUrl},#{createTime})
</insert>
<update id="updateStatus">
update user
set status=#{status}
where id = #{id}
</update>
<update id="updateHeader">
update user
set header_url=#{headerUrl}
where id = #{id}
</update>
<update id="updatePassword">
update user
set password=#{password}
where id = #{id}
</update>
</mapper>
编写测试类用来测试sql
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MapperTests {
@Autowired
private UserMapper userMapper;
}
查询
@Test
public void TestSelectUser() {
User user = userMapper.selectById(101);
System.out.println(user);
user= userMapper.selectByName("张三");
System.out.println(user);
user = userMapper.selectByEmail("1345313@sina.com");
System.out.println(user);
}
插入
@Test
public void TestInsertUser(){
User user = new User();
user.setUsername("李四");
user.setPassword("123456");
user.setSalt("abc");
user.setEmail("lisi@qq.com");
user.setHeaderUrl("");
user.setCreateTime(new Date());
int row = userMapper.insertUser(user);
System.out.println(row);
System.out.println(user.getId());
}
修改
@Test
public void TestUpdateUser(){
int row = userMapper.updateStatus(123, 1);
System.out.println(row);
row = userMapper.updateHeader(123, "123.com");
System.out.println(row);
row = userMapper.updatePassword(123, "23123");
System.out.println(row);
}
1.3 开发社区首页
1. 准备环境
导入静态资源
引入thymealeaf
pom.xml
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
关闭thymealeaf模板引擎
application.yml
# 关闭缓存
thymeleaf:
cache: false
2. 代码开发
实体类
DiscussPost
/**
* 帖子表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DiscussPost {
private Integer id;
private Integer userId;
private String title;
private String content;
// 类型 0:普通 1:置顶
private Integer type;
// 状态 0:正常 1:精华 2:拉黑
private Integer status;
private Date createTime;
private Integer commentCount; // 评论数量
private double score;
}
数据访问层
DiscussPostMapper
@Mapper
public interface DiscussPostMapper {
// 根据用户id查询帖子列表,动态sql(也可以不使用userId)
List<DiscussPost> selectDiscussPosts(int userId,int offset,int limit);
// 查询数据行数 动态拼接条件且只有一个参数时 需要@Param
int selectDiscussPostRows(@Param("userId") int userId);
}
DiscussPostMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.community.dao.DiscussPostMapper">
<sql id="selectFields">
id,user_id,title,content,type,status,create_time,comment_count,score
</sql>
<select id="selectDiscussPosts" resultType="DiscussPost">
select
<include refid="selectFields"></include>
from discuss_post
where status!=2
<if test="userId!=0">
and user_id=#{userId}
</if>
order by type desc,create_time desc
limit #{offset},#{limit}
</select>
<select id="selectDiscussPostRows" resultType="int">
select count(id)
from discuss_post
where status!=2
<if test="userId!=0">
and user_id=#{userId}
</if>
</select>
</mapper>
测试sql
/**
* DiscussPostMapper
*/
@Test
public void testSelectPosts(){
List<DiscussPost> list = discussPostMapper.selectDiscussPosts(0, 0, 10);
for (DiscussPost discussPost : list) {
System.out.println(discussPost);
}
int rows = discussPostMapper.selectDiscussPostRows(0);
System.out.println(rows);
}
数据业务层
DiscussPostService
@Service
public class DiscussPostService {
@Autowired
private DiscussPostMapper discussPostMapper;
/**
* 查询帖子列表
* @param userId
* @param offset
* @param limit
* @return
*/
public List<DiscussPost> findDiscussPosts(int userId,int offset,int limit){
return discussPostMapper.selectDiscussPosts(userId, offset, limit);
}
/**
* 获取帖子行数
* @param userId
* @return
*/
public int findDiscussPostRows(int userId){
return discussPostMapper.selectDiscussPostRows(userId);
}
}
UserService
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 根据id查询用户信息
* @param id
* @return
*/
public User findUserById(int id){
return userMapper.selectById(id);
}
}
视图层
HomeController
@Controller
public class HomeController {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String GetIndexPage(Model model){
List<DiscussPost> list = discussPostService.findDiscussPosts(0, 0, 10);
List<Map<String,Object>> discussPosts=new ArrayList<>();
if (list!=null){
for (DiscussPost post : list) {
Map<String,Object> map=new HashMap<>();
map.put("post",post);
User user = userService.findUserById(post.getUserId());
map.put("user",user);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts",discussPosts);
return "/index";
}
}
index.html
启动测试
输入http://localhost:8080/community/index
实现分页
Page
/**
* 封装分页相关信息
*/
@Data
public class Page {
// 当前页码
private int current = 1;
// 显示上限
private int limit = 10;
// 数据总数 (用于计算总页数)
private int rows;
// 查询路径 (用于复用分页链接)
private String path;
public void setCurrent(int current) {
if (current >= 1) {
this.current = current;
}
}
public void setLimit(int limit) {
if (limit >= 1 && limit <= 100) {
this.limit = limit;
}
}
public void setRows(int rows) {
if (rows >= 0) {
this.rows = rows;
}
}
/**
* 获取当前页的起始页
*
* @return
*/
public int getOffset() {
// current * limit -limit
return (current - 1) * limit;
}
/**
* 获取总页数
*
* @return
*/
public int getTotal() {
// rows / limit [+1]
if (rows % limit == 0) {
return rows / limit;
} else {
return rows / limit + 1;
}
}
/**
* 获取起始页码
*
* @return
*/
public int getFrom() {
int from = current - 2;
return from < 1 ? 1 : from;
}
/**
* 获取结束页码
* @return
*/
public int getTo() {
int to = current + 2;
int total = getTotal();
return to > total ? total : to;
}
}
优化Controller代码
HomeController
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String GetIndexPage(Model model, Page page){
// 方法调用前,SpringMVC会自动实力胡model和page,并将Page注入Model
// 所以,在thymealeaf中可以直接访问Page对象中的数据
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
// 获取索引帖子信息
List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
// 向页面返回的集合
List<Map<String,Object>> discussPosts=new ArrayList<>();
if (list!=null){
for (DiscussPost post : list) {
Map<String,Object> map=new HashMap<>();
map.put("post",post);
User user = userService.findUserById(post.getUserId());
map.put("user",user);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts",discussPosts);
return "/index";
}
将页面静态效果修改为动态效果
<!-- 分页 -->
<nav class="mt-5" th:if="${page.rows>0}" th:fragment="pagination">
<ul class="pagination justify-content-center" >
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=1)}">首页</a>
</li>
<li th:class="|page-item ${page.current==1?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页</a>
</li>
<li th:class="|page-item ${i==page.current?'active':''}|"
th:each="i:${#numbers.sequence(page.getFrom(),page.getTo())}">
<a class="page-link" th:href="@{${page.path}(current=${i})}" th:text="${i}">1</a>
</li>
<li th:class="|page-item ${page.current==page.getTotal()?'disabled':''}|">
<a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
</li>
<li class="page-item">
<a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
</li>
</ul>
</nav>
第2章 SpringBoot实践,开发社区登录模块
1、发送邮件
启用SMTP服务
jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
编写配置文件
application.yml
这里邮件发送我用的是qq邮箱
工具类MailSender
/**
* 邮件发送
*/
@Component
public class MailClient {
// 声明log
private static final Logger logger= LoggerFactory.getLogger(MailClient.class);
@Autowired
private JavaMailSender mailSender;
// 发件人
@Value("${spring.mail.username}")
private String from;
/**
* 发送邮件
* @param to 收件人
* @param subject 标题
* @param content 内容
*/
public void sendMail(String to,String subject,String content){
try {
// 创建message对象
MimeMessage message =mailSender.createMimeMessage();
MimeMessageHelper helper =new MimeMessageHelper(message);
// 设置邮件信息
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content,true); // 可转换html标签
// 发送邮件
mailSender.send(helper.getMimeMessage());
} catch (MessagingException e) {
logger.error("发送邮件失败"+e.getMessage());
}
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MailTests {
@Autowired
private MailClient mailClient;
@Test
public void testTextMail(){
mailClient.sendMail("@qq.com","test","welcome");
}
}
接收到的邮件效果
利用Html模板发送邮件
templates/mail/activation.html
引入thymeleaf,传入一个username参数
发送模板邮件测试
@Autowired
private TemplateEngine templateEngine;
@Test
public void testHtmlMail(){
Context context=new Context();
context.setVariable("username", "sunday");
String content=templateEngine.process("/mail/activation",context);
System.out.println(content);
mailClient.sendMail("@qq.com","html",content);
}
控制台输出
邮箱内容
2、开发注册功能
2.1 访问注册页面
编写LoginController来获取注册页面
LoginController
@Controller
public class LoginController {
/**
* 获取注册页面
* @return
*/
@RequestMapping(value = "/register",method = RequestMethod.GET)
public String getRegisterPage(){
return "/site/register";
}
}
使用thymeleaf对页面进行修改
静态资源路径
修改主页的头部信息
效果
点击注册进入注册页面
2.2 提交注册数据
准备工作
- 导