<span>如何搭建一个WEB服务器项目(六)—— 上传图片至服务器</span>

上传图片(用户头像)至服务器

  观前提示:本系列文章有关服务器以及后端程序这些概念,我写的全是自己的理解,并不一定正确,希望不要误人子弟。欢迎各位大佬来评论区提出问题或者是指出错误,分享宝贵经验。先谢谢了( ̄▽ ̄)"!

  前两期介绍了如何从服务器获取数据和加载图片,现在我们来看看如何把图片上传到服务器。这是一个很常见的需求,比如说上传用户头像等,本期也将围绕这个内容展开。首先服务器这边我们写一个上传图片的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去了😜。毕竟是开学前的狂欢呀,虽然我的开学遥遥无期,甚至有的同学已经得到不开学的通知了(酸了🍋),不过还是有点盼望开学的(再不开代码都敲不利索了!)最近的进度也是停滞不前,没有干劲,真怕老师突然宣布要交出点成果了😱。仔细想想,可能还是因为目标不够明确吧,都这个时候了还是不清楚往哪个方向用功,我已经能看到我惨淡的人生了😭。但是不管怎么样,生活还是要继续下去,总不能一直这么颓废,这不一有时间就更新了么,嘿嘿(快夸我( ̄▽ ̄))。最近这个系列应该都不更了,因为也没太多能分享的了,所以断更一段时间,期间可能会更新计算机网络或者算法相关的吧,有兴趣的可以关注一下我的每周动态。那么我们有缘再见👋

全部评论

相关推荐

jack_miller:我给我们导员说我不在这里转正,可能没三方签了。导员说没事学校催的时候帮我想办法应付一下
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务