SSM(七)在JavaWeb應(yīng)用中使用Redis

前言

1

由于最近換(mang)了(de)家(yi)公(bi)司接觸了新的東西所以很久沒有更新了。
這次談?wù)凴edis,關(guān)于Redis應(yīng)該很多朋友就算沒有用過也聽過,算是這幾年最流行的NoSql之一了。
Redis的應(yīng)用場(chǎng)景非常多這里就不一一列舉了,這次就以一個(gè)最簡(jiǎn)單的也最常用的 緩存數(shù)據(jù) 來舉例。
先來看一張效果圖:

01.gif
01.gif

作用就是在每次查詢接口的時(shí)候首先判斷Redis中是否有緩存,有的話就讀取,沒有就查詢數(shù)據(jù)庫(kù)并保存到Redis中,下次再查詢的話就會(huì)直接從緩存中讀取了。
Redis中的結(jié)果:
02.gif
02.gif

之后查詢r(jià)edis發(fā)現(xiàn)確實(shí)是存進(jìn)來了。

Redis安裝與使用

首先第一步自然是安裝Redis。我是在我VPS上進(jìn)行安裝的,操作系統(tǒng)是CentOS6.5

  • 下載Redishttps://redis.io/download,我機(jī)器上安裝的是3.2.5

  • 將下載下來的'reidis-3.2.5-tar.gz'上傳到usr/local這個(gè)目錄進(jìn)行解壓。

  • 進(jìn)入該目錄。


    03.jpg
  • 編譯安裝

make
make install
  • 修改redis.conf配置文件。

這里我只是簡(jiǎn)單的加上密碼而已。

vi redis.conf
requirepass 你的密碼
  • 啟動(dòng)Redis

啟動(dòng)時(shí)候要選擇我們之前修改的配置文件才能使配置文件生效。

進(jìn)入src目錄
cd /usr/local/redis-3.2.5/src
啟動(dòng)服務(wù)
./redis-server ../redis.conf
  • 登陸redis
./redis-cli -a 你的密碼

Spring整合Redis

這里我就直接開始用Spring整合畢竟在實(shí)際使用中都是和Spring一起使用的。

  • 修改Spring配置文件
    加入以下內(nèi)容:
<!-- jedis 配置 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}"/>
        <property name="maxWaitMillis" value="${redis.maxWait}"/>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    </bean>
    <!-- redis服務(wù)器中心 -->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="poolConfig" ref="poolConfig"/>
        <property name="port" value="${redis.port}"/>
        <property name="hostName" value="${redis.host}"/>
        <property name="password" value="${redis.password}"/>
        <property name="timeout" value="${redis.timeout}"></property>
    </bean>
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>
        <property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/>
        </property>
    </bean>

    <!-- cache配置 -->
    <bean id="methodCacheInterceptor" class="com.crossoverJie.intercept.MethodCacheInterceptor">
        <property name="redisUtil" ref="redisUtil"/>
    </bean>
    <bean id="redisUtil" class="com.crossoverJie.util.RedisUtil">
        <property name="redisTemplate" ref="redisTemplate"/>
    </bean>

    <!--配置切面攔截方法 -->
    <aop:config proxy-target-class="true">
        <!--將com.crossoverJie.service包下的所有select開頭的方法加入攔截
        去掉select則加入所有方法w
        -->
        <aop:pointcut id="controllerMethodPointcut" expression="
        execution(* com.crossoverJie.service.*.select*(..))"/>

        <aop:pointcut id="selectMethodPointcut" expression="
        execution(* com.crossoverJie.dao..*Mapper.select*(..))"/>

        <aop:advisor advice-ref="methodCacheInterceptor" pointcut-ref="controllerMethodPointcut"/>
    </aop:config>

更多的配置可以直接在源碼里面查看:https://github.com/crossoverJie/SSM/blob/master/src/main/resources/spring-mybatis.xml
以上都寫有注釋,也都是一些簡(jiǎn)單的配置相信都能看懂。
下面我會(huì)著重說下如何配置緩存的。

Spring切面使用緩存

Spring的AOP真是是一個(gè)好東西,還不太清楚是什么的同學(xué)建議先自行Google下吧。
在不使用切面的時(shí)候如果我們想給某個(gè)方法加入緩存的話肯定是在方法返回之前就要加入相應(yīng)的邏輯判斷,只有一個(gè)或幾個(gè)倒還好,如果有幾十上百個(gè)的話那GG了,而且維護(hù)起來也特別麻煩。

好在Spring的AOP可以幫我們解決這個(gè)問題。
這次就在我們需要加入緩存方法的切面加入這個(gè)邏輯,并且只需要一個(gè)配置即可搞定,就是上文中所提到的配置文件,如下:

    <!--配置切面攔截方法 -->
    <aop:config proxy-target-class="true">
        <!--將com.crossoverJie.service包下的所有select開頭的方法加入攔截
        去掉select則加入所有方法w
        -->
        <aop:pointcut id="controllerMethodPointcut" expression="
        execution(* com.crossoverJie.service.*.select*(..))"/>

        <aop:pointcut id="selectMethodPointcut" expression="
        execution(* com.crossoverJie.dao..*Mapper.select*(..))"/>

        <aop:advisor advice-ref="methodCacheInterceptor" pointcut-ref="controllerMethodPointcut"/>
    </aop:config>

這里我們使用表達(dá)式execution(* com.crossoverJie.service.*.select*(..))來攔截service中所有以select開頭的方法。這樣只要我們要將加入的緩存的方法以select命名開頭的話每次進(jìn)入方法之前都會(huì)進(jìn)入我們自定義的MethodCacheInterceptor攔截器。
這里貼一下MethodCacheInterceptor中處理邏輯的核心方法:

@Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object value = null;

        String targetName = invocation.getThis().getClass().getName();
        String methodName = invocation.getMethod().getName();
        // 不需要緩存的內(nèi)容
        //if (!isAddCache(StringUtil.subStrForLastDot(targetName), methodName)) {
        if (!isAddCache(targetName, methodName)) {
            // 執(zhí)行方法返回結(jié)果
            return invocation.proceed();
        }
        Object[] arguments = invocation.getArguments();
        String key = getCacheKey(targetName, methodName, arguments);
        logger.debug("redisKey: " + key);
        try {
            // 判斷是否有緩存
            if (redisUtil.exists(key)) {
                return redisUtil.get(key);
            }
            // 寫入緩存
            value = invocation.proceed();
            if (value != null) {
                final String tkey = key;
                final Object tvalue = value;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        if (tkey.startsWith("com.service.impl.xxxRecordManager")) {
                            redisUtil.set(tkey, tvalue, xxxRecordManagerTime);
                        } else if (tkey.startsWith("com.service.impl.xxxSetRecordManager")) {
                            redisUtil.set(tkey, tvalue, xxxSetRecordManagerTime);
                        } else {
                            redisUtil.set(tkey, tvalue, defaultCacheExpireTime);
                        }
                    }
                }).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
            if (value == null) {
                return invocation.proceed();
            }
        }
        return value;
    }
  • 先是查看了當(dāng)前方法是否在我們自定義的方法中,如果不是的話就直接返回,不進(jìn)入攔截器。
  • 之后利用反射獲取的類名、方法名、參數(shù)生成rediskey
  • 用key在redis中查詢是否已經(jīng)有緩存。
  • 有緩存就直接返回緩存內(nèi)容,不再繼續(xù)查詢數(shù)據(jù)庫(kù)。
  • 如果沒有緩存就查詢數(shù)據(jù)庫(kù)并將返回信息加入到redis中。

使用PageHelper

這次為了分頁方便使用了比較流行的PageHelper來幫我們更簡(jiǎn)單的進(jìn)行分頁。
首先是新增一個(gè)mybatis的配置文件mybatis-config

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="25"/>
        <setting name="safeRowBoundsEnabled" value="false"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>

    <plugins>
        <!-- com.github.pagehelper為PageHelper類所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <property name="dialect" value="mysql"/>
            <!-- 該參數(shù)默認(rèn)為false -->
            <!-- 設(shè)置為true時(shí),會(huì)將RowBounds第一個(gè)參數(shù)offset當(dāng)成pageNum頁碼使用 -->
            <!-- 和startPage中的pageNum效果一樣 -->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 該參數(shù)默認(rèn)為false -->
            <!-- 設(shè)置為true時(shí),使用RowBounds分頁會(huì)進(jìn)行count查詢 -->
            <property name="rowBoundsWithCount" value="true"/>

            <!-- 設(shè)置為true時(shí),如果pageSize=0或者RowBounds.limit = 0就會(huì)查詢出全部的結(jié)果 -->
            <!-- (相當(dāng)于沒有執(zhí)行分頁查詢,但是返回結(jié)果仍然是Page類型) <property name="pageSizeZero" value="true"/> -->

            <!-- 3.3.0版本可用 - 分頁參數(shù)合理化,默認(rèn)false禁用 -->
            <!-- 啟用合理化時(shí),如果pageNum<1會(huì)查詢第一頁,如果pageNum>pages會(huì)查詢最后一頁 -->
            <!-- 禁用合理化時(shí),如果pageNum<1或pageNum>pages會(huì)返回空數(shù)據(jù) -->
            <property name="reasonable" value="true"/>
            <!-- 3.5.0版本可用 - 為了支持startPage(Object params)方法 -->
            <!-- 增加了一個(gè)`params`參數(shù)來配置參數(shù)映射,用于從Map或ServletRequest中取值 -->
            <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認(rèn)值 -->
            <!-- 不理解該含義的前提下,不要隨便復(fù)制該配置 -->
            <property name="params" value="pageNum=start;pageSize=limit;"/>
        </plugin>
    </plugins>
</configuration>

接著在mybatis的配置文件中引入次配置文件:

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 自動(dòng)掃描mapping.xml文件 -->
        <property name="mapperLocations" value="classpath:mapping/*.xml"></property>
        <!--加入PageHelper-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

接著在service方法中:

    @Override
    public PageEntity<Rediscontent> selectByPage(Integer pageNum, Integer pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        //因?yàn)槭莇emo,所以這里默認(rèn)沒有查詢條件。
        List<Rediscontent> rediscontents = rediscontentMapper.selectByExample(new RediscontentExample());
        PageEntity<Rediscontent> rediscontentPageEntity = new PageEntity<Rediscontent>();
        rediscontentPageEntity.setList(rediscontents);
        int size = rediscontentMapper.selectByExample(new RediscontentExample()).size();
        rediscontentPageEntity.setCount(size);
        return rediscontentPageEntity;
    }

只需要使用PageHelper.startPage(pageNum, pageSize);方法就可以幫我們簡(jiǎn)單的分頁了。
這里我自定義了一個(gè)分頁工具類PageEntity來更方便的幫我們?cè)谥笊?code>JSON數(shù)據(jù)。

package com.crossoverJie.util;

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

/**
 * 分頁實(shí)體
 *
 * @param <T>
 */
public class PageEntity<T> implements Serializable {
    private List<T> list;// 分頁后的數(shù)據(jù)
    private Integer count;

    public Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }
}

更多PageHelper的使用請(qǐng)查看一下鏈接:
https://github.com/pagehelper/Mybatis-PageHelper

前端聯(lián)調(diào)

接下來看下控制層RedisController:

package com.crossoverJie.controller;

import com.crossoverJie.pojo.Rediscontent;
import com.crossoverJie.service.RediscontentService;
import com.crossoverJie.util.CommonUtil;
import com.crossoverJie.util.PageEntity;
import com.github.pagehelper.PageHelper;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpServletResponse;


@Controller
@RequestMapping("/redis")
public class RedisController {

    private static Logger logger = LoggerFactory.getLogger(RedisController.class);

    @Autowired
    private RediscontentService rediscontentService;


    @RequestMapping("/redis_list")
    public void club_list(HttpServletResponse response,
                          @RequestParam(value = "page", defaultValue = "0") int page,
                          @RequestParam(value = "pageSize", defaultValue = "0") int pageSize) {
        JSONObject jsonObject = new JSONObject();
        JSONObject jo = new JSONObject();
        try {
            JSONArray ja = new JSONArray();
            PageHelper.startPage(1, 10);
            PageEntity<Rediscontent> rediscontentPageEntity = rediscontentService.selectByPage(page, pageSize);
            for (Rediscontent rediscontent : rediscontentPageEntity.getList()) {
                JSONObject jo1 = new JSONObject();
                jo1.put("rediscontent", rediscontent);
                ja.add(jo1);
            }
            jo.put("redisContents", ja);
            jo.put("count", rediscontentPageEntity.getCount());
            jsonObject = CommonUtil.parseJson("1", "成功", jo);

        } catch (Exception e) {
            jsonObject = CommonUtil.parseJson("2", "操作異常", "");
            logger.error(e.getMessage(), e);
        }
        //構(gòu)建返回
        CommonUtil.responseBuildJson(response, jsonObject);
    }
}

這里就不做過多解釋了,就是從redis或者是service中查詢出數(shù)據(jù)并返回。

前端的顯示界面在https://github.com/crossoverJie/SSM/blob/master/src/main/webapp/redis/showRedis.jsp中(并不是前端,將就看)。
其中核心的redis_list.js的代碼如下:

var page = 1,
    rows = 10;
$(document).ready(function () {
    initJqPaginator();
    //加載
    load_redis_list();
    $(".query_but").click(function () {//查詢按鈕
        page = 1;
        load_redis_list();
    });
});
//初始化分頁
function initJqPaginator() {
    $.jqPaginator('#pagination', {
        totalPages: 100,
        visiblePages: 10,
        currentPage: 1,
        first: '<li class="prev"><a href="javascript:;">首頁</a></li>',
        last: '<li class="prev"><a href="javascript:;">末頁</a></li>',
        prev: '<li class="prev"><a href="javascript:;">上一頁</a></li>',
        next: '<li class="next"><a href="javascript:;">下一頁</a></li>',
        page: '<li class="page"><a href="javascript:;">{{page}}</a></li>',
        onPageChange: function (num, type) {
            page = num;
            if (type == "change") {
                load_redis_list();
            }
        }
    });
}
//列表
function create_club_list(redisContens) {
    var phone = 0;
    var html = '<div class="product_box">'
        + '<div class="br">'
        + '<div class="product_link">'
        + '<div class="product_phc">'
        + '<img class="phc" src="" >'
        + '</div>'
        + '<span class="product_name">' + redisContens.id + '</span></div>'
        + '<div class="product_link toto">' + redisContens.content + '</div>'
        + '<div class="product_link toto">'
        + '<span>' + "" + '</span>'
        + '</div>'
        + '<div class="product_link toto">'
        + '<span>' + phone + '</span></div>'
        + '<div class="product_link toto">'
        + '<span>' + 0 + '</span></div>'
        + '<div class="product_link toto product_operation">'
        + '<span onclick="edit_club(' + 0 + ')">編輯</span>'
        + '<span onclick="edit_del(' + 0 + ')">刪除</span></div></div>'
        + '</div>';
    return html;
}
//加載列表
function load_redis_list() {
    var name = $("#name").val();
    $.ajax({
        type: 'POST',
        url: getPath() + '/redis/redis_list',
        async: false,
        data: {name: name, page: page, pageSize: rows},
        datatype: 'json',
        success: function (data) {
            if (data.result == 1) {
                $(".product_length_number").html(data.data.count);
                var html = "";
                var count = data.data.count;
                for (var i = 0; i < data.data.redisContents.length; i++) {
                    var redisContent = data.data.redisContents[i];
                    html += create_club_list(redisContent.rediscontent);
                }
                $(".product_content").html(html);
                //這里是分頁的插件
                $('#pagination').jqPaginator('option', {
                    totalPages: (Math.ceil(count / rows) < 1 ? 1 : Math.ceil(count / rows)),
                    currentPage: page
                });
            } else {
                alert(data.msg);
            }
        }
    });
    $(".product_box:even").css("background", "#e6e6e6");//隔行變色
}

其實(shí)就是一個(gè)簡(jiǎn)單的請(qǐng)求接口,并根據(jù)返回?cái)?shù)據(jù)動(dòng)態(tài)生成Dom而已。

總結(jié)

以上就是一個(gè)簡(jiǎn)單的redis的應(yīng)用。
redis的應(yīng)用場(chǎng)景還非常的多,比如現(xiàn)在我所在做的一個(gè)項(xiàng)目就有用來處理短信驗(yàn)證碼的業(yè)務(wù)場(chǎng)景,之后有時(shí)間可以寫一個(gè)demo。

項(xiàng)目地址:https://github.com/crossoverJie/SSM.git
個(gè)人博客地址:http://crossoverjie.top
GitHub地址:https://github.com/crossoverJie

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,581評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,374評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,591評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,789評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,322評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,554評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容