2019-06-02 14:25:44 qq_33765199 阅读数 809
  • Django开发基础 

    Django是出名的py框架。Django大而全,它出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。总之,Django是开发网站的强大工具。 本期课程主要有Bootstrap框架,Django的工程创建,常用命令,配置文件,简单模版语言等。

    3120 人正在学习 去看看 李杰

Django服务器与App(Android)客户端的简单实现

作者是一个技术小白,这也是本人的第一篇文章,虽然网上关于Django和Android结合搭建app的文章很多,但是还是想要根据自己在搭建过程中遇到的问题写一篇学习笔记,同时也供他人借鉴一下。
说明:本示例app为本人实现 的一个很简单的记账软件,不喜勿喷。
附github地址:https://github.com/Kingqibin/paykeep

Django后台搭建

环境要求:

  1. Ubuntu 16.04 LTS 桌面版/服务器版
  2. Python 3
  3. pip等

代码实现

  1. 安装Django:
pip install django
  1. 创建django项目:
django-admin startproject paykeep # 创建项目
django-admin startapp paykeep_service # 主要的服务内容

startproject命令主要的作用是开启了一个项目的成熟框架,在之后的操作中只需要修改一下里面的参数。
startapp命令主要的功能是创建实现功能的模板。
以下在搭建时主要就是修改paykeep和paykeep_service文件夹下的主要内容。
paykeep的目录文件
paykeep_service下的文件目录结构

  1. 修改paykeep_service下的内容:

(1)修改app.py的内容:

class PaykeepServiceConfig(AppConfig):
    name = 'paykeep_service'   #修改name为你项目的名称

(2)修改model.py的内容:

from django.db import models
# 以下定义自己的类模型,以及相对应的数据段
class Pay(models.Model):
    id = models.IntegerField(null=False, primary_key=True)
    item_spend = models.CharField(null=False, max_length=50)
    money = models.FloatField(null=False)
    year = models.IntegerField(null=False)
    month = models.IntegerField(null=False)
    day = models.IntegerField(null=False)
    isPri = models.BooleanField(null=False)
    data_added = models.DateTimeField(auto_now_add=True)
# 以下的函数的作用是在展示 调试时显示相应的数据内容,可以自行配置
    def __str__(self):
        temp = "id is : " + str(self.id) + "--item is : " + str(self.item_spend)
        return temp

关于更多的类型数据定义可以自行google相关的django API。

(3)修改最重要的view.py的内容:

from django.shortcuts import render
from .models import Pay #!!!
from django.http import HttpResponse#!!!
from django.core.exceptions import ObjectDoesNotExist


def pay(request):
# 解析请求方式:
    if request.method == 'POST':
        item_spend = request.POST.get('item_spend')
        id = request.POST.get('id')
        money = request.POST.get('money')
        year = request.POST.get('year')
        month = request.POST.get('month')
        day = request.POST.get('day')
        isPri = request.POST.get('isPri')
        fun = request.POST.get('fun')
        # 功能解析
        if fun == '1':
            Pay.objects.create(id=id, item_spend=item_spend, money=money, year=year, month=month, day=day, isPri=isPri)
            try:
                t = Pay.objects.get(id=id)
                return HttpResponse("add_ok")
            except ObjectDoesNotExist:
                return HttpResponse("add_error")
        elif fun == '2':
            try:
                temp = Pay.objects.get(id=id)
                temp.delete()
                return HttpResponse("delete_ok")
            except ObjectDoesNotExist:
                return HttpResponse("not_found")
    else:#注意代码对齐
        return HttpResponse("error")

以上为本程序实现所需要的核心代码部分,其中我在使用时的django请求方式好像只能用get和post(因为我在网络方面也是小白,具体的咱也不是太清楚,感兴趣的同学可以详细研究下,看其它地博主好像可以用)。因此使用了一个比较蠢的功能解析(即传fun字段来判断要执行哪一个功能)的方式来实现。
另外在网络响应时使用的是HttpResponse具体的其它功能读者可以自行google API。

  1. 修改paykeep下的内容:

(1)修改setting.py的内容:

···
DEBUG = False
ALLOWED_HOSTS = ['*'] # 可访问的ip
···
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'paykeep_service', # 项目名字
]

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',
]

应该只需要做以上的修改就好,具体的可以参考github的源码。

(2)修改重要的urls.py的内容:

from django.contrib import admin
from django.urls import path
from paykeep_service.views import pay

urlpatterns = [
    path('admin/', admin.site.urls),
    path('add_pay/', pay),# !!!!!!!!!!!!!!!!
]

这里的 add_pay/ 为请求方式,pay 为在view.py中定义的方式名,这里一定要对应。如果在view中写了更多的请求功能调用也可以在这里一一列明。

  1. 初始化一下数据库:
    使用manage.py文件
python manage.py migrate
  1. 运行:
python manage.py runserver 0.0.0.0:10000

允许接入的ip地址(0.0.0.0)表示允许所有的ip接入,10000端口表示开放的端口。如果使用的是云服务器,一定要在安全组里面配置一下哟。


目前为止,关于后台的搭建已经基本完成,因为我对于后台的了解也不是太多,所以讲的一些东西可能会不太准确,但是操作基本都是正确的。另外推荐大家在搭建好后台后使用postman进行一下响应的测试,看看传入响应的数据后是否会产生预期的效果,如果没用可能需要改一下代码了。防止在后面搭建完app后再反过头来修改后台,浪费时间。本人在写后台时也是经过了很长时间的调试才达到最后的效果,大家要保持耐心哟~提醒一下,如果模型更改了就要按照第5部重新部署一下数据库文件。


APP的构建(Android)

在这里假设大家对一些基础的android操作都已经很熟悉了,因此只是写一些与网络通信的介绍,其它的可以去github参照下源代码。按照国际惯例先贴代码:

// 添加支出记录:
 private void addPay(final Pay pay,final int n){
 		//开启网络传输线程------这里可以使用rxjava等热门的框架代替,这里从简
        new Thread(new Runnable() {
            @Override
            public void run() {
            	//okhttp 客户端
                OkHttpClient client = new OkHttpClient();
                //请求体----添加你需要上传的字段
                RequestBody requestBody = new FormBody.Builder()
                        .add("id",String.valueOf(pay.getId()))
                        .add("item_spend",pay.getName())
                        .add("money",String.valueOf(pay.getMoney()))
                        .add("year",String.valueOf(pay.getYear()))
                        .add("month",String.valueOf(pay.getMonth()))
                        .add("day",String.valueOf(pay.getDay()))
                        .add("isPri",pay.isPrivate()?"True":"False")
                        .add("fun",String.valueOf(1))
                        .build();
                // 请求构建  添加你自己的ip地址   另外 请求方式 add_pay 要与urls.py中定义的相同
                Request request = new Request.Builder()
                        .url("http://"+ip+"/add_pay/")
                        .post(requestBody)
                        .build();
                try {
                    Response response = client.newCall(request).execute();
                    //请求处理
                    String rd = response.body().string();
                    Log.d(TAG, "get response : "+rd);
                    if (rd.isEmpty()){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                if (n==1)
                                    Toast.makeText(MainActivity.this,"啊咧咧~下次再上传~",Toast.LENGTH_SHORT).show();
                            }
                        });
                    }else {
                        if (rd.equals("add_ok")){
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    pay.setUploaded(true);
                                    pay.save();
                                    refreshSum();
                                    if (n==1)
                                        Toast.makeText(MainActivity.this,"添加成功",Toast.LENGTH_SHORT).show();
                                }
                            });
                        }else {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    if(n==1)
                                        Toast.makeText(MainActivity.this,"添加失败",Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            if (n==1)
                                Toast.makeText(MainActivity.this,"啊咧咧~下次再上传~",Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        }).start();
    }
	// 删除消费记录 几乎跟上面相同
    private void deletePay(final Pay pay,final int pos){
        new Thread(new Runnable() {
            @Override
            public void run() {
                OkHttpClient client = new OkHttpClient();
                RequestBody requestBody = new FormBody.Builder()
                        .add("id",String.valueOf(pay.getId()))
                        .add("item_spend",pay.getName())
                        .add("money",String.valueOf(pay.getMoney()))
                        .add("year",String.valueOf(pay.getYear()))
                        .add("month",String.valueOf(pay.getMonth()))
                        .add("day",String.valueOf(pay.getDay()))
                        .add("isPri",pay.isPrivate()?"True":"False")
                        .add("fun",String.valueOf(2))
                        .build();
                Request request = new Request.Builder()
                        .url("http://"+ip+"/add_pay/")
                        .post(requestBody)
                        .build();
                try {
                    Response response = client.newCall(request).execute();
                    String rd = response.body().string();
                    Log.d(TAG, "get response : "+rd);
                    if (rd.isEmpty()){
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(MainActivity.this,"啊咧咧~电波无法到达~",Toast.LENGTH_SHORT).show();
                            }
                        });
                    }else {
                        if (rd.equals("delete_ok")){
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    pays.remove(pos);
                                    adapter.notifyItemRemoved(pos);
                                    recyclerView.scrollToPosition(pos);
                                    LitePal.delete(Pay.class,pay.getId());
                                    refreshSum();
                                    Toast.makeText(MainActivity.this,"消除成功",Toast.LENGTH_SHORT).show();
                                }
                            });
                        }else {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(MainActivity.this,"消除失败",Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this,"啊咧咧~电波无法到达~",Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        }).start();
    }

其它的一些安卓代码,大家有兴趣的话参见github,我在这里就只放核心了,如有问题可在评论区留言。


目前为止项目基本就结束了!因为写文章时离项目完成有一段时间了,所以大家如果觉得我写的有问题,不清楚的地方都可以在评论区留言,我及时修改(只要我看地到-哈哈牛逼地叉会儿腰)

转载请声明出处,谢谢

2013-12-29 07:01:12 crxmai 阅读数 4150
  • Django开发基础 

    Django是出名的py框架。Django大而全,它出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。总之,Django是开发网站的强大工具。 本期课程主要有Bootstrap框架,Django的工程创建,常用命令,配置文件,简单模版语言等。

    3120 人正在学习 去看看 李杰

本文目录:

一,android机子连接django测试服务器方法

二,mac下停止django监听端口命令


一,

自己在随便写写一个小项目,服务器用django搭建,今天在测试Android连接django服务器。看文档是说让别人连接到服务器是通过python manage.py runserver 0.0.0.0:8000命令实现。但是恕我愚钝,一开始没反映过来到底用什么地址去访问。在电脑上直接访问0.0.0.0:8000就可以,但是在手机上怎么样都连不上,经过尝试才发现 “0.0.0.0″ 这个 IP 地址是告诉服务器去侦听任意的网络接口,

python manage.py runserver 0.0.0.0:8000 

完成设置后,本地网络中的其他终端就可以在访问你的 IP 地址了。比如我电脑的ip是http://192.168.1.103 那么从Android就可以通过http://192.168.1.103:8000访问到服务器了!


二,

因为碰到端口占用的错误,所以查了下,当然有些命令并不适合mac,所以特地贴一下:

通过lsof -i:[port]找到当下占用该端口的进程,然后用kill -[PID]杀进程就ok了。

另外注意ctr+c只是挂起进程,并不会杀掉。

2018-03-10 19:50:59 huyaoyu 阅读数 214
  • Django开发基础 

    Django是出名的py框架。Django大而全,它出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。总之,Django是开发网站的强大工具。 本期课程主要有Bootstrap框架,Django的工程创建,常用命令,配置文件,简单模版语言等。

    3120 人正在学习 去看看 李杰

概况

最近研究了一下如何在Android上添加新的account。实际上我是为了实现Sync Adapter而做的准备工作。目前的需求是这样的,在一个web server上有用户的数据(protected data/api),web server后台是自己用Django做的,安装了oauth toolkit提供OAuth2授权服务。现在待开发的Android App需要实现一个stub authenticator,用来向Android系统添加新用户,并在添加用户过程中与web server进行交互,登录并获取符合OAuth2规范的access token(在Android系统上也称为authToken)。成功添加用户后,Sync Adapter应当可以通过保存在设备上的access token访问位于web server上的用户数据。


由于自己是Android初学,目前仅实现了添加用户和获取access token。尚未实现refresh token的逻辑。这里仅做一个记录,等后期完善后再对本文进行更新(也许是自己骗自己也说不定。。)


目前开发的目标系统是Android API24,web server跑的是Django 1.11,安装了oauth toolkit。Android App利用Retrofit库实现对web server的访问(在OAuth2的定义下,这个server也是auhorization server)。调试时server和Android App都运行在本地,本机局域网ip地址为192.168.123.96,Django跑在8080端口上。


方案

本工作受到了如下博文的启发,并直接使用了博主公开的部分代码。

http://blog.udinic.com/2013/04/24/write-your-own-android-authenticator/


罗列一下所用到的概念、功能和库:

(1)Android要求我们通过继承AbstractAccountAuthenticator来定义我们的Authenticator。

(2)为了能够协助在Android上注册新的用户并获取所需的授权(access token),Android预定义了AccountAuthenticatorActivity作为辅助。

(3)为了能在添加新用户到Android系统上时访问authorization server并提示用户登录和授权access token给App,App需要另外一个Activity来显示网页,这利用了Android的WebView,并且配置了WebViewClient用于过滤http(https)协议或非http(https)协议。访问web server是通过Retrofit完成的。


目前的App在添加account到Android系统方面的设计构成图如下图所示。该图的全尺寸版本位于我的个人主页上。




基于上图对添加account的流程进行说明和解释。红色圈表示流程号,以下用“流程x”指代。

(1)通过继承AbstractAccountAuthenticator获得Authenticator类,Android要求派生类override多个方法,这里主要实现addAccount()和getAuthToken()方法,其他方法为默认形式。

(2)在Android系统的Settings->Accounts里,点击Add account,之后通过点击本App的列表条目时(如下图所示)将从Android系统的account manager发出一个调用,调用Authenticator的addAccount()方法,如流程1。目前App向Android系统注册的用户类型名为“huyaoyu.com",是我的个人主页地址,图标仅是一个png图片。



(3)Authenticator类将配套一个Activity,这个Activity将从AccountAuthenticatorActivity派生而来,之所以从AccountAuthenticatiorActivity派生,是因为它提供了setAccountAuthenticatorResult()方法和finish()方法,这些是Android期待的预定义行为。该Activity称为LoginActivity。LoginActivity从Android studio提供的LoginActivity修改而来,如下图所示,将activity原来的父类从AppCompatActivity修改为AccountAuthenticatorActivity,并增加了一个”用户名“EditText。


(4)如流程2,Authenticator的addAccount()方法将返回一个Bundle,内有一个Intent。这个Intent指定了使用App的LoginActivity作为处理该Intent的activity。此时Android系统将调用LoginActivity,如流程3

(5)用户在LoginActivity上输入必要的user credentials,例如用户名,email和密码。之后用户点击activity上的按钮(将从按钮的on click listener 内调用attemptLogin(),进而调用sendLoginIntent()函数),提交request给web server。

(6)本来从LoginActivity应当直接提交一个登录request给web server,然后web server验证登录信息后再提示用户授权App。但是目前的调试中并未实现该流程,而是显示了一个web页面,需要用户再次输入用户名和密码来登录服务器。这里将会在以后做出调整。(呵呵哒)

(7)LoginActivity从CredentialInfo类中获取与web server和本App直接相关的client ID,client secret以及redirect uri。这些信息是预先在web server上通过访问applications页面注册App而得到的,具体流程可参考我的另一篇博客

(8)为了能够在web server返回access token时,LoginActivity仍能保有有效的用户信息,LoginActivity提交登录请求时使用了一个新的activity而不是使用Android系统的浏览器,该activity称为LoginWebActivity。如流程4,LoginActivity通过一个Intent显式调用LoginWebActivity。在这个Intent的data属性中保存着需要访问的URL。

(9)LoginWebActivity内只有一个WebView,用于显示用户登录web server的页面。LoginWebActivity从Intent中获取URL,并向web server发出GET request,如流程5

(10)Web server响应请求,将登录页面返回给LoginWebActivity的WebView控件。用户得到WebView渲染好的网页,输入自己的用户名和密码进行登录,如下图。


之后web server仍然通过WebView显示一个新的授权页面。用户需要选择同意授权,如下图。此时web server充当authorization server的角色,将会根据OAuth2的要求,向Android系统返回一个authorization code,如流程6。此时LoginWebActivity的onResume()函数将响应这个response,从response中提取出该authorization code,并立即使用这个authorization code再次从web server请求一个access token,如流程7


(11)web server检验Android系统提交的request中信息的有效性,当信息有效时,将access token等信息发送回给指定的redirect uri,如流程8

(12)若做任何处理,此时默认的行为本应是LoginWebActivity的WebView继续尝试访问redirect uri指定的位置。但是在实际业务流程里,不希望LoginWebActivity处理对access token的接受,而是希望由LoginWebActivity处理。于是在LoginWebActivity的WebView上需要配置一个WebViewClient对象用于过滤uri中的sheme。这里是通过WebViewClient的派生类tokenWebViewClient实现的。tokenWebViewClient使得LoginWebActivity只对http(https) scheme做出响应,而忽略其他scheme。web server指定的redirect uri的scheme不是http或https,而是huyaoyuauth。关于redirect uri和shceme的概念,请参考我的另一篇博客

(13)根据目前LoginActivity的设计,它具备一个intent-filter,来响应web server返回的redirect uri。也就是对流程9进行响应。实际响应流程9的接口为LoginActivity的onResume()函数。onResume()函数提取web server发来的access token和其他关键信息,之后创建一个新的accout,并将access token等信息存储在这个accout内。利用继承来的setAccountAuthenticatorResult()函数和finish()函数完成account的添加。

(14)LoginActivity停止,将返回Android的account manager,流程10。添加好account后,Android系统的Settings->Accounts列表下将出现相应的account,如下图所示。


(15)为了测试新添加用户的可用性,设计了一个MainActivity,其layout上有一个button。点击该button时将尝试通过AccountManager来获取刚刚添加的那个用户的authToken(也就是刚刚获取到的access token)。

(16)测试时MainActivity通过getAuthTokenByFeatures()(在getTokenForAccountCreateIfNeeded()中调用,这个函数尚未完全完成create的功能)向Android系统发出查询请求,要求系统查找符合特定要求的用户,流程11


(17)Android系统查找到相应的accout,若对应于该account,Android系统上已经存储过authToken,则直接返回该authToken。若Android系统长没有valid的authToken时,通过调用Authenticator的getAuthToken()函数来获取authToken。由于在流程9中已经通过setAuthToken()函数设置过authToken,Android系统此时不会调用Authenticator的getAuthToken()函数。当系统使用AccountManager.invalidAuthToken()显式地令一个authToken失效后,再次获取authToken时就会调用Authenticator的getAuthToken()函数。

(18)正确获得到authToken后,返回给Android系统,流程13。Android系统将结果返回给MainActivity,流程14




参考

http://blog.udinic.com/2013/04/24/write-your-own-android-authenticator/

https://developer.android.com/training/id-auth/authenticate.html

https://developer.android.com/training/sync-adapters/creating-authenticator.html#DeclareAuthenticator


实现细节,tips

在LoginActivity里增加”用户名“EditText的原因

Django的admin默认使用用户名作为主键。


用户在流程5中显示的网页上输入用户名和密码登录后,如何能够将用户名显示在随后的授权网页上?

替换oauth toolkit的默认template。该template原本位于

python3.5/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html

复制该template到目前server开发路径上的模板文件夹内。模板文件夹的位置可通过Django项目的settings.py设定,变量名为TEMPLATES。在页面上显示当前的用户名,这个变脸是存储在user.username内。

参考

https://django-oauth-toolkit.readthedocs.io/en/latest/templates.html#

https://docs.djangoproject.com/en/1.11/topics/templates/


如何使用Django的默认login页面?

流程5的WebView中显示的是Django的默认accounts/login,在GET请求中需要明确指定next变量。这个next变量将替换Django 的login页面模板中的next表单域的值。这个next表单(隐藏表单)用于成功登录后定向到oauth toolkit的authorize页面。next的值最初是在LoginActivity的sendLoginIntent()函数内生成的,过程中使用了java.net.URLEncoder库。


Authenticator Service

为了能够在流程1中使Android系统能够正确找到对应于huyaoyu.com类型的用户创建时所使用的类,需要设计一个Authenticator Service用以配合从AbstractAccountAuthenticator派生而来的Authenticator类。这需要一个JAVA class和一个xml资源文件。可参考

https://developer.android.com/training/id-auth/custom_auth.html


AbstractAccountAuthenticator类和AccountAuthenticatorActivity类的关系

在AbstractAccountAuthenticator类的派生类Authenticator类的addAccount()函数中,需要将一个Intent放置于一个Bundle中返回。这个Intent必须包含一个作为参数传递进addAccount()来的response。这在Android的开发说明中已经强调了,

https://developer.android.com/reference/android/accounts/AccountAuthenticatorActivity.html

在AccountAuthenticatorActivity的派生类LoginActivity结束添加account前,需要显式调用 setAccountAuthenticatorResult()函数。


使用WebViewClient对象来对scheme进行过滤

流程8中提到,需要使用WebViewClient对象实现对http(https)scheme和非http(https) scheme进行过滤,这需要使用到shouldOverrideUrlLoading()。目前使用的Android SDK的WebViewClient类提供了两个shouldOverrideUrlLoading()的接口。实际测试时发现只有一个是可用的。参考

https://stackoverflow.com/questions/36484074/is-shouldoverrideurlloading-really-deprecated-what-can-i-use-instead


在WebView中使能JavaScript

在进行网页上的授权操作时(流程5、6)需要使用JavaScript,WebView默认情况下是关闭JavaScript支持的。使能JavaScript的方法参考

https://developer.android.com/guide/webapps/webview.html#UsingJavaScript


Android Permission

在测试时遇到了几次Android系统授权错误,于是在manifest文件中一股脑添加了几个permission,这些包括

    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.READ_PROFILE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>

参考

https://developer.android.com/training/id-auth/authenticate.html


20180317更新

将获取authToken的测试过程修改为在点击GET AUTHTOKEN按钮后,启动一个AsyncTask,在AsyncTask的doInBackground()函数中,通过AccountManager.blockingGetAuthToken()函数获取authToken。并根据authToken的时效来判断是否需要链接服务器来refresh token。成功获取有效的authToken后,将在AsyncTask的onPostExecute()函数中对MainActivity的TextView对象进行内容更新。使用AsyncTask获取authToken的流程如下图所示。原尺寸图片位于我的个人网站

Flowchart of AsyncTask

oauth toolkit执行refresh token的方法参考了

https://stackoverflow.com/questions/26903087/how-to-use-refresh-token-for-getting-new-access-tokendjango-oauth-toolkit


源码

本实例的源码已共享在我的GitHub上。

2018-03-31 12:19:02 qq_40213521 阅读数 2063
  • Django开发基础 

    Django是出名的py框架。Django大而全,它出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。总之,Django是开发网站的强大工具。 本期课程主要有Bootstrap框架,Django的工程创建,常用命令,配置文件,简单模版语言等。

    3120 人正在学习 去看看 李杰

参加一个比赛,指定用虹软的人脸识别功能,奈何虹软人脸识别要自己建人脸库,不然就只能离线用,总不能装个样子,简单看了下虹软Demo,下面决定用这种简单方法实现在线人脸识别:

    Android端(虹软Demo)取出人脸信息和姓名,人脸信息写入.data文件,存入手机本地------>取出文件上传至Python Djano后台,后台把文件保存在工程下并把路径存入数据库------>Android端访问后台接口,遍历所有数据库中文件路径然后作为参数再次访问后台,得到所有人脸信息文件和姓名,使用识别功能时,就可以开始和所有人脸信息比对,得到效果


Django 后台代码:

在工程下新建一个文件夹uploads,用来存放人脸数据.data文件

为简单实现,在setting.py中注释

#'django.middleware.csrf.CsrfViewMiddleware',
不然post请求被拦截,认真做工程不建议这样做

app.urls:

from django.conf.urls import url, include
from . import views

urlpatterns = [
    url(r'^posts',views.posttest),
    url(r'^getallface',views.getface),
    url(r'^filedown',views.file_download),
]
views.py
#coding:utf-8
import json
from imp import reload
import sys
import os
from django.http import HttpResponse, HttpResponseRedirect, StreamingHttpResponse
from django.shortcuts import render, render_to_response
from django.template import RequestContext
from faceproject.settings import BASE_DIR, MEDIA_ROOT
reload(sys)
from faceapp.models import face, Faceinfos, FaceinfoForm


def getface(request):
    if request.method=='GET':
        WbAgreementModel = Faceinfos.objects.all()
        cflist = []
        cfdata = {}
        cfdata["coding"] = 1
        cfdata['message'] = 'success'
        for wam in WbAgreementModel:
            wlist = {}
            wlist['name'] = wam.username
            wlist['faceinfo']=wam.fileinfo
            cflist.append(wlist)
        cfdata['data'] = cflist
        return HttpResponse(json.dumps(cfdata, ensure_ascii=False), content_type="application/json")

def posttest(request):
    if request.method=='POST':
        files=request.FILES['fileinfo']
        if not files:
            return HttpResponse("no files for upload!")
        Faceinfos(
            username=request.POST['username'],
            fileinfo=MEDIA_ROOT+'/'+files.name
        ).save()
        f = open(os.path.join('uploads', files.name), 'wb')
        for line in files.chunks():
            f.write(line)
            f.close()
    return HttpResponseRedirect('/face/')

def file_download(request):
    global dirs
    if request.method=='GET':
        dirs=request.GET['dirs']
    def file_iterator(file_name, chunk_size=512):
        with open(file_name) as f:
            while True:
                c = f.read(chunk_size)
                if c:
                    yield c
                else:
                    break

    the_file_name = dirs
    response = StreamingHttpResponse(file_iterator(the_file_name))
    return response

Android代码,使用Okhttp进行网络连接

网络访问类:

public class HttpUtil {
    public static void sendOkHttpRequestPost(String address, RequestBody requestBody, okhttp3.Callback callback) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(address)
                .post(requestBody)
                .build();
        client.newCall(request).enqueue(callback);
    }

    public static void sendOkHttpRequestGET(String address, okhttp3.Callback callback) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(address)
                .build();
        client.newCall(request).enqueue(callback);
    }

}
public class downloadf {
        private static downloadf downloadUtil;
        private final OkHttpClient okHttpClient;
        public static downloadf get() {
            if (downloadUtil == null) {
                downloadUtil = new downloadf();
            }
            return downloadUtil;
        }
        private downloadf() {
            okHttpClient = new OkHttpClient();
        }
        /**
         * @param url 下载连接
         * @param saveDir 储存下载文件的SDCard目录
         * @param listener 下载监听
         */
        public void download(final String url, final String saveDir, final OnDownloadListener listener) {
            Request request = new Request.Builder().url(url).build();
            okHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    // 下载失败
                    listener.onDownloadFailed();
                }
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    InputStream is = null;
                    byte[] buf = new byte[2048];
                    int len = 0;
                    FileOutputStream fos = null;
                    // 储存下载文件的目录
                    String savePath = isExistDir(saveDir);
                    try {
                        is = response.body().byteStream();
                        long total = response.body().contentLength();
                        File file = new File(savePath, getNameFromUrl(url));
                        fos = new FileOutputStream(file);
                        long sum = 0;
                        while ((len = is.read(buf)) != -1) {
                            fos.write(buf, 0, len);
                            sum += len;
                            int progress = (int) (sum * 1.0f / total * 100);
                            // 下载中
                            listener.onDownloading(progress);
                        }
                        fos.flush();
                        // 下载完成
                        listener.onDownloadSuccess();
                    } catch (Exception e) {
                        listener.onDownloadFailed();
                    } finally {
                        try {
                            if (is != null)
                                is.close();
                        } catch (IOException e) {
                        }
                        try {
                            if (fos != null)
                                fos.close();
                        } catch (IOException e) {
                        }
                    }
                }
            });
        }

        /**
         * @param saveDir
         * @return
         * @throws IOException
         * 判断下载目录是否存在
         */
        private String isExistDir(String saveDir) throws IOException {
            // 下载位置
            File downloadFile = new File(Environment.getExternalStorageDirectory(), saveDir);
            if (!downloadFile.mkdirs()) {
                downloadFile.createNewFile();
            }
            String savePath = downloadFile.getAbsolutePath();
            return savePath;
        }

        /**
         * @param url
         * @return
         * 从下载连接中解析出文件名
         */
        @NonNull
        private String getNameFromUrl(String url) {
            return url.substring(url.lastIndexOf("/") + 1);
        }

        public interface OnDownloadListener {
            /**
             * 下载成功
             */
            void onDownloadSuccess();

            /**
             * @param progress
             * 下载进度
             */
            void onDownloading(int progress);

            /**
             * 下载失败
             */
            void onDownloadFailed();
        }
    }
使用:

MediaType type = MediaType.parse("application/octet-stream");//"text/xml;charset=utf-8"
File file = new File(mDBPath +"/"+name+".data");
            File file1=new File(mDBPath+"/face.txt");
RequestBody fileBody = RequestBody.create(type, file);
RequestBody fileBody1 = RequestBody.create(type, file1);
RequestBody requestBody1 = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart("username",name)
      .addFormDataPart("fileinfo",name+".data",fileBody)
      .build();

HttpUtil.sendOkHttpRequestPost("http://120.79.51.57:8000/face/posts", requestBody1, new Callback() {
   @Override
   public void onFailure(Call call, IOException e) {
      Log.v("oetrihgdf","失败");
   }
   @Override
   public void onResponse(Call call, Response response) throws IOException {
      Log.v("oifdhdfg","成功");

   }

});

HttpUtil.sendOkHttpRequestGET("http://120.79.51.57:8000/face/getallface", new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        final String responsedata=response.body().string();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Gson gson=new Gson();
                Facelist facelist= gson.fromJson(responsedata,new TypeToken<Facelist>(){}.getType());
                faces.addAll(facelist.getData());
                Log.v("faceilist",faces.get(0).getFaceinfo());
                for (Face face:faces){
                    Log.v("orihgdofg",face.getFaceinfo());
                    downloadf.get().download("http://120.79.51.57:8000/face/filedown?"+"dir="+face.getFaceinfo(), getExternalCacheDir().getPath(), new downloadf.OnDownloadListener() {
                        @Override
                        public void onDownloadSuccess() {
                            Log.v("jmhfgh","下载完成");
                            //Utils.showToast(MainActivity.this, "下载完成");
                        }
                        @Override
                        public void onDownloading(int progress) {
                            // progressBar.setProgress(progress);
                        }
                        @Override
                        public void onDownloadFailed() {
                            Log.v("jmhfgh","下载失败");
                            // Utils.showToast(MainActivity.this, "下失败载失败");
                        }
                    });
                    FileInputStream fs = null;
                    try {
                        fs = new FileInputStream(getExternalCacheDir().getPath()+"/"+face.getName()+".data");
                        ExtInputStream bos = new ExtInputStream(fs);
                        //load version
                        byte[] version_saved = bos.readBytes();
                        FaceDB.FaceRegist frface = new FaceDB.FaceRegist(face.getName());
                        AFR_FSDKFace fsdkFace = new AFR_FSDKFace(version_saved);
                        frface.mFaceList.add(fsdkFace);
                        mResgist.add(frface);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

    }
});

2016-06-06 22:30:06 wyb199026 阅读数 2803
  • Django开发基础 

    Django是出名的py框架。Django大而全,它出名的是其全自动化的管理后台:只需要使用起ORM,做简单的对象定义,它就能自动生成数据库结构、以及全功能的管理后台。总之,Django是开发网站的强大工具。 本期课程主要有Bootstrap框架,Django的工程创建,常用命令,配置文件,简单模版语言等。

    3120 人正在学习 去看看 李杰

背景

我旁边的开发在学ReactNative,说要拿我的博客练手,顺带给我写一个Android的博客客户端。这感情好啊,这个时候就必须要给他写两个接口,一个是获取文章列表,另一个是文章的详情。

接口

说实话,写两个接口应该是非常简单的,我也是这么想的,当然,分分钟也就写出来了。

def get_article(request, article_id):
    """
    获取文章详情
    :param request:
    :param article_id:文章ID
    :return: Json
    """
    article_view = BlogBody.objects.get(id=article_id)
    rst_data = {
        "errmsg": "OK",
        "article_title": article_view.blog_title,
        "article_body": markdown.markdown(article_view.blog_body),
        "article_author": article_view.blog_author,
    }
    return JsonResponse(rst_data, safe=False)
def get_article_list(request, list_type):
    """
    获取文章列表
    :param request:
    :param list_type:类型
    :return:
    """
    article_list = []
    if list_type == "all":
        article_list_all = BlogBody.objects.all()
        for x in article_list_all:
            article_list_title = x.blog_title
            article_list_id = x.id
            article_data = {
                "title": article_list_title,
                "id": article_list_id
            }
            article_list.append(article_data)
        rst_data = {
            "errmsg": "OK",
            "article_list": article_list
        }
        return JsonResponse(rst_data, safe=False)
    else:
        article_list_type = BlogBody.objects.filter(blog_type=list_type)
        for x in article_list_type:
            article_list_title = x.blog_title
            article_list_id = x.id
            article_data = {
                "title": article_list_title,
                "id": article_list_id
            }
            article_list.append(article_data)
        rst_data = {
            "errmsg": "OK",
            "article_list": article_list
        }
        return JsonResponse(rst_data, safe=False)

URL我就不当列了,并不难写。

测试

身为一个测试人员,自己写完的东西肯定是要测一下的。我在本地环境调试的时候测试通过,部署上SAE之后,我自己也测了一下,并没有问题。该返回的也都返回了。

问题

当我把接口文档丢给开发之后,开发的反馈是,无法获取数据。这就很奇怪了,我自己测试时很正常啊,就是一个普通的http请求而已。

根据开发的经验,这个是跨域调用的问题。我返回的数据必须要是CORS的数据,也就是返回头部要有Access-Control-Allow-Origin这个信息。否则只能同源调用。

什么是CORS?

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

解决办法

Google了一下,有两种解决办法,一种是使用JSONP来处理返回数据,看起来有点麻烦,另一种是在Http返回的时候加一个头部信息。

response = HttpResponse()
response['Access-Control-Allow-Origin'] = '*'
return response

那么问题又来了,我使用的是JsonResponse(),并不是HttpResponse()。

好吧,其实两个是一样的,我们可以看JsonResponse的源码

class JsonResponse(HttpResponse):
    """
    An HTTP response class that consumes data to be serialized to JSON.

    :param data: Data to be dumped into json. By default only ``dict`` objects
      are allowed to be passed due to a security flaw before EcmaScript 5. See
      the ``safe`` parameter for more information.
    :param encoder: Should be an json encoder class. Defaults to
      ``django.core.serializers.json.DjangoJSONEncoder``.
    :param safe: Controls if only ``dict`` objects may be serialized. Defaults
      to ``True``.
    """

    def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, **kwargs):
        if safe and not isinstance(data, dict):
            raise TypeError('In order to allow non-dict objects to be '
                'serialized set the safe parameter to False')
        kwargs.setdefault('content_type', 'application/json')
        data = json.dumps(data, cls=encoder)
        super(JsonResponse, self).__init__(content=data, **kwargs)

其他的看不懂没关系,只要看懂JsonResponse是继承于HttpResponse就行了。父类的方法,子类肯定也是可以适用的,最后的返回结果我们只要简单的修改一下就行了。

    response = JsonResponse(rst_data, safe=False)
    response['Access-Control-Allow-Origin'] = '*'
    return response

    response = JsonResponse(rst_data, safe=False)
    response['Access-Control-Allow-Origin'] = '*'
    return response

这样用jQuery就可以成功的跨域调用了。

注意:没必要共享的接口就不要使用跨域资源共享,否则容易引起攻击

restart

阅读数 109

没有更多推荐了,返回首页