Springboot 页面调取微信拍照或从手机相册中选图接口

官方具体文档
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
之前使用的是input标签来打开摄像头,但是在微信里面有兼容性问题,索性就用微信官方的

准备工作

测试账号

前去微信公众平台申请个测试账号,获取appID和appsecret,具体配置请去我的另一篇文章
//www.greatytc.com/p/7e8b1c2b031d
appID、appsecret和后台代码配置完成之后,需要在微信测试账号管理中修改JS接口安全域名,图中红框内容替换成自己本地的内网穿透的域名

20190410182204.png

正式工作

打开摄像头或相册
    <button type="button" onclick="getConfig()" class="chooseImage">选择</button>
    // 选择图片
    function getConfig() {
        var url = "/config"; //该地址为后台鉴权配置地址
        //url(当前网页的URL,不包含#及其后面部分)
        var pathUrl = window.location.href.split('#')[0];
        mui.get(url, {url: pathUrl}, function (data) {
                //获得服务器响应
                //步骤三:通过config接口注入权限验证配置
                wx.config({
                    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                    appId: data.appId,   // 必填,公众号的唯一标识
                    timestamp: data.timestamp, // 必填,生成签名的时间戳
                    nonceStr: data.nonceStr, // 必填,生成签名的随机串
                    signature: data.signature,// 必填,签名,见附录1
                    jsApiList: ["chooseImage", "previewImage", "uploadImage", "downloadImage"] 
                    // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
                });

                // 步骤四:通过ready接口处理成功验证
                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接口,请升级到最新的微信版本!');
                                return;
                            } else {
                                wxChooseImage();
                            }
                        }
                    });
                });

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

    //拍照或从手机相册中选图接口
    function wxChooseImage() {
        wx.chooseImage({
            count: 1, // 默认9
            sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
            success: function (res) {
                images.localId = res.localIds;
                if (images.localId.length == 0) {
                    alert('请先使用 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);  // 选择的图片在页面的回调显示
                    }
                })
            }
        });
    }

后台 /config 接口:
返回给前端 appId,timestamp,nonceStr,signature,前端拿着这个值去微信服务器请求验证,验证通过了,才能进行后续工作

@Controller
public class WXConntroller {

    // 微信的access_token分为两种,微信网页授权是通过OAuth2.0机制实现的,在用户授权给公众号后,
    // 公众号可以获取到一个网页授权特有的接口调用凭证(网页授权access_token),通过网页授权access_token可以进行授权后接口调用,
    // 如获取用户基本信息。两种的请求路径不同,access_token有效期为2小时
    // 这里对请求回来的access_token做缓存,项目中写了个定时任务,每两小时执行一次
    // 进行刷新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.获取参数
        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;  // 将此信息返回给前端,微信会根据该信息进行验证sign签名,匹配正确,则通过,否则 不通过(达到上面步骤四)
        // 此时便可打开微信的摄像头以及相册
    }
}

阿里云的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 {

    /**
     * 获取当前时间 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 时间戳
     * @param nonce     随机数
     * @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();
    }

    /**
     * 将字符串进行sha1加密
     *
     * @param str 需要加密的字符串
     * @return 加密后的内容
     */
    public static String sha1(String str) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(str.getBytes());
            byte messageDigest[] = digest.digest();
            // 创建 16进制字符串
            StringBuffer hexString = new StringBuffer();
            // 字节数组转换为 十六进制 数
            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 "";
    }

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

    /**
     * 获取公众号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();

    }


    /**
     * 取出一个指定长度大小的随机正整数.
     *
     * @param length int 设定所取出随机数的长度。length小于11
     * @return int 返回生成的随机数。
     */
    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 {
        // 注意这里参数名必须全部小写,且必须有序
        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;
    }
}
图片上传服务器
  • chooseImage:拍照或从手机相册中选图接口
  • previewImage:预览图片接口
  • uploadImage:上传图片接口(上传到微信服务器,上传图片有效期3天,可用微信多媒体接口下载图片到自己的服务器,此处获得的 serverId 即 media_id。)
  • downloadImage:下载图片接口(从微信服务器下载)
  • getLocalImgData:获取本地图片接口(可根据chooseImage返回的localIds,配合该接口直接获取该图片的base64编码)
    注:getLocalImgData接口仅在 iOS WKWebview 下提供,用于兼容 iOS WKWebview 不支持 localId 直接显示图片的问题。意思就是getLocalImgData获取到图片的base64编码在ios端可以直接作为img标签的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');

最后页面再上传即可。

正式上线

将springboot 打包放到服务器后,需要在
https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=1889838119&lang=zh_CN
基本配置-公众号开发信息中,将服务器的IP配置到白名单中

20190411124701.png

最后需要在
https://mp.weixin.qq.com/cgi-bin/settingpage?t=setting/function&action=function&token=1889838119&lang=zh_CN
公众号设置-功能设置 将JS接口安全域名填写自己的域名
20190411124901.png

前后台分离的前端页面使用微信jssdk

上面的配置是springboot+freemarker,前后台不分离的,所以在本地调试的时候,直接将微信JS接口安全域名填写为springboot访问freemarker的地址(freemarker被springboot代理了),但是前后台分离的html,在浏览器中未被代理前微信服务器是不能访问到的,所以我用的windows版的nginx代理了本地的html文件,nginx配置信息如下:

server {
        listen 80;
        server_name 127.0.0.1;
        location / {
            root   E:\Project;  // html文件的绝对地址
            index  index.html;  // 代理时访问的首页
        }
}

将本地html页面代理后,还需要配置微信JS接口安全域名,本地测试的时候填写本地的ip地址就行了,线上的话还需要将鉴权文本放到你html页面存放的位置,然后JS接口安全域名填写线上的域名,后台就用上面springboot后台即可。

注意:
  • 不能使用hbuilder代理的html页面后,再用内网穿透将hbuilder代理的ip和端口号穿透到公网,这样是访问不了的,hbuilder代理的时候内部做了限制
  • 微信jssdk 只能在微信环境下使用
转载还请注明出处
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,165评论 6 523
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,476评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,446评论 0 368
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,157评论 1 301
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,164评论 6 400
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,615评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,969评论 3 430
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,959评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,495评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,529评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,641评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,233评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,976评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,407评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,552评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,218评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,715评论 2 366

推荐阅读更多精彩内容