實現手機掃描二維碼頁面登陸,相似web微信-第三篇,手機客戶端

轉載自:http://blog.csdn.net/otangba/article/details/8265896html

上一篇,介紹了二維碼生成的機制,緊接着,咱們就要開發手機客戶端來識別這個二維碼。java

二維碼,其實是記錄了這個頁面的sessionID,目的是爲了最後讓服務器能經過long polling的機制去通知到這個瀏覽器。node

建立二維碼的時候咱們採用了nodejs的QRcode庫,其實若是換了其餘的web服務器,也能夠有其餘的可選包,例如zxing。android

手機上用的比較多的就是zxing庫,不過用過的人都知道,zxing庫的核心core只是提供二維碼的解析,而應用程序自己對攝像頭的操做部分必須參考zxing的應用源碼。web

那個源碼比較的複雜,雖然很好理解,可是代碼量太大了。若是要分析那部分源碼,文章就要寫的長篇大論了,因此這一次,咱們不用zxing庫,而選擇一個更爲高效實用的android二維碼掃描組件:zbar瀏覽器

ZBar不是純的java代碼,而是用了C編譯的native library,所以識別的效率上比zxing要高不少。服務器

閒話少說,先看看程序運行的一系列流程吧:微信

第一步,登陸手機軟件,咱們作測試用,就只須要輸入一個用戶名,提交到服務器,返回一個tokensession

爲何要作第一步,由於咱們實現手機二維碼登陸的基礎原則就是咱們的手機客戶端必須的登陸的,這樣才能做爲一個憑據app

例如微信,假如你不登陸是不能掃描的,因此咱們的例子模擬一個登陸的過程

第二步,登陸成功以後,開始掃描,二維碼就顯示在屏幕上

第三步,掃描完成後,確認是否登陸網頁

最後,頁面提示登陸完成

下面開始,因爲long polling的過程我已經作好,所以手機軟件才能正常運行,而今天咱們只說手機客戶端,服務器端的內容下一篇再說,因此,咱們先假設全部的接口都OK

手機客戶端分爲三個Activity,分別爲登陸,掃描,確認

先作第一個activity

eclipse創建項目,爲了符合android4的UI規範,咱們採用了sherlock actionbar來實現3.x一下版本android系統的actionbar

所以,項目須要引用actionbar lib,sherlock actionbar的庫不能直接引用jar包,必需要下載源碼而且以lib的方式引用源碼

引用完以後,新建一個class,叫作LoginActivity 繼承自SherlockActivity

爲了要實如今actionbar上的loading進度圈,須要設置窗體的屬性

 

[java]  view plain copy
 
  1. requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);  
  2.         setContentView(R.layout.login);  
  3.         setSupportProgressBarIndeterminateVisibility(false);  

 

第一個activity界面很簡單,就是幾個按鈕,可是須要有一次和服務器的通訊,也就是登陸的過程若是登陸成功,則顯示下一步掃描的按鈕,第一個activity很簡單

所有代碼:

 

[java]  view plain copy
 
  1. package com.zbiti.qrcodelogin.activity;  
  2.   
  3. import java.util.logging.LogRecord;  
  4.   
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.os.Bundle;  
  8. import android.os.Handler;  
  9. import android.os.Message;  
  10. import android.view.View;  
  11. import android.view.View.OnClickListener;  
  12. import android.widget.Button;  
  13. import android.widget.EditText;  
  14. import android.widget.TextView;  
  15. import android.widget.Toast;  
  16.   
  17. import com.actionbarsherlock.app.SherlockActivity;  
  18. import com.actionbarsherlock.view.Window;  
  19. import com.zbiti.qrcodelogin.R;  
  20. import com.zbiti.qrcodelogin.util.BaseHttpClient;  
  21.   
  22. public class LoginActivity extends SherlockActivity {  
  23.     private Context mContext;  
  24.     private TextView txtInfo;  
  25.     private EditText txtUserName;  
  26.     private Button btnLogin;  
  27.     private Button btnStartScan;  
  28.     private Button btnRelogin;  
  29.     private String token = null;  
  30.     private final static String LOGIN_URL = "http://192.168.111.109:8000/moblogin?";  
  31.   
  32.     private final static int MSG_LOGIN_FAILED = 0;  
  33.     private final static int MSG_LOGIN_OK = 1;  
  34.   
  35.     @Override  
  36.     protected void onCreate(Bundle savedInstanceState) {  
  37.         super.onCreate(savedInstanceState);  
  38.         mContext = LoginActivity.this;  
  39.         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);  
  40.         setContentView(R.layout.login);  
  41.         setSupportProgressBarIndeterminateVisibility(false);  
  42.         // reference all used view  
  43.         txtInfo = (TextView) findViewById(R.id.txt_info);  
  44.         txtUserName = (EditText) findViewById(R.id.edit_username);  
  45.         btnLogin = (Button) findViewById(R.id.btn_client_login);  
  46.         btnStartScan = (Button) findViewById(R.id.btn_startscan);  
  47.         btnRelogin = (Button) findViewById(R.id.btn_relogin);  
  48.         btnRelogin.setOnClickListener(new OnClickListener() {  
  49.   
  50.             @Override  
  51.             public void onClick(View v) {  
  52.                 txtInfo.setText(R.string.login_hint);  
  53.                 findViewById(R.id.cont_login).setVisibility(View.VISIBLE);  
  54.                 findViewById(R.id.cont_loggedin).setVisibility(View.GONE);  
  55.   
  56.             }  
  57.         });  
  58.   
  59.         btnLogin.setOnClickListener(new OnClickListener() {  
  60.   
  61.             @Override  
  62.             public void onClick(View v) {  
  63.                 setSupportProgressBarIndeterminateVisibility(true);  
  64.                 new Thread(new Runnable() {  
  65.   
  66.                     @Override  
  67.                     public void run() {  
  68.                         getToken();  
  69.                     }  
  70.                 }).start();  
  71.             }  
  72.         });  
  73.   
  74.         btnStartScan.setOnClickListener(new OnClickListener() {  
  75.   
  76.             @Override  
  77.             public void onClick(View v) {  
  78.                 Intent intent = new Intent();  
  79.                 intent.putExtra("token", token);  
  80.                 intent.setClass(mContext, MainActivity.class);  
  81.                 startActivity(intent);  
  82.   
  83.             }  
  84.         });  
  85.     }  
  86.   
  87.     private void getToken() {  
  88.         String userName = txtUserName.getText().toString().trim();  
  89.         if (!userName.equals("")) {  
  90.             try {  
  91.                 token = BaseHttpClient.httpGet(LOGIN_URL + userName).trim();  
  92.             } catch (Exception e) {  
  93.                 // TODO Auto-generated catch block  
  94.                 e.printStackTrace();  
  95.             }  
  96.             System.out.println(">>>" + token);  
  97.         }  
  98.         if (token == null)  
  99.             return;  
  100.         if (token.equals("")) {  
  101.             handler.sendEmptyMessage(MSG_LOGIN_FAILED);  
  102.         } else {  
  103.             handler.sendEmptyMessage(MSG_LOGIN_OK);  
  104.         }  
  105.     }  
  106.   
  107.     private Handler handler = new Handler() {  
  108.   
  109.         @Override  
  110.         public void handleMessage(Message msg) {  
  111.             if (msg.what == MSG_LOGIN_OK) {  
  112.                 // 成功得到token  
  113.                 setSupportProgressBarIndeterminateVisibility(false);  
  114.   
  115.                 txtInfo.setText(getString(R.string.token_info, token));  
  116.                 findViewById(R.id.cont_login).setVisibility(View.GONE);  
  117.                 findViewById(R.id.cont_loggedin).setVisibility(View.VISIBLE);  
  118.             } else if (msg.what == MSG_LOGIN_FAILED) {  
  119.                 setSupportProgressBarIndeterminateVisibility(false);  
  120.                 Toast.makeText(mContext, R.string.login_failed,  
  121.                         Toast.LENGTH_SHORT).show();  
  122.             }  
  123.         }  
  124.   
  125.     };  
  126. }  


佈局文件:

 

 

[html]  view plain copy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <LinearLayout  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:gravity="center_vertical"  
  11.         android:orientation="horizontal" >  
  12.   
  13.         <TextView  
  14.             android:id="@+id/txt_info"  
  15.             android:layout_width="match_parent"  
  16.             android:layout_height="wrap_content"  
  17.             android:layout_margin="20dp"  
  18.             android:drawableLeft="@drawable/chat"  
  19.             android:drawablePadding="10dp"  
  20.             android:text="@string/login_hint" />  
  21.     </LinearLayout>  
  22.   
  23.     <LinearLayout  
  24.         android:id="@+id/cont_loggedin"  
  25.         android:layout_width="match_parent"  
  26.         android:layout_height="wrap_content"  
  27.         android:orientation="vertical"  
  28.         android:visibility="gone" >  
  29.   
  30.         <LinearLayout  
  31.             android:layout_width="match_parent"  
  32.             android:layout_height="wrap_content"  
  33.             android:orientation="vertical"  
  34.             android:paddingLeft="20dp"  
  35.             android:paddingRight="20dp"  
  36.             android:paddingTop="10dp" >  
  37.   
  38.             <Button  
  39.                 android:id="@+id/btn_startscan"  
  40.                 android:layout_width="match_parent"  
  41.                 android:layout_height="wrap_content"  
  42.                 android:text="@string/start_scan" >  
  43.             </Button>  
  44.   
  45.             <Button  
  46.                 android:id="@+id/btn_relogin"  
  47.                 android:layout_marginTop="10dp"  
  48.                 android:layout_width="match_parent"  
  49.                 android:layout_height="wrap_content"  
  50.                 android:text="@string/relogin" >  
  51.             </Button>  
  52.         </LinearLayout>  
  53.     </LinearLayout>  
  54.   
  55.     <LinearLayout  
  56.         android:id="@+id/cont_login"  
  57.         android:layout_width="match_parent"  
  58.         android:layout_height="wrap_content"  
  59.         android:orientation="vertical" >  
  60.   
  61.         <LinearLayout  
  62.             android:layout_width="match_parent"  
  63.             android:layout_height="wrap_content"  
  64.             android:orientation="horizontal"  
  65.             android:paddingLeft="20dp"  
  66.             android:paddingRight="20dp"  
  67.             android:paddingTop="10dp" >  
  68.   
  69.             <TextView  
  70.                 android:layout_width="60dp"  
  71.                 android:layout_height="wrap_content"  
  72.                 android:text="@string/user_name" />  
  73.   
  74.             <EditText  
  75.                 android:id="@+id/edit_username"  
  76.                 android:layout_width="match_parent"  
  77.                 android:layout_height="wrap_content" />  
  78.         </LinearLayout>  
  79.   
  80.         <LinearLayout  
  81.             android:layout_width="match_parent"  
  82.             android:layout_height="wrap_content"  
  83.             android:orientation="horizontal"  
  84.             android:paddingLeft="20dp"  
  85.             android:paddingRight="20dp"  
  86.             android:paddingTop="10dp" >  
  87.   
  88.             <Button  
  89.                 android:id="@+id/btn_client_login"  
  90.                 android:layout_width="match_parent"  
  91.                 android:layout_height="wrap_content"  
  92.                 android:text="@string/login" >  
  93.             </Button>  
  94.         </LinearLayout>  
  95.     </LinearLayout>  
  96.   
  97. </LinearLayout>  

 

 

代碼裏的 privatefinalstatic StringLOGIN_URL ="http://192.168.111.109:8000/moblogin?";

這一行是我本地測試用的模擬驗證的服務器地址,和生成二維碼的頁面同樣,都是Nodejs生成的,代碼咱們下一篇解釋,這個接口接收手機填寫的用戶名,而且經過sha1進行加密,將加密事後的字符串返回給手機,手機將這個字符串做爲token變量而且會傳遞下去。

 

 

下面開始第二個activity,就是掃描界面

首先引用zbar的包,將zbar相關的包拷貝進libs目錄

包含的so文件就是c編寫的native code

新建類MainActivity繼承自SherlockActivity

實現掃描的代碼能夠從zbar的例子裏整,這裏不重複

須要把上一個activity傳遞的token獲取,並往下傳遞

 

[java]  view plain copy
 
  1. @Override  
  2.     public void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.         setContentView(R.layout.main);  
  5.         getSupportActionBar().setDisplayHomeAsUpEnabled(true);  
  6.         autoFocusHandler = new Handler();  
  7.         preview = (FrameLayout) findViewById(R.id.cameraPreview);  
  8.         // mCamera = getCameraInstance();  
  9.         Intent intent = getIntent();  
  10.         token = intent.getStringExtra("token");  
  11.         if (token == null || token.equals(""))  
  12.             finish();  
  13.     }  


在掃描完成的回調裏,咱們將掃描得到sessionID和token一塊兒往下一個activity傳遞

 

 

[java]  view plain copy
 
  1. PreviewCallback previewCb = new PreviewCallback() {  
  2.         public void onPreviewFrame(byte[] data, Camera camera) {  
  3.             Camera.Parameters parameters = camera.getParameters();  
  4.             Size size = parameters.getPreviewSize();  
  5.   
  6.             Image barcode = new Image(size.width, size.height, "Y800");  
  7.             barcode.setData(data);  
  8.   
  9.             int result = scanner.scanImage(barcode);  
  10.             String qrcodeString = null;  
  11.             if (result != 0) {  
  12.                 previewing = false;  
  13.                 mCamera.setPreviewCallback(null);  
  14.                 mCamera.stopPreview();  
  15.   
  16.                 SymbolSet syms = scanner.getResults();  
  17.                 for (Symbol sym : syms) {  
  18.                     qrcodeString = sym.getData();  
  19.                 }  
  20.   
  21.             }  
  22.             if (qrcodeString != null) {  
  23.                 Intent intent = new Intent();  
  24.                 intent.setClass(MainActivity.this, ConfirmActivity.class);  
  25.                 intent.putExtra("qrcodestring", qrcodeString);  
  26.                 intent.putExtra("token", token);  
  27.                 startActivity(intent);  
  28.             }  
  29.   
  30.         }  
  31.     };  


在完成掃描的回調裏,咱們把qrcodestring和token都提交給下一個activity

 

接着,咱們來寫第三個activity

仍然建立一個類集成sherlockactivity,類名ConfirmActivity

這個activity在啓動的時候,也就意味着,掃描成功了,那麼就先通知服務器端,掃描成功,頁面也會即時展現出掃描成功,等待手機確認登陸的信息

接下來,若是點確認登陸,則通知服務器確認登陸。

所以咱們有2個接口

 

private final static StringSCANNED_URL ="http://192.168.111.109:8000/scanned?";

privatefinalstatic StringCONFIRMLOGIN_URL ="http://192.168.111.109:8000/confirmed?";

一個是通知服務器已經成功掃描的http接口,一個是通知服務器確認登陸的接口。參數都是sessionID,也就是二維碼帶的信息,和用戶token。

 

[java]  view plain copy
 
  1. package com.zbiti.qrcodelogin.activity;  
  2.   
  3. import android.content.Context;  
  4. import android.content.Intent;  
  5. import android.os.Bundle;  
  6. import android.os.Handler;  
  7. import android.os.Message;  
  8. import android.view.View;  
  9. import android.view.View.OnClickListener;  
  10. import android.widget.Button;  
  11. import android.widget.TextView;  
  12.   
  13. import com.actionbarsherlock.app.SherlockActivity;  
  14. import com.actionbarsherlock.view.MenuItem;  
  15. import com.zbiti.qrcodelogin.R;  
  16. import com.zbiti.qrcodelogin.util.BaseHttpClient;  
  17.   
  18. public class ConfirmActivity extends SherlockActivity {  
  19.     private Context mContext;  
  20.     private String sessionID;  
  21.     private String token;  
  22.     private final static String SCANNED_URL = "http://192.168.111.109:8000/scanned?";  
  23.     private final static String CONFIRMLOGIN_URL = "http://192.168.111.109:8000/confirmed?";  
  24.     private final static int LOGIN_SUCCESS=1;  
  25.     private final static int LOGIN_FAIL=0;  
  26.   
  27.     private Button btnConfirmLogin;  
  28.     private Button btnCancel;  
  29.     private TextView txtInfo;  
  30.   
  31.     @Override  
  32.     protected void onCreate(Bundle savedInstanceState) {  
  33.         super.onCreate(savedInstanceState);  
  34.         mContext = ConfirmActivity.this;  
  35.         setContentView(R.layout.confirm_login);  
  36.         getSupportActionBar().setDisplayHomeAsUpEnabled(true);  
  37.         btnConfirmLogin = (Button) findViewById(R.id.btn_login);  
  38.         btnCancel = (Button) findViewById(R.id.btn_cancel);  
  39.         txtInfo=(TextView)findViewById(R.id.txt_confirm_info);  
  40.   
  41.         btnConfirmLogin.setOnClickListener(new OnClickListener() {  
  42.   
  43.             @Override  
  44.             public void onClick(View v) {  
  45.                 new Thread(new Runnable() {  
  46.   
  47.                     @Override  
  48.                     public void run() {  
  49.                         notifyConfirmed();  
  50.   
  51.                     }  
  52.                 }).start();  
  53.   
  54.             }  
  55.         });  
  56.         btnCancel.setOnClickListener(new OnClickListener() {  
  57.   
  58.             @Override  
  59.             public void onClick(View v) {  
  60.                 finish();  
  61.             }  
  62.         });  
  63.   
  64.         Intent intent = getIntent();  
  65.         sessionID = intent.getStringExtra("qrcodestring");  
  66.         token = intent.getStringExtra("token");  
  67.         if (sessionID == null || sessionID == null) {  
  68.             finish();  
  69.         }  
  70.         new Thread(new Runnable() {  
  71.   
  72.             @Override  
  73.             public void run() {  
  74.                 notifyScanned();  
  75.             }  
  76.         }).start();  
  77.   
  78.     }  
  79.   
  80.     private void notifyConfirmed() {  
  81.         String url = CONFIRMLOGIN_URL + "token=" + token + "&sessionid="  
  82.                 + sessionID;  
  83.         String s = null;  
  84.         try {  
  85.             s = BaseHttpClient.httpGet(url);  
  86.             if(s.equals("confirmed")){  
  87.                 handler.sendEmptyMessage(LOGIN_SUCCESS);  
  88.             }else{  
  89.                 handler.sendEmptyMessage(LOGIN_FAIL);  
  90.             }  
  91.         } catch (Exception e) {  
  92.             e.printStackTrace();  
  93.         }  
  94.   
  95.     }  
  96.   
  97.     private void notifyScanned() {  
  98.         String url = SCANNED_URL + "token=" + token + "&sessionid=" + sessionID;  
  99.         String s = null;  
  100.         try {  
  101.             s = BaseHttpClient.httpGet(url);  
  102.         } catch (Exception e) {  
  103.             e.printStackTrace();  
  104.         }  
  105.   
  106.     }  
  107.     private Handler handler= new Handler(){  
  108.   
  109.         @Override  
  110.         public void handleMessage(Message msg) {  
  111.             if(msg.what==LOGIN_FAIL){  
  112.                 txtInfo.setText(R.string.pc_login_fail);  
  113.             }else if(msg.what==LOGIN_SUCCESS){  
  114.                 btnConfirmLogin.setVisibility(View.GONE);  
  115.                 btnCancel.setVisibility(View.GONE);  
  116.                 txtInfo.setText(R.string.pc_login_succuess);  
  117.             }  
  118.         }  
  119.           
  120.     };  
  121.   
  122.     @Override  
  123.     public boolean onMenuItemSelected(int featureId, MenuItem item) {  
  124.         if (item.getItemId() == android.R.id.home) {  
  125.             finish();  
  126.         }  
  127.         return super.onMenuItemSelected(featureId, item);  
  128.     }  
  129. }  


佈局以下:

 

 

[html]  view plain copy
 
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <ImageView  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:layout_gravity="center_horizontal"  
  11.         android:layout_marginBottom="10dp"  
  12.         android:layout_marginLeft="40dp"  
  13.         android:layout_marginRight="40dp"  
  14.         android:layout_marginTop="10dp"  
  15.         android:src="@drawable/pcs" />  
  16.   
  17.     <TextView android:id="@+id/txt_confirm_info"  
  18.         android:layout_width="wrap_content"  
  19.         android:layout_height="wrap_content"  
  20.         android:layout_gravity="center_horizontal"  
  21.         android:text="@string/confirm_login_label"  
  22.         android:textSize="18sp" />  
  23.   
  24.     <Button android:id="@+id/btn_login"  
  25.         android:layout_width="match_parent"  
  26.         android:layout_height="wrap_content"  
  27.         android:layout_marginTop="20dp"  
  28.         android:layout_marginLeft="40dp"  
  29.         android:layout_marginRight="40dp"  
  30.         android:text="@string/btn_confirm_login" />  
  31.   
  32.     <Button android:id="@+id/btn_cancel"  
  33.         android:layout_width="match_parent"  
  34.         android:layout_height="wrap_content"  
  35.         android:layout_marginLeft="40dp"  
  36.         android:layout_marginRight="40dp"  
  37.         android:text="@string/btn_cancel" />  
  38.   
  39. </LinearLayout>  


這樣一個手機客戶端就完成了,其中用到的http請求的過程以下:

 

 

[java]  view plain copy
 
  1. public static String httpGet(String url) throws Exception{  
  2.         String result = null;  
  3.         HttpClient client = new DefaultHttpClient();   
  4.         HttpGet get = new HttpGet(url);  
  5.         HttpResponse response = client.execute(get);  
  6.         if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {   
  7.             InputStream is = response.getEntity().getContent();  
  8.             result=inStream2String(is);  
  9.         }  
  10.         return result;  
  11.     }  
  12.       
  13.     public static String inStream2String(InputStream is) throws Exception {    
  14.         ByteArrayOutputStream baos = new ByteArrayOutputStream();    
  15.         byte[] buf = new byte[1024];    
  16.         int len = -1;    
  17.         while ((len = is.read(buf)) != -1) {    
  18.             baos.write(buf, 0, len);    
  19.         }    
  20.         return new String(baos.toByteArray());    
  21.     }   

 

string以下

 

[html]  view plain copy
 
  1. <resources>  
  2.     <string name="app_name">二維碼登陸客戶端</string>  
  3.     <string name="confirm_login_title">已經掃描,請確認登陸</string>  
  4.     <string name="confirm_login_label">即將在瀏覽器上登陸系統\n請確認是不是本人操做</string>  
  5.     <string name="btn_confirm_login">我確認登陸系統</string>  
  6.     <string name="btn_cancel">取消</string>  
  7.     <string name="user_name">用戶名</string>  
  8.     <string name="user_password">密 碼</string>  
  9.     <string name="login">登陸</string>  
  10.     <string name="relogin">從新登陸</string>  
  11.     <string name="start_scan">開始掃描</string>  
  12.     <string name="login_failed">登陸失敗</string>  
  13.     <string name="login_hint">隨便輸入用戶名,登陸以後,服務器會返回一個表明你身份的token。</string>  
  14.     <string name="token_info">您已經成功登陸,token:\n %1$s</string>  
  15.     <string name="pc_login_succuess">網頁登陸成功</string>  
  16.     <string name="pc_login_fail">網頁登陸失敗,可能您掃描的頁面已過時!</string>  
  17. </resources>  

 

原創文章,轉載請註明出處

 

【爲了方便測試,我把客戶端打包上傳,能夠點這裏下載 http://download.csdn.net/detail/otangba/4857059 】

測試程序在運行時能夠設置服務器地址,服務器咱們在下一篇會介紹。

 

 

 

實現手機掃描二維碼頁面登陸,相似web微信-第四篇,服務器端
http://www.cnblogs.com/fengyun99/p/3541256.html

實現手機掃描二維碼頁面登陸,相似web微信-第三篇,手機客戶端
http://www.cnblogs.com/fengyun99/p/3541254.html

實現手機掃描二維碼頁面登陸,相似web微信-第二篇,關於二維碼的自動生成
http://www.cnblogs.com/fengyun99/p/3541251.html

實現手機掃描二維碼頁面登陸,相似web微信-第一篇,業務分析
http://www.cnblogs.com/fengyun99/p/3541249.html

相關文章
相關標籤/搜索