(Django服務器+mySQL數據庫+nginx反向代理+redis+channels+android客戶端)數據交互及在線聊天

1、項目總地址:https://github.com/xieyipeng/SSM
2、Django學習:https://github.com/xieyipeng/SSM/tree/master/Django_test
3、Django實戰:https://github.com/xieyipeng/SSM/tree/master/LoginAndRegistration
4、Django與Android連接(服務端Django工程):https://github.com/xieyipeng/SSM/tree/master/qq

以下兩個android工程的服務端基於鏈接4
5、Django與Android交互(Http請求 - android工程):https://github.com/xieyipeng/SSM/tree/master/demoHttp
6、基於Django(webSocket)實現在線聊天(android工程):https://github.com/xieyipeng/SSM/tree/master/SoketTest
7、javaBean基類(GetPostUtil):https://github.com/xieyipeng/Django/blob/master/MineIM/app/src/main/java/com/example/xieyipeng/mineim/tools/GetPostUtil.java

在線聊天我是看官方文檔(相信他講的比我好Django + channels):
中文版:http://www.likecs.com/show-22817.html

對下面發送圖片請求的優化

一、Django

Django 教程-劉江的博客

1、虛擬環境創建工程

  • 注意venv這個虛擬環境目錄,以及我們額外創建的templats目錄

2、創建APP

  • 檢查環境:
    pycharm下方terminal 輸入:where pythonpython -V
(venv) E:\SSM\LoginAndRegistration>where python
E:\SSM\LoginAndRegistration\venv\Scripts\python.exe
D:\python\python.exe

(venv) E:\SSM\LoginAndRegistration>python -V
Python 3.6.8
  • 創建login這個app: terminal 輸入:python manage.py startapp login

3、設置時區、語言

  • 項目settings文件中:
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

改爲:

# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'zh-hans'     # 這裏修改了

TIME_ZONE = 'Asia/Shanghai'    # 這裏修改了

USE_I18N = True

USE_L10N = True

USE_TZ = False    # 這裏修改了

4、啓動開發服務器

  • 在Pycharm的Run/Debug Configurations配置界面裏,將HOST設置爲127.0.0.1,Port保持原樣的8000,確定後,運行。

5、設計數據模型

  • 數據庫模型設計(model)
  • 設置數據庫後端
# TODO: 連接數據庫
import pymysql

pymysql.install_as_MySQLdb()

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'login',
        'HOST': '127.0.0.1',
        'USER': 'root',
        'PASSWORD': '270030',
        'PORT': '3306',
    }
}
  • 註冊app
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'login',
]
  • 創建記錄和數據表

1、創建數據庫 - login

2、修改E:\SSM\LoginAndRegistration\venv\Lib\site-packages\django\db\backends\mysql目錄下base.pyoperations.py兩個文件

base.py:註釋掉兩行

if version < (1, 3, 13):
    raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__)

operations.py:修改decode爲encode

    def last_executed_query(self, cursor, sql, params):
        # With MySQLdb, cursor objects have an (undocumented) "_executed"
        # attribute where the exact query sent to the database is saved.
        # See MySQLdb/cursors.py in the source distribution.
        query = getattr(cursor, '_executed', None)
        if query is not None:
            query = query.encode(errors='replace')
        return query

3、app中的models建立好了後,並不會自動地在數據庫中生成相應的數據表,需要你手動創建。進入Pycharm的terminal終端,執行下面的命令:

python manage.py makemigrations
python manage.py migrate

4、創建管理員賬戶

python manage.py createsuperuser

二、與android虛擬機的鏈接

1、服務端設置:

基本配置就不重複說了。

  • 1、settings中註釋掉一行:(具體原因未知:正常在android虛擬機中發送get請求完全正確,但是發送post請求出現錯誤,具體原因貌似是什麼csrf的檢查,加密啥的,自己也不明白,只知道註釋掉之後,post請求發送的數據可以顯示在pycharm的terminal裏面了)
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    #'django.middleware.csrf.CsrfViewMiddleware',        # 這一行
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
  • 2、創建模型類[../myApp/models.py]:
class User(models.Model):
    gender = (
        ('male', '男'),
        ('female', '女')
    )

    name = models.CharField(max_length=128)
    sex = models.CharField(max_length=32, choices=gender, default='男')
    c_time = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ["-c_time"]
        verbose_name = "用戶"
        verbose_name_plural = "用戶"
  • 3、設置路由[../qq/qq/urls.py]:
from django.contrib import admin
from django.urls import path

from login import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('get_test/', views.get_test),  # 響應get文本請求
    path('post_test/', views.post_test),  # 響應post文本請求
    path('post_file_test/', views.upload_file)  # 響應get文件請求
]
  • 4、邏輯[../myApp/views.py]:
import json
import os

from django.http import HttpResponse
from login.models import User


def get_test(request):
    pass


def post_test(request):
    pass


def upload_file(request):
    pass

  • 5、允許所有的地址訪問[../qq/settings.py]

當然可以只限定幾個已知的地址訪問該服務器

ALLOWED_HOSTS = ['*']
  • 6、在android端加入網絡權限[AndroidManifest.xml]
<uses-permission android:name="android.permission.INTERNET"/>

注意:Android模擬器下 127.0.0.1 或者localhost訪問的是模擬器本機。

如果想要訪問電腦,使用10.0.2.2

10.0.2.2 是 Android 模擬器設置的特定 ip,是本機電腦的 alias - (別名)

2、文本類型請求

  • 發送get請求:

Django端[../myApp/views.py]:

def get_test(request):
    users = []
    list = User.objects.all()
    if request.method == 'GET': # 測試代碼,真正邏輯自己編寫
        for var in list:
            user = {}
            user['id'] = var.id
            user['name'] = var.name
            user['ctime'] = str(var.c_time)
            user['sex'] = var.sex
            users.append(user)
    return HttpResponse(json.dumps(users))  # 返回user數據給安卓端

android端get請求:

   /**
     * 發送get請求
     * 
     * @param url eg: http://10.0.2.2:8000/get_test/
     * @return 返回服務器的響應
     */
    public static String sendGetRequest(String url) {
        HttpURLConnection connection = null;
        BufferedReader bufferedReader = null;
        StringBuilder result = null;
        try {
            URL realUrl = new URL(url);
            //打開鏈接
            connection = (HttpURLConnection) realUrl.openConnection();
            // 設置通用的請求屬性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.setRequestMethod("GET");
            if (HttpURLConnection.HTTP_OK == connection.getResponseCode()) {
                //若鏈接正常(ResponseCode == 200)
                Log.e(TAG, "sendGetRequest: Get Request Successful");
                InputStream inputStream = connection.getInputStream();
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                result = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    result.append(line);
                }
            } else {
                Log.e(TAG, "sendGetRequest: Get Request Failed");
            }

        } catch (IOException e) {
            Log.e(TAG, "sendGetRequest: error: " + e.getMessage());
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result.toString();
    }

android端點擊事件:

 getRequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /*
                  TODO: 子線程中訪問網絡
                 */
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        String url = "http://10.0.2.2:8000/get_test/";
                        final String get = GetPostUtil.sendGetRequest(url);

                        /*
                          TODO: 子線程中更新UI
                         */
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                getTV.setText(get);
                            }
                        });
                    }
                }).start();
            }
        });
  • 發送post請求:

Django端:

def post_test(request):
    if request.method == 'POST':
        print(request.POST)
        req = request.POST.get('v1')  # 獲取post請求中的v1所對應的值
        print('post請求')
        print(req)
        return HttpResponse(req)  # 返回v1所對應的值 -- 只是用來測試代碼,實際邏輯按自己要求來
    else:
        return HttpResponse("none")

android端post請求

    /**
     * 發送post請求
     *
     * @param url   發送請求的 URL
     * @param data 請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
     * @return json數據包
     */
    public static String sendPostRequest(String url, String data) {
        PrintWriter printWriter = null;
        StringBuilder result = null;
        BufferedReader bufferedReader = null;
        try {
            URL realUrl = new URL(url);
            URLConnection connection = realUrl.openConnection();

            // TODO: 設置通用的請求屬性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");

            // TODO: 發送POST請求必須設置如下兩行
            connection.setDoOutput(true);
            connection.setDoInput(true);

            // TODO: 獲取URLConnection對象對應的輸出流
            printWriter = new PrintWriter(connection.getOutputStream());

            // TODO: 發送請求參數
            // TODO: flush輸出流的緩衝
            printWriter.print(data);
            printWriter.flush();
            Log.e(TAG, "sendPostRequest: Post Request Successful");

            // TODO: 定義BufferedReader輸入流來讀取URL的響應
            result = new StringBuilder();
            bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            Log.e(TAG, "sendPostRequest: " + e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                if (printWriter != null) {
                    printWriter.close();
                }
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result.toString();
    }

android端點擊事件:

 postRequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        String url = "http://10.0.2.2:8000/post_test/";
                        String data = "v1=v&v2=v";
                        final String get = GetPostUtil.sendPostRequest(url, data);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                postTV.setText(get);
                            }
                        });
                    }
                }).start();
            }
        });

圖片(文件)請求

  • 發送post請求

Django端:

def upload_file(request):
    if request.method == 'POST':
        temp_file = request.FILES
        if not temp_file:
            print("文件傳輸失敗")
            return HttpResponse('upload failed!')
        else:
            print("文件傳輸成功")

            # 自行理解下面輸出的內容
            # print(request.POST)
            # print(temp_file)
            # print(temp_file.get('file'))
            # print(temp_file.get('file').name)
        # TODO: 獲取圖片並存儲在./uploads目錄下 -- 實際邏輯按自己要求來
        destination = open(os.path.join("./uploads", temp_file.get('file').name), 'wb+')
        for chunk in temp_file.get('file').chunks():
            destination.write(chunk)
        destination.close()
    return HttpResponse('upload success!')

android端post請求:

    /**
     * 發送post請求上傳圖片
     * 
     * @param actionUrl url
     * @param inputStream 圖片的流
     * @param fileName name
     * @return 服務器響應
     */
    public static String upLoadFiles(String actionUrl, InputStream inputStream, String fileName) {
        StringBuffer result = new StringBuffer();
        OutputStream outputStream = null;
        DataInputStream dataInputStream = null;
        try {
            final String newLine = "\r\n"; // 換行符
            final String boundaryPrefix = "--"; //邊界前綴
            final String boundary = String.format("=========%s", System.currentTimeMillis()); // 定義數據分隔線
            // 連接
            URL url = new URL(actionUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            // 發送POST請求必須設置如下兩行
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            // 設置請求頭參數
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("Charsert", "UTF-8");
            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
            // 獲取輸出流
            outputStream = new DataOutputStream(connection.getOutputStream());
            // 文件參數
            // 參數頭設置完以後需要兩個換行,然後纔是參數內容
            String stringBuilder = boundaryPrefix +
                    boundary +
                    newLine +
                    "Content-Disposition: form-data;name=\"file\";filename=\"" + fileName + "\"" + newLine +
                    "Content-Type:application/octet-stream" +
                    newLine +
                    newLine;
            // 將參數頭的數據寫入到輸出流中
            outputStream.write(stringBuilder.getBytes());
            // 數據輸入流,用於讀取文件數據
            dataInputStream = new DataInputStream(inputStream);
            byte[] bufferOut = new byte[1024];
            int bytes = 0;
            // 每次讀1KB數據,並且將文件數據寫入到輸出流中
            while ((bytes = dataInputStream.read(bufferOut)) != -1) {
                outputStream.write(bufferOut, 0, bytes);
            }
            // 最後添加換行
            outputStream.write(newLine.getBytes());
            //關閉流
            inputStream.close();
            dataInputStream.close();
            // 定義最後數據分隔線,即--加上boundary再加上--。
            byte[] end_data = (newLine + boundaryPrefix + boundary + boundaryPrefix + newLine).getBytes();
            // 寫上結尾標識
            outputStream.write(end_data);
            outputStream.flush();
            outputStream.close();
            // 定義BufferedReader輸入流來讀取服務器的響應
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line);
            }
            Log.e(TAG, "upLoadFiles: 響應: " + result.toString());
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "uploadFiles: " + e.getMessage());
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.e(TAG, "upLoadFiles: " + e.getMessage());
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    Log.e(TAG, "upLoadFiles: " + e.getMessage());
                    e.printStackTrace();
                }
            }
            if (dataInputStream != null) {
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    Log.e(TAG, "upLoadFiles: " + e.getMessage());
                }
            }
        }
        return result.toString();
    }

爲了方便獲取圖片資源的stream流,創建assets目錄,創建方法如下(並將一張圖片放在該目錄下):
ZmL4tP.md.png

android端點擊事件:

 postFileRequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        String fileName = "IMG.JPG";
                        // 獲取工程目錄下Assets目錄裏面的資源文件
                        // 也可獲取相機裏的文件流,同理
                        AssetManager assetManager = MainActivity.resources.getAssets();
                        try {
                            InputStream inputStream = assetManager.open(fileName);
                            final String get = GetPostUtil.upLoadFiles("http://10.0.2.2:8000/post_file_test/", inputStream, fileName);
                            /*
                             TODO: 子線程中更新UI
                             */
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    postFileTV.setText(get);
                                }
                            });
                        } catch (IOException e) {
                            e.printStackTrace();
                            Log.e(TAG, "run: " + e.getMessage());
                        }
                    }
                }).start();
            }
        });
  • 獲取圖片資源(nginx)

道理同url獲取圖片資源,eg:http://pic37.nipic.com/20140113/8800276_184927469000_2.png,反向代理。

1、首先下載nginx

網址:http://nginx.org/en/download.html

選擇 stable versionwin10

2、 安裝

加壓後直接雙擊根目錄下nginx.exe即可運行

任務管理器中看到兩個nginx的進程即可

3、 修改config(nginx解壓目錄下./conf/nginx.conf

http包下的servcer包內,修改listen屬性爲81(防止別的資源搶佔80端口),charset改爲UTF-8,root改爲自己的./upload目錄:

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

修改後:

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;

    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       81;
        server_name  localhost;

        charset UTF-8;

        #access_log  logs/host.access.log  main;

        location / {
            root   "E:\\SSM\\qq\\uploads"; # 根據自己的情況而定
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

4、 驗證

直接在瀏覽器輸入127.0.0.1:81/img.jpg 即可。(當然,你的./upload路徑下要有該img文件)

5、android端訪問

注意:用10.0.2.2:81訪問

導入Glide依賴

    implementation 'com.github.bumptech.glide:glide:4.5.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.5.0'
    getFileRequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String url = "http://10.0.2.2:81/微信圖片_20190609175306.jpg";
                Glide.with(context)
                        .load(url)
                        .into(imageView);
            }
        });
剛接觸這些東西,有錯誤的還請指出,特別是服務端的代碼,只敢用於課程設計,大工程肯定不敢這樣寫0.0
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章