西安专业得网站建设公司,成都公司网页制作,小程序注册之后怎么开发,企业邮箱是干嘛用的八、博客管理系统
创建新的SpringBoot项目#xff0c;综合运用以上知识点#xff0c;做一个文章管理的后台应用。依赖#xff1a; Spring WebLombokThymeleafMyBatis FrameworkMySQL DriverBean Validationhutool 需求#xff1a;文章管理工作#xff0c;发布新文章…八、博客管理系统
创建新的SpringBoot项目综合运用以上知识点做一个文章管理的后台应用。依赖 Spring WebLombokThymeleafMyBatis FrameworkMySQL DriverBean Validationhutool 需求文章管理工作发布新文章编辑文章查看文章内容等
8.1配置文件
1.组织配置文件
app-base.yml
article:#最低阅读数low-read: 10#首页显示记录的数量top-read: 20db.properties(一定要用properties)
#配置数据源
spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver
spring.datasource.urljdbc:mysql://localhost:3306/springboot?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8autoReconnecttrueuseSSLfalse
spring.datasource.usernameroot
spring.datasource.password030522
spring.datasource.typecom.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.auto-committrue
spring.datasource.hikari.maximum-pool-size10
spring.datasource.hikari.minimum-idle10
#获取连接时检测语句
spring.datasource.hikari.connection-test-queryselect 1
spring.datasource.hikari.connection-timeout20000
#其他属性
spring.datasource.hikari.data-source-properties.cachePrepStmtstrue
spring.datasource.hikari.data-source-properties.dataSource.cachePrepStmtsttrue
spring.datasource.hikari.data-source-properties.dataSource.prepStmtCacheSize250
spring.datasource.hikari.data-source-properties.dataSource.prepStmtCacheSqlLimit2048
spring.datasource.hikari.data-source-properties.dataSource.useServerPrepStmtstrueimg src“C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20230902103338099.png”
8.2视图文件
2.logo文件
favicon.ico 放在 static/ 根目录下
3.创建模板页面 articleList.html
!DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/titlelink relicon th:href{/favicon.ico} typeimage/x-icon/script srchttps://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js/script
/head
body
div stylemargin-left: 200pxh3阅读最多的20篇文章/h3table border1px cellpadding0px cellspacing0pxtheadth选择/thth序号/thth标题/thth副标题/thth已读数/thth发布时间/thth最后修改时间/thth操作/th/theadtbodytr th:eacharticle , loopStatus : ${articleList}tdinput typecheckbox th:value${article.id}/tdtd th:text${loopStatus.index 1}/tdtd th:text${article.title}/tdtd th:text${article.summary}/tdtd th:text${article.readCount}/tdtd th:text${article.createTime}/tdtd th:text${article.updateTime}/tdtda th:href{/article/get(id${article.id})}编辑文章/a /td/trtrtd colspan8table width100%trtdbutton idadd onclickaddArticle()发布新文章/button/tdtdbutton iddel onclickdeleteArticle()删除文章/button/tdtdbutton idview onclickoverView()文章概览/button/td/tr/table/td/tr/tbody/tableform idfrm th:action{/view/addArticle} methodget/formform iddelFrm th:action{/article/remove} methodpostinput typehidden nameids idids value/form
/div
scriptfunction addArticle(){$(#frm).submit();}function deleteArticle(){var ids[];$(input[typecheckbox]:checked).each((index,item){ids.push(item.value);})if (ids.length0){alert(请选择文章);return;}$(#ids).val(ids);$(#delFrm).submit();}function overView(){var ids [];$(input[typecheckbox]:checked).each( (index,item) {ids.push(item.value);})if( ids.length ! 1){alert(请选择一个文章查看);return;}$.get(../article/detail/overview,{id: ids[0]}, (data,status){alert(data);})}
/script
/body
/htmladdArticle.html
!DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/titlelink relicon th:href{/favicon.ico} typeimage/x-icon/
/head
body
div stylemargin-left: 200pxh3发布新的文章/h3form th:action{/article/add} methodposttabletrtd标题/tdtdinput typetext nametitle/td/trtrtd副标题/tdtdinput typetext namesummary size50/td/trtrtd内容/tdtdtextarea namecontent cols60 rows20/textarea/td/tr/tablebr/input typesubmit value发布新文章 stylemargin-left: 200px/form
/div
/body
/htmleditArticle.html
!DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/titlelink relicon th:href{/favicon.ico} typeimage/x-icon/
/head
body
div stylemargin-left: 200pxh3修改文章/h3form th:action{/article/edit} methodposttabletrtd标题/tdtdinput typetext nametitle th:value${article.title}/td/trtrtd副标题/tdtdinput typetext namesummary size50 th:value${article.summary}/td/trtrtd内容/tdtdtextarea namecontent cols60 rows20 th:text${article.content}/textarea/td/tr/tablebr/input typehidden th:value${article.id} nameidinput typesubmit value确认修改 stylemargin-left: 200px/form
/div
/body
/htmlbind.html
!DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/titlelink relicon th:href{/favicon.ico} typeimage/x-icon/
/head
body
h3输入异常/h3
table border1pxtheadth字段/thth描述/th/theadtbodytr th:eacherr:${errors}td th:text${err.field}/tdtd th:text${err.defaultMessage}/td/tr/tbody
/table
/body
/htmlerror.html
!DOCTYPE html
html langen xmlns:thhttp://www.thymeleaf.org
headmeta charsetUTF-8titleTitle/titlelink relicon th:href{/favicon.ico} typeimage/x-icon/
/head
body
h3请求错误/h3
h3 th:text${msg}/h3
/body
/html8.3Java代码
4.java代码
model包 ArticleDTO
package com.hhb.blog.model.dto;import lombok.Data;Data
public class ArticleDTO {private Integer id;private String title;private String summary;private String content;
}ArticleAndDetailMap
package com.hhb.blog.model.map;import lombok.Data;Data
public class ArticleAndDetailMap {private Integer id;private String title;private String summary;private String content;
}ArticleParam
package com.hhb.blog.model.param;import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;Data
public class ArticleParam {//使用JSR303注解public static interface AddArticle{};public static interface EditArticle{};NotNull(message 修改时必须有id,groups {EditArticle.class})Min(value 1,message 文章id大于{value},groups {EditArticle.class})private Integer id;NotBlank(message 请输入文章标题,groups {AddArticle.class,EditArticle.class})Size(min 2,max 20,message 文章标题在{min}-{max},groups {AddArticle.class,EditArticle.class})private String title;NotBlank(message 请输入文章副标题,groups {AddArticle.class,EditArticle.class})Size(min 10,max30,message 文章副标题在{min}-{max},groups {AddArticle.class,EditArticle.class})private String summary;NotBlank(message 请输入文章内容,groups {AddArticle.class,EditArticle.class})Size(min 20,max 8000,message 文章内容至少{min}字最多{max}字,groups {AddArticle.class,EditArticle.class})private String content;}ArticleDeatilPO
package com.hhb.blog.model.po;import lombok.Data;Data
public class ArticleDetailPO {private Integer id;private Integer articleId;private String content;
}ArticlePO
package com.hhb.blog.model.po;import lombok.Data;import java.time.LocalDateTime;Data
public class ArticlePO {private Integer id;private Integer userId;private String title;private String summary;private Integer readCount;private LocalDateTime createTime;private LocalDateTime updateTime;
}ArticleVO
package com.hhb.blog.model.po;import lombok.Data;import java.time.LocalDateTime;Data
public class ArticlePO {private Integer id;private Integer userId;private String title;private String summary;private Integer readCount;private LocalDateTime createTime;private LocalDateTime updateTime;
}mapper包
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
ArticleMapper
package com.hhb.blog.mapper;import com.hhb.blog.model.dto.ArticleDTO;
import com.hhb.blog.model.map.ArticleAndDetailMap;
import com.hhb.blog.model.po.ArticleDetailPO;
import com.hhb.blog.model.po.ArticlePO;
import org.apache.ibatis.annotations.*;import java.util.List;public interface ArticleMapper {//查询首页需要的文章列表Select(select id,user_id ,title,summary, read_count , create_time, update_timefrom articlewhere read_count #{lowRead}order by read_count desc limit #{topRead})ListArticlePO topSortByReadCount(Integer lowRead, Integer topRead);//添加文章Insert(insert into article(user_id, title, summary, read_count, create_time, update_time)values(#{userId},#{title},#{summary},#{readCount},#{createTime},#{updateTime}))//主键自增Options(useGeneratedKeys true, keyProperty id, keyColumn id)int insertArticle(ArticlePO articlePO);//添加文章内容Insert(insert into article_detail(article_id, content) values(#{articleId},#{content}))int insertArticleDetail(ArticleDetailPO articleDetailPO);//两表连接,根据主键查询文章Select(select article.id,title,summary,contentfrom article,article_detailwhere article.idarticle_detail.article_id and article_id#{id})ArticleAndDetailMap selectArticleAndDetail(Integer id);//修改文章属性Update(update article set title#{title},summary#{summary},update_time#{updateTime}where id#{id})int updateArticle(ArticlePO articlePO);//更新文章内容Update(update article_detail set content#{content} where article_id#{articleId})int updateArticleDetail(ArticleDetailPO articleDetailPO);//删除文章Delete(scriptdelete from article where id in foreach itemid collectionidList open( separator, close)#{id}/foreach/script)int deleteArticle(ListInteger idList);//删除文章内容Delete(scriptdelete from article_detail where article_id in foreach itemid collectionidList open( separator, close)#{id}/foreach/script)int deleteDetail(ListInteger idList);//根据id查询内容Select(select id,article_id,content from article_detailwhere article_id #{articleId})ArticleDetailPO selectArticleDetailByArticleId(Integer articleId);
}service包 ArticleService
package com.hhb.blog.service;import com.hhb.blog.model.dto.ArticleDTO;
import com.hhb.blog.model.po.ArticlePO;import java.util.List;public interface ArticleService {//获取首页文章列表ListArticlePO queryTopArticle();//发布文章article,article_detailboolean addArticle(ArticleDTO articleDTO);//根据主键查询文章ArticleDTO queryByArticleId(Integer id);//修改文章属性和内容boolean modifyArticle(ArticleDTO articleDTO);//删除文章boolean removeArticle(ListInteger idList);//查询文章内容前20个字符String queryTop20Content(Integer id);
}ArticleServiceImpl
package com.hhb.blog.service.impl;import cn.hutool.core.bean.BeanUtil;
import com.hhb.blog.mapper.ArticleMapper;
import com.hhb.blog.model.dto.ArticleDTO;
import com.hhb.blog.model.map.ArticleAndDetailMap;
import com.hhb.blog.model.po.ArticleDetailPO;
import com.hhb.blog.model.po.ArticlePO;
import com.hhb.blog.service.ArticleService;
import com.hhb.blog.settings.ArticleSettings;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;import java.time.LocalDateTime;
import java.util.List;
import java.util.Random;Service
RequiredArgsConstructor
public class ArticleServiceImpl implements ArticleService {private final ArticleMapper articleMapper;private final ArticleSettings articleSettings;//构造注入/* public ArticleServiceImpl(ArticleMapper articleMapper) {this.articleMapper articleMapper;}*/Overridepublic ListArticlePO queryTopArticle() {Integer lowRead articleSettings.getLowRead();Integer topRead articleSettings.getTopRead();return articleMapper.topSortByReadCount(lowRead, topRead);}//发布文章Transactional(rollbackFor Exception.class)Overridepublic boolean addArticle(ArticleDTO articleDTO) {//文章ArticlePO articlePO new ArticlePO();articlePO.setTitle(articleDTO.getTitle());articlePO.setSummary(articleDTO.getSummary());articlePO.setCreateTime(LocalDateTime.now());articlePO.setUpdateTime(LocalDateTime.now());articlePO.setReadCount(new Random().nextInt(1000));articlePO.setUserId(new Random().nextInt(5000));int addArticle articleMapper.insertArticle(articlePO);//文章内容ArticleDetailPO articleDetailPO new ArticleDetailPO();articleDetailPO.setArticleId(articlePO.getId());articleDetailPO.setContent(articleDTO.getContent());int addDetail articleMapper.insertArticleDetail(articleDetailPO);return (addDetail addArticle) 2 ? true : false;}Overridepublic ArticleDTO queryByArticleId(Integer id) {//文章属性内容ArticleAndDetailMap mapper articleMapper.selectArticleAndDetail(id);//转为DTO,两种方式/*ArticleDTO articleDTO new ArticleDTO();articleDTO.setTitle(mapper.getTitle());articleDTO.setContent(mapper.getContent());articleDTO.setSummary(mapper.getSummary());articleDTO.setId(mapper.getId());*/ArticleDTO articleDTO BeanUtil.copyProperties(mapper, ArticleDTO.class);return articleDTO;}Transactional(rollbackFor Exception.class)Overridepublic boolean modifyArticle(ArticleDTO articleDTO) {//修改文章属性ArticlePO articlePO new ArticlePO();articlePO.setTitle(articleDTO.getTitle());articlePO.setSummary(articleDTO.getSummary());articlePO.setUpdateTime(LocalDateTime.now());articlePO.setId(articleDTO.getId());int article articleMapper.updateArticle(articlePO);//修改文章内容ArticleDetailPO articleDetailPO new ArticleDetailPO();articleDetailPO.setArticleId(articleDTO.getId());articleDetailPO.setContent(articleDTO.getContent());int detail articleMapper.updateArticleDetail(articleDetailPO);return (article detail) 2 ? true : false;}//删除文章属性、内容Transactional(rollbackFor Exception.class)Overridepublic boolean removeArticle(ListInteger idList) {int article articleMapper.deleteArticle(idList);int detail articleMapper.deleteDetail(idList);return article detail ? true : false;}//查询文章内容前20个字符Overridepublic String queryTop20Content(Integer id) {ArticleDetailPO articleDetailPO articleMapper.selectArticleDetailByArticleId(id);String content 无内容;if( articleDetailPO ! null ){content articleDetailPO.getContent();if(StringUtils.hasText(content)){//content content.substring(0, content.length() 20 ? 20 : content.length() );content content.substring(0, 20 );}}return content;}
}controller包
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
ArticleController
package com.hhb.blog.controller;import cn.hutool.core.bean.BeanUtil;
import com.hhb.blog.format.IdType;
import com.hhb.blog.handler.exp.IdTypeException;
import com.hhb.blog.model.dto.ArticleDTO;
import com.hhb.blog.model.param.ArticleParam;
import com.hhb.blog.model.po.ArticlePO;
import com.hhb.blog.model.vo.ArticleVO;
import com.hhb.blog.service.ArticleService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.util.List;RequiredArgsConstructor
Controller
public class ArticleController {private final ArticleService articleService;GetMapping(value {/, /article/hot})public String showHotArticle(Model model) {ListArticlePO articlePOList articleService.queryTopArticle();//转为VO .hutool工具类ListArticleVO articleVOList BeanUtil.copyToList(articlePOList, ArticleVO.class);//添加数据model.addAttribute(articleList, articleVOList);//视图return /blog/articleList;}//发布新文章PostMapping(/article/add)//接收对象类型参数public String addArticle(Validated(ArticleParam.AddArticle.class) ArticleParam param) {ArticleDTO articleDTO new ArticleDTO();articleDTO.setContent(param.getContent());articleDTO.setSummary(param.getSummary());articleDTO.setTitle(param.getTitle());boolean add articleService.addArticle(articleDTO);return redirect:/article/hot;}//查询文章内容GetMapping(/article/get)public String queryById(Integer id, Model model) {if (id ! null id 0) {ArticleDTO articleDTO articleService.queryByArticleId(id);//DTO-VOArticleVO articleVO BeanUtil.copyProperties(articleDTO, ArticleVO.class);//添加数据model.addAttribute(article, articleVO);//视图return /blog/editArticle;} else {return /blog/error/error;}}//更新文章PostMapping(/article/edit)public String modifyArticle(Validated(ArticleParam.EditArticle.class) ArticleParam param) {/*ArticleDTO articleDTO new ArticleDTO();articleDTO.setId(param.getId());articleDTO.setTitle(param.getTitle());articleDTO.setSummary(param.getSummary());articleDTO.setContent(param.getContent());*/ArticleDTO articleDTO BeanUtil.copyProperties(param, ArticleDTO.class);boolean edit articleService.modifyArticle(articleDTO);return redirect:/article/hot;}//删除文章PostMapping(/article/remove)//public String removeArticle(Integer ids[])public String removeArticle(RequestParam(ids) IdType idType) {if (idType null) {throw new IdTypeException(ID为空);}boolean delete articleService.removeArticle(idType.getIdList());return redirect:/article/hot;}//预览文章GetMapping(/article/detail/overview)ResponseBodypublic String queryDetail(Integer id) {String top20Content 无ID;if (id ! null) {top20Content articleService.queryTop20Content(id);}return top20Content;}
}异常处理包 GlobalExceptionHandler
package com.hhb.blog.handler;import java.util.List;import com.hhb.blog.handler.exp.IdTypeException;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;ControllerAdvice
public class GlobalExceptionHandler {//处理JSR303ExceptionHandler({BindException.class})public String handlerBindException(BindException bindException, Model model) {BindingResult bindingResult bindException.getBindingResult();ListFieldError fieldErrors bindingResult.getFieldErrors();model.addAttribute(errors, fieldErrors);return /blog/error/bind;}ExceptionHandler({IdTypeException.class})public String handleIdTypeException(IdTypeException idTypeException, Model model) {model.addAttribute(msg, idTypeException.getMessage());return /blog/error/error;}ExceptionHandler({Exception.class})public String handleDefaultException(Exception e, Model model) {model.addAttribute(msg, 请稍后重试);return /blog/error/error;}
}BlogRootException
package com.hhb.blog.handler.exp;public class BlogRootException extends RuntimeException{public BlogRootException() {}public BlogRootException(String message) {super(message);}
}IdTypeException
package com.hhb.blog.handler.exp;public class IdTypeException extends BlogRootException {public IdTypeException() {}public IdTypeException(String message) {super(message);}
}数据格式化包 IdType
package com.hhb.blog.format;import lombok.Data;import java.util.List;Data
public class IdType {private ListInteger idList;
}IdTypeFormatter
package com.hhb.blog.format;import org.springframework.format.Formatter;
import org.springframework.util.StringUtils;import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;public class IdTypeFormatter implements FormatterIdType {Overridepublic IdType parse(String text, Locale locale) throws ParseException {IdType idType null;if (StringUtils.hasText(text)) {ListInteger ids new ArrayList();for (String id : text.split(,)) {ids.add(Integer.parseInt(id));}idType new IdType();idType.setIdList(ids);}return idType;}Overridepublic String print(IdType object, Locale locale) {return null;}
}设置包 ArticleSettings
package com.hhb.blog.settings;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;Data
ConfigurationProperties(prefix article)
public class ArticleSettings {private Integer lowRead;private Integer topRead;
}MvcSettings
package com.hhb.blog.settings;import com.hhb.blog.format.IdTypeFormatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;Configuration
public class MvcSettings implements WebMvcConfigurer {Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController(/view/addArticle).setViewName(/blog/addArticle);}Overridepublic void addFormatters(FormatterRegistry registry) {registry.addFormatter(new IdTypeFormatter());}
}启动类
package com.hhb.blog;import com.hhb.blog.settings.ArticleSettings;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.transaction.annotation.EnableTransactionManagement;EnableTransactionManagement
MapperScan(basePackages com.hhb.blog.mapper)
EnableConfigurationProperties({ArticleSettings.class})
SpringBootApplication
public class Springboot20BlogAdminApplication {public static void main(String[] args) {SpringApplication.run(Springboot20BlogAdminApplication.class, args);}}8.4界面展示