目录

文章详情

接口说明

编码实现

Controller控制层

Service业务逻辑层

前端测试

redis incr自增实现浏览量

Redis配置类

Redis工具类

Dao持久层准备

Mapper接口

Mapper.xml

Service业务逻辑层准备

ArticleService接口

ArticleServiceImpl实现类

监听类

AOP实现

前端测试


文章详情

接口说明

接口url:/articles/view/{id}

请求方式:POST

请求参数:

参数名称 参数类型 说明
id long 文章id(路径参数)

返回数据:

{success: true, code: 200, msg: "success",…}
code: 200
data: {id: "1405916999732707330", title: "SpringBoot入门案例", summary: "springboot入门案例", commentCounts: 0,…}
msg: "success"
success: true

编码实现

Controller控制层

ArticleController:

    /*** 根据文章id查询文章详情* @param articleId* @return*/@PostMapping("view/{id}")public Result findArticleById(@PathVariable("id") Long articleId){return articleService.findArticleById(articleId);}

Service业务逻辑层

ArticleService接口:

    /*** 文章详情* @param articleId* @return*/Result findArticleById(Long articleId);

ArticleServiceImpl实现类:

    @Overridepublic Result findArticleById(Long articleId) {Article article = articleMapper.selectById(articleId);ArticleVo articleVo = copy(article,true,true,true,true);//查看完文章了,新增阅读数,有没有问题呢?//查看完文章之后,本应该直接返回数据了,这时候做了一个更新操作,更新时加写锁,阻塞其他的读操作,性能就会比较低(没办法解决,增加阅读数必然要加锁)//更新增加了此次接口的耗时(考虑减少耗时)如果一旦更新出问题,不能影响查看操作//线程池   可以把更新操作扔到 线程池中去执行和主线程就不相关了//threadService.updateArticleViewCount(articleMapper, article);//在这里我们采用redis  incr自增实现return Result.success(articleVo);}

前端测试

redis incr自增实现浏览量

Redis配置类

package com.huing.blog.config;import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** Redis配置类** @Author huing* @Create 2022-07-06 16:49*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{/*** 配置自定义redisTemplate* @return*/@BeanRedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);// 设置值(value)的序列化采用Jackson2JsonRedisSerializer。redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// 设置键(key)的序列化采用StringRedisSerializer。redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}
}

Redis工具类

package com.huing.blog.utils;import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;/*** @Author huing* @Create 2022-07-06 16:41*/
@Component
public class RedisUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;private StringRedisTemplate stringRedisTemplate;public RedisUtil(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {this.redisTemplate = redisTemplate;this.stringRedisTemplate = stringRedisTemplate;}/*** 指定缓存失效时间* @param key 键* @param time 时间(秒)* @return*/public boolean expire(String key,long time){try {if(time>0){redisTemplate.expire(key, time, TimeUnit.SECONDS);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据key 获取过期时间* @param key 键 不能为null* @return 时间(秒) 返回0代表为永久有效*/public long getExpire(String key){return redisTemplate.getExpire(key,TimeUnit.SECONDS);}/*** 判断key是否存在* @param key 键* @return true 存在 false不存在*/public boolean hasKey(String key){try {return redisTemplate.hasKey(key);} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除缓存* @param key 可以传一个值 或多个*/@SuppressWarnings("unchecked")public void del(String ... key){if(key!=null&&key.length>0){if(key.length==1){redisTemplate.delete(key[0]);}else{redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));}}}//============================String=============================/*** 普通缓存获取* @param key 键* @return 值*/public Object get(String key){return key==null?null:redisTemplate.opsForValue().get(key);}/*** 普通缓存放入* @param key 键* @param value 值* @return true成功 false失败*/public boolean set(String key,Object value) {try {redisTemplate.opsForValue().set(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 普通缓存放入并设置时间* @param key 键* @param value 值* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期* @return true成功 false 失败*/public boolean set(String key,Object value,long time){try {if(time>0){redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);}else{set(key, value);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 递增* @param key 键* @param delta 要增加几(大于0)* @return*/public long incr(String key, long delta){if(delta<0){throw new RuntimeException("递增因子必须大于0");}return redisTemplate.opsForValue().increment(key, delta);}/*** 递减* @param key 键* @param delta 要减少几(小于0)* @return*/public long decr(String key, long delta){if(delta<0){throw new RuntimeException("递减因子必须大于0");}return redisTemplate.opsForValue().increment(key, -delta);}//================================Map=================================/*** HashGet* @param key 键 不能为null* @param item 项 不能为null* @return 值*/public Object hget(String key,String item){return redisTemplate.opsForHash().get(key, item);}/*** 获取hashKey对应的所有键值* @param key 键* @return 对应的多个键值*/public Map<Object,Object> hmget(String key){return redisTemplate.opsForHash().entries(key);}/*** HashSet* @param key 键* @param map 对应多个键值* @return true 成功 false 失败*/public boolean hmset(String key, Map<String,Object> map){try {redisTemplate.opsForHash().putAll(key, map);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** HashSet 并设置时间* @param key 键* @param map 对应多个键值* @param time 时间(秒)* @return true成功 false失败*/public boolean hmset(String key, Map<String,Object> map, long time){try {redisTemplate.opsForHash().putAll(key, map);if(time>0){expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建* @param key 键* @param item 项* @param value 值* @return true 成功 false失败*/public boolean hset(String key,String item,Object value) {try {redisTemplate.opsForHash().put(key, item, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 向一张hash表中放入数据,如果不存在将创建* @param key 键* @param item 项* @param value 值* @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间* @return true 成功 false失败*/public boolean hset(String key,String item,Object value,long time) {try {redisTemplate.opsForHash().put(key, item, value);if(time>0){expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 删除hash表中的值* @param key 键 不能为null* @param item 项 可以使多个 不能为null*/public void hdel(String key, Object... item){redisTemplate.opsForHash().delete(key,item);}/*** 判断hash表中是否有该项的值* @param key 键 不能为null* @param item 项 不能为null* @return true 存在 false不存在*/public boolean hHasKey(String key, String item){return redisTemplate.opsForHash().hasKey(key, item);}/*** hash递增 如果不存在,就会创建一个 并把新增后的值返回* @param key 键* @param item 项* @param by 要增加几(大于0)* @return*/public double hincr(String key, String item,double by){return redisTemplate.opsForHash().increment(key, item, by);}/*** hash递减* @param key 键* @param item 项* @param by 要减少记(小于0)* @return*/public double hdecr(String key, String item,double by){return redisTemplate.opsForHash().increment(key, item,-by);}//============================set=============================/*** 根据key获取Set中的所有值* @param key 键* @return*/public Set<Object> sGet(String key){try {return redisTemplate.opsForSet().members(key);} catch (Exception e) {e.printStackTrace();return null;}}/*** 根据value从一个set中查询,是否存在* @param key 键* @param value 值* @return true 存在 false不存在*/public boolean sHasKey(String key,Object value){try {return redisTemplate.opsForSet().isMember(key, value);} catch (Exception e) {e.printStackTrace();return false;}}/*** 将数据放入set缓存* @param key 键* @param values 值 可以是多个* @return 成功个数*/public long sSet(String key, Object...values) {try {return redisTemplate.opsForSet().add(key, values);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 将set数据放入缓存* @param key 键* @param time 时间(秒)* @param values 值 可以是多个* @return 成功个数*/public long sSetAndTime(String key,long time,Object...values) {try {Long count = redisTemplate.opsForSet().add(key, values);if(time>0) {expire(key, time);}return count;} catch (Exception e) {e.printStackTrace();return 0;}}/*** 获取set缓存的长度* @param key 键* @return*/public long sGetSetSize(String key){try {return redisTemplate.opsForSet().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 移除值为value的* @param key 键* @param values 值 可以是多个* @return 移除的个数*/public long setRemove(String key, Object ...values) {try {Long count = redisTemplate.opsForSet().remove(key, values);return count;} catch (Exception e) {e.printStackTrace();return 0;}}//===============================list=================================/*** 获取list缓存的内容* @param key 键* @param start 开始* @param end 结束  0 到 -1代表所有值* @return*/public List<Object> lGet(String key, long start, long end){try {return redisTemplate.opsForList().range(key, start, end);} catch (Exception e) {e.printStackTrace();return null;}}/*** 获取list缓存的长度* @param key 键* @return*/public long lGetListSize(String key){try {return redisTemplate.opsForList().size(key);} catch (Exception e) {e.printStackTrace();return 0;}}/*** 通过索引 获取list中的值* @param key 键* @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推* @return*/public Object lGetIndex(String key,long index){try {return redisTemplate.opsForList().index(key, index);} catch (Exception e) {e.printStackTrace();return null;}}/*** 将list放入缓存* @param key 键* @param value 值* @return*/public boolean lSet(String key, Object value) {try {redisTemplate.opsForList().rightPush(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* @param key 键* @param value 值* @param time 时间(秒)* @return*/public boolean lSet(String key, Object value, long time) {try {redisTemplate.opsForList().rightPush(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* @param key 键* @param value 值* @return*/public boolean lSet(String key, List<Object> value) {try {redisTemplate.opsForList().rightPushAll(key, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 将list放入缓存* @param key 键* @param value 值* @param time 时间(秒)* @return*/public boolean lSet(String key, List<Object> value, long time) {try {redisTemplate.opsForList().rightPushAll(key, value);if (time > 0) {expire(key, time);}return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 根据索引修改list中的某条数据* @param key 键* @param index 索引* @param value 值* @return*/public boolean lUpdateIndex(String key, long index,Object value) {try {redisTemplate.opsForList().set(key, index, value);return true;} catch (Exception e) {e.printStackTrace();return false;}}/*** 移除N个值为value* @param key 键* @param count 移除多少个* @param value 值* @return 移除的个数*/public long lRemove(String key,long count,Object value) {try {Long remove = redisTemplate.opsForList().remove(key, count, value);return remove;} catch (Exception e) {e.printStackTrace();return 0;}}/*** 模糊查询获取key值* @param pattern* @return*/public Set keys(String pattern){return redisTemplate.keys(pattern);}/*** 使用Redis的消息队列* @param channel* @param message 消息内容*/public void convertAndSend(String channel, Object message){redisTemplate.convertAndSend(channel,message);}/*** 根据起始结束序号遍历Redis中的list* @param listKey* @param start  起始序号* @param end  结束序号* @return*/public List<Object> rangeList(String listKey, long start, long end) {//绑定操作BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);//查询数据return boundValueOperations.range(start, end);}/*** 弹出右边的值 --- 并且移除这个值* @param listKey*/public Object rifhtPop(String listKey){//绑定操作BoundListOperations<String, Object> boundValueOperations = redisTemplate.boundListOps(listKey);return boundValueOperations.rightPop();}/**----------------zSet相关操作------------------*//*** 添加元素,有序集合是按照元素的score值由小到大排列** @param key* @param value* @param score* @return*/public Boolean zAdd(String key, String value, double score) {return stringRedisTemplate.opsForZSet().add(key, value, score);}public Long zAdd(String key, Set<ZSetOperations.TypedTuple<String>> values) {return stringRedisTemplate.opsForZSet().add(key, values);}public Long zRemove(String key, Object... values) {return stringRedisTemplate.opsForZSet().remove(key, values);}/*** 增加元素的score值,并返回增加后的值* @return*/public Double zIncrementScore(String key, String value, double delta) {return stringRedisTemplate.opsForZSet().incrementScore(key, value, delta);}/*** 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列* @param key* @param value* @return*/public Long zRank(String key, Object value) {return stringRedisTemplate.opsForZSet().rank(key, value);}/*** 返回元素在集合的排名,按元素的score值由大到小排列* @param key* @param value* @return*/public Long zReverseRank(String key, Object value) {return stringRedisTemplate.opsForZSet().reverseRank(key ,value);}/*** 获取集合的元素, 从小到大排序* @param key* @param start* @param end* @return*/public Set<String> zRange(String key, long start, long end) {return stringRedisTemplate.opsForZSet().range(key, start, end);}/*** 获取集合元素, 并且把score值也获取*/public Set<ZSetOperations.TypedTuple<String>> zRangeWithScores(String key, long start, long end) {return stringRedisTemplate.opsForZSet().rangeWithScores(key, start, end);}/*** 根据Score值查询集合元素* @param key* @param min* @param max* @return*/public Set<String> zRangeByScore(String key, double min, double max) {return stringRedisTemplate.opsForZSet().rangeByScore(key, min, max);}/*** 根据Score值查询集合元素, 从小到大排序* @param key* @param min* @param max* @return*/public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double min, double max) {return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);}public Set<ZSetOperations.TypedTuple<String>> zRangeByScoreWithScores(String key, double min, double max, long start, long end) {return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max, start, end);}/*** 获取集合的元素, 从大到小排序* @param key* @param start* @param end* @return*/public Set<String> zReverseRange(String key, long start, long end) {return stringRedisTemplate.opsForZSet().reverseRange(key, start, end);}/*** 获取集合的元素, 从大到小排序, 并返回score值* @param key* @param start* @param end* @return*/public Set<ZSetOperations.TypedTuple<String>> zReverseRangeWithScores(String key, long start, long end) {return stringRedisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);}
}

Dao持久层准备

Mapper接口

ArticleMapper:

    /*** 更新(浏览量,评论数)* @param article* @return*/int updateNumById(Article article);

Mapper.xml

ArticleMapper.xml:

    <!--        int updateNumById(Article article);--><update id="updateNumById">update ms_article<set><if test="viewCounts != null">view_counts = #{viewCounts},</if><if test="commentCounts != null">comment_counts = #{commentCounts}</if></set><where>id = #{id}</where></update>

Service业务逻辑层准备

ArticleService接口

    /*** 获取文章详情* @param articleId* @return*/Article getArticleById(Long articleId);/*** 获取所有文章详情* @return*/List<Article> findArticleAll();/*** 更新(浏览量,评论数)* @param article*/Boolean updateNumById(Article article);

ArticleServiceImpl实现类

    @Overridepublic Article getArticleById(Long articleId) {Article article = articleMapper.selectById(articleId);return article;}@Overridepublic List<Article> findArticleAll() {List<Article> articleList = articleMapper.selectList(null);return articleList;}@Overridepublic Boolean updateNumById(Article article) {return articleMapper.updateNumById(article) > 0;}

万事具备,接下来需要写一个监听类来实现Redis和MySQL数据库的数据交换。项目启动时,将MySQL数据库中的文章浏览量查询出来写入到Redis中。Servlet销毁时或者每隔多少秒就将Redis中的文章浏览量写入到MySQL数据库当中,这样就保证了不会因为服务关闭而导致数据丢失和数据不一致的情况。

监听类

ListenHandler:

package com.huing.blog.handler;import com.huing.blog.dao.pojo.Article;
import com.huing.blog.service.ArticleService;
import com.huing.blog.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.Set;/*** @Author huing* @Create 2022-07-06 16:59*/
@Slf4j
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@EnableScheduling
public class ListenHandler {@Autowiredprivate ArticleService articleService;@Autowiredprivate RedisUtil redisUtil;private static final String VIEW_KEY = "viewNum";           //浏览量private static final String COMMENT_KEY = "commentNum";     //评论数//@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。//通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序://Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)@PostConstructpublic void init(){log.info("数据初始化开始(浏览量,评论数)...");List<Article> articleList = articleService.findArticleAll();for (Article article : articleList) {//将浏览量、点赞数和评论数写入redisredisUtil.zAdd(VIEW_KEY,article.getId().toString(),article.getViewCounts());redisUtil.zAdd(COMMENT_KEY,article.getId().toString(),article.getCommentCounts());}log.info("数据(浏览量,评论数)已写入redis...");}//PreDestroy()方法在destroy()方法执行执行之后执行@PreDestroypublic void afterDestroy() {log.info("开始关闭...");//将redis中的数据写入数据库Set<ZSetOperations.TypedTuple<String>> viewNum = redisUtil.zReverseRangeWithScores("viewNum", 0, -1);Set<ZSetOperations.TypedTuple<String>> commentNum = redisUtil.zReverseRangeWithScores("commentNum", 0, -1);writeNum(viewNum, VIEW_KEY);writeNum(commentNum, COMMENT_KEY);log.info("redis写入数据库完毕");}@Scheduled(cron = "*/15 * * * * ?")         //每十五秒触发一次public void updateNum() {log.info("周期任务开始执行...");Set<ZSetOperations.TypedTuple<String>> viewNum = redisUtil.zReverseRangeWithScores("viewNum", 0, -1);writeNum(viewNum, VIEW_KEY);log.info("周期任务执行完毕,redis写入数据库完毕");}private void writeNum(Set<ZSetOperations.TypedTuple<String>> set, String fieldName) {set.forEach(item -> {Long id = Long.valueOf(item.getValue());Integer num = item.getScore().intValue();Article article = articleService.getArticleById(id);switch (fieldName) {case VIEW_KEY:article.setViewCounts(num);break;case COMMENT_KEY:article.setCommentCounts(num);break;default:return;}//更新数据库articleService.updateNumById(article);log.info("{} 更新完毕", fieldName);});}
}

最后我们利用AOP,在用户请求Controller查看文章详情接口后实现查看的文章浏览量加一

AOP实现

MyAspect:

package com.huing.blog.common;import com.huing.blog.service.ArticleService;
import com.huing.blog.utils.RedisUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** @Author huing* @Create 2022-07-06 17:46*/
//指定为切面类
@Aspect
@Component
public class MyAspect {@Autowiredprivate ArticleService articleService;@Autowiredprivate RedisUtil redisUtil;//定义一个名为"myPointCut()"的切面,getById()这个方法中@Pointcut("execution(public * com.huing.blog.controller.ArticleController.findArticleById(..))")public void myPointCut() { }//在这个方法执行后@After("myPointCut()")public void doAfter(JoinPoint joinPoint) throws Throwable {Object[] objs = joinPoint.getArgs();Long id = (Long) objs[0];//根据id更新浏览量redisUtil.zIncrementScore("viewNum", id.toString(), 1);}
}

前端测试

后端项目启动,控制台:

此时数据库id为“1”文章:

请求对应接口

控制台:

数据库:

在线博客系统——文章详情(redis incr自增实现增加阅读数和评论数)相关推荐

  1. php博客系统 加载评论,Yii实现单用户博客系统文章详情页插入评论表单的方法...

    本文实例讲述了Yii实现单用户博客系统文章详情页插入评论表单的方法.分享给大家供大家参考,具体如下: action部分: function test($objs) { $objs->var=10 ...

  2. [java手把手教程][第二季]java后端博客系统文章系统——No10

    项目github地址:github.com/pc859107393- 实时项目同步的地址是国内的码云:git.oschina.net/859107393/m- 我的简书首页是:www.jianshu. ...

  3. springboot+vue练手级项目,真实的在线博客系统

    文章目录 spring boot 练手实战项目说明 基础知识 面试准备 1. 工程搭建 1.1 新建maven工程 1.1.2遇到的bug 1.2 配置 1.3 启动类 2. 首页-文章列表 2.1 ...

  4. java网络文章博客抓取系统_java 后端博客系统文章系统——No3

    工具 IDE为idea16* JDK环境为1.8 gradle构建,版本:2.14.1 Mysql版本为5.5.27 Tomcat版本为7.0.52 流程图绘制(xmind) 建模分析软件PowerD ...

  5. ***博客系统文章的数据库存储方式

    在通常的博客系统中,我们发表文章的时候,在数据库中存储的一般不仅仅是文章的文字,还包括文章的样式,而且很多时候都是所见即所得的效果.这就要求我们以html+文字这样存进数据库中,通过查找资料,可以用专 ...

  6. 博客系统文章的数据库存储方式

    在通常的博客系统中,我们发表文章的时候,在数据库中存储的一般不仅仅是文章的文字,还包括文章的样式,而且很多时候都是所见即所得的效果.这就要求我们以html+文字这样存进数据库中,通过查找资料,可以用专 ...

  7. 基于Spring Boot和Vue3的博客平台文章详情与评论功能实现

    在前面的教程中,我们已经实现了基于Spring Boot和Vue3的发布.编辑.删除文章功能以及文章列表与分页功能.本教程将引导您实现博客平台的文章详情与评论功能. 整个实现过程可以分为以下几个步骤: ...

  8. django博客项目-文章详情页功能

    文章详情页左侧边栏数据复用 文章详情页和个人站点的左侧边栏的内容格式都是一样的,从个人站点路由和文章详情路由进入到网页,侧边栏显示的内容是一模一样,解决方案: 方案一: 写一个home_site.ht ...

  9. 在线博客系统——最热文章、最新文章

    目录 最热文章 接口说明 编码实现 配置类application.properties Controller控制层 Service业务逻辑层 前端测试 最新文章 接口说明 编码实现 Controlle ...

最新文章

  1. Swift里计数相关的小细节
  2. leftjoin多个on条件_MYSQL|为什么LEFT JOIN会这么慢?
  3. 辽宁交通高等专科学校计算机专业宿舍,辽宁省交通高等专科学校宿舍条件怎么样 有独立卫生间和空调吗...
  4. java离线语音识别_你家的油烟机,可以语音控制了吗?
  5. 微信版花呗将上线;苹果在华支持以旧换新;谷歌推出 Flutter1.9 | 极客头条
  6. Java 11:字符串类中的新方法
  7. android listview固定内容,Android ListView 列表分隔,条目中添加分类信息(文字,图片等)...
  8. mybaits.xml文件约束,头部
  9. 网络层(六)MAC地址与IP地址
  10. 数组分割 java_分割java数组
  11. 一分钟了解光纤、单模光纤、多模光纤
  12. 如何解决 Elasticsearch 中未分配的分片
  13. JAVA编程基本步骤
  14. vue页面报错: Uncaught ReferenceError: Login is not defined at HTMLButtonElement.onclick
  15. java如何删除一本图书_javaEE项目网上书城后台(如何删除一本书并且有提示)...
  16. 思科模拟器 --- 三层交换机的基本配置
  17. Task3 | HLM | 高收入个体更吝啬吗
  18. 基于springboot搭建的个人博客系统
  19. BlackSquid恶意软件分析:利用8个臭名昭著的漏洞攻击服务器,并投放挖矿恶意软件
  20. 自动阅读脚本root的好还是免root的好?

热门文章

  1. 【超全面】机器学习中的超参优化方法总结
  2. 第2章第26节:英文排版技巧:把英文字母排成一个圆圈 [PowerPoint精美幻灯片实战教程]
  3. linux 根目录结构
  4. linux-tomcat日志清理方案
  5. Linux 安全设置
  6. 如何通俗的理解函数的极限_函数的极限问题怎么解释更通俗易懂?初高中数学辅导...
  7. CNdeepdive 安装报错:deepdive Failed connect to raw.githubusercontent.com:443; Connection refused
  8. 用Raphael在网页中画圆环进度条
  9. KITTI数据集下载(百度云)
  10. python循环语句打印三角形_python循环输出三角形图案的例子