開發一個博客園系統

来源:http://www.cnblogs.com/Jeffding/archive/2017/12/23/8035312.html
-Advertisement-
Play Games

最近在學django框架,準備用django寫一個博客園的系統,並且在寫的過程中也遇到一些問題,實踐出真知,對django開發web應用方面也有了進一步的瞭解。很多操作實現都是以我所認知的技術完成的,可能存在不合理的地方(畢竟實現的方法多種多樣),基本完成後會將源碼上傳到git,也歡迎各位大神指正。 ...


  最近在學django框架,準備用django寫一個博客園的系統,並且在寫的過程中也遇到一些問題,實踐出真知,對django開發web應用方面也有了進一步的瞭解。很多操作實現都是以我所認知的技術完成的,可能存在不合理的地方(畢竟實現的方法多種多樣),基本完成後會將源碼上傳到git,也歡迎各位大神指正。

  首先,要寫未登錄主站(index)。這裡需要註意文章的分類:

  文章的分類切換,網站本身有定義的文章類型:

   type_choices = [
        (1, "Python"),
        (2, "Linux"),
        (3, "OpenStack"),
        (4, "GoLang"),
    ]

  要實現主頁的分類(分類標簽樣式要突出)需要使用一個前端與後端都有的id來顯示分類。

    if request.method=='GET':
        type_id = int(kwargs.get('type_id')) if kwargs.get('type_id') else None
        #後臺都是get傳參
        if type_id:
            article_list = models.Article.objects.filter(article_type_id=type_id).extra(select={'c': "strftime('%%Y-%%m',create_time)"})
        else:
            article_list = models.Article.objects.all().extra(select={'c': "strftime('%%Y-%%m',create_time)"})
        type_choice_list = models.Article.type_choices#分類的
        # print(type_choice_list)#[(1, 'Python'), (2, 'Linux'), (3, 'OpenStack'), (4, 'GoLang')]
後臺代碼
{% if type_id %}
                        <li><a href="/">全部</a></li>
                    {% else %}
                        <li class="active"><a href="/">全部</a></li>
                    {% endif %}

                    {% for item in type_choice_list %}
                        {% if item.0 == type_id %}
                            <li class="active"><a href="/all/{{ item.0 }}/">{{ item.1 }}</a></li>
                        {% else %}
                            <li><a href="/all/{{ item.0 }}/">{{ item.1 }}</a></li>
                        {% endif %}
                    {% endfor %}
前端代碼

  登陸與註冊頁面

  登陸與註冊的驗證使用form表單功能完成,除此之外我們還需要有一個圖片驗證碼用於認證。

   在前端設置一個圖片,圖片src屬性指向後端(獲取圖片時向後端發生get請求方式,後端返回的),驗證碼圖片由後端生成圖片在上面顯示,點擊更換我們使用每次點擊在src屬性後面加一個?,這樣url改變了前端向後臺發送一個get請求,那麼就會獲得一個新的驗證碼圖片了。

 <img style="width: 120px;height: 30px;" src="/check_code/" title="點擊更換" id="change_img">

        $(function(){
            change_img();
        });
        function change_img() {//get方式在url上加?刷新圖片
            $('#change_img').click(function () {
                $(this)[0].src=$(this)[0].src+'?';
            })
        }
前端代碼
from PIL import Image,ImageDraw,ImageFont,ImageFilter
import random

def rd_check_code(width=120, height=30, char_length=4, font_file='kumo.ttf', font_size=28):
    code = []
    img = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
    draw = ImageDraw.Draw(img, mode='RGB')
 
    def rndChar():
        """
        生成隨機字母   
        :return:
        """
        return chr(random.randint(65, 90))
 
    def rndColor():
        """
        生成隨機顏色
        :return:
        """
        return (random.randint(0, 255), random.randint(10, 255), random.randint(64, 255))
 
    # 寫文字
    font = ImageFont.truetype(font_file, font_size)
    for i in range(char_length):
        char = rndChar()
        code.append(char)
        h = random.randint(0, 4)
        draw.text([i * width / char_length, h], char, font=font, fill=rndColor())
 
    # 寫干擾點
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
 
    # 寫干擾圓圈
    for i in range(40):
        draw.point([random.randint(0, width), random.randint(0, height)], fill=rndColor())
        x = random.randint(0, width)
        y = random.randint(0, height)
        draw.arc((x, y, x + 4, y + 4), 0, 90, fill=rndColor())
 
    # 畫干擾線
    for i in range(5):
        x1 = random.randint(0, width)
        y1 = random.randint(0, height)
        x2 = random.randint(0, width)
        y2 = random.randint(0, height)
 
        draw.line((x1, y1, x2, y2), fill=rndColor())
 
    img = img.filter(ImageFilter.EDGE_ENHANCE_MORE)
    return img,''.join(code)
PIL生成隨機碼模塊
def check_code(request):
    from io import BytesIO
    from utils.random_check_code import rd_check_code
    img,code = rd_check_code()
    stream = BytesIO()#開闢一個記憶體空間,類似於文件句柄
    print(stream)#io空間,<_io.BytesIO object at 0x06D6EAE0>
    print(img)#pillow生成的圖片對象
    img.save(stream,'png')
    print(stream)
    print(stream.getvalue())#bytes類型的圖片信息,返回前端生成圖片
    print(img)
    request.session['code'] = code#將生成的隨機字元串存到session用於驗證
    return HttpResponse(stream.getvalue())
後端返回隨機碼

  登錄後將session信息寫入瀏覽器cookie,可以完成兩周免登陸效果。註銷我使用的時Ajax,後臺需要清理session。這個過程中要註意,Ajax需要向後臺發送自己的csrf碼,否則後端預設是偽造的跨站請求,不給予服務。

 $(function () {
            $(".take_off").click(function () {
            {#                        $(".take_off").click(function () {#}
            {#            $.ajaxSetup({#}
            {#                data:{csrfmiddlewaretoken:'{{ csrf_token }}'}#}
            {#            });#}
                $.ajax({
                url:'/',
                type:'POST',
                {#data:{ 'csrftoken':{{ csrf_token}} },#}
                data:{csrfmiddlewaretoken:'{{ csrf_token }}'},
                dataType:"JSON",
                success:function(arg){
                    console.log(arg);
                    if(arg.status){
                        location.href='/'
                    }else{
                    }}
            })
            });
       });
Ajax註銷

  註冊也有一個地方需要註意,就是圖片上傳的問題,我使用的是硬解碼的方式存放圖片:

            with open(os.path.join('/static/imgs/', obj.cleaned_data.get('avatar').name), 'wb') as file:
                all = obj.cleaned_data.get('avatar').chunks()  # 拿到整個文件
                for trunk in all:
                    file.write(trunk)
                file.close()
            obj.cleaned_data['avatar'] = os.path.join('/static/imgs/', obj.cleaned_data.get('avatar').name)
            models.UserInfo.objects.create(**obj.cleaned_data)
直接在後端進行存儲

  這種方法還是比較笨重的解決方法,在創建資料庫的時候有一個upload_to欄位可以直接指定文件存放路徑。

avatar = models.ImageField(verbose_name='頭像',upload_to='static/imgs')
創建數據表直接指定

  不過這兩種方法都不夠靈活,不能防止圖片名重覆的問題,這裡有一篇博客對存儲路徑進行優化的方式。這已經解決了很多一部分命名問題了。http://blog.csdn.net/alxandral_brother/article/details/53415551。

  用Ajax完成圖片預覽功能

  首先文件上傳的醜陋的介面我們是沒有辦法修改的(點擊上傳那個),所以我們使用預設圖片遮住這個文件框。

<div class="col-sm-10" style="position: relative;height:80px;width: 80px;">
     <img id="previewImg" style="position: absolute;height:80px;width: 80px;" src="/static/imgs/default.png">
    {{ obj.avatar }}<span>{{ obj.errors.avatar.0 }}</span>
</div>

  接下來是關於上傳預覽的部分,最早我們使用Ajax把前端獲取的圖片發給後端,後端接收後保存再發送回前端顯示預覽,但是這樣做會導致用戶上傳了圖片但是沒有註冊成功,那麼後端保存的圖片信息就是垃圾數據,那麼我們必須要進行定期的數據清理工作。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" />
    <style>
        .login{
            width: 600px;
            margin: 0 auto;
            padding: 20px;
            margin-top: 80px;
        }
        .f1{
            position: absolute;height:80px;width: 80px;top:0;left: 0;opacity: 0;
        }

    </style>
</head>
<body>
    <div class="login">
        <div style="position: relative;height:80px;width: 80px; left:260px;top: -10px ">
            <img id="previewImg" style="height:80px;width: 80px;" src="/static/image/default.png">
            <input id="imgSelect" style="height: 80px;width: 80px; position: absolute; top:0;left: 0; opacity: 0" type="file">
            {{ obj.avatar }}
        </div>
    </div>

    <script src="/static/jquery-3.2.1.js"></script>

    <script>
        $(function () {
            bindAvartar1();
        });

        function bindAvartar1() {
            $("#imgSelect").change(function () {
                //$(this)[0]           #jquery變成DOM對象
                //$(this)[0].files     #獲取上傳當前文件的上傳對象
                //$(this)[0].files[0]  #獲取上傳當前文件的上傳對象的某個對象
                var obj = $(this)[0].files[0];
                console.log(obj);

                //ajax 發送後臺獲取頭像路徑
                //img src 重新定義新的路徑

                var formdata = new FormData();  //創建一個對象
                formdata.append("file",obj);
                var xhr = new XMLHttpRequest();
                xhr.open("POST","/register/");
                xhr.send(formdata);

                xhr.onreadystatechange = function () {
                    if(xhr.readyState ==4){
                        var file_path = xhr.responseText;
                        console.log(file_path);
                        $("#previewImg").attr("src","/" + file_path)
                    }
                };

            })
        }
    </script>
</body>
</html>
Ajax上傳預覽
import os
def register(request):
    if request.method == "GET":
        return render(request,"register.html")
    else:
        print(request.POST)
        print(request.FILES)
        file_obj = request.FILES.get("file")
        print(file_obj)
        file_path = os.path.join("static", file_obj.name)
        with open(file_path, "wb") as f:
            for chunk in file_obj.chunks():
                f.write(chunk)
        return HttpResponse(file_path)
後端保存圖片

  當然,我們還可以使用本地預覽的方式。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" />
    <style>
        .login{
            width: 600px;
            margin: 0 auto;
            padding: 20px;
            margin-top: 80px;
        }
        .f1{
            position: absolute;height:80px;width: 80px;top:0;left: 0;opacity: 0;
        }

    </style>
</head>
<body>
    <div class="login">
        <div style="position: relative;height:80px;width: 80px; left:260px;top: -10px ">
            <img id="previewImg" style="height:80px;width: 80px;" src="/static/image/default.png">
            <input id="imgSelect" style="height: 80px;width: 80px; position: absolute; top:0;left: 0; opacity: 0" type="file">
            {{ obj.avatar }}
        </div>
    </div>

    <script src="/static/jquery-3.2.1.js"></script>

    <script>
        $(function () {
            bindAvartar2();
        });

      

        function bindAvartar2() {
            $("#imgSelect").change(function () {
                var obj = $(this)[0].files[0];
                console.log(obj);

                //將文件對象上傳到瀏覽器
                //IE10 以下不支持
                var v = window.URL.createObjectURL(obj);
                $("#previewImg").attr("src",v);

                //不會自動釋放記憶體
                //當載入完圖片後,釋放記憶體
                document.getElementById("previewImg").onload= function () {
                    window.URL.revokeObjectURL(v);
                };
            })
        }





        function bindAvartar3() {
            $("#imgSelect").change(function () {
                var obj = $(this)[0].files[0];
                console.log(obj);

                var reader = new FileReader();
                reader.onload = function (e) {
                    $("#previewImg").attr("src",this.result);
                };
                reader.readAsDataURL(obj)
            })
        }

    </script>
</body>
</html>
本地上傳預覽的兩種方式

  因為用戶的瀏覽器版本限制,我們可以採用多重手段給不同的用戶使用預覽功能:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.5-dist/css/bootstrap.css" />
    <style>
        .login{
            width: 600px;
            margin: 0 auto;
            padding: 20px;
            margin-top: 80px;
        }
        .f1{
            position: absolute;height:80px;width: 80px;top:0;left: 0;opacity: 0;
        }

    </style>
</head>
<body>
    <div class="login">
        <div style="position: relative;height:80px;width: 80px; left:260px;top: -10px ">
            <img id="previewImg" style="height:80px;width: 80px;" src="/static/image/default.png">
            <input id="imgSelect" style="height: 80px;width: 80px; position: absolute; top:0;left: 0; opacity: 0" type="file">

        </div>
    </div>

    <script src="/static/jquery-3.2.1.js"></script>

    <script>
        $(function(){
           bindAvatar();
        });

        function bindAvatar(){
            if(window.URL.createObjectURL){
                bindAvatar2();
            }else if(window.FileReader){
                bindAvatar3()
            }else{
                bindAvatar1();
            }
        }



        function bindAvatar1() {
            $("#imgSelect").change(function () {
                //$(this)[0]           #jquery變成DOM對象
                //$(this)[0].files     #獲取上傳當前文件的上傳對象
                //$(this)[0].files[0]  #獲取上傳當前文件的上傳對象的某個對象
                var obj = $(this)[0].files[0];
                console.log(obj);

                //ajax 發送後臺獲取頭像路徑
                //img src 重新定義新的路徑

                var formdata = new FormData();  //創建一個對象
                formdata.append("file",obj);
                var xhr = new XMLHttpRequest();
                xhr.open("POST","/register/");
                xhr.send(formdata);

                xhr.onreadystatechange = function () {
                    if(xhr.readyState ==4){
                        var file_path = xhr.responseText;
{#                        console.log(file_path);#}
                        $("#previewImg").attr("src","/" + file_path)
                    }
                };
            })
        }


        function bindAvatar2() {
            $("#imgSelect").change(function () {
                var obj = $(this)[0].files[0];
                console.log(obj);

                //將文件對象上傳到瀏覽器
                //IE10 以下不支持


                //不會自動釋放記憶體
                //當載入完圖片後,釋放記憶體

                document.getElementById("previewImg").onload= function () {
                    window.URL.revokeObjectURL(v);
                };

                var v = window.URL.createObjectURL(obj);
                $("#previewImg").attr("src",v);
            })
        }





        function bindAvatar3() {
            $("#imgSelect").change(function () {
                var obj = $(this)[0].files[0];
                console.log(obj);

                var reader = new FileReader();
                reader.onload = function (e) {
                    $("#previewImg").attr("src",this.result);
                };
                reader.readAsDataURL(obj)
            })
        }



    </script>
</body>
</html>
一步到位,大家都能用

  主頁部分,主頁部分的主要操作就是各項分類,你可以將標簽,隨筆和時間分開寫,其實我一開始也是這麼做的,但實際上重覆代碼有很多,這些按分類展現的頁面,唯一的不同就是根據不同類型分類的文章也不同。根據這一點,我們可以將分類寫到一個視圖函數裡面,這樣代碼更為精簡。

url(r'^(?P<site>\w+)/(?P<key>((tag)|(date)|(category)))/(?P<val>\w+-*\w*)/', views.filter)

  而分類的過程中主要涉及的就是ORM的操作,並且也沒有十分難的數據表操作。

  文章頁

  文章頁的部分主要是點贊與評論部分,先說一下評論部分,評論可以做成縮進的多級評論,但是需要將資料庫獲得的數據進行數據結構改造,快速索引。

    msg_list = [
        {'id':1,'content':'寫的太好了','parent_id':None},
        {'id':2,'content':'你說得對','parent_id':None},
        {'id':3,'content':'頂樓上','parent_id':None},
        {'id':4,'content':'你眼瞎嗎','parent_id':1},
        {'id':5,'content':'我看是','parent_id':4},
        {'id':6,'content':'雞毛','parent_id':2},
        {'id':7,'content':'你是沒呀','parent_id':5},
        {'id':8,'content':'惺惺惜惺惺想尋','parent_id':3},
    ]
    msg_list_dict = {}
    for item in msg_list:
        item['child'] = []#每一行加一個空列表child,存放子數據
        msg_list_dict[item['id']] = item#每個行加一個索引的序列改造成[1;{},2:{}]

    # #### msg_list_dict用於查找,msg_list
    result = []
    for item in msg_list:
        pid = item['parent_id']
        if pid:#如果有父id
            msg_list_dict[pid]['child'].append(item)#加到剛纔的child列表中
        else:
            result.append(item)#列表裡都是第一級的評論
    # ########################### 列印 ###################
    from utils.comment import comment_tree
    comment_str = comment_tree(result)#自定義把所有的評論一級一級遞歸的撥開,解析成HTML格式
多級評論
def comment_tree(comment_list):
    """

    :param result: [ {id,:child:[xxx]},{}]
    :return:
    """
    comment_str = "<div class='comment'>"
    for row in comment_list:
        tpl = "<div class='content'>%s</div>" %(row['content'])
        comment_str += tpl
        if row['child']:
            #
            child_str = comment_tree(row['child'])
            comment_str += child_str
    comment_str += "</div>"

    return comment_str
util.comment

  個人覺得也可以寫成博客園的@的方式,@的回覆可跨表取到。

{% for re in reply %}
    <div style="background-color: #e0e0e0">{{ re.comment__create_time }}&nbsp [發言人]{{ re.comment__user__username }}</div>
    {% if re.comment__reply__user__username %}
    <p>@{{ re.comment__reply__user__username }}</p>
        {% else %}
        <p></p>
    {% endif %}
<div style="width: 100%;border-bottom: #00b3ee 1px solid ;margin-top: 5px">  {{ re.comment__content }} </div>

{% endfor %}
數據可以後端跨表取

  點贊要給贊綁定點擊事件,定義1為贊,0為踩,

onclick="updown(this,{{ content.nid }},1);//傳給綁定事件觸發的函數
        function updown(ths,nid,val){
            $.ajax({
                url: '/updown.html',
                data:{'val':val,'nid':nid,'csrfmiddlewaretoken':'{{ csrf_token }}'},
                type: "POST",
                dataType:'JSON',
                success:function(arg){
                    if(arg.status){
                        // 點贊成功刷新頁面
                        location.reload();
                    }else{
                        alert(arg.msg)
                    }
                }
            })
        }
綁定事件

  後臺管理可以使用xadmin來做,當然也可以寫一個後臺管理,我這個後臺管理暫時使用管理員管理界面。

  xadmin使用方法

  這兩天把後臺搭起來再把源碼上傳。我發現這個xadmin功能很強大啊,待我修習幾日直接用它來做後臺管理。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • //運行期以索引獲取tuple元素-C++17 //需支持C++17及以上標準的編譯器,VS2017 15.5.x、CodeBlocks 16.01 gcc 7.2 //參見:http://purecpp.org/?p=1581 #include #include using namespace s... ...
  • 本文的目的是把以前的ssm項目改造成基於springboot搭建的。 以前的ssm項目在SSM(SPRING,SPRINGMVC,MYBATIS)整合的MAVEN單工程(上) http://www.cnblogs.com/yuanjava/p/6748956.html 文章里 1.新增maven工程 ...
  • 2 兩個星號加字典名 ...
  • //遍歷輸出tuple元素的簡潔方式(C++11) //Win32Con17_VS2017_01.cpp #include #include using namespace std; template void myprint_impl(tuple tup) //泛化版本 { //cout (tup... ...
  • 引言 - 一時心起, libuv linux 搭建 有一天突然想起來想寫個動畫. 找了一下 ui 庫太大. 後面想起以前弄過的 libuv. 但發現 libuv 相關資料也很少. 所以就有了這些內容. libuv - https://github.com/libuv/libuv libuv 在 li ...
  • 這個工具類,用線程的方式實現文件的複製,效果勉強還算可以。功能不夠完善。 根據兩個路徑字元串(文件路徑或文件夾路徑),實現文件的複製,具體用的方法,註釋還算清楚。 不能在構造方法里拋出異常,好難受。 ...
  • 1# 判斷二元一次方程ax+by=c是否有整數解: c%gcd(a,b) == 0 2# ab的最小公倍數 = a*b/最大公約數 3# 最大公約數: 輾轉相除法 4# n進位的轉換用% / +迴圈/遞歸來解決. 5# a&1判斷一個數是奇數還是偶數 6# 高精度加法,乘法原理: 7# 整數a/b向 ...
  • UI界面展示: 3D模型界面: 灰度分佈界面: 下麵是源程式: 下一個任務:進行邊界檢測 思路: 文中圖片 鏈接:鏈接:https://pan.baidu.com/s/1hsImLla 密碼:m2ip ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...