有時候,想要把電腦上的文件快速便捷的傳輸到手機,就可以用到這個工具了。
使用方法示例:
1、準備需要傳輸到手機的文件
注:1.2版本不僅支持對文件的一鍵分享,還支持文件夾的一鍵分享。
2、選中文件或文件夾,右鍵屬性,點擊QrShare
3、彈出二維窗口,用手機掃描二維碼下載文件
提示:此時鼠標的粘貼板已經粘貼了二維碼的內容,直接發給小夥伴,分析文件吧。
4、手機下載文件結果
下載時,服務端可以強制斷開分享,也可以等待客戶端下載完成後自動關閉分享;同時,客戶端同時在線下載數量不能超過10個。
當分享的文件類型是圖片格式(png,jpg,gif,bmp,ico等)和網頁格式(html,htm)時,瀏覽器會對其數據進行頁面顯示。即:直接顯示圖片而不是彈出下載圖片(可通過另存爲下載圖片);直接顯示html內容而不是提示下載文件。其他格式文件,直接提示下載文件。
直接顯示出圖片。
注意:需要安裝jdk1.6+,手機和電腦必須在同一局域網內,除非電腦部署在外網環境。
程序分享:
先說說使用的技術:
- 使用zxing生成二維碼
- jdk swing的基礎知識
- jdk httpServer
- jdk zip壓縮文件
- 如何將可jar轉換成可執行的exe文件
- 修改windos註冊表,實現右鍵菜單
代碼如下:
QrShare.java
package com.kancy.httpserver;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
/**
* 使用二維碼實現文件分享
* @author kancy
* @see 2018/07/30
*/
public class QrShare {
public static void main(String[] arg) {
try {
// 1.獲取文件路徑,獲取失敗時,系統自動關閉
File file = acquireAndCheckDownloadFile(arg);
// 2.啓動服務
startJavaHttpFileServer(file);
// 3.顯示二維碼
String title = " 掃碼下載文件("+ file.getName() +")";
String content = "http://" + InetAddress.getLocalHost().getHostAddress().toString() + ":" + HTTP_PORT
+ "/download"+ getFileSuffix(file) + "?id=" + System.currentTimeMillis();
setSysClipboardText(content);
createQrImageFrame(title, content);
waitDelay(Setting.getProperty("wait_delay_time", Setting.wait_delay_time));
// 4.提示是否強制關閉服務
int unAccomplishedSize = accomplishMap.size();
if(unAccomplishedSize > 0){
int result = JOptionPane.showConfirmDialog(null, "目前存在"+ unAccomplishedSize+"個客戶端正在下載,是否強制關閉服務?","系統關閉提示",2);
if(result == 0){
closeService();
System.exit(0);
}
}
// 5.系統退出
waitAndExitSystem(0);
} catch (Exception e) {
alertAndExit("系統異常:" + e.getMessage());
}
}
/**
* 檢查和獲取文件
* @param arg
* @return
*/
private static File acquireAndCheckDownloadFile(String[] arg) {
if (arg == null || arg.length <= 0) {
alertAndExit("請指定下載的文件全路徑!");
}
// 獲取本地文件
String filePath = arg[0];
File file = new File(filePath);
if(!file.exists()){
alertAndExit("文件不存在!");
}
// 如果是目錄,先進行zip打包
if(file.isDirectory()){
filePath = packZipFile(file);
file = new File(filePath);
}
if (!file.isFile()) {
alertAndExit("文件不存在!");
}
return file;
}
/**
* 壓縮目錄
* @param dir
* @return
*/
private static String packZipFile(File dir) {
String zipFilePath = dir.getParent().replace("\\", "/") + "/" + dir.getName() + ".zip";
try {
ZipUtils.zip(zipFilePath, dir);
} catch (Exception e) {
e.printStackTrace();
alertAndExit("壓縮目錄異常:\n"+e.getMessage());
}
return zipFilePath;
}
/**
* 使用隨機端口
* @return
*/
private static int getRandomPort() {
Random random = new Random();
String value = "";
while(true){
int num = random.nextInt(10);
// 確保端口小於65535
if(value.length() == 0 && (num == 0 || num > 5)){
continue;
}
value += num;
if(value.length() == 5){
break;
}
}
return Integer.parseInt(value);
}
/**
* 提示並且退出
* @param msg
*/
private static void alertAndExit(String msg) {
JOptionPane.showMessageDialog(null, msg);
System.exit(0);
}
/**
* 關閉服務
*/
private static void closeService() {
if(!threadPool.isShutdown()){
threadPool.shutdown();
}
server.stop(0);
}
/**
* 關閉系統
* @param i
* @throws InterruptedException
*/
private static void waitAndExitSystem(int i) {
int unAccomplishedSize = accomplishMap.size();
if(unAccomplishedSize == 0){
closeService();
System.out.println("系統即將關閉!");
System.exit(0);
}else{
System.out.println("存在"+unAccomplishedSize+"個活動鏈接,請等待...");
waitDelay(1000);
waitAndExitSystem(i);
}
}
/**
* 睡眠
* @param l
*/
private static void waitDelay(long l) {
try {
Thread.sleep(l);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 將字符串複製到剪切板。
*/
private static void setSysClipboardText(String writeMe) {
Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable tText = new StringSelection(writeMe);
clip.setContents(tText, null);
}
/**
* 依賴 com.google.zxing core-3.0.0.jar
*
* @param title
* @param content
* @throws WriterException
*/
private static void createQrImageFrame(String title, String content) throws WriterException {
Map<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.CHARACTER_SET, Setting.default_charset);
BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, 300, 300, hints);
int width = matrix.getWidth();
int height = matrix.getHeight();
BufferedImage image = new BufferedImage(width, height, 1);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, matrix.get(x, y) ? -16777216 : -1);
}
}
ImageIcon icon = new ImageIcon(image);
JOptionPane.showMessageDialog(null, "", title, 1, icon);
}
//-----------------------------------------------------------------------------
// jdk HttpServer 提供http服務
//-----------------------------------------------------------------------------
private static final int MAX_POOL_NUM = Setting.http_pool_size > 0 ? Setting.http_pool_size : 10;
private static final int HTTP_PORT = Setting.http_port > 0 ? Setting.http_port : getRandomPort();
private static HttpServer server;
private static ExecutorService threadPool = Executors.newFixedThreadPool(MAX_POOL_NUM);// 只允許10個用戶同時下載
private static Map<String, Boolean> accomplishMap = new Hashtable<String, Boolean>();// 防止未下載完成,關閉服務
/**
* 開啓http服務
* @param file
* @throws Exception
*/
private static void startJavaHttpFileServer(File file) throws Exception {
server = HttpServer.create(new InetSocketAddress(HTTP_PORT), 0);
server.setExecutor(threadPool);
server.createContext("/download"+ getFileSuffix(file), new FileServerHandler(file));
Thread httpServiceThread = new Thread(new Runnable() {
@Override
public void run() {
server.start();
System.out.println("服務啓動成功!");
}
});
httpServiceThread.start();
}
/**
* 獲取文件後綴
* @param file
* @return
*/
private static String getFileSuffix(File file) {
String name = file.getName();
String fileSuffix = "";
if(name.contains(".")){
fileSuffix = name.substring(name.lastIndexOf("."), name.length());
}
return fileSuffix.trim().toLowerCase();
}
/**
* 文件下載靜態處理器
* @author kancy
*/
static class FileServerHandler implements HttpHandler {
private File file;
public FileServerHandler(File file) {
this.file = file;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
if(accomplishMap.size() >= MAX_POOL_NUM){
String msg = "服務隊列已滿,請稍候下載!";
exchange.sendResponseHeaders(200, 0);
exchange.getResponseBody().write(msg.getBytes(Setting.default_charset));
exchange.getResponseBody().flush();
exchange.getResponseBody().close();
return;
}
String handlerId = UUID.randomUUID().toString();
try {
// 開始處理
accomplishMap.put(handlerId, false);
OutputStream os = exchange.getResponseBody();
FileInputStream in = new FileInputStream(file);
// 判斷文件的類型
// 1.圖片類型/網頁類型 直接顯示在瀏覽器,通過另存爲下載
// 3.其他類型 下載模式
String fileSuffix = getFileSuffix(file);
String contentType = Constants.getContentType(fileSuffix);
// 設置下載頭
if(Constants.CONTENT_TYPE_DEFAULT.equals(contentType)
|| (!Setting.show_static_resource && !Constants.CONTENT_TYPE_DEFAULT.equals(contentType) )){
exchange.getResponseHeaders().add("Content-Disposition", "attachment;filename=\""
+ new String(file.getName().getBytes(Setting.default_charset), Setting.content_disposition_charset) + "\"");
}
exchange.getResponseHeaders().add("Content-Type", contentType);
exchange.getResponseHeaders().add("Content-Length", String.valueOf(file.length()));
// 發送響應
exchange.sendResponseHeaders(200, file.length());
// 寫文件
writeAndClose(os, in);
} finally {
// 處理完成
accomplishMap.remove(handlerId);
}
}
/**
* 寫文件並且關閉流
* @param os
* @param in
* @throws IOException
*/
private static void writeAndClose(OutputStream os, FileInputStream in) throws IOException {
try {
byte[] buf = new byte[1024];
int size = -1;
while ((size = in.read(buf)) != -1) {
os.write(buf, 0, size);
os.flush();
}
} finally{
if(os != null){
os.close();
}
if(in != null){
in.close();
}
}
}
}
}
ZipUtils.java
package com.kancy.httpserver;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* ZIP 工具
*
* @author kancy
* @see 2018/07/30
*/
public class ZipUtils {
public static void unzip(String srcZip, String outPath) {
long startTime = System.currentTimeMillis();
try {
ZipInputStream Zin = new ZipInputStream(new FileInputStream(srcZip));// 輸入源zip路徑
BufferedInputStream Bin = new BufferedInputStream(Zin);
File Fout = null;
ZipEntry entry;
try {
while ((entry = Zin.getNextEntry()) != null && !entry.isDirectory()) {
Fout = new File(outPath, entry.getName());
if (!Fout.exists()) {
(new File(Fout.getParent())).mkdirs();
}
FileOutputStream out = new FileOutputStream(Fout);
BufferedOutputStream Bout = new BufferedOutputStream(out);
int b;
while ((b = Bin.read()) != -1) {
Bout.write(b);
}
Bout.close();
out.close();
System.out.println(Fout + "解壓成功");
}
Bin.close();
Zin.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("耗費時間: " + (endTime - startTime) + " ms");
}
public static void zip(String zipFileName, File inputFile) throws Exception {
System.out.println("壓縮中...");
File file = new File(zipFileName);
if (!file.exists())
file.createNewFile();
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file));
BufferedOutputStream bo = new BufferedOutputStream(out);
zip(out, inputFile, inputFile.getName(), bo);
bo.close();
out.close(); // 輸出流關閉
System.out.println("壓縮完成");
}
public static void zip(String zipFileName, String... filePaths) throws Exception {
System.out.println("壓縮中...");
File file = new File(zipFileName);
if (!file.exists())
file.createNewFile();
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(file));
BufferedOutputStream bo = new BufferedOutputStream(out);
for (int i = 0; i < filePaths.length; i++) {
File inputFile = new File(filePaths[i]);
zip(out, inputFile, inputFile.getName(), bo);
}
bo.close();
out.close();
System.out.println("壓縮完成");
}
private static void zip(ZipOutputStream out, File f, String base, BufferedOutputStream bo) throws Exception { // 方法重載
if (f.isDirectory()) {
File[] fl = f.listFiles();
if (fl.length == 0) {
out.putNextEntry(new ZipEntry(base + "/")); // 創建zip壓縮進入點base
System.out.println(base + "/");
}
for (int i = 0; i < fl.length; i++) {
zip(out, fl[i], base + "/" + fl[i].getName(), bo); // 遞歸遍歷子文件夾
}
} else {
out.putNextEntry(new ZipEntry(base)); // 創建zip壓縮進入點base
System.out.println(base);
FileInputStream in = new FileInputStream(f);
BufferedInputStream bi = new BufferedInputStream(in);
int b;
while ((b = bi.read()) != -1) {
bo.write(b); // 將字節流寫入當前zip目錄
}
bi.close();
in.close(); // 輸入流關閉
}
}
}
Setting.java
package com.kancy.httpserver;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
/**
* 系統設置
* @author kancy
*
*/
public class Setting {
/**
* 是否顯示圖片和網頁資源
*/
public static boolean show_static_resource = true;
/**
* 採用固定的端口設置
*/
public static int http_port = 0;
/**
* 線程池大小設置,同時下載最大數量控制
*/
public static int http_pool_size = 0;
/**
* 延遲自動關閉系統,時間單位毫秒
*/
public static long wait_delay_time = 1L;
/**
* 默認編碼
*/
public static String default_charset = Constants.CHARACTER_UTF8;
/**
* content_disposition編碼
*/
public static String content_disposition_charset = Constants.CHARACTER_ISO8859_1;
//----------------------------------------------------------------------------
// setting end
//-----------------------------------------------------------------------------
private static String configpath = System.getProperty("user.home").replace("\\", "/").concat("/").concat("QrShare/config.ini");
private volatile static Properties setting = new Properties();
public static String getProperty(String key) {
return setting.getProperty(key);
}
public static String getProperty(String key, String defaultValue) {
return setting.getProperty(key, defaultValue);
}
public static int getProperty(String key, int defaultValue) {
return Integer.parseInt(setting.getProperty(key, String.valueOf(defaultValue)));
}
public static long getProperty(String key, long defaultValue) {
return Long.parseLong(setting.getProperty(key, String.valueOf(defaultValue)));
}
public static boolean getProperty(String key, boolean defaultValue) {
return "true".equalsIgnoreCase(setting.getProperty(key, String.valueOf(defaultValue)));
}
public static void setProperty(String key, String value) {
setting.setProperty(key, value);
storeSetting();
}
private static void storeSetting() {
OutputStream fos = null;
try {
fos = new FileOutputStream(configpath);// 加載讀取文件流
setting.store(fos, "QrShare Config");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 初始化設置
*/
static{
init();
// 取值
show_static_resource = getProperty("show_static_resource", show_static_resource);
wait_delay_time = getProperty("wait_delay_time", wait_delay_time);
http_port = getProperty("http_port", http_port);
http_pool_size = getProperty("http_pool_size", http_pool_size);
default_charset = getProperty("default_charset", default_charset);
content_disposition_charset = getProperty("content_disposition_charset", content_disposition_charset);
}
private static void initDefaultConfig() {
setting.setProperty("default_charset", default_charset);
setting.setProperty("wait_delay_time", String.valueOf(wait_delay_time));
setting.setProperty("http_port", String.valueOf(http_port));
setting.setProperty("http_pool_size", String.valueOf(http_pool_size));
setting.setProperty("show_static_resource", String.valueOf(show_static_resource));
setting.setProperty("content_disposition_charset", content_disposition_charset);
storeSetting();
}
private static void init() {
FileInputStream fis = null;
try {
File file = new File(configpath);
if(!file.getParentFile().exists() && !file.getParentFile().isDirectory()){
file.getParentFile().mkdirs();
}
if (!(file.exists() && file.isFile())) {
file.createNewFile();
initDefaultConfig();
}
file.setReadable(true);
file.setWritable(true);
fis = new FileInputStream(configpath);
setting.load(fis);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Constants.java
package com.kancy.httpserver;
import java.util.Properties;
/**
* 常量
* @author kancy
*
*/
public class Constants {
public static final String CHARACTER_UTF8 = "UTF-8";
public static final String CHARACTER_GBK = "GBK";
public static final String CHARACTER_ISO8859_1 = "ISO8859-1";
//---------------------------------------------------------
// http://tool.oschina.net/commons
//---------------------------------------------------------
public static final String CONTENT_TYPE_IMAGE = "image/jpeg";
public static final String CONTENT_TYPE_GIF = "image/gif";
public static final String CONTENT_TYPE_PNG = "image/png";
public static final String CONTENT_TYPE_ICO= "image/x-icon";
public static final String CONTENT_TYPE_JPG = "image/jpeg";
public static final String CONTENT_TYPE_JPEG = "image/jpeg";
public static final String CONTENT_TYPE_BMP = "image/jpeg";
public static final String CONTENT_TYPE_HTML = "text/html";
public static final String CONTENT_TYPE_DEFAULT = "application/octet-stream";
private static Properties staticResourceContentTypes = new Properties();
static{
staticResourceContentTypes.setProperty(".html", CONTENT_TYPE_HTML);
staticResourceContentTypes.setProperty(".htm", CONTENT_TYPE_HTML);
staticResourceContentTypes.setProperty(".gif", CONTENT_TYPE_GIF);
staticResourceContentTypes.setProperty(".png", CONTENT_TYPE_PNG);
staticResourceContentTypes.setProperty(".ico", CONTENT_TYPE_ICO);
staticResourceContentTypes.setProperty(".jpeg", CONTENT_TYPE_JPG);
staticResourceContentTypes.setProperty(".jpg", CONTENT_TYPE_JPEG);
staticResourceContentTypes.setProperty(".bmp", CONTENT_TYPE_BMP);
}
public static String getContentType(String fileSuffix){
assert fileSuffix != null;
return staticResourceContentTypes.getProperty(fileSuffix.trim().toLowerCase(), CONTENT_TYPE_DEFAULT);
}
}
將jar轉換成exe文件:
這裏不做教程,有興趣可以去了解 exe4j :http://www.softpedia.com/get/Authoring-tools/Setup-creators/exe4j.shtml
添加右鍵菜單:
安裝:
reg add "HKEY_CLASSES_ROOT\*\shell\QrShare\command" /ve /d "\"%~dp0\QrShare.exe\" \"%%1\"" /f
reg add "HKEY_CLASSES_ROOT\Directory\shell\QrShare\command" /ve /d "\"%~dp0\QrShare.exe\" \"%%1\"" /f
卸載:
reg delete "HKEY_CLASSES_ROOT\*\shell\QrShare" /f
reg delete "HKEY_CLASSES_ROOT\Directory\shell\QrShare" /f
無法運行說明:
如果遇到以下錯誤,請更換32版本的QrShare.exe,直接覆蓋,名稱改爲QrShare.exe
免費下載鏈接:https://pan.baidu.com/s/1hdVmGj1rVrOkq7zdUUxDWg 密碼:2uxa
修改記錄:
version1.1
1.文件路徑帶有空格問題修復
下載地址:https://download.csdn.net/download/qq_25166683/10572340
version1.2
1.添加文件夾分享
下載地址:https://download.csdn.net/download/qq_25166683/10572845
version1.3
1.添加設置功能
2.圖片和網頁可以直接顯示,而不是下載,但可以通過另存爲下載
3.微信可以支持直接下載和圖片的查看
4.部分優化 (其中:軟件包體積減半,要的就是輕便小)
最新工具下載地址: https://download.csdn.net/download/qq_25166683/10574889(1.3版本)
IT視頻教程集合:http://blog.sina.com.cn/s/blog_189450fd80102xp2f.html
原文地址:https://blog.csdn.net/qq_25166683/article/details/81281177