一、项目介绍
个人博客系统
- 相关技术: SpringBoot+SpringMvc+Mybatis+Mysql+Redis
- 项目简介:本项目为一个功能完善的个人博客系统,支持文章的编辑、修改、删除和发布,以及作者个人信息的展示等功能。
- 项目描述:
- 采用前后端分离架构,基于SpringBoot和Mybatis等框架构建。
- 设计统一的前后端数据返回格式,提高系统可读性和可维护性。
- 使用拦截器实现用户登录校验,并采用MD5算法对密码进行加密存储。
- 利用Redis对内容数据进行缓存,减轻数据库压力,提高系统性能。
- 使用ThreadPoolExecutor线程池技术,并发执行查询文章详情和修改文章阅读量等任务,提升系统响应速度。
二、项目实现效果
项目包含7个前端页面(注册,登录,个人博客,总博客,修改已有博客,增加新博客,显示博客详情),每个页面顶端有一个导航栏。其中编辑文章使用markdown编辑器,不过其中个人信息没有实现修改功能,其头像和博客地址固定了(显示为奶龙和我的gitee地址)。
注册页面:
登录页面:
个人博客页面:
总博客页面:
修改已有博客页面:
增加新博客页面:
显示博客详情页面(左边有作者个人信息):
三、项目具体实现
1.软件生命周期
一个软件的生命周期可以划分为
- 可行性研究
- 需求分析
- 概要设计
- 详细设计
- 编码实现
- 测试
- 使用及维护
- 退役
2.项目需求分析
- 用户 注册、登录、退出登录 的功能。
- 显示个人博客列表:按发布时间倒序排列,各博客显示 标题、发布时间、简介。博客下方有 查看全文、修改、删除等功能。
- 显示总博客列表:分页显示(首页、末页、上一页、下一页功能),按发布时间倒序排列,各博客显示 标题、发布时间、简介。博客下方有 查看全文 功能。
- 查看全文:显示作者信息(头像,账号名,博客地址,所发布文章总数),博客信息(标题,发布时间,文章详情,文章阅读量)。
- 增加新文章功能。
- 修改已有文章功能(在修改界面,导入文章之前已有的内容)。
- 删除文章功能。
- 用户权限限制:进入总博客页面,查看全文,无需登录。进入个人博客页面(修改,删除博客),增加新博客,都需要登录(如果用户没有登录,则强制登录)。
3.设计
主要是设计数据库存储用户和文章信息。
用户信息表结构:
表中的 昵称 未作具体使用,博客地址,头像 固定了不可修改。
文章信息表结构:
文章发布和修改时间自动为当前时间。
4.编码实现
4.1项目构建及相关配置
基于SpringBoot和Mybatis等框架构建项目,及mysql,Mybatis,Redis等相关配置...
4.2创建实体类(依据数据库中表结构)
例如:
@Data
public class Userinfo implements Serializable {private int id;private String username;private String password;private String nickname;private String blogSite;private String photo;private boolean state;}
4.3数据库持久层(涉及到的增删查改)
例如:
public interface ArticleMapper {//添加文章@Insert("insert into articleinfo(title,content,author_id,intro) values(#{title},#{content},#{author_id},#{intro})")public int addArticle(Articleinfo articleinfo);//以用户id获取用户文章列表@Select("select * from articleinfo where author_id=#{author_id} order by id desc")public List<Articleinfo> getUserArticleList(@Param("author_id") int author_id);//以用户id和文章id删除文章@Delete("delete from articleinfo where author_id=#{author_id} and id=#{id}")public int deleteArticle(@Param("author_id")int author_id,@Param("id") int id);//根据用户id和文章id得到文章@Select("select * from articleinfo where author_id=#{author_id} and id=#{id}")public Articleinfo getArticleByUidAid(@Param("author_id")int author_id,@Param("id") int id);//修改文章@Update("update articleinfo set title=#{title},content=#{content},intro=#{intro} where id=#{id}")public int updateArticle(Articleinfo articleinfo);//根据文章id查询到文章@Select("select * from articleinfo where id=#{id}")public Articleinfo getArticleByAid(@Param("id") int id);//统计用户发布文章数量@Select("select count(*) from articleinfo where author_id=#{author_id}")public int getArticleCountByUid(@Param("author_id")int author_id );//修改文章阅读量,+1.@Update("update articleinfo set read_count=read_count+1 where id=#{id}")public int updateArticleReadCount(@Param("id") int id);//查询所有文章,分页.@Select("select * from articleinfo order by id desc limit #{perPage} offset #{offsetPage}")public List<Articleinfo> getAllArticles(@Param("perPage") int perPage,@Param("offsetPage") int offsetPage);//计算一下总文章数@Select("select count(*) from articleinfo ")public int getAllArticleCount();
4.4统一前后端数据交互对象
public class ResultAjax {private int code;private String msg;private Object data;public static ResultAjax successful1(Object data){ResultAjax result=new ResultAjax();result.setCode(200);result.setData(data);result.setMsg("");return result;}public static ResultAjax successful2(int code,String msg,Object data){ResultAjax result=new ResultAjax();result.setCode(code);result.setData(data);result.setMsg(msg);return result;}public static ResultAjax fail1(int code,String msg){ResultAjax result=new ResultAjax();result.setCode(code);result.setMsg(msg);return result;}public static ResultAjax fail2(int code,String msg,Object data){ResultAjax result=new ResultAjax();result.setCode(code);result.setData(data);result.setMsg(msg);return result;}
}
4.5注册、登录、退出登录
采用MD5算法对密码进行加密存储:
存储密码:利用uuid生成唯一的字符串作为盐值,与用户注册时输入的密码拼接后MD5加密生成最终密码,在数据库中存储盐值+最终密码。
验证密码:将数据库中盐值与最终密码分开, 将用户登录时输入的待验证密码与盐值进行MD5加密后,再与最终密码比较.
public class PasswordUtils {//加密public static String encrypt(String password){//1.用uuid作为盐值String salt= UUID.randomUUID().toString().replace("-","");//2.盐值加密码用md5加密String finalPassword= DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));//3.存储盐值和最终密码return salt+'$'+finalPassword;}//验证public static boolean verifyPassword(String password,String databasePassword){//1.将盐值与最终密码分开String[] saltPassword=databasePassword.split("\\$");//2.将待验证密码与盐值进行md5加密,再与最终密码比较.String salt=saltPassword[0];String finalPassword =saltPassword[1];String verifyPassword= DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));if(verifyPassword.equals(finalPassword)){return true;}else {return false;}}}
登录后利用Session(会话)存储登录信息:
//得到当前登录用户对象
public class SessionUtils {public static Userinfo getLoginUser(HttpServletRequest request){HttpSession session=request.getSession(false);if(session!=null && session.getAttribute(SESSION_KEY)!=null){return (Userinfo) session.getAttribute(SESSION_KEY);}return null;}
}
4.6显示个人博客列表
根据用户id查询该作者所有文章,按时间倒序(也可以按文章id倒序排列)显示。
4.7显示总博客列表
要根据文章总数来计算总页数,和分页查询显示当前页面:利用ThreadPoolTaskExecutor线程池,并发执行分页查询数据库中文章和统计所有文章数(两个都是查询操作可以并发执行)每页显示文章数,由前端传入(默认每页显示两条文章)。
//查询所有文章,分页.@RequestMapping("/getAllArticles")public ResultAjax getAllArticles(Integer currentPage,Integer perPage) throws ExecutionException, InterruptedException {//无需验证登录也可访问总博客页.//1.校验参数if(currentPage==null||currentPage<=0||perPage==null ||perPage<=0){return ResultAjax.fail1(-1,"参数错误");}//2.查询数据库.分页查询limit perPage offset (currentPage-1)*perPage,task1.int offsetPage=(currentPage-1)*perPage;//计算一下总文章数,再来计算总页数,task2.int allArticleCount=0;//这两个都是查询,可以并发执行.FutureTask<List<Articleinfo>> task1 = new FutureTask(() -> {return articleService.getAllArticles(perPage,offsetPage);});taskExecutor.submit(task1);FutureTask<Integer> task2 = new FutureTask(() -> {return articleService.getAllArticleCount();});taskExecutor.submit(task2);List<Articleinfo> list=task1.get();allArticleCount= task2.get();int pageSzie=1;if(allArticleCount!=0){pageSzie=allArticleCount/perPage;if(allArticleCount%perPage!=0){pageSzie+=1;}}HashMap<String,Object> hashMap=new HashMap<>();hashMap.put("articleList",list);hashMap.put("pageSize",pageSzie);return ResultAjax.successful1(hashMap);}
}
4.8查看全文
显示文章和用户信息:根据文章id查询到文章,再拿到用户id,从而查询到用户信息。其中(查询用户对象和统计该用户发布文章数量 并且修改当前文章阅读量+1,以上三个操作利用ThreadPoolTaskExecutor线程池并发执行):
//查看全文//返回文章和用户对象.@RequestMapping("/fullText")public ResultAjax fullText(Integer id) throws ExecutionException, InterruptedException {//1.校验参数if(id==null || id<=0){return ResultAjax.fail1(-1,"参数错误");}//2.根据文章id查询到文章,再从中拿到用户id.Articleinfo articleinfo=articleService.getArticleByAid(id);if(articleinfo==null){return ResultAjax.fail1(-2,"查看全文失败");}int uid=articleinfo.getAuthor_id();if(uid<=0){return ResultAjax.fail1(-2,"查看全文失败");}// 得到相应用户对象和统计发布文章数量// 并且修改文章阅读量,+1.//3.以上三个操作并发执行,利用线程池.//根据uid查询用户对象FutureTask<UserinfoVO> userTask = new FutureTask(() -> {return userService.getUserByUserId(uid);});taskExecutor.submit(userTask);//统计用户发布文章数量FutureTask<Integer> articleCountTask = new FutureTask(() -> {return articleService.getArticleCountByUid(uid);});taskExecutor.submit(articleCountTask);//修改文章阅读量,+1.FutureTask <Integer> articleReadCountTask = new FutureTask(() -> {return articleService.updateArticleReadCount(id);});taskExecutor.submit(articleReadCountTask );UserinfoVO userinfovo=userTask.get();int articleCount=articleCountTask.get();int articleReadCount=articleReadCountTask.get();//返回更新操作影响的行数//校验一下参数if(userinfovo==null || articleCount<=0 || articleReadCount !=1){return ResultAjax.fail1(-2,"查看全文失败");}//4.组装好数据userinfovo.setArticleCount(articleCount);//设好文章发布数量articleinfo.setRead_count(articleinfo.getRead_count()+1);//文章阅读量+1.//5.用一个哈希表返回HashMap<String,Object> hashMap=new HashMap<>();hashMap.put("user",userinfovo);hashMap.put("article",articleinfo);return ResultAjax.successful1(hashMap);}
4.9增加新文章
增加文章操作...
4.10修改文章
修改文章操作:先查询导入原有文章内容,还要验证该文章是否为当前作者的文章后才能修改(通过比较当前登录用户id与文章用户id)。
4.12删除文章
此处删除文章操作是真的删除数据库中文章...
4.13用户权限限制
使用拦截器实现用户登录校验:
进入总博客页面,查看全文,无需登录。进入个人博客页面(修改,删除博客),增加新博客,需要登录(如果用户没有登录,则强制登录)
/*** 登录拦截器*/
public class LoginIntercept implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session=request.getSession(false);if(session!=null && session.getAttribute(SESSION_KEY)!=null){return true;}//没登录就跳转到登录页面response.sendRedirect("/login.html");return false;}
}/*** 系统配置*/
@Configuration
public class MyConfig implements WebMvcConfigurer {// 添加拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginIntercept()).addPathPatterns("/**") // 拦截所有接⼝.excludePathPatterns("/editor.md/*")//放行.excludePathPatterns("/img/*").excludePathPatterns("/js/*").excludePathPatterns("/css/*").excludePathPatterns("/blog_list.html").excludePathPatterns("/article/getAllArticles").excludePathPatterns("/blog_content.html").excludePathPatterns("/article//fullText").excludePathPatterns("/reg.html").excludePathPatterns("/user/reg").excludePathPatterns("/login.html").excludePathPatterns("/user/login");}
}
四、项目代码(gitee地址)
blog_system · new林/项目 - 码云 - 开源中国 (gitee.com)
(服务器过期了,没部署...,redis没用到...)