当前位置: 首页 > ops >正文

微服务的编程测评系统19-我的消息功能-竞赛排名功能

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 1. 我的消息功能
    • 1.1 业务分析
    • 1.2 消息发送
    • 1.3 消息列表展示
    • 1.4 前端开发
  • 2. 竞赛排名功能
  • 总结8


前言

1. 我的消息功能

1.1 业务分析

在这里插入图片描述
这个是站内通信

我的消息功能

站内信:网站内部的一种通信方式。
1.用户和用户之间的通信。(点对点)
2.管理员/系统 和 某个用户之间的通信。(点对点) ===》竞赛结果的通信消息
3. 管理员/系统 和 某个用户群(指的是满足某一条件的用户的群体)之间的通信。(点对面)

而且每个用户的消息都不一样
这个是给用户群发消息,福利信息每个用户的消息都一样—》点对面
因为每个用户消息不一样—》点对点

消息的话我们还要设计数据库

因为如果是消息群的话,那么就会把相同的消息发给多个人,所以我们可以设计两个表,一个消息内容表,一个是消息和用户的对应表,这样就不会相同消息发给多个人了

消息内容表
create table tb_message_text(
text_id  bigint unsigned NOT NULL COMMENT '消息内容id(主键)',
message_title varchar(10)  NOT NULL COMMENT '消息标题',
message_content varchar(200)  NOT NULL COMMENT '消息内容',
create_by    bigint unsigned not null  comment '创建人',
create_time  datetime not null comment '创建时间',
update_by    bigint unsigned  comment '更新人',
update_time  datetime comment '更新时间',
primary key (text_id)
)
# 消息表
create table tb_message(
message_id  bigint unsigned NOT NULL COMMENT '消息id(主键)',
text_id  bigint unsigned NOT NULL COMMENT '消息内容id(主键)',
send_id  bigint unsigned NOT NULL COMMENT '消息发送人id',
rec_id  bigint unsigned NOT NULL COMMENT '消息接收人id',
create_by    bigint unsigned not null  comment '创建人',
create_time  datetime not null comment '创建时间',
update_by    bigint unsigned  comment '更新人',
update_time  datetime comment '更新时间',
primary key (message_id)
);

消息如何产生—》竞赛结果通知消息–》凌晨统计排名,对当天结束的竞赛进行排名的统计—》产生消息—》竞赛结束时间不能超过晚上十点—>把消息存在数据库中,然后查询就可以了
然后消息也要存在redis中,不然还是太慢了
在这里插入图片描述
一个是user:message:list:userId,存储list,每个元素是消息id
还有一个是message:detail:textId,存储的是JSON,消息详情

1.2 消息发送

通过定时任务生成消息–.>存储到数据库和缓存中,获取消息列表的时候就可以从缓存中获取了,注意生成消息的时候只存在缓存中
缓存没有修改和删除

@Data
public class UserScore {private Long examId;private Long userId;private Integer score;
}

这个是新增加的类

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ck.job.mapper.user.UserSubmitMapper"><select id="selectUserScoreList" resultType="com.ck.job.domain.user.UserScore">SELECTuser_id,exam_id,sum(score) as scoreFROMtb_user_submit<where><foreach collection="examIdSet" open="exam_id in (" close=")" item="examId" separator="," > #{examId}</foreach></where>GROUP BY user_id , exam_idORDER BYscore DESC</select>
</mapper>

这个是使用的xml

@Service
public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> implements IMessageService{@Overridepublic boolean batchInsert(List<Message> messageList){return saveBatch(messageList);}
}
@Service
public class MessageTextServiceImpl extends ServiceImpl<MessageTextMapper, MessageText> implements IMessageTextService {@Overridepublic boolean batchInsert(List<MessageText> messageTextList){return saveBatch(messageTextList);}
}

这个是批量插入的service方法

    public static final Long SYSTEM_USER_ID = 1L;

因为createBy无法获取用户Id,所以我们提前要设置好

    public static final String USER_MESSAGE_LIST_USERID = "user:message:list:";public static final String MESSAGE_DETAIL_MESSAGEID = "message:detail:";

这个是存入缓存的结构

@Data
public class MessageCacheVO {private String messageTitle;private String messageContent;
}

这个是存入信息详细数据的类

最后展示定时器的代码

    @XxlJob("examResultHandler")public void examResultHandler(){log.info("*****examResultHandler:凌晨统计排名*****");//先从数据库中获取所有已经结束的竞赛列表LocalDateTime now = LocalDateTime.now();LocalDateTime minusDays = now.minusDays(1);//获取小于一天的时间List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>().select(Exam::getExamId,Exam::getTitle).ge(Exam::getEndTime, minusDays).le(Exam::getEndTime, now)//小于等于当前时间.eq(Exam::getStatus, Constants.TRUE));//已经发布)if(CollectionUtil.isEmpty(examList)){return;}Set<Long> examIdSet = examList.stream().map(Exam::getExamId).collect(Collectors.toSet());//然后根据examIdSet获取所有用户的分数List<UserScore> userScoreList = userSubmitMapper.selectUserScoreList(examIdSet);Map<Long, List<UserScore>> examIdUserScoreMap = userScoreList.stream().collect(Collectors.groupingBy(UserScore::getExamId));//按照examId分组,那么分数排名就已经OK了saveMessage(examList,examIdUserScoreMap);}private void saveMessage(List<Exam> examList, Map<Long, List<UserScore>> examIdUserScoreMap ) {List<Message> messageList = new ArrayList<>();//插入msg与用户对应信息数据库List<MessageText> messageTextList = new ArrayList<>();//插入数据库,msg详细信息for(Exam exam: examList){Long examId = exam.getExamId();List<UserScore> userScoreList = examIdUserScoreMap.get(examId);int userTotal = userScoreList.size();int rank  = 1;for (UserScore userScore : userScoreList){String msgTitle = exam.getTitle()+"——排名情况";String msgContent = "你参加的竞赛:"+ exam.getTitle()+",你的分数为:"+userScore.getScore()+",总人数:+"+userTotal +",你的排名为:"+rank;rank++;MessageText messageText = new MessageText();messageText.setMessageTitle(msgTitle);messageText.setMessageContent(msgContent);messageText.setCreateBy(Constants.SYSTEM_USER_ID);messageTextList.add(messageText);Message message = new Message();message.setSendId(Constants.SYSTEM_USER_ID);message.setRecId(userScore.getUserId());message.setCreateBy(Constants.SYSTEM_USER_ID);messageList.add(message);}}messageTextService.batchInsert(messageTextList);//给messageList添加messageIdMap<String, MessageCacheVO> messageCacheVOMap = new HashMap<>();//存入redis,信息详细数据for(int i=0;i<messageTextList.size();i++){MessageText messageText = messageTextList.get(i);Message message = messageList.get(i);message.setTextId(messageText.getTextId());MessageCacheVO messageCacheVO = new MessageCacheVO();messageCacheVO.setMessageContent(messageText.getMessageContent());messageCacheVO.setMessageTitle(messageText.getMessageTitle());String messageDetailKey = getMessageDetailKey(messageText.getTextId());messageCacheVOMap.put(messageDetailKey,messageCacheVO);}redisService.multiSet(messageCacheVOMap);messageService.batchInsert(messageList);//存入缓存,用户的信息列表---》那么就要把信息按照userId进行分组了Map<Long, List<Message>> userMsgMap = messageList.stream().collect(Collectors.groupingBy(Message::getRecId));Iterator<Map.Entry<Long, List<Message>>> iterator = userMsgMap.entrySet().iterator();while(iterator.hasNext()){Map.Entry<Long, List<Message>> entry = iterator.next();Long userId = entry.getKey();String userMessageListKey = getUserMessageListKey(userId);List<Message> userMessageList = entry.getValue();List<Long> userMsgIdList = userMessageList.stream().map(Message::getTextId).toList();redisService.rightPushAll(userMessageListKey,userMsgIdList);}
//        for (Map.Entry<Long, List<Message>> entry : userMsgMap.entrySet()) {
//            Long userId = entry.getKey();
//            String userMessageListKey = getUserMessageListKey(userId);
//            List<Message> userMessageList = entry.getValue();
//            List<Long> userMsgIdList = userMessageList.stream().map(Message::getTextId).toList();
//            redisService.rightPushAll(userMessageListKey, userMsgIdList);
//        }}private String getUserMessageListKey(Long userId) {return CacheConstants.USER_MESSAGE_LIST_USERID + userId;}private String getMessageDetailKey(Long messageTextId) {return CacheConstants.MESSAGE_DETAIL_MESSAGEID + messageTextId;}

我们使用的都是批量插入
在 Java 中,Iterator(迭代器)初始化后,其初始状态是指向集合中第一个元素的「前面」

1.3 消息列表展示

创建一个新的controller,UserMessageController

@RestController
@RequestMapping("/user/message")
@Tag(name = "C端用户信息接口")
@Slf4j
public class UserMessageController {@Autowiredprivate IUserMessageService userMessageService;@GetMapping("/list")@Operation(description = "获取用户接收到的信息")public TableDataInfo list(PageQueryDTO dto){log.info("获取用户接收到的信息,PageQueryDTO:{}", dto);return userMessageService.list(dto);}
}

直接拷贝以前获取竞赛列表的代码,然后改吧改吧

@Data
public class MessageCacheVO {private Long textId;private String messageTitle;private String messageContent;
}

这个类要完善一下,因为从数据库中可以查询出这个类,然后刷新缓存就要用到messageTextId,记得定时器存入缓存的时候,这个字段也要完善

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ck.friend.mapper.message.MessageTextMapper"><select id="selectUserMsgList" resultType="com.ck.friend.domain.message.vo.MessageCacheVO">SELECTtext_id,message_title,message_content,FROMtb_message mJOINtb_message_text tmONm.text_id = tm.text_id<where>m.rec_id = #{userId}</where>ORDER BYt.create_time DESC</select>
</mapper>

这个是根据userId查询它的所有的信息的xml

    @Overridepublic TableDataInfo list(PageQueryDTO dto) {Long userId= ThreadLocalUtil.get(Constants.USER_ID,Long.class);Long listSize = userMessageCacheManager.getListSize(userId);List<MessageCacheVO> list ;if(listSize==null||listSize==0){//说明缓存中没有数据,所以要先从数据库中获取数据,然后存入redisPageHelper.startPage(dto.getPageNum(), dto.getPageSize());list = messageTextMapper.selectUserMsgList(userId);userMessageCacheManager.refreshCache(userId);long total = new PageInfo<>(list).getTotal();return TableDataInfo.success(list, total);}else{//直接从redis中获取数据list = userMessageCacheManager.getUserMsgList(dto,userId);listSize = userMessageCacheManager.getListSize(userId);return TableDataInfo.success(list, listSize);}}

这是service代码
然后是userMessageCacheManager的代码

@Component
public class UserMessageCacheManager {@Autowiredprivate RedisService redisService;@Autowiredprivate MessageTextMapper messageTextMapper;public Long getListSize(Long userId) {String userMessageListKey = getUserMessageListKey(userId);return redisService.getListSize(userMessageListKey);}public void refreshCache(Long userId) {List<MessageCacheVO> messageCacheVOList = new ArrayList<>();messageCacheVOList = messageTextMapper.selectUserMsgList(userId);//没有分页List<Long> userMsgIdList = messageCacheVOList.stream().map(MessageCacheVO::getTextId).toList();if (CollectionUtil.isEmpty(messageCacheVOList)) {return;}redisService.rightPushAll(getUserMessageListKey(userId), userMsgIdList);      //刷新列表缓存//刷新信息详情缓存Map<String, MessageCacheVO> messageCacheVOMap = new HashMap<>();for (MessageCacheVO messageCacheVO : messageCacheVOList) {messageCacheVOMap.put(getMessageDetailKey(messageCacheVO.getTextId()),messageCacheVO);}redisService.multiSet(messageCacheVOMap);  //刷新详情缓存}public List<MessageCacheVO> getUserMsgList(PageQueryDTO dto, Long userId) {int start = (dto.getPageNum() - 1) * dto.getPageSize();int end = start + dto.getPageSize() - 1; //下标需要 -1String userMessageListKey = getUserMessageListKey(userId);List<Long> messageIdList = redisService.getCacheListByRange(userMessageListKey, start, end, Long.class);List<MessageCacheVO> messageCacheVOList = assembleExamVOList(messageIdList);//从缓存中加载详情if (CollectionUtil.isEmpty(messageCacheVOList)) {//说明redis中数据可能有问题 从数据库中查数据并且重新刷新缓存messageCacheVOList = getMessageVOListByDB(dto,userId); //从数据库中获取数据refreshCache(userId);}return messageCacheVOList;}private List<MessageCacheVO> getMessageVOListByDB(PageQueryDTO dto, Long userId) {PageHelper.startPage(dto.getPageNum(), dto.getPageSize());return messageTextMapper.selectUserMsgList(userId);}private List<MessageCacheVO> assembleExamVOList(List<Long> messageIdList) {if (CollectionUtil.isEmpty(messageIdList)) {//说明redis当中没数据 从数据库中查数据并且重新刷新缓存return null;}//拼接redis当中key的方法 并且将拼接好的key存储到一个list中List<String> detailKeyList = new ArrayList<>();for (Long messageTextId : messageIdList) {detailKeyList.add(getMessageDetailKey(messageTextId));}List<MessageCacheVO> messageCacheVOList = redisService.multiGet(detailKeyList, MessageCacheVO.class);CollUtil.removeNull(messageCacheVOList);if (CollectionUtil.isEmpty(messageCacheVOList) || messageCacheVOList.size() != messageIdList.size()) {//说明redis中数据有问题 从数据库中查数据并且重新刷新缓存return null;}return messageCacheVOList;}private String getUserMessageListKey(Long userId) {return CacheConstants.USER_MESSAGE_LIST_USERID + userId;}private String getMessageDetailKey(Long messageTextId) {return CacheConstants.MESSAGE_DETAIL_MESSAGEID + messageTextId;}
}

1.4 前端开发

创建文件UserMessage.vue

<template><div class="message-list"><div class="message-list-block"><div class="message-list-header"><span class="ms-title">我的消息</span><span class="message-list-back" @click="goBack()">返回</span></div><div class="mesage-list-content" v-for="(item, index) in messageList" :key="index"><img src="@/assets/message/notice.png" width="50px" class="image" /><div class="message-content"><div class="title-box"><div class="title">{{ item.messageTitle }}</div></div><div class="content">{{ item.messageContent }}</div></div><el-button class="mesage-button" type="text" @click.stop="handlerDelete(item)">删除</el-button></div><div class="message-pagination"><!-- 增加分页展示器 --><el-pagination background layout="total, sizes, prev, pager, next, jumper" :total="total"v-model:current-page="params.pageNum" v-model:page-size="params.pageSize":page-sizes="[5, 10, 15, 20]" @size-change="handleSizeChange"@current-change="handleCurrentChange" /></div></div></div>
</template><script setup>
import { getMessageListService } from "@/apis/message"
import router from "@/router"
import { reactive, ref } from "vue"const messageList = ref([]) //消息列表const total = ref(0)
const params = reactive({pageNum: 1,pageSize: 10,
})//消息列表
async function getMessageList() {const ref = await getMessageListService(params)messageList.value = ref.rowstotal.value = ref.total
}
getMessageList()const goBack = () => {router.go(-1)
}
// 分页
function handleSizeChange(newSize) {params.pageNum = 1getMessageList()
}function handleCurrentChange(newPage) {getMessageList()
}
</script>
import service from "@/utils/request";export function getMessageListService(params) {return service({url: "/user/message/list",method: "get",params,});
}

然后就可以测试了
在这里插入图片描述
我们创建三个用户来测试一下

在这里插入图片描述

    <select id="selectUserMsgList" resultType="com.ck.friend.domain.message.vo.MessageCacheVO">SELECTtm.text_id,tm.message_title,tm.message_contentFROMtb_message mJOINtb_message_text tmONm.text_id = tm.text_idWHEREm.rec_id = #{userId}ORDER BYm.create_time DESC</select>

然后发现xml文件有问题

修改一下

在这里插入图片描述
成功了

2. 竞赛排名功能

在这里插入图片描述
就是历史竞赛那里的排名功能
未完赛没有查看排名功能
在这里插入图片描述

排名和得分和用户id都有了

但是得分和排名还没有存储—》tb_user_exam有排名的得分字段
----》不用重新统计了–》直接获取—》存入数据库,在定时器的时候

然后还要存入缓存–》key为exam:rank:list:examId
value为userId?不是,第一我们是为了防止一个数据存储多份,所以才存id,但是这里的排名是不会存储多份的,因为不同竞赛的排名和分数是不一样的,而且排名和分数是不能修改的
所以value就是需要什么存什么–》json–>examRank,nickName,score,其中examRank和score在不同竞赛中一般是不同的
但是nickName是会重复的,而且用户修改nickname还要改redis—》所以可以存userId,然后由userId获取redis中的nickname

我们现在定时器那里,修改tb_user_exam表,完善score和exam_rank字段
然后往redis中存入排名数据

@Data
public class UserScore {private Long examId;private Long userId;private Integer score;private Integer examRank;
}

完善一下这个类,这个是可以直接存入数据库中,什么都有了

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ck.job.mapper.user.UserExamMapper"><update id="updateScoreAndExamRank"><foreach collection="userScoreList" item="item" separator=";">UPDATEtb_user_examSETscore = #{item.score}, exam_rank = #{item.examRank}WHEREexam_id = #{item.examId} AND user_id = #{item.userId}</foreach></update>
</mapper>

这个是修改数据库tb_user_exam的xml语句

    public static final String EXAM_RANK_LIST_EXAMID = "exam:rank:list:";
    private String getExamRankListKey(Long examId) {return CacheConstants.EXAM_RANK_LIST_EXAMID + examId;}

这个是redis的key
在savemessage方法中

        for(Exam exam: examList){Long examId = exam.getExamId();List<UserScore> userScoreList = examIdUserScoreMap.get(examId);int userTotal = userScoreList.size();int rank  = 1;for (UserScore userScore : userScoreList){String msgTitle = exam.getTitle()+"——排名情况";String msgContent = "你参加的竞赛:"+ exam.getTitle()+",你的分数为:"+userScore.getScore()+",总人数:"+userTotal +",你的排名为:"+rank;userScore.setExamRank(rank);rank++;MessageText messageText = new MessageText();messageText.setMessageTitle(msgTitle);messageText.setMessageContent(msgContent);messageText.setCreateBy(Constants.SYSTEM_USER_ID);messageTextList.add(messageText);Message message = new Message();message.setSendId(Constants.SYSTEM_USER_ID);message.setRecId(userScore.getUserId());message.setCreateBy(Constants.SYSTEM_USER_ID);messageList.add(message);}userExamMapper.updateScoreAndExamRank(userScoreList);redisService.rightPushAll(getExamRankListKey(examId),userScoreList);}

然后是创建查询的controller

    @GetMapping("/rank/list")public TableDataInfo rankList(RankQueryDTO rankQueryDTO){log.info("获取竞赛排名列表信息,rankQueryDTO:{}", rankQueryDTO);return examService.rankList(rankQueryDTO);}
@Data
public class RankQueryDTO extends PageQueryDTO {private Long examId;
}

然后service

@Data
public class ExamRankCacheVO {private String nickName;private Long userId;private Integer score;private Integer examRank;
}

这个类是从缓存中要获取的数据,其中只用获取后面三个字段,第一个字段是再次获取的,再次从redis中获取

    <select id="selectExamRankCacheVOList" resultType="com.ck.friend.domain.exam.vo.ExamRankCacheVO">SELECTuser_id,score,exam_rankFROMtb_user_examWHEREexam_id = #{examId}ORDER BYexam_rank </select>

这个是从数据库中获取排名信息的xml语句,根据examId

    @Overridepublic TableDataInfo rankList(RankQueryDTO rankQueryDTO) {Long listSize = examCacheManager.getExamRankListSize(rankQueryDTO.getExamId());List<ExamRankCacheVO> list;if(listSize==null||listSize==0){//说明缓存中没有数据,所以要先从数据库中获取数据,然后存入redisPageHelper.startPage(rankQueryDTO.getPageNum(), rankQueryDTO.getPageSize());list = examMapper.selectExamRankCacheVOList(rankQueryDTO.getExamId());examCacheManager.refreshExamRankListCache(rankQueryDTO.getExamId());listSize  = new PageInfo<>(list).getTotal();}else{//直接从redis中获取数据list = examCacheManager.getExamRankList(rankQueryDTO);}assembleExamRankList(list);return TableDataInfo.success(list, listSize);}private void assembleExamRankList(List<ExamRankCacheVO> list) {if(CollectionUtil.isEmpty(list)){return;}for (ExamRankCacheVO examRankCacheVO : list) {UserVO user = userCacheManager.getUserById(examRankCacheVO.getUserId());examRankCacheVO.setNickName(user.getNickName());}}

然后是examCacheManager中的方法

//竞赛排名public Long getExamRankListSize(Long examId) {return redisService.getListSize(getExamRankListKey(examId));}private String getExamRankListKey(Long examId) {return CacheConstants.EXAM_RANK_LIST_EXAMID + examId;}public void refreshExamRankListCache(Long examId) {//没有分页查询List<ExamRankCacheVO> examRankCacheVOList = examMapper.selectExamRankCacheVOList(examId);redisService.rightPushAll(getExamRankListKey(examId),examRankCacheVOList);}public List<ExamRankCacheVO> getExamRankList(RankQueryDTO rankQueryDTO) {int start = (rankQueryDTO.getPageNum() - 1) * rankQueryDTO.getPageSize();int end = start + rankQueryDTO.getPageSize() - 1; //下标需要 -1return redisService.getCacheListByRange(getExamRankListKey(rankQueryDTO.getExamId()),start,end,ExamRankCacheVO.class);}

这样就OK了

然后拷贝前端代码到exam.vue

  <el-dialog v-model="dialogVisible" width="600px" top="30vh" :show-close="true" :close-on-click-modal="false":close-on-press-escape="false" class="oj-login-dialog-centor" center><el-table :data="examRankList"><el-table-column label="排名" prop="examRank" /><el-table-column label="用户昵称" prop="nickName" /><el-table-column label="用户得分" prop="score" /></el-table><el-pagination class="range_page" background layout="total, sizes, prev, pager, next, jumper" :total="rankTotal"v-model:current-page="rankParams.pageNum" v-model:page-size="rankParams.pageSize" :page-sizes="[5, 10, 15, 20]"@size-change="handleRankSizeChange" @current-change="handleRankCurrentChange" /></el-dialog>

//竞赛排名const rankParams = reactive({examId:'',pageNum: 1,pageSize: 9,
})
const examRankList = ref([])
const rankTotal = ref(0)// 分页
function handleRankSizeChange(newSize) {rankParams.pageNum = 1getExamRankList()
}function handleRankCurrentChange(newPage) {getExamRankList()
}const dialogVisible = ref(false)async function getExamRankList() {const result = await getExamRankListService(rankParams)examRankList.value = result.rowsrankTotal.value = result.total
}function togglePopover(examId) {dialogVisible.value = truerankParams.examId = examIdgetExamRankList()
}
export function getExamRankListService(params) {return service({url: "/exam/rank/list",method: "get",params,});
}

然后就可以进行测试了
但是有一个要注意的点就是
我们的sql是不支持批量update的
要加上allowMultiQueries=true才可以
在这里插入图片描述

在这里插入图片描述

这样就成功了

总结8

http://www.xdnf.cn/news/19658.html

相关文章:

  • ChartView的基本使用
  • 【学Python自动化】 7.1 Python 与 Rust 输入输出对比学习笔记
  • Linux系统shell脚本(二)
  • 【Python - 基础 - 工具】解决pycharm“No Python interpreter configured for the project”问题
  • 机器学习入门,支持向量机
  • Vite + React + Tailwind v4 正确配置指南(避免掉进 v3 的老坑)
  • 为什么程序员总是发现不了自己的Bug?
  • Flutter 3.35.2 主题颜色设置指南
  • 使用 qmake 生成 Makefile,Makefile 转换为 Qt 的 .pro 文件
  • Redis核心数据类型解析——string篇
  • 基于YOLO8的番茄成熟度检测系统(数据集+源码+文章)
  • 2025年女性最实用的IT行业证书推荐:赋能职业发展的8大选择
  • Elasticsearch面试精讲 Day 5:倒排索引原理与实现
  • IoTDB对比传统数据库的五大核心优势
  • 深度估计:单目视觉实现车距测量和车速估计(含完整项目代码)
  • ubantu20.04 git clone 无法连接问题与解决方法
  • netstat用法
  • 别再让分散 IO 拖慢性能!struct iovec:高效处理聚集 IO 的底层利器
  • pikachu之 unsafe upfileupload (不安全的文件上传漏洞)
  • 力扣hot100:除自身以外数组的乘积(除法思路和左右前缀乘积)(238)
  • 毕业项目推荐:70-基于yolov8/yolov5/yolo11的苹果成熟度检测识别系统(Python+卷积神经网络)
  • 【无人机三维路径规划】基于遗传算法GA结合粒子群算法PSO无人机复杂环境避障三维路径规划(含GA和PSO对比)研究
  • 基于单片机醉酒驾驶检测系统/酒精检测/防疲劳驾驶设计
  • 基于单片机雏鸡孵化恒温系统/孵化环境检测系统设计
  • WPF启动窗体的三种方式
  • 【Day 42】Shell-expect和sed
  • 【python】lambda函数
  • Ubuntu 24.04 服务器配置MySQL 8.0.42 三节点集群(一主两从架构)安装部署配置教程
  • ubuntu部署MySQL服务
  • 数据结构——树(04二叉树,二叉搜索树专项,代码练习)