文章目录

  • 1. 热帖排行
    • 1.1 统计发生分数变化帖子
      • 1.1.1 RedisKeyUtil
      • 1.1.2 DiscussPostController
      • 1.1.3 CommentController
      • 1.1.4 LikeController
    • 1.2 定时任务
      • 1.2.1 PostScoreRefreshJob
      • 1.2.2 QuartzConfig
    • 1.3 页面展现
      • 1.3.1 DiscussPostMapper
      • 1.3.2 discusspost-mapper.xml
      • 1.3.3 HomeController
      • 1.3.4 index.html
  • 2. 生成长图
    • 2.1 固定命令调用
    • 2.2 application.properties
    • 2.3 WkConfig
    • 2.4 模拟开发分享功能
      • 2.4.1 ShareController.share
      • 2.4.2 EventConsumer
      • 2.4.3 ShareController.getShareImage
  • 3. 将文件上传至云服务器
    • 3.1 引入依赖
    • 3.2 配置
    • 3.3 客户端上传:上传头像
      • 3.3.1 UserController
      • 3.3.2 setting.html
      • 3.3.3 setting.js
    • 3.4 服务端直传:上传分享图片
      • 3.4.1 ShareController
      • 3.4.2 handleShareMessage
  • 4. 性能优化
    • 4.1 本地缓存和分布式缓存
    • 4.2 多级缓存
    • 4.3 优化热门帖子列表
      • 4.3.1 pom.xml
      • 4.3.2 application.properties
      • 4.3.4 DiscussPostService
      • 4.3.5 CaffeineTests
      • 4.3.6 jmeter压力测试

1. 热帖排行

1.1 统计发生分数变化帖子

把一段时间内分数发生变化的帖子放入一个set

1.1.1 RedisKeyUtil

  1. 定义前缀
private static final String PREFIX_POST = "post";
  1. 添加方法统计帖子分数
// 帖子分数
public static String getPostScoreKey() {return PREFIX_POST + SPLIT + "score";
}

1.1.2 DiscussPostController

  1. addDiscussPost新增帖子时做处理,把新增的帖子放入redis,不重复,无序,存入set
// 计算帖子分数
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, post.getId());
  1. 置顶不做处理,直接加到最上面
  2. setWonderful加精做处理
// 计算帖子分数
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, id);

1.1.3 CommentController

addComment添加评论时做处理

// 计算帖子分数
String redisKey = RedisKeyUtil.getPostScoreKey();
redisTemplate.opsForSet().add(redisKey, discussPostId);

1.1.4 LikeController

处理逻辑同上

if(entityType == ENTITY_TYPE_POST) {// 计算帖子分数String redisKey = RedisKeyUtil.getPostScoreKey();redisTemplate.opsForSet().add(redisKey, postId);
}

1.2 定时任务

1.2.1 PostScoreRefreshJob

  1. 打日志,养成习惯
  2. 注入redis,DiscussPostService,LikeService,ElasticsearchService
  3. 声明静态常量牛客纪元private static final Date epoch;
  4. 重写execute
    1. 取到上一步存的rediskey
    2. 如果没有数据变化,打个日志任务取消
    3. 否则开始刷新前刷新后都记日志
    4. 遍历刷新
  5. 刷新方法
    1. 得到帖子id
    2. 如果帖子不在了打个日志
    3. 更新帖子分数,同步es搜索数据(在相应的Mapper和Config里添加响应方法)
public class PostScoreRefreshJob implements Job, CommunityConstant {private static final Logger logger = LoggerFactory.getLogger(PostScoreRefreshJob.class);@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate DiscussPostService discussPostService;@Autowiredprivate LikeService likeService;@Autowiredprivate ElasticsearchService elasticsearchService;// 牛客纪元private static final Date epoch;static {try {epoch = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2014-08-01 00:00:00");} catch (ParseException e) {throw new RuntimeException("初始化牛客纪元失败!", e);}}@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {String redisKey = RedisKeyUtil.getPostScoreKey();BoundSetOperations operations = redisTemplate.boundSetOps(redisKey);if (operations.size() == 0) {logger.info("[任务取消] 没有需要刷新的帖子!");return;}logger.info("[任务开始] 正在刷新帖子分数: " + operations.size());while (operations.size() > 0) {this.refresh((Integer) operations.pop());}logger.info("[任务结束] 帖子分数刷新完毕!");}private void refresh(int postId) {DiscussPost post = discussPostService.findDiscussPostById(postId);if (post == null) {logger.error("该帖子不存在: id = " + postId);return;}// 是否精华boolean wonderful = post.getStatus() == 1;// 评论数量int commentCount = post.getCommentCount();// 点赞数量long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, postId);// 计算权重double w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2;// 分数 = 帖子权重 + 距离天数double score = Math.log10(Math.max(w, 1))+ (post.getCreateTime().getTime() - epoch.getTime()) / (1000 * 3600 * 24);// 更新帖子分数discussPostService.updateScore(postId, score);// 同步搜索数据post.setScore(score);elasticsearchService.saveDiscussPost(post);}}

1.2.2 QuartzConfig

  1. JobDetailFactoryBean
  2. SimpleTriggerFactoryBean
// 刷新帖子分数任务
@Bean
public JobDetailFactoryBean postScoreRefreshJobDetail() {JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();factoryBean.setJobClass(PostScoreRefreshJob.class);factoryBean.setName("postScoreRefreshJob");factoryBean.setGroup("communityJobGroup");factoryBean.setDurability(true);factoryBean.setRequestsRecovery(true);return factoryBean;
}@Bean
public SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail) {SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();factoryBean.setJobDetail(postScoreRefreshJobDetail);factoryBean.setName("postScoreRefreshTrigger");factoryBean.setGroup("communityTriggerGroup");factoryBean.setRepeatInterval(1000 * 60 * 5);factoryBean.setJobDataMap(new JobDataMap());return factoryBean;
}

1.3 页面展现

1.3.1 DiscussPostMapper

添加参数 orderMode,默认值是0按照原先的排,如果1就按照热度排

List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit, int orderMode);

1.3.2 discusspost-mapper.xml

完成下面修改后通过finduse把用到这个方法的地方都改一下

<select id="selectDiscussPosts" resultType="DiscussPost">select <include refid="selectFields"></include>from discuss_postwhere status != 2<if test="userId!=0">and user_id = #{userId}</if><if test="orderMode==0">order by type desc, create_time desc</if><if test="orderMode==1">order by type desc, score desc, create_time desc</if>limit #{offset}, #{limit}
</select>

1.3.3 HomeController

  1. 添加参数ordermode
  2. 第一次访问,还没有值,所以需要默认参数是0
  3. 在路径上拼上参数page.setPath("/index?orderMode=" + orderMode);
  4. 最后ordermode再装到模板,模板要用
@RequestMapping(path = "/index", method = RequestMethod.GET)
public String getIndexPage(Model model, Page page,@RequestParam(name = "orderMode", defaultValue = "0") int orderMode) {// 方法调用钱,SpringMVC会自动实例化Model和Page,并将Page注入Model.// 所以,在thymeleaf中可以直接访问Page对象中的数据.page.setRows(discussPostService.findDiscussPostRows(0));page.setPath("/index?orderMode=" + orderMode);List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit(), orderMode);List<Map<String, Object>> discussPosts = new ArrayList<>();if (list != null) {for (DiscussPost post : list) {Map<String, Object> map = new HashMap<>();map.put("post", post);User user = userService.findUserById(post.getUserId());map.put("user", user);long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId());map.put("likeCount", likeCount);discussPosts.add(map);}}model.addAttribute("discussPosts", discussPosts);model.addAttribute("orderMode", orderMode);return "/index";
}

1.3.4 index.html

  1. 修改超链接th:href="@{/index(orderMode=0)}"
  2. 动态显示页签th:class="|nav-link ${orderMode==0?'active':''}|"
<!-- 筛选条件 -->
<ul class="nav nav-tabs mb-3"><li class="nav-item"><a th:class="|nav-link ${orderMode==0?'active':''}|" th:href="@{/index(orderMode=0)}">最新</a></li><li class="nav-item"><a th:class="|nav-link ${orderMode==1?'active':''}|" th:href="@{/index(orderMode=1)}">最热</a></li>
</ul>

2. 生成长图

2.1 固定命令调用

package com.nowcoder.community;import java.io.IOException;public class WkTests {public static void main(String[] args) {String cmd = "d:/work/wkhtmltopdf/bin/wkhtmltoimage --quality 75  https://www.nowcoder.com d:/work/data/wk-images/3.png";try {Runtime.getRuntime().exec(cmd);System.out.println("ok.");} catch (IOException e) {e.printStackTrace();}}}

2.2 application.properties

配置文件定义命令和存放目录

# wk
wk.image.command=d:/work/wkhtmltopdf/bin/wkhtmltoimage
wk.image.storage=d:/work/data/wk-images

2.3 WkConfig

在服务启动时检查目录是否存在否则创建

package com.nowcoder.community.config;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;import javax.annotation.PostConstruct;
import java.io.File;@Configuration
public class WkConfig {private static final Logger logger = LoggerFactory.getLogger(WkConfig.class);@Value("${wk.image.storage}")private String wkImageStorage;@PostConstructpublic void init() {// 创建WK图片目录File file = new File(wkImageStorage);if (!file.exists()) {file.mkdir();logger.info("创建WK图片目录: " + wkImageStorage);}}}

2.4 模拟开发分享功能

2.4.1 ShareController.share

  1. logger
  2. 异步方式,注入kafka生产者
  3. 注入域名项目访问名,图片存放位置,
  4. share方法
    1. 传入参数为路径
    2. 文件名UUID随机生成
    3. 构建一个event,异步生成长图
    4. 返回访问路径
@RequestMapping(path = "/share", method = RequestMethod.GET)
@ResponseBody
public String share(String htmlUrl) {// 文件名String fileName = CommunityUtil.generateUUID();// 异步生成长图Event event = new Event().setTopic(TOPIC_SHARE).setData("htmlUrl", htmlUrl).setData("fileName", fileName).setData("suffix", ".png");eventProducer.fireEvent(event);// 返回访问路径map.put("shareUrl", domain + contextPath + "/share/image/" + fileName);return CommunityUtil.getJSONString(0, null, map);
}

2.4.2 EventConsumer

// 消费分享事件
@KafkaListener(topics = TOPIC_SHARE)
public void handleShareMessage(ConsumerRecord record) {if (record == null || record.value() == null) {logger.error("消息的内容为空!");return;}Event event = JSONObject.parseObject(record.value().toString(), Event.class);if (event == null) {logger.error("消息格式错误!");return;}String htmlUrl = (String) event.getData().get("htmlUrl");String fileName = (String) event.getData().get("fileName");String suffix = (String) event.getData().get("suffix");String cmd = wkImageCommand + " --quality 75 "+ htmlUrl + " " + wkImageStorage + "/" + fileName + suffix;try {Runtime.getRuntime().exec(cmd);logger.info("生成长图成功: " + cmd);} catch (IOException e) {logger.error("生成长图失败: " + e.getMessage());}// 启用定时器,监视该图片,一旦生成了,则上传至七牛云.UploadTask task = new UploadTask(fileName, suffix);Future future = taskScheduler.scheduleAtFixedRate(task, 500);task.setFuture(future);
}

2.4.3 ShareController.getShareImage

// 废弃
// 获取长图
@RequestMapping(path = "/share/image/{fileName}", method = RequestMethod.GET)
public void getShareImage(@PathVariable("fileName") String fileName, HttpServletResponse response) {if (StringUtils.isBlank(fileName)) {throw new IllegalArgumentException("文件名不能为空!");}response.setContentType("image/png");File file = new File(wkImageStorage + "/" + fileName + ".png");try {OutputStream os = response.getOutputStream();FileInputStream fis = new FileInputStream(file);byte[] buffer = new byte[1024];int b = 0;while ((b = fis.read(buffer)) != -1) {os.write(buffer, 0, b);}} catch (IOException e) {logger.error("获取长图失败: " + e.getMessage());}
}

3. 将文件上传至云服务器

3.1 引入依赖

<dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>7.2.23</version>
</dependency>

3.2 配置

  1. 密钥
  2. 空间名字
# qiniu
qiniu.key.access=6RA-Uus95ZT_1znMrCMD8BpqfjT-K7OKmQTfKB48
qiniu.key.secret=kPNnLFz2_tzztKUVpSLm0lYngtuHWyIq5LzTmLIL
qiniu.bucket.header.name=community_header
quniu.bucket.header.url=http://pvghrij81.bkt.clouddn.com
qiniu.bucket.share.name=community_share
qiniu.bucket.share.url=http://pvghvvuzm.bkt.clouddn.com

3.3 客户端上传:上传头像

3.3.1 UserController

  1. 注入属性
  2. 废弃原有uploadHeadergetHeader方法
  3. getSettingPage
    1. 上传文件名称
    2. 设置响应信息,存入StringMap对象
    3. 生成上传凭证
    4. 数据存入model
  4. updateHeaderUrl,更新头像路径
@Value("${qiniu.key.access}")
private String accessKey;@Value("${qiniu.key.secret}")
private String secretKey;@Value("${qiniu.bucket.header.name}")
private String headerBucketName;@Value("${quniu.bucket.header.url}")
private String headerBucketUrl;@LoginRequired
@RequestMapping(path = "/setting", method = RequestMethod.GET)
public String getSettingPage(Model model) {// 上传文件名称String fileName = CommunityUtil.generateUUID();// 设置响应信息StringMap policy = new StringMap();policy.put("returnBody", CommunityUtil.getJSONString(0));// 生成上传凭证Auth auth = Auth.create(accessKey, secretKey);String uploadToken = auth.uploadToken(headerBucketName, fileName, 3600, policy);model.addAttribute("uploadToken", uploadToken);model.addAttribute("fileName", fileName);return "/site/setting";
}// 更新头像路径
@RequestMapping(path = "/header/url", method = RequestMethod.POST)
@ResponseBody
public String updateHeaderUrl(String fileName) {if (StringUtils.isBlank(fileName)) {return CommunityUtil.getJSONString(1, "文件名不能为空!");}String url = headerBucketUrl + "/" + fileName;userService.updateHeader(hostHolder.getUser().getId(), url);return CommunityUtil.getJSONString(0);
}

3.3.2 setting.html

  1. 为了提交js添加idid="uploadForm"
  2. 补充两个hidden,uploadTokenfileName
<!--上传到七牛云-->
<form class="mt-5" id="uploadForm"><div class="form-group row mt-4"><label for="head-image" class="col-sm-2 col-form-label text-right">选择头像:</label><div class="col-sm-10"><div class="custom-file"><input type="hidden" name="token" th:value="${uploadToken}"><input type="hidden" name="key" th:value="${fileName}"><input type="file" class="custom-file-input" id="head-image" name="file" lang="es" required=""><label class="custom-file-label" for="head-image" data-browse="文件">选择一张图片</label><div class="invalid-feedback">该账号不存在!</div></div></div></div><div class="form-group row mt-4"><div class="col-sm-2"></div><div class="col-sm-10 text-center"><button type="submit" class="btn btn-info text-white form-control">立即上传</button></div></div>
</form><script th:src="@{/js/setting.js}"></script>

3.3.3 setting.js

  1. 页面加载完后给form定义事件,点击按钮触发upload
  2. upload()最后return false,事件到此为止
  3. upload() 使用$.ajax()功能完全
    1. url
    2. 提交方式post
    3. processData: false不转成字符串
    4. contentType: false
    5. data封装表单对象
$(function(){$("#uploadForm").submit(upload);
});function upload() {$.ajax({url: "http://upload-z1.qiniup.com",method: "post",processData: false,contentType: false,data: new FormData($("#uploadForm")[0]),success: function(data) {if(data && data.code == 0) {// 更新头像访问路径$.post(CONTEXT_PATH + "/user/header/url",{"fileName":$("input[name='key']").val()},function(data) {data = $.parseJSON(data);if(data.code == 0) {window.location.reload();} else {alert(data.msg);}});} else {alert("上传失败!");}}});return false;
}

3.4 服务端直传:上传分享图片

3.4.1 ShareController

访问路径更改

@Value("${qiniu.bucket.share.url}")
private String shareBucketUrl;@RequestMapping(path = "/share", method = RequestMethod.GET)
@ResponseBody
public String share(String htmlUrl) {// 文件名String fileName = CommunityUtil.generateUUID();// 异步生成长图Event event = new Event().setTopic(TOPIC_SHARE).setData("htmlUrl", htmlUrl).setData("fileName", fileName).setData("suffix", ".png");eventProducer.fireEvent(event);// 返回访问路径Map<String, Object> map = new HashMap<>();
//        map.put("shareUrl", domain + contextPath + "/share/image/" + fileName);map.put("shareUrl", shareBucketUrl + "/" + fileName);return CommunityUtil.getJSONString(0, null, map);
}

3.4.2 handleShareMessage

启用定时器,监视该图片,一旦生成了,则上传至七牛云.

UploadTask方法:

  1. 启动任务的返回值Future可以用来停止
  2. 超时或超次数就失败
// 消费分享事件
@KafkaListener(topics = TOPIC_SHARE)
public void handleShareMessage(ConsumerRecord record) {if (record == null || record.value() == null) {logger.error("消息的内容为空!");return;}Event event = JSONObject.parseObject(record.value().toString(), Event.class);if (event == null) {logger.error("消息格式错误!");return;}String htmlUrl = (String) event.getData().get("htmlUrl");String fileName = (String) event.getData().get("fileName");String suffix = (String) event.getData().get("suffix");String cmd = wkImageCommand + " --quality 75 "+ htmlUrl + " " + wkImageStorage + "/" + fileName + suffix;try {Runtime.getRuntime().exec(cmd);logger.info("生成长图成功: " + cmd);} catch (IOException e) {logger.error("生成长图失败: " + e.getMessage());}// 启用定时器,监视该图片,一旦生成了,则上传至七牛云.UploadTask task = new UploadTask(fileName, suffix);Future future = taskScheduler.scheduleAtFixedRate(task, 500);task.setFuture(future);
}class UploadTask implements Runnable {// 文件名称private String fileName;// 文件后缀private String suffix;// 启动任务的返回值private Future future;// 开始时间private long startTime;// 上传次数private int uploadTimes;public UploadTask(String fileName, String suffix) {this.fileName = fileName;this.suffix = suffix;this.startTime = System.currentTimeMillis();}public void setFuture(Future future) {this.future = future;}@Overridepublic void run() {// 生成失败if (System.currentTimeMillis() - startTime > 30000) {logger.error("执行时间过长,终止任务:" + fileName);future.cancel(true);return;}// 上传失败if (uploadTimes >= 3) {logger.error("上传次数过多,终止任务:" + fileName);future.cancel(true);return;}String path = wkImageStorage + "/" + fileName + suffix;File file = new File(path);if (file.exists()) {logger.info(String.format("开始第%d次上传[%s].", ++uploadTimes, fileName));// 设置响应信息StringMap policy = new StringMap();policy.put("returnBody", CommunityUtil.getJSONString(0));// 生成上传凭证Auth auth = Auth.create(accessKey, secretKey);String uploadToken = auth.uploadToken(shareBucketName, fileName, 3600, policy);// 指定上传机房UploadManager manager = new UploadManager(new Configuration(Zone.zone1()));try {// 开始上传图片Response response = manager.put(path, fileName, uploadToken, null, "image/" + suffix, false);// 处理响应结果JSONObject json = JSONObject.parseObject(response.bodyString());if (json == null || json.get("code") == null || !json.get("code").toString().equals("0")) {logger.info(String.format("第%d次上传失败[%s].", uploadTimes, fileName));} else {logger.info(String.format("第%d次上传成功[%s].", uploadTimes, fileName));future.cancel(true);}} catch (QiniuException e) {logger.info(String.format("第%d次上传失败[%s].", uploadTimes, fileName));}} else {logger.info("等待图片生成[" + fileName + "].");}}
}

4. 性能优化

4.1 本地缓存和分布式缓存

本地缓存性能最好
分布式缓存比本地缓存性能低一点,主要是因为网络开销上

使用本地缓存,如果存的时用户信息相关数据,如果app从缓存中取用户相关数据没有,就无法登录。如果时热门帖子等信息,使用本地缓存没事,用户和数据本身不是有强关联可以本地缓存。

使用redis进行缓存就没有这种情况。redis可以跨服务器

4.2 多级缓存

4.3 优化热门帖子列表

spring整合缓存用一个缓存管理器管理所有缓存,不合适,不如单独使用一个缓存

4.3.1 pom.xml

<dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.7.0</version>
</dependency>

4.3.2 application.properties

  1. max-size:缓存空间缓存数量
  2. expire-seconds:过期时间,数据变化淘汰不合适,因为缓存的是一整页,有一个变了都淘汰不合适
# caffeine
caffeine.posts.max-size=15
caffeine.posts.expire-seconds=180

4.3.4 DiscussPostService

  1. 注入日志和参数
  2. LoadingCache:同步缓存,AsyncLoadingCache:异步缓存
  3. 声明帖子列表缓存和帖子总数缓存
  4. 初始化缓存
    1. .newBuilder()初始化
    2. .maximumSize最大缓存数
    3. .expireAfterWrite过期时间
    4. new CacheLoader接口的匿名实现
      1. load是缓存中没有的查询方法
      2. 如果key或长为0抛异常
      3. 切割参数得到两个数据
      4. (可以先访问redis,如果redis没有再访问数据库)
      5. 使用discussPostMapper中的查询方法查询
  5. 修改方法findDiscussPosts,如果是第一页且按热度排行就调取咖啡因缓存
  6. 修改方法findDiscussPostRows,如果首页访问就调取咖啡因
@PostConstruct
public void init() {// 初始化帖子列表缓存postListCache = Caffeine.newBuilder().maximumSize(maxSize).expireAfterWrite(expireSeconds, TimeUnit.SECONDS).build(new CacheLoader<String, List<DiscussPost>>() {@Nullable@Overridepublic List<DiscussPost> load(@NonNull String key) throws Exception {if (key == null || key.length() == 0) {throw new IllegalArgumentException("参数错误!");}String[] params = key.split(":");if (params == null || params.length != 2) {throw new IllegalArgumentException("参数错误!");}int offset = Integer.valueOf(params[0]);int limit = Integer.valueOf(params[1]);// 二级缓存: Redis -> mysqllogger.debug("load post list from DB.");return discussPostMapper.selectDiscussPosts(0, offset, limit, 1);}});// 初始化帖子总数缓存postRowsCache = Caffeine.newBuilder().maximumSize(maxSize).expireAfterWrite(expireSeconds, TimeUnit.SECONDS).build(new CacheLoader<Integer, Integer>() {@Nullable@Overridepublic Integer load(@NonNull Integer key) throws Exception {logger.debug("load post rows from DB.");return discussPostMapper.selectDiscussPostRows(key);}});
}

4.3.5 CaffeineTests

  1. 放30万条数据进数据库,方便压力测试
  2. 三次访问热门帖子列表,应该只打印一次日志
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class CaffeineTests {@Autowiredprivate DiscussPostService postService;@Testpublic void initDataForTest() {for (int i = 0; i < 300000; i++) {DiscussPost post = new DiscussPost();post.setUserId(111);post.setTitle("互联网求职暖春计划");post.setContent("今年的就业形势,确实不容乐观。过了个年,仿佛跳水一般,整个讨论区哀鸿遍野!19届真的没人要了吗?!18届被优化真的没有出路了吗?!大家的“哀嚎”与“悲惨遭遇”牵动了每日潜伏于讨论区的牛客小哥哥小姐姐们的心,于是牛客决定:是时候为大家做点什么了!为了帮助大家度过“寒冬”,牛客网特别联合60+家企业,开启互联网求职暖春计划,面向18届&19届,拯救0 offer!");post.setCreateTime(new Date());post.setScore(Math.random() * 2000);postService.addDiscussPost(post);}}@Testpublic void testCache() {System.out.println(postService.findDiscussPosts(0, 0, 10, 1));System.out.println(postService.findDiscussPosts(0, 0, 10, 1));System.out.println(postService.findDiscussPosts(0, 0, 10, 1));System.out.println(postService.findDiscussPosts(0, 0, 10, 0));}}

4.3.6 jmeter压力测试

牛客网项目——项目开发(十):热帖排行,生成长图,上传云服务器,性能优化相关推荐

  1. Java牛客网社区项目——知识点面试题

    Java牛客网社区项目--知识点&面试题 持续更新中(ง •̀_•́)ง 文章目录 Java牛客网社区项目--知识点&面试题 请简要介绍一下你的项目? 什么是Spring框架? 对Sp ...

  2. 2019牛客网高级项目

    本项目是一个基于SpringBoot的社区平台,实现了牛客网讨论区的功能.实现了邮箱注册.验证码登录.发帖.评论.私信.点赞.关注.统计网站访问次数等功能,数据库使用Mybatis.Redis,使用K ...

  3. 仿牛客网社区项目 全栈总结

    学习仿牛客网社区项目 代码&资源 各章节总结 第一章 第二章 第三章 第四章 第五章 第六章 第七章 第八章 争取让每个知识点都有链接可点 项目总结 网站架构图 常见面试题 MySQL Red ...

  4. webpack项目上传云服务器,webpack项目上传云服务器

    webpack项目上传云服务器 内容精选 换一换 为了避免不必要的费用产生,完成本示例体验后,可释放以下资源.资源释放后无法恢复,请谨慎操作.项目是使用DevCloud各服务的基础,删除项目可将该项目 ...

  5. 怎样把项目上传服务器,怎样将项目上传云服务器

    怎样将项目上传云服务器 内容精选 换一换 本节操作介绍本地Linux操作系统主机通过SCP向Linux云服务器传输文件的操作步骤.登录管理控制台,在ECS列表页面记录待上传文件的云服务器的弹性公网IP ...

  6. 牛客网中级项目学习笔记(一)

    牛客中级项目学习: Controller 解析web请求 Service 业务层 DAO(data access object)数据处理层 database 底层数据库 重定向 代码如下: @Requ ...

  7. SSM项目上传云服务器

    1.购买服务器 阿里云新人免费领取网站 腾讯云新人免费领取网站 领取后在控制台进行操作,可以发现公网ip地址与重置密码,之后记住修改服务器安全组规则,打开8080端口 2.连接服务器 开始菜单输入ms ...

  8. 2022-1-13牛客网C++项目—— 第二章 Linux 多进程开发(一)

    复习用的问题 进程和程序之间的关系是什么? 进程包含了哪些信息? 一.程序当中包含了一系列的信息,这些信息用于描述如何创建一个进程. 1)二进制格式标识:描述文件的格式,内核根据这个信息来解释文件中的 ...

  9. 牛客网实战项目详细到每一步(更新中)

    一技术架构 Spring Boot Spring Spring MVC MyBatics Redis Kafka Elasticsearch重点的提高性能的技术 Spring Security, Sp ...

  10. [牛客网中级项目]第四章用户注册登陆管理

    目录 1. 预习 1.1 拦截器: 1.2 MD5加密算法: 2. 内容: 3. 注册: 3.1 注册要实现的功能: 3.2 代码实现: 3.2.1 建立LoginCotroller.class 3. ...

最新文章

  1. pandas计算滑动窗口中的数值总和实战(Rolling Sum of a Pandas Column):计算单数据列滑动窗口中的数值总和(sum)、计算多数据列滑动窗口中的数值总和(sum)
  2. selenium2使用记录
  3. umi搭建react+antd项目(一)环境配置
  4. Windows核心编程 第七章 线程的调度、优先级和亲缘性(上)
  5. 空间句法软件_【特训营2:空间句法高阶班】GIS中的空间句法运用 丨城市数据派...
  6. DTO数据传输对象详解
  7. Apache Flink 零基础入门(一):基础概念解析
  8. 6,synchronized, lock 区别
  9. CV2摄像头人脸、人眼、微笑等检测
  10. 异步api_如何设计无服务器异步API
  11. Android系统(46)--- 基本常识
  12. 你认识的老罗又回来了!罗永浩:我的创造力才刚刚开始猥琐发育
  13. 无线传感器网络 | 期末复习知识点1
  14. Battle Zone 战争地带
  15. js调用html打印去掉页眉页脚,js 客户端打印html 并且去掉页眉、页脚的实例
  16. 笔记本的结构深入分析
  17. mysql 查询view_MySQL之视图(VIEW)
  18. 【牛客】网易2018校招数据分析师笔试解析
  19. python 读取csv文件转成字符串,python实现csv格式文件转为asc格式文件的方法
  20. 微信小游戏云开发 | 72小时极限编程体验

热门文章

  1. T检验和p-value含义及计算公式
  2. Collecting Coins
  3. 基于python的税额计算器
  4. 肿瘤信息学中一些专业词汇整理(不断更新ing)
  5. robot_localization 源码解析(1)ekf_localization_node
  6. html 打印页面不全,浏览器网页打印内容显示不全的解决方法教程[多图]
  7. 论文阅读笔记:Weakly-supervised Semantic Segmentation in Cityscape via Hyperspectral Image
  8. 算法训练 - 黑色星期五 有些西方人比较迷信,如果某个月的13号正好是星期五,他们就会觉得不太吉利,用古人的说法,就是“诸事不宜”。请你编写一个程序,统计出在某个特定的年份中,出现了多少次既是13号又
  9. java数组下标从几开始的_为什么数组角标从0开始
  10. 超声成像发射声场仿真(Ultrasound Emit Field Simulation)