SpringBoot开源项目个人博客——(10)博客详情页
博客详情页面包括博客文章内容和评论两大部分。博客详情和评论功能都是独立的,这里将评论单独用一个类来编写接口,查询博客详情就直接放在首页的控制器进行处理。
在跳转博客详情页面的时候,返回model时携带文章详情内容,评论列表在进入blog页面时,用ajax请求刷新处理。
一、博客文章内容
1. 博客详情实体类
根据前端需求,博客详情除了显示博客信息外,还需要显示分类信息。在VO
包下创建DetailedBlog
博客详情实体类(省略get、set、构造、toString)
/*vo存博客详情,用户信息,分类名*/
public class DetailedBlog {
private Long id; //主键id
private String firstPicture; //首图地址
private String title; //博客标题
private String content; //博客内容
private String flag; //是否为原创
private Integer views; //浏览次数
private Integer commentCount; //评论次数
private Date updateTime;//更新时间
private boolean appreciation; //是否开启赞赏
private boolean shareStatement; //是否开启版权
private boolean commentabled;//是否开启评论
private String nickname;
private String typeName; //分类名称
}
2. Markdown编辑器工具类
我们现在需要把markdown代码转换为HTML进行存储,这样typo样式才能应用到。
- 添加依赖Editor相关依赖
在pom.xml中添加
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-heading-anchor</artifactId>
<version>0.10.0</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<version>0.10.0</version>
</dependency>
- 添加MarkdownUtils工具类
在util工具包下添加MarkdownUtils工具类:
public class MarkdownUtils {
/**
* markdown格式转换成HTML格式
* @param markdown
* @return
*/
public static String markdownToHtml(String markdown) {
Parser parser = Parser.builder().build();
Node document = parser.parse(markdown);
HtmlRenderer renderer = HtmlRenderer.builder().build();
return renderer.render(document);
}
/**
* 增加扩展[标题锚点,表格生成]
* Markdown转换成HTML
* @param markdown
* @return
*/
public static String markdownToHtmlExtensions(String markdown) {
//h标题生成id
Set<Extension> headingAnchorExtensions = Collections.singleton(HeadingAnchorExtension.create());
//转换table的HTML
List<Extension> tableExtension = Arrays.asList(TablesExtension.create());
Parser parser = Parser.builder()
.extensions(tableExtension)
.build();
Node document = parser.parse(markdown);
HtmlRenderer renderer = HtmlRenderer.builder()
.extensions(headingAnchorExtensions)
.extensions(tableExtension)
.attributeProviderFactory(new AttributeProviderFactory() {
public AttributeProvider create(AttributeProviderContext context) {
return new CustomAttributeProvider();
}
})
.build();
return renderer.render(document);
}
/**
* 处理标签的属性
*/
static class CustomAttributeProvider implements AttributeProvider {
@Override
public void setAttributes(Node node, String tagName, Map<String, String> attributes) {
//改变a标签的target属性为_blank
if (node instanceof Link) {
attributes.put("target", "_blank");
}
if (node instanceof TableBlock) {
attributes.put("class", "ui celled table");
}
}
}
public static void main(String[] args) {
String table = "| hello | hi | 哈哈哈 |\n" +
"| ----- | ---- | ----- |\n" +
"| 斯维尔多 | 士大夫 | f啊 |\n" +
"| 阿什顿发 | 非固定杆 | 撒阿什顿发 |\n" +
"\n";
String a = "[ONESTAR](https://onestar.newstar.net.cn/)";
System.out.println(markdownToHtmlExtensions(a));
}
}
3. 持久层接口
这里把跳转到博客详情页面的接口代码放在博客业务这一块。
在BlogDao接口中添加查询博客详情、文章访问更新(点击一次就更新)、查询评论数量接口
/*===========博客详情(根据id)==============*/
//查询博客详情
DetailedBlog getDetailBlog(Long id);
//更新后 在服务层调用,设置detailedBlog
//文章访问更新
int updateView(Long id);
//根据博客id更新评论数量
int getCommentCountById(Long id);
4. mapper
在mapper
文件夹下的BlogDao.xml
文件中添加如下SQL:
<!--==========================博客详情(根据博客id)===================-->
<resultMap id="detailedBlog" type="DetailedBlog">
<!-- deatiledBlog 只需要 typeName 对应-->
<result column="name" property="typeName"/>
</resultMap>
<!--查询博客详情-->
<select id="getDetailBlog" resultMap="detailedBlog">
select b.id, b.appreciation, b.commentabled, b.content, b.first_picture, b.flag, b.share_statement, b.title, b.update_time, b.views, b.comment_count,
t.name,
u.nickname
from t_blog b,t_type t,t_user u
where b.type_id = t.id and b.user_id = u.id
and b.id = #{id};
</select>
<!--访问增加-->
<update id="updateView">
update t_blog
set views = views+1
where id=#{id};
</update>
<!--根据博客id查询出评论数量 并 更新(评论更新后,就可以更新数量了)-->
<update id="getCommentCountById">
update t_blog b
set b.comment_count = (
select count(*)
from t_comment c where c.blog_id = #{id} and b.id = #{id}
)
where b.id = #{id};
</update>
5. Service业务层
在BlogService接口下添加
/*========博客详情================*/
DetailedBlog getDetailBlog(Long id);
在BlogServiceImpl接口实现类添加
接口实现主要是设置文章显示格式,文章访问自增和文章评论的统计
/*========博客详情================*/
@Override
public DetailedBlog getDetailBlog(Long id) {
DetailedBlog detailedBlog = blogDao.getDetailBlog(id);
if(detailedBlog == null){
throw new NotFoundException("该博客不存在");
}
/*设置文章显示格式 marktohtml,这样typo样式才能应用*/
String content = detailedBlog.getContent();
detailedBlog.setContent(MarkdownUtils.markdownToHtmlExtensions(content));
//文章访问数量自增 设置(这样获取就是最新的)
blogDao.updateView(id);
//评论数量 更新设置
blogDao.getCommentCountById(id);
return detailedBlog;
}
6. 控制器
在IndexController
类中添加接口
/*跳转到 博客详情页 /blog/{id}*/
@GetMapping("/blog/{id}")
public String blog(@PathVariable Long id,Model model){
DetailedBlog detailedBlog = blogService.getDetailBlog(id);
model.addAttribute("blog", detailedBlog);
return "blog";//没有携带评论数据(在blog 中 ajax刷新)
}
二、评论功能
评论稍微复杂些,本博客不会做太多详细讲述,详情请移步oneStar的博客。
(笔者开始也不是很理解评论功能的实现,过一遍代码流程,然后手打几次后,对评论功能的理解能更熟一点)
1. 持久层接口
在dao
包下创建CommentDao
接口,添加如下接口:
@Mapper
@Repository
public interface CommentDao {
//新增一个评论
int savaComment(Comment comment);
//删除一个评论
void deleteComment(Long id);
/*查询父级评论 查询一级回复 查询二级回复*/
//多个参数 用@param
//查询父级评论 : 它的评论类的父评论的-1
List<Comment> findByBlogIdParentIdNull(@Param("blogId") Long blogId,@Param("commentParentId")Long commentParentId);
//查询一级回复 : 根据父评论id
List<Comment> findByBlogIdParentNotNull(@Param("blogId") Long blogId,@Param("parentId")Long parentId);
//查询二级回复 : 根据子回复 id
List<Comment> findByBlogAndReplyId(@Param("blogId")Long blogId,@Param("childId")Long childId);
}
/*
* 查询评论信息需要:
* 查询父级评论(findByBlogIdParentIdNull)、
* 查询一级回复(findByBlogIdParentIdNotNull)、
* 查询二级回复(findByBlogIdAndReplayId)
* =====================
*
根据id为“-1”和博客id查询出所有父评论(父级评论id为‘-1’)
根据父评论的id查询出一级子回复
根据子回复的id循环迭代查询出所有子集回复
将查询出来的子回复放到一个集合中
* */
2.mapper
在mapper
目录下创建CommentDao.xml
文件,添加如下
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.hxj.dao.CommentDao">
<!--新增一个评论-->
<insert id="savaComment" parameterType="Comment">
insert into t_comment (nickname, email, content, avatar, create_time, blog_id, parent_comment_id, admin_comment)
values (#{nickname},#{email},#{content},#{avatar},#{createTime},#{blogId},#{parentCommentId},#{adminComment});
</insert>
<!--删除一个评论-->
<delete id="deleteComment">
delete
from t_comment
where id=#{id};
</delete>
<!--查询父级评论 : 它的评论类的父评论的-1(没爹)-->
<select id="findByBlogIdParentIdNull" resultType="Comment">
select *
from t_comment
where blog_id=#{blogId} and parent_comment_id = #{commentParentId}
order by create_time desc;
</select>
<!--查询一级回复 : 根据父评论id(有爹)-->
<select id="findByBlogIdParentNotNull" resultType="Comment">
select *
from t_comment
where blog_id = #{blogId} and parent_comment_id = #{parentId}
order by create_time desc;
</select>
<!--根据子回复 id-->
<select id="findByBlogAndReplyId" resultType="Comment">
select *
from t_comment
where blog_id = #{blogId} and parent_comment_id = #{childId}
order by create_time desc;
</select>
</mapper>
3. 业务层
在service
包下创建CommentService
接口
public interface CommentService {
//新增一个评论
int savaComment(Comment comment);
//删除一个评论 Comment comment
void deleteComment(Long blogId,Long id);
/*查询评论 根据博客id查询评论信息*/
List<Comment> listCommentByBlogId(Long blogId);
}
在Impl
包下创建接口实现类:CommentServiceImpl
接口实现类
@Service
public class CommentServiceImpl implements CommentService {
@Autowired
private CommentDao commentDao;
@Autowired
private BlogDao blogDao; //评论数据更新后, 也要更新博客的 评论数据
//存放迭代找出的所有"子代评论"的集合
private List<Comment> tempReplys = new ArrayList<>();
@Override
public int savaComment(Comment comment) {
comment.setCreateTime(new Date());
int comments = commentDao.savaComment(comment);
//文章评论统计 更新
blogDao.getCommentCountById(comment.getBlogId());
return comments; //返回影响数
}
@Override
public void deleteComment(Long blogId,Long id) {
//dao只需要根据id 删除即可
commentDao.deleteComment(id);
blogDao.getCommentCountById(/*comment.getBlogId()*/ blogId);
}
/*查询评论*/
@Override
public List<Comment> listCommentByBlogId(Long blogId) {
//查出 父节点, 没有爹的(pid = -1)
List<Comment> comments = commentDao.findByBlogIdParentIdNull(blogId, (long) -1);
//为每个父评论 找 它的子代评论
for (Comment comment : comments) {
Long id = comment.getId();
String Nickname = comment.getNickname();
List<Comment> childComments = commentDao.findByBlogIdParentNotNull(blogId, id);//查 一级子评论
//查询出子(一)评论
combineChildren(blogId,childComments,Nickname);
comment.setReplyComments(tempReplys);//最后查出所有 子代评论后,就赋给 该评论.
//System.out.println(tempReplys);
//tempReplys.clear(); //(然后清空temp,存下一个 评论的 子代) 不能这样, 会清了内容, controller拿不到,地址要留着
tempReplys=new ArrayList<>();
}
return comments;
}
//查询出子(一级)评论 (blogId,childComments, 上一代评论名 做为 下一代的父评论名)
private void combineChildren(Long blogId,List<Comment> childComments,String parentNickname){
//判断是否有 一级子评论
if(childComments.size() > 0){
//循环找出子评论的 id
for (Comment childComment : childComments) {
String parentNickname1 = childComment.getNickname(); //子评论的 nickname
childComment.setParentNickname(parentNickname); //在这里才设置 回复的 parentNickname
tempReplys.add(childComment);//存 入子代评论中
Long childId = childComment.getId();
//查询出子二级评论
recursively(blogId,childId,parentNickname1);
}
}
}
//查询出子二级评论 (blogId,parentNickname, 上一代评论名 做为 下一代的父评论名)
private void recursively(Long blogId,Long childId,String parentNickname){
//根据一级评论 的id 找到二级评论
List<Comment> replyComments = commentDao.findByBlogAndReplyId(blogId, childId);
if(replyComments.size() > 0){
for (Comment replyComment : replyComments) {
String parentNickname2 = replyComment.getNickname();
replyComment.setParentNickname(parentNickname);
Long replyId = replyComment.getId();
tempReplys.add(replyComment); //把二级评论加入
//评论可能 还有子代评论 .... 接着找
recursively(blogId, replyId, parentNickname2);
}
}
}
}
4. 控制器
引用oneStar的内容
添加图片配置 在评论中,需要显示头像,这里直接在配置文件里面进行配置,在application.yml中添加如下配置(这里直接把后面留言的显示图片也一起添加了):
comment.avatar: /images/avatar.png message.avatar: /images/avatar.png
在controller
包下创建CommentController
类,编写如下代码:
@Controller
public class CommentController {
@Autowired
private CommentService commentService;
@Value("${comment.avatar}")
private String avatar;
private String subject="HXJ的博客评论回复";
//查询评论列表
@GetMapping("/comments/{blogId}")
public String comments(@PathVariable("blogId") Long blogId, Model model){
List<Comment> comments = commentService.listCommentByBlogId(blogId);
System.out.println(comments);
model.addAttribute("comments", comments);
return "blog :: commentList";
}
//新增评论(有session 就是 管理员(后台登录了),游客就没有,(发评论时就没有传session)所以也删除不了)
@PostMapping("/comments")
public String post(Comment comment, HttpSession session,Model model){
Long blogId = comment.getBlogId();
User user =(User)session.getAttribute("user");
if(user!=null){
comment.setAvatar(user.getAvatar());
comment.setAdminComment(true);
}else {
comment.setAvatar(avatar);
}
comment.setContent(sensitiveFilterUtil.filter(comment.getContent()));
//前端 传来的是 parentComment.id
if(comment.getParentComment().getId() !=null ){
//是评论
comment.setParentCommentId(comment.getParentComment().getId()); //到impl中 的展示才设置parentNickname
//是子评论
if(comment.getParentComment().getId()!=-1){
Comment parentComment = commentService.findByParentId(comment.getParentComment().getId());//获得父评论(目的为了它的昵称)
}
}
commentService.savaComment(comment);//新增评论
List<Comment> comments = commentService.listCommentByBlogId(blogId);//得到最新评论
//System.out.println(comments);
model.addAttribute("comments", comments);
return "blog :: commentList";
}
//删除 评论
@PostMapping("/comments/delete")
public String delete(Model model,@RequestParam("blogId") Long blogId,@RequestParam("commentId") Long id){
commentService.deleteComment(blogId,id);
List<Comment> comments = commentService.listCommentByBlogId(blogId);
System.out.println(comments);
model.addAttribute("comments", comments);
return "blog :: commentList";
}
}
实现评论操作的局部刷新
基于oneStar的前端改动而来:
删除操作也改成局部刷新(注释掉的就是原版)
<div class="actions">
<a class="reply" data-commentid="1" data-commentnickname="Matt" th:attr="data-commentid=${comment.id},data-commentnickname=${comment.nickname}" onclick="reply(this)">回复</a>
<a class="delete" onclick="shanchu(this)" th:attr="data-blogid=${comment.blogId},data-commentid=${comment.id}" th:if="${session.user}">删除</a>
<!--<a class="delete" href="#" th:href="@{/comment/{param1}/{param2}/delete(param1=${comment.blogId},param2=${comment.id})}" οnclick="return confirm('确定要删除该评论吗?三思啊! 删了可就没了!')" th:if="${session.user}">删除</a>-->
<!--<a class="delete" href="#" th:href="@{/comment/{id}/delete(id=${comment.id})}" οnclick="return confirm('确定要删除该评论吗?三思啊! 删了可就没了!')" th:if="${session.user}">删除</a>-->
</div>
.....
....
.....
<div class="actions">
<a class="reply" data-commentid="1" data-commentnickname="Matt" th:attr="data-commentid=${reply.id},data-commentnickname=${reply.nickname}" onclick="reply(this)">回复</a>
<a class="delete" href="#" onclick="shanchu(this)" th:attr="data-blogid=${reply.blogId},data-commentid=${reply.id}" th:if="${session.user}">删除</a>
<!--<a class="delete" href="#" th:href="@{/comment/{param1}/{param2}/delete(param1=${reply.blogId},param2=${reply.id})}" οnclick="return confirm('确定要删除该评论吗?三思啊! 删了可就没了!')" th:if="${session.user}">删除</a>-->
<!--<a class="delete" href="#" th:href="@{/comment/{id}/delete(id=${reply.id})}" οnclick="return confirm('确定要删除该评论吗?三思啊! 删了可就没了!')" th:if="${session.user}">删除</a>-->
</div>
JS内容如下:
$(function () {
$("#comment-container").load(/*[[@{/comments/{id}(id=${blog.id})}]]*/"comments/6");
});
$('#commentpost-btn').click(function () {
var boo = $('.ui.form').form('validate form');
if (boo) {
console.log('校验成功');
postData();
} else {
console.log('校验失败');
}
});
function postData() {
$("#comment-container").load(/*[[@{/comments}]]*/"/comments",{
"parentComment.id" : $("[name='parentComment.id']").val(),
"blogId" : $("[name='blogId']").val(),
"nickname": $("[name='nickname']").val(),
"email" : $("[name='email']").val(),
"content" : $("[name='content']").val()
},function (responseTxt, statusTxt, xhr) {
//$(window).scrollTo($('#goto'),500);
$(window).scrollTo($('#comment-container'),500);
clearContent();
});
}
function clearContent() {
$("[name='nickname']").val('');
$("[name='email']").val('');
var user = /*[[${session.user}]]*/ null;
/*因为我们在 admin的logincontroller 把user 传进 session*/
console.log(user);
if( user ){
$("[name='nickname']").val(user.nickname);
$("[name='email']").val(user.email);
}
$("[name='content']").val('');
$("[name='parentComment.id']").val(-1);
$("[name='content']").attr("placeholder", "请输入评论信息...");
}
function reply(obj) {
var commentId = $(obj).data('commentid');
var commentNickname = $(obj).data('commentnickname');
$("[name='content']").attr("placeholder", "@"+commentNickname).focus();
$("[name='parentComment.id']").val(commentId);
//$(window).scrollTo($('#comment-container'),500);
$(window).scrollTo($('#comment-form'),500);
}
/*改造成,删除了只改动评论区. 不然模板包太大,评论区又会问题..目前只能这样*/
function shanchu(obj){
var flag = confirm('确定要删除该评论吗?三思啊! 删了可就没了!');
if(flag == true){
var blogId = $(obj).data('blogid');
var commentId = $(obj).data('commentid');
$("#comment-container").load(/*[[@{/comments/delete}]]*/"",{
"blogId" : blogId,
"commentId" : commentId
});
}
$(window).scrollTo($('#comment-container'),500);
}
评论新增功能(敏感词过滤/回复发邮件)
敏感词过滤
参考: 敏感词
直接代码拷贝用就行
-
在
resources
目录下新建sensitive-words.txt
,按照参考那样写点敏感词。 -
在
utils
包下新建一个SensitiveFilterUtil
工具类。
@Component
public class SensitiveFilterUtil {
private static final Logger logger = LoggerFactory.getLogger(SensitiveFilterUtil.class);
private static final String REPLACEMENT = "**";
//根节点
private static final TrieNode rootNode = new TrieNode();
// @PostConstruct 表明这是一个初始化方法,在类被实例化的时候就执行。
@PostConstruct
public void init(){
// 获取类加载器,类加载器是从类路径下,去加载资源,所谓的类路径就是target/classes/目录之下。
// 我们的程序一编译,所有的程序都编译到classes下,包括配置文件。
try(
//得到字节流
InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
//从字节流中读文字不太方便,把字节流转成字符流,把字符流改成缓冲流会效率更高
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
){
String keyword; //用这个keyword去读数据
while((keyword = reader.readLine()) != null){//一行一行的读
//添加到前缀树
this.addKeyWord(keyword);
}
}catch (IOException e){
logger.error("获取铭感词文件失败:"+e.getMessage());
}
}
//把一个敏感词添加到前缀树中去。
public void addKeyWord(String keyword){
TrieNode tempNode = rootNode;
for(int i = 0 ; i< keyword.length() ; ++i){
char key = keyword.charAt(i);
//找子节点
TrieNode subNode = tempNode.getSubNode(key);
if(subNode == null){//如果没有这个子节点
//初始化子节点;
subNode = new TrieNode();
tempNode.addSubNode(key,subNode);
}
//指向子节点,进入下一次循环
tempNode = subNode;
if(i == keyword.length() -1){
tempNode.setKeyWordEnd(true);
}
}
}
/**
* 过滤敏感词
* @param text 待过滤的文本
* @return 过滤后的文本
*/
public String filter(String text){
if(StringUtils.isBlank(text)){
return null;
}
//指针1 ,指向树
TrieNode tempNode = rootNode;
//指针2 指向字符串的慢指针,一直往前走
int begin = 0;
//指针3 指向字符串的快指针,往后走检查,如果不是就归位。
int position = 0;
//结果
StringBuilder result = new StringBuilder();
while(position < text.length()){
char c = text.charAt(position);
//跳过符号
if(isSymbol(c)){
//若指针处于根节点。则将此符号计入结果,让指针向下走一步。
if(tempNode == rootNode){
result.append(c);
begin++;
}
//无论结构在开头或中间指针3都向下走一步。
position++;
continue;
}
tempNode = tempNode.getSubNode(c);
if(tempNode == null){
// 以begin为开头的字符串不存在敏感词
result.append(text.charAt(begin));
//进入下一个位置
position = ++begin;
// 重新指向根节点。
tempNode = rootNode;
}else if(tempNode.isKeyWordEnd()){
//发现了敏感词,将begin-posttion中的字符串替换
result.append(REPLACEMENT);
// 进入下一个位置。
begin = ++ position;
tempNode = rootNode;
}else{
//检查下一个字符
position++;
}
}
//将最后一批字符计入结构
result.append(text.substring(begin));
return result.toString();
}
//判断是否为符号,是的话返回true,不是的话返回false
public boolean isSymbol(Character c){
//!CharUtils.isAsciiAlphanumeric(c)判断合法字符
//c < 0x2E80 || c > 0x9fff 东亚文字的范围是0x2E80到0x9fff
return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9fff);
}
//定义一个内部类,作为前缀树的结构
private static class TrieNode{
//关键词结束的标识
private boolean isKeyWordEnd = false;
//子节点(key 代表下级的节点字符, value是下级节点)
private Map<Character, TrieNode> subNode = new HashMap<>();
public boolean isKeyWordEnd() {
return isKeyWordEnd;
}
public void setKeyWordEnd(boolean keyWordEnd) {
isKeyWordEnd = keyWordEnd;
}
//添加子节点
public void addSubNode(Character key,TrieNode subNode){
this.subNode.put(key,subNode);
}
//获取子节点
public TrieNode getSubNode(Character key){
return subNode.get(key);
}
}
}
发邮件功能
代码直接拷贝用即可
- 在
utils
包下新建一个MailUtil
工具类。
@Component
public class MailUtil {
@Value("${spring.mail.username}")
String from;
@Autowired
JavaMailSender mailSender;
//简单邮件
public void sendSimpleMail(String to, String subject, String content){
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from); //发件人
message.setTo(to);//收件人
message.setSubject(subject); //标题
message.setText(content); //文件内容
try {
mailSender.send(message);
System.out.println("简单邮件发送成功!");
} catch (Exception e){
System.out.println("发送简单邮件时发生异常!"+e);
}
}
//html格式邮件
public void sendHtmlMail(String to, String subject, String content){
MimeMessage message = mailSender.createMimeMessage();
try {
//true表示需要创建一个multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
mailSender.send(message);
System.out.println("html邮件发送成功!");
} catch (MessagingException e) {
System.out.println("发送html邮件时发生异常!"+e);
}
}
//带附件的邮件
public void sendAttachmentsMail(String to, String subject, String content, String filePath){
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
mailSender.send(message);
System.out.println("带附件的邮件已经发送。");
} catch (MessagingException e) {
System.out.println("发送带附件的邮件时发生异常!" + e);
}
}
//带静态资源的邮件
public void sendInlineResourceMail(String to, String subject, String content, String rscPath, String rscId){
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);
mailSender.send(message);
System.out.println("嵌入静态资源的邮件已经发送。");
} catch (MessagingException e) {
System.out.println("发送嵌入静态资源的邮件时发生异常!" + e);
}
}
}
我的思路是在controller直接发生邮件, 但此时父评论的信息并没有(它只在评论列表获取的service接口实现类才存在)。
所以为了获取父评论信息,直接mvc3层都新增一段新代码。
-
配置
application.yml
#邮件配置(在spring层级下的) mail: host: smtp.qq.com username: 邮箱 password: 授权码 default-encoding: utf-8 port: 25
-
在
dao
包下的CommentDao
接口,添加如下接口:
/*单独查询一条评论,查出父评论名(用于在controller发送邮件)-虽然在service中才会赋值父评论名,我们想提前用*/
Comment findByParentId(@Param("id")Long id);
- 在
mapper
目录下CommentDao.xml
文件中,添加如下sql:
<!--查询父评论(为了昵称),在controller提前用来发邮件-->
<select id="findByParentId" resultType="Comment">
select *
from t_comment where id=#{id};
</select>
- 在
service
包下的CommentService
添加接口:
Comment findByParentId(Long id);
在Impl
包下的CommentServiceImpl
接口实现类,添加:
@Override
public Comment findByParentId(Long id) {
return commentDao.findByParentId(id);
}
控制器
在controller
包下的CommentController
类中,注入上面这两个工具类,然后修改新增评论接口如下:
//新增评论(有session 就是 管理员(后台登录了),游客就没有,(发评论时就没有传session)所以也删除不了)
@PostMapping("/comments")
public String post(Comment comment, HttpSession session,Model model){
Long blogId = comment.getBlogId();
User user =(User)session.getAttribute("user");
if(user!=null){
comment.setAvatar(user.getAvatar());
comment.setAdminComment(true);
}else {
comment.setAvatar(avatar);
}
comment.setContent(sensitiveFilterUtil.filter(comment.getContent()));
//前端 传来的是 parentComment.id
if(comment.getParentComment().getId() !=null ){
//是评论
comment.setParentCommentId(comment.getParentComment().getId()); //到impl中 的展示才设置parentNickname
//是子评论
if(comment.getParentComment().getId()!=-1){
Comment parentComment = commentService.findByParentId(comment.getParentComment().getId());//获得父评论(目的为了它的昵称)
//发邮件回复给父评论
String rpl="亲爱的【"+parentComment.getNickname()+"】,你在【HXJ的博客】的评论:"+parentComment.getContent()+".收到了来自【"+
comment.getNickname()+"】的回复! 内容如下:\n\n";
mailUtil.sendSimpleMail(parentComment.getEmail(), subject, rpl+comment.getContent());
}
}
commentService.savaComment(comment);//新增评论
List<Comment> comments = commentService.listCommentByBlogId(blogId);//得到最新评论
//System.out.println(comments);
model.addAttribute("comments", comments);
return "blog :: commentList";
}
至此实现了博客详情内容,困难点是评论(笔者把oneStar原本的前端全部改为局部刷新,强迫症的我效果舒服一点。而且js改成如果session有用户信息,直接会在评论区的输入框中填入用户信息(使用了js内联操作,现学现卖的),不过这个人只能是我自己了。)
浏览后的效果示例图如下: