• 微信JS-SDK接口 + FLASK实现图片上传


     最近在做一个项目从全球各地采集图片,考虑采用微信JS-SDK来简化开发。图片会首先上传到微信的服务器,返回一个id,然后根据这个id去微信服务器获取图片。微信提供可选择的压缩图片功能。图片首先上传到微信的服务器,可以保障较好的上传稳定性。

    详细内容请参考微信的官方文档:微信JS-SDK说明文档

    运行页面如下

    HTML

    在微信demo的基础上修改,见微信JS-SDK DEMO页面

    增加图片的多次选择,多次上传,删除等功能

    
    
    uploadImage.html
    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>图片上传 Demo</title>
        <link rel="stylesheet" href="http://203.195.235.76/jssdk/css/style.css">
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
        <style>
            .box{ position:relative;width:97%;}
            input[type="checkbox"]{
                 position:absolute; right:0px;
                -webkit-tap-highlight-color: rgba(0,0,0,0);
                   -webkit-appearance: none;
                width: 24px;
                height: 24px;
                background-size: 100%;
                outline:none;
                background-image: url("data:image/png;base64,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");
            }
            img {
                height: auto;
                width: 100%;
            }
            .imgdiv {
                height: auto;
                width: 24%;
                float:left;
            }
        </style>
    </head>
    <body ontouchstart="">
    <div class="wxapi_container">
        <div class="lbox_close wxapi_form">
            <h3 id="menu-image">已选择图片</h3>
            <div id="imgs"></div>
            <div style="clear:both"></div>
            <h3 id="upload-image">已上传图片</h3>
            <div id="uploadImgs"></div>
            <div style="clear:both"></div>
            <span class="desc">拍照或从手机相册中选择图片</span>
            <button class="btn btn_primary" id="chooseImage">选择图片</button>
            <button class="btn btn_primary" id="uploadImage">上传图片</button>
    
            <button class="btn btn_primary" id="postData">提交</button>
    
        </div>
    </div>
    </body>
    <script src="//cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script>
    <script src="https://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
    <script type="text/javascript" charset="utf-8"
            src="demo.js"></script>
    <script>
    
    //配置微信
    wx.config({
        debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: '1234567890', // 必填,公众号的唯一标识
        timestamp: {{ signature.timestamp }}, // 必填,生成签名的时间戳
        nonceStr: '{{ signature.nonceStr }}', // 必填,生成签名的随机串
        signature: '{{ signature.signature }}',// 必填,签名,见附录1
        jsApiList: [ 'chooseImage', 'uploadImage'] // 必填,需要使用的JS接口列表,所有JS接口列表见微信sdk文档附录2
    });
    </script>
    </html>
    
    

    JS代码

    var images = {
            index:1,   //用于产生全局图片id,绑定已选择图片和已上传图片
            selectIds: {},  //保存已经选择的图片id
            uploadIds:{}  //保存已经上传到微信服务器的图片
        };
    
        wx.ready(function () {
            // 5 图片接口
            // 5.1 拍照、本地选图
            $("#chooseImage").on("click", function () {
                wx.chooseImage({
                    success: function (res) {
                        for (var i = 0; i < res.localIds.length; i++) {
                             //全局图片id,绑定微信选择图片产生的localId,将用户选择图片追加到已选择图片
                            var id = '' + images.index++; 
                            images.selectIds[id] = res.localIds[i];  
                            $('#imgs').append('<div class="imgdiv"><div class="box"><input id="' + id +'" type="checkbox"/><img src="' + res.localIds[i] + '" /></div></div>');
                        }
                        console.log('已选择了 ' + Object.keys(images.selectIds).length + ' 张图片');
                    }
                });
            });
    
                // 5.3 上传图片
            $("#uploadImage").on("click", function () {
                if (Object.keys(images.selectIds).length == 0) {
                    alert('请先选择图片');
                    return;
                }
                var i = 0, length = Object.keys(images.selectIds).length;
                var selectIds = [];  //需要上传的图片的全局图片id
                for(var id in images.selectIds){
                    selectIds.push(id);
                }
                function upload() {
                    wx.uploadImage({
                        localId: images.selectIds[selectIds[i]],  //根据全局图片id获取已选择图片
                        isShowProgressTips: 0, // 默认为1,显示进度提示
                        success: function (res) {
                            //上传成功,images.selectIds中移除,images.uploadIds追加
                            //图片从已选择移到已上传区域
                            var selectId = selectIds[i];
                            localId = images.selectIds[selectId];
                            removeId(selectId);
                            $('#uploadImgs').append('<div class="imgdiv"><div class="box"><input id="' + selectId +'" type="checkbox"/><img src="' + localId + '" /></div></div>');
                            images.uploadIds[selectId] = res.serverId
                            i++;
                            if (i < length) {
                                console.log('已上传成功 ' + i + '/' + length);
                                upload();
                            } else {
                                alert('图片上传完毕, 已上传成功 ' + i + '/' + length);
                            }
                        },
                        fail: function (res) {
                            alert('上传失败 ' + i + '/' + length);
                        }
                    });
                }
                upload();
            });
        });
    
        wx.error(function (res) {
            alert(res.errMsg);
        });
    
    
        $('#postData').click(function () {
            if (Object.keys(images.uploadIds).length == 0) {
                alert('请先上传图片');
                return false;
            }
    
            var serverIds = [];
            for(var id in images.uploadIds){
                serverIds.push(images.uploadIds[id]);
            }
            var data = {
                'imgIds': serverIds
            }
            $.ajax({
                type: "post",
                async: false,
                url: '/uploadImgData',
                data: {data: JSON.stringify(data)},
                dataType: "json",
                success: function (res) {
                    if (res.rc === 0) {
                         alert('提交成功!')
                    } else {
                        alert(res.msg)
                    }
    
                },
                error: function (e) {
                    alert(JSON.stringify(e));
                }
            });
        });
    
    
    $("body").on('click', ':checkbox', function(){
        var id = $(this).attr('id');
        removeId(id);
    });
    
    function removeId(id){
        if(id in images.selectIds){
            delete images.selectIds[id]
        }else{
            delete images.uploadIds[id]
        }
        $('#' + id).parent().parent().remove();
    }

    FLASK代码

    生成微信签名,参考微信JSSDK文档附录1

    生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket 。

    1. 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):../15/54ce45d8d30b6bf6758f68d2e95bc627.html
    2. 用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi
    @app.route('/uploadImgDemo', methods=['GET', 'POST'])
    def uploadImgDemo():
        # if not in_weixin():
        #     return u'请在微信客户端打开'
        now = int(time.time())
        noncestr = '%s' % time.time()
        url = request.url.decode('utf-8')
        jsapi_ticket = get_jsapi_ticket()  #用户自定义函数,用于获取jsapi_ticket 
        signature = 'jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s' % (jsapi_ticket, noncestr, now, url)
        m = hashlib.sha1()
        m.update(signature)
        signature = m.hexdigest()
        signature = {"timestamp": now, "nonceStr": noncestr, "signature": signature, "url": url}
        return render_template('uploadImage.html', signature=signature)

    接受用户提交的数据存到数据库

    @app.route('/uploadImgData', methods=['GET', 'POST'])
    def uploadImgData():
        # if not in_weixin():
        #    return u'请在微信客户端打开'
        try:
            data = request.form.get('data')
            if data:
                data = json.loads(data)
                if data.get('imgIds'):
                    data['stamp'] = int(time.time())
                    # db.image.insert(data)  #保存id到数据库。
                    return json.dumps({'rc': 0, 'msg': '上传成功'})
            return json.dumps({'rc': 1, 'msg': '图片不能为空'})
        except Exception as e:
            print e
            return json.dumps({'rc': 1, 'msg': '上传失败'})

    图片上传到微信服务器后,用python下载图片

    access_token = None
    def get_access_token(refresh = False):
        global access_token
        if not access_token or refresh:
            access_token = access_token() #用户自定义函数,获取access_token
        return access_token
    
    
    #去微信服务器下载图片
    def downloadPic(img_dir, img_id):
        url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s' % (get_access_token(), img_id)
        down_file = '%s.jpg' % os.path.join(img_dir, img_id)
        with closing(requests.get(url, stream=True)) as r:
            content_length = int(r.headers.get('content-length', '0'))
            if 'content-disposition' in r.headers:
                m = re.match(r'attachment;s*filename="(.*)"', r.headers.get('content-disposition'))
                if m:
                    down_file = os.path.join(img_dir, m.group(1))
            if content_length < 1000:
                try:
                    r_json = r.json()
                    #提示access_token错误或过期,需要刷新token,重新下载
                    if r_json.get('errcode') == 40001 or r_json.get('errcode') == 42001:
                        get_access_token(True)
                    return False
                except Exception as e:
                    print e
            with open(down_file, 'wb') as f:
                for data in r.iter_content(1024):
                    f.write(data)
            return True
  • 相关阅读:
    架构中的技术性解决难题
    设计一个js的架构第二篇
    DOCTYPE 严格模式与混杂模式
    架构中的技术性解决难题之解决篇
    css常用页面布局
    记录一个css的综合运用
    写在立春
    Win7重装后,如何删除cygwin目录?
    重读《The C Programming Language》
    [分享]多个选项卡切换效果
  • 原文地址:https://www.cnblogs.com/lilinwei340/p/6379464.html
Copyright © 2020-2023  润新知