有人相愛,有人跳海
系列文章目錄
1. 項目介紹及環境配置
2. 短信驗證碼登入
3. 使用者資訊
4. MongoDB
5. 推薦好友清單/MongoDB叢集/動态釋出與檢視
6. 圈子動态/圈子互動
7. 即時通訊(基于第三方API)
8. 附近的人(百度地圖APi)
9. 小視訊
10.網關配置
11.背景管理
文章目錄
- 系列文章目錄
- 一、訪客功能
-
- 1. 需求分析
- 2. 儲存訪客
-
- ⑴. 需求
- ⑵. 編碼實作
-
- ①. 實體對象
- ②. Service - 檢視佳人詳情
- ③. Api
-
- Ⅰ. Api
- Ⅱ. 實作類
- ⑶. 功能測試
- 3. 誰看過我
-
- ⑴. 需求
- ⑵. 接口文檔
- ⑶. 編碼實作
-
- ①. vo對象
- ②. Controller
- ③. Service
- ④. Api
- ⑤. Api實作類
- ⑷. 頁面效果
- 二、分布式存儲FastDFS
-
- 1. 内部結構
- 2. 工作原理
- 3. 服務搭建
- 4. 測試案例
-
- ⑴. 添加配置資訊
- ⑵. 測試類
- 5. 測試結果
- 三、視訊功能
-
- 1. 需求分析
- 2. 釋出視訊
-
- ⑴. 接口文檔
- ⑵. 釋出流程
- ⑶. 編碼實作
-
- ①. yml配置檔案
- ②. 實體類
- ③. Controller
- ④. Service
- ⑤. Api
- ⑥. Api實作類
- ⑷. 頁面效果
- 3. 視訊清單查詢
-
- ⑴. 接口文檔
- ⑵. 編碼實作
-
- ①. vo對象
- ②. Controller
- ③. Service
- ④. Api
- ⑤. ApiImpl
- ⑶. Postman
- ⑷. 頁面效果
- 四、通用緩存SpringCache
-
- 1. 存在的問題
-
- ⑴. 問題
- ⑵. 問題分析
- 2. SpringCache入門案例
-
- ⑴. 概述
- ⑵. 入門案例
-
- ①. 引入依賴
- ②. 開啟緩存
- ③. 配置注解
- ④. 測試類
- ⑶. redis緩存
-
- ①. 引入依賴
- ②. 加入redis配置
- ③. 配置注解
- ④. 測試類
- 3. 常用注解
- 4. 使用SpringCache優化視訊清單
-
-
- ①. 開啟緩存
- ②. 配置注解
- ③. 設定失效時間
-
一、訪客功能
1. 需求分析
- 使用者在浏覽我的首頁時,需要記錄訪客資料,訪客在一天内每個使用者隻記錄一次
- 首頁展示最新5條訪客記錄
- 我的子產品,分頁展示所有的訪客記錄
visirots(訪客記錄表):
2. 儲存訪客
⑴. 需求
- 使用者在浏覽我的首頁時,需要記錄訪客資料
- 訪客在一天内每個使用者隻記錄一次
⑵. 編碼實作
①. 實體對象
建立
tanhua-model/src/main/java/com/tanhua/model/mongo/Visitors.java
檔案:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "visitors")
public class Visitors implements java.io.Serializable{
private static final long serialVersionUID = 2811682148052386573L;
private ObjectId id;
private Long userId; //我的id
private Long visitorUserId; //來訪使用者id
private String from; //來源,如首頁、圈子等
private Long date; //來訪時間
private String visitDate;//來訪日期
private Double score; //得分
}
②. Service - 檢視佳人詳情
編輯
tanhua-app-server/src/main/java/com/tanhua/server/service/TanhuaService.java
檔案:
...
// 檢視佳人詳情
public TodayBest personalInfo(Long userId) {
// 1. 根據使用者id,查詢使用者詳情
UserInfo userInfo = userInfoApi.findById(userId);
// 2. 根據操作人id, 和檢視的使用者id,查詢兩者的推薦資料(緣分值)
RecommendUser user = recommendUserApi.queryByUserId(userId, UserHolder.getUserId());
// 構造訪客資料,調用API儲存
Visitors visitors = new Visitors();
visitors.setUserId(userId);
visitors.setVisitorUserId(UserHolder.getUserId());
visitors.setFrom("首頁");
visitors.setDate(System.currentTimeMillis());
visitors.setVisitDate(new SimpleDateFormat("yyyyMMdd").format(new Date()));
visitors.setScore(user.getScore());
visitorsApi.save(visitors);
// 3. 構造傳回值
return TodayBest.init(userInfo, user);
}
...
③. Api
Ⅰ. Api
建立
tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/VisitorsApi.java
檔案:
public interface VisitorsApi {
// 儲存訪客資料
void save(Visitors visitors);
}
Ⅱ. 實作類
建立
tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/VisitorsApiImpl.java
檔案:
@DubboService
public class VisitorsApiImpl implements VisitorsApi{
@Autowired
private MongoTemplate mongoTemplate;
/**
* 儲存訪客資料
* 對于同一個訪客,一天隻能儲存一次資料
*/
@Override
public void save(Visitors visitors) {
// 1. 查詢訪客資料
Query query = Query.query(Criteria.where("userId").is(visitors.getUserId())
.and("visitorUserId").is(visitors.getVisitorUserId())
.and("visitDate").is(visitors.getVisitDate()));
// 2. 不存在,儲存
if(!mongoTemplate.exists(query, Visitors.class)) {
mongoTemplate.save(visitors);
}
}
}
⑶. 功能測試
3. 誰看過我
⑴. 需求
首頁查詢最新訪客清單,查詢資料時,如果使用者查詢過清單,就需要記錄這次查詢資料的時間,下次查詢時查詢大于等于該時間的資料。如果,使用者沒有記錄查詢時間,就查詢最近的5個來訪使用者。
⑵. 接口文檔
⑶. 編碼實作
①. vo對象
建立
tanhua-model/src/main/java/com/tanhua/model/vo/VisitorsVo.java
檔案:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class VisitorsVo {
private Long id; //使用者id
private String avatar;
private String nickname;
private String gender; //性别 man woman
private Integer age;
private String[] tags;
private Long fateValue; //緣分值
/**
* 在vo對象中,補充一個工具方法,封裝轉化過程
*/
public static VisitorsVo init(UserInfo userInfo, Visitors visitors) {
VisitorsVo vo = new VisitorsVo();
BeanUtils.copyProperties(userInfo,vo);
if(userInfo.getTags() != null) {
vo.setTags(userInfo.getTags().split(","));
}
vo.setFateValue(visitors.getScore().longValue());
return vo;
}
}
②. Controller
編輯
tanhua-app-server/src/main/java/com/tanhua/server/controller/MovementController.java
檔案:
@RestController
@RequestMapping("/movements")
public class MovementController {
@Autowired
private MovementService movementService;
@Autowired
private CommentsService commentsService;
/**
* 釋出動态
* @return
*/
@PostMapping
public ResponseEntity movements(Movement movement, MultipartFile imageContent[]) throws IOException {
movementService.publishMovement(movement, imageContent);
return ResponseEntity.ok(null);
}
/**
* 查詢我的動态
*/
@GetMapping("/all")
public ResponseEntity findByUserId(Long userId,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult pr = movementService.findByUserId(userId, page, pagesize);
return ResponseEntity.ok(pr);
}
/**
* 查詢好友動态
*/
@GetMapping()
public ResponseEntity movements( @RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult pr = movementService.findFriendMovements(page, pagesize);
return ResponseEntity.ok(pr);
}
/**
* 查詢推薦動态
*/
@GetMapping("/recommend")
public ResponseEntity recommend( @RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult pr = movementService.findRecommendMovements(page, pagesize);
return ResponseEntity.ok(pr);
}
/**
* 查詢單條動态
*/
@GetMapping("/{id}")
public ResponseEntity findById(@PathVariable("id") String movementId) {
MovementsVo vo = movementService.findById(movementId);
return ResponseEntity.ok(vo);
}
/**
* 點贊
*/
@GetMapping("/{id}/like")
public ResponseEntity like(@PathVariable("id") String movementId) {
Integer likeCount = commentsService.likeComment(movementId);
return ResponseEntity.ok(likeCount);
}
/**
* 取消點贊
*/
@GetMapping("/{id}/dislike")
public ResponseEntity dislike(@PathVariable("id") String movementId) {
Integer likeCount = commentsService.dislikeComment(movementId);
return ResponseEntity.ok(likeCount);
}
/**
* 喜歡
*/
@GetMapping("/{id}/love")
public ResponseEntity love(@PathVariable("id") String movementId) {
Integer likeCount = commentsService.loveComment(movementId);
return ResponseEntity.ok(likeCount);
}
/**
* 取消喜歡
*/
@GetMapping("/{id}/unlove")
public ResponseEntity unlove(@PathVariable("id") String movementId) {
Integer likeCount = commentsService.disloveComment(movementId);
return ResponseEntity.ok(likeCount);
}
/**
* 誰看過我
*/
@GetMapping("visitors")
public ResponseEntity queryVisitorsList(){
List<VisitorsVo> list = movementService.queryVisitorsList();
return ResponseEntity.ok(list);
}
}
③. Service
編輯
tanhua-app-server/src/main/java/com/tanhua/server/service/MovementService.java
檔案:
@Service
public class MovementService {
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private MovementApi movementApi;
@DubboReference
private UserInfoApi userInfoApi;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@DubboReference
private VisitorsApi visitorsApi;
/**
* 釋出動态
*/
public void publishMovement(Movement movement, MultipartFile[] imageContent) throws IOException {
// 1. 判斷釋出動态的内容是否存在
if(StringUtils.isEmpty(movement.getTextContent())) {
throw new BusinessException(ErrorResult.contentError());
}
// 2. 擷取目前登入的使用者id
Long userId = UserHolder.getUserId();
// 3. 将檔案内容上傳到阿裡雲OSS, 擷取請求位址
List<String> medias = new ArrayList<>();
for (MultipartFile multipartFile : imageContent) {
// String upload = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream());
// !!! 阿裡雲OSS收費, 這裡暫時跳過
String upload = "https://img0.baidu.com/it/u=1501084209,93021381&fm=253&fmt=auto&app=138&f=JPEG";
medias.add(upload);
}
// 4. 将資料封裝到movement對象
movement.setUserId(userId);
movement.setMedias(medias);
//5. 調用API完成動态釋出
movementApi.publish(movement);
}
// 查詢我的動态
public PageResult findByUserId(Long userId, Integer page, Integer pagesize) {
// 1. 根據使用者id, 調用API查詢個人動态内容(PageResult -- Movement)
PageResult pr = movementApi.findByUserId(userId, page, pagesize);
// 2. 擷取PageResult中item清單對象
List<Movement> items = (List<Movement>) pr.getItems();
// 3. 非空判斷
if(items == null) {
return pr;
}
// 4. 循環資料清單
UserInfo userInfo = userInfoApi.findById(userId);
List<MovementsVo> vos = new ArrayList<>();
for (Movement item : items) {
// 5. 一個Movement建構一個VO對象
MovementsVo vo = MovementsVo.init(userInfo, item);
vos.add(vo);
}
// 6. 建構傳回值
pr.setItems(vos);
return pr;
}
// 查詢好友動态
public PageResult findFriendMovements(Integer page, Integer pagesize) {
// 1. 擷取目前使用者id
Long userId = UserHolder.getUserId();
// 2. 調用API查詢目前使用者好友釋出的動态清單
List<Movement> list = movementApi.findFriendMovements(page, pagesize, userId);
return getPageResult(page, pagesize, list);
}
// 公共方法
private PageResult getPageResult(Integer page, Integer pagesize, List<Movement> list) {
// 3. 判斷清單是否為空
// if(list == null || list.isEmpty()) {
if(CollUtil.isEmpty(list)) {
return new PageResult();
}
// 4. 提取動态釋出人的id清單
List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
// 5. 根據使用者id清單擷取使用者詳情
Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);
// 6. 一個Movement構造一個vo對象
List<MovementsVo> vos = new ArrayList<>();
for (Movement movement : list) {
UserInfo userInfo = map.get(movement.getUserId());
if(userInfo != null) {
MovementsVo vo = MovementsVo.init(userInfo, movement);
// 添加點贊狀态,判斷hashKey是否存在
String key = Constants.MOVEMENTS_INTERACT_KEY + movement.getId().toHexString();
String hashKey = Constants.MOVEMENT_LIKE_HASHKEY + UserHolder.getUserId();
if(redisTemplate.opsForHash().hasKey(key, hashKey)) {
vo.setHasLiked(1);
}
// 添加喜歡狀态,判斷hashKey是否存在
String loveHashKey = Constants.MOVEMENT_LOVE_HASHKEY + UserHolder.getUserId();
if(redisTemplate.opsForHash().hasKey(key, loveHashKey)) {
vo.setHasLoved(1);
}
vos.add(vo);
}
}
// 7. 構造PageResult并傳回
return new PageResult(page, pagesize, 0l, vos);
}
// 查詢推薦動态
public PageResult findRecommendMovements(Integer page, Integer pagesize) {
// 1. 從redis從擷取推薦資料
String redisKey = Constants.MOVEMENTS_RECOMMEND + UserHolder.getUserId();
String redisValue = redisTemplate.opsForValue().get(redisKey);
// 2. 判斷推薦資料是否存在
List<Movement> list = Collections.EMPTY_LIST;
if(StringUtils.isEmpty(redisValue)) {
// 3. 如果不存在, 調用API随機構造10條動态資料
list = movementApi.randomMovements(pagesize);
} else {
// 4. 如果存在, 處理pid資料 "16,17,18,19,20,21,10015,10020,10040,10064,10092,10093,10099,10067"
String[] values = redisValue.split(",");
// 4.1 判斷目前頁的起始條數是否小于數組的總數
if((page - 1) * pagesize < values.length) {
List<Long> pids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
.map(e -> Long.valueOf(e))
.collect(Collectors.toList());
// 5. 調用API根據PID數組查詢動态資料
list = movementApi.findMovementByPids(pids);
}
}
// 6. 調用公共方法構造傳回值
return getPageResult(page, pagesize, list);
}
// 查詢單條動态
public MovementsVo findById(String movementId) {
// 1. 調用API查詢動态詳情
Movement movement = movementApi.findById(movementId);
// 2. 轉換vo對象
if(movement != null) {
UserInfo userInfo = userInfoApi.findById(movement.getUserId());
return MovementsVo.init(userInfo, movement);
} else {
return null;
}
}
// 首頁 - 訪客清單
public List<VisitorsVo> queryVisitorsList() {
// 1. 查詢通路時間
String key = Constants.VISITORS_USER;
String hashKey = String.valueOf(UserHolder.getUserId());
String value = (String) redisTemplate.opsForHash().get(key, hashKey);
Long date = StringUtils.isEmpty(value) ? null : Long.valueOf(value);
// 2. 調用API查詢資料清單 List<Visitors>
List<Visitors> list = visitorsApi.queryVisitorsList(date, UserHolder.getUserId());
if(CollUtil.isEmpty(list)) {
return new ArrayList<>();
}
// 3. 提取使用者id
List<Long> visitorUserIds = CollUtil.getFieldValues(list, "visitorUserId", Long.class);
// 4. 檢視使用者詳情
Map<Long, UserInfo> map = userInfoApi.findByIds(visitorUserIds, null);
// 5. 構造傳回
List<VisitorsVo> vos = new ArrayList<>();
for (Visitors visitors : list) {
UserInfo userInfo = map.get(visitors.getVisitorUserId());
if(userInfo != null) {
VisitorsVo vo = VisitorsVo.init(userInfo, visitors);
vos.add(vo);
}
}
return vos;
}
}
④. Api
編輯
tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/VisitorsApi.java
檔案:
public interface VisitorsApi {
// 儲存訪客資料
void save(Visitors visitors);
// 首頁 - 查詢訪客清單
List<Visitors> queryVisitorsList(Long date, Long userId);
}
⑤. Api實作類
編輯
tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/VisitorsApiImpl.java
檔案:
@DubboService
public class VisitorsApiImpl implements VisitorsApi{
@Autowired
private MongoTemplate mongoTemplate;
/**
* 儲存訪客資料
* 對于同一個訪客,一天隻能儲存一次資料
*/
@Override
public void save(Visitors visitors) {
// 1. 查詢訪客資料
Query query = Query.query(Criteria.where("userId").is(visitors.getUserId())
.and("visitorUserId").is(visitors.getVisitorUserId())
.and("visitDate").is(visitors.getVisitDate()));
// 2. 不存在,儲存
if(!mongoTemplate.exists(query, Visitors.class)) {
mongoTemplate.save(visitors);
}
}
// 首頁 - 查詢訪客清單
public List<Visitors> queryVisitorsList(Long date, Long userId) {
Criteria criteria = Criteria.where("userId").is(userId);
if(date != null) {
criteria.and("date").gt(date);
}
Query query = Query.query(criteria).limit(5).with(Sort.by(Sort.Order.desc("date")));
return mongoTemplate.find(query, Visitors.class);
}
}
⑷. 頁面效果
二、分布式存儲FastDFS
FastDFS是分布式檔案系統。使用 FastDFS很容易搭建一套高性能的檔案伺服器叢集提供檔案上傳、下載下傳等服務
1. 内部結構
- Tracker server:
- 配置叢集
- Tracker server監控各個Storage server,排程存儲服務
- Storage server:
- Storage server(存儲伺服器),檔案最終存放的位置
- 通過Group(組),拓展檔案存儲容量
- 各個Group(組)中,通過叢集解決單點故障
2. 工作原理
上傳:
- Storage Server 向Tracker Server, 彙報目前存儲節點的狀态資訊(包括磁盤剩餘空間、檔案同步狀況等統計資訊)
- 用戶端程式連接配接Tracker Server發給上傳請求
- Tracker Server計算可用的Storage Server 節點,傳回
- 用戶端将檔案上傳到Storage Server,并擷取傳回的file_id(包括路徑資訊和檔案名稱)
- 用戶端儲存請求位址
下載下傳:
- 和檔案上傳類似
- 檔案下載下傳使用頻率并不高,由于用戶端記錄的通路位址,直接拼接位址通路即可
3. 服務搭建
探花交友所需的第三方服務元件,已經以Docker-Compose準備好了。僅僅需要進入相關目錄,以指令形式啟動運作即可
#進入目錄
cd /root/docker-file/fastdfs/
#建立容器并啟動
docker-compose up –d
#檢視容器
docker ps -a
4. 測試案例
⑴. 添加配置資訊
編輯
tanhua-app-server/src/main/resources/application.yml
檔案:
...
# ===================================================================
# 分布式檔案系統FDFS配置
# ===================================================================
fdfs:
so-timeout: 1500
connect-timeout: 600
#縮略圖生成參數
thumb-image:
width: 150
height: 150
#TrackerList參數,支援多個
tracker-list: 192.168.136.160:22122
web-server-url: http://192.168.136.160:8888/
⑵. 測試類
建立
tanhua-app-server/src/test/java/com/tanhua/test/FastDFSTest.java
檔案:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppServerApplication.class)
public class FastDFSTest {
/**
* 測試FastDFS的檔案上傳
*/
// 用于檔案上傳或下載下傳
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;// 擷取存儲伺服器的請求URL
@Test
public void testUpload() throws FileNotFoundException {
//1、指定檔案
File file = new File("D:\\Course\\HM\\img\\1.jpg");
//2、檔案上傳
StorePath path = client.uploadFile(new FileInputStream(file), file.length(), "jpg", null);
//3、拼接通路路徑
String fullPath = path.getFullPath();
System.out.println(fullPath);
String url = webServer.getWebServerUrl() + fullPath;
System.out.println(url);
}
}
5. 測試結果
三、視訊功能
1. 需求分析
小視訊功能類似于抖音、快手小視訊的應用,使用者可以上傳小視訊進行分享,也可以浏覽檢視别人分享的視訊,并且可以對視訊評論和點贊操作。
video(視訊記錄表):
2. 釋出視訊
⑴. 接口文檔
⑵. 釋出流程
- 用戶端上傳視訊時,自動生成封面圖檔一并發送請求
- 封面圖檔:上傳到阿裡雲OSS
- 視訊:上傳到FastDFS
- 使用者發通過用戶端APP上傳視訊到server服務
- server服務上傳視訊到FastDFS檔案系統,上傳成功後傳回視訊的url位址
- server服務上傳封面圖檔到阿裡雲OSS
- server通過rpc的調用dubbo服務進行儲存小視訊資料
⑶. 編碼實作
①. yml配置檔案
編輯
tanhua-app-server/src/main/resources/application.yml
檔案:
...
Spring:
servlet:
multipart:
max-file-size: 30MB
max-request-size: 30MB
②. 實體類
建立
tanhua-model/src/main/java/com/tanhua/model/mongo/Video.java
檔案:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "video")
public class Video implements java.io.Serializable {
private static final long serialVersionUID = -3136732836884933873L;
private ObjectId id; //主鍵id
private Long vid; //自動增長
private Long created; //建立時間
private Long userId;
private String text; //文字
private String picUrl; //視訊封面檔案,URL
private String videoUrl; //視訊檔案,URL
private Integer likeCount=0; //點贊數
private Integer commentCount=0; //評論數
private Integer loveCount=0; //喜歡數
}
③. Controller
建立
tanhua-app-server/src/main/java/com/tanhua/server/controller/SmallVideoController.java
檔案:
@RestController
@RequestMapping("/smallVideos")
public class SmallVideoController {
@Autowired
private SmallVideosService videosService;
/**
* 釋出視訊
* 接口路徑:POST
* 請求參數:
* videoThumbnail:封面圖
* videoFile:視訊檔案
*/
@PostMapping
public ResponseEntity saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
videosService.saveVideos(videoThumbnail,videoFile);
return ResponseEntity.ok(null);
}
}
④. Service
編輯
tanhua-app-server/src/main/java/com/tanhua/server/service/SmallVideosService.java
檔案:
@Service
public class SmallVideosService {
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private VideoApi videoApi;
/**
* 上傳視訊
* @param videoThumbnail 視訊封面圖檔
* @param videoFile 視訊檔案
*/
public void saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
if(videoThumbnail.isEmpty() || videoFile.isEmpty()) {
throw new BusinessException(ErrorResult.error());
}
// 1. 将視訊上傳到FastDFS,擷取通路url
String filename = videoFile.getOriginalFilename();
filename = filename.substring(filename.lastIndexOf(".") + 1);
StorePath storePath = client.uploadFile(videoFile.getInputStream(), videoFile.getSize(), filename, null);
String videoUrl = webServer.getWebServerUrl() + storePath.getFullPath();
// 2. 将封面圖檔上傳到阿裡雲OSS,擷取通路的url
// String upload = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());
// // !!! 阿裡雲OSS收費, 這裡暫時跳過
String upload = "https://img0.baidu.com/it/u=8672387,2873147723&fm=253&fmt=auto&app=138&f=JPEG";
// 3. 建構Videos對象
Video video = new Video();
video.setUserId(UserHolder.getUserId());
video.setVideoUrl(videoUrl);
video.setPicUrl(upload);
video.setText("我就是我, 是顔色不一樣的煙火");
// 4. 調用API儲存資料
String videoId = videoApi.save(video);
if(StringUtils.isEmpty(videoId)) {
throw new BusinessException(ErrorResult.error());
}
}
}
⑤. Api
建立
tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/VideoApi.java
檔案:
public interface VideoApi {
// 儲存視訊
String save(Video video);
}
⑥. Api實作類
建立
tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/VideoApiImpl.java
檔案:
@DubboService
public class VideoApiImpl implements VideoApi{
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private IdWorker idWorker;
// 儲存視訊
@Override
public String save(Video video) {
// 1. 設定屬性
video.setVid(idWorker.getNextId("video"));
video.setCreated(System.currentTimeMillis());
// 2. 調用方法儲存對象
mongoTemplate.save(video);
// 3. 傳回對象id
return video.getId().toHexString();
}
}
⑷. 頁面效果
3. 視訊清單查詢
⑴. 接口文檔
⑵. 編碼實作
①. vo對象
建立
tanhua-model/src/main/java/com/tanhua/model/vo/VideoVo.java
檔案:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class VideoVo implements Serializable {
private Long userId;
private String avatar; //頭像
private String nickname; //昵稱
private String id;
private String cover; //封面
private String videoUrl; //視訊URL
private String signature; //釋出視訊時,傳入的文字内容
private Integer likeCount; //點贊數量
private Integer hasLiked; //是否已贊(1是,0否)
private Integer hasFocus; //是否關注 (1是,0否)
private Integer commentCount; //評論數量
public static VideoVo init(UserInfo userInfo, Video item) {
VideoVo vo = new VideoVo();
//copy使用者屬性
BeanUtils.copyProperties(userInfo,vo); //source,target
//copy視訊屬性
BeanUtils.copyProperties(item,vo);
vo.setCover(item.getPicUrl());
vo.setId(item.getId().toHexString());
vo.setSignature(item.getText());
vo.setHasFocus(0);
vo.setHasLiked(0);
return vo;
}
}
②. Controller
編輯
tanhua-app-server/src/main/java/com/tanhua/server/controller/SmallVideoController.java
檔案:
@RestController
@RequestMapping("/smallVideos")
public class SmallVideoController {
@Autowired
private SmallVideosService videosService;
/**
* 釋出視訊
* 接口路徑:POST
* 請求參數:
* videoThumbnail:封面圖
* videoFile:視訊檔案
*/
@PostMapping
public ResponseEntity saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
videosService.saveVideos(videoThumbnail,videoFile);
return ResponseEntity.ok(null);
}
/**
* 視訊清單
*/
@GetMapping
public ResponseEntity queryVideoList(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pagesize) {
PageResult result = videosService.queryVideoList(page, pagesize);
return ResponseEntity.ok(result);
}
}
③. Service
編輯
tanhua-app-server/src/main/java/com/tanhua/server/service/SmallVideosService.java
檔案:
@Service
public class SmallVideosService {
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private VideoApi videoApi;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@DubboReference
private UserInfoApi userInfoApi;
/**
* 上傳視訊
* @param videoThumbnail 視訊封面圖檔
* @param videoFile 視訊檔案
*/
public void saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
if(videoThumbnail.isEmpty() || videoFile.isEmpty()) {
throw new BusinessException(ErrorResult.error());
}
// 1. 将視訊上傳到FastDFS,擷取通路url
String filename = videoFile.getOriginalFilename();
filename = filename.substring(filename.lastIndexOf(".") + 1);
StorePath storePath = client.uploadFile(videoFile.getInputStream(), videoFile.getSize(), filename, null);
String videoUrl = webServer.getWebServerUrl() + storePath.getFullPath();
// 2. 将封面圖檔上傳到阿裡雲OSS,擷取通路的url
// String upload = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());
// // !!! 阿裡雲OSS收費, 這裡暫時跳過
String upload = "https://img0.baidu.com/it/u=8672387,2873147723&fm=253&fmt=auto&app=138&f=JPEG";
// 3. 建構Videos對象
Video video = new Video();
video.setUserId(UserHolder.getUserId());
video.setVideoUrl(videoUrl);
video.setPicUrl(upload);
video.setText("我就是我, 是顔色不一樣的煙火");
// 4. 調用API儲存資料
String videoId = videoApi.save(video);
if(StringUtils.isEmpty(videoId)) {
throw new BusinessException(ErrorResult.error());
}
}
// 查詢視訊清單
public PageResult queryVideoList(Integer page, Integer pagesize) {
// 1. 查詢redis資料
String redisKey = Constants.VIDEOS_RECOMMEND + UserHolder.getUserId();
String redisValue = redisTemplate.opsForValue().get(redisKey);
// 2. 判斷redis資料是否存在
List<Video> list = new ArrayList<>();
int redisPages = 0;
if(!StringUtils.isEmpty(redisValue)) {
// 3. 如果redis資料存在,根據VID查詢資料
String[] values = redisValue.split(",");
// 4. 判斷redis中資料是否滿足本次分頁條數
if((page - 1) * pagesize < values.length) {
List<Long> vids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
.map(e -> Long.valueOf(e))
.collect(Collectors.toList());
list = videoApi.findMovementsByPids(vids);
}
redisPages = PageUtil.totalPage(values.length, pagesize);
}
// 5. 如果redis資料不存在, 分頁查詢視訊資料
if(list.isEmpty()) {
// page的計算規則, 傳入的頁碼, --redis查詢的總頁數
list = videoApi.queryVideoList(page - redisPages, pagesize);
}
// 6. 提取視訊清單中所有的使用者id
List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
// 7. 查詢使用者資訊
Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);
// 8. 建構傳回值
List<VideoVo> vos = new ArrayList<>();
for (Video video : list) {
UserInfo info = map.get(video.getUserId());
if(info != null) {
VideoVo vo = VideoVo.init(info, video);
vos.add(vo);
}
}
return new PageResult(page, pagesize, 0l, vos);
}
}
④. Api
編輯
tanhua-dubbo/tanhua-dubbo-interface/src/main/java/com/tanhua/dubbo/api/VideoApi.java
檔案:
public interface VideoApi {
// 儲存視訊
String save(Video video);
// 根據vid查詢資料清單
List<Video> findMovementsByPids(List<Long> vids);
// 分頁查詢資料清單
List<Video> queryVideoList(int page, Integer pagesize);
}
⑤. ApiImpl
編輯
tanhua-dubbo/tanhua-dubbo-mongo/src/main/java/com/tanhua/dubbo/api/VideoApiImpl.java
檔案:
@DubboService
public class VideoApiImpl implements VideoApi{
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private IdWorker idWorker;
// 儲存視訊
@Override
public String save(Video video) {
// 1. 設定屬性
video.setVid(idWorker.getNextId("video"));
video.setCreated(System.currentTimeMillis());
// 2. 調用方法儲存對象
mongoTemplate.save(video);
// 3. 傳回對象id
return video.getId().toHexString();
}
// 根據vid查詢資料清單
@Override
public List<Video> findMovementsByPids(List<Long> vids) {
Query query = Query.query(Criteria.where("vid").in(vids));
return mongoTemplate.find(query, Video.class);
}
// 分頁查詢資料清單
@Override
public List<Video> queryVideoList(int page, Integer pagesize) {
Query query = new Query().skip((page - 1) * pagesize).limit(pagesize)
.with(Sort.by(Sort.Order.desc("created")));
return mongoTemplate.find(query, Video.class);
}
}
⑶. Postman
⑷. 頁面效果
四、通用緩存SpringCache
1. 存在的問題
⑴. 問題
在項目中,我們通常會把高頻的查詢進行緩存。如資訊網站首頁的文章清單、電商網站首頁的商品清單、微網誌等社交媒體熱搜的文章等等,當大量的使用者發起查詢時,借助緩存提高查詢效率,同時減輕資料庫壓力。目前的緩存架構有很多:比如Redis、Memcached、Guava、Caffeine等等
以redis為例:
⑵. 問題分析
- 使用SpringAOP動态增強
- 自定義注解,進行緩存配置
- 适配多種緩存
2. SpringCache入門案例
⑴. 概述
Spring Cache是Spring提供的通用緩存架構。它利用了AOP,實作了基于注解的緩存功能,使開發者不用關心底層使用了什麼緩存架構,隻需要簡單地加一個注解,就能實作緩存功能了。使用者使用Spring Cache,可以快速開發一個很不錯的緩存功能。
Gitee倉庫: https://gitee.com/yuan0_0/tanhua_spring_cache.git
⑵. 入門案例
①. 引入依賴
編輯
pom.xml
檔案:
<!--spring cache依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
②. 開啟緩存
編輯
src/main/java/com/itheima/cache/CachingApplication.java
檔案:
@SpringBootApplication
@EnableCaching // 開啟緩存
public class CachingApplication {
public static void main(String[] args) {
SpringApplication.run(CachingApplication.class, args);
}
}
③. 配置注解
編輯
src/main/java/com/itheima/cache/service/UserService.java
檔案:
@CachePut(value="user"")
public User findById(Long id) {
return userDao.findById(id);
}
④. 測試類
編輯
src/test/java/com/itheima/cache/test/UserServiceTest.java
檔案:
/**
* 根據id查詢使用者
*/
@Test
public void testFindById() {
for (int i = 0; i < 5; i++) {
User user = userService.findById(1l);
System.out.println(user);
}
}
⑶. redis緩存
①. 引入依賴
編輯
pom.xml
檔案:
<!--SpringDataRedis依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
②. 加入redis配置
編輯
src/main/resources/application.yml
檔案:
spring:
redis:
port: 6379
host: 192.168.136.160
③. 配置注解
編輯
src/main/java/com/itheima/cache/service/UserService.java
檔案:
@Service
public class UserService {
@Autowired
private UserDao userDao;
// @CachePut(value="user"")
/**
* value:名稱空間(分組)
* key: 支援springel
* redis-key的命名規則:
* value + "::" + key
*/
@CachePut(value="user" , key = "'test' + #id")
public User findById(Long id) {
return userDao.findById(id);
}
//@CacheEvict(value="user" , key = "'test' + #id")
@Caching(
evict = {
@CacheEvict(value="user" , key = "'test' + #id"),
@CacheEvict(value="user" , key = "#id")
}
)
public void update(Long id) {
userDao.update(id);
}
}
④. 測試類
編輯
src/test/java/com/itheima/cache/test/UserServiceTest.java
檔案:
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
/**
* 根據id查詢使用者
*/
@Test
public void testFindById() {
for (int i = 0; i < 5; i++) {
User user = userService.findById(1l);
System.out.println(user);
}
}
@Test
public void testFindById2() {
User user = userService.findById(2l);
System.out.println(user);
}
//更新:更新資料庫,删除redis中的緩存資料
@Test
public void testUpdate() {
userService.update(2l);
}
}
3. 常用注解
- @Cacheable:
- 注解表示這個方法有了緩存的功能,方法的傳回值會被緩存下來,下一次調用該方法前,會去檢查是否緩存中已經有值,如果有就直接傳回,不調用方法。如果沒有,就調用方法,然後把結果緩存起來。
- @CachePut:
- 加了@CachePut注解的方法,會把方法的傳回值put到緩存裡面緩存起來,供其它地方使用。
- @CacheEvit:
- 使用了CacheEvict注解的方法,會清空指定緩存。
- @Caching:
- Java代碼中,同個方法,一個相同的注解隻能配置一次。如若操作多個緩存,可以使用@Caching
4. 使用SpringCache優化視訊清單
①. 開啟緩存
編輯
tanhua-app-server/src/main/java/com/tanhua/server/AppServerApplication.java
檔案:
//啟動類
// @SpringBootApplication
@SpringBootApplication(exclude = {
MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class
}) //排除mongo的自動配置
@EnableCaching //開啟緩存
public class AppServerApplication {
public static void main(String[] args) {
SpringApplication.run(AppServerApplication.class,args);
}
}
②. 配置注解
編輯
tanhua-app-server/src/main/java/com/tanhua/server/service/SmallVideosService.java
檔案:
@Service
public class SmallVideosService {
@Autowired
private FastFileStorageClient client;
@Autowired
private FdfsWebServer webServer;
@Autowired
private OssTemplate ossTemplate;
@DubboReference
private VideoApi videoApi;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@DubboReference
private UserInfoApi userInfoApi;
/**
* 上傳視訊
* @param videoThumbnail 視訊封面圖檔
* @param videoFile 視訊檔案
*/
@CacheEvict(value="videoList",allEntries = true) // 清空緩存
public void saveVideos(MultipartFile videoThumbnail, MultipartFile videoFile) throws IOException {
if(videoThumbnail.isEmpty() || videoFile.isEmpty()) {
throw new BusinessException(ErrorResult.error());
}
// 1. 将視訊上傳到FastDFS,擷取通路url
String filename = videoFile.getOriginalFilename();
filename = filename.substring(filename.lastIndexOf(".") + 1);
StorePath storePath = client.uploadFile(videoFile.getInputStream(), videoFile.getSize(), filename, null);
String videoUrl = webServer.getWebServerUrl() + storePath.getFullPath();
// 2. 将封面圖檔上傳到阿裡雲OSS,擷取通路的url
// String upload = ossTemplate.upload(videoThumbnail.getOriginalFilename(), videoThumbnail.getInputStream());
// // !!! 阿裡雲OSS收費, 這裡暫時跳過
String upload = "https://img0.baidu.com/it/u=8672387,2873147723&fm=253&fmt=auto&app=138&f=JPEG";
// 3. 建構Videos對象
Video video = new Video();
video.setUserId(UserHolder.getUserId());
video.setVideoUrl(videoUrl);
video.setPicUrl(upload);
video.setText("我就是我, 是顔色不一樣的煙火");
// 4. 調用API儲存資料
String videoId = videoApi.save(video);
if(StringUtils.isEmpty(videoId)) {
throw new BusinessException(ErrorResult.error());
}
}
// 查詢視訊清單
@Cacheable(
value="videos",
key = "T(com.tanhua.server.interceptor.UserHolder).getUserId()+'_'+#page+'_'+#pagesize") //userid _ page_pagesize
public PageResult queryVideoList(Integer page, Integer pagesize) {
// 1. 查詢redis資料
String redisKey = Constants.VIDEOS_RECOMMEND + UserHolder.getUserId();
String redisValue = redisTemplate.opsForValue().get(redisKey);
// 2. 判斷redis資料是否存在
List<Video> list = new ArrayList<>();
int redisPages = 0;
if(!StringUtils.isEmpty(redisValue)) {
// 3. 如果redis資料存在,根據VID查詢資料
String[] values = redisValue.split(",");
// 4. 判斷redis中資料是否滿足本次分頁條數
if((page - 1) * pagesize < values.length) {
List<Long> vids = Arrays.stream(values).skip((page - 1) * pagesize).limit(pagesize)
.map(e -> Long.valueOf(e))
.collect(Collectors.toList());
list = videoApi.findMovementsByPids(vids);
}
redisPages = PageUtil.totalPage(values.length, pagesize);
}
// 5. 如果redis資料不存在, 分頁查詢視訊資料
if(list.isEmpty()) {
// page的計算規則, 傳入的頁碼, --redis查詢的總頁數
list = videoApi.queryVideoList(page - redisPages, pagesize);
}
// 6. 提取視訊清單中所有的使用者id
List<Long> userIds = CollUtil.getFieldValues(list, "userId", Long.class);
// 7. 查詢使用者資訊
Map<Long, UserInfo> map = userInfoApi.findByIds(userIds, null);
// 8. 建構傳回值
List<VideoVo> vos = new ArrayList<>();
for (Video video : list) {
UserInfo info = map.get(video.getUserId());
if(info != null) {
VideoVo vo = VideoVo.init(info, video);
vos.add(vo);
}
}
return new PageResult(page, pagesize, 0l, vos);
}
}
③. 設定失效時間
編輯
tanhua-app-server/src/main/java/com/tanhua/server/config/RedisCacheConfig.java
檔案:
@Configuration
public class RedisCacheConfig {
//設定失效時間
private static final Map<String, Duration> cacheMap;
static {
cacheMap = ImmutableMap.<String, Duration>builder().put("videos", Duration.ofSeconds(30L)).build();
}
//配置RedisCacheManagerBuilderCustomizer對象
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> {
//根據不同的cachename設定不同的失效時間
for (Map.Entry<String, Duration> entry : cacheMap.entrySet()) {
builder.withCacheConfiguration(entry.getKey(),
RedisCacheConfiguration.defaultCacheConfig().entryTtl(entry.getValue()));
}
};
}
}