如何搭建一個WEB服務器項目(六)—— 上傳圖片至服務器


上傳圖片(用戶頭像)至服務器

  觀前提示:本系列文章有關服務器以及后端程序這些概念,我寫的全是自己的理解,並不一定正確,希望不要誤人子弟。歡迎各位大佬來評論區提出問題或者是指出錯誤,分享寶貴經驗。先謝謝了( ̄▽ ̄)"!

  前兩期介紹了如何從服務器獲取數據和加載圖片,現在我們來看看如何把圖片上傳到服務器。這是一個很常見的需求,比如說上傳用戶頭像等,本期也將圍繞這個內容展開。首先服務器這邊我們寫一個上傳圖片的controller:

 1 package dolphin.controller;
 2 
 3 import org.springframework.stereotype.Controller;
 4 import org.springframework.web.bind.annotation.RequestMapping;
 5 import org.springframework.web.bind.annotation.ResponseBody;
 6 import org.springframework.web.multipart.MultipartFile;
 7 
 8 import javax.servlet.http.HttpServletRequest;
 9 import java.io.File;
10 import java.io.IOException;
11 
12 /**
13  * @description :數據更新控制層
14  * @author :郭小柒w
15  * @date :2020/5/16 16:03
16  * @version :1.0
17  */
18 @Controller
19 public class UpdateController {
20     /**
21      *
22      * @param file
23      * @param request
24      * @return String 不同的返回值代表不同上傳結果
25      * @throws IllegalStateException
26      * @throws IOException
27      */
28     @RequestMapping( "/Upload")
29     @ResponseBody
30     public String photoUpload(MultipartFile file, HttpServletRequest request) throws IllegalStateException, IOException {
31         if (file != null) {// 判斷上傳的文件是否為空
32             String path = null;// 文件路徑
33             String type = null;// 文件類型
34             String fileName = file.getOriginalFilename();// 文件原名稱
35             System.out.println("上傳的文件原名稱:"+fileName);
36             // 判斷文件類型
37             type = fileName.indexOf(".") != -1 ? fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()) : null;
38             if (type != null) {// 判斷文件類型是否為空
39                 if ("GIF".equals(type.toUpperCase()) || "PNG".equals(type.toUpperCase()) || "JPG".equals(type.toUpperCase())) {
40                     // 項目在容器中實際發布運行的根路徑
41                     String realPath = request.getSession().getServletContext().getRealPath("/");
42                     // 自定義的文件名稱
43                     String trueFileName = fileName;
44                     // 設置存放圖片文件的路徑
45                     path = realPath + "WEB-INF\\images\\head\\" + trueFileName;
46                     // 轉存文件到指定的路徑
47                     file.transferTo(new File(path));
48                     System.out.println("文件成功上傳到指定目錄下");
49                 }else {
50                     System.out.println("不是我們想要的文件類型,請按要求重新上傳");
51                     return "1";
52                 }
53             }else {
54                 System.out.println("文件類型為空");
55                 return "2";
56             }
57         }else {
58             System.out.println("沒有找到相對應的文件");
59             return "3";
60         }
61         return "0";
62     }
63 }

  單單有這個還不夠,我們還需要進行如下配置:

1.在pom.xml里加入上傳文件相關的依賴(版本可根據需要進行更改):

 1 <!-- io包 -->
 2 <dependency>
 3     <groupId>org.apache.commons</groupId>
 4     <artifactId>commons-io</artifactId>
 5     <version>1.3.2</version>
 6 </dependency>
 7 <!-- 文件上傳組件 -->
 8 <dependency>
 9     <groupId>commons-fileupload</groupId>
10     <artifactId>commons-fileupload</artifactId>
11     <version>1.3.1</version>
12 </dependency>

2.在springmvc.xml里新增如下配置:

1 <!-- SpringMVC上傳文件時,需要配置MultipartResolver處理器 -->
2     <bean id="multipartResolver"
3           class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
4         <property name="defaultEncoding" value="UTF-8" />
5         <!-- 指定所上傳文件的總大小,單位字節。注意maxUploadSize屬性的限制不是針對單個文件,而是所有文件的容量之和 -->
6         <property name="maxUploadSize" value="10240000" />
7     </bean>

然后是用於測試controller的頁面,index.jsp(enctype="multipart/form-data"的作用是將form表單的數據以二進制的方式傳輸

 1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
 2 <html>
 3 <head>
 4     <title>Title</title>
 5 </head>
 6 <body>
 7 <fieldset>
 8     <legend>圖片上傳</legend>
 9     <h2>只能上傳單張10M以下的 PNG、JPG、GIF 格式的圖片</h2>
10     <form action="./Upload" method="post" enctype="multipart/form-data">
11         選擇文件:<input type="file" name="file">
12         <input type="submit" value="上傳">
13     </form>
14 </fieldset>
15 </body>
16 </html>

  我們先看一下效果,從本地選擇一張符合要求圖片:

  選擇完畢后是這樣:

  然后點擊上傳,頁面跳轉。上傳成功的話頁面只有一個“0”,圖片不再貼出,我們先看一下控制台輸出:

   從輸出的存放路徑找到我們上傳的圖片:

  從圖中可以看出已經上傳成功,證明我們的controller是可行的,接下來就是編寫安卓端的代碼,嘗試從客戶端上傳圖片。

  客戶端獲取圖片大致就兩種方式:1.拍照;2.本地圖庫。

  由於博主也不是什么技術大佬,所以老老實實地使用大神們造的輪子,主要是以下兩個庫:

  更詳細的介紹請到相應網址查看,我這只是簡單的使用,首先是頁面布局:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:id="@+id/activity_main"
 5     android:orientation="vertical"
 6     android:layout_width="match_parent"
 7     android:layout_height="match_parent"
 8     tools:context=".UserHeadActivity">
 9     <LinearLayout
10         android:layout_width="match_parent"
11         android:layout_height="30dp">
12     </LinearLayout>
13     <TextView
14         android:layout_width="wrap_content"
15         android:layout_height="wrap_content"
16         android:layout_gravity="center_horizontal"
17         android:text="UserHeadActivity" />
18     <com.example.dolphin.utils.RoundImageView
19         android:id="@+id/image_view"
20         android:layout_marginTop="10dp"
21         android:layout_gravity="center_horizontal"
22         android:layout_width="150dp"
23         android:layout_height="150dp"
24         android:src="@drawable/ic_launcher"
25         />
26     <Button
27         android:id="@+id/take_from_camera"
28         android:text="拍照"
29         android:layout_gravity="center_horizontal"
30         android:layout_width="wrap_content"
31         android:layout_height="wrap_content" />
32     <Button
33         android:id="@+id/take_from_galley"
34         android:text="圖庫"
35         android:layout_gravity="center_horizontal"
36         android:layout_width="wrap_content"
37         android:layout_height="wrap_content" />
38 </LinearLayout>

  可能你會有疑問,“com.example.dolphin.utils.RoundImageView”是個什么玩意兒?由於要做用戶頭像,我就順便在網上找了一個自定義圓形ImageView控件的例子,非常簡單,代碼如下:

 1 package com.example.dolphin.utils;
 2 
 3 import android.annotation.SuppressLint;
 4 import android.content.Context;
 5 import android.graphics.Bitmap;
 6 import android.graphics.BitmapShader;
 7 import android.graphics.Canvas;
 8 import android.graphics.Matrix;
 9 import android.graphics.Paint;
10 import android.graphics.Shader;
11 import android.graphics.drawable.BitmapDrawable;
12 import android.graphics.drawable.Drawable;
13 import android.util.AttributeSet;
14 import android.widget.ImageView;
15 
16 import androidx.annotation.Nullable;
17 
18 /**
19  * @author :created by 郭小柒w
20  * 時間 2020/5/16 15
21  * 自定義的圓形ImageView,可以直接當組件在布局中使用。
22  */
23 
24 @SuppressLint("AppCompatCustomView")
25 public class RoundImageView extends ImageView {
26 
27     //畫筆
28     private Paint mPaint;
29     //圓形圖片的半徑
30     private int mRadius;
31     //圖片的宿放比例
32     private float mScale;
33 
34     public RoundImageView(Context context) {
35         super(context);
36     }
37 
38     public RoundImageView(Context context, @Nullable AttributeSet attrs) {
39         super(context, attrs);
40     }
41 
42     public RoundImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
43         super(context, attrs, defStyleAttr);
44     }
45 
46     @Override
47     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
48         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
49         //由於是圓形,寬高應保持一致
50         int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
51         mRadius = size / 2;
52         setMeasuredDimension(size, size);
53     }
54 
55     @SuppressLint("DrawAllocation")
56     @Override
57     protected void onDraw(Canvas canvas) {
58 
59         mPaint = new Paint();
60 
61         Drawable drawable = getDrawable();
62 
63         if (null != drawable) {
64             Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
65 
66             //初始化BitmapShader,傳入bitmap對象
67             BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
68             //計算縮放比例
69             mScale = (mRadius * 2.0f) / Math.min(bitmap.getHeight(), bitmap.getWidth());
70 
71             Matrix matrix = new Matrix();
72             matrix.setScale(mScale, mScale);
73             bitmapShader.setLocalMatrix(matrix);
74             mPaint.setShader(bitmapShader);
75             //畫圓形,指定好坐標,半徑,畫筆
76             canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
77         } else {
78             super.onDraw(canvas);
79         }
80     }
81 
82 }
RoundImageView.java

  在你的項目里創建這個類,之后就可以像上邊那樣使用啦(使用時注意類的路徑,不要直接復制粘貼上面的代碼),最后是頁面對應的Activity:

  1 package com.example.dolphin;
  2 
  3 import android.Manifest;
  4 import android.content.Intent;
  5 import android.net.Uri;
  6 import android.os.Environment;
  7 import android.os.Bundle;
  8 import android.util.Log;
  9 import android.view.View;
 10 import android.widget.Button;
 11 import android.widget.ImageView;
 12 import android.widget.Toast;
 13 
 14 import com.bumptech.glide.Glide;
 15 import com.example.dolphin.utils.Constants;
 16 import com.jph.takephoto.app.TakePhoto;
 17 import com.jph.takephoto.app.TakePhotoActivity;
 18 import com.jph.takephoto.compress.CompressConfig;
 19 import com.jph.takephoto.model.CropOptions;
 20 import com.jph.takephoto.model.TResult;
 21 import com.yanzhenjie.permission.AndPermission;
 22 import com.yanzhenjie.permission.PermissionListener;
 23 import com.zhy.http.okhttp.OkHttpUtils;
 24 import com.zhy.http.okhttp.callback.StringCallback;
 25 
 26 import java.io.File;
 27 import java.util.List;
 28 
 29 import okhttp3.Call;
 30 
 31 public class UserHeadActivity extends TakePhotoActivity {
 32 
 33     //UIs
 34     private Button takeFromCameraBtn, takeFromGalleyBtn;  //拍照以及從相冊中選取Button
 35     private ImageView imageView;  //圖片展示ImageView
 36 
 37     //TakePhoto
 38     private TakePhoto takePhoto;
 39     private CropOptions cropOptions;  //裁剪參數
 40     private CompressConfig compressConfig;  //壓縮參數
 41     private Uri imageUri;  //圖片保存路徑
 42 
 43     @Override
 44     protected void onCreate(Bundle savedInstanceState) {
 45         super.onCreate(savedInstanceState);
 46         setContentView(R.layout.activity_userhead);
 47         //申請相關權限
 48         initPermission();
 49         //設置壓縮、裁剪參數
 50         initData();
 51         takeFromCameraBtn = (Button) findViewById(R.id.take_from_camera);
 52         takeFromCameraBtn.setOnClickListener(new View.OnClickListener() {
 53             @Override
 54             public void onClick(View view) {
 55                 imageUri = getImageCropUri();
 56                 //拍照並裁剪
 57                 takePhoto.onPickFromCaptureWithCrop(imageUri, cropOptions);
 58                 //僅僅拍照不裁剪
 59                 //takePhoto.onPickFromCapture(imageUri);
 60             }
 61         });
 62 
 63         takeFromGalleyBtn = (Button) findViewById(R.id.take_from_galley);
 64         takeFromGalleyBtn.setOnClickListener(new View.OnClickListener() {
 65             @Override
 66             public void onClick(View view) {
 67                 imageUri = getImageCropUri();
 68                 //從相冊中選取圖片並裁剪
 69                 takePhoto.onPickFromGalleryWithCrop(imageUri, cropOptions);
 70                 //從相冊中選取不裁剪
 71                 //takePhoto.onPickFromGallery();
 72             }
 73         });
 74 
 75         imageView = (ImageView) findViewById(R.id.image_view);
 76     }
 77 
 78     @Override
 79     public void takeSuccess(TResult result) {
 80         super.takeSuccess(result);
 81         String iconPath = result.getImage().getOriginalPath();
 82         //Toast顯示圖片路徑
 83         Toast.makeText(this, "imagePath:" + iconPath, Toast.LENGTH_SHORT).show();
 84         //上傳圖片
 85         submitHead(iconPath);
 86         //Google Glide庫 用於加載圖片資源,這里是把圖片展示在頁面上
 87         Glide.with(this).load(iconPath).asBitmap().into(imageView);
 88     }
 89 
 90     @Override
 91     public void takeFail(TResult result, String msg) {
 92         super.takeFail(result, msg);
 93         Toast.makeText(UserHeadActivity.this, "Error:" + msg, Toast.LENGTH_SHORT).show();
 94     }
 95 
 96     @Override
 97     public void takeCancel() {
 98         super.takeCancel();
 99     }
100 
101     private void initPermission() {
102         // 申請權限。
103         AndPermission.with(this)
104                 .requestCode(100)
105                 .permission(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE)
106                 .send();
107     }
108 
109     @Override
110     public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
111         // 只需要調用這一句,其它的交給AndPermission吧,最后一個參數是PermissionListener。
112         AndPermission.onRequestPermissionsResult(requestCode, permissions, grantResults, listener);
113     }
114 
115     //權限申請回調接口
116     private PermissionListener listener = new PermissionListener() {
117         @Override
118         public void onSucceed(int requestCode, List<String> grantedPermissions) {
119             // 權限申請成功回調。
120             if(requestCode == 100) {
121                 // TODO 相應代碼。
122                 //do nothing
123             }
124         }
125         @Override
126         public void onFailed(int requestCode, List<String> deniedPermissions) {
127             // 權限申請失敗回調。
128 
129             // 用戶否勾選了不再提示並且拒絕了權限,那么提示用戶到設置中授權。
130             if (AndPermission.hasAlwaysDeniedPermission(UserHeadActivity.this, deniedPermissions)) {
131 
132                 // 用自定義的提示語
133                 AndPermission.defaultSettingDialog(UserHeadActivity.this, 103)
134                         .setTitle("權限申請失敗")
135                         .setMessage("我們需要的一些權限被您拒絕或者系統發生錯誤申請失敗,請您到設置頁面手動授權,否則功能無法正常使用!")
136                         .setPositiveButton("好,去設置")
137                         .show();
138             }
139         }
140     };
141 
142     private void initData() {
143         ////獲取TakePhoto實例
144         takePhoto = getTakePhoto();
145         //設置裁剪參數
146         cropOptions = new CropOptions.Builder().setAspectX(1).setAspectY(1).setWithOwnCrop(false).create();
147         //設置壓縮參數
148         compressConfig=new CompressConfig.Builder().setMaxSize(50*1024).setMaxPixel(800).create();
149         takePhoto.onEnableCompress(compressConfig,true);  //設置為需要壓縮
150     }
151 
152     //獲得照片的輸出保存Uri
153     private Uri getImageCropUri() {
154         File file=new File(Environment.getExternalStorageDirectory(), "/temp/"+System.currentTimeMillis()+".jpg");
155         if (!file.getParentFile().exists())
156             file.getParentFile().mkdirs();
157         return Uri.fromFile(file);
158     }
159 
160     private void submitHead(String iconPath){
161         //獲取對應圖片
162         File file = new File(iconPath);
163         //設置網絡請求路徑
164         String url = Constants.BASE_URL+"/Upload";
165         OkHttpUtils.post().url(url)
166                 .addFile("file",file.getName(),file)
167                 .build()
168                 .execute(new StringCallback() {
169                     @Override
170                     public void onError(Call call, Exception e, int id) {
171                         Toast.makeText(UserHeadActivity.this,"網絡異常,請稍后再試",Toast.LENGTH_SHORT).show();
172                         System.out.println("頁面請求失敗=="+e.getMessage());
173                     }
174 
175                     @Override
176                     public void onResponse(String response, int id) {
177                         System.out.println("首頁請求成功=="+response);
178                         ResultOfUpload(response);
179                     }
180                 });
181     }
182     //對返回結果進行處理
183     private void ResultOfUpload(String code){
184         if(code.equals("0"))
185             Toast.makeText(this,"上傳成功",Toast.LENGTH_SHORT).show();
186         else if(code.equals("1"))
187             Toast.makeText(this,"文件格式不符,請重新上傳",Toast.LENGTH_SHORT).show();
188         else if(code.equals("2"))
189             Toast.makeText(this,"文件類型為空",Toast.LENGTH_SHORT).show();
190         else
191             Toast.makeText(this,"未找到對應文件",Toast.LENGTH_SHORT).show();
192     }
193 }

   所有工作都已完成,接下來看看實際效果如何。下面是調試時的錄屏,這里只給出從圖庫獲取並上傳的示例,拍照的模塊大家可以自行測試,我測試時沒問題(錄屏轉gif還挺麻煩,畫質有點糊,各位湊合着看吧🙃)。

 IDEA控制台輸出信息如下:

可以看到圖片已經成功上傳:

最后非常感謝下面這幾篇博客,有了他們我才能東拼西湊,做出這期想做的東西:

—————————————我———是———分———割———線————————————

 拖更快樂!(bushi)

  托更也是無奈嘛╮(╯-╰)╭,誰讓上周五我又跟着同學們happy去了😜。畢竟是開學前的狂歡呀,雖然我的開學遙遙無期,甚至有的同學已經得到不開學的通知了(酸了🍋),不過還是有點盼望開學的(再不開代碼都敲不利索了!)最近的進度也是停滯不前,沒有干勁,真怕老師突然宣布要交出點成果了😱。仔細想想,可能還是因為目標不夠明確吧,都這個時候了還是不清楚往哪個方向用功,我已經能看到我慘淡的人生了😭。但是不管怎么樣,生活還是要繼續下去,總不能一直這么頹廢,這不一有時間就更新了么,嘿嘿(快誇我( ̄▽ ̄))。最近這個系列應該都不更了,因為也沒太多能分享的了,所以斷更一段時間,期間可能會更新計算機網絡或者算法相關的吧,有興趣的可以關注一下我的每周動態。那么我們有緣再見👋


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM