> 文章列表 > 动力节点王鹤SpringBoot3笔记——第八章 文章管理模块

动力节点王鹤SpringBoot3笔记——第八章 文章管理模块

动力节点王鹤SpringBoot3笔记——第八章 文章管理模块

目录

第八章  文章管理模块

8.1  配置文件 

8.2 视图文件

8.3 Java代码


 

第八章  文章管理模块

创建新的Spring Boot项目,综合运用视频中的知识点,做一个文章管理的后台应用。 新的Spring Boot项目Lession20-BlogAdmin。Maven构建工具,包名称com.bjpowernode.blog JDK19,依赖:

  • Spring Web
  • Lombok
  • Thymeleaf
  • MyBatis Framework
  • MySQL Driver

依赖还需要Bean Validation 需求:文章管理工作,发布新文章,编辑文章,查看文章内容等 

8.1  配置文件 

组织配置文件 

 app-base.yml

article:#最低文章阅读数量low-read: 10#首页显示最多的文章数量top-read: 20

db.yml

spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Shanghaiusername: rootpassword: 123456hikari:auto-commit: truemaximum-pool-size: 10minimum-idle: 10#获取连接时,检测语句connection-test-query: select 1connection-timeout: 20000#其他属性data-source-properties:cachePrepStmts: truedataSource.cachePrepStmtst: truedataSource.prepStmtCacheSize: 250dataSource.prepStmtCacheSqlLimit: 2048dataSource.useServerPrepStmts: true

8.2 视图文件

logo文件
favicon.ico放在static/ 根目录下

创建模板页面 

 articleList.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>文章列表</title><link rel="icon" href="../favicon.ico" type="image/x-icon"/><script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body><div style="margin-left: 200px"><h3>阅读最多的前10篇文章</h3><table  border="1px"  cellspacing="0px" cellpadding="2px"><thead><th>选择</th><th>序号</th><th>标题</th><th>副标题</th><th>已读数量</th><th>发布时间</th><th>最后修改时间</th><th>编辑</th></thead><tbody><tr th:each="article,loopStats : ${articleList}"><td><input type="checkbox" th:value="${article.id}"></td><td th:text="${loopStats.index+1}"></td><td th:text="${article.title}"></td><td th:text="${article.summary}"></td><td th:text="${article.readCount}"></td><td th:text="${article.createTime}"></td><td th:text="${article.updateTime}"></td><td><a th:href="@{/article/get(id=${article.id})}">编辑</a></td></tr><tr><td colspan="8"><table width="100%"><tr><td><button id="add" onclick="addArticle()">发布新文章</button></td><td><button id="delete" onclick="deleteArticle()">删除文章</button></td><td><button id="read" onclick="readOverview()">文章概览</button></td></tr></table></td></tr></tbody></table><form id="frm" th:action="@{/view/addArticle}" method="get"></form><form id="delfrm" th:action="@{/article/removeArticle}" method="post"><input type="hidden" id="idsDom" name="ids" value="" ></form></div><script type="text/javascript">function addArticle(){$("#frm").submit();}function deleteArticle(){var ids = [];$("input[type='checkbox']:checked").each( (index,item)=>{ids.push( item.value );})$("#idsDom").val(ids);$("#delfrm").submit();}function readOverview(){var ids = [];$("input[type='checkbox']: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>
</html>

bind.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div style="margin-left: 200px"><div th:each="field:${errors}"><div th:text="${field.field}"></div><div th:text="${field.defaultMessage}"></div></div></div>
</body>
</html>

error.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><div style="margin-left: 200px"><div th:text="${error}"></div></div>
</body>
</html>

8.3 Java代码

 java代码 

model包: 

 ArticleVO.java

@Data
public class ArticleVO {private Integer id;private Integer userId;private String title;private String summary;private String content;private Integer readCount;private LocalDateTime createTime;private LocalDateTime updateTime;
}

ArticleParam.java

package com.bjpowernode.blog.model.param;import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.awt.PageAttributes.MediaType;
import lombok.Data;
import lombok.NonNull;
import org.hibernate.validator.constraints.Range;@Datapublic class ArticleParam {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,max = 30,message = "文章副标题{min}-{max}",groups ={ AddArticle.class, EditArticle.class })private String summary;@NotBlank(message = "请输入文章副标题",groups ={ AddArticle.class, EditArticle.class })@Size(min = 50,max = 8000,message = "文章至少五十个字,文章至多八千字",groups ={ AddArticle.class, EditArticle.class })private String content;
}

ArticleDTO.java

@Data
public class ArticleDTO {private Integer id;private Integer userId;private String title;private String summary;private Integer readCount;private String content;private LocalDateTime createTime;private LocalDateTime updateTime;
}

po包

 ArticlePO.java

@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;
}

ArticleDetailPO.java

@Data
public class ArticleDetailPO {private Integer id;private Integer articleId;private String content;
}

mapper包

 ArticleMapper.java

package com.bjpowernode.blog.mapper;public interface ArticleMapper {@Select("""select id,user_id,title,summary,read_count,create_time,update_timefrom articlewhere read_count >=#{lowRead}order by read_count desclimit #{topRead}""")@Results(id = "ArticleBaseMap", value = {@Result(id = true, column = "id", property = "id"),@Result(column = "user_id", property = "userId"),@Result(column = "title", property = "title"),@Result(column = "summary", property = "summary"),@Result(column = "read_count", property = "readCount"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime"),})List<ArticlePO> 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 m.id as articleId,title,summary,content from article m left join article_detail ad on m.id = ad.article_idwhere m.id=#{id}""")@Results({@Result(id = true, column = "articleId", property = "id"),@Result(column = "title", property = "title"),@Result(column = "summary", property = "summary"),@Result(column = "content", property = "content", jdbcType = JdbcType.LONGVARCHAR, javaType = String.class)})ArticleDTO selectArticleAndDetail(Integer id);//更新文章title,summary@Update("""update article set title=#{title},summary=#{summary} where id=#{id}""")int updateArticle(ArticlePO articlePO);@Update("""update article_detail set content=#{content} where article_id=#{articleId}""")int updateArticleDetail(ArticleDetailPO articleDetailPO);//<script>动态sql</script>@Delete("""<script>delete from article where id in<foreach item="id" collection="list" open="(" separator="," close=")">#{id}</foreach></script>""")int deleteArticle(List<Integer> ids);@Delete("""<script>delete from article_detail where id in<foreach item="id" collection="list" open="(" separator="," close=")">#{id}</foreach></script>""")int deleteArticleDetail(List<Integer> ids);@Select("""select id,article_id,content from article_detailwhere article_id= #{id}""")ArticleDetailPO selectDetailByArticleId(Integer id);
}

 ArticleService.java

public interface ArticleService {List<ArticlePO> queryTopAritcle();boolean addArticle(ArticleDTO article);boolean modifyArticle(ArticleParam param);int removeArticle(List<Integer> ids);ArticleDTO queryByArticleId(Integer id);String queryTop20Detail(Integer id);
}

ArticleServiceImpl.java

@RequiredArgsConstructor
@Service
public class ArticleServiceImpl implements ArticleService {private final ArticleMapper articleMapper;private final ArticleSettings articleSettings;@Overridepublic List<ArticlePO> queryTopAritcle() {Integer lowRead = articleSettings.getLowRead();Integer topRead = articleSettings.getTopRead();return articleMapper.topSortByReadCount(lowRead, topRead);}@Transactional(rollbackFor = Exception.class)@Overridepublic boolean addArticle(ArticleDTO article) {ArticlePO articlePO = new ArticlePO();articlePO.setTitle(article.getTitle());articlePO.setSummary(article.getSummary());//从登陆信息中获取,现在给个默认articlePO.setUserId(new Random().nextInt(1000));articlePO.setReadCount(new Random().nextInt(50));articlePO.setCreateTime(LocalDateTime.now());articlePO.setUpdateTime(LocalDateTime.now());articleMapper.insertArticle(articlePO);ArticleDetailPO articleDetailPO = new ArticleDetailPO();articleDetailPO.setArticleId(articlePO.getId());articleDetailPO.setContent(article.getContent());articleMapper.insertArticleDetail(articleDetailPO);return true;}@Transactional(rollbackFor = Exception.class)public boolean modifyArticle(ArticleParam param){ArticlePO  articlePO = new ArticlePO();articlePO.setId(param.getId());articlePO.setTitle(param.getTitle());articlePO.setSummary(param.getSummary());int editArticle = articleMapper.updateArticle(articlePO);ArticleDetailPO detailPO = new ArticleDetailPO();detailPO.setArticleId(param.getId());detailPO.setContent(param.getContent());int editDetail = articleMapper.updateArticleDetail(detailPO);if( editArticle > 0 && editDetail > 0 ){return true;}return false;}@Transactional(rollbackFor = Exception.class)@Overridepublic int removeArticle(List<Integer> ids) {int master = articleMapper.deleteArticle(ids);int detail = articleMapper.deleteArticleDetail(ids);return master;}@Overridepublic ArticleDTO queryByArticleId(Integer id) {return articleMapper.selectArticleAndDetail(id);}@Overridepublic String queryTop20Detail(Integer id) {ArticleDetailPO articleDetailPO = articleMapper.selectDetailByArticleId(id);String content = articleDetailPO.getContent();if(StringUtils.hasText(content)){content = content.substring(0, content.length() >=20 ? 20 : content.length());}return content;}
}

 ArticleController.java

@RequiredArgsConstructor
@Controller
public class ArticleController {private final ArticleService articleService;@GetMapping( value = {"/", "/article/hot"})public String showHotArticle(Model model){List<ArticlePO> articlePOList = articleService.queryTopAritcle();//转为VOList<ArticleVO> 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 article = new ArticleDTO();article.setTitle(param.getTitle());article.setSummary(param.getSummary());article.setContent(param.getContent());boolean add = articleService.addArticle(article);return "redirect:/article/hot";}//查询文章@GetMapping("/article/get")public String queryById(Integer id, Model model){ArticleDTO articleDTO = articleService.queryByArticleId(id);ArticleVO articleVO = BeanUtil.copyProperties(articleDTO, ArticleVO.class);model.addAttribute("article",articleVO);return "/blog/editArticle";}//修改文章@PostMapping("/article/edit")public String modifyArticle(@Validated(ArticleParam.EditArticle.class) ArticleParam param){boolean edit = articleService.modifyArticle(param);return "redirect:/article/hot";}//删除文章@PostMapping("/article/removeArticle")public String removeArticle(@RequestParam("ids") IdType idType){System.out.println("ids="+idType);if(idType.getIdList() == null){throw new IdNullException("Id为null");}articleService.removeArticle(idType.getIdList());return "redirect:/article/hot";}//查询文章开始的20个字@GetMapping("/article/detail/overview")@ResponseBodypublic String queryDetail(Integer id){String top20Content = articleService.queryTop20Detail(id);return  top20Content;}
}

 IdNullException.java

public class IdNullException extends BlogRootException{public IdNullException() {super();}public IdNullException(String message) {super(message);}
}

BlogRootException.java

public class BlogRootException extends RuntimeException{public BlogRootException() {super();}public BlogRootException(String message) {super(message);}
}

GlobalHandleException.java

@ControllerAdvice
public class GlobalHandleException {@ExceptionHandler( BindException.class)public String handlerBindException(BindException bindException, Model model){BindingResult result = bindException.getBindingResult();if( result.hasErrors()){model.addAttribute("errors",result.getFieldErrors());System.out.println("result.getFieldErrors()="+result.getFieldErrors().size());}return "/blog/error/bind";}@ExceptionHandler( Exception.class)public String handlerDefaultException(Exception exception, Model model){model.addAttribute("msg","请稍后重试!!!");return "/blog/error/error";}
}

 IdType.java

@Data
public class IdType {private List<Integer> idList;
}

IdTypeFormatter.java

public class IdTypeFormatter implements Formatter<IdType> {@Overridepublic IdType parse(String text, Locale locale) throws ParseException {IdType idType = new IdType();if(StringUtils.hasText(text)){List<Integer>  ids = new ArrayList<>();for (String id : text.split(",")) {ids.add(Integer.parseInt(id));}idType.setIdList(ids);}return idType;}@Overridepublic String print(IdType object, Locale locale) {return null;}
}

 ArticleSettings.java

@Data
@ConfigurationProperties(prefix = "article")
public class ArticleSettings {private Integer lowRead;private Integer topRead;
}

WebMvcSettings.java

@Configuration
public class WebMvcSettings implements WebMvcConfigurer {@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addFormatter(new IdTypeFormatter());}@Overridepublic void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/view/addArticle").setViewName("/blog/addArticle");}
}

启动类:
Lession19AdminApplication.java

@MapperScan(basePackages = { "com.bjpowernode.blog.mapper" })
@EnableConfigurationProperties( {ArticleSettings.class} )
@SpringBootApplicationpublic class Lession19AdminApplication {public static void main(String[] args) {SpringApplication.run(Lession19AdminApplication.class, args);}}