目录
1. 综述
博客(Blog)是一种基于网络技术的在线日记,它是由一组文章组成的网站,它允许个人或团体在互联网上发布文章、观点、图片和视频等内容,同时也允许访问者对这些内容进行评论和互动交流。
博客的作用非常广泛,它可以用于个人记录生活、分享经验和见解;也可以用于商业推广、品牌宣传和营销推广;还可以用于新闻报道、时事评论和社会观察等方面。博客不仅是一种媒介,更是一种社交工具,它可以帮助人们建立社交网络和人际关系,增加知识和经验的交流和共享。
博客的特点是实时更新、互动交流、内容多样化和自由性强,它为人们提供了一个开放的平台,让个人和团体可以借助互联网的力量,传递自己的声音和信息,影响和改变社会。
包括我们目前正在使用的CSDN也是博客平台,我们可以在这上面交流技术,分享见解等等等,那么你有没有想过自己实现一个个人博客项目呢?接下来我将使用ssm框架进行个人博客项目的实现。(以专业版IDEA为例)
2. 实现步骤
2.1 创建一个SSM项目
首先,我们要在IDEA中创建出一个SSM项目,其步骤具体如下:
首先要新建一个项目,选择Spring Initializr,同时要记得下面的Java版本要选JDK8,其余具体配置如下图:
随后点击next,Spring Boot的版本注意选择2.X.X版本的,我这里就选择了2.7.10,注意后面有括号的一般指的是测试版,可能有些不稳定,尽量不要选有括号的
然后我们要分别勾选Spring Boot DevTools,Lombok,Spring Web,MySQL Driver以及MyBatis Framework这五大依赖,随后点击finish即可,如下图:
2.2 准备项目
在进行正式的项目实现前,我们需要先对项目进行初始化。
2.2.1 删除无用文件
见上图,其中.mvn、HELP.md、mvnw、mvnw.cmd都属于无用文件,可以删除,当然,不删除也是没什么问题的。
2.2.2 引入前端页面
作为一个ssm框架实现的项目,我们是需要前端页面和后端代码实现的,但是我们作为后端程序员,前端页面自然不用我们自己写,关于前端页面的代码可以去我的gitee中引入,这里就放在文章的末尾了,当我们拿到前端的代码的时候,我们要将其放置在resources文件下的static目录中,添加后如下图:
2.2.3 添加配置文件
配置文件分为properties以及yml,由于properties较为老旧,我们通常使用yml的配置文件,因此我们需要选择在resources下新建一个application.yml的文件,同时删除掉application.properties文件
随后我们要在配置文件中,即application.yml中添加相对应的配置信息,如下
# 配置数据库的连接字符串
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
username: root
password: "021226"
driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:
mapper-locations: classpath:mapper/*Mapper.xml
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:
level:
com:
example:
demo: debug
以上有两点需要注意,这个配置文件中,password要改成你数据库自己的密码;还有url中,
url: jdbc:mysql://127.0.0.1:3306/连接的数据库名字?characterEncoding=utf8,即问号前面要改成你要连接哪个数据库,还有配置打印MyBatis执行的SQL的目录要改成对应的目录,我这里的话是com.example.demo,故就这样写,其他地方可以不需要进行改动
2.3 初始化数据库
我们需要创建一个对应的database,即数据库,随后往数据库中添加两张表,一张为用户表,其字段为id,username,password,photo,createtime,updatetime,state;
另一张为文章表,字段为id,title,content,createtime,updatetime,uid,rcount,state这里可以直接复制这里的代码到数据库中即可
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
use mycnblog;
-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(65) not null,
photo varchar(500) default '',
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
`state` int default 1
) default charset 'utf8mb4';
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int not null,
rcount int not null default 1,
`state` int default 1
)default charset 'utf8mb4';
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
uid int
)default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
values('Java','Java正文',1);
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
2.4 进行项目分层
一个SSM项目,应该由controller层,service层,mapper层,config层以及实体类,我们要在demo下创建这几个软件包,同时在resoucrs下也创建出一个文件目录,名为mapper用于后面的MyBatis的操作
2.5 添加统一返回类型
我们在demo下新建一个common的package,同时创建一个类名为AjaxResult用于规定统一返回类型
其中规定返回类型时,有状态码,状态码的具体信息以及返回的数据,我们需要考虑失败时以及成功时的情况,进行不同方法的创建,同时要重载方法,使得传值的时候更加灵活
package com.example.demo.common;
import lombok.Data;
import java.io.Serializable;
/*
统一数据格式返回
*/
@Data
public class AjaxResult implements Serializable {
//状态码
private Integer code;
//状态码描述信息
private String msg;
//返回的数据
private Object data;
/*
操作成功返回的结果
*/
public static AjaxResult success(Object data){
AjaxResult result=new AjaxResult();
result.setCode(200);
result.setMsg("");
result.setData(data);
return result;
}
public static AjaxResult success(Object data,int code){
AjaxResult result=new AjaxResult();
result.setCode(code);
result.setMsg("");
result.setData(data);
return result;
}
public static AjaxResult success(Object data,int code, String msg){
AjaxResult result=new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
/*
* 返回失败结果
* */
public static AjaxResult fail(int code,String msg){
AjaxResult result=new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(null);
return result;
}
public static AjaxResult fail(int code,String msg,Object data){
AjaxResult result=new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
}
但是并不是所有人都会使用这个类来进行统一数据格式来返回的,于是我们要做出以防万一的手段,使得如果返回的数据类型被检测为不是统一的对象,我们就将其封装为统一的对象,我们在common包下创建出一个ResponseAdvice类用来实现这一功能
实现这一功能需要添加ControllerAdvice注解,同时要实现ResponseBodyAdvice接口,重写supports方法和beforeBodyWrite方法
在进行处理时,如果是AjaxResult类型,直接返回即可;如果是String类型需要进行特殊处理,需要使用jackson进行类型的转化
package com.example.demo.config;
import com.example.demo.common.AjaxResult;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/*
实现 统一数据返回的保底类
即在返回数据之前,检测数据的类型是否是统一对象,如果不是,则封装成统一的对象
*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
// 开关,唯有返回值为true时才会进行beforeBodyWrite的调用
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
/*
对数据格式进行校验和封装
*/
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
//是统一的数据格式
if(body instanceof AjaxResult){
return body;
}
//如果是字符串类型,我们需要引入ObjectMapper进行数据的处理,将其处理为json格式
if(body instanceof String){
return objectMapper.writeValueAsString(AjaxResult.success(body));
}
return AjaxResult.success(body);
}
}
2.6 实现用户的注册功能
实现用户的注册功能,我们可以先从前端到后端;我们要先编写reg.html,用于发送ajax请求,这里就不进行前端的编写了,可以直接去我的源码那里复制粘贴即可,这里接下来要演示后端的写法
进行后端的操作时,我们要先在controller下创建一个UserController,控制器是一个安保系统,来验证前端发来的数据的可靠性,再调用UserService进行服务的编排和调度,最后UserService调用UserMapper进行用户的添加
在进行用户的添加时,我们需要先创建出用户这个类,即在entity包下创建一个Userinfo类
package com.example.demo.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Userinfo {
private Integer id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer state;
}
其中@Data注解是Lombok提供的注解,可以让我们不需要写get,set方法等也能够使用对应方法
创建好用户类后,我们需要在mapper下创建一个UserMapper接口进行操作的描写
同时要在resources下刚刚创建的mapper文件夹中新建一个名为UserMapper.xml的文件,此时目录结构如下图:
UserMapper.xml中要引入配置,其代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
</mapper>
其中要注意修改namespace中的目录结构和类名,随后就可以进行添加操作的描写
添加是insert操作,id为reg,其sql如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<insert id="reg">
insert into userinfo(username,password) values(#{username},#{password})
</insert>
</mapper>
然后我们再在service包下创建出UserService类,其中要注入userMapper同时,要调用userMapper的reg方法
package com.example.demo.service;
import com.example.demo.entity.Userinfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public int reg(Userinfo userinfo){
return userMapper.reg(userinfo);
}
}
随后我们再在UserMapper类中也进行注册代码的完善,如下:
package com.example.demo.controller;
import com.example.demo.common.AjaxResult;
import com.example.demo.entity.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/reg")
public AjaxResult reg(Userinfo userinfo){
//非空校验和参数有效性校验
if(userinfo==null || !StringUtils.hasLength(userinfo.getUsername()) ||
!StringUtils.hasLength(userinfo.getPassword())){
return AjaxResult.fail(-1,"非法参数");
}
return AjaxResult.success(userService.reg(userinfo));
}
}
到这里,注册的功能就已经实现了
2.7 实现用户的登录功能
与注册功能类似,我们也需要前端与后端的编写,同样,前端部分可以去我的源码处复制粘贴即可
后端部分,我们同样从mapper部分开始进行写
这里同时也需要在sql中给用户名添加唯一约束,为了后面根据用户名查询user对象时不会报错
alter table userinfo add unique(username);
然后在userMapper中添加一个查询操作,即根据用户名来获取用户对象
package com.example.demo.mapper;
import com.example.demo.entity.Userinfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserMapper {
//注册
int reg(Userinfo userinfo);
//根据用户查询userinfo对象
Userinfo getUserByName(@Param("username") String username);
}
同时在UserMapper.xml中添加对应的sql语句
<select id="getUserByName" resultType="com.example.demo.entity.Userinfo">
select * from userinfo where username=#{username}
</select>
然后再在UserService中添加相应的方法
public Userinfo getUserByName(String username){
return userMapper.getUserByName(username);
}
然后在controller中创建一个相应的方法,其过程具体如下
@RequestMapping("/login")
public AjaxResult login(String username,String password){
//1.非空校验
if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
return AjaxResult.fail(-1,"非法请求");
}
//2.查询数据库
Userinfo userinfo=userService.getUserByName(username);
if(userinfo!=null && userinfo.getId()>0){
//有效的用户名
//密码是否正确
if(password.equals(userinfo.getPassword())){
//登录成功
userinfo.setPassword(""); //返回前端之前,隐藏敏感信息
return AjaxResult.success(userinfo);
}
}
return AjaxResult.success(null,0);
}
到这里,登陆功能的代码就已经基本实现了,不过上述controller的代码仍有问题,用户在成功登陆后,我们应该将用户的信息存储到session中,我们需要先获取到HttpServletRequest对象,再获取session
改动后的代码如下:
@RequestMapping("/login")
public AjaxResult login(HttpServletRequest request,String username, String password){
//1.非空校验
if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
return AjaxResult.fail(-1,"非法请求");
}
//2.查询数据库
Userinfo userinfo=userService.getUserByName(username);
if(userinfo!=null && userinfo.getId()>0){
//有效的用户名
//密码是否正确
if(password.equals(userinfo.getPassword())){
//登录成功
//todo:将用户存储到session中
HttpSession session=request.getSession();
session.setAttribute(AppVariable.USER_SESSION_KEY,userinfo);
userinfo.setPassword(""); //返回前端之前,隐藏敏感信息
return AjaxResult.success(userinfo);
}
}
return AjaxResult.success(null,0);
}
同时我们可以在common下创建一个类,这个类中用于存储全局变量,比如说session的key值等
package com.example.demo.common;
/*
全局变量
*/
public class AppVariable {
//用户 session key
public static final String USER_SESSION_KEY="USER_SESSION_KEY";
}
在完成了登陆操作后,由于用户可能会在未登陆状态下点击功能,此时我们需要拦截用户,让其去登陆,故我们要写一个登陆拦截器,我们在config下新建一个LoginIntercept类,让其实现HandlerInterceptor接口,并重写preHandle方法
package com.example.demo.config;
import com.example.demo.common.AppVariable;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/*
登陆拦截器
*/
public class LoginIntercept implements HandlerInterceptor {
/*
返回true即用户已登陆,false即用户未登陆
* */
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session=request.getSession(false);
if(session!=null && session.getAttribute(AppVariable.USER_SESSION_KEY)!=null){
//用户已登陆
return true;
}
//跳转到登陆页面
response.sendRedirect("/login.html");
return false;
}
}
拦截器写完后,我们要将其配置到系统文件内,我们在config下新建一个AppConfig类,让其实现WebMvcConfigurer接口,并添加@Configuration注解,我们选定部分路径选择不进行拦截
package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginIntercept())
.addPathPatterns("/**")
.excludePathPatterns("/css/**")
.excludePathPatterns("/editor.md/**")
.excludePathPatterns("/img/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/login.html")
.excludePathPatterns("/reg.html")
.excludePathPatterns("/blog_list.html")
.excludePathPatterns("/blog_content.html")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/reg");
}
}
到这里结束后,登陆功能就已经完成了。
2.8 实现用户详情页
用户详情页也同样需要前后端进行实现,前端代码还是可以复制我的源码
后端部分,我们同样开始从mapper进行实现,同时想要获取文章的内容,应该去查文章表,所以我们要先在mapper目录下新建一个ArticleMapper接口
package com.example.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface ArticleMapper {
int getArtCountByUid(@Param("uid") Integer uid);
}
同时我们要在resources下的mapper也创建一个对应的xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleMapper">
<select id="getArtCountByUid" resultType="Integer">
select count(*) from articleinfo where uid=#{uid};
</select>
</mapper>
接下来,跟上面类似,我们要在service下创建出一个ArticleService类
package com.example.demo.service;
import com.example.demo.mapper.ArticleMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class ArticleService {
@Resource
private ArticleMapper articleMapper;
public int getArtCountByUid(Integer uid){
return articleMapper.getArtCountByUid(uid);
}
}
接下来要进行Controller的实现,我们在UserController中注入ArticleService类,并写出相应方法
但是这里多了个新的属性,文章总数,所以我们可以新建一个类,让这个类继承Userinfo,同时在这个类中添加新的属性artCount,我们可以在entity下新建一个package,然后新建一个类进行继承
package com.example.demo.entity.vo;
import com.example.demo.entity.Userinfo;
import lombok.Data;
@Data
public class UserinfoVO extends Userinfo {
private Integer artCount;//发表的文章总数
}
由于需要多次用到用户的登陆信息,我们可以在common下新建一个公共类,用来提供当前登陆用户相关的操作
package com.example.demo.common;
import com.example.demo.entity.Userinfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/*
当前登陆用户相关操作
*/
public class UserSessionUtils {
//得到当前登陆用户
public static Userinfo getSessionUser(HttpServletRequest request){
HttpSession session=request.getSession(false);
if(session!=null && session.getAttribute(AppVariable.USER_SESSION_KEY)!=null){
//用户已登陆
return (Userinfo)session.getAttribute(AppVariable.USER_SESSION_KEY);
}
return null;
}
}
此时,我们就可以进行登陆用户相关的信息补充,即在UserController类中进行用户文章数目的填充
@RequestMapping("/showinfo")
public AjaxResult showInfo(HttpServletRequest request){
UserinfoVO userinfoVO=new UserinfoVO();
//1.得到当前登陆用户(从session中获取)
Userinfo userinfo= UserSessionUtils.getSessionUser(request);
if(userinfo==null){
return AjaxResult.fail(-1,"非法请求");
}
//Spring提供的深克隆方法
BeanUtils.copyProperties(userinfo,userinfoVO);
//2.得到用户发表文章的总数
userinfoVO.setArtCount(articleService.getArtCountByUid(userinfo.getId()));
return AjaxResult.success(userinfoVO);
}
接下来我们要获取右边的信息,即文章列表页的信息,想要获取文章列表的信息,我们需要文章的实体类,于是我们应该在entity下新建一个文章的实体类
package com.example.demo.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
@Data
public class Articleinfo {
private Integer id;
private String title;
private String content;
//JsonFormat注解用于规范输出
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private LocalDateTime createtime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private LocalDateTime updatetime;
private Integer uid;
private Integer rcount;
private Integer state;
}
随后在ArticleMapper中添加对应的根据uid获取文章列表的方法
List<Articleinfo> getMyList(@Param("uid") Integer uid);
再到ArticleMapper.xml中添加对应的sql语句
<select id="getMyList" resultType="com.example.demo.entity.Articleinfo">
select * from articleinfo where uid=#{uid}
</select>
然后再接着进行Service的对应方法
public List<Articleinfo> getMyList(Integer uid){
return articleMapper.getMyList(uid);
}
然后最后要进行Controller的完成,但是因为我们要进行的是文章表的相关操作,我们需要新建一个ArticleController类
package com.example.demo.controller;
import com.example.demo.common.AjaxResult;
import com.example.demo.common.UserSessionUtils;
import com.example.demo.entity.Articleinfo;
import com.example.demo.entity.Userinfo;
import com.example.demo.service.ArticleService;
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;
import java.util.List;
@RestController
@RequestMapping("/art")
public class ArticleController {
@Autowired
private ArticleService articleService;
@RequestMapping("/mylist")
public AjaxResult getMyList(HttpServletRequest request){
Userinfo userinfo= UserSessionUtils.getSessionUser(request);
if(userinfo==null){
return AjaxResult.fail(-1,"非法请求");
}
List<Articleinfo> list=articleService.getMyList(userinfo.getId());
return AjaxResult.success(list);
}
}
2.9 注销功能的实现
注销功能是不需要操作数据库的,我们直接在UserController中进行操作即可,我们直接调用session的removeAttribute方法
/*
* 注销功能的实现
* */
@RequestMapping("/logout")
public AjaxResult logout(HttpSession session){
session.removeAttribute(AppVariable.USER_SESSION_KEY);
return AjaxResult.success("1");
}
2.10 删除文章
删除文章同样需要前后端进行交互,前端还是可以看我的源码,后端的部分过程如下:
我们首先要在ArticleMapper中定义一个删除的方法,删除文章我们需要文章的id同时要有用户的id,我们要比对文章的所归属的uid与当前的用户id是否相同,如果不同则不能进行删除操作
int del(@Param("id") Integer id,@Param("uid") Integer uid);
随后去ArticleMapper.xml中实现相应的删除sql
<delete id="del">
delete from articleinfo where id=#{id} and uid=#{uid}
</delete>
与上面相同,接下来要去ArticleService中进行实现
public int del(Integer id,Integer uid){
return articleMapper.del(id,uid);
}
最后前往ArticleController进行相应的方法实现
@RequestMapping("/del")
public AjaxResult del(Integer id,HttpServletRequest request){
if(id==null && id<=0){
//参数有误
return AjaxResult.fail(-1,"参数异常");
}
Userinfo user=UserSessionUtils.getSessionUser(request);
if(user==null){
return AjaxResult.fail(-2,"用户未登陆");
}
return AjaxResult.success(articleService.del(id,user.getId()));
}
2.11 查看文章详情页
查看文章详情页的过程应该为,先从url中得到文章id,从后端查询当前文章的详细信息,再根据上一步查询的uid来查询用户的信息,最后再实现阅读量的自增
后端的代码同样要从ArticleMapper开始写起,我们创建一个获取文章信息的方法
Articleinfo getDetail(@Param("id") Integer id);
然后到ArticleMapper.xml中进行对应sql语句的补充
<select id="getDetail" resultType="com.example.demo.entity.Articleinfo">
select * from articleinfo where id=#{id}
</select>
同样的,我们接下来再到ArticleService中进行对应方法的实现
public Articleinfo getDetail(Integer id){
return articleMapper.getDetail(id);
}
然后我们再到ArticleController中进行对应方法的实现
@RequestMapping("/detail")
public AjaxResult getDetail(Integer id){
//非空校验
if(id==null || id<0){
return AjaxResult.fail(-1,"非法参数");
}
return AjaxResult.success(articleService.getDetail(id));
}
2.11 显示文章作者的详情信息
我们需要根据用户的id获取用户对象,同理我们先在UserMapper中进行对应方法的实现
//根据用户IdD查询userinfo对象
Userinfo getUserById(@Param(("id")) Integer id);
然后再到UserMapper.xml中实现相应的sql语句
<select id="getUserById" resultType="com.example.demo.entity.Userinfo">
select * from userinfo where id=#{id}
</select>
与上面同理,我们再到UserService中进行相应方法的实现
public Userinfo getUserById(Integer id){
return userMapper.getUserById(id);
}
UserService的方法写完后,我们就可以到UserController中进行方法的实现
@RequestMapping("/getuserbyid")
public AjaxResult getUserById(Integer id){
if(id==null || id<=0){
//无效参数
return AjaxResult.fail(-1,"非法参数");
}
Userinfo userinfo=userService.getUserById(id);
if(userinfo==null || userinfo.getId()<=0){
return AjaxResult.fail(-1,"无效参数");
}
//去除敏感数据
userinfo.setPassword("");
//查询当前用户发表的文章数
UserinfoVO userinfoVO=new UserinfoVO();
BeanUtils.copyProperties(userinfo,userinfoVO);
userinfoVO.setArtCount(articleService.getArtCountByUid(id));
return AjaxResult.success(userinfoVO);
}
2.12 请求详情页时使得阅读量增加
我们还是从后端部分的ArticleMapper开始写起,我们定义一个incrRCount()方法
int incrRCount(@Param("id") Integer id);
随后我们进行mapper部分的sql语句的实现
<update id="incrRCount">
update articleinfo set rcount=rcount+1 where id=#{id}
</update>
然后再进行ArticleService部分的代码实现
public int incrRCount(Integer id){
return articleMapper.incrRCount(id);
}
最后再到ArticleController进行最后的代码实现即可
@RequestMapping("/incr-rcount")
public AjaxResult incrRCount(Integer id){
if(id!=null && id>0){
return AjaxResult.success(articleService.incrRCount(id));
}
return AjaxResult.fail(-1,"未知错误");
}
2.13 文章的添加和修改功能
我们先从文章的添加功能说起,文章的添加功能的后端代码方面,我们也先从ArticleMapper进行写起
int add(Articleinfo articleinfo);
我们再到ArticleMapper.xml中进行对应sql的实现
<insert id="add">
insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})
</insert>
我们再到ArticleService中进行对应的实现
public int add(Articleinfo articleinfo){
return articleMapper.add(articleinfo);
}
最后再到ArticleController中进行对应代码的实现
@RequestMapping("/add")
public AjaxResult add(Articleinfo articleinfo,HttpServletRequest request){
//1.非空校验
if(articleinfo==null || !StringUtils.hasLength(articleinfo.getTitle()) ||
!StringUtils.hasLength(articleinfo.getContent())){
//非法参数
return AjaxResult.fail(-1,"非法参数");
}
//2.数据库添加操作
//a.得到当前用户的uid
Userinfo userinfo=UserSessionUtils.getSessionUser(request);
if(userinfo==null || userinfo.getId()<=0){
//无效的用户
return AjaxResult.fail(-2,"无效的登陆用户");
}
articleinfo.setUid(userinfo.getId());
return AjaxResult.success(articleService.add(articleinfo));
}
博客的修改功能的实现较为复杂,我们要先获取到文章的id,再前往后端得到文章的详情信息并设置在页面上,再进行文章修改操作,前面两个操作我们在之前已经进行实现过了,我们的重点在于实现第三个功能,即进行文章的修改操作,我们也同样从mapper部分开始实现
int update(Articleinfo articleinfo);
然后我们再进行ArticleMapper.xml部分代码的实现
<update id="update">
update articleinfo set title=#{title},content=#{content},updatetime=#{updatetime} where id=#{id} and uid=#{uid}
</update>
然后我们再进行ArticleService部分的代码实现
public int update(Articleinfo articleinfo){
return articleMapper.update(articleinfo);
}
最后再进行ArticleController部分的代码实现
@RequestMapping("/update")
public AjaxResult update(Articleinfo articleinfo,HttpServletRequest request){
if(articleinfo==null || !StringUtils.hasLength(articleinfo.getTitle())
|| !StringUtils.hasLength(articleinfo.getContent()) ||
articleinfo.getId()==null){
//非法参数
return AjaxResult.fail(-1,"非法参数");
}
//得到当前登陆用户的ID
Userinfo userinfo=UserSessionUtils.getSessionUser(request);
if(userinfo==null && userinfo.getId()==null){
//无效登陆用户
return AjaxResult.fail(-2,"无效用户");
}
//核心代码(解决了修改文章归属人)
articleinfo.setUid(userinfo.getId());
articleinfo.setUpdatetime(LocalDateTime.now());
return AjaxResult.success(articleService.update(articleinfo));
}
2.14 密码加盐处理
密码加盐处理的重点在于,每次调用方法的时候都会产生盐值,且这个盐值随机且唯一,我们将这个随机盐值与我们本身的密码相加,得到的就是最终的密码,加密其实较为简单,难的在于解密操作,我们这里的盐值可以选定为uuid,因为uuid都是随机的
解密操作我们需要得到盐值,即我们根据自己设定一个规律,对用户输入的密码进行相应的加密,我们可以设定最终密码格式为:盐值$最终的密码,然后与数据库中的密码进行相应的对比,来判断是否是正确的密码
即正确做法是,先从最终密码获取得到盐值,再将盐值与用户输入的密码组合在一起进行md5加密,最后再与盐值结合起来与数据库中存储的密码进行比对
我们可以在common下新建一个关于密码的工具类,写两个主要方法,一个方法用于加盐,一个用于验证密码
package com.example.demo.common;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.UUID;
public class PasswordUtils {
//1. 加盐并生成密码
public static String encrypt(String password){
//盐值
String salt= UUID.randomUUID().toString().replace("-",""); //32位盐值
//生成加盐后密码
String saltPassword= DigestUtils.md5DigestAsHex((salt+password).getBytes());
//生成最终密码(保存到数据库中的密码,32位盐值+$+32位加盐之后的密码)
String finalPassword=salt+"$"+saltPassword;
return finalPassword;
}
//2.生成加盐的密码(重载
public static String encrypt(String password,String salt){
//生成加盐密码
String saltPassword= DigestUtils.md5DigestAsHex((salt+password).getBytes());
//生成最终密码
String finalPassword=salt+"$"+saltPassword;
return finalPassword;
}
//3.验证密码
//inputPassword为用户输入明文密码
//finalPassword为数据库保存的密码
public static boolean check(String inputPassword,String finalPassword){
if(StringUtils.hasLength(inputPassword) && StringUtils.hasLength(finalPassword) && finalPassword.length()==65){
//得到盐值
String salt=finalPassword.split("\\$")[0];
// 进行加密得到相应的密码
String confirmPassword=encrypt(inputPassword,salt);
//对比两个密码是否相同
return confirmPassword.equals(finalPassword);
}
return false;
}
}
此时我们需要修改两个地方,一个是userController中登陆时的密码校验,以及注册时的密码存储
@RequestMapping("/login")
public AjaxResult login(HttpServletRequest request,String username, String password){
//1.非空校验
if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
return AjaxResult.fail(-1,"非法请求");
}
//2.查询数据库
Userinfo userinfo=userService.getUserByName(username);
if(userinfo!=null && userinfo.getId()>0){
//有效的用户名
//密码是否正确
if(PasswordUtils.check(password,userinfo.getPassword())){
//登录成功
//todo:将用户存储到session中
HttpSession session=request.getSession();
session.setAttribute(AppVariable.USER_SESSION_KEY,userinfo);
userinfo.setPassword(""); //返回前端之前,隐藏敏感信息
return AjaxResult.success(userinfo);
}
}
return AjaxResult.success(null,0);
}
@RequestMapping("/reg")
public AjaxResult reg(Userinfo userinfo){
//非空校验和参数有效性校验
if(userinfo==null || !StringUtils.hasLength(userinfo.getUsername()) ||
!StringUtils.hasLength(userinfo.getPassword())){
return AjaxResult.fail(-1,"非法参数");
}
//密码加盐
userinfo.setPassword(PasswordUtils.encrypt(userinfo.getPassword()));
return AjaxResult.success(userService.reg(userinfo));
}
2.15 所有人的博客页面以及分页功能的实现
分页功能的实现关键在于,前端需要知道每页显示的固定博客数量,以及当前页码
后端也需要知道这两个
同时分页功能的实现也需要数据库中的limit offset关键字来进行实现
同理,我们在后端部分的代码也先从ArticleMapper部分写起
List<Articleinfo> getListByPage(@Param("psize")Integer psize,@Param("offsize") Integer offsize);
接下来,我们再进行ArticleMapper.xml部分的sql语句的实现
<select id="getListByPage" resultType="com.example.demo.entity.Articleinfo">
select * from articleinfo limit #{psize} offset #{offsize}
</select>
完成了Mapper部分的设计后,我们接下来继续完成ArticleService的代码实现
public List<Articleinfo> getListByPage(Integer psize,Integer offsize){
return articleMapper.getListByPage(psize,offsize);
}
最后就是完成ArticleController部分的代码实现
@RequestMapping("/listbypage")
//pindex为当前页面,且至少为1
//psize为每页的显示条数
public AjaxResult getListByPage(Integer pindex,Integer psize){
//1.参数矫正
if(pindex==null || pindex<=1){
pindex=1;
}
if(psize==null || psize<=1){
psize=2;
}
//分页公式的值(当前页码-1)*每页显示数
int offset=(pindex-1)*psize;
List<Articleinfo> list=articleService.getListByPage(psize,offset);
return AjaxResult.success(list);
}
由于项目繁琐复杂,许多地方可能难以讲清,因为一个项目不仅要后端,还需要一定程度上的前端代码的实现,而这里不便进行相应前端代码的讲解,望理解
源码地址在这,JavaSenior/mycnblog-ssm · 夕夕夕夕夕立/java - 码云 - 开源中国 (gitee.com)