Spring Boot + MongoDB实现作品或文章的三级评论

用Spring Boot + MongoDB,做一个作品评论的功能,用户可以发表评论;其他用户可以对作品现有的评论进行回复;最顶级的评论者,可以对回复评论的人再回复评论。其中,评论的数据结构如下:

t_resource_comment {
	_id:,
	resource_id: 123,
	comment_user_id: "A",
	comment_user_name:,
	comment_content: "A的评论",
	ctime:,
	status:,
	comment_responses: [
		{
			response_user_id: "B",
			response_user_name:,
			response_content:[
				{ctime:, content:"B给A的第一个评论"},
				{ctime:, content:"B给A的第二个评论"},
				{ctime:, content:"B给A的第三个评论"}
			]
			get_reply:[
				{ctime:, content:"这是A给B的某一个评论的回复如果有就对应插入index对应的元素没有就是空串"},
				{ctime:, content:"A没有回复B这一条就是空串"},
				{ctime:, content:"A个神经病跳着回复了这一条评论,这数组的第三个元素就是A回复的内容"}
			]
		},
		{
			response_user_id: "C",
			response_user_name:,
			response_content:[
				{ctime:, content:"C给A的第一个评论"},
				{ctime:, content:"C给A的第二个评论"},
				{ctime:, content:"C给A的第三个评论"}
			]
			get_reply:[
				{ctime:, content:"这是A给C的某一个评论的回复如果有就对应插入index对应的元素没有就是空串"},
				{ctime:, content:"A没有回复C这一条就是空串"},
				{ctime:, content:"A个神经病跳着回复了这一条评论,这数组的第三个元素就是A回复的内容"}
			]
		},
		...
	]
}

比如,A对文章或作品123发表了一个评论;B看到发表的评论后,给A的评论回复了一个评论;A看到B的回复后,又对B的回复评论做了一个回复。一共三级。

创建一个Spring Boot项目,pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.fhbean.springboot</groupId>
	<artifactId>springboot-mongodb</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springboot-mongodb</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

评论相关的实体类:

package com.fhbean.springboot.mongodb.entity;

import java.util.Date;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

@Document(collection = "resource_comment")
public class ResourceComment {
	@Id
	private String id;
	
	/**
	 * 接收评论的资源ID或作品ID
	 */
	@Indexed
	@Field(value = "resource_id")
	private Long resourceId;
	
	/**
	 * 发表评论的用户ID
	 */
	@Indexed
	@Field(value = "comment_user_id")
	private Long commentUserId;
	
	/**
	 * 发表评论的用户名
	 */
	@Field(value = "comment_user_name")
	private String commentUserName;
	
	/**
	 * 发表的评论内容
	 */
	@Field(value = "comment_content")
	private String commentContent;
	
	/**
	 * 发表评论的时间
	 */
	private Date ctime;
	
	/**
	 * 此条评论的状态:0为正常....
	 */
	private String status;
	
	/**
	 * 对此评论的回复
	 */
	@Field(value = "comment_responses")
	private CommentResponse[] commentResponses;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public Long getResourceId() {
		return resourceId;
	}
	public void setResourceId(Long resourceId) {
		this.resourceId = resourceId;
	}
	public Long getCommentUserId() {
		return commentUserId;
	}
	public void setCommentUserId(Long commentUserId) {
		this.commentUserId = commentUserId;
	}
	public String getCommentUserName() {
		return commentUserName;
	}
	public void setCommentUserName(String commentUserName) {
		this.commentUserName = commentUserName;
	}
	public String getCommentContent() {
		return commentContent;
	}
	public void setCommentContent(String commentContent) {
		this.commentContent = commentContent;
	}
	public Date getCtime() {
		return ctime;
	}
	public void setCtime(Date ctime) {
		this.ctime = ctime;
	}
	public String getStatus() {
		return status;
	}
	public void setStatus(String status) {
		this.status = status;
	}
	public CommentResponse[] getCommentResponses() {
		return commentResponses;
	}
	public void setCommentResponses(CommentResponse[] commentResponses) {
		this.commentResponses = commentResponses;
	}

}

package com.fhbean.springboot.mongodb.entity;

import org.springframework.data.mongodb.core.mapping.Field;

public class CommentResponse {
	/**
	 * 回复评论的用户ID
	 */
	@Field(value = "response_user_id")
	private Long responseUserId;
	
	/**
	 * 回复评论的用户名
	 */
	@Field(value = "response_user_name")
	private String responseUserName;
	
	/**
	 * 回复的评论内容
	 */
	@Field(value = "response_contents")
	private ResponseContent[] responseContents;
	
	/**
	 * 一楼评论作者 对 回复评论者 的回复
	 */
	@Field(value = "get_replys")
	private ResponseContent[] getReplys;
	
	public Long getResponseUserId() {
		return responseUserId;
	}
	public void setResponseUserId(Long responseUserId) {
		this.responseUserId = responseUserId;
	}
	public String getResponseUserName() {
		return responseUserName;
	}
	public void setResponseUserName(String responseUserName) {
		this.responseUserName = responseUserName;
	}
	public ResponseContent[] getResponseContents() {
		return responseContents;
	}
	public void setResponseContents(ResponseContent[] responseContents) {
		this.responseContents = responseContents;
	}
	public ResponseContent[] getGetReplys() {
		return getReplys;
	}
	public void setGetReplys(ResponseContent[] getReplys) {
		this.getReplys = getReplys;
	}

}
package com.fhbean.springboot.mongodb.entity;

import java.util.Date;

public class ResponseContent {
	
	private Date ctime;
	private String content;
	
	public ResponseContent() {
		super();
	}
	
	public ResponseContent(Date ctime, String content) {
		super();
		this.ctime = ctime;
		this.content = content;
	}

	public Date getCtime() {
		return ctime;
	}
	public void setCtime(Date ctime) {
		this.ctime = ctime;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}

}

创建Repository接口

package com.fhbean.springboot.mongodb.reposistory;

import org.springframework.data.mongodb.repository.MongoRepository;

import com.fhbean.springboot.mongodb.entity.ResourceComment;

public interface ResourceCommentRepository extends MongoRepository<ResourceComment, String> {

}

创建Service

package com.fhbean.springboot.mongodb.service;

import java.util.List;

import com.fhbean.springboot.mongodb.entity.ResourceComment;

public interface ResourceCommentService {
	
	/**
	 * 查询某资源的所有评论
	 * 
	 * @param resourceId
	 * @return
	 */
	public List<ResourceComment> findResourceComment(Long resourceId);

	/**
	 * 对指定资源发表评论
	 * 
	 * @param resourceId      资源ID
	 * @param commentUserId   用户ID
	 * @param commentUserName 用户名
	 * @param commentContent  评论内容
	 * @return 1成功,0失败
	 */
	public int saveComment(Long resourceId, Long commentUserId, String commentUserName, String commentContent);
	
	/**
	 * 回复评论
	 * 
	 * @param id                 原评论的ID
	 * @param responseUserId     回复评论的用户ID
	 * @param responseUserName   回复评论的用户名
	 * @param responseContent    回复的内容
	 * @return 1成功,0失败
	 */
	public int saveResponse(String id, Long responseUserId, String responseUserName, String responseContent);

	/**
	 * 一楼评论者 回复二楼评论者 的评论
	 * 
	 * @param id                 原评论的ID
	 * @param responseUserId     回复评论的用户ID
	 * @param index              要回复的评论索引值,从0开始
	 * @param replyContent       一楼评论者 回复的内容
	 * @return 1成功,0失败
	 */
	public int saveReply(String id, Long responseUserId, int index, String replyContent);
}
package com.fhbean.springboot.mongodb.service.impl;

import java.util.Date;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.fhbean.springboot.mongodb.entity.CommentResponse;
import com.fhbean.springboot.mongodb.entity.ResourceComment;
import com.fhbean.springboot.mongodb.entity.ResponseContent;
import com.fhbean.springboot.mongodb.reposistory.ResourceCommentRepository;
import com.fhbean.springboot.mongodb.service.ResourceCommentService;

@Service(value = "resourceCommentService")
public class ResourceCommentServiceImpl implements ResourceCommentService {

	@Autowired
	private ResourceCommentRepository resourceCommentRepository;

	/**
	 * 查询某个资源的所有评论
	 */
	@Override
	public List<ResourceComment> findResourceComment(Long resourceId) {
		if (null == resourceId) {
			return null;
		}
		
		ResourceComment rc = new ResourceComment();
		rc.setResourceId(resourceId);
		return resourceCommentRepository.findAll(Example.of(rc));
	}

	/**
	 * 发表评论
	 */
	@Override
	public int saveComment(Long resourceId, Long commentUserId, String commentUserName, String commentContent) {
		//数据合法性校验 begin
		if (null == resourceId || null == commentUserId) {
			return 0;
		}
		
		if (StringUtils.isEmpty(commentUserName) || StringUtils.isEmpty(commentContent)) {
			return 0;
		}
		//数据合法性校验 end
		
		//保存评论 begin
		ResourceComment rc = new ResourceComment();
		rc.setResourceId(resourceId);
		rc.setCommentUserId(commentUserId);
		rc.setCommentUserName(commentUserName);
		rc.setCommentContent(commentContent);
		rc.setCtime(new Date());
		rc.setStatus("0"); //此处可采用常量或枚举类型
		resourceCommentRepository.save(rc);
		//保存评论 end
		
		return 0;
	}

	/**
	 * 回复评论
	 */
	@Override
	public int saveResponse(String id, Long responseUserId, String responseUserName, String responseContent) {
		//数据合法性校验 begin
		if (StringUtils.isEmpty(id)) {
			return 0;
		}
		
		if (null == responseUserId) {
			return 0;
		}
		
		if (StringUtils.isEmpty(responseUserName) || StringUtils.isEmpty(responseContent)) {
			return 0;
		}
		//数据合法性校验 end
		
		//查找指定评论 begin
		ResourceComment rc = new ResourceComment();
		rc.setId(id);
		
		Optional<ResourceComment> optional = resourceCommentRepository.findOne(Example.of(rc));
		if (!optional.isPresent()) {
			//未找到指定的评论
			return 0;
		}
		
		rc = optional.get();
		//查找指定评论 end
		
		//得到巳有回复
		CommentResponse[] crs = rc.getCommentResponses();
		if (null == crs) {
			//此评论之前没有任何人回复,当前回复作为第一条回复
			CommentResponse cr = getNewResponse(responseUserId, responseUserName, responseContent);
			rc.setCommentResponses(new CommentResponse[]{cr});
			
			resourceCommentRepository.save(rc);
			return 1;
		}
		
		//己有人回复过评论,判断responseUserId是否回复过评论 begin
		//responseUserId之前是否回复过此评论 begin
		boolean responsed = false;
		for (int i = 0; i < crs.length; i++) {
			if (crs[i].getResponseUserId().equals(responseUserId)) {
				responsed = true; //之前回复过
				ResponseContent[] rcsNew = getNewResponseContent(responseContent, crs, crs[i].getResponseContents());
				
				crs[i].setResponseContents(rcsNew);
				
				break; //已经回复,不再继续循环
			}
		}
		//己有人回复过评论,判断responseUserId是否回复过评论 end
		if (responsed) {
			//之前,responseUserId已经回复过此评论,在现有回复后追加一个回复即可
			resourceCommentRepository.save(rc);
			return 1;
		}
		
		//之前别人回复过此评论,但responseUserId没有回复过此评论 begin
		//将之前所有人的回复转储到新的数组
		CommentResponse[] crsNew = new CommentResponse[crs.length + 1];
		CommentResponse cr = getNewResponse(responseUserId, responseUserName, responseContent);
		for (int i = 0; i < crs.length; i++) {
			crsNew[i] = crs[i];
		}
		crsNew[crsNew.length - 1] = cr;
		//之前别人回复过此评论,但responseUserId没有回复过此评论 end
		rc.setCommentResponses(crsNew);
		
		resourceCommentRepository.save(rc);
		return 1;
	}

	/**
	 * 回复评论的评论
	 */
	@Override
	public int saveReply(String id, Long responseUserId, int index, String replyContent) {
		//数据合法性校验 begin
		if (StringUtils.isEmpty(id)) {
			return 0;
		}
		
		if (null == responseUserId) {
			return 0;
		}
		
		if (index < 0) {
			//索引值不能小于零
			return 0;
		}
		
		if (StringUtils.isEmpty(replyContent)) {
			return 0;
		}
		//数据合法性校验 end
		
		//查找指定评论 begin
		ResourceComment rc = new ResourceComment();
		rc.setId(id);
		
		Optional<ResourceComment> optional = resourceCommentRepository.findOne(Example.of(rc));
		if (!optional.isPresent()) {
			//未找到指定的评论
			return 0;
		}
		
		rc = optional.get();
		//查找指定评论 end
		
		CommentResponse[] crs = rc.getCommentResponses();
		if (null == crs || crs.length < 1) {
			//没有人回复过评论
			return 0;
		}
		
		for (int i = 0; i < crs.length; i++) {
			if (responseUserId.equals(crs[i].getResponseUserId())) {
				//找到responseUserId回复的评论
				ResponseContent[] rcs = crs[i].getResponseContents();//回复的评论数组
				if (null == rcs || index >= rcs.length) {
					//responseUserId没有对评论回复任何内容或index越界,指定的回复不存在,不再继续
					return 0;
				}
				
				ResponseContent[] grs = crs[i].getGetReplys();
				if (null == grs) {
					//之前没有回复过评论的评论
					grs = new ResponseContent[index+1];
					ResponseContent reply = new ResponseContent(new Date(), replyContent);
					grs[index] = reply;
					crs[i].setGetReplys(grs);
				} else {
					//之前回复过评论的评论
					if (index >= grs.length) {
						//索引值超过现有回复数组的长度,给数组增长
						ResponseContent[] grsNew = new ResponseContent[index + 1];
						ResponseContent reply = new ResponseContent(new Date(), replyContent);
						grsNew[index] = reply;
						crs[i].setGetReplys(grsNew);
					} else {
						//索引值未超过回复数组的长度,reply内容直接覆盖索引值的reply
						ResponseContent reply = new ResponseContent(new Date(), replyContent);
						grs[index] = reply;
						crs[i].setGetReplys(grs);
					}
				}
				
				resourceCommentRepository.save(rc);
				return 1;
			}
		}
		
		return 0;
	}
	
	private ResponseContent[] getNewResponseContent(String responseContent, CommentResponse[] crs, ResponseContent[] rcs) {
		ResponseContent[] rcsNew = null;
		int rcsSize = 0;
		if (null == rcs) {
			rcsSize++;
			rcsNew = new ResponseContent[rcsSize]; //防止程序异常,没有回复
		} else {
			rcsSize = rcs.length + 1;
			rcsNew = new ResponseContent[rcsSize];
			for (int i = 0; i < rcs.length; i++) {
				//原先的回复转储到新的数组
				rcsNew[i] = rcs[i];
			}
		}
		ResponseContent rcon = new ResponseContent();
		rcon.setCtime(new Date());
		rcon.setContent(responseContent);
		rcsNew[rcsSize - 1] = rcon;
		return rcsNew;
	}

	private CommentResponse getNewResponse(Long responseUserId, String responseUserName, String responseContent) {
		CommentResponse cr = new CommentResponse();
		cr.setResponseUserId(responseUserId);
		cr.setResponseUserName(responseUserName);
		ResponseContent rct = new ResponseContent();
		rct.setCtime(new Date());
		rct.setContent(responseContent);
		cr.setResponseContents(new ResponseContent[] {rct});
		return cr;
	}

	
}

创建Controller

package com.fhbean.springboot.mongodb.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.fhbean.springboot.mongodb.service.ResourceCommentService;

@RestController
@RequestMapping(value = "/rc")
public class ResourceCommentController {

    @Autowired
    private ResourceCommentService resourceCommentService;
    
    
    /**
     * 列出某个资源的所有评论
     * 
     * 客户端页面要做到这些权限限制
     * 三级按钮:发表评论btn1,回复评论btn2,回复评论的评论btn3
     * 当前登录用户是本条评论的第一级评论者,
     *     可以看到发表评论按钮btn1
     *     不可以看到第二级的回复按钮btn2,即不能给自己的评论回复
     *     可以看到第三级的按钮btn3,这个按钮可以给回复评论的人回复评论
     * 
     * 当前登录用户是本条评论的第二级评论者,即回复评论者
     *     可以看到发表评论按钮btn1
     *     可以看到第二级的回复按钮btn2,即可以给他人的评论回复
     *     不可以看到第三级的按钮btn3,即不可以给自己的回复发表回复评论
     *     
     * 当前登录用户不是本条评论的第一级和第二级用户    
     *     可以看到发表评论按钮btn1
     *     可以看到第二级的回复按钮btn2,即可以给他人的评论回复
     *     不可以看到第三级的按钮btn3,只有版主才可以回复评论的评论
     * 
     * @param resourceId
     * @return
     * @throws Exception
     * 
     * example http://localhost:8080/rc/listComments/1
     */
    @ResponseBody
    @RequestMapping(value = "/listComments/{resourceId}")
    public Object listComments(@PathVariable(value = "resourceId", required = true)Long resourceId) throws Exception {
        
        return resourceCommentService.findResourceComment(resourceId);
    }
    
    /**
     * 发表评论
     * @param resourceId
     * @param commentUserId
     * @param commentUserName
     * @param commentContent
     * @return
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping(value = "/saveComment")
    public Object saveComment(
            @RequestParam(value = "resourceId", required = true)Long resourceId, 
            @RequestParam(value = "commentUserId", required = true)Long commentUserId, 
            @RequestParam(value = "commentUserName", required = true)String commentUserName, 
            @RequestParam(value = "commentContent", required = true)String commentContent) throws Exception {
        return resourceCommentService.saveComment(resourceId, commentUserId, commentUserName, commentContent);
    }
    
    /**
     * 回复评论
     * @param id
     * @param responseUserId
     * @param responseUserName
     * @param responseContent
     * @return
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping(value = "/saveResponse")
    public Object saveResponse(
            @RequestParam(value = "id", required = true)String id, 
            @RequestParam(value = "responseUserId", required = true)Long responseUserId, 
            @RequestParam(value = "responseUserName", required = true)String responseUserName, 
            @RequestParam(value = "responseContent", required = true)String responseContent) throws Exception {

        return resourceCommentService.saveResponse(id, responseUserId, responseUserName, responseContent);
    }
    
    /**
     * 回复评论的评论
     *     
     * @param id
     * @param responseUserId
     * @param index
     * @param replyContent
     * @return
     * @throws Exception
     */
    @ResponseBody
    @RequestMapping(value = "/saveReply")
    public Object saveReply(
            @RequestParam(value = "id", required = true)String id, 
            @RequestParam(value = "responseUserId", required = true)Long responseUserId, 
            @RequestParam(value = "index", required = true)Integer index, 
            @RequestParam(value = "replyContent", required = true)String replyContent) throws Exception {
        if (null == index) {
            index = 0;
        }
        return resourceCommentService.saveReply(id, responseUserId, index, replyContent);
    }
}

配置application.yml

spring:
  data:
    mongodb:
      host: 127.0.0.1
      port: 27017
      database: foobar

编写testcase

package com.fhbean.springboot.mongodb;

import java.util.Optional;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;
import org.springframework.test.context.junit4.SpringRunner;

import com.fhbean.springboot.mongodb.entity.ResourceComment;
import com.fhbean.springboot.mongodb.reposistory.ResourceCommentRepository;
import com.fhbean.springboot.mongodb.service.ResourceCommentService;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ResourceCommentTests {

	@Autowired
	private ResourceCommentRepository resourceCommentRepository;
	
	@Autowired
	private ResourceCommentService resourceCommentService;
	
	@Before
	public void setUp() {
		
	}
	
	/**
	 * 测试评论
	 */
	@Test
	public void testSaveComment() {
		Long resourceId = 1L;
		Long commentUserId = 1L;
		String commentUserName = "user" + commentUserId;
		String commentContent = commentUserName + "'s comment on " + System.currentTimeMillis();
		resourceCommentService.saveComment(resourceId, commentUserId, commentUserName, commentContent);
	}
	
	/**
	 * 测试评论回复
	 */
	@Test
	public void testSaveResponse() {
		String id = "5acdae25c986d7518c1a7081";
		Long responseUserId = 2L;
		String responseUserName = "user" + responseUserId;
		String responseContent = responseUserName + "'s response on " + System.currentTimeMillis();
		
		resourceCommentService.saveResponse(id, responseUserId, responseUserName, responseContent);
	}
	
	/**
	 * 测试回复评论的评论
	 */
	@Test
	public void testSaveReply() {
		String id = "5acdae25c986d7518c1a7081";
		Long responseUserId = 2L;
		int index = 1;
		String replyContent = "user"+responseUserId+" reply index["+index+"] on " + System.currentTimeMillis();
		
		resourceCommentService.saveReply(id, responseUserId, index, replyContent);
	}
	
	@Test
	public void testFind() {
		ResourceComment rc = new ResourceComment();
		rc.setResourceId(1L);
		rc.setCommentUserId(1L);
		Example<ResourceComment> example = Example.of(rc);
		Optional<ResourceComment> optional = resourceCommentRepository.findOne(example);
		Assert.assertTrue(optional.isPresent());
		Assert.assertEquals("user1", optional.get().getCommentUserName());
	}
	
	
}

未编写页面ajax脚本

项目结构:


依次跑一下ResourceCommentTests的三个方法:testSaveComment()、testSaveResponse()、testSaveReply()

然后启动Spring Boot项目,在浏览器地址栏录入http://localhost:8080/rc/listComments/1


项目在github上的源码 https://github.com/cjrjc/springboot-mongodb.git

参考 https://segmentfault.com/a/1190000010391079


猜你喜欢

转载自blog.csdn.net/wender/article/details/79901587
今日推荐