在学习Servlet的时候,也写了一个博客系统,主要的就是使用servelet加Tomcat进行实现的,而这个项目 仅仅适合去学习Web项目开发的思想,并不满足当下企业使用框架的思想,进行学习过Spring,Spring Boot,Spring MVC以及MyBatis之后,我们就可以对之前的项目使用SSM框架的形式进行升级,然后对相应的功能进行升级,帮助我们对企业的开发流程进行了解,文章最后会有本项目的Gitee地址,欢迎访问!!
下图对此项目对照之前的版本进行了项目升级的总结.
那话不多说啦,开始我们的新项目.
目录
前言
编辑
1. 前期项目准备
1.1 创建项目
1.2 准备项目
a. 删除项目中无用的文件和目录
b. 引入前端页面
c. 初始化数据库
d. 项目分层
2. 添加统一的返回格式
3. 配置登录拦截器
4. 实现用户的注册功能
4.1 编写前端Ajax请求代码
4.2 编写后端代码
a. 用户信息类UserInfo.class
b. Mapper层(UserMapper)
c. MyBatis的xml文件的构造
d. 服务层Service
e. Controller层
4.3 测试功能
5. 实现用户的登录功能
5.1 编写前端Ajax请求代码
5.2 编写后端代码
a. Mapper层(UserMapper)
b. MyBatis的xml文件的构造
c. service层
d. Controller层
5.3 测试功能
6. 个人主页展示功能
6.1 左侧个人信息
6.1.1 编写前端Ajax请求代码
6.1.2 编写后端代码
a. Mapper层(UserMapper)
b. MyBatis的xml文件的构造
c. service层
d. Controller层
6.1.3 测试功能
6.2 右侧个人博客列表信息
6.2.1 编写前端Ajax请求代码
6.2.2 编写后端代码
a. Mapper层(ArticleMapper)
b. MyBatis的xml文件的构造
c. service层
d. Controller层
6.3 删除博客功能
6.3.1 编写前端Ajax请求代码
6.3.2 编写后端代码
a. Mapper 层(ArticleMapper)
b. MyBatis的xml文件的构造
c. service层
d. Controller层
6.3.3 测试功能
6.4 查看全文功能
6.4.1 编写前端Ajax请求代码
6.4.2 编写后端代码
a. Mapper层(ArticleMapper)
b. MyBatis的xml文件的构造
c. service层
d. Controller层
6.4.3 测试功能
6.5 修改博客功能
6.5.1 编写前端Ajax请求代码
6.5.2 编写后端代码
a. Mapper层(ArticleMapper)
b. MyBatis的xml文件的构造
c. service层
d. Controller层
6.5.3 测试功能
6.6 发布博客功能
6.6.1 编写前端Ajax请求代码
6.6.2 编写后端代码
a. Mapper层(ArticleMapper)
b. MyBatis的xml文件的构造
c. service层
d. Controller层
6.6.3 功能测试
6.7 注销用户功能
6.7.1 编写前端Ajax请求代码
6.7.2 编写后端代码
a. Mapper层(ArticleMapper)
b. MyBatis的xml文件的构造
c. service层
d. Controller层
6.7.3 功能测试
7. 博客主页功能实现
7.1 主页的导航栏
7.1.1 编写前端Ajax请求代码
7.1.2 编写后端代码
7.2 分页功能
8. 密码使用加盐的算法进行加密
9. Session持久化到Redis
结语
第一步 : 创建SSM项目
第二步: 添加项目依赖
(这里就不打算总结前端页面设计的具体代码了,我们把重点放在后端是实现以及前端与后端交互.)(具体的前端代码,我会将此代码放在项目路径下面,然后大家根据下面的步骤进行下载就可以了).
前端代码在项目中路径,大家将有关于前端的代码放在resources/static下面就可以了
我使用的MySQL版本是5.7版本,下面是建库建表的sql
-- 创建数据库
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(32) not null,
photo varchar(500) default '',
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
`state` int default 1
) default charset 'utf8mb4';
alter table userinfo modify password varchar(65);
-- 创建⽂章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime datetime default CURRENT_TIMESTAMP,
updatetime datetime 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';
alter table userinfo add unique(username);
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,createtime,updatetime,uid) values('Java','Java正文','2021-12-06 17:10:48', '2021-12-06 17:10:48',1);
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
insert into articleinfo(title,content,uid) values('Java','Java正文',1);
我们按照统一的格式,将项目进行分层操作
e. 项目的配置文件application.yml
# 配置数据库的连接字符串
spring:
# jackson:
# date-format: yyyy-MM-dd HH:mm:ss # 设置时间的格式
# time-zone: GMT+8 # 设置地域
datasource:
url: jdbc:mysql://127.0.0.1/mycnblog?characterEncoding=utf8
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 8800
# 设置 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
至此,我们项目的前期准备就完成了,接下来我们将一一进行实现功能.
为了与前端更方便更有效率的进行交互,我们可以规定好统一的返回格式给前端,使用了SpringAOP的思想,在项目的common文件夹中创建一个类为AjaxResult.class.我们返回给前端的数据格式为Json格式的数据,主要包括了状态码,状态码描述信息,以及返回的数据.具体代码如下.
具体分为两个方法,一个是访问成功,一个是失败.然后对这两个方法进行重载.
package com.example.demo.common;
import lombok.Data;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* Created with IntelliJ IDEA.
* Description:普通的对象,统一的返回格式
* User: YAO
* Date: 2023-07-17
* Time: 19:17
*/
@Data
public class AjaxResult implements Serializable {
// 1.状态码
private Integer code;
// 2.状态码描述信息
private String msg;
// 3.返回的数据
private Object data;
/**
* 1. 操作成功返回的结果
*/
public static AjaxResult success(Object data){
AjaxResult result = new AjaxResult();
result.setCode(200);
result.setMsg("");
result.setData(data);
return result;
}
public static AjaxResult success(int code,Object data){
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg("");
result.setData(data);
return result;
}
public static AjaxResult success(String msg,int code,Object data){
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
/**
* 2. 返回失败结果
*/
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(String msg,int code,Object data){
AjaxResult result = new AjaxResult();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
}
为了保证最后的返回格式的正确性,我们最后对数据的返回的格式进行验证.在config文件目录下创建返回格式的保底类:ResponseAdvice,对特殊的String类型进行处理.
package com.example.demo.config;
import com.example.demo.common.AjaxResult;
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;
/**
* Created with IntelliJ IDEA.
* Description:统一返回格式,最保底的返回数据的格式
* 说明:在返回数据之前,检测数据的类型是否为统一的对象,如果不是,封装成统一的数据格式
* User: YAO
* Date: 2023-07-17
* Time: 19:28
*/
@ControllerAdvice // 统一返回格式
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
/**
* 只有返回为true的时候,才会调用supports方法
* @param returnType
* @param converterType
* @return
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
/**
* 对数据格式进行校验和封装
* @param body
* @param returnType
* @param selectedContentType
* @param selectedConverterType
* @param request
* @param response
* @return
*/
@SneakyThrows // 抛出异常
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof AjaxResult) {
return body;
}
if (body instanceof String) {
// String 比较特殊需要单独处理
// 使用Jackson封装成成功返回的对象的格式
return objectMapper.writeValueAsString(AjaxResult.success(body));
}
return AjaxResult.success(body);
}
}
我们对有些系统功能进行捕获用户的登录状态,实现必须登录之后才能进行使用功能.在config的文件目录下创建LoginInterceptor.class.实现HandlerInterceptor接口,重写preHandle方法.获取当前登录的用户的SESSION,判断是否为空,为空就重定向到登录页面,然后将自定义好的登录拦截器进行配置到项目中(添加注解@Configuration,实现接口WebMvcConfigurer,重写addInterceptors方法)具体代码如下:
1.创建全局变量的 USER_SESSION_KEY(SESSION的key值)
package com.example.demo.common;
/**
* Created with IntelliJ IDEA.
* Description:全局变量
* User: YAO
* Date: 2023-07-18
* Time: 10:58
*/
public class AppVariable {
// 全局变量,session的key值
public static final String USER_SESSION_KEY = "USER_SESSION_KEY";
// 所有用户的key值是一样的但是内容是不一样的.就相当于大家都用的是钥匙但是钥匙的类型是不一样的.
// 这里只是一个统一的格式,或者是同一种规格
}
2. 自定义拦截器
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;
/**
* Created with IntelliJ IDEA.
* Description:自定义登录功能呢的拦截器
* User: YAO
* Date: 2023-07-18
* Time: 11:02
*/
public class LoginInterceptor implements HandlerInterceptor {
/**
* true --> 用户已经登录
* false --> 用户未登录,跳转到登录页
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@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;
}
}
3. 将自定义拦截器加入到项目的配置中.(具体的拦截界面,大家先不要全部添加,可以随着项目的功能的实现,对指定的页面和路由进行拦截.)
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;
/**
* Created with IntelliJ IDEA.
* Description:项目的配置文件
* User: YAO
* Date: 2023-07-18
* Time: 11:07
*/
@Configuration
public class AppConfig implements WebMvcConfigurer {
/**
* 1. 配置自定义的拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/css/**")
.excludePathPatterns("/img/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/editor.md/**")
.excludePathPatterns("/login.html")
.excludePathPatterns("/blog_content.html")
.excludePathPatterns("/blog_list.html")
.excludePathPatterns("/reg.html")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/reg")
.excludePathPatterns("/art/detail")
.excludePathPatterns("/user/getuserbyid")
.excludePathPatterns("/art/incr-rcount")
.excludePathPatterns("/art/add")
.excludePathPatterns("/art/listbypage")
.excludePathPatterns("/user/loginstate");
}
}
此处我们就用到了Servlet的和前端交互的思想了.以下所有的功能实现就是下面这两步:
reg.html文件中进行补充代码
我们对这个提交的按钮进行构造请求
因为要对数据库中的用户表进行操作,所以后段要对用户表进行创建对象.
package com.example.demo.entity;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* Created with IntelliJ IDEA.
* Description:用户表对象
* User: YAO
* Date: 2023-07-18
* Time: 9:24
*/
@Data
public class UserInfo implements Serializable {
private Integer id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private Integer state;
}
@Mapper
public interface UserMapper {
/**
* 1.注册功能
*/
int reg(UserInfo userInfo);
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 1. 注册功能
*/
public int reg(UserInfo userInfo){
return userMapper.reg(userInfo);
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private ArticleService articleService;
@RequestMapping("/reg")
public AjaxResult reg(UserInfo userInfo){
// 1. 进行非空校验和参数的有效性
if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername())||
!StringUtils.hasLength(userInfo.getPassword())){
return AjaxResult.fail(-1,"非法参数");
}
// 2.密码加密
String olderPassword = userInfo.getPassword();
// 加密
userInfo.setPassword(PasswordUtils.encrypt(olderPassword));
// 2.返回成功的修改值
return AjaxResult.success(userService.reg(userInfo));
}
}
后端代码完成
注册小白用户
数据库用户展示(密码是经过加盐加密的,后面会讲到这一点)
我们的注册功能就结束了.
用户登录功能与注册功能逻辑差不多,都是先进行前端 提交ajax数据请求给后端,在进行效验,其中后端进行数据库查询判断用户名或者密码是否存在且正确。
/**
* 2.登录功能
*/
UserInfo getUserByName(@Param("username") String username);
/**
* 2. 根据名字查询用户信息
*/
public UserInfo getUserByName(String username){
return userMapper.getUserByName(username);
}
(其中代码加入了密码加盐加密的实现,这个具体后面再将)
@RequestMapping("/login")
public AjaxResult login(HttpServletRequest request,String username, String password){
System.out.println(username + " " + 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())){
// 登陆成功
// 将用户存储带到Session中
HttpSession session = request.getSession();
session.setAttribute(AppVariable.USER_SESSION_KEY,userInfo);
// 返回之前将密码等信息进行隐藏
userInfo.setPassword("");
return AjaxResult.success(userInfo);
}
}
return AjaxResult.success(0,null);
}
myblog_list.html文件进行补充
这里仅仅对名字和文章数量进行动态展示,其他的标签就没实现,.但是实现的原理都是一样的,后续会做扩展.
因为要进行获取当前登录用户的信息,所以我们得实现一个得到当前用户的方法
我们在common文件目录下创建一个类UserSessionUtils实现用户登录的相关操作
实现方法getSessionUser()
package com.example.demo.common;
import com.example.demo.entity.UserInfo;
import com.example.demo.entity.vo.UserInfoVo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* Created with IntelliJ IDEA.
* Description:当前登录用户相关的操作
* User: YAO
* Date: 2023-07-18
* Time: 14:28
*/
public class UserSessionUtils {
/**
* 1. 得到当前的登录用户
* @param request
* @return
*/
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;
}
}
/**
* 3.根据id返回用户信息
*/
UserInfo getUserById(@Param("id") Integer id);
/**
* 3.根据id返回用户信息
*/
public UserInfo getUserById(Integer id){
return userMapper.getUserById(id);
}
@RequestMapping("/showinfo")
public AjaxResult showinfo(HttpServletRequest request){
UserInfoVo userInfoVo = new UserInfoVo();
// 1. 得到当前登录用户
UserInfo userInfo = UserSessionUtils.getSessionUser(request);
if (userInfo == null){
return AjaxResult.fail(-1,"非法请求");
}
// 将获得对象赋值给UserinfoVo
BeanUtils.copyProperties(userInfo,userInfoVo);// Spring 提供的深拷贝的方法
// 2. 得到用户发表文章的总数
userInfoVo.setArtCount(articleService.getArtCountByUid(userInfo.getId()));
return AjaxResult.success(userInfoVo);
}
myblog_list.html文件进行补充
文章信息表实体对象
import java.time.LocalDateTime;
import java.util.Date;
/**
* Created with IntelliJ IDEA.
* Description:文章表
* User: YAO
* Date: 2023-07-18
* Time: 13:52
*/
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
@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;
}
/**
* 2.根据uid查询对应用户的文章列表
*/
List getMyList(@Param("uid")Integer uid);
public List getMyList(Integer uid){
return articleMapper.getMyList(uid);
}
@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 list = articleService.getMyList(userInfo.getId());
for (ArticleInfo articleInfo : list) {
if (articleInfo.getContent().length() > 100){
// 当文章的长度大于100字的时候,不完全进行展示
articleInfo.setContent(articleInfo.getContent().substring(0,100) + "...");
}
}
return AjaxResult.success(list);
}
}
myblog_list.html文件进行补充
// 删除文章
function myDel(id){
if(confirm("确实删除?")){
// 删除文章
jQuery.ajax({
url:"art/del",
type:"get",
data:{"id":id},
success:function(result){
if(result!=null && result.code==200 && result.data==1){
alert("删除成功!");
// 刷新当前页面
location.href = location.href;
}else{
alert("抱歉:删除失败,请重试!");
}
}
});
}
}
/**
* 3.根据id进行删除文章
*/
int del(@Param("id") Integer id,@Param("uid") Integer uid);
delete from articleinfo where id=#{id} and uid=#{uid}
public Integer del(Integer id,Integer uid){
return articleMapper.del(id,uid);
}
@RequestMapping("/del")
public AjaxResult del(HttpServletRequest request,Integer id){
if (id == null || id <= 0){
return AjaxResult.fail(-1,"参数异常");
}
UserInfo userInfo = UserSessionUtils.getSessionUser(request);
if (userInfo == null){
return AjaxResult.fail(-2,"用户未登录");
}
return AjaxResult.success(articleService.del(id,userInfo.getId()));
}
blog-content.html文件进行补充
查看全文的功能,要对文章的内容进行展示,以及文章的作者,发布信息,以及文章阅读量进行展示.需要根据url中的id进行获取指定博客的id,我们需要对id的提取进行设置方法,因为后面的修改文章也需要获取文章id,以及主页的列表也需要,所以将此方法写入到公共的js方法中,此处需要注意的操作就是,每次查看的时候就要给文章的阅读量进行加1操作.
common.js
function getUrlValue(key){
// 一开始获取的字符串为?id=2&v=1
var params = location.search;
if(params.length>1){
//?id=2&v=1 去除这个问号
params = location.search.substring(1); // 从第二个字符开始取出
var paramArr = params.split("&"); // 差分成key-value的数组
for(var i=0;i
/**
* 3.根据id获取文章的具体内容
*/
ArticleInfo getDetail(@Param("id")Integer id);
/**
* 4.文章阅读量进行加1
*/
int incrRCount(@Param("id") Integer id);
update articleinfo set rcount=rcount+1 where id=#{id};
public int getArtCountByUid(Integer uid){
return articleMapper.getArtCountByUid(uid);
}
public ArticleInfo getDetail(Integer id){
return articleMapper.getDetail(id);
}
@RequestMapping("/incr-rcount")
public AjaxResult incrRCount(Integer id){
if (id == null || id <= 0){
return AjaxResult.fail(-1,"参数异常");
}
return AjaxResult.success(articleService.incrRCount(id));
}
@RequestMapping("/detail")
public AjaxResult getDetail(HttpServletRequest request,Integer id){
if (id == null || id <= 0){
return AjaxResult.fail(-1,"参数异常");
}
return AjaxResult.success(articleService.getDetail(id));
}
blog_edit.html文件进行补充
修改功能就是点击跳转到编辑页面,首先发送请求得到当前文章的内容,再进行修改提交发送Ajax请求
function mysub(){
// 1.非空效验
var title = jQuery("#title");
if(title.val()==""){
alert("请先输入标题!");
title.focus();
return;
}
if(editor.getValue()==""){
alert("请先输入正文!");
return;
}
// 2.进行修改操作
if(confirm("确定修改文章?")){
jQuery.ajax({
url:"/art/update",
type:"POST",
data:{"id":id,"title":title.val(),"content":editor.getValue()},
success:function(result){
if(result!=null && result.code==200 && result.data==1){
alert("修改成功!");
location.href = "myblog_list.html";
}else{
alert("抱歉:操作失败,请重试!");
}
}
});
}
}
// 文章初始化
function initArt(){
// 得到当前页面 url 中的参数 id(文章id)
id = getUrlValue("id");
if(id==""){
alert("无效参数");
location.href = "myblog_list.html";
return;
}
// 请求后端,查询文章的详情信息
jQuery.ajax({
url:"art/detail",
type:"POST",
data:{"id":id},
success:function(result){
if(result!=null && result.code==200){
jQuery("#title").val(result.data.title);
initEdit(result.data.content);
}else{
alert("查询失败,请重试!");
}
}
});
}
initArt();
/**
* 3.根据id获取文章的具体内容
*/
ArticleInfo getDetail(@Param("id")Integer id);
/**
* 6. 修改文章
* @param articleInfo
* @return
*/
int update(ArticleInfo articleInfo);
update articleinfo set title=#{title},content=#{content},updatetime=#{updatetime}
where id=#{id} and uid=#{uid}
public ArticleInfo getDetail(Integer id){
return articleMapper.getDetail(id);
}
public int update(ArticleInfo articleInfo){
return articleMapper.update(articleInfo);
}
@RequestMapping("/detail")
public AjaxResult getDetail(HttpServletRequest request,Integer id){
if (id == null || id <= 0){
return AjaxResult.fail(-1,"参数异常");
}
return AjaxResult.success(articleService.getDetail(id));
}
@RequestMapping("/update")
public AjaxResult update(HttpServletRequest request, ArticleInfo articleInfo){
if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle())
|| !StringUtils.hasLength(articleInfo.getContent()) || articleInfo.getId()==null){
return AjaxResult.fail(-1,"非法参数");
}
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));
}
blog_add.html文件进行补充
/**
* 5. 添加文章
* @param articleInfo
* @return
*/
int add(ArticleInfo articleInfo);
insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})
public int add(ArticleInfo articleInfo){
return articleMapper.add(articleInfo);
}
@RequestMapping("/add")
public AjaxResult add(HttpServletRequest request, ArticleInfo articleInfo){
// 1.进行非空校验
if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle())
|| !StringUtils.hasLength(articleInfo.getContent())){
return AjaxResult.fail(-1,"参数异常");
}
// 2.获取当前登录用户的id,将当前登录用户的id进行设置到传入sql的文章信息的对象,尽心添加操作的时候就会准确的直到这是当前登录用户记性发布的
UserInfo userInfo = UserSessionUtils.getSessionUser(request);
if (userInfo == null || userInfo.getId() <= 0){
return AjaxResult.fail(-2,"无效的登录用户");
}
articleInfo.setUid(userInfo.getId());
int result = articleService.add(articleInfo);
return AjaxResult.success(result);
}
我们要给检测到登录状态的页面加注销功能, 如个人主页,写博客,主页.因为是公共操作,所以直接写成一个公共的js,然后在需要的页面直接引入就可以.
common.js
// 公共操作 -- 注销登录
function logout(){
if(confirm("确定注销??")){
// 注销登录
jQuery.ajax({
url:"user/logout",
type:"get",
data:{},
success:function(result){
if(result!=null && result.code==200){
location.href = "/login.html";
}
}
});
}
}
/**
* 5. 添加文章
* @param articleInfo
* @return
*/
int add(ArticleInfo articleInfo);
insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})
public int add(ArticleInfo articleInfo){
return articleMapper.add(articleInfo);
}
@RequestMapping("/add")
public AjaxResult add(HttpServletRequest request, ArticleInfo articleInfo){
// 1.进行非空校验
if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle())
|| !StringUtils.hasLength(articleInfo.getContent())){
return AjaxResult.fail(-1,"参数异常");
}
// 2.获取当前登录用户的id,将当前登录用户的id进行设置到传入sql的文章信息的对象,尽心添加操作的时候就会准确的直到这是当前登录用户记性发布的
UserInfo userInfo = UserSessionUtils.getSessionUser(request);
if (userInfo == null || userInfo.getId() <= 0){
return AjaxResult.fail(-2,"无效的登录用户");
}
articleInfo.setUid(userInfo.getId());
int result = articleService.add(articleInfo);
return AjaxResult.success(result);
}
这里需要注意的是,已经登录的用户的导航栏和未登录的有细微的区别
已经登录的主页是可以进行切换到个人主页的,未登录的是没有这个选项的.
在进入主页上的时候发送请求的时候进行验证登录的状态,检测为登录,就给导航栏拼接个人主页的跳转按钮就可以了
在login.html中进行编写代码
因为不用操作数据库,所以只编写Controller层就可以了
@RequestMapping("loginstate")
public AjaxResult loginstate(HttpServletRequest request){
UserInfo userInfo = UserSessionUtils.getSessionUser(request);
if (userInfo == null || userInfo.getId()<=0){
return AjaxResult.fail(-1,"当前用户未登录");
}
return AjaxResult.success(userInfo);
}
实现分页功能,就要先获取总的文章数量,以及定义好每页显示多少条内容.具体总结为下面.
为了更形象的展示,我使用了别的画图软件
主要总结了三种加密方式\
本项目采用自己写的加盐算法:
package com.example.demo.common;
import org.springframework.beans.BeanUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.UUID;
/**
* Created with IntelliJ IDEA.
* Description:关于密码的工具类
* User: YAO
* Date: 2023-07-19
* Time: 13:49
*/
public class PasswordUtils {
/**
* 1.加盐并生成密码
* @param password 明文密码
* @return 保存到数据库的密码
*/
public static String encrypt(String password){
// 1. 32位盐值
String salt = UUID.randomUUID().toString().replace("-","");
// 2. 生成加盐之后的密码
String saltPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
// 3. 生成最终保存在数据库的密码 约定:32位盐值+$+32位加盐之后MD5加密之后的密码 -->65位
String finalPassword = salt + "$" + saltPassword;
return finalPassword;
}
/**
* 2.生成用于验证的加盐密码
* @param password 明文密码
* @param salt 盐值
* @return 验证的密码
*/
public static String encrypt(String password,String salt){
// 1. 生成一个加盐之后的密码
String saltPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
// 2.生成最终的密码 约定:32位盐值+$+32位加盐之后MD5加密之后的密码 -->65位
String finalPassword = salt + "$" + saltPassword;
return finalPassword;
}
/**
* 3.验证密码
* @param inputPassword 用户输入的明文密码
* @param finalPassword 数据库存储的最终的密码
* @return
*/
public static Boolean check(String inputPassword, String finalPassword){
if (StringUtils.hasLength(inputPassword) && StringUtils.hasLength(finalPassword)
&& finalPassword.length() == 65){
// 1.得到盐值
String salt = finalPassword.split("\\$")[0];
// 2.将用户输入的铭文密码进行加盐加密
String finalInputPassword = encrypt(inputPassword,salt);
// 3.进行比较
return finalInputPassword.equals(finalPassword);
}
return false;
}
}
在注册的Controller以及登录的Controller可以看出相应的使用.
Redis的具体使用我会另外进行总结一个系列,这里直接将SSM项目Session持久化到Redis进行总结.
在我们的项目只需要配置好依赖文件配置文件,指定Session存储的类型为redis,指定好Session存储的路径以及仓库.
我们使用redis的可视化工具可以查看我们存储的Session.
写到这里,这个基础的SSM项目就算实现的差不多了,后续还可以扩展很多的功能,比如评论,草稿,登录验证码登功能,后续时间不紧的话我也会进行响应的更新.好啦,就到这里咯,谢谢!!!可以的话,给个赞,点个关注吧!!!
代码链接
SSM博客系统(前端+后端)https://gitee.com/yao-fa/advanced-java-ee/tree/master/MyCnBlog-SSM