1.个人博客系统项目

一、项目介绍

  个人博客系统

  • 相关技术:  SpringBoot+SpringMvc+Mybatis+Mysql+Redis
  • 项目简介:本项目为一个功能完善的个人博客系统,支持文章的编辑、修改、删除和发布,以及作者个人信息的展示等功能。
  • 项目描述:
  1. 采用前后端分离架构,基于SpringBoot和Mybatis等框架构建。
  2. 设计统一的前后端数据返回格式,提高系统可读性和可维护性。
  3. 使用拦截器实现用户登录校验,并采用MD5算法对密码进行加密存储。
  4. 利用Redis对内容数据进行缓存,减轻数据库压力,提高系统性能。
  5. 使用ThreadPoolExecutor线程池技术,并发执行查询文章详情和修改文章阅读量等任务,提升系统响应速度。

二、项目实现效果

  项目包含7个前端页面(注册,登录,个人博客,总博客,修改已有博客,增加新博客,显示博客详情),每个页面顶端有一个导航栏。其中编辑文章使用markdown编辑器,不过其中个人信息没有实现修改功能,其头像和博客地址固定了(显示为奶龙和我的gitee地址)。

注册页面:

登录页面:

个人博客页面:

总博客页面:

修改已有博客页面:

增加新博客页面:

显示博客详情页面(左边有作者个人信息):


三、项目具体实现

1.软件生命周期

  一个软件的生命周期可以划分为

  • 可行性研究
  • 需求分析
  • 概要设计
  • 详细设计
  • 编码实现
  • 测试
  • 使用及维护
  • 退役

2.项目需求分析

  1. 用户 注册、登录、退出登录 的功能。
  2. 显示个人博客列表:按发布时间倒序排列,各博客显示 标题、发布时间、简介。博客下方有 查看全文、修改、删除等功能。
  3. 显示总博客列表:分页显示(首页、末页、上一页、下一页功能),按发布时间倒序排列,各博客显示 标题、发布时间、简介。博客下方有 查看全文 功能。
  4. 查看全文:显示作者信息(头像,账号名,博客地址,所发布文章总数),博客信息(标题,发布时间,文章详情,文章阅读量)。
  5. 增加新文章功能。
  6. 修改已有文章功能(在修改界面,导入文章之前已有的内容)。
  7. 删除文章功能。
  8. 用户权限限制:进入总博客页面,查看全文,无需登录。进入个人博客页面(修改,删除博客),增加新博客,都需要登录(如果用户没有登录,则强制登录)。

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 {
    @Override
    public 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 {
    // 添加拦截器
    @Override
    public 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没用到...)


  • 17
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值