当前位置: 首页 > news >正文

项目——博客系统

文章目录

    • 项目优点
    • 项目创建
    • 创建相应的目录,文件,表,导入前端资源
    • 实现common工具类
      • 实现拦截器验证用户登录
      • 实现统一数据返回格式
      • 实现加盐加密类
        • 实现encrypt方法
        • 实现decrypt方法
      • 实现SessionUtil类
    • 实现注册页面
      • 实现前端代码
      • 实现后端代码
    • 实现登录页面
      • 实现前端代码
      • 实现后端代码
    • 实现个人主页
      • 实现退出登录功能
        • 实现前端代码
        • 实现后端代码
      • 初始化个人信息(包括个人文章列表的个人信息)和完成删除功能
        • 实现前端代码
        • 实现后端代码
    • 实现详情页(blog_content.html)
      • 实现前端代码
      • 实现后端代码
    • 实现博客修改功能(blog_update.html)
      • 实现前端代码
      • 实现后端代码
    • 实现博客编辑页
      • 实现前端代码
      • 实现后端代码
    • 实现我的草稿箱(draft_list.html)
      • 实现前端代码
      • 实现后端代码
    • 实现博客主页(blog_list.html)
      • 实现前端代码
      • 实现后端代码
    • 将session持久化到redis
    • 其他扩展功能

前言
在这里插入图片描述
这个博客系统前端分为8个页面,分别是注册页,登录页,编辑页,修改页,个人主页,博客正文页,草稿列表页,博客列表页

项目优点

  1. 框架:使用ssm(SpringBoot + SpringMVC + MyBatis)
  2. 密码:用户登录用的密码是使用加盐算法处理然后存储到数据库
  3. 用户登录状态持久化:将session持久化到redis
  4. 功能升级:在博客列表页实现一个分页功能
  5. 使用拦截器进行用户的登录验证,统一数据返回格式,统一异常处理

项目创建

创建一个SpringBoot项目,添加需要的依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

创建相应的目录,文件,表,导入前端资源

首先在数据库建表,这里直接提供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 datetime default now(),updatetime datetime default now(),`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 datetime default now(),updatetime datetime default now(),uid int not null,rcount int not null default 1,`state` int default 1
)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,state)values('喜讯','今天是正月初六',9,2);
  1. 实体层(model):创建实体类UserInfo,ArticleInfo
  2. 控制层(controller):创建控制器UserController和ArticleInfo
  3. 服务层(servlce):创建服务类:UserService和ArticleService
  4. 持久层(mapper):创建接口UserMapper和ArticleMapper,并创建对应的xml文件,在yml文件里配置好
  5. 工具层(common):统一返回类(ResponseAdvice,AjaxResult等)

注意:创建完相应的类就得将需要加的注解加上去,这里就不一一展示了
创建完后的目录结构
在这里插入图片描述

yml配置文件的内容

# 配置数据库的连接字符串
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:mapper-locations: classpath:mybatis/**Mapper.xmlconfiguration: # 配置打印 MyBatis 执行的 SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:level:com:example:demo: debug

将前端的东西导入static
在这里插入图片描述

实现common工具类

在这里插入图片描述
由于工具类后面我们基本都会用到,所以这里先实现一下

实现拦截器验证用户登录

  1. 步骤1创建一个Constant类,定义session的key
  2. 步骤2:创建一个普通类实现HandlerInterceptor接口,重写preHandle
  3. 步骤3:创建一个普通类实现 WebMvcConfigurer接口,重写addInterceptors
public class Constant {//登录信息存储到session中的key值public static final String SESSION_USERINFO_KEY = "session_userinfo_key";
}
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断登录业务HttpSession session = request.getSession(false);//会根据请求发送来的sessionId去服务器找对应的会话if(session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {//根据key拿到valuereturn true;}response.setStatus(401);return false;}
}
@Configuration
public class AppConfig implements WebMvcConfigurer {//不拦截的urlList<String> excludes = new ArrayList<>() {{add("/**/*.html");add("/js/**");add("/editor.md/**");add("/css/**");add("/img/**"); // 放行 static/img 下的所有文件}};@AutowiredLoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//配置拦截器InterceptorRegistration registration = registry.addInterceptor(loginInterceptor);registration.addPathPatterns("/**");registration.excludePathPatterns(excludes);}
}

实现统一数据返回格式

步骤1:创建一个普通的类,实现业务成功返回的方法和业务失败返回的方法
步骤2:创建一个普通的类实现ResponseAdvice接口,重写supports方法和beforeBodyWrite方法
代码

public class AjaxResult {/*** 业务执行成功返回的方法* @param data* @return*/public static HashMap<String,Object> success(Object data) {HashMap<String,Object> result = new HashMap<>();result.put("code",200);result.put("msg","");result.put("data",data);return result;}/*** 业务执行成功时返回的代码* @param data* @param msg* @return*/public static HashMap<String,Object> success(Object data,String msg) {HashMap<String,Object> result = new HashMap<>();result.put("code",200);result.put("msg",msg);result.put("data",data);return result;}/*** 业务执行失败返回的方法* @param code* @param data* @param msg* @return*/public static HashMap<String,Object> fail(int code,Object data,String msg) {HashMap<String,Object> result = new HashMap<>();result.put("code",code);result.put("msg",msg);result.put("data",data);return result;}/*** 业务执行失败返回的方法* @param code* @param msg* @return*/public static HashMap<String,Object> fail(int code,String msg) {HashMap<String,Object> result = new HashMap<>();result.put("code",code);result.put("msg",msg);result.put("data","");return result;}
}
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if(body instanceof HashMap) {return body;}//如果body是字符串类型,需要特殊处理if(body instanceof String) {ObjectMapper objectMapper = new ObjectMapper();return objectMapper.writeValueAsString(AjaxResult.success(body));}return AjaxResult.success(body);}
}

实现加盐加密类

创建一个普通类,实现两个方法,encrypt方法和decrypt方法。

  • encrypt方法根据用户输入的密码,进行加盐加密,返回最终加密的密码
  • decrypt方法根据用户输入的密码和数据库存的加密的密码进行验证
public class SecurityUtil {/*** 对password进行加盐加密* @param password* @return*/public static String encrypt(String password) {}/*** 验证password和数据库拿出来的finalPassword进行验证* @param password* @param finalPassword* @return*/public static String decrypt(String password,String finalPassword) {}
}

实现encrypt方法

加盐思路:用UUID类生成一个32长度的字符串作为盐值,然后将盐值+password进行md5加密生成一个32长度的字符串,然后盐值+这个有md5加密的字符串,就是最终的结果

 /*** 对password进行加盐加密* @param password* @return*/public static String encrypt(String password) {//每次生成内容不同,但是长度固定为32的字符串String salt = UUID.randomUUID().toString().replace("-","");//盐值+password进行md5加密String finalPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());//返回盐值+密码,总共64位,存到数据库中return salt+finalPassword;}

实现decrypt方法

这个方法用来解密验证密码
思路:这个方法有两个参数password:待验证的密码,finalPassword:最终正确的密码(在数据库查询的密码),从finalPassword中提取出盐值,然后盐值+password进行md5加密生成一个字符串,然后盐值+字符串和finalPassword判断是否相等

/*** 验证password和数据库拿出来的finalPassword进行验证* password:待验证的密码* finalPassword:最终正确的密码(在数据库查询的密码)* @param password* @param finalPassword* @return*/public static boolean decrypt(String password,String finalPassword) {//非空效验if(!StringUtils.hasLength(password) || !StringUtils.hasLength(finalPassword)) {return false;}if(finalPassword.length()!=64) {return false;}//提取出盐值String salt = finalPassword.substring(0,32);//使用盐值+密码生成一个32位的密码String securityPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());//使用上一个32位的密码拼接上盐值进行密码验证return (salt+securityPassword).equals(finalPassword);}

实现SessionUtil类

这个工具类用来查询当前用户登录的session信息

public class SessionUtil {public static UserInfo getLoginUser(HttpServletRequest request) {HttpSession session = request.getSession(false);if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {UserInfo userInfo = (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);return userInfo;}return null;}
}

实现注册页面

这里开始就涉及到前后端交互了,要约定好前后端交互的接口

实现前端代码

那先来编写前端代码(打开reg.html进行编写代码)
记得要引入jquery
在这里插入图片描述
mysub方法

function mysub() {//1.非空效验var username = jQuery("#username");var password = jQuery("#password");var password2 = jQuery("#password2");if(username.val()=="") {alert("用户名为空");username.focus();return false;}if(password.val()=="") {alert("密码为空");password.focus();return false;}if(password2.val()=="") {alert("确认密码为空");password2.focus();return false;}if(password.val()!=password2.val()) {alert("两次输入的密码不一致");return false;}//2.发送ajax请求jQuery.ajax({url:"/user/reg",type:"POST",data:{username:username.val(),password:password.val()},success:function(result) {if(result.code==200 && result.data==1) {alert("注册成功");if(confirm("是否去登录?")) {location.href="login.html";}} else if(result.code==-2 && result.msg!=null) {alert("注册失败,用户名已存在");} else {alert("注册失败请重试");}}});}

实现后端代码

由前端代码可知,url为/user/reg,我们需要在AppConfig里放行这个url,因为默认是所有url都要拦截,但是注册不能拦截,因为还没注册怎么能登录呢
在这里插入图片描述
后端代码基本思路就是,controller调用service,service调用mapper
所以要在UserController中注入UserService,在UserService中注入UserMapper
在这里插入图片描述
在这里插入图片描述
在UserController中实现reg方法

@RequestMapping("/reg")public Object reg(String username,String password) {//1.非空效验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return AjaxResult.fail(-1,"非法参数");}//根据用户名查询用户UserInfo userInfo = userService.getUserByName(username);if(userInfo!=null && userInfo.getId()>0) {//用户名已经存在return AjaxResult.fail(-2,"用户名已存在");}//2.进行添加操作int result = userService.add(username, SecurityUtil.encrypt(password));if(result==1) {return AjaxResult.success(1,"添加成功");} else {return AjaxResult.fail(-1,"添加失败");}}

可以看到,我们还需要在UserService中实现getUserByName和add方法

public UserInfo getUserByName(String username) {return userMapper.getUserByName(username);}public int add(String username,String password) {return userMapper.add(username,password);}

实现完之后,还需要在UserMapper接口中定义getUserByName方法和add方法,然后在对应的xml文件中实现sql语句
UserMapper

 public UserInfo getUserByName(@Param("username") String username);public int add(@Param("username") String username,@Param("password") String password);

对应的UserMapper.xml文件
在这里插入图片描述

 <select id="getUserByName" resultType="com.example.demo.model.UserInfo">select * from userinfo where username=#{username}</select><insert id="add">insert into userinfo(username,password)values(#{username},#{password})</insert>

到这里注册页面就实现完成了

实现登录页面

实现前端代码

打开login.html进行编写代码
还是一样,引入jquery,然后在submit设置onclick监听,然后实现mysub()方法
在这里插入图片描述

function mysub() {//1.非空效验var username = jQuery("#username");var password = jQuery("#password");if(username.val()=="") {alert("用户名为空");username.focus();return false;}if(password.val()=="") {alert("密码为空");password.focus();return false;}//2.发送ajax请求jQuery.ajax({url:"/user/login",type:"POST",data:{"username":username.val(),"password":password.val()},success:function(result) {if(result.code==200 && result.data==1) {location.href="myblog_list.html";} else {alert("用户名或密码错误");username.focus();}}});}

实现后端代码

先放行/user/login接口
然后在UserController中编写login方法

@RequestMapping("/login")public int login(HttpServletRequest request,String username,String password) {//1.非空效验if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {return 0;}//2.进行数据库查询操作UserInfo userInfo = userService.getUserByName(username);if(userInfo!=null && userInfo.getId()>0) {//用户名正确,验证密码if(SecurityUtil.decrypt(password,userInfo.getPassword())) {//将userInfo存到session中HttpSession session = request.getSession(true);session.setAttribute(Constant.SESSION_USERINFO_KEY,userInfo);return 1;}}return 0;}

实现个人主页

实现退出登录功能

当然由于退出登录功能在很多页面中都应该存在,所以后面可能就不详细说明

实现前端代码

退出登录功能后面很多页面都会用,所以我们新建一个Tool.js文件,将退出登录的前端方法写在里面
在这里插入图片描述
然后在Tool.js中编写代码

//退出登录
function onExit() {if(confirm("是否确认退出?")) {//发送ajax请求退出jQuery.ajax({url:"/user/logout",type:"POST",data:{},success: function(result) {location.href="login.html";},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}
}

编写myblog_list.html文件
并引入Tool.js和jquery
在这里插入图片描述
然后只需要修改一句代码即可
在这里插入图片描述
当你点击退出登录,它会自动调用Tool.js里的onExit()方法

实现后端代码

当用户点击退出登录,发送ajax请求到后端时,后端就将用户对应的session给删除即可。

在UserController中编写代码

@RequestMapping("/logout")public boolean logout(HttpServletRequest request) {HttpSession session = request.getSession(false);if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {session.removeAttribute(Constant.SESSION_USERINFO_KEY);}return true;}

初始化个人信息(包括个人文章列表的个人信息)和完成删除功能

当跳转到myblog_list.html时,就前端就调用initList()和myinfo()方法,这两个方法发送ajax请求去查询数据库,然后将个人信息和个人发表的文章返回给前端,前端再根据数据进行渲染

实现前端代码

<script>var descLen = 60;//简介最大长度//这个方法用来从正文中提取字符串function mySubStr(content) {if(content.length>desLen) {return content.substr(0,descLen);}return content;}//初始化个人列表信息function initList() {jQuery.ajax({url:"/art/mylist",type:"POST",data:{},//不用传uid,因为session中有userinfo,不能轻信前端传来的参数success:function(result) {if(result.code==200 && result.data!=null && result.data.length>0) {var html = "";for(var i=0;i<result.data.length;i++) {var item = result.data[i];//如果state==2,说明是草稿箱里的文章,不显示出来if(item.state==2) {continue;}html+='<div class="blog">';html+='<div class="title">'+item.title+'</div>';html+='<div class="date">'+item.createtime+'</div>'+'<div class="desc">'+mySubstr(item.content)+' </div>';html+='<div style="text-align: center;margin-top: 50px;">';html+='<a href="blog_content.html?id='+item.id+'">查看详情</a>&nbsp;&nbsp;';html+='<a href="blog_update.html?id='+item.id+'">修改</a>&nbsp;&nbsp;<a href="javascript:myDel('+item.id+')">删除</a></div>';html+='</div>';}jQuery("#artlistDiv").html(html);} else {//此人没有发表文章jQuery("#artlistDiv").html("<h1>暂无数据</h1>");}},error:function(err) {if(err!=null && err.statue==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}initList();//当浏览器渲染引擎执行到此行时就会调用此方法//获取个人信息function myinfo() {jQuery.ajax({url:"user/myinfo",type:"POST",data:{},success:function(result) {if(result.code==200 && result.data!=null) {jQuery("#username").text(result.data.username);}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}myinfo();//删除功能function myDel(aid) {jQuery.ajax({url:"art/del",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data==1) {alert("删除成功");location.href="myblog_list.html";}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录");location.href="login.html"} else {alert("删除失败");location.href="myblog_list.html"}}});}</script>

代码很长,但其实主要也就三个主要的方法,这3个方法都有ajax请求,所以需要在后端进行处理,然后返回结果

实现后端代码

先处理initList的ajax请求
对于initList发送来的ajax请求,我们要根据sesion里存的userInfo的id去查文章表拿到该用户的所有文章信息,然后进行返回
同样,先进行注入
在这里插入图片描述

 @RequestMapping("/mylist")public List<ArticleInfo> myList(HttpServletRequest request) {UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null) {return articleService.getMyList(userInfo.getId());}return null;}

接下来一样要在ArticleService中实现getMyList方法
在这里插入图片描述
在ArticleService中也要先注入

 public List<ArticleInfo> getMyList(Integer uid) {return articleMapper.getMyList(uid);}

接下来在ArticleMapper中定义getMyList方法,然后在对应的xml文件中编写sql语句

public List<ArticleInfo> getMyList(@Param("uid") Integer uid);
<select id="getMyList" resultType="com.example.demo.model.ArticleInfo">select * from articleinfo where uid=#{uid}</select>

接下来处理myinfo的ajax请求

 @RequestMapping("/myinfo")public UserInfo myInfo(HttpServletRequest request) {HttpSession session = request.getSession(false);if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {UserInfo userInfo = SessionUtil.getLoginUser(request);return userInfo;}return null;}

接下来处理myDel发送来的ajax请求

@RequestMapping("/del")public Integer artDel(HttpServletRequest request,Integer aid) {//非空效验if(aid!=null && aid>0) {//根据文章id查询文章详情ArticleInfo articleInfo = articleService.getDetail(aid);//进行归属人验证UserInfo userInfo = SessionUtil.getLoginUser(request);if(articleInfo!=null && userInfo!=null && userInfo.getId()!=articleInfo.getUid()) {//归属人正确进行删除int result = articleService.artDel(aid);return result;}}return null;}

接下来得在ArticleService中实现getDetail方法和artDel方法

public ArticleInfo getDetail(Integer aid) {return articleMapper.getDetail(aid);}public Integer artDel(Integer aid) {return articleMapper.artDel(aid);}

接下来在ArticleMapper中定义getDetail方法和artDel方法,然后在对应的xml文件中编写sql语句

public ArticleInfo getDetail(@Param("aid") Integer aid);public Integer artDel(@Param("aid") Integer aid);
 <select id="getDetail" resultType="com.example.demo.model.ArticleInfo">select * from articleinfo where id=#{aid}</select><delete id="artDel">delete from articleinfo where id=#{aid}</delete>

实现详情页(blog_content.html)

在这里插入图片描述
myblog_list.html的页面效果大概是这样的,其中删除功能已经实现了,那么现在来实现查看详情功能,当点击查看详情时,就会跳转blog_content.html页面,并将文章id传过去(如下图)
在这里插入图片描述

实现前端代码

那接下来打开blog_content.html来进行编写前端代码
这个页面同样也有退出登录功能,跟上面写法一样
在这里插入图片描述
在编写之前,我们需要在Tool.js中写一个方法getURLParam,这个方法用来从url上获取参数
这里直接给代码

// 获取当前 url 中某个参数的方法
function getURLParam(key){var params = location.search;if(params.indexOf("?")>=0){params = params.substring(1);var paramArr = params.split('&');for(var i=0;i<paramArr.length;i++){var namevalues = paramArr[i].split("=");if(namevalues[0]==key){return namevalues[1];}}}else{return "";}
}

然后开始编写blog_content.html

 //获取文章详细信息function getArtDetail() {//从url中获取文章id,也就是在myblog_list.html跳转到这里时url中的参数var aid = getURLParam("id");if(aid!=null && aid>0) {//发送ajax请求,查询数据库拿到文章详情jQuery.ajax({url:"art/detail",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data!=null) {var art = result.data;jQuery("#title").text(art.title);jQuery("#date").text(art.createtime);jQuery("#rcount").text(art.rcount);editormd = editormd.markdownToHTML("editorDiv",{markdown : art.content});//根据uid获取个人信息myInfo(art.uid);}}});}}getArtDetail();//根据uid获取个人信息function myInfo(uid) {jQuery.ajax({url:"user/myinfobyid",type:"POST",data:{"uid":uid},success:function(result) {if(result.code==200 && result.data!=null) {jQuery("#username").text(result.data.username);}},error:function(err) {if(err!=null && err.status==401) {alert("用户为登录,即将跳转登录页面");location.href="login.html"}}});}

实现后端代码

上面前端代码也是两个ajax请求
先来处理getArtDetail方法发送来的ajax请求
要在AppConfig中放行detail接口
在这里插入图片描述

 @RequestMapping("/detail")public Object getDetail(Integer aid) {if(aid!=null && aid>0) {ArticleInfo articleInfo = articleService.getDetail(aid);//访问量加1synchronized (locker) {int result = articleService.rountAdd(aid,articleInfo.getContent()+1);if(result!=1) {return AjaxResult.fail(-1,"访问量添加失败");}}//返回文章详情return AjaxResult.success(articleInfo);}return AjaxResult.fail(-1,"查询失败");}

接下来要在ArtclieService中实现rcountAdd方法

public int rcountAdd(Integer aid,Integer rcount) {return articleMapper.rcountAdd(aid,rcount);}

接下来在ArticleMapper中定义rcountAdd方法并在对应的xml文件中编写sql语句

 public int rcountAdd(@Param("aid") Integer aid,@Param("rcount") Integer rcount);
<update id="rcountAdd">update articleinfo set rcount=#{rcount} where id=#{aid}</update>

然后处理myinfo发送的ajax请求,要在AppConfig放行/user/myinfobyuid
在这里插入图片描述

@RequestMapping("/myinfobyuid")public UserInfo getMyInfoByUid(Integer uid) {if(uid!=null && uid>0) {return userService.getMyInfoByUid(uid);}return null;}
public UserInfo getMyInfoByUid(Integer uid) {return userMapper.getMyInfoByUid(uid);}
public UserInfo getMyInfoByUid(@Param("uid") Integer uid);
<select id="getMyInfoByUid" resultType="com.example.demo.model.UserInfo">select * from userinfo where id=#{uid}</select>

实现博客修改功能(blog_update.html)

在这里插入图片描述
当点击修改时,就会将跳转到blog_update.html并将id传过去

实现前端代码

同样这个页面有退出登录功能,实现方法跟上面一样
在这里插入图片描述

 function mysub(){// alert(editor.getValue()); // 获取值// editor.setValue("#123") // 设置值\var title = jQuery("#title");var content = editor.getValue();//非空效验if(title=="") {title.focus();alert("请先输入标题");return false;}if(content=="") {alert("请输入正文");return false;}jQuery.ajax({url:"/art/update",type:"POST",data:{"aid":aid,"title":title.val(),"content":content},success:function(result) {if(result.code==200 && result.data>0) {alert("修改成功");location.href="myblog_list.html";} else {alert("修改失败,请重试");}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录");location.href="login.html";}}});}//查询文章详情并展现function showArt() {//从url中获取文章idaid = getURLParam("id");if(aid!=null && aid>0) {//访问后端详情jQuery.ajax({url:"/art/detailbyid",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data!=null) {var art = result.data;jQuery("#title").val(art.title);initEdit(art.content);} else {alert("您没有权限修改");location.href="myblog_list.html";}},error:function(err) {if(err!=null && err.status==401) {alert("用户还未登录,即将跳转登录页面");location.href="login.html";}}});}}showArt();

实现后端代码

上面前端代码中涉及到两个ajax请求
先来处理showArt方法发送的ajax请求

 @RequestMapping("/detailbyid")public Object getDetailById(HttpServletRequest request,Integer aid) {if(aid!=null && aid>0) {//根据文章id查询文章详情ArticleInfo articleInfo = articleService.getDetail(aid);//文章归属人验证UserInfo userInfo = SessionUtil.getLoginUser(request);if(articleInfo!=null && userInfo!=null && articleInfo.getUid()==userInfo.getId()) {//文章归属人正确return AjaxResult.success(articleInfo);}}return AjaxResult.fail(-1,"查询失败");}

接下来处理mysub发送的ajax请求

@RequestMapping("/update")public int update(HttpServletRequest request,Integer aid,String title,String content) {//非空效验if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content) || aid == 0 || aid<=0) {return 0;}UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null && userInfo.getId()>0) {return articleService.update(aid,userInfo.getId(),title,content);}return 0;}

然后在ArticleService中实现update

 public int update(Integer aid,Integer uid,String title,String content) {return articleMapper.update(aid,uid,title,content);}

然后在ArticleMapper中定义update并在对应的xml文件中编写sql语句

public int update(@Param("aid") Integer aid,@Param("uid") Integer uid,@Param("title") String title,@Param("content") String content);
<update id="update">update articleinfo set title=#{title},content=#{content}where id=#{aid} and uid=#{uid}</update>

实现博客编辑页

实现前端代码

 function mysub(){// alert(editor.getValue()); // 获取值// editor.setValue("#123") // 设置值var title = jQuery("#title");var content = editor.getValue();//非空效验if(title=="") {title.focus();alert("请先输入标题");return false;}if(content=="") {alert("请先输入正文");return false;}jQuery.ajax({url:"/art/release",type:"POST",data:{"title":title.val(),"content":content},success:function(result) {if(result.code==200 && result.data>0) {alert("发布成功");location.href="myblog_list.html";} else {alert("发布失败,请重试");}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}//检查是否已经登录function checkIsLogged() {jQuery.ajax({url:"/user/islogged",type:"GET",success:function(result) {return true;},error:function(err) {alert("用户为登录,即将跳转登录页面");location.href="login.html";}});}checkIsLogged();//保存文章到草稿箱,将state设置为2function draft() {var title = jQuery("#title");var content = editor.getValue();//非空效验if(title=="") {title.focus();alert("请先输入标题");return false;}if(content=="") {alert("请先输入正文");return false;}jQuery.ajax({url:"/art/draft",type:"POST",data:{"title":title.val(),"content":content},success:function(result) {if(result.code==200 && result.data>0) {alert("保存成功");location.href="myblog_list.html";} else {alert("发布失败,请重试");}},error:function(err) {if(err!=null && err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}

实现后端代码

上面的前端代码总共涉及到3个ajax请求
先来处理 checkIsLogged方法的ajax请求,这个主要用来验证时候登录

 @RequestMapping("/islogged")public int isLogged(HttpServletRequest request, HttpServletResponse response) {UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null) {return 1;}response.setStatus(401);return 0;}

然后处理mysub()方法的ajax请求,这个方法用来发布文章,默认state1说明是发布的文章,state2是保存在草稿箱的文章

@RequestMapping("/release")public int releaseArt(HttpServletRequest request,String title,String content) {//非空效验if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {return 0;}UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null && userInfo.getId()>0) {int result = articleService.releaseArt(title,content,userInfo.getId());if(result==1) {return 1;}}return 0;}

还要在ArtcileService中实现releaseArt方法,然后在ArticleMapper中定义releaseArt方法,并在对应的xml文件中编写sql语句

 @RequestMapping("/release")public int releaseArt(HttpServletRequest request,String title,String content) {//非空效验if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {return 0;}UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null && userInfo.getId()>0) {int result = articleService.releaseArt(title,content,userInfo.getId());if(result==1) {return 1;}}return 0;}
 public int releaseArt(@Param("title")String title,@Param("content") String content,@Param("uid") Integer uid);
<insert id="releaseArt">insert into articleinfo (title,content,uid) values (#{title},#{content},#{uid})</insert>

最后处理保存文章也就是draft()的ajax请求,处理方法是将文章保存到数据库中,但是要将字段state设置为2,然后前端在渲染时,如果state==2,文章会显示在我的草稿箱中

 @RequestMapping("/draft")public int draftArt(HttpServletRequest request,String title,String content) {//非空效验if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {return 0;}int state = 2;//将state设置为2UserInfo userInfo = SessionUtil.getLoginUser(request);if(userInfo!=null && userInfo.getId()>0) {//将文章保存到数据库,并将state设置为2,表示还未发布int result = articleService.draftArt(title,content,userInfo.getId(),2);return result;}return 0;}

还是老步骤在,ArticleService中实现draftArt,然后在ArticleMapper定义draftArt方法,并在对应的xml文件中编写sql语句

public int draftArt(String title,String content,Integer uid,Integer state) {return articleMapper.draftArt(title,content,uid,state);}
 public int draftArt(@Param("title") String title,@Param("content") String content,@Param("uid") Integer uid,@Param("state") Integer state);
 <insert id="draftArt">insert into articleinfo (title,content,uid,state) values (#{title},#{content},#{uid},#{state})</insert>

实现我的草稿箱(draft_list.html)

在这里插入图片描述
当点击我的草稿箱时,就会跳转到draft_list.html页面

实现前端代码

draft_list.html的代码基本和myblog_list.html的代码一样,最主要的区别主要是state2还是state2

 var descLen = 60;//简介最大长度//字符串截取,将文章正文截取成简介function mySubstr(content) {if(content.length>descLen) {return content.substr(0,descLen);}return content;}//初始化个人列表信息function initList() {jQuery.ajax({url:"/art/mylist",type:"POST",data:{},//不用传uid,因为session中有userinfo,不能轻信前端的参数success:function(result) {if(result.code==200 && result.data!=null && result.data.length>0) {//todo:有文章var html="";var count = 0;for(var i=0;i<result.data.length;i++) {var item=result.data[i];//如果state==2说明是草稿箱里的文章,显示出来if(item.state==2) {html+='<div class="blog">';html+='<div class="title">'+item.title+'</div>';html+='<div class="date">'+item.createtime+'</div>'+'<div class="desc">'+mySubstr(item.content)+' </div>';html+='<div style="text-align: center;margin-top: 50px;">';html+='<a href="javascript:publish('+item.id+')">发布文章</a>&nbsp;&nbsp;';html+='<a href="blog_update.html?id='+item.id+'">修改</a>&nbsp;&nbsp;<a href="javascript:myDel('+item.id+')">删除</a></div>';html+='</div>';count++;}}jQuery("#artlistDiv").html(html); if(count==0) {//此人草稿箱没有文章jQuery("#artlistDiv").html("<h1>暂无数据</h1>");} } },error:function(err) {if(err!=null&&err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}initList();//当浏览器渲染引擎执行到此行时就会调用此方法//获取个人信息function myInfo() {jQuery.ajax({url:"/user/myinfo",type:"POST",data:{},success:function(result) {if(result.code==200 && result.data!=null) {jQuery("#username").text(result.data.username);}},error:function(err) {if(err!=null&&err.status==401) {alert("用户未登录,即将跳转登录页面");location.href="login.html";}}});}myInfo();//删除功能function myDel(aid) {jQuery.ajax({url:"/art/del",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data==1) {alert("删除成功");location.href="draft_list.html";}},error:function(err) {if(err!=null&&err.status==401) {alert("该用户没有删除权限!");} else {alert("删除失败!");}location.href="draft_list.html";}});}//将草稿箱里的文章发布:将state设置成1function publish(aid) {jQuery.ajax({url:"/art/publish",type:"POST",data:{"aid":aid},success:function(result) {if(result.code==200 && result.data==1) {alert("发布成功");location.href="myblog_list.html";}},error:function(err) {if(err!=null&&err.status==401) {alert("该用户没有发布权限!");} else {alert("发布失败!");}location.href="draft_list.html";}});}

实现后端代码

上面涉及到的3个ajax请求,前两个ajax请求在之前的后端代码已经处理了,所以这里只需要再处理publish方法里的ajax请求即可

 @RequestMapping("/publish")public int publishArt(Integer aid) {if(aid!=null) {//将state设置为1int result = articleService.publishArt(aid,1);if(result==1) {return 1;}}return 0;}

然后在ArticleService中实现draftArt方法,接着在ArticleMapper中定义draftArt方法并在对应的xml文件中编写sql语句

public int publishArt(Integer aid,Integer state) {return articleMapper.publishArt(aid,state);}
 public int publishArt(@Param("aid") Integer aid,@Param("state") Integer state);
<update id="publishArt">update articleinfo set state=#{state} where id=#{aid}</update>

实现博客主页(blog_list.html)

在这里插入图片描述
博客主页存放所有的博客,有5个按钮分别是,查看全文,首页,上一页,下一页和末页

首页,上一页,下一页和末页就属于分页功能了,接下来讲一下分页的思路
分页要素:

  1. 页码(pageIndex):要查询第几页的数据
  2. 每页显示多少条数据(pageSize):每页展示最大长度数据
    首先所有的数据都是从数据库中去查询的,查询的语句中有一个关键字limit,这样就可以限制发送多少条记录去前端,比如limit pageSize ,就查询pageSize条记录,然后发送去前端然后进行渲染页面
    数据库还有一个关键字就是offset(偏移量),比如说limit 2 offer 2,就是跳过前两条记录,然后查询第3,第4条记录。
    那么比如你想查询第pageIndex页的数据,那么它的偏移量就是pageSize*(pageIndex-1)
    所以分页公式(偏移量):pageSize*(pageIndex-1)
    分页语法:select * from articleinfo limit pageSize offset pageSize * (pageIndex-1)

实现前端代码

在这里插入图片描述

 var descLen = 200;//简介最大长度//字符串截取,将文章正文截取成简介function mySubstr(content) {if(content.length>descLen) {return content.substr(0,descLen);}return content;}var pindex = 1;//当前的页码var psize = 2;//每页显示的条数信息var totalpage = 1;//总页数//初始化分页参数,尝试从url中获取pindex和psizefunction initPageParam() {var pi = getURLParam("pindex");if(pi!="") {pindex=pi;}var ps = getURLParam("psize");if(ps!="") {psize=ps;}}initPageParam();//查询分页数据function getList() {jQuery.ajax({url:"/art/list",type:"GET",data:{"pindex":pindex,"psize":psize},success:function(result) {if(result.code==200 && result.data!=null && result.data.length>0) {//循环拼接控制documentvar finalHtml = "";for(var i=0;i<result.data.length;i++) {var item = result.data[i];//如果state==2说明是草稿箱的文章,不显示出来if(item.state==2) {continue;}finalHtml+='<div class="blog">';finalHtml+='<div class="title">'+item.title+'</div>';finalHtml+='<div class="date">'+item.createtime+'</div>';finalHtml+='<div class="desc">'+mySubstr(item.content)+'</div>';finalHtml+='<a href="blog_content.html?id='+item.id+'" class="detail">查看全文</a>';finalHtml+='</div>';}jQuery("#listDiv").html(finalHtml);}}});}getList();//查询总共多少页function getTotalPage() {jQuery.ajax({url:"/art/totalpage",type:"GET",data:{"psize":psize},success:function(result) {if(result.code==200 && result.data!=null) {totalpage=result.data;}}});}getTotalPage();//首页function firstClick() {location.href="blog_list.html";}//上一页function beforeClick() {if(pindex<=1) {alert("前面已经没有内容了!");return false;}pindex-=1;location.href="blog_list.html?pindex="+pindex+"&psize="+psize;}//下一页function nextClick() {pindex = parseInt(pindex);if(pindex>=totalpage) {//已经是最后一页alert("后面已经没有内容了哦!");return false;}pindex+=1;location.href="blog_list.html?pindex="+pindex+"&psize="+psize;}//末页function lastClick() {pindex=totalpage;location.href="blog_list.html?pindex="+pindex+"&psize="+psize;}

实现后端代码

上面涉及到两个ajax请求
两个请求的url都需要放行
在这里插入图片描述

先来处理getTotalPage方法的ajax请求

@RequestMapping("/totalpage")public Integer getTotalCount(Integer psize) {if(psize!=null) {//参数有效int totalCount = articleService.getTotalCount();int totalPage = (int)Math.ceil(totalCount*1.0/psize);return totalPage;}return null;}

接下来在ArticleService中实现getTotalCount方法,然后在ArticleMapper中定义getTotalCount方法并在对应的xml文件中编写sql语句

public int getTotalCount() {return articleMapper.getTotalCount();}
public int getTotalCount();
<select id="getTotalCount" resultType="java.lang.Integer">select count(*) from articleinfo where state=1</select>

接下来处理getList的ajax请求

public List<ArticleInfo> getList(Integer psize,Integer offset) {return articleMapper.getList(psize,offset);}

然后在ArticleService中实现getList,还要在ArticleMapper中定义getList并在对应的xml文件中编写sql语句

 public List<ArticleInfo> getList(@Param("psize") Integer psize,@Param("offset") Integer offset);
 <select id="getList" resultType="com.example.demo.model.ArticleInfo">select * from articleinfo limit #{psize} offset #{offset}</select>

将session持久化到redis

首先你需要在你的linux环境上安装redis,这里就不介绍怎么安装了
然后在你的项目上的application.properties上配置连接redis即可,springboot已经内置了将session持久化到redis,所以你只需要在application.properties上配置即可

spring.redis.host=#你redis安装在哪台机器的ip地址
spring.redis.password=#redis的密码
spring.redis.port=6379#redis默认端口号
spring.redis.database=0#redis操作的数据库
spring.session.store-type=redis
server.servlet.session.timeout=1800#session存到redis的过期时间
spring.session.redis.flush-mode=on_save#会将session保存到redis本地
spring.session.redis.namespace=spring:session

其他扩展功能

  • 定时发布功能
  • 更换头像
  • 冻结账号
    等等,大家可以尝试自己实现,下面这个源代码链接,后续有新功能也会将源代码更新

源码链接:https://gitee.com/maze-white/project/tree/master/blog_system_ssm

http://www.lryc.cn/news/9377.html

相关文章:

  • PHP(14)会话技术
  • 对JAVA 中“指针“理解
  • 功率放大器在MEMS微结构模态测试研究中的应用
  • 【算法基础】字典树(Trie树)
  • MyBatis 插件 + 注解轻松实现数据脱敏
  • MySQL优化篇-MySQL压力测试
  • CF43A Football 题解
  • Nginx常用命令及具体应用(Linux系统)
  • 从零实现Web服务器(三):日志优化,压力测试,实战接收HTTP请求,实战响应HTTP请求
  • MFC入门
  • 1、H5+CSS面试题
  • 亚马逊云科技重磅发布《亚马逊云科技汽车行业解决方案》
  • Springboot扩展点之FactoryBean
  • 新库上线 | CnOpenDataA股上市公司交易所监管措施数据
  • 同步辐射XAFS表征方法的应用场景分析
  • 06 antdesign react Anchor 不同页面之间实现锚点
  • mysql调优-内存缓冲池
  • 【LeetCode】每日一题(5)
  • 输入任意多个整数, 把这些数据保存到文件data.txt中.(按ctrl + z)
  • Mysql数据库的时间(3)一如何用函数插入时间
  • 关于eval函数(将JSON格式的字符串转换成JSON格式对象)
  • 2023最强软件测试面试题,精选100 道,内附答案版,冲刺金3银4
  • 一文搞懂Docker容器里进程的 pid 是如何申请出来的?
  • 若依框架如何新增自定义主题风格
  • C语言格式化输入和输出; Format格式化
  • Revit教程:怎么关掉工具栏的实时提示?
  • javascript 简介
  • 医学图象分割常用损失函数(附Pytorch和Keras代码)
  • 【新2023】华为OD机试 - 病菌感染(Python)
  • QGIS中进行批量坡向计算