• 使用Redis实现点赞功能


    参考1 参考2 参考3 参考4 redis命令

    以上是参考文章,以下是个人总结,可能没有以上总结的好,仅做自我复盘。

    点赞操作比较频繁,而且比较随意,所以数据变更很快,如果用mysql,会对mysql产生很大的压力,于是决定使用Redis,防止数据丢失,所以会定期将数据持久化同步到mysql中。

    然后开始进行分析,点赞功能需要保存的数据都有哪些,比如给某个新闻点赞,那么需要保存的数据需要有该新闻的id(topicId)、点赞的用户id(fromUid)、点赞的状态(status)。

    通过研究Redis的基本数据类型,最终觉得使用hash进行保存,定义一个点赞key(AGREE),所以最后的数据结构如下

    127.0.0.1:6379> hset AGREE 1::2 0

    将被点赞id和点赞用户进行拼接作为一个字段,最后一个0代表状态,0代表未点赞,1代表一点赞,在存入之前进行判断,进行相应的保存。

    大概的流程图如下:

     具体实现代码:

    RedisKeyUtils

    package org.jeecg.modules.common.util;
    
    /**
     * @Author: qiaochengqiang
     * @Date: 2022/2/17
     * @Description: 根据一定规则生成key
     **/
    public class RedisKeyUtils {
    
        //保存用户点赞数据的key
        public static final String MAP_KEY_USER_LIKED = "MAP_USER_LIKED";
        //保存用户被点赞数量的key
        public static final String MAP_KEY_USER_LIKED_COUNT = "MAP_USER_LIKED_COUNT";
        /**
         * 拼接被点赞的用户id和点赞的人的id作为key。格式 222222::333333::主题id
         * @param topicId likedUserId 被点赞的人id  == topicId
         * @param fromUid likedPostId 点赞的人的id  == fromUid
         * @return
         */
        //* @param likedUserId 被点赞的人id  == topicId
        //* @param likedPostId 点赞的人的id  == fromUid
        public static String getLikedKey(String topicId, String fromUid){
            StringBuilder builder = new StringBuilder();
            builder.append(topicId);
            builder.append("::");
            builder.append(fromUid);
            return builder.toString();
        }
    }

    LikedStatusEnum

    package org.jeecg.modules.common.util;
    
    import lombok.Getter;
    
    /**
     * @Author: qiaochengqiang
     * @Date: 2022/2/17
     * @Description: 用户点赞状态
     **/
    @Getter
    public enum LikedStatusEnum {
    
        LIKE(1, "点赞"),
        UNLIKE(0, "取消点赞/未点赞"),
                ;
        private Integer code;
        private String msg;
        LikedStatusEnum(Integer code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    }

    CommonAgree

    package org.jeecg.modules.agree.entity;
    
    import java.io.Serializable;
    import java.io.UnsupportedEncodingException;
    import java.util.Date;
    import java.math.BigDecimal;
    import java.util.Map;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;
    import com.fasterxml.jackson.annotation.JsonFormat;
    import org.springframework.format.annotation.DateTimeFormat;
    import org.jeecgframework.poi.excel.annotation.Excel;
    import org.jeecg.common.aspect.annotation.Dict;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.EqualsAndHashCode;
    import lombok.experimental.Accessors;
    
    /**
     * @Description: 点赞表
     * @Author: jeecg-boot
     * @Date:   2022-02-15
     * @Version: V1.0
     */
    @Data
    @TableName("common_agree")
    @Accessors(chain = true)
    @EqualsAndHashCode(callSuper = false)
    @ApiModel(value="common_agree对象", description="点赞表")
    public class CommonAgree implements Serializable {
        private static final long serialVersionUID = 1L;
    
        /**主键*/
        @TableId(type = IdType.ASSIGN_UUID)
        @ApiModelProperty(value = "主键")
        private java.lang.String id;
        /**创建人*/
        @ApiModelProperty(value = "创建人")
        private java.lang.String createBy;
        /**创建日期*/
        @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
        @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
        @ApiModelProperty(value = "创建日期")
        private java.util.Date createTime;
        /**更新人*/
        @ApiModelProperty(value = "更新人")
        private java.lang.String updateBy;
        /**更新日期*/
        @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
        @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
        @ApiModelProperty(value = "更新日期")
        private java.util.Date updateTime;
        /**所属部门*/
        @ApiModelProperty(value = "所属部门")
        private java.lang.String sysOrgCode;
        /**系统id*/
        @Excel(name = "系统id", width = 15)
        @ApiModelProperty(value = "系统id")
        private java.lang.String sysCode;
        /**租户id*/
        @Excel(name = "租户id", width = 15)
        @ApiModelProperty(value = "租户id")
        private java.lang.String tenantId;
        /**基础flag*/
        @Excel(name = "基础flag", width = 15)
        @ApiModelProperty(value = "基础flag")
        private java.lang.String baseFlag;
        /**点赞用户*/
        @Excel(name = "点赞用户", width = 15)
        @ApiModelProperty(value = "点赞用户")
        private java.lang.String fromUid;
        /**被点赞的评论或回复id*/
        @Excel(name = "被点赞的评论或回复id", width = 15)
        @ApiModelProperty(value = "被点赞的评论或回复id")
        private java.lang.String topicId;
        /**被点赞类型*/
        @Excel(name = "被点赞类型", width = 15)
        @ApiModelProperty(value = "被点赞类型")
        private java.lang.String topicType;
        /**被点赞用户*/
        @Excel(name = "被点赞用户", width = 15)
        @ApiModelProperty(value = "被点赞用户")
        private java.lang.String toUid;
        /**点赞状态*/
        @Excel(name = "点赞状态", width = 15)
        @ApiModelProperty(value = "点赞状态")
        private java.lang.Integer status;
    
        @TableField(exist = false)
        public Map<String,Integer> map;
    
        public CommonAgree() {
        }
    
        public CommonAgree(String topicId, String toUid, Integer status) {
            this.topicId = topicId;
            this.toUid = toUid;
            this.status = status;
        }
    }

    RedisService

    package org.jeecg.modules.common.service;
    
    import org.jeecg.modules.agree.entity.CommonAgree;
    
    import java.util.List;
    
    public interface RedisService {
    
        /**
         * 点赞。状态为1
         * @param topicId
         * @param fromUserId
         */
        void saveLiked2Redis(String topicId, String fromUserId);
        /**
         * 取消点赞。将状态改变为0
         * @param topicId
         * @param fromUserId
         */
        void unlikeFromRedis(String topicId, String fromUserId);
        /**
         * 从Redis中删除一条点赞数据
         * @param topicId
         * @param fromUserId
         */
        void deleteLikedFromRedis(String topicId, String fromUserId);
        /**
         * 该用户的点赞数加1
         * @param toUserId
         */
        void incrementLikedCount(String toUserId);
        /**
         * 该用户的点赞数减1
         * @param toUserId
         */
        void decrementLikedCount(String toUserId);
        /**
         * 获取Redis中存储的所有点赞数据
         * @return
         */
        List<CommonAgree> getLikedDataFromRedis();
    
    
        /**
         * 获取redis中的存储判断是否已经点赞
         * @return 可以根据key 字段 判断是否已经点赞 而不用拿到所有 然后进行遍历
         */
        CommonAgree checkIsAgreeFromRedis(String topidId,String fromUserId);
    
        /**
         * 根据主题id获取所有的点赞数
         * @return
         */
        List<CommonAgree> getAgreeFromRedisByTopicId(String topidId);
    
        /**
         * 根据用户id获取所有的点赞数
         * @return
         */
        List<CommonAgree> getAgreeFromRedisByFromUserId(String fromUserId);
    }

    RedisServiceImpl

    package org.jeecg.modules.common.service.impl;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.jeecg.modules.agree.entity.CommonAgree;
    import org.jeecg.modules.common.service.RedisService;
    import org.jeecg.modules.common.util.LikedStatusEnum;
    import org.jeecg.modules.common.util.RedisKeyUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.Cursor;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ScanOptions;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    @Service
    @Slf4j
    public class RedisServiceImpl implements RedisService {
    
        @Autowired
        RedisTemplate redisTemplate;
        @Override
        public void saveLiked2Redis(String topicId, String fromUserId) {
            String key = RedisKeyUtils.getLikedKey(topicId, fromUserId);
            //用户点赞,存储的键为:topicId::fromUId,对应的值为 1
            redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.LIKE.getCode());
        }
        @Override
        public void unlikeFromRedis(String topicId, String fromUserId) {
            String key = RedisKeyUtils.getLikedKey(topicId, fromUserId);
            //用户点赞,存储的键为:topicId::fromUId,对应的值为 0
            redisTemplate.opsForHash().put(RedisKeyUtils.MAP_KEY_USER_LIKED, key, LikedStatusEnum.UNLIKE.getCode());
        }
        @Override
        public void deleteLikedFromRedis(String topicId, String fromUserId) {
            String key = RedisKeyUtils.getLikedKey(topicId, fromUserId);
            redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);
        }
        @Override
        public void incrementLikedCount(String toUserId) {
            redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, toUserId, 1);
        }
        @Override
        public void decrementLikedCount(String toUserId) {
            redisTemplate.opsForHash().increment(RedisKeyUtils.MAP_KEY_USER_LIKED_COUNT, toUserId, -1);
        }
        @Override
        public List<CommonAgree> getLikedDataFromRedis() {
            Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);
            List<CommonAgree> list = new ArrayList<>();
            while (cursor.hasNext()){
                Map.Entry<Object, Object> entry = cursor.next();
                String key = (String) entry.getKey();
                //分离出 toUserId,fromUserId
                String[] split = key.split("::");
                //被点赞用户
                String topicId = split[0];
                String fromUserId = split[1];
                Integer value = (Integer) entry.getValue();
                //组装成 CommonAgree 对象
                CommonAgree userLike = new CommonAgree(topicId, fromUserId, value);
                list.add(userLike);
                //存到 list 后从 Redis 中删除
                redisTemplate.opsForHash().delete(RedisKeyUtils.MAP_KEY_USER_LIKED, key);
            }
            return list;
        }
    
        @Override
        public CommonAgree checkIsAgreeFromRedis(String topicId, String fromUserId) {
            //可以根据key 字段 判断是否已经点赞 而不用拿到所有 然后进行遍历
            String smallKey = topicId+"::"+fromUserId;
            CommonAgree commonAgree = new CommonAgree();
            Integer value = (Integer) redisTemplate.opsForHash().get(RedisKeyUtils.MAP_KEY_USER_LIKED,smallKey);
            if (value != null){  //如果能够查询到 则将查询到的数据直接进行赋值即可
                commonAgree.setStatus(value);
            }else{  //redis 如果没有 则认为是未点赞
                commonAgree.setStatus(LikedStatusEnum.UNLIKE.getCode());
            }
            return commonAgree;
        }
    
        @Override
        public List<CommonAgree> getAgreeFromRedisByTopicId(String topicId) {
            List<CommonAgree> commonAgrees = new ArrayList<>();
            Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);
            while (cursor.hasNext()){
                CommonAgree commonAgree = new CommonAgree();
                Map.Entry<Object, Object> entry = cursor.next();
                String key = (String) entry.getKey();
                //分离出 topicId,toUserId
                String[] split = key.split("::");
                //被点赞主题
                String topicIdRedis = split[0];
                String fromUserIdRedis = split[1];
                Integer value = (Integer) entry.getValue();
                //如果主题id 点赞用户id 以及 状态为1 则代表已经点赞
                if (StringUtils.equals(topicId,topicIdRedis)){
                    commonAgree.setTopicId(topicIdRedis);
                    commonAgree.setFromUid(fromUserIdRedis);
                    commonAgree.setStatus(value);
                    commonAgrees.add(commonAgree);
                }
            }
            return commonAgrees;
        }
    
        @Override
        public List<CommonAgree> getAgreeFromRedisByFromUserId(String fromUserId) {
            List<CommonAgree> commonAgrees = new ArrayList<>();
            Cursor<Map.Entry<Object, Object>> cursor = redisTemplate.opsForHash().scan(RedisKeyUtils.MAP_KEY_USER_LIKED, ScanOptions.NONE);
            while (cursor.hasNext()){
                CommonAgree commonAgree = new CommonAgree();
                Map.Entry<Object, Object> entry = cursor.next();
                String key = (String) entry.getKey();
                //分离出 topicId,toUserId
                String[] split = key.split("::");
                //被点赞主题
                String topicIdRedis = split[0];
                String fromUserIdRedis = split[1];
                Integer value = (Integer) entry.getValue();
                //如果主题id 点赞用户id 以及 状态为1 则代表已经点赞
                if (StringUtils.equals(fromUserId,fromUserIdRedis)){
                    commonAgree.setTopicId(topicIdRedis);
                    commonAgree.setFromUid(fromUserIdRedis);
                    commonAgree.setStatus(value);
                    commonAgrees.add(commonAgree);
                }
            }
            return commonAgrees;
        }
    }

    ICommonAgreeService

    package org.jeecg.modules.agree.service;
    
    import org.jeecg.modules.agree.entity.CommonAgree;
    import com.baomidou.mybatisplus.extension.service.IService;
    
    import java.util.List;
    
    /**
     * @Description: 点赞表
     * @Author: jeecg-boot
     * @Date:   2022-02-15
     * @Version: V1.0
     */
    public interface ICommonAgreeService extends IService<CommonAgree> {
    
        /**
         * @Author: qiaochengqiang
         * @Date: 2022/2/17
         * @Description: 通过点赞人和被点赞人查询是否存在点赞记录
         **/
        CommonAgree getByTopicIdAndFromUserId(String topicId, String fromUserId);
    
        /**
         * @Author: qiaochengqiang
         * @Date: 2022/2/17
         * @Description: 通过点赞人和被点赞人查询是否存在点赞记录
         **/
        List<CommonAgree> getAllByTopicIdOrFromUserId(String topicId, String fromUserId);
    
        /**
         * @Author: qiaochengqiang
         * @Date: 2022/2/17
         * @Description: 定时任务 将Redis里的点赞数据存入数据库中
         **/
        void transLikedFromRedis2DB();
    
    }

    CommonAgreeServiceImpl

    package org.jeecg.modules.agree.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import org.apache.commons.lang3.StringUtils;
    import org.jeecg.modules.agree.entity.CommonAgree;
    import org.jeecg.modules.agree.mapper.CommonAgreeMapper;
    import org.jeecg.modules.agree.service.ICommonAgreeService;
    import org.jeecg.modules.common.service.RedisService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    
    import java.util.List;
    
    /**
     * @Description: 点赞表
     * @Author: jeecg-boot
     * @Date:   2022-02-15
     * @Version: V1.0
     */
    @Service
    public class CommonAgreeServiceImpl extends ServiceImpl<CommonAgreeMapper, CommonAgree> implements ICommonAgreeService {
    
        @Autowired
        RedisService redisService;
    
        @Override
        public CommonAgree getByTopicIdAndFromUserId(String topicId, String fromUserId) {
            QueryWrapper<CommonAgree> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(CommonAgree::getFromUid,fromUserId);
            queryWrapper.lambda().eq(CommonAgree::getTopicId,topicId);
            return getOne(queryWrapper);
        }
    
        @Override
        public List<CommonAgree> getAllByTopicIdOrFromUserId(String topicId, String fromUserId) {
            QueryWrapper<CommonAgree> queryWrapper = new QueryWrapper<>();
            if (topicId != null && StringUtils.isNotEmpty(topicId)){
                queryWrapper.lambda().eq(CommonAgree::getTopicId,topicId);
            }
            if (fromUserId != null && StringUtils.isNotEmpty(fromUserId)){
                queryWrapper.lambda().eq(CommonAgree::getFromUid,fromUserId);
            }
            return list(queryWrapper);
        }
    
        @Override
        public void transLikedFromRedis2DB() {
            //数据同步的时候是否需要查询所有数据 只同步当天的是否可以
            List<CommonAgree> list = redisService.getLikedDataFromRedis();
            for (CommonAgree commonAgree : list) {
                CommonAgree cg = getByTopicIdAndFromUserId(commonAgree.getTopicId(), commonAgree.getFromUid());
                if (cg == null){
                    //没有记录,直接存入
                    save(commonAgree);
                }else{
                    //有记录,需要更新
                    cg.setStatus(commonAgree.getStatus());
                    save(cg);
                }
            }
        }
    }

    CommonAgreeController

    package org.jeecg.modules.agree.controller;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang3.StringUtils;
    import org.jeecg.common.api.vo.Result;
    import org.jeecg.common.system.query.QueryGenerator;
    import org.jeecg.common.util.oConvertUtils;
    import org.jeecg.modules.agree.entity.CommonAgree;
    import org.jeecg.modules.agree.service.ICommonAgreeService;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import lombok.extern.slf4j.Slf4j;
    
    import org.jeecg.modules.common.service.RedisService;
    import org.jeecg.modules.common.util.LikedStatusEnum;
    import org.jeecgframework.poi.excel.ExcelImportUtil;
    import org.jeecgframework.poi.excel.def.NormalExcelConstants;
    import org.jeecgframework.poi.excel.entity.ExportParams;
    import org.jeecgframework.poi.excel.entity.ImportParams;
    import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
    import org.jeecg.common.system.base.controller.JeecgController;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.web.multipart.MultipartHttpServletRequest;
    import org.springframework.web.servlet.ModelAndView;
    import com.alibaba.fastjson.JSON;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.jeecg.common.aspect.annotation.AutoLog;
    
     /**
     * @Description: 点赞表
     * @Author: jeecg-boot
     * @Date:   2022-02-15
     * @Version: V1.0
     */
    @Api(tags="点赞表")
    @RestController
    @RequestMapping("/common/commonAgree")
    @Slf4j
    public class CommonAgreeController extends JeecgController<CommonAgree, ICommonAgreeService> {
        @Autowired
        private ICommonAgreeService commonAgreeService;
    
        @Autowired
        private RedisService redisService;
        
        /**
         *   添加
         *
         * @param commonAgree
         * @return
         */
        @AutoLog(value = "点赞表-添加")
        @ApiOperation(value="点赞表-添加", notes="点赞表-添加")
        @PostMapping(value = "/add")
        public Result<?> add(@RequestBody CommonAgree commonAgree) {
            //点赞分两个步骤 首先保存到redis 定时计划保存到数据库中
            //将数据保存到 redis 保存是会根据状态判断是点赞还是取消点赞
            Integer status = commonAgree.getStatus();
            //如果状态为null 或者状态为0 则属于点赞
            if (status == null || LikedStatusEnum.UNLIKE.getCode() == status){
                redisService.saveLiked2Redis(commonAgree.getTopicId(), commonAgree.getFromUid());
            }else{  //取消点赞
                redisService.unlikeFromRedis(commonAgree.getTopicId(), commonAgree.getFromUid());
            }
            return Result.OK("点赞成功!");
        }
    
         /**
          * @Author: qiaochengqiang
          * @Date: 2022/2/17
          * @Description: 根据主题id和登录用户id查询是否已经点赞
          **/
         @AutoLog(value = "点赞表-通过id查询")
         @ApiOperation(value="点赞表-通过用户id和主题id查询", notes="点赞表-通过用户id和主题id查询")
         @GetMapping(value = "/queryAgree")
         public Result<?> queryAgree(@RequestParam(name="fromUid",required=true) String fromUid,
                                    @RequestParam(name="topicId",required=true) String topicId) {
             CommonAgree commonAgree = new CommonAgree();
             commonAgree = redisService.checkIsAgreeFromRedis(topicId, fromUid);
             return Result.OK(commonAgree);
         }
    
         /**
          * @Author: qiaochengqiang
          * @Date: 2022/2/17
          * @Description: 通过点赞id 被点赞主体id 以及被点赞用户id 进行查询
          * 统计一共被点了多少赞
          * 统计一共点了多少赞
          **/
         @AutoLog(value = "点赞表-通过id查询")
         @ApiOperation(value="点赞表-通过id查询", notes="点赞表-通过id查询")
         @GetMapping(value = "/queryAgreeByTopicIdOrFromUserId")
         public Result<?> queryAgreeByTopicIdOrFromUserId(@RequestParam(name="topicId",required=false) String topicId,
                                                          @RequestParam(name="fromUserId",required=false) String fromUserId) {
             //按照主题id 查询统计总数
             if (StringUtils.isEmpty(topicId) && StringUtils.isEmpty(fromUserId)){
                 return Result.error("参数不能都为空!");
             }
             if (StringUtils.isNotEmpty(topicId) && StringUtils.isNotEmpty(fromUserId)){
                 return Result.error("参数不能都存在!");
             }
             List<CommonAgree> commonAgrees = new ArrayList<>();
             if (StringUtils.isNotEmpty(topicId)){
                 //查询redis
                 List<CommonAgree> agreeFromRedisByTopicId = redisService.getAgreeFromRedisByTopicId(topicId);
                 commonAgrees.addAll(agreeFromRedisByTopicId);
             }
             //按照用户 查询一共点赞多少 给谁点赞
             if (StringUtils.isNotEmpty(fromUserId)){
                 List<CommonAgree> agreeFromRedisByFromUserId = redisService.getAgreeFromRedisByFromUserId(fromUserId);
                 commonAgrees.addAll(agreeFromRedisByFromUserId);
             }
             return Result.OK(commonAgrees);
         }
    }

    然后定时保存数据库中即可。

  • 相关阅读:
    [React Testing] Intro to Shallow Rendering
    人物-IT-马化腾:马化腾
    人物-IT-雷军:雷军
    Less:Less(CSS预处理语言)
    文学-谚语-英文谚语:英文谚语
    文明-墓-太阳墓:太阳墓
    地理-撒哈拉之眼:撒哈拉之眼
    生物-海底人:海底人
    地理-蓝洞:蓝洞
    文明-根达亚文明:根达亚文明
  • 原文地址:https://www.cnblogs.com/qcq0703/p/15908528.html
Copyright © 2020-2023  润新知