Redis从入门到精通(十一)Redis实战(八)关注、共同关注和Feed流

news2024/11/26 0:52:07

↑↑↑请在文章开头处下载测试项目源代码↑↑↑

文章目录

    • 前言
    • 4.9 好友关注
      • 4.9.1 关注和取消关注
        • 4.9.1.1 创建表是实体类
        • 4.9.1.2 实现关注和取消关注
      • 4.9.2 共同关注
        • 4.9.2.1 改造关注和取消关注功能
        • 4.9.2.2 实现查询共同关注好友功能
      • 4.9.3 Feed流
        • 4.9.3.1 Feed流介绍及其实现模式
        • 4.9.3.2 Timeline模式的实现方案
        • 4.9.3.3 实现推动到粉丝邮件箱功能
        • 4.9.3.4 实现分页查询收件箱功能

前言

Redis实战系列文章:

Redis从入门到精通(四)Redis实战(一)短信登录
Redis从入门到精通(五)Redis实战(二)商户查询缓存
Redis从入门到精通(六)Redis实战(三)优惠券秒杀
Redis从入门到精通(七)Redis实战(四)库存超卖、一人一单与Redis分布式锁
Redis从入门到精通(八)Redis实战(五)分布式锁误删与原子性问题、Redisson
Redis从入门到精通(九)Redis实战(六)基于Redis队列实现异步秒杀下单
Redis从入门到精通(十)Redis实战(七)达人探店、点赞与点赞排行榜

4.9 好友关注

4.9.1 关注和取消关注

4.9.1.1 创建表是实体类

好友关注功能涉及的表有1个:

CREATE TABLE `tb_follow`  (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` BIGINT(20) UNSIGNED NOT NULL COMMENT '用户id',
  `follow_user_id` BIGINT(20) UNSIGNED NOT NULL COMMENT '关注的用户id',
  `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = COMPACT;

表对应的实体类如下:

// com.star.redis.dzdp.pojo.Follow

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("tb_follow")
public class Follow implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 关注的用户id
     */
    private Long followUserId;

    /**
     * 创建时间
     */
    private Date createTime;
}
4.9.1.2 实现关注和取消关注

创建实体类Follow对应的FollowController类-IFollowService接口-FollowServiceImpl实现类-FollowMapper类。详见测试项目代码。

在FollowController类中创建一个follow()方法,用于实现关注和取消关注功能。其接口文档及代码如下:

项目说明
请求方法POST
请求路径/follow/{id}/{isFollow}
请求参数id,Long,要关注的用户ID
isFollow,boolean,=true时表示关注,=false时表示取消关注
返回值
// com.star.redis.dzdp.controller.FollowController

@Slf4j
@RestController
@RequestMapping("/follow")
public class FollowController {

    @Resource
    private IFollowService followService;

    /**
     * 关注或者取消关注
     * @author hsgx
     * @since 2024/4/8 14:27
     * @param id 被关注的用户ID
     * @param isFollow true-关注 false-取消关注
     * @param request 
     * @return com.star.redis.dzdp.pojo.BaseResult
     */
    @PostMapping("/{id}/{isFollow}")
    public BaseResult follow(@PathVariable("id") Long id,
                             @PathVariable("isFollow") Boolean isFollow,
                             HttpServletRequest request) {
        return followService.follow(id, isFollow, (Long)request.getAttribute("userId"));
    }
}

接着在IFollowService接口中定义一个follow()方法,并在FollowServiceImpl实现类中具体实现,用于关注/取消关注用户:

// com.star.redis.dzdp.service.impl.FollowServiceImpl

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {

    @Override
    public BaseResult follow(Long followUserId, Boolean isFollow, Long userId) {
        log.info("userId = {}, followUserId = {}, idFollow = {}",
                userId, followUserId, isFollow);
        // 1.判断是关注还是取关
        if(isFollow) {
            // 2.关注,新增数据
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            save(follow);
            log.info("add follow done.");
        } else {
            // 3.取关,删除数据
            remove(new QueryWrapper<Follow>().eq("user_id", userId).eq("follow_user_id", followUserId));
            log.info("remove follow done.");
        }
        return BaseResult.setOk();
    }
}

最后进行功能测试。调用/follow/1/true接口关注ID=1的用户:

[http-nio-8081-exec-4] userId = 1012, followUserId = 1, idFollow = true
[http-nio-8081-exec-4] ==>  Preparing: INSERT INTO tb_follow ( user_id, follow_user_id ) VALUES ( ?, ? )
[http-nio-8081-exec-4] ==> Parameters: 1012(Long), 1(Long)
[http-nio-8081-exec-4] <==    Updates: 1
[http-nio-8081-exec-4] add follow done.

再次调用/follow/1/false接口取消关注ID=1的用户:

[http-nio-8081-exec-6] userId = 1012, followUserId = 1, idFollow = false
[http-nio-8081-exec-6] ==>  Preparing: DELETE FROM tb_follow WHERE (user_id = ? AND follow_user_id = ?)
[http-nio-8081-exec-6] ==> Parameters: 1012(Long), 1(Long)
[http-nio-8081-exec-6] <==    Updates: 1
[http-nio-8081-exec-6] remove follow done.

4.9.2 共同关注

要实现共同关注好友功能,可以利用Redis的Set集合:把用户1关注的人放在Key为follow:user:{id1}的Set集合中,把用户2关注的人放在Key为follow:user:{id2}的Set集合中,那么两个Set集合的交集就是两个用户共同关注的好友。

4.9.2.1 改造关注和取消关注功能

首先对FollowServiceImpl类的关注和取消关注方法follow()进行改造:新增关注时,除了写数据库,还要把关注用户的ID放入Redis的Set集合中;取消关注时,除了删数据库,还要把关注用户的ID从Set集合移除。

// com.star.redis.dzdp.service.impl.FollowServiceImpl

@Resource
private StringRedisTemplate stringRedisTemplate;

@Override
public BaseResult follow(Long followUserId, Boolean isFollow, Long userId) {
    log.info("userId = {}, followUserId = {}, idFollow = {}",
            userId, followUserId, isFollow);
    // 1.判断是关注还是取关
    if(isFollow) {
        // 2.关注,新增数据
        Follow follow = new Follow();
        follow.setUserId(userId);
        follow.setFollowUserId(followUserId);
        boolean save = save(follow);
        // 把关注用户的ID放入Set集合
        if(save) {
            stringRedisTemplate.opsForSet().add("follow:user:" + userId, followUserId.toString());
            log.info("add follow done.");
            return BaseResult.setOk("关注成功!");
        }
    } else {
        // 3.取关,删除数据
        boolean remove = remove(new QueryWrapper<Follow>().eq("user_id", userId)
                .eq("follow_user_id", followUserId));
        // 把关注用户的ID从Set集合移除
        if(remove) {
            stringRedisTemplate.opsForSet().remove("follow:user:" + userId, followUserId.toString());
            log.info("remove follow done.");
            return BaseResult.setOk("取消关注成功!");
        }
    }
    return BaseResult.setFail("操作失败!");
}

功能测试:

调用接口使当前用户(ID=1012)关注ID=[1,2,77]的三个用户:

此时Redis中的数据:

4.9.2.2 实现查询共同关注好友功能

在FollowController类中编写一个commons()方法,用于查询共同关注好友。其接口文档和代码如下:

项目说明
请求方法GET
请求路径/follow/commons/{id}
请求参数id,Long,与当前用户有共同关注好友的用户ID
返回值List<User>,共同关注好友列表
// com.star.redis.dzdp.controller.FollowController

/**
 * 查询共同关注好友
 * @author hsgx
 * @since 2024/4/8 17:15
 * @param id 与当前用户有共同关注好友的用户ID
 * @param request
 * @return com.star.redis.dzdp.pojo.BaseResult<java.util.List<com.star.redis.dzdp.pojo.User>>
 */
@GetMapping("/commons/{id}")
public BaseResult<List<User>> commons(@PathVariable("id") Long id, HttpServletRequest request) {
    return followService.queryCommonFollows(id, (Long)request.getAttribute("userId"));
}

然后在IFollowService接口定义一个queryCommonFollows()方法,并在FollowServiceImpl实现类中具体实现:

// com.star.redis.dzdp.service.impl.FollowServiceImpl

@Override
public BaseResult<List<User>> queryCommonFollows(Long followUserId, Long userId) {
    log.info("queryCommonFollows, followUserId = {}, userId = {}",
            followUserId, userId);
    // 1.求两个Set集合之间的交集
    String key1 = "follow:user:" + userId;
    String key2 = "follow:user:" + followUserId;
    Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
    // 2.判断有无交集
    if(intersect == null || intersect.isEmpty()) {
        log.info("获取两个Set集合的交集:key1 = {}, key2 = {}, 结果为空", key1, key2);
        return BaseResult.setOkWithData(Collections.emptyList());
    }
    // 3.有交集,解析ID集合
    List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
    log.info("获取两个Set集合的交集:key1 = {}, key2 = {}, 结果为:{}", key1, key2, ids);
    // 4.查询用户列表
    List<User> userList = userService.listByIds(ids);
    return BaseResult.setOkWithData(userList);
}

功能测试:

当前在Redis中,ID=1009的用户关注了ID=[1,2,4,5]的用户,ID=1012的用户关注了ID=[1,2,77]的用户,两者的共同好友ID=[1,2]:

调用/follow/commons/1009接口查询ID为1009和1012的用户之间的共同关注好友:

[http-nio-8081-exec-3] queryCommonFollows, followUserId = 1009, userId = 1012
[http-nio-8081-exec-3] 获取两个Set集合的交集:key1 = follow:user:1012, key2 = follow:user:1009, 结果为:[1, 2]
[http-nio-8081-exec-3] ==>  Preparing: SELECT id,phone,password,nick_name,icon,create_time,update_time FROM tb_user WHERE id IN ( ? , ? )
[http-nio-8081-exec-3] ==> Parameters: 1(Long), 2(Long)
[http-nio-8081-exec-3] <==      Total: 2

4.9.3 Feed流

4.9.3.1 Feed流介绍及其实现模式

所谓Feed流,直译过来叫“投喂”,也就是当我们关注了某个用户后,一旦这个用户发了动态,我们就能收到推送过来的数据。 例如微信订阅号的消息推送、小红书关注栏目的笔记推送等。

对于传统的模式,需要用户通过搜索引擎或者其他方式去查找想看的内容,例如百度;而对于新型的Feed流,不需要用户再去搜索,而是系统分析用户到底想看到什么,然后直接把相关内容推送给用户,从而节约用户的时间。

Feed流的实现有两种模式:

  • 1)Timeline

    不做内容筛选,简单地按照内容发布时间排序,例如朋友圈。 这种模式的优点在于,信息全面不会有缺失,并且实现相对简单;缺点在于,信息噪音较多,用户不一定感兴趣(例如朋友圈的营销号、微商等),内容获取效率低。

  • 2)智能排序

    利用智能算法屏蔽掉违规的、用户不感兴趣的内容,只推送用户感兴趣的内容来吸引用户。 这种模式的优点在于,投喂用户感兴趣的信息,用户粘度高;缺点在于,如果算法不够精准,可能起到反作用,而足够精准时容易让用户沉迷。

4.9.3.2 Timeline模式的实现方案

在本案例中,采用Timeline模式,只需要拿到关注的用户信息,再按照时间排序展示这些用户发布的动态信息。

Timeline模式的实现方案有三种:

  • 1)拉模式(读扩散)

    含义:当张三、李四和王五发了消息后,都会保存在自己的邮箱中。假设赵六关注了这三位,要读取信息时,他会读取自己的收件箱,此时系统会把他关注的人所发的信息全部都进行拉取,然后在进行排序

    优点:比较节约空间,因为赵六在读信息时,并没有重复读取,而且读取完之后可以进行清除。

    缺点:比较延迟,当用户要读取数据时才去关注的人里边读取数据,假设用户关注了大量的用户,那么此时就会拉取海量的内容,对服务器压力巨大。

  • 2)推模式(写扩散)

    含义:推模式是没有写邮箱的。当张三发了一个消息,系统会主动地把张三发的内容推送到他的粉丝收件箱中去。假设他的粉丝李四要读取消息,只需要读取自己地收件箱,而不用再去临时拉取。

    优点:时效快,不用临时拉取。

    缺点:内存压力大,假设一个大V发消息,很多人关注他,就会写很多分数据到粉丝那边去。

  • 3)推拉结合(读写混合)

    含义:推拉模式是一个折中的方案,兼具推和拉两种模式的优点。

    站在发消息这一端:如果是个普通的用户,那么就采用推模式,直接把消息写入到他的粉丝中去,因为普通用户的粉丝关注量比较小,所以这样做没有压力;如果是大V,则直接将消息写到自己的发件箱,然后再写一份到活跃粉丝收件箱里边去。

    站在收消息这一端:如果是活跃粉丝,那么大V和普通的用户发的消息都会直接写入到自己收件箱里边来;而如果是普通粉丝,由于上线不是很频繁,所以等上线时再从关注用户的收件箱里边去拉取信息。

4.9.3.3 实现推动到粉丝邮件箱功能
  • 1)需求分析

新增探店笔记时,在保存Blog到数据库的同时,推送到粉丝的收件箱。收件箱可以根据时间戳排序,使用Redis的SortedSet数据结构实现。

  • 2)代码实现

改造BlogController类的add()方法:

// com.star.redis.dzdp.controller.BlogController

@PostMapping("/add")
public BaseResult<Long> add(@RequestBody Blog blog, HttpServletRequest request) {
    log.info("add {}", blog.toString());
    // 1.设置探店笔记为登录用户的笔记
    Long userId = (Long) request.getAttribute("userId");
    blog.setUserId(userId);
    
    // // 2.保存探店笔记
    // blogService.save(blog);
    // // 3.返回
    // return BaseResult.setOkWithData(blog.getId());

    return blogService.addBlog(blog);
}

在IBlogService接口定义一个addBlog()方法,并在BlogServiceImpl实现类中具体实现:

// com.star.redis.dzdp.service.impl.BlogServiceImpl

@Override
public BaseResult<Long> addBlog(Blog blog) {
    log.info("addBlog: {}", blog.toString());
    // 1.保存探店笔记
    boolean save = save(blog);
    if(!save) {
        return BaseResult.setFail("发表失败!");
    }
    // 2.查询用户的所有粉丝 select * from t_follow where follow_user_id = ?
    List<Follow> followList = followService.query().eq("follow_user_id", blog.getUserId()).list();
    // 3.推送笔记ID给所有粉丝
    if(followList != null && !followList.isEmpty()) {
        log.info("当前用户粉丝数:{}", followList.size() );
        for (Follow follow : followList) {
            String key = "blog:feed:" + follow.getFollowUserId();
            long score = System.currentTimeMillis();
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), score);
            log.info("add to ZSet: {} {} {}", key, score, blog.getId());
        }
    } else {
        log.info("当前用户粉丝数:0");
    }
    // 4.返回ID
    return BaseResult.setOkWithData(blog.getId());
}
  • 3)功能测试

此时Redis中保存了数据:

4.9.3.4 实现分页查询收件箱功能
  • 1)需求分析

Feed流中的数据会不断更新,数据的下标也在不断变化,因此不能采用传统的分页模式。

传统方案:

如上图所示,假设在t1时刻读取第一页,此时page=1,size=5,那么拿到的就是10~6这几条记录;

假设t2时刻又发布了一条记录,在t3时刻读取第二页,传入的参数是page=2,size=5,那么读取到的第二页实际上是6~2。

因此,使用传统方案读取到了重复的数据。

Feed流的滚动分页:

如上图所示,Feed流的滚动分页需要记录每次操作后的最后一条数据的下标。

假设在t1时刻读取第一页,此时lastId=10, size=5,那么拿到的就是10-6这几条记录,此时将lastId赋值为6;

假设t2时刻又发布了一条记录,在t3时刻读取第二页,传入的参数是lastId=6,size=5,那么读取到的第二页实际上是5~1。

综上,可以总结出分页查询收件箱的逻辑:

  • 第一次查询时,由前端指定lastId和size参数,后续查询则使用后台返回结果作为条件;

  • 根据lastId和size参数查询数据,并找出这组数据中的最小ID,赋值给lastId并返回前端。

  • 在BlogServiceImpl类的addBlog()方法中,将当前时间戳作为score存入SortedSet中,因此这里的lastId就是最小时间戳。

  • 2)代码实现

接口文档如下:

项目说明
请求方法GET
请求路径/blog/feed
请求参数minTime:Long类型,上一次查询时的最小时间戳
size:Integer类型,每页条数
返回值List<Blog>:小于指定时间戳的笔记集合
minTime:本次查询的最小时间戳
size:每页条数

根据接口文档,首先定义一个实体类用于接收数据:

// com.star.redis.dzdp.pojo.BlogFeed

@Data
@EqualsAndHashCode(callSuper = false)
public class BlogFeed implements Serializable {

    private static final long serialVersionUID = 1L;
    /**
     * 小于指定时间戳的笔记集合
     */
    private List<Blog> blogList;
    /**
     * 本次查询的最小时间戳
     */
    private Long minTime;
    /**
     * 每页条数
     */
    private Integer size;
}

接着在BlogController类中创建一个feed()方法,用于实现分页查询关注用户的探店笔记列表:

// com.star.redis.dzdp.controller.BlogController

/**
 * 分页查询关注用户的探店笔记列表
 * @author hsgx
 * @since 2024/4/9 10:14
 * @param minTime 上一次查询的最小时间戳
 * @param size 每页条数
 * @param request 
 * @return com.star.redis.dzdp.pojo.BaseResult<com.star.redis.dzdp.pojo.BlogFeed>
 */
@GetMapping("/feed")
public BaseResult<BlogFeed> feed(Long minTime, Integer size, HttpServletRequest request) {
    Long userId = (Long) request.getAttribute("userId");
    return blogService.queryBlogFeed(minTime, size, userId);
}

然后在IBlogService接口定义一个queryBlogFeed()方法,并在BlogServiceImpl实现类中具体实现:

// com.star.redis.dzdp.service.impl.BlogServiceImpl

@Override
public BaseResult<BlogFeed> queryBlogFeed(Long minTime, Integer size, Long userId) {
    log.info("queryBlogFeed: minTime = {}, size = {}, userId = {}",
            minTime, size, userId);
    // 1.查询收件箱 ZREVRANGEBYSCORE key max min LIMIT offset count
    String key = "blog:feed:" + userId;
    Set<ZSetOperations.TypedTuple<String>> typedTuples =
            stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, minTime, 1, size);
    log.info("=> ZREVRANGEBYSCORE {} {} 0 LIMIT 1 {}", key, minTime, size);
    // 2.判断查询结果
    if(typedTuples == null || typedTuples.isEmpty()) {
        return BaseResult.setOk("查询结果为空");
    }
    // 3.解析数据
    List<Long> ids = new ArrayList<>(typedTuples.size());
    long newMinTime = 0;
    for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
        // 获取ID
        ids.add(Long.valueOf(typedTuple.getValue()));
        // 一直往下寻找最小时间
        newMinTime = typedTuple.getScore().longValue();
    }
    // 4.根据ID查询探店笔记
    String idStr = StrUtil.join(",", ids);
    List<Blog> blogList = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
    // 5.封装并返回
    BlogFeed blogFeed = new BlogFeed();
    blogFeed.setBlogList(blogList);
    blogFeed.setMinTime(newMinTime);
    blogFeed.setSize(size);
    return BaseResult.setOkWithData(blogFeed);
}
  • 3)功能测试

目前用户收件箱有10条数据:

调用/blog/feed?minTime=1712630253549&size=2接口,预计可以查询出id为[34,33]的两条笔记,实际返回结果如下:

再以上面返回结果为参数,调用/blog/feed?minTime=1712630245198&size=2接口,预计可以查询出id为[32,31]的两条笔记,实际返回结果如下:

可见,分页查询关注用户的探店笔记列表完成。

本节完,更多内容请查阅分类专栏:Redis从入门到精通

感兴趣的读者还可以查阅我的另外几个专栏:

  • SpringBoot源码解读与原理分析(已完结)
  • MyBatis3源码深度解析(已完结)
  • 再探Java为面试赋能(持续更新中…)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1582728.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯模拟赛题——魔法失灵了——toRefs()

目标 找到 index.html 中 TODO 部分&#xff0c;正确修复代码使 data 对象恢复响应式特性&#xff0c;即点击页面上的 - 与 按钮可以改变 value 的值。正确实现后效果如下&#xff1a; 题解 value是reactive 利用toRefs; toRefs() 函数可以将 reactive() 创建出来的响应式对象…

SSL证书有什么作用?

SSL证书的作用&#xff0c;简单来说就是给你的网站穿上“防护服”&#xff0c;保护用户数据安全&#xff0c;增强网站可信度。具体来说&#xff1a; 最重要的是可以帮您的网站实现HTTPS访问。 1. 加密传输&#xff1a; - 像给信件加封密锁一样&#xff0c;SSL证书让网站和用户…

python爬虫 爬取网页图片

http://t.csdnimg.cn/iQgHw //爬虫爬取图片其实是很简单的&#xff0c;但是大多数同学&#xff0c;可能对 url的设置一直有困惑&#xff08;这点本人也在研究&#xff09;&#xff0c;而本篇文章&#xff0c;对于想要爬取图片的小白简直是福利。你只需要将文章代码运行即可&am…

Matplotlib实现数据可视化

Matplotlib是Python中应用较为广泛的绘图工具之一&#xff0c;首次发布于2007年。它在函数设计上参考了MATLAB&#xff0c;因此名字以"Mat"开头&#xff0c;中间的"plot"代表绘图功能&#xff0c;结尾的"lib"表示它是一个集合。Matplotlib支持众…

CLion图像调试方法研究

在windows下有vistual studio,针对opencv有image watch,在ubuntu下用Clion插件Image Watch要收费,遂研究OpenImageDebugger与CLion问题及在Clion中调试方法 1.Open Image Debugger Open Image Debugger is a tool for visualizing in-memory buffers during debug sessions…

arcgis使用面shp文件裁剪线shp文件报错

水系数据裁剪&#xff0c;输出为空&#xff1a; ArcGIS必会的几个工具的应用 --提取、分割、融合、裁剪&#xff08;矢&#xff09;、合并、追加、镶嵌、裁剪&#xff08;栅&#xff09;、重采样_arcgis分割-CSDN博客 下面的方法都不行&#xff1a; ArcGIS Clip&#xff08;裁…

ZYNQ学习之Petalinux 设计流程实战

基本都是摘抄正点原子的文章&#xff1a;<领航者 ZYNQ 之嵌入式Linux 开发指南 V3.2.pdf&#xff0c;因初次学习&#xff0c;仅作学习摘录之用&#xff0c;有不懂之处后续会继续更新~ PetaLinux工具提供了在 Xilinx 处理系统上自定义、构建和部署嵌入式 Linux 解决方案所需的…

C语言进阶课程学习记录-第27课 - 数组的本质分析

C语言进阶课程学习记录-第27课 - 数组的本质分析 数组实验-数组元素个数的指定实验-数组地址与数组首元素地址实验-指针与数组地址的区别小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 数组 实验-数…

Android 13 aosp 预置三方应用apk

前提条件 编译启动 launch 选择了 sdk_pc_x86_64-userdebug 该版本 添加一个三方预置应用 Android_source/vendor/third_party/MdmLib/MdmLib.apk 配置三方应用对应的Android.mk Android_source/vendor/third_party/MdmLib/Android.mk LOCAL_PATH : $(call my-dir)include $(CL…

突如其来:OpenAI分家的Anthropic公司悄悄地释放出他们的秘密武器——Claude3

突如其来的消息&#xff0c;OpenAI分家的Anthropic公司悄悄地释放出他们的秘密武器——Claude3 这货居然在默默无闻中一举超越了GPT-4的地位。没发布会&#xff0c;没吹牛逼&#xff0c;就发了一帖子。 字少&#xff0c;事大。 Claude3独挡一面的推理能力 Anthropic推出了三款…

Linux--进程的概念(一)

目录 一、冯诺依曼体系结构二、操作系统2.1 什么是操作系统2.2 操作系统的意义 三、进程3.1 进程的基本概念3.2 描述进程——PCB3.3 进程和程序的区别3.4 task_struct-PCB的一种3.5 task_struct的内容分类 四、如何查看进程4.1 通过系统文件查看进程4.2 通过ps指令查看进程 五、…

nginxWebUI配置conf

在左边相应位置写入要修改的语句后&#xff0c;依次点击“校验文件”、“替换文件”、“重新装载”即可重启conf

前端mock数据——使用mockjs进行mock数据

前端mock数据——使用mockjs进行mock数据 一、安装二、mockjs的具体使用 一、安装 首选需要有nodejs环境安装mockjs&#xff1a;npm install mockjs 若出现像上图这样的错&#xff0c;则只需npm install mockjs --legacy-peer-deps即可 src下新建mock文件夹&#xff1a; mo…

HTML+CSS+JS实现京东首页[web课设代码+模块说明+效果图]

系列文章目录 文章目录 系列文章目录前言一、HTML结构图二、CSS部分代码图三、每部分效果图展示3.1 导航栏、头部搜索栏效果图3.2 中心区域商品展示效果图3.3 秒杀区和特惠区域效果图3.4 页脚&#xff08;底部导航、版权信息、技术支持等内容&#xff09;效果图 总结 前言 用时…

RAG 修炼手册|一文讲透 RAG 背后的技术

在之前的文章中《RAG 修炼手册&#xff5c;RAG敲响丧钟&#xff1f;大模型长上下文是否意味着向量检索不再重要》&#xff0c;我们已经介绍过 RAG 对于解决大模型幻觉问题的不可或缺性&#xff0c;也回顾了如何借助向量数据库提升 RAG 实战效果。 今天我们继续剖析 RAG&#xf…

统一用安卓Studio修改项目包名

可以逃跑&#xff0c;可以哭泣&#xff0c;但不可以放弃 --《鬼灭之刃》 修改项目包名 1&#xff09;选中项目中药修改的包名&#xff1a; 2)目结构显示方式&#xff0c;取消 Compact Middle Packages 选项&#xff1b; 3)右键要修改的包名&#xff0c;选择 Refactor —— Re…

企业焦急等待!湖北交安ABC证为何迟迟不开考?

企业焦急等待&#xff01;湖北交安ABC证为何迟迟不开考&#xff1f; 2024年湖北公路水运安全员ABC交安ABC证为何迟迟不开考 2024年湖北交安ABC预计考核大概时间是6月份&#xff0c;以往每年4月份就开始发布考核计划&#xff0c;年初交安ABC报名系统更新维护&#xff0c;一直没…

书生·浦语大模型第二期实战营第二课笔记和基础作业

来源&#xff1a; 作业要求:Homework - Demo 文档教程:轻松玩转书生浦语大模型趣味 Demo B站教程:轻松玩转书生浦语大模型趣味 Demo 1. 笔记 2.基础作业 2.1 作业要求 2.2 算力平台 2.3 新建demo目录&#xff0c;以及新建目录下的文件&#xff0c;下载模型参数 2.4 Intern…

异常处理过程和范例

目录 异常定义 异常关联 异常捕获与处理 查询 emp 数据表中工作岗位是 MANAGER 的员工信息&#xff0c;如果不存在这个员工&#xff0c;则输出“没有数据记录返回”&#xff0c;如果存在多个记录&#xff0c;则输出“返回数据记录超过一行” 更新数据表 emp 中部门编号&am…

Proxmox VE qm 方式备份虚拟机

前言 使用qm 备份Proxmox VE虚拟机&#xff0c;高效便捷。 登录Proxmox VE shell 执行备份操作 备份建议关闭虚拟机 qm shutdown 虚拟机名称号--compress 备份格式 0(代表vma格式) gzip lzo zstd--storage local&#xff08;备份的位置&#xff09;备份默认位置/var/lib/…