上傳頭像

背景

在小米的面試中,最后一輪被問到了一個場景。即關于在 WebView 下開發一個用戶上傳頭像的場景的完整流程。但是當時回答的好多細節都沒有回答上來。

原因

  1. 沒有使用過 WebView;
  2. 文件上傳的功能做的不多;
  3. 文件上傳的前后端實現都是用的插件,導致實現原理不清楚;

目標

  • 理清使用 WebView 上傳頭像的業務邏輯;
  • 理清技術細節;
  • 實現 Demo;

分析

關于 WebView

什么是 WebView

Android WebView is a system for the Android operating system
(OS) that allows Android apps to display content from the web directly inside an application.
There are two ways to view web content on an Android device: though a traditional web browser or through an Android application that includes WebView in the layout. If a developer wants to add browser functionality to an application, she can include the WebView library and create an instance of a WebView class; this essentially embeds a browser within the app to do things like render web pages and execute JavaScript. WebView is powerful because it not only provides the app with an embedded browser, it also allows the developer's app to interact with web pages and other web apps.

簡而言之,對于 Android 來說,WebView 就是一個內置瀏覽器組件,開發者可以調用該組件來在 App 中顯示網頁。

業務邏輯

  1. 用戶打開個人資料頁面,先默認顯示灰色頭像,即頭像的 img 標簽的 src 屬性為 data:image/*;base64,**
  2. 前端向后端請求個人資料,后端鑒權通過后,將個人資料返回給前端;
  3. 如果個人資料中頭像屬性為空,則對頭像不做任何操作;如果頭像屬性不為空,則將網頁中頭像的 img 標簽的 src 屬性的值修改為響應的頭像屬性;
  4. 用戶點擊頭像,彈出對話框,包含拍照和選擇相冊兩個選項;
  5. 用戶點擊選擇相冊以后,彈出相冊選擇界面;
  6. 只允許用戶選擇圖片類型的文件;
  7. (可選)用戶選擇好以后,彈出裁剪界面;
  8. 操作完成后,壓縮圖片,并通過 form data 上傳到后端服務器,前端顯示操作中提示;
  9. 后端接收到表單以后,計算圖片的 hash 值,在數據庫中查詢是否已經有存在相同頭像,如果已經有相同頭像,則在數據庫中復制原有圖片地址;如果沒有,則把文件保存在被 nginx 反向代理的本地文件夾后,再將地址和 hash 值保存在數據庫;
  10. 后端保存成功后,給前端返回一個狀態值為 200 的響應,并將頭像的完整地址作為響應的屬性;
  11. 前端接收到響應以后,將網頁中頭像的 img 標簽的 src 屬性的值修改為響應的頭像屬性;

潛在問題

  • WebView 開發。沒有接觸過;
  • 文件命名規則。為了避免文件重名和惡意代碼注入,需要使用一種生成唯一值的規則,作為文件的命名規范,還不能太長。之前使用過時間戳作為規則;
  • 服務器頭像文件夾結構。為了避免降低文件查找的性能,需要建立不同層級的文件夾,規則可以按照年月日建立不同的文件夾保存文件;

開發步驟

既然 WebView 也是瀏覽器,那么應該按照最小化問題的原則,一步一步實現最終的目標。因此,決定按照以下步驟實現功能。

  1. 搭建后端服務器,然后使用 Postman 之類的工具測試;
  2. 電腦瀏覽器環境下,實現前端功能目標;
  3. 環境切換為真正的 WebView 環境,實現功能目標;

開發

后端搭建

關鍵點

  • [x] 后端支持提供靜態文件;
  • [x] 接收 form data 類型表單,并保存成文件;
  • [x] 按照規則重命名;
  • [ ] 計算 hash 編碼;

效果圖

Postman 效果圖

代碼

const path = require('path')

const express = require('express')
const formidable = require('formidable')
const cors = require('cors')

const app = express()

app.use(cors())

app.use('/static', express.static(path.join(__dirname, 'public')))

app.post('/api/v1/avatar', (req, res) => {
  if (req.url === '/api/v1/avatar' && req.method.toLowerCase() === 'post') {
    const form = new formidable.IncomingForm()
    form.uploadDir = './public/avatars'
    form.keepExtensions = true

    form.parse(req, (err, fields, files) => {
      if (err) {
        console.error(err)
        return res.status(500).json({
          message: '服務器發生錯誤!'
        })
      }
      const filename = path.basename(files['avatar']['path'])
      return res.json({
        success: true,
        path: '/static/avatars/' + filename
      })
    })
  }
})

app.listen(3000, () => console.log('Avatar back end service starts!'))

電腦瀏覽器環境

關鍵點

  • [x] 文件上傳 HTML5 API;
  • [x] 驗證文件類型和大小;
  • [x] 隱藏掉原生 <input type="file"> 元素,點擊頭像即可上傳文件;

效果圖

電腦瀏覽器效果圖

代碼

import { Component } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';

import { Uploader } from './uploader.service';

@Component({
  selector: 'app-root',
  template: `
    <div style="text-align:center">
      <div style="margin:50px 0">
        <label for="avatar"><img width="300" alt="avatar" class="img-thumbnail" [src]="avatarSrc"></label>
        <input type="file" id="avatar" name="avatar" accept="image/*"
        (change)="onChangeAvatar($event)" placeholder="更換頭像" style="visibility:hidden">
      </div>
    </div>
  `
})
export class AppComponent {

  private rootEndPoint: string = 'http://localhost:3000'

  public avatarSrc = this.sanitizer.bypassSecurityTrustUrl('data:image/svg+xml;base64,\
    PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgM\
    jUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMT\
    IzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzM\
    wMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiI\
    C8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wx\
    MS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==');

  constructor (
    private sanitizer: DomSanitizer,
    private uploader: Uploader
  ) {}

  private getFormValue (file: any): FormData {
    const formData = new FormData();
    formData.append('avatar', file);
    return formData;
  }

  public onChangeAvatar (event: any) {
    if (event.target.files.length === 0) {
      return;
    }
    const file = event.target.files[0];
    // 檢查文件格式和大小是否滿足要求
    if (file.size > 1024 * 1024) {
      window.alert('文件格式不規范!')
      return;
    }

    this.uploader
      .upload(this.getFormValue(file))
      .subscribe(
        path => this.avatarSrc = this.sanitizer.bypassSecurityTrustUrl(this.rootEndPoint + path),
        errorMessage => console.log(errorMessage)
      );
  }
}

IOS WebView 環境

開發過程

  1. 使用 Xcode 建立一個簡單工程;
  2. 將 WKWebView 控件拖入;
  3. 使用代碼加載 url;
  4. 修改配置,允許 http 協議傳輸;

然后運行以后,一切正常,點擊頭像也能夠調用相冊。但是,噩夢就來了,無法正常選擇圖片并上傳,找到了很多解決辦法,但是因為對 swift 語言不熟悉,暫時沒有辦法測試。

IOS - WebView

參考資料

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,761評論 25 708
  • 在Gooogle I/O 2013年的大會上面,展示的Volley庫,已經成為android開發中最常用的處理和緩...
    優才學院閱讀 8,165評論 2 9
  • 嗨!大家好!這是我第三次看到簡書這個App了,起初還沒有太在意,后面看到用的人越來越多,于是自己也下載一個,想知道...
    成為你自己onlyone閱讀 282評論 0 1
  • ①飲み物 ジュース(アップルジュース、オレンジジュース、ピーチジュース、カルピス、コーラ)、コーヒー、ココア、お茶...
    一直都是干物女啊WWW閱讀 609評論 4 1