day10-好友關註

来源:https://www.cnblogs.com/liyuelian/archive/2023/05/03/17369936.html
-Advertisement-
Play Games

功能05-好友關註 6.功能05-好友關註 6.1關註和取關 6.1.1需求分析 在探店圖文的詳情頁面中,可以關註發佈筆記的作者: 關註和取關:點擊關註按鈕就會發出請求(上圖):http://127.0.0.1:8080/api/follow/2/true(2是關註的用戶id,最後面的參數可以是tr ...


功能05-好友關註

6.功能05-好友關註

6.1關註和取關

6.1.1需求分析

在探店圖文的詳情頁面中,可以關註發佈筆記的作者:

  1. 關註和取關:點擊關註按鈕就會發出請求(上圖):http://127.0.0.1:8080/api/follow/2/true(2是關註的用戶id,最後面的參數可以是true或者false,取決於當前的關註狀態)
  2. 查詢當前關註狀態:(下圖)http://127.0.0.1:8080/api/follow/or/not/2,返回兩種狀態:true(已關註)或者false(未關註)。關註和取關功能根據關註狀態來實現。
  3. 整體流程:進入頁面詳情的時候,會自動查詢當前用戶對blog博主的關註狀態,根據關註狀態來懸渲染“關註”或“已關註”按鈕,根據關註狀態,用戶可以做相對的“關註”或者“取關”操作。
image-20230502201711496

需求:基於該表數據結構,實現兩個介面:

  1. 關註和取關介面
  2. 判斷是否關註的介面

關註是User之間的關係,是博主與粉絲之間的關係,資料庫使用tb_follow來表示:

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 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT

註意:這裡要把主鍵改為自增長,簡化開發。

取關就是刪除該表的一條對應記錄,關註就是新增一條表對應的記錄。根據user_id和follow_user_id判斷關註狀態。

6.1.2代碼實現

(1)Follow.java,記錄用戶和博主的關係

package com.hmdp.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 關註關係(用戶和博主)
 *
 * @author 李
 * @version 1.0
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@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 LocalDateTime createTime;
}

(2)IFollowService.java,聲明方法介面

package com.hmdp.service;

import com.hmdp.dto.Result;
import com.hmdp.entity.Follow;
import com.baomidou.mybatisplus.extension.service.IService;

/**
 *  服務類
 *
 * @author 李
 * @version 1.0
 */
public interface IFollowService extends IService<Follow> {

    Result follow(Long followUserId, Boolean isFollow);

    Result isFollow(Long followUserId);
}

(3)FollowServiceImpl.java,實現方法

package com.hmdp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hmdp.dto.Result;
import com.hmdp.entity.Follow;
import com.hmdp.mapper.FollowMapper;
import com.hmdp.service.IFollowService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;

/**
 * 服務實現類
 *
 * @author 李
 * @version 1.0
 */
@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {

    //關註or取關功能
    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        if (UserHolder.getUser() == null) {
            return Result.fail("用戶未登錄");
        }
        //1.獲取登錄用戶
        Long userId = UserHolder.getUser().getId();
        //2.判斷是關註還是取關功能
        if (isFollow) {
            //3.關註,新增數據
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            save(follow);
        } else {
            //4.取關,刪除數據 delete form tb_follow where user_id = ? and follow_user_id = ?
            remove(new QueryWrapper<Follow>()
                    .eq("user_id", userId).eq("follow_user_id", followUserId));
        }
        return Result.ok();
    }

    //查詢當前用戶對某博主的關註狀態
    @Override
    public Result isFollow(Long followUserId) {
        if (UserHolder.getUser() == null) {
            return Result.fail("用戶未登錄");
        }
        //1.獲取登錄用戶
        Long userId = UserHolder.getUser().getId();
        //2.查詢是否關註 select count(*) from tb_follow where user_id =? and follow_user_id =?
        Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
        return Result.ok(count > 0);//如果count>0,表示已關註,返回true,反之,返回false
    }
}

(4)FollowController.java

package com.hmdp.controller;


import com.hmdp.dto.Result;
import com.hmdp.service.IFollowService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * 前端控制器
 *
 * @author 李
 * @version 1.0
 */
@RestController
@RequestMapping("/follow")
public class FollowController {
    @Resource
    private IFollowService followService;

    @PutMapping("/{id}/{isFollow}")
    public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFollow) {
        return followService.follow(followUserId, isFollow);
    }

    @GetMapping("/or/not/{id}")
    public Result follow(@PathVariable("id") Long followUserId) {
        return followService.isFollow(followUserId);
    }
}

(5)測試:重啟項目,進入博客詳情

點擊關註按鈕,提示關註成功:

image-20230502214936254

資料庫的tb_follow表添加一條新數據:

image-20230502215200169

再點擊取消關註按鈕,提示取消關註成功:

image-20230502215027846

資料庫tb_follow刪除該條數據:

image-20230502215242963

6.2共同關註

6.2.1博主首頁信息

點擊博主頭像,可以進入博主首頁,查看博主首頁的信息:包括博主信息,發佈的筆記,共同關註。

當點擊進入博主首頁的時候,將會發出兩個請求:

  1. 請求博主的用戶信息
  2. 請求博主發佈過的筆記信息
image-20230503141405082

當點擊共同關註的時候,就會發出請求查詢共同關註。

博主個人首頁依賴於兩個功能:

(1)在UserController.java中增加queryUserById()方法,用於請求博主的用戶信息

//根據id查詢用戶信息
@GetMapping("/{id}")
public Result queryUserById(@PathVariable("id") Long userId) {
    //查詢詳情
    User user = userService.getById(userId);
    if (user == null) {
        return Result.ok();
    }
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    //返回
    return Result.ok(userDTO);
}

(2)在BlogController.java中增加queryBlogByUserId方法,用於查詢最近10條筆記

//根據用戶id查詢blog
@GetMapping("/of/user")
public Result queryBlogByUserId(
        @RequestParam(value = "current", defaultValue = "1") Integer current,
        @RequestParam("id") Long id) {
    //根據用戶查詢
    Page<Blog> page = blogService.query()
            .eq("user_id", id)
            .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
    //獲取當前頁數據
    List<Blog> records = page.getRecords();
    return Result.ok(records);
}

(3)重啟項目,點擊某個博主首頁,顯示如下:

image-20230503143926514

6.2.2共同關註

需求:使用Redis合適的數據結構,實現共同關註功能。在博主個人頁面展示出當前用戶與博主的共同好友。

image-20230503144257628

我們可以使用Redis的Set結構,求多個set集合的交集:

SINTER key [key ...]
summary: Intersect multiple sets
since: 1.0.0

例如:

127.0.0.1:6379> SADD s1 m1 m2
(integer) 2
127.0.0.1:6379> SADD s2 m2 m3
(integer) 2
127.0.0.1:6379> SINTER s1 s2
1) "m2"

代碼實現

要使用set結構實現共同關註功能,首先將用戶關註的列表添加到redis的set集合中。

因此,我們需要修改之前的關註功能:在關註用戶的時候,不僅要記錄到資料庫中,還要將關註的用戶放到redis的set集合中(key為當前用戶id,value為當前用戶關註的所有用戶的id)。

(1)修改FollowServiceImpl.java的follow方法:

//關註or取關功能
@Override
public Result follow(Long followUserId, Boolean isFollow) {
    if (UserHolder.getUser() == null) {
        return Result.fail("用戶未登錄");
    }
    //1.獲取登錄用戶
    Long userId = UserHolder.getUser().getId();
    String key = "follows:" + userId;
    //2.判斷是關註還是取關功能
    if (isFollow) {
        //3.關註,新增數據
        Follow follow = new Follow();
        follow.setUserId(userId);
        follow.setFollowUserId(followUserId);
        boolean isSuccess = save(follow);
        if (isSuccess) {
            //將關註用戶的id,放入到redis的set集合中 sadd userId followerUserId
            stringRedisTemplate.opsForSet().add(key, followUserId.toString());
        }
    } else {
        //4.取關,刪除數據 delete form tb_follow where user_id = ? and follow_user_id = ?
        boolean isSuccess = remove(new QueryWrapper<Follow>()
                .eq("user_id", userId).eq("follow_user_id", followUserId));
        if (isSuccess) {
            //將關註用戶的id從set集合中移除
            stringRedisTemplate.opsForSet().remove(key, followUserId);
        }
    }
    return Result.ok();
}

(2)測試,使用一個用戶任意關註兩個博主後,redis中的數據:

image-20230503153247870

資料庫:

image-20230503153414091

重新登錄一個用戶,關註兩個博主:

image-20230503153737358

可以看到用戶1034和用戶1的共同關註為用戶2號,關註功能已經修改完畢,接下來實現共同關註功能。

(3)修改IFollowService介面,聲明followCommons方法

Result followCommons(Long id);

(4)修改FollowServiceImpl,實現followCommons()方法

@Override
public Result followCommons(Long id) {
    //1.獲取當前用戶
    Long userId = UserHolder.getUser().getId();
    String key = "follows:" + userId;
    String key2 = "follows:" + id;
    //2.求交集
    //結果為交集的所有用戶id
    Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
    if (intersect == null || intersect.isEmpty()) {
        //如果沒有交集
        return Result.ok(Collections.emptyList());
    }
    //3.解析id集合
    List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
    //4.查詢用戶
    List<UserDTO> users = userService.listByIds(ids)
            .stream()
            .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
            .collect(Collectors.toList());
    return Result.ok(users);
}

(5)修改FollowController,增加介面

@GetMapping("/common/{id}")
public Result followCommons(@PathVariable("id") Long id) {
    return followService.followCommons(id);
}

(6)測試:登錄id為1034的用戶,查看id為1的用戶主頁,頁面顯示兩個用戶共同關註為2號用戶。

image-20230503161044716

和redis中的數據一致,測試通過。

6.3關註推送

6.3.1Feed流的Timeline模式

當我們關註了用戶後,如果這個用戶發了動態,那麼我們應該把這些數據推送給用戶,這個需求又叫做Feed流。關註推送也叫做Feed流,直譯為投喂。為用戶持續的提供“沉浸式”的體驗,通過無限下拉刷新獲取新的信息。

對於傳統的模式的內容解鎖:需要用戶去通過搜索引擎或者是其他的方式去解鎖想要看的內容

image-20230503234805013

對於新型的Feed流的的效果:不需要用戶再去推送信息,而是系統分析用戶到底想要什麼,然後直接把內容推送給用戶,從而使用戶能夠更加的節約時間,不用主動去尋找。

image-20230503234755264

Feed流產品有兩種常見模式:

  • Timeline:不做內容篩選,簡單地按照內容發佈時間排序,常用於好友或關註。例如朋友圈
    • 優點:信息全面,不會有缺失。並且實現也相對簡單
    • 缺點:信息“噪音”較多,用戶不一定感興趣,內容獲取效率低
  • 智能排序:利用智能演算法屏蔽掉違規的、用戶不感興趣的內容。推送用戶感興趣的信息來吸引用戶
    • 優點:投喂用戶感興趣信息,用戶粘度很高,容易沉迷
    • 缺點:如果演算法不精準,可能會起到反作用

在本例中的個人主頁,是基於關註的好友來做Feed流的,因此採用的是Timeline的模式。

image-20230503164523130

該模式的實現方案有三種:

  • 拉模式
  • 推模式
  • 拉推結合模式

(1)拉模式:也叫讀擴散

image-20230503164858817

如上所示,每個博主都會發佈自己的筆記,視頻等數據,我們稱之為“消息”。每個人都會有一個發件箱,發送的消息會發到各自的發件箱里(消息除了數據本身,還會附帶一個時間戳)。

粉絲會有一個收件箱,這個收件箱平常是空的,只有當他要去讀消息的時候,才會從他關註的人的發件箱中,一個一個地拉取消息到自己的收件箱中,然後按照信息會按照時間排序,這樣他就可以按照時間去讀消息了。

拉模式只有在讀消息的時候才會拉取一個消息副本,因此拉模式又叫讀擴散

  • 優點:節省記憶體空間。因為收件箱讀完後就可以清理掉數據了,下一次要讀的時候再重新拉取消息,消息只保存在發件人的發件箱中,比較節省記憶體空間。
  • 缺點:延時高。用戶每次讀消息的時候,都要重新拉取發件箱的消息,然後做消息排序,這一系列動作耗時長,讀取的延遲比較高。

(2)推模式:也叫寫擴散。

image-20230503171740382

推模式沒有發件箱,博主發佈的消息會直接推送到他的所有粉絲的收件箱中,然後收件箱中的消息會按照時間進行排序。粉絲要讀消息的時候,就可以直接讀取已經排序好的消息,不需要臨時去拉取消息。

因此這種模式的優點是延時低,但缺點是記憶體占用高。

推模式模式在發佈消息的時候,通過直接寫到收件箱中來進行消息擴散,因此叫做寫擴散。

(3)推拉結合模式:也叫做讀寫混合,兼具推和拉兩種模式的優點。

image-20230503174933451
  • 在發件人角度來看

    • 如果是普通用戶,將採用寫擴散方式,直接把數據寫入到粉絲的收件箱中去,因為普通用戶的粉絲關註量比較小,所以這樣做沒有壓力
    • 如果是大V,則直接將數據先寫入到一份到發件箱裡邊去,然後再直接寫一份到活躍粉絲收件箱裡邊去
  • 在收件人角度來看

    • 如果是活躍粉絲,那麼大V和普通的人發的都會直接寫入到自己收件箱裡邊來
    • 如果是普通粉絲,由於他們上線不是很頻繁,所以等他們上線時,再從發件箱裡邊去拉信息

(4)三種模式對比

image-20230503175308998

這裡採取推模式。

6.3.2基於推模式實現關註推送

6.3.2.1需求分析
  1. 修改新增探店筆記的業務,在保存blog到資料庫的同時,推送到粉絲的收件箱
  2. 收件箱滿足可以根據時間戳排序,必須用Redis的數據結構實現
  3. 查詢收件箱數據時,可以實現分頁查詢

推模式是沒有發件箱的,用戶發佈的消息會直接推送消息到其粉絲收件箱中。在我們的業務中,消息就是探店筆記,每當有人發佈探店筆記時,我們就應該將筆記推送到其粉絲的收件箱。

之前實現的探店筆記功能:當用戶發佈探店筆記時,會將筆記的信息直接保存到資料庫中。為了實現新功能——消息推送,需要改造發佈探店筆記功能:保存筆記到資料庫的同時,還要將筆記推送到粉絲的收件箱中。為了節省記憶體空間,推送消息時,只需要推送一個blogId即可。粉絲去查詢筆記時,再根據id到資料庫中查詢筆記詳細信息。

綜上,關註推送業務的關鍵,就是:

  1. 實現收件箱

  2. 推送消息

最後是消息的分頁功能:

Redis中的list和zset結構都可以實現排序,list結構可以按照腳標查詢;zset結構沒有腳標,但是可以按照排名(根據score)進行查詢,也可以實現分頁。那麼應該如何選擇呢?

Feed流的分頁問題:

因為Feed流中的數據會不斷更新,所以數據的腳標也在變化,因此不能使用傳統的分頁模式:

如下,t1時刻有10條消息,它們按照時間排序。此時讀取的第一頁(假設為5條)為消息10-6。在t2時刻發佈了一條新消息,由於是按時間排序,此條消息會被放到最上面。這時,當讀取第二頁的時候,由於分頁是從當前的第一條消息(11)開始計算,因此讀取的就是6-2。

我們可以發現6被重覆讀取了兩次,分頁出現了混亂,因此Feed不能採用傳統的分頁模式。

image-20230503201318731Feed流的滾動分頁:

所謂的滾動分頁,其實就是記錄每次查詢的最後一條,下一次查詢以該位置作為起始位置。第一次查詢時,起始位置記為無窮。

如下,t1時讀取了第一頁,記錄lastId為6;t2時發佈一條新消息,經過排序放到了最新的位置。t3時刻讀取第二頁,由於記錄了lastId為6,就不會出現重覆讀取的問題。

image-20230503202817319

Feed流滾動分頁選用的數據結構:

回到之前的問題:list結構不支持這種滾動分頁,因為在list中查詢數據,只能按照腳標查詢(即只能實現傳統的分頁模式)。zset可以按照score值排序,但如果按照排名1,2,3,4....這樣查詢,就和list腳標查詢一樣了。

但是,zset還支持按照score值範圍進行查詢:在score中存放時間戳,每一次查詢時,記住最小的時間戳(即當前頁的最後一條消息),這樣就相當於記錄了lastId;下次查詢時,去找比這個時間戳小的消息,如此就可以實現滾動分頁了。

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
summary: Return a range of members in a sorted set, by score, with scores ordered from high to low
since: 2.2.0

因此,我們可以選擇zset結構作為實現Feed流分頁的底層結構。

(在數據有變化的情況下,儘量不要使用List這種隊列去做分頁,而是使用SortedSet,例如排行榜)

6.3.2.2代碼實現

需求1:修改新增探店筆記的業務,在保存blog到資料庫的同時,推送到粉絲的收件箱

(1)修改IBlogService.java,增加方法聲明

Result saveBlog(Blog blog);

(2)修改BlogServiceImpl.java,實現saveBlog()方法

@Override
public Result saveBlog(Blog blog) {
    //1.獲取登錄用戶
    UserDTO user = UserHolder.getUser();
    blog.setUserId(user.getId());
    //2.保存探店筆記
    boolean isSuccess = save(blog);
    if (!isSuccess) {
        return Result.fail("新增筆記失敗!");
    }
    //3.查詢筆記作者的所有粉絲 select * from tb_follow where follow_user_id=?
    List<Follow> follows = followService.query()
            .eq("follow_user_id", user.getId()).list();
    //4.推送筆記id給所有粉絲
    for (Follow follow : follows) {
        //4.1獲取粉絲id
        Long userId = follow.getUserId();
        //4.2推送
        String key = "feed:" + userId;
        //key為粉絲id,value為blogId,score為時間戳
        stringRedisTemplate.opsForZSet()
                .add(key, blog.getId().toString(), System.currentTimeMillis());
    }
    //5.返回筆記id
    return Result.ok(blog.getId());
}

(3)修改BlogController,添加介面

@PostMapping
public Result saveBlog(@RequestBody Blog blog) {
   return blogService.saveBlog(blog);
}

(4)測試

可以看到當前id=1和id=1034的用戶都關註了id=2的用戶

image-20230503225824199

我們登陸id=2的用戶,發佈一篇探店筆記:

image-20230503230147397 image-20230503230222065

在資料庫的tb_blog表中可以看到已經成功保存筆記數據:blogId=11

image-20230503230420878

在redis中,可以看到id=1和id=1034兩個用戶的收件箱中都分別收到了blogId=11的筆記推送(每個用戶都有一個收件箱):

image-20230503230722256 image-20230503230710798

測試通過。

需求2:在個人主頁的“關註”卡片中,查詢並展示推送的Blog信息,並實現分頁查詢

image-20230503225302848
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Pandas是一個開源的Python數據分析庫。 它提供了快速,靈活和富有表現力的數據結構,旨在使數據清洗和分析變得簡單而快速。 Pandas是基於NumPy數組構建的,因此它在許多NumPy函數上提供了直接的支持。它還提供了用於對錶格數據進行操作的數據結構,例如Series和DataFrame。 ...
  • 常量 const double PI = 3.1415926; 常量名命名一般使用大寫字母 枚舉類型 開發一個游戲,游戲角色有法師(Mage)、射手(Archer)、刺客(Assassin)、坦克(Tank)、鋪助(Support)、戰士(Warrior),等不同類型。 ❓如何存儲游戲角色 使用in ...
  • 接了個活, 寫個 WPF 上位機用 PCAN 或 ECAN 和單片機通訊, 讀取感測器數據. 程式邏輯是 : 選擇連接類型 PCAN / ECAN, 選擇波特率, 選擇通道號, 輸入查詢間隔, 連接設備. 然後開啟一個後臺線程迴圈發送讀取指令逐個讀取感測器數據. 使用 PCAN 時, 連接和收發數據 ...
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是i.MXRT1xxx上第三級啟動保障 - SDMMC manufacture模式。 如果你在 i.MXRT1xxx 板卡上嘗試過從 SD/eMMC 卡啟動,你會發現一個奇怪的現象:如果把 SD/eMMC 卡還插著(並且裡面保留正常的 A ...
  • https://github.com/zk2013/windows_remote_lock_unlock_screen 將生成的DLL註冊至註冊表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\ ...
  • 以沁恆官方的定製版 RISC-V Embedded GCC 和 OpenOCD 為例說明 Ubuntu 下如何配置基於Makefile的開發環境, 如何進行命令行Debug, 以及VSCode下Debug環境的配置 ...
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是恩智浦i.MXRT1xxx系列MCU的SD/eMMC卡啟動。 最近在恩智浦官方社區上支持了一個關於 i.MXRT 從 SD 卡啟動的案例,這讓痞子衡想起了一年前寫過的一篇《i.MXRT600從SD/eMMC啟動》,那一篇重點介紹了基於 ...
  • (DCL語言) 一、概述 數據控制語言,用來定義訪問許可權和安全級別。主要包含包括grant,revoke 關鍵字 grant 授予許可權 revoke 回收許可權 二 、授權 2.1 語法格式 GRANT priv_type [(column_list)] [, priv_type [(column_l ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...