Springboot 頁(yè)面調(diào)取微信拍照或從手機(jī)相冊(cè)中選圖接口

官方具體文檔
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
之前使用的是input標(biāo)簽來(lái)打開(kāi)攝像頭,但是在微信里面有兼容性問(wèn)題,索性就用微信官方的

準(zhǔn)備工作

測(cè)試賬號(hào)

前去微信公眾平臺(tái)申請(qǐng)個(gè)測(cè)試賬號(hào),獲取appID和appsecret,具體配置請(qǐng)去我的另一篇文章
http://m.itdecent.cn/p/7e8b1c2b031d
appID、appsecret和后臺(tái)代碼配置完成之后,需要在微信測(cè)試賬號(hào)管理中修改JS接口安全域名,圖中紅框內(nèi)容替換成自己本地的內(nèi)網(wǎng)穿透的域名

20190410182204.png

正式工作

打開(kāi)攝像頭或相冊(cè)
  • 在當(dāng)前頁(yè)面引入微信
    在需要調(diào)用JS接口的頁(yè)面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.4.0.js
    如需進(jìn)一步提升服務(wù)穩(wěn)定性,當(dāng)上述資源不可訪問(wèn)時(shí),可改訪問(wèn):http://res2.wx.qq.com/open/js/jweixin-1.4.0.js (支持https)。
    備注:支持使用 AMD/CMD 標(biāo)準(zhǔn)模塊加載方法加載

  • 使用一個(gè)Button來(lái)設(shè)置點(diǎn)擊事件

    <button type="button" onclick="getConfig()" class="chooseImage">選擇</button>
    // 選擇圖片
    function getConfig() {
        var url = "/config"; //該地址為后臺(tái)鑒權(quán)配置地址
        //url(當(dāng)前網(wǎng)頁(yè)的URL,不包含#及其后面部分)
        var pathUrl = window.location.href.split('#')[0];
        mui.get(url, {url: pathUrl}, function (data) {
                //獲得服務(wù)器響應(yīng)
                //步驟三:通過(guò)config接口注入權(quán)限驗(yàn)證配置
                wx.config({
                    debug: true, // 開(kāi)啟調(diào)試模式,調(diào)用的所有api的返回值會(huì)在客戶端alert出來(lái),若要查看傳入的參數(shù),可以在pc端打開(kāi),參數(shù)信息會(huì)通過(guò)log打出,僅在pc端時(shí)才會(huì)打印。
                    appId: data.appId,   // 必填,公眾號(hào)的唯一標(biāo)識(shí)
                    timestamp: data.timestamp, // 必填,生成簽名的時(shí)間戳
                    nonceStr: data.nonceStr, // 必填,生成簽名的隨機(jī)串
                    signature: data.signature,// 必填,簽名,見(jiàn)附錄1
                    jsApiList: ["chooseImage", "previewImage", "uploadImage", "downloadImage"] 
                    // 必填,需要使用的JS接口列表,所有JS接口列表見(jiàn)附錄2
                });

                // 步驟四:通過(guò)ready接口處理成功驗(yàn)證
                wx.ready(function () {
                    // mui.alert("wx.config success.");
                    wx.checkJsApi({
                        jsApiList: [
                            'chooseImage',
                            'previewImage',
                            'uploadImage',
                            'downloadImage'
                        ],
                        success: function (res) {
                            if (res.checkResult.getLocation == false) {
                                alert('你的微信版本太低,不支持微信JS接口,請(qǐng)升級(jí)到最新的微信版本!');
                                return;
                            } else {
                                wxChooseImage();
                            }
                        }
                    });
                });

                wx.error(function (res) {
                    alert("wx.config failed.");
                });
            }, 'json'
        );
    };
    var images = {
        localId: [],
        serverId: []
    };

    //拍照或從手機(jī)相冊(cè)中選圖接口
    function wxChooseImage() {
        wx.chooseImage({
            count: 1, // 默認(rèn)9
            sourceType: ['album', 'camera'], // 可以指定來(lái)源是相冊(cè)還是相機(jī),默認(rèn)二者都有
            success: function (res) {
                images.localId = res.localIds;
                if (images.localId.length == 0) {
                    alert('請(qǐng)先使用 chooseImage 接口選擇圖片');
                    return;
                }
                var i = 0, length = images.localId.length;
                images.serverId = [];

                wx.getLocalImgData({
                    localId: images.localId[0],//圖片的本地ID
                    success: function (res) {
                        var localData = res.localData;
                        if (localData.indexOf('data:image') != 0) {
                            //判斷是否有這樣的頭部
                            localData = 'data:image/jpeg;base64,' + localData
                        }
                        base64Img = localData.replace(/\r|\n/g, '').replace('data:image/jgp', 'data:image/jpeg');
                        $("#face_img").attr('src', base64Img);  // 選擇的圖片在頁(yè)面的回調(diào)顯示
                    }
                })
            }
        });
    }

后臺(tái) /config 接口:
返回給前端 appId,timestamp,nonceStr,signature,前端拿著這個(gè)值去微信服務(wù)器請(qǐng)求驗(yàn)證,驗(yàn)證通過(guò)了,才能進(jìn)行后續(xù)工作

@Controller
public class WXConntroller {

    // 微信的access_token分為兩種,微信網(wǎng)頁(yè)授權(quán)是通過(guò)OAuth2.0機(jī)制實(shí)現(xiàn)的,在用戶授權(quán)給公眾號(hào)后,
    // 公眾號(hào)可以獲取到一個(gè)網(wǎng)頁(yè)授權(quán)特有的接口調(diào)用憑證(網(wǎng)頁(yè)授權(quán)access_token),通過(guò)網(wǎng)頁(yè)授權(quán)access_token可以進(jìn)行授權(quán)后接口調(diào)用,
    // 如獲取用戶基本信息。兩種的請(qǐng)求路徑不同,access_token有效期為2小時(shí)
    // 這里對(duì)請(qǐng)求回來(lái)的access_token做緩存,項(xiàng)目中寫(xiě)了個(gè)定時(shí)任務(wù),每?jī)尚r(shí)執(zhí)行一次
    // 進(jìn)行刷新access_token
    public void getAccessToken() throws IOException {
        if (!redisUtil.hasKey("access_token")){
            redisUtil.set("access_token",WxUtil.getAccessToken(appid,appsecret),7140000); //存入redis,有效期119分鐘
        }
    }

    @GetMapping("/config")
    @ResponseBody
    public Map<String, Object> config(@Param("url") String url) throws IOException, NoSuchAlgorithmException {
        Map<String, Object> subJsonMap = new HashMap<>();

        // 1.獲取參數(shù)
        getAccessToken();
        String accessToken = redisUtil.get("access_token").toString();
        String ticket = WxUtil.getTicket(accessToken);

        Long timestamp = System.currentTimeMillis() / 1000;
        String nonceStr = WxUtil.getNonceStr();
        String sign = WxUtil.getSign(ticket, nonceStr, timestamp, url);
        subJsonMap.put("result", "1");
        subJsonMap.put("ticket", ticket);
        subJsonMap.put("timestamp", timestamp);
        subJsonMap.put("nonceStr", nonceStr);
        subJsonMap.put("appId", appid);
        subJsonMap.put("signature", sign);
        return subJsonMap;  // 將此信息返回給前端,微信會(huì)根據(jù)該信息進(jìn)行驗(yàn)證sign簽名,匹配正確,則通過(guò),否則 不通過(guò)(達(dá)到上面步驟四)
        // 此時(shí)便可打開(kāi)微信的攝像頭以及相冊(cè)
    }
}

阿里云的fastjson,WxUtil工具類中使用到

<!-- 阿里 JSON -->
<dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>fastjson</artifactId>
       <version>1.2.47</version>
</dependency>
import com.alibaba.fastjson.JSONObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Formatter;

public class WxUtil {

    /**
     * 獲取當(dāng)前時(shí)間 yyyyMMddHHmmss
     */
    public static String getCurrTime() {
        Date now = new Date();
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String s = outFormat.format(now);
        return s;
    }

    /**
     * 排序方法
     *
     * @param token     Token
     * @param timestamp 時(shí)間戳
     * @param nonce     隨機(jī)數(shù)
     * @return
     */
    public static String sort(String token, String timestamp, String nonce) {
        String[] strArray = {token, timestamp, nonce};
        Arrays.sort(strArray);
        StringBuilder sb = new StringBuilder();
        for (String str : strArray) {
            sb.append(str);
        }

        return sb.toString();
    }

    /**
     * 將字符串進(jìn)行sha1加密
     *
     * @param str 需要加密的字符串
     * @return 加密后的內(nèi)容
     */
    public static String sha1(String str) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(str.getBytes());
            byte messageDigest[] = digest.digest();
            // 創(chuàng)建 16進(jìn)制字符串
            StringBuffer hexString = new StringBuffer();
            // 字節(jié)數(shù)組轉(zhuǎn)換為 十六進(jìn)制 數(shù)
            for (int i = 0; i < messageDigest.length; i++) {
                String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexString.append(0);
                }
                hexString.append(shaHex);
            }
            return hexString.toString();

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 生成隨機(jī)字符串
     */
    public static String getNonceStr() {
        String currTime = getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = buildRandom(4) + "";
        return strTime + strRandom;
    }

    /**
     * 獲取公眾號(hào)access_token
     *
     * @param appid
     * @param secret
     * @return
     * @throws IOException
     */
    public static String getAccessToken(String appid, String secret) throws IOException {
        String token;
        String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid
                + "&secret=" + secret;
        JSONObject result = HttpClientUtils.doGet(token_url);
        return result.get("access_token").toString();
    }

    /**
     * 獲取微信ticket
     *
     * @param token
     * @return
     * @throws IOException
     */
    public static String getTicket(String token) throws IOException {
        if ("".equalsIgnoreCase(token) || null == token) {
            return "";
        }
        String ticket_url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + token + "&type=jsapi";
        JSONObject result = HttpClientUtils.doGet(ticket_url);
        return result.get("ticket").toString();

    }


    /**
     * 取出一個(gè)指定長(zhǎng)度大小的隨機(jī)正整數(shù).
     *
     * @param length int 設(shè)定所取出隨機(jī)數(shù)的長(zhǎng)度。length小于11
     * @return int 返回生成的隨機(jī)數(shù)。
     */
    public static int buildRandom(int length) {
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }

    /**
     * 獲取簽名
     *
     * @param jsapi_ticket
     * @param nonce_str
     * @param timestamp
     * @param url
     * @return
     * @throws UnsupportedEncodingException
     * @throws NoSuchAlgorithmException
     */
    public static String getSign(String jsapi_ticket, String nonce_str, Long timestamp, String url) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        // 注意這里參數(shù)名必須全部小寫(xiě),且必須有序
        String string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str
                + "&timestamp=" + timestamp + "&url=" + url;
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(string1.getBytes("UTF-8"));
        String signature = byteToHex(crypt.digest());
        return signature;
    }

    public static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash) {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }
}
圖片上傳服務(wù)器
  • chooseImage:拍照或從手機(jī)相冊(cè)中選圖接口
  • previewImage:預(yù)覽圖片接口
  • uploadImage:上傳圖片接口(上傳到微信服務(wù)器,上傳圖片有效期3天,可用微信多媒體接口下載圖片到自己的服務(wù)器,此處獲得的 serverId 即 media_id。)
  • downloadImage:下載圖片接口(從微信服務(wù)器下載)
  • getLocalImgData:獲取本地圖片接口(可根據(jù)chooseImage返回的localIds,配合該接口直接獲取該圖片的base64編碼)
    注:getLocalImgData接口僅在 iOS WKWebview 下提供,用于兼容 iOS WKWebview 不支持 localId 直接顯示圖片的問(wèn)題。意思就是getLocalImgData獲取到圖片的base64編碼在ios端可以直接作為img標(biāo)簽的src屬性值,在安卓端卻不行使用如下代碼解決:
    if (localData.indexOf('data:image') != 0) {
            //判斷是否有這樣的頭部
           localData = 'data:image/jpeg;base64,' + localData
    }
    base64Img = localData.replace(/\r|\n/g, '').replace('data:image/jgp', 'data:image/jpeg');

最后頁(yè)面再上傳即可。

正式上線

將springboot 打包放到服務(wù)器后,需要在
https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=1889838119&lang=zh_CN
基本配置-公眾號(hào)開(kāi)發(fā)信息中,將服務(wù)器的IP配置到白名單中

20190411124701.png

最后需要在
https://mp.weixin.qq.com/cgi-bin/settingpage?t=setting/function&action=function&token=1889838119&lang=zh_CN
公眾號(hào)設(shè)置-功能設(shè)置 將JS接口安全域名填寫(xiě)自己的域名
20190411124901.png

前后臺(tái)分離的前端頁(yè)面使用微信jssdk

上面的配置是springboot+freemarker,前后臺(tái)不分離的,所以在本地調(diào)試的時(shí)候,直接將微信JS接口安全域名填寫(xiě)為springboot訪問(wèn)freemarker的地址(freemarker被springboot代理了),但是前后臺(tái)分離的html,在瀏覽器中未被代理前微信服務(wù)器是不能訪問(wèn)到的,所以我用的windows版的nginx代理了本地的html文件,nginx配置信息如下:

server {
        listen 80;
        server_name 127.0.0.1;
        location / {
            root   E:\Project;  // html文件的絕對(duì)地址
            index  index.html;  // 代理時(shí)訪問(wèn)的首頁(yè)
        }
}

將本地html頁(yè)面代理后,還需要配置微信JS接口安全域名,本地測(cè)試的時(shí)候填寫(xiě)本地的ip地址就行了,線上的話還需要將鑒權(quán)文本放到你html頁(yè)面存放的位置,然后JS接口安全域名填寫(xiě)線上的域名,后臺(tái)就用上面springboot后臺(tái)即可。

注意:
  • 不能使用hbuilder代理的html頁(yè)面后,再用內(nèi)網(wǎng)穿透將hbuilder代理的ip和端口號(hào)穿透到公網(wǎng),這樣是訪問(wèn)不了的,hbuilder代理的時(shí)候內(nèi)部做了限制
  • 微信jssdk 只能在微信環(huán)境下使用
轉(zhuǎn)載還請(qǐng)注明出處
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容