配套視頻:https://www.bilibili.com/video/BV1oA411B7gv/
背景
今天鼓搗了一下手機投屏到筆記本,就想錄個視頻展示一下學習成果,正好就想起了很早之前實現(xiàn)的這個功能。
H5文件下載是一個很簡單的功能,但是把這個H5放在安卓版微信打開,功能就不能用了,因為安卓端的微信內(nèi)置瀏覽器攔截了所有下載文件的請求。
即使微信的sdk也沒有提供直接保存文件的接口,所以出路只有一條,就是跳到第三方應用進行下載,比如跳到手機瀏覽器、跳到微信小程序。如果是上架了應用寶的app,可以跳轉(zhuǎn)應用寶下載。
之所以屏蔽,應該是H5無法監(jiān)管的原因,但是不能理解的是,ios端的微信是可以下載的,難道蘋果手機高人一等?
解決方案收集
-
1.微信公眾號sdk(無法實現(xiàn))
- 可能以前有這個功能,但是現(xiàn)在確實是沒有了,找不到這種接口
- 微信公眾號sdk官方文檔:附錄2-所有 JS 接口列表
-
2.跳轉(zhuǎn)第三方應用
- 2.1.跳轉(zhuǎn)小程序(沒有實踐過,但是跳轉(zhuǎn)小程序,還不如跳轉(zhuǎn)手機瀏覽器呢)
- 參考:在微信瀏覽器打開 H5,居然無法一鍵下載圖片?
- 2.2.第三方應用生成的鏈接可以直接觸發(fā)跳轉(zhuǎn)外部瀏覽器選擇窗口(騙人的吧)
-
參考:成功解決微信跳轉(zhuǎn)到手機默認瀏覽器下載
-
這些網(wǎng)站都打不開了,不靠譜
-
- 2.3.如果是app,可以跳騰訊出品的應用寶下載
- 參考:H5在微信下載app
- 2.4.前端寫彈窗提示或是遮罩提示,引導用戶在右上角通過瀏覽器打開
- 參考:2022-12-06 uniApp H5端實現(xiàn)下載文件(包含微信瀏覽器內(nèi)處理)
- 參考:微信H5保存或下載視頻到本地,將視頻直接分享視頻給好友
- 參考:微信跳轉(zhuǎn)手機默認瀏覽器提示 微信h5頁面中下載第三方app的方法
- 2.1.跳轉(zhuǎn)小程序(沒有實踐過,但是跳轉(zhuǎn)小程序,還不如跳轉(zhuǎn)手機瀏覽器呢)
最終選擇的解決方案
-
想到這個方案,是一個意外。
-
一開始我只測試了zip的下載,確實不能下載,以至于我以偏概全地以為所有格式都不能下載,所以就轉(zhuǎn)到百度上找答案。
-
然后測試跟我說,ios的文件有些也不能預覽,不能下載。
-
所以我就丟下這個坑,先去解決ios的問題。
-
百度發(fā)現(xiàn)ios也是偽下載,它是先以預覽的方式打開文件,需要用戶點擊右上角手動保存。
-
而且文件后綴和響應頭
content-type
要嚴格對應,不對應就會報錯,預覽不了 -
參考:解決移動端H5下載文件提示文件類型無法識別或非法文件的問題
-
改完ios的問題,我傳了各種格式的文件測試了一遍,確認修復之后,又轉(zhuǎn)回安卓端。
-
隨手點擊了幾下,就是這么幾下讓我看到了希望。
-
并不是所有類型的文件都不能下載,針對docx、pdf、xlsx、txt等格式,微信會主動喚起跳轉(zhuǎn)其他瀏覽器的選擇彈窗。
-
這比起前端寫提示窗無疑要友好許多。
-
所以只要發(fā)揮偷蒙拐騙的優(yōu)良品質(zhì),讓微信對所有文件一視同仁,都喚起跳轉(zhuǎn)窗口就行了。
-
到此,安卓端H5下載文件的問題完美解決。
-
欺騙的手段也很簡單,反正微信也不能下載,就所有的下載請求,都給它一個假文件,比如123456.xlsx。
java實現(xiàn)
- 注意,如果接口使用cookie鑒權(quán),跳轉(zhuǎn)外部瀏覽器,cookie是帶不過去的。
- 需要提供一個不需要鑒權(quán)的接口,換一種方式鑒權(quán),比如時效分享碼或者直接攜帶sessionId之類的
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class ApiController {
// 獲取日志對象 Spring Boot 中內(nèi)置了日志框架 Slf4j
private static Logger log = LoggerFactory.getLogger(ApiController.class);
/**
* 處理微信文件下載
* 欺騙安卓微信喚起打開外部瀏覽器的選擇框
* ios微信可以預覽每種格式的文件,但是不支持直接下載,需要用戶在預覽頁點右上角手動保存
* 另外,ios對content_type要求嚴格,如果文件后綴和content_type對不上,連預覽頁都進不了
* 企業(yè)微信不用做任何處理
*/
@GetMapping("downloadFileWx")
public void downloadFileWx(@RequestParam String path, HttpServletRequest request, HttpServletResponse response) throws Exception {
responseOutputFileWx(path, null, request, response);
}
/**
* 響應文件流
* @param path 文件路徑
* @param outputFileName 文件名稱,賦值給響應頭Content-Disposition
* @param request
* @param response
*/
public void responseOutputFileWx(String path, String outputFileName,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
File file = new File(path);
if (file == null || !file.exists() || !file.isFile()) {
log.error("文件不存在");
// 重定向到當前頁面,相當于刷新頁面
String contextPath = request.getContextPath();
response.sendRedirect(contextPath + "/downFile");
return;
}
if (outputFileName == null || outputFileName.trim().length() == 0) {
// 假如下載文件名參數(shù)為空,則設(shè)置為原始文件名
outputFileName = file.getName();
}
ServletContext context = request.getServletContext();
// 文件絕對路徑
String absolutePath = file.getAbsolutePath();
// 獲取文件的MIME type
String mimeType = context.getMimeType(absolutePath);
if (mimeType == null) {
// 沒有發(fā)現(xiàn)則設(shè)為二進制流
mimeType = "application/octet-stream";
}
response.setContentType(mimeType);
// 設(shè)置文件下載響應頭
String headerKey = "Content-Disposition";
String headerValue = null;
if (isWx(request)) {
// 微信瀏覽器,打開手機默認瀏覽器下載文件
// 注意排除企業(yè)微信
try {
if (isAndroidWx(request)) {
// 安卓端,xlsx文件類型會觸發(fā)微信彈出跳轉(zhuǎn)外部瀏覽器窗口,欺騙一下
response.setContentType("application/octet-stream");
outputFileName = "123456.xlsx";
} else {
// ios 微信對contentType要求比較嚴格
// https://juejin.cn/post/6844904086463053837
if (absolutePath.endsWith("xlsx")) {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
} else if (absolutePath.endsWith("xls")) {
response.setContentType("application/vnd.ms-excel");
} else if (absolutePath.endsWith("doc")) {
response.setContentType("application/msword");
} else if (absolutePath.endsWith("docx")) {
response.setContentType("application/application/vnd.openxmlformats-officedocument.wordprocessingml.document");
}
}
headerValue = String.format("attachment; filename=\"%s\"", URLEncoder.encode(outputFileName, "UTF-8"));
} catch (Exception e) {
headerValue = String.format("attachment; filename=\"%s\"", outputFileName);
log.error(e.getMessage(), e);
}
} else {
try {
// 解決Firefox瀏覽器中文件名中文亂碼
// https://blog.csdn.net/Jon_Smoke/article/details/53699400
headerValue = String.format("attachment; filename* = UTF-8''%s",
URLEncoder.encode(outputFileName, "UTF-8")
);
} catch (Exception e) {
headerValue = String.format("attachment; filename=\"%s\"", outputFileName);
log.error(e.getMessage(), e);
}
}
response.setHeader(headerKey, headerValue);
String fileName = file.getName();
try (OutputStream outputStream = response.getOutputStream()) {
response.setCharacterEncoding("utf-8");
// 將下面2行放開,可以測試微信最原始反應
// 設(shè)置返回類型
// response.setContentType("multipart/form-data");
// // 文件名轉(zhuǎn)碼一下,不然會出現(xiàn)中文亂碼
// response.setHeader("Content-Disposition", "attachment;fileName=" + encodeStr(fileName));
byte[] bytes = readBytes(file);
if (bytes == null) {
log.error("文件不存在");
}
outputStream.write(bytes);
log.info("文件下載成功!" + fileName);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 對字符串(文件名或路徑)進行url編碼
*/
private String encodeStr(String str) throws Exception {
return URLEncoder.encode(str, "UTF-8");
}
/**
* 將文件轉(zhuǎn)為byte數(shù)組
*/
public byte[] readBytes(File file) throws Exception {
long len = file.length();
// 無論數(shù)組的類型如何,數(shù)組中的最大元素數(shù)為Integer.MAX_VALUE,大約20億
if (len >= 2147483647L) {
return null;
} else {
byte[] bytes = new byte[(int) len];
try (FileInputStream in = new FileInputStream(file)) {
int readLength = in.read(bytes);
if ((long) readLength < len) {
log.error("文件未讀取完全");
return null;
}
} catch (Exception var10) {
return null;
}
return bytes;
}
}
/**
* 是否從安卓端微信請求,需要排除企業(yè)微信
*/
private static boolean isAndroidWx(HttpServletRequest request) {
String userAgent = request.getHeader("user-agent");
return userAgent != null && userAgent.toLowerCase().indexOf("micromessenger") > -1
&& userAgent.toLowerCase().indexOf("wxwork") < 0
&& userAgent.toLowerCase().indexOf("android") > -1;
}
/**
* 是否從微信請求,需要排除企業(yè)微信
* 安卓或ios
*/
private static boolean isWx(HttpServletRequest request) {
String userAgent = request.getHeader("user-agent");
return userAgent != null && userAgent.toLowerCase().indexOf("micromessenger") > -1
&& userAgent.toLowerCase().indexOf("wxwork") < 0;
}
}
題外話:手機如何投屏筆記本
方式1:win10自帶投屏
-
按 “Windows 徽標鍵+I” 打開設(shè)置,設(shè)置–>系統(tǒng)–>投影到此電腦
-
投影到此電腦中顯示灰色不可選,或顯示“我們正在確認這項功能”
-
第一次需要安裝 無線顯示器
-
手機使用電腦自帶功能進行投屏
-
手機投屏到筆記本之后,筆記本會被劫持,就是只能操作手機畫面,鼠標移不出來,可以在電腦上用鼠標直接操作手機。
-
這一點,有點不方便,比如想一邊寫代碼,一邊預覽手機效果,就不能實現(xiàn)。
-
另外,建議選擇 僅第一次 需要驗證,我第一次投成功了,關(guān)閉之后,就死活投不上去,主要是筆記本不能彈出確認對話框文章來源:http://www.zghlxwxcb.cn/news/detail-403217.html
-
之后,重啟電腦才能第二次投屏成功。文章來源地址http://www.zghlxwxcb.cn/news/detail-403217.html
方式2:幕享 軟件
- 官網(wǎng)下載頁
- 官方使用教程:如何使用幕享Windows版
- 我是在這個分享視頻里面找到的這個軟件:需要手機投屏電腦?這五款軟件就夠了!
- 這是純投屏軟件,不能在筆記本上操作手機。對手機錄屏,然后傳輸?shù)焦P記本上,局域網(wǎng)下延遲不高。
到了這里,關(guān)于安卓端微信H5下載文件處理:讓微信自動彈起跳轉(zhuǎn)外部瀏覽器窗口的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!