上传图片(用户头像)至服务器
观前提示:本系列文章有关服务器以及后端程序这些概念,我写的全是自己的理解,并不一定正确,希望不要误人子弟。欢迎各位大佬来评论区提出问题或者是指出错误,分享宝贵经验。先谢谢了( ̄▽ ̄)"!
前两期介绍了如何从服务器获取数据和加载图片,现在我们来看看如何把图片上传到服务器。这是一个很常见的需求,比如说上传用户头像等,本期也将围绕这个内容展开。首先服务器这边我们写一个上传图片的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.本地图库。
由于博主也不是什么技术大佬,所以老老实实地使用大神们造的轮子,主要是以下两个库:
- TakePhoto:https://github.com/crazycodeboy/TakePhoto
- AndPermission:https://github.com/yanzhenjie/AndPermission
,我这只是简单的使用,首先是页面布局:
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控制台输出信息如下:
可以看到图片已经成功上传:
最后非常感谢下面这几篇博客,有了他们我才能东拼西凑,做出这期想做的东西:
- OkHttputils上传用户头像到服务器
- 《Android三方库--TakePhoto》
- SpringMvc MutipartFile图片文件上传
—————————————我———是———分———割———线————————————
拖更快乐!(bushi)
酸了🍋),不过还是有点盼望开学的(再不开代码都敲不利索了!)最近的进度也是停滞不前,没有干劲,真怕老师突然宣布要交出点成果了😱。仔细想想,可能还是因为目标不够明确吧,都这个时候了还是不清楚往哪个方向用功,我已经能看到我惨淡的人生了😭。但是不管怎么样,生活还是要继续下去,总不能一直这么颓废,这不一有时间就更新了么,嘿嘿(快夸我( ̄▽ ̄))。最近这个系列应该都不更了,因为也没太多能分享的了,所以断更一段时间,期间可能会更新计算机网络或者算法相关的吧,有兴趣的可以关注一下我的每周动态。那么我们有缘再见👋