第5章 圈子功能实现-1

今日内容介绍

63546821510

学习目标

  • 完成公共列表-作业
  • 圈子功能说明
  • 圈子技术实现
  • 圈子技术方案
  • 圈子实现发布动态
  • 圈子实现好友动态
  • 圈子实现推荐动态
  • 圈子实现点赞、喜欢功能(第6章实现)
  • 圈子实现评论(第6章实现)
  • 圈子实现评论的点赞(第6章实现)

1.公告管理-作业

【目标】

首页推荐需求介绍

首页推荐功能分析

首页推荐功能实现

【路径】

1:了解首页推荐需求

2:首页推荐功能分析

3:首页推荐功能实现

【讲解】

1.1. 服务消费者-公告管理

61371886876

1.1.1. 接口说明

61371419723

1.1.2. 公告实体类与VO

在tanhua-domain模块下创建Announcement

package com.tanhua.domain.db;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Announcement extends BasePojo {
    private String id;
    private String title;
    private String description;
}

1.1.3. Announcement

在tanhua-domain模块下创建AnnouncementVo

package com.tanhua.domain.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AnnouncementVo {
    private String id;
    private String title;
    private String description;
    private String createDate;
}

1.1.4. AnnounController

在tanhua-server模块下创建AnnounController

package com.tanhua.server.controller;

import com.tanhua.server.service.AnnounService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 公告
 */
@RestController
@RequestMapping("/messages")
public class AnnounController {

    @Autowired
    private AnnounService announService;

    /**
     * 查询公告列表
     *  GET /announcements
     *  参数:page,pagesize
     */
    @GetMapping("/announcements")
    public ResponseEntity announcements(@RequestParam(defaultValue = "1") int page,
                                        @RequestParam(defaultValue = "10")  int pagesize) {

        return announService.announcements(page,pagesize);
    }
}

1.1.5. AnnounService

在tanhua-server模块下创建AnnounService

package com.tanhua.server.service;

import com.tanhua.domain.db.Announcement;
import com.tanhua.domain.vo.AnnouncementVo;
import com.tanhua.domain.vo.PageResult;
import com.tanhua.dubbo.api.AnnouncementApi;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.BeanUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

@Service
public class AnnounService {

    @Reference
    private AnnouncementApi announcementApi;

    /**
     * 查询公告列表
     */
    public ResponseEntity announcements(int page, int pagesize) {
        //1、调用API查询分页数据 PageResult
        PageResult<Announcement> pageResult = announcementApi.findAll(page, pagesize);
        //2、获取所有的公告对象
        List<Announcement> records = pageResult.getItems();
        //3、一个公告对象,转化为一个vo对象
        List<AnnouncementVo> list = new ArrayList<>();

        for (Announcement record : records) {
            AnnouncementVo vo = new AnnouncementVo();
            BeanUtils.copyProperties(record,vo);
            if(record.getCreated() != null) {
                vo.setCreateDate(new SimpleDateFormat("yyyy-MM-dd hh:mm").format(record.getCreated()));
            }
            list.add(vo);
        }
        //4、构造需要的分页对象,PageResult
        PageResult resultVo = new PageResult(pageResult.getCounts(), pageResult.getPagesize(),pageResult.getPages(),pageResult.getPage(),list);
        //5、构造返回值
        return ResponseEntity.ok(resultVo);
    }
}

1.2. 服务提供者-公告管理

1.2.1. AnnouncementApi

在tanhua-dubbo-interface模块下创建AnnouncementApi

package com.tanhua.dubbo.api;

import com.tanhua.domain.db.Announcement;
import com.tanhua.domain.vo.PageResult;

public interface AnnouncementApi {

    /**
     * 分页查询
     */
    PageResult<Announcement> findAll(int page, int size);
}

1.2.2 AnnouncementApiImpl

在tanhua-dubbo-service模块下创建AnnouncementApiImpl

package com.tanhua.dubbo.api;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tanhua.domain.db.Announcement;
import com.tanhua.domain.vo.PageResult;
import com.tanhua.dubbo.mapper.AnnouncementMapper;
import org.apache.dubbo.config.annotation.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class AnnouncementApiImpl implements AnnouncementApi {

    @Autowired
    private AnnouncementMapper announcementMapper;

    @Override
    public PageResult<Announcement> findAll(int page, int size) {
        Page<Announcement> pages = new Page<>(page,size);
        IPage<Announcement> pageInfo = announcementMapper.selectPage(pages, new QueryWrapper<>());
        PageResult<Announcement> pageResult = new PageResult<Announcement>(pageInfo.getTotal(), pageInfo.getSize(),pageInfo.getPages(),pageInfo.getCurrent(),pageInfo.getRecords());
        return pageResult;
    }
}

1.2.3. AnnouncementMapper

在tanhua-dubbo-service模块下创建AnnouncementMapper

package com.tanhua.dubbo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tanhua.domain.db.Announcement;

public interface AnnouncementMapper extends BaseMapper<Announcement> {

}

1.3.4. 测试

61371816656

61371814103

【小结】

掌握公共管理功能实现

2. 首页推荐

【目标】

首页推荐需求介绍

首页推荐功能分析

首页推荐功能实现

【路径】

1:了解首页推荐需求

2:首页推荐功能分析

3:首页推荐功能实现

【讲解】

1567064038029

61392313363

2.3. 服务消费者-首页推荐

2.3.1. 接口说明

1567064120936

1567064266212

响应:

{
    "counts": 4698,
    "pagesize": 20,
    "pages": 58,
    "page": 16,
    "items": [
        {
            "id": 1011,
            "avatar": "assets/images/avatar_2.png",
            "nickname": "黑马小妹",
            "gender": "woman",
            "age": 23,
            "tags": [
                "本科",
                "年龄相仿",
                "单身"
            ],
            "fateValue": 96
        },
        {
            "id": 2495,
            "avatar": "assets/images/avatar_1.png",
            "nickname": "米朵妹妹",
            "gender": "man",
            "age": 28,
            "tags": [
                "年龄相仿",
                "本科",
                "单身"
            ],
            "fateValue": 87
        }
        ......
    ]
}

2.3.2. RecommendUserQueryParam

在tanhua-domain模块vo下创建RecommendUserQueryParam

package com.tanhua.domain.vo;
import lombok.Data;
import java.io.Serializable;

@Data
public class RecommendUserQueryParam implements Serializable {

    private Integer page;
    private Integer pagesize;
    private String gender;
    private String lastLogin;
    private Integer age;
    private String city;
    private String education;
}

2.3.3. TodayBestController

在tanhua-server模块TodayBestController创建recommendList

/**
     * 推荐用户- 分页查询
     */
@RequestMapping(value = "/recommendation",method = RequestMethod.GET)
public ResponseEntity recommendation(RecommendUserQueryParam ruqp){
    PageResult<TodayBestVo> pageResult = todayBestService.recommendation(ruqp.getPage(),ruqp.getPagesize());
    return ResponseEntity.ok(pageResult);
}

2.3.4. TodayBestService

在tanhua-server模块TodayBestService创建recommendList

    /**
     * 今日佳人
     */
    public TodayBestVo todayBest() {
        Long toUserId = UserHolder.getUserId();//当前登录用户id
        //1.查询mongodb获取今日佳人用户
        RecommendUser recommendUser = recommendUserApi.queryWithMaxScore(toUserId);
        //2.如果没有查询到今日佳人 设置默认推荐用户
        if(recommendUser == null){
            recommendUser = new RecommendUser();
            recommendUser.setUserId(20l);//佳人用户id
            recommendUser.setToUserId(toUserId);//当前用户id
            recommendUser.setScore(99d);//缘分值
        }
        //3.查询推荐用户的用户信息tb_user_info
        TodayBestVo vo = getTodayBestVo(recommendUser);
        return vo;
    }

    /**
     * 推荐用户-分页 分页查询
     */
    public PageResult<TodayBestVo> recommendation(Integer page, Integer pagesize) {
        Long userId = UserHolder.getUserId();//当前用户id
        //1 分页查询推荐用户列表
        PageResult<RecommendUser> recommendUserPageResult =  recommendUserApi.findPageRecommendUser(userId,page,pagesize);
        //2.如果分页列表数据为空 则设置默认分页数据(10条)
        if(recommendUserPageResult == null || CollectionUtils.isEmpty(recommendUserPageResult.getItems())){
            recommendUserPageResult = new PageResult<>(10l,10l,1l,1l,defaultRecommend());
        }
        //3 循环遍历 推荐用户列表 查询tb_userInfo
        List<TodayBestVo> todayBestVoList = new ArrayList<>();
        for (RecommendUser recommendUser : recommendUserPageResult.getItems()) {
            TodayBestVo vo = getTodayBestVo(recommendUser);
            todayBestVoList.add(vo);
        }
        //4.将数据封装PageResult<TodayBestVo>
        PageResult<TodayBestVo> voPageResult = new PageResult<>();
        BeanUtils.copyProperties(recommendUserPageResult,voPageResult); //copy counts pagesize pages page
        voPageResult.setItems(todayBestVoList);//设置返回集合Vo
        return voPageResult;
    }

    private TodayBestVo getTodayBestVo(RecommendUser recommendUser) {
        //3.查询推荐用户的用户信息tb_user_info
        UserInfo userInfo = userInfoApi.findUserInfoByUserId(recommendUser.getUserId());
        //4.构建vo返回
        TodayBestVo vo = new TodayBestVo();
        BeanUtils.copyProperties(userInfo, vo);//nickname avatar gender age
        if (!StringUtils.isEmpty(userInfo.getTags())) {
            vo.setTags(userInfo.getTags().split(","));
        }
        vo.setId(recommendUser.getUserId());//佳人用户id
        vo.setFateValue(recommendUser.getScore().longValue());//缘分值
        return vo;
    }


    //构造默认数据
    private List<RecommendUser> defaultRecommend() {
        String ids = "1,2,3,4,5,6,7,8,9,10";
        List<RecommendUser> records = new ArrayList<>();
        for (String id : ids.split(",")) {
            RecommendUser recommendUser = new RecommendUser();
            recommendUser.setUserId(Long.valueOf(id));
            recommendUser.setScore(RandomUtils.nextDouble(70, 98));
            records.add(recommendUser);
        }
        return records;
    }

2.2. 服务提供者-首页推荐

2.2.1. RecommendUserApi

在tanhua-dubbo-interface模块下创建RecommendUserApi

/**
     * 首页推荐-分页列表查询
     * @param userId
     * @param page
     * @param pagesize
     * @return
     */
PageResult<RecommendUser> findPageRecommendUser(Long userId, Integer page, Integer pagesize);

2.2.2 RecommendUserApiImpl

在tanhua-dubbo-service模块下创建RecommendUserApiImpl

    /**
     * 首页推荐-分页列表查询
     * @param userId
     * @param page
     * @param pagesize
     * @return
     */
    @Override
    public PageResult<RecommendUser> findPageRecommendUser(Long userId, Integer page, Integer pagesize) {
        Query query = new Query();
        query.addCriteria(Criteria.where("toUserId").is(userId));
        query.limit(pagesize);
        query.skip((page-1)*pagesize);
        long counts = mongoTemplate.count(query, RecommendUser.class);
        //2,查询当前页面数据
        List<RecommendUser> recommendUserList = mongoTemplate.find(query, RecommendUser.class);
        //3.封装PageResult对象返回
        //pages //总页数
        long pages =  counts/pagesize + (counts%pagesize > 0 ?1:0);
        return new PageResult<>(counts,(long)pagesize,pages,(long)page,recommendUserList);
    }

2.3.3. 测试

postman测试

61327309443

app测试

61372551142

【小结】

掌握首页推荐功能

3. 圈子功能

【目标】

圈子功能需求介绍

圈子功能功能分析

圈子功能功能实现

【路径】

1:了解圈子功能需求

2:圈子功能分析

3:圈子功能实现

【讲解】

3.1. 功能介绍

探花交友项目中的圈子功能,类似微信的朋友圈,基本的功能为:发布动态、浏览好友动态、浏览推荐动态、点赞、评论、喜欢等功能。

1567496518185

发布:

1567497614205

3.2. 实现方案分析

对于圈子功能的实现,我们需要对它的功能特点做分析:

  • 数据量会随着用户数增大而增大
  • 读多写少
  • 非好友看不到其动态内容
  • ……

针对以上特点,我们来分析一下:

  • 对于数据量大而言,显然不能够使用关系型数据库进行存储,我们需要通过MongoDB进行存储
  • 对于读多写少的应用,需要减少读取的成本
    • 比如说,一条SQL语句,单张表查询一定比多张表查询要快
  • 对于每个人数据在存储层面最好做到相互隔离,这样的话就不会有影响

所以对于存储而言,主要是核心的6张表:

  • 发布表:记录了所有用户的发布的东西信息,如图片、视频等。
  • 相册:相册是每个用户独立的,记录了该用户所发布的所有内容。
  • 评论:针对某个具体发布的朋友评论和点赞操作。
  • 时间线:所谓“刷朋友圈”,就是刷时间线,就是一个用户所有的朋友的发布内容。
  • 好友表:记录好友关系
  • 推荐圈子表:记录推荐动态表

3.3. 技术方案(重点)

根据之前我们的分析,对于技术方案而言,将采用MongoDB+Redis来实现,其中MongoDB负责存储,Redis负责缓存数据。

3.1.1. 发布流程

1567523191578

流程说明:

  • 用户发布动态,首先将动态内容写入到发布表。
  • 然后,将发布的指向写入到自己的相册表中。
  • 最后,将发布的指向写入到好友的时间线中。

3.1.2. 查看流程

1567525088273

流程说明:

  • 用户查看动态,如果查看自己的动态,直接查询相册表即可
  • 如果查看好友动态,查询时间线表即可
  • 如果查看推荐动态,查看推荐表即可

由此可见,查看动态的成本较低,可以快速的查询到动态数据。

3.4. 数据库表分析-重点理解

数据库表使用分析-重点掌握

63547263658

为什么这么设计表?-了解

****为了提升查询速度,因为查询频率远远高于写入频率,所以设计时间线表 相册表,这2张表数据远远小于发布表。直接在这2个表中做分页查询速度比较快的,查询完成后得到发布id,再根据发布id查询发布表,得到动态数据。

3.4.1. 数据库表

① 发布表

#表名:quanzi_publish
{
    "id":1,#主键id
    "userId":1, #用户id
    "text":"今天心情很好", #文本内容
    "medias":"http://xxxx/x/y/z.jpg", #媒体数据,图片或小视频 url
    "seeType":1, #谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看
    "seeList":[1,2,3], #部分可见的列表
    "notSeeList":[4,5,6],#不给谁看的列表
	"longitude":108.840974298098,#经度
	"latitude":34.2789316522934,#纬度
    "locationName":"上海市浦东区", #位置名称
    "created",1568012791171 #发布时间
}

② 相册表

#表名:quanzi_album_{userId}
{
    "id":1,#主键id
    "publishId":1001, #发布id
    "created":1568012791171 #发布时间
}

③ 时间线表

#表名:quanzi_time_line_{userId}
{
    "id":1,#主键id,
    "userId":2, #好友id
    "publishId":1001, #发布id
    "date":1568012791171 #发布时间
}

④ 评论表

#表名:quanzi_comment
{
    "id":1, #主键id
    "publishId":1001, #发布id
    "commentType":1, #评论类型,1-点赞,2-评论,3-喜欢
    "content":"给力!", #评论内容
    "userId":2, #评论人
    "isParent":false, #是否为父节点,默认是否
    "parentId":1001, #父节点id
    "created":1568012791171
}

⑤ 好友表

#表名:tanhua_users
{
    "id":1, #主键id
    "userId":1001, #用户id
    "friendId":1,  #好友id
    "created":1568012791171
}

⑥ 推荐动态表

#表名:recommend_quanzi
{ 
    "id" : 1, #主键id
    "userId" : 1001, #用户id
    "score" : 9.0, 
    "created" : 1568012791171,  
    "publishId":1001, #发布id
}

3.4.2. 实体类与VO

tanhua-domain模块mongo包中创建以下实体对象

① Publish

package com.tanhua.domain.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;
import java.util.List;

/**
 * <p>
 * 发布的动态信息表
 * </p>
 */
@Data
@Document(collection = "quanzi_publish")
public class Publish implements Serializable {

    private ObjectId id; //主键id
    private Long pid; //Long类型,用于推荐系统的模型
    private Long userId;
    private String textContent; //文字

    private List<String> medias; //媒体数据,图片或小视频 url
    private Integer seeType; // 谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看

    private String longitude; //经度
    private String latitude; //纬度
    private String locationName; //位置名称
    private Long created; //发布时间

    private Integer likeCount=0; //点赞数
    private Integer commentCount=0; //评论数
    private Integer loveCount=0; //喜欢数
}

② Album

package com.tanhua.domain.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

/**
 * <p>
 * 相册表,用于存储自己发布的数据,每一个用户一张表进行存储
 * </p>
 */
@Data
public class Album implements Serializable {

    private ObjectId id; //主键id
    private ObjectId publishId; //发布id
    private Long created; //发布时间
}

③ TimeLine

package com.tanhua.domain.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

/**
 * <p>
 * 时间线表,用于存储发布(或推荐)的数据,每一个用户一张表进行存储
 * </p>
 */
@Data
public class TimeLine implements Serializable {

    private ObjectId id;

    private Long userId; // 好友id
    private ObjectId publishId; //发布id

    private Long created; //发布的时间
}

④ Friend

package com.tanhua.domain.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

/**
 * <p>
 * 好友关系
 * </p>
 */
@Data
@Document(collection = "tanhua_users")
public class Friend implements Serializable {
    private ObjectId id;
    private Long userId; //用户id
    private Long friendId; //好友id
    private Long created; //时间
}

⑤ RecommendQuanzi

package com.tanhua.domain.mongo;

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

import java.io.Serializable;

/**
 * <p>
 * 推荐动态
 * </p>
 */
@Data
@Document(collection = "recommend_quanzi")
public class RecommendQuanzi implements Serializable {

    @Id
    private ObjectId id; // 主键
    @Indexed
    private Long userId; // 推荐的用户id
    private Long pid;
    private ObjectId publishId; // 发布的动态的id
    @Indexed
    private Double score = 0d; // 推荐分数
    private Long created; // 日期
}

tanhua-domain模块vo包中创建以下VO

⑥ PublishVo

package com.tanhua.domain.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;

@Data
public class PublishVo implements Serializable {

    private Long userId; // 用户id
    private String textContent; // 文本内容
    private String location; // 地理位置
    private String longitude; // 经度
    private String latitude; // 纬度
    private List<String> medias; // 图片url
}

⑦ MomentVo

package com.tanhua.domain.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class MomentVo implements Serializable {
    private String id; //动态id

    private Long userId; //用户id
    private String avatar; //头像
    private String nickname; //昵称
    private String gender; //性别 man woman
    private Integer age; //年龄
    private String[] tags; //标签


    private String textContent; //文字动态
    private String[] imageContent; //图片动态
    private String distance; //距离
    private String createDate; //发布时间 如: 10分钟前
    private int likeCount; //点赞数
    private int commentCount; //评论数
    private int loveCount; //喜欢数


    private Integer hasLiked; //是否点赞(1是,0否)
    private Integer hasLoved; //是否喜欢(1是,0否)
}

3.5. 服务消费者-发布动态

61373460207

3.5.1. 接口说明

61373185635

3.5.2. MomentController

tanhua-server模块编写MomentController,完成发布动态功能

package com.tanhua.server.controller;

import com.tanhua.domain.vo.PublishVo;
import com.tanhua.server.service.MovementsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

/**
 * 圈子管理-控制层
 */
@RestController
@RequestMapping("/movements")
public class MovementsController {

    @Autowired
    private MovementsService movementsService;

    /**
     * 发布动态
     */
    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity savePublish(PublishVo publishVo, MultipartFile[] imageContent) throws IOException {
        movementsService.savePublish(publishVo,imageContent);
        return ResponseEntity.ok(null);
    }


}

3.5.3. MovementsService

tanhua-server模块编写MovementsService,完成发布动态功能

package com.tanhua.server.service;

import com.tanhua.commons.templates.OssTemplate;
import com.tanhua.domain.vo.PublishVo;
import com.tanhua.dubbo.api.mongo.PublishApi;
import com.tanhua.server.interceptor.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 圈子管理-业务逻辑处理层
 */
@Service
@Transactional
@Slf4j //日志注解
public class MovementsService {

    @Reference
    private PublishApi publishApi;

    @Autowired
    private OssTemplate ossTemplate;

    /**
     * 发布动态
     */
    public void savePublish(PublishVo publishVo, MultipartFile[] imageContent) throws IOException {
        Long userId = UserHolder.getUserId();
        // 1.封装PublishVo 用户id
        publishVo.setUserId(userId);//当前用户id
        //2 图片地址
        List<String> medias = new ArrayList<>();
        if(imageContent != null && imageContent.length>0) {
            for (MultipartFile multipartFile : imageContent) {
                String imgUrl = ossTemplate.upload(multipartFile.getOriginalFilename(), multipartFile.getInputStream());
                medias.add(imgUrl);
            }
        }
        publishVo.setMedias(medias);//图片地址
        //3. 调用服务将publishVo传入
        publishApi.savePublish(publishVo);
    }
}

3.6. 服务提供者-发布动态

3.6.1. PublishApi

tanhua-dubbo-interface模块mongo包中创建PublishApi

package com.tanhua.dubbo.api.mongo;

import com.tanhua.domain.vo.PublishVo;

/**
 * 圈子服务接口
 */
public interface PublishApi {
    /**
     * 发布动态
     * @param publishVo
     */
    void savePublish(PublishVo publishVo);
}

3.6.2. PublishApiImpl

tanhua-dubbo-service模块mongo包中创建PublishApiImpl

package com.tanhua.dubbo.api.mongo;

import com.tanhua.domain.mongo.Album;
import com.tanhua.domain.mongo.Friend;
import com.tanhua.domain.mongo.Publish;
import com.tanhua.domain.mongo.TimeLine;
import com.tanhua.domain.vo.PublishVo;
import org.apache.dubbo.config.annotation.Service;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * 圈子服务接口实现类
 */
@Service
public class PublishApiImpl implements PublishApi{

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 发布动态
     * @param publishVo
     */
    @Override
    public void savePublish(PublishVo publishVo) {
        Long userId = publishVo.getUserId();///当前用户id
        long nowTime = System.currentTimeMillis();
        //1.往发布表插入动态数据
        Publish publish = new Publish();
        BeanUtils.copyProperties(publishVo,publish);//用户id 文本内容 经纬度 图片URL
        publish.setLocationName(publishVo.getLocation());//地址位置
        publish.setId(ObjectId.get());//设置主键id 后续相册表 时间线表 都跟此id 产生关系
        publish.setPid(66l);//推荐系统 忽略此字段即可
        publish.setSeeType(1);//谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看
        publish.setCreated(nowTime);//发布时间
        mongoTemplate.insert(publish);
        //2.往相册表插入我的动态记录
        Album album = new Album();
        album.setId(ObjectId.get());//主键id
        album.setPublishId(publish.getId());//发布id
        album.setCreated(nowTime);//当前时间
        mongoTemplate.insert(album,"quanzi_album_"+userId);
        //3,查询好友表 得到好友ids
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(userId));
        List<Friend> friendList = mongoTemplate.find(query, Friend.class);
        //4.根据好友ids往好友时间线表插入数据
        if(!CollectionUtils.isEmpty(friendList)){
            for (Friend friend : friendList) {
                TimeLine timeLine = new TimeLine();
                timeLine.setId(ObjectId.get());//主键id
                timeLine.setUserId(userId);//好友id
                timeLine.setPublishId(publish.getId());//发布id
                timeLine.setCreated(nowTime);//创建时间
                mongoTemplate.insert(timeLine,"quanzi_time_line_"+friend.getFriendId());
            }
        }
    }
}

3.6.3. 测试

postman测试

image-20201116164126272

结果:1567687806725

app测试

1567688253472

1567688271394

3.7. 服务消费者-查询好友动态

查询好友动态其实就是查询自己的时间线表,好友在发动态时已经将动态信息写入到了自己的时间线表中。

61374189779

3.7.1. MomentController

tanhua-server模块controller包中修改 MomentController 完成查询好友动态功能

/**
     * 好友动态
     */
@RequestMapping(method = RequestMethod.GET)
public ResponseEntity findPagePublish(@RequestParam(defaultValue = "1") int page,
                                      @RequestParam(defaultValue = "10")  int pagesize) {
    PageResult<MomentVo>  pageResult = movementsService.findPagePublish(page,pagesize);
    return ResponseEntity.ok(pageResult);
}

3.7.2. MomentService

tanhua-server模块service包中修改 MomentService 完成查询好友动态功能

/**
     * 好友动态
     */
public PageResult<MomentVo> findPagePublish(int page, int pagesize) {
    Long userId = UserHolder.getUserId();//当前用户id
    //1调用服务 分页查询发布表数据
    PageResult<Publish> pagePublish =publishApi.findPagePublish(userId,page,pagesize);
    if(pagePublish == null || CollectionUtils.isEmpty(pagePublish.getItems())){
        return new PageResult<>(0l,10l,0l,1l,null);
        //return null;
    }
    //2根据用户id查询用户信息
    //2.1将publish  userInfo  转为 MomentVo
    List<MomentVo> momentVoList = new ArrayList<>();
    for (Publish publish : pagePublish.getItems()) {
        MomentVo momentVo = new MomentVo();
        Long publishUserId = publish.getUserId();//动态发布者用户id
        //用户信息
        UserInfo userInfo = userInfoApi.findUserInfoByUserId(publishUserId);
        //copy
        BeanUtils.copyProperties(publish,momentVo);//userId textContent likeCount commentCount  loveCount
        BeanUtils.copyProperties(userInfo,momentVo);// nickname avatar  gender  age


        //设置动态id
        momentVo.setId(publish.getId().toHexString());//动态id
        //设置标签
        if(!StringUtils.isEmpty(userInfo.getTags())){
            momentVo.setTags(userInfo.getTags().split(","));
        }
        //图片url地址 将List<String> medias 转为 String[] imageContent
        momentVo.setImageContent(publish.getMedias().toArray(new String[]{}));
        momentVo.setDistance("1米");//距离
        //发布时间
        momentVo.setCreateDate(RelativeDateFormat.format(new Date(publish.getCreated())));
        //是否点赞 是否喜欢
        momentVo.setHasLiked(0);
        momentVo.setHasLoved(0);
        momentVoList.add(momentVo);
    }
    //3封装数据返回
    PageResult<MomentVo> voPageResult = new PageResult<>();
    BeanUtils.copyProperties(pagePublish,voPageResult);
    voPageResult.setItems(momentVoList);
    return voPageResult;
}

3.7.3. RelativeDateFormat

tanhua-server模块utils包中创建RelativeDateFormat

package com.tanhua.server.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class RelativeDateFormat {
 
    private static final long ONE_MINUTE = 60000L;
    private static final long ONE_HOUR = 3600000L;
    private static final long ONE_DAY = 86400000L;
    private static final long ONE_WEEK = 604800000L;
 
    private static final String ONE_SECOND_AGO = "秒前";
    private static final String ONE_MINUTE_AGO = "分钟前";
    private static final String ONE_HOUR_AGO = "小时前";
    private static final String ONE_DAY_AGO = "天前";
    private static final String ONE_MONTH_AGO = "月前";
    private static final String ONE_YEAR_AGO = "年前";
 
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:m:s");
        Date date = format.parse("2013-11-11 18:35:35");
        System.out.println(format(date));
    }
 
    public static String format(Date date) {
        long delta = new Date().getTime() - date.getTime();
        if (delta < 1L * ONE_MINUTE) {
            long seconds = toSeconds(delta);
            return (seconds <= 0 ? 1 : seconds) + ONE_SECOND_AGO;
        }
        if (delta < 45L * ONE_MINUTE) {
            long minutes = toMinutes(delta);
            return (minutes <= 0 ? 1 : minutes) + ONE_MINUTE_AGO;
        }
        if (delta < 24L * ONE_HOUR) {
            long hours = toHours(delta);
            return (hours <= 0 ? 1 : hours) + ONE_HOUR_AGO;
        }
        if (delta < 48L * ONE_HOUR) {
            return "昨天";
        }
        if (delta < 30L * ONE_DAY) {
            long days = toDays(delta);
            return (days <= 0 ? 1 : days) + ONE_DAY_AGO;
        }
        if (delta < 12L * 4L * ONE_WEEK) {
            long months = toMonths(delta);
            return (months <= 0 ? 1 : months) + ONE_MONTH_AGO;
        } else {
            long years = toYears(delta);
            return (years <= 0 ? 1 : years) + ONE_YEAR_AGO;
        }
    }
 
    private static long toSeconds(long date) {
        return date / 1000L;
    }
 
    private static long toMinutes(long date) {
        return toSeconds(date) / 60L;
    }
 
    private static long toHours(long date) {
        return toMinutes(date) / 60L;
    }
 
    private static long toDays(long date) {
        return toHours(date) / 24L;
    }
 
    private static long toMonths(long date) {
        return toDays(date) / 30L;
    }
 
    private static long toYears(long date) {
        return toMonths(date) / 365L;
    }
 
}

3.8. 服务提供者-查询好友动态

3.8.1. PublishApi

tanhua-dubbo-interface模块mongo包中创建PublishApi

package com.tanhua.dubbo.api.mongo;
import com.tanhua.domain.vo.PageResult;
import com.tanhua.domain.vo.PublishVo;

/**
 * <p>
 *  圈子动态api
 * </p>
 */
public interface PublishApi {

    /**
     * 好友动态分页查询
     * @param userId
     * @param page
     * @param pagesize
     * @return
     */
    PageResult<Publish> findPagePublish(Long userId, int page, int pagesize);
}

3.8.2. PublishApiImpl

tanhua-dubbo-service模块mongo包中创建PublishApiImpl

package com.tanhua.dubbo.api.mongo;

import com.tanhua.domain.mongo.*;
import com.tanhua.domain.vo.PageResult;
import com.tanhua.domain.vo.PublishVo;
import com.tanhua.dubbo.utils.IdService;
import org.apache.dubbo.config.annotation.Service;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 圈子动态api的实现,也为其服务的提供者
 * </p>
 */
@Service
public class PublishApiImpl implements PublishApi {

    @Autowired
    private MongoTemplate mongoTemplate;

        /**
     * 好友动态
     * @param userId
     * @param page
     * @param pagesize
     * @return
     */
    @Override
    public PageResult<Publish> findPagePublish(Long userId, int page, int pagesize) {
        //1查询好友时间线表(分页查询)
        Query query = new Query();
        query.limit(pagesize);
        query.skip((page-1)*pagesize);
        query.with(Sort.by(Sort.Direction.DESC,"created"));
        long counts = mongoTemplate.count(query, "quanzi_time_line_"+userId);
        //1.1,查询当前页面数据
        List<TimeLine> timeLineList = mongoTemplate.find(query,TimeLine.class, "quanzi_time_line_"+userId);

        //2再查询发布表动态数据
        if(CollectionUtils.isEmpty(timeLineList)){
            return new PageResult<>(counts,(long)pagesize,0l,(long)page,null);
        }
        //将List<TimeLine> 转为 List<Publish>
        List<Publish> publishList = new ArrayList<>();
        for (TimeLine timeLine : timeLineList) {
            //2.1根据发布id 查询 发布表
            ObjectId publishId = timeLine.getPublishId();//发布id
            Publish publish = mongoTemplate.findById(publishId, Publish.class);
            if(publish != null) {
                publishList.add(publish);
            }
        }
        //3返回PageResult<Publish>分页数据
        long pages =  counts/pagesize + (counts%pagesize > 0 ?1:0);
        return new PageResult<>(counts,(long)pagesize,pages,(long)page,publishList);
    }
}

3.8.3. 测试

61330924237

3.9. 服务消费者-查询推荐动态

推荐动态是通过推荐系统计算出的结果,现在我们只需要实现查询即可。

推荐动态和好友动态的结构是一样的,所以我们只需要查询推荐的时间表即可。

61374538102

3.9.1. MomentController

tanhua-server模块controller包中修改MomentController 完成查询推荐动态功能

    /**
     * 推荐动态-陌生人动态
     */
    @RequestMapping(value = "/recommend",method = RequestMethod.GET)
    public ResponseEntity findPageRecommend(@RequestParam(defaultValue = "1") int page,
                                          @RequestParam(defaultValue = "10")  int pagesize) {
        PageResult<MomentVo>  pageResult = movementsService.findPageRecommend(page,pagesize);
        return ResponseEntity.ok(pageResult);
    }

3.9.2. MomentService

tanhua-server模块service包中修改MomentService完成查询推荐动态功能

/**
     * 好友动态
     */
    public PageResult<MomentVo> findPagePublish(int page, int pagesize) {
        Long userId = UserHolder.getUserId();//当前用户id
        //1调用服务 分页查询发布表数据
        PageResult<Publish> pagePublish =publishApi.findPagePublish(userId,page,pagesize);
        if(pagePublish == null || CollectionUtils.isEmpty(pagePublish.getItems())){
            return new PageResult<>(0l,10l,0l,1l,null);
        }
        return getMomentVoPageResult(pagePublish);
    }

    /**
     * 将publish  userInfo  转为 MomentVo 动态数据转换公共方法
     * @param pagePublish
     * @return
     */
    private PageResult<MomentVo> getMomentVoPageResult(PageResult<Publish> pagePublish) {
        //2根据用户id查询用户信息
        //2.1将publish  userInfo  转为 MomentVo
        List<MomentVo> momentVoList = new ArrayList<>();
        for (Publish publish : pagePublish.getItems()) {
            MomentVo momentVo = new MomentVo();
            Long publishUserId = publish.getUserId();//动态发布者用户id
            //用户信息
            UserInfo userInfo = userInfoApi.findUserInfoByUserId(publishUserId);
            //copy
            BeanUtils.copyProperties(publish,momentVo);//userId textContent likeCount commentCount  loveCount
            BeanUtils.copyProperties(userInfo,momentVo);// nickname avatar  gender  age


            //设置动态id
            momentVo.setId(publish.getId().toHexString());//动态id
            //设置标签
            if(!StringUtils.isEmpty(userInfo.getTags())){
                momentVo.setTags(userInfo.getTags().split(","));
            }
            //图片url地址 将List<String> medias 转为 String[] imageContent
            momentVo.setImageContent(publish.getMedias().toArray(new String[]{}));
            momentVo.setDistance("1米");//距离
            //发布时间
            momentVo.setCreateDate(RelativeDateFormat.format(new Date(publish.getCreated())));
            //是否点赞 是否喜欢
            momentVo.setHasLiked(0);
            momentVo.setHasLoved(0);
            momentVoList.add(momentVo);
        }
        //3封装数据返回
        PageResult<MomentVo> voPageResult = new PageResult<>();
        BeanUtils.copyProperties(pagePublish,voPageResult);
        voPageResult.setItems(momentVoList);
        return voPageResult;
    }


    /**
     * 推荐动态-陌生人动态
     */
    public PageResult<MomentVo> findPageRecommend(int page, int pagesize) {
        Long userId = UserHolder.getUserId();//当前用户id
        //1调用服务 分页查询发布表数据
        PageResult<Publish> pagePublish =publishApi.findPageRecommend(userId,page,pagesize);
        if(pagePublish == null || CollectionUtils.isEmpty(pagePublish.getItems())){
            return new PageResult<>(0l,10l,0l,1l,null);
        }
        return getMomentVoPageResult(pagePublish);
    }

3.10. 服务提供者-查询推荐动态

3.10.1. PublishApi

tanhua-dubbo-interface模块mongo包中创建PublishApi

package com.tanhua.dubbo.api.mongo;
import com.tanhua.domain.vo.PageResult;
import com.tanhua.domain.vo.PublishVo;

/**
 * <p>
 *  圈子动态api
 * </p>
 */
public interface PublishApi {
    /**
     * 推荐动态-陌生人动态
     */
    PageResult<Publish> findPageRecommend(Long userId, int page, int pagesize);

}

3.10.2. PublishApiImpl

tanhua-dubbo-service模块mongo包中创建PublishApiImpl

package com.tanhua.dubbo.api.mongo;

import com.tanhua.domain.mongo.*;
import com.tanhua.domain.vo.PageResult;
import com.tanhua.domain.vo.PublishVo;
import com.tanhua.dubbo.utils.IdService;
import org.apache.dubbo.config.annotation.Service;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 圈子动态api的实现,也为其服务的提供者
 * </p>
 */
@Service
public class PublishApiImpl implements PublishApi {

    @Autowired
    private MongoTemplate mongoTemplate;

        /**
     * 推荐动态-陌生人动态
     */
    @Override
    public PageResult<Publish> findPageRecommend(Long userId, int page, int pagesize) {
        //1查询推荐动态表(分页查询)
        Query query = new Query();
        query.limit(pagesize);
        query.skip((page-1)*pagesize);
        query.addCriteria(Criteria.where("userId").is(userId));
        query.with(Sort.by(Sort.Direction.DESC,"created"));
        long counts = mongoTemplate.count(query, RecommendQuanzi.class);
        //1.1,查询当前页面数据
        List<RecommendQuanzi> recommendQuanziList = mongoTemplate.find(query,RecommendQuanzi.class);

        //2再查询发布表动态数据
        if(CollectionUtils.isEmpty(recommendQuanziList)){
            return new PageResult<>(counts,(long)pagesize,0l,(long)page,null);
        }
        //将List<RecommendQuanzi> 转为 List<Publish>
        List<Publish> publishList = new ArrayList<>();
        for (RecommendQuanzi recommendQuanzi : recommendQuanziList) {
            //2.1根据发布id 查询 发布表
            ObjectId publishId = recommendQuanzi.getPublishId();//发布id
            Publish publish = mongoTemplate.findById(publishId, Publish.class);
            if(publish != null) {
                publishList.add(publish);
            }
        }
        //3返回PageResult<Publish>分页数据
        long pages =  counts/pagesize + (counts%pagesize > 0 ?1:0);
        return new PageResult<>(counts,(long)pagesize,pages,(long)page,publishList);
    }
}

3.10.3. 测试

1.将publishId为空的数据删除

db.recommend_quanzi.remove({"publishId":null})

2.查询推荐动态数据

db.recommend_quanzi.find({userId:1}).sort({created:-1}).limit(10).skip(0);

手动修改publishId(id全部重复了)

61331900406

【小结】

掌握圈子功能实现

4. 缓存-作业

【目标】

掌握缓存使用

【路径】

1:Spring Cache简介

2:Spring Cache使用

【讲解】

实现缓存逻辑有2种方式:

  1. 每个接口单独控制缓存逻辑
  2. 统一控制缓存逻辑

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;

  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;

  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

  • 使用Spring缓存抽象时我们需要关注以下两点;

    1、确定方法需要被缓存以及他们的缓存策略

    2、从缓存中读取之前缓存存储的数据

4.1. 重要概念

名称 解释
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。 与@Cacheable区别在于是否每次都调用方法,常用于更新
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略
@CacheConfig 统一配置本类的缓存注解的属性

4.2. SPEL表达式

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

注意:

1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如

@Cacheable(key = "targetClass + methodName +#p0")

2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:

@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")

SpEL提供了多种运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&,||,!,and,or,not,between,instanceof
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],[1],$[…]

以上的知识点适合你遗忘的时候来查阅,下面正式进入学习!

4.3. 入门案例

4.3.1. 导入依赖

4.3.2. 开启缓存支持

然后在启动类注解@EnableCaching开启缓存

@SpringBootApplication
@EnableCaching  //开启缓存
public class DemoApplication{
 
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

4.3.3. 缓存@Cacheable

@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。

@Cacheable(value = "user" ,key = "#userId")
public User queryById(Long userId) {
    UserInfo user = new UserInfo();
    user.setId(userId);
    user.setNickname("ceshi");
    return user;
}

此处的value是必需的,它指定了你的缓存存放在哪块命名空间。

此处的key是使用的spEL表达式,参考上章。这里有一个小坑,如果你把methodName换成method运行会报错,观察它们的返回类型,原因在于methodNameStringmethohMethod

此处的User实体类一定要实现序列化public class User implements Serializable,否则会报java.io.NotSerializableException异常。

到这里,你已经可以运行程序检验缓存功能是否实现。

4.3.4. 更新@CachePut

@CachePut注解的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用 。简单来说就是用户更新缓存数据。但需要注意的是该注解的valuekey 必须与要更新的缓存相同,也就是与@Cacheable 相同。示例:

@CachePut(value = "user", key = "#userId")
public User updata(Long userId) {
   	UserInfo user = new UserInfo();
    user.setId(userId);
    user.setNickname("ceshi123");
    return user;
}

4.3.5. 清除@CacheEvict

@CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。

//清除一条缓存,key为要清空的数据
@CacheEvict(value="user",key="#id")
public void delect(int id) {
   //删除用户
}

【小结】

掌握Spring Cache缓存使用

作业

1.公共管理-作业

2.首页推荐-功能思路分析

3.发布动态-功能思路分析

4.好友动态-功能思路分析

5.推荐动态-功能思路分析

6.缓存

总结

1.首页推荐

页面发送首页推荐请求>controller(看接口文档)>service(业务处理 调用服务获取recommend_user 查询tb_user_info)==>将tb_user_info recommend数据封装vo返回

2.发布动态

点击发布动态发送请求>controller(看接口文档 接收两个参数)>service(业务处理将图片转为地址设置到vo 调用服务)==>a.往发布表插入动态数据 b.往相册表插入我的动态记录 c.查询好友表 得到好友ids d.根据好友ids往好友时间线表插入数据

3.好友动态

点击好友动态发送请求>controller(看接口文档 接收两个分页参数)>service(业务处理 调用服务获取好友动态数据 将动态数据 userInfo转为vo进行返回)==>a.查询好友时间表 b.查询发布表


4.推荐动态

点击好友动态发送请求>controller(看接口文档 接收两个分页参数)>service(业务处理 调用服务获取推荐动态数据 将动态数据 userInfo转为vo进行返回)==>a.查询推荐动态表 b.查询发布表


  1. ↩︎

posted on 2022-04-23 15:12  ofanimon  阅读(208)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css