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
1、虛擬環境創建工程
- 注意venv這個虛擬環境目錄,以及我們額外創建的templats目錄
2、創建APP
- 檢查環境:
pycharm下方terminal 輸入:where python
和python -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.py
和operations.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目錄,創建方法如下(並將一張圖片放在該目錄下):
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 version
– win10
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);
}
});