TypechoJoeTheme

香草物语

统计
登录
用户名
密码
/
注册
用户名
邮箱
输入密码
确认密码

微信小程序获取用户头像后上传到七牛云

Laughing博主
2022-11-14
/
0 评论
/
1,673 阅读
/
1951 个字
/
百度已收录
11/14
本文最后更新于2024年03月15日,已超过44天没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

一、事情起因

【油耗笔记OilNote】小程序好久没有升级了,最近打算对代码进行一些优化,但是新版本突然发现无法获取到用户微信头像及微信昵称了。

查阅官方文档才知道,官方有对getUserProfile接口进行调整了。

自 2022 年 10 月 25 日 24 时后(以下统称 “生效期” ),用户头像昵称获取规则将进行如下调整:

  1. 自生效期起,小程序 wx.getUserProfile 接口将被收回:生效期后发布的小程序新版本,通过 wx.getUserProfile 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的小程序版本不受影响,但如果要进行版本更新则需要进行适配。
  2. 自生效期起,插件通过 wx.getUserInfo 接口获取用户昵称头像将被收回:生效期后发布的插件新版本,通过 wx.getUserInfo 接口获取用户头像将统一返回默认灰色头像,昵称将统一返回 “微信用户”。生效期前发布的插件版本不受影响,但如果要进行版本更新则需要进行适配。通过 wx.login 与 wx.getUserInfo 接口获取 openId、unionId 能力不受影响。
  3. 「头像昵称填写能力」支持获取用户头像昵称:如业务需获取用户头像昵称,可以使用「头像昵称填写能力」(基础库 2.21.2 版本开始支持,覆盖iOS与安卓微信 8.0.16 以上版本),具体实践可见下方《最佳实践》。
  4. 小程序 wx.getUserProfile 与插件 wx.getUserInfo 接口兼容基础库 2.27.1 以下版本的头像昵称获取需求:对于来自低版本的基础库与微信客户端的访问,小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称,插件通过 wx.getUserInfo 接口将正常返回用户头像昵称,开发者可继续使用以上能力做向下兼容。

现在只要是发布的新版本,默认都需要调整,不然就显示下面灰色头像,已经发布的版本不受影响。

既然官方调整了,那么我们也只有被动接受的份。

二、油耗笔记的开发框架

油耗笔记OilNote不是直接使用微信开发者工具开发的,而是使用UniApp开发的,后端是SpringBoot。

三、改进思路

3.1、获取用户头像

由于官方指导意见是,使用button组件 open-type 的值设置为 chooseAvatar,当用户选择需要使用的头像之后,可以通过 bindchooseavatar 事件回调获取到头像信息的临时路径。

从官方的指导我们可以看到,微信并没有给我们返回一个具体的路径,只是返回了一个临时的路径,因此我们就必须自己获取到这个临时的文件,然后存储起来。

3.2、使用七牛云

由于使用的腾讯云的低配服务器,带宽、存储都比较捉襟见肘,所以我打算把头像都存储到七牛云上,既能减轻带宽压力也能节省服务器空间。

四、具体改进

4.1、UniApp页面改进

当用户通过微信登录时,此时获取用户头像信息,如果头像存在,登录之后跳转到首页,否则跳转到个人信息界面,让用户维护头像及昵称,此方法适用于新用户,同时也适用于老用户重新登录。

//微信授权登录
            getUserInfo(e) {
                let that = this;
                var p = this.getSetting();
                p.then(function(isAuth) {
                    console.log('是否已经授权', isAuth);
                    if (isAuth) {
                        console.log('用户信息,加密数据', e);
                        //eData  包括//微信头像//微信名称 还有加密的数据.
                        // let eData = JSON.parse(e.detail.rawData);
                        uni.getUserProfile({
                            desc: 'Wexin', // 这个参数是必须的
                            success: function(infoRes) {
                                //接下来就是访问接口.
                                that.$request(
                                    'wechat/authCode2Session?code=' + that.weChatCode, 'POST'
                                ).then(function(res) {
                                        if (res.code == 200) {
                                            //将接口返回的数据保存在全局变量中.
                                            let userInfo = {}
                                            // 用户id
                                            userInfo.id = res.data.id
                                            userInfo.username = res.data.username
                                            userInfo.tel = res.data.tel
                                            userInfo.email = res.data.email
                                            userInfo.wechatOpenId = res.data.wechatOpenId
                                            userInfo.nickName = res.data.nickName
                                            userInfo.avatarUrl = res.data.avatarUrl ? res.data
                                                .avatarUrl : infoRes.userInfo.avatarUrl
                                            userInfo.gender = infoRes.userInfo.gender
                                            userInfo.password = ''
                                            if (!userInfo.province) {
                                                userInfo.province = uni.getStorageSync('province')
                                            }
                                            if (!userInfo.city) {
                                                userInfo.city = uni.getStorageSync('city')
                                            }
                                            uni.setStorageSync('userInfo', userInfo);
                                            if (!res.data.avatarUrl) { //没有头像时,跳转到用户信息维护界面
                                                uni.redirectTo({
                                                    url: '/pages/profile/profile'
                                                })
                                            } else {
                                                uni.redirectTo({
                                                    url: '/pages/index/index'
                                                })
                                            }
                                        }
                                    },
                                    function(err) {
                                        uni.showToast({
                                            title: '授权登录失败!',
                                            mask: true,
                                            icon: 'none'
                                        })
                                    }
                                )
                            }
                        });
                    } else {
                        uni.showToast({
                            title: '授权失败,请确认授权已开启',
                            mask: true,
                            icon: 'none'
                        })
                    }
                });
            },

4.1.1、登录界面改造

4.1.2、个人信息界面改造

油耗笔记之前有一个【个人信息】页面,因此我打算把修改头像的功能发放到里面。

在合适的位置放入选择头像的按钮

<button class="avatar-wrapper" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
                <view class="cu-avatar xl round margin-center" :style="{backgroundImage:'url('+userInfo.avatarUrl+')'}"></view>
</button>

增加回调方法

用户选择微信头像之后,会回调chooseavatar方法,因此我们增加一个chooseavatar用于用户选择头像之后上传到服务器(进一步上传到七牛)

//选择头像回调
            onChooseAvatar(e) {
                const that = this;
                this.$set(this.userInfo, "avatarUrl", e.detail.avatarUrl);
                uni.uploadFile({
                    url: operate.api + 'user/uploadAvatar/', //上传接口
                    header: {
                        token: that.userInfo.id ? that.userInfo.id : '',
                    },
                    formData: {
                        'userInfo': JSON.stringify(that.userInfo)
                    },
                    filePath: e.detail.avatarUrl,
                    name: 'file',
                    success: (uploadFileRes) => {
                        uni.hideLoading();
                        const back = JSON.parse(uploadFileRes.data);
                        if (back.code == 200) {
                            that.$set(that.userInfo, 'avatarUrl', back.data.avatarUrl)
                        } else {
                            uni.showToast(back.msg)
                        }
                    },
                    fail: (error) => {
                        uni.hideLoading();
                        uni.showToast("图片上传失败,请联系开发!")
                    },
                    complete: function() {
                        uni.hideLoading();
                    }
                });
            }

4.2、后端改造

我们首先在后端增加一个方法,用于接受前端传递的附件及其他参数(我这里主要传递的是用户信息,用户更新用户表,记录头像地址)。

4.2.1、Controller

增加接受用户上传头像的Api,具体的实现我们稍后在说。

/**
     * 上传头像
     *
     * @param multipartFile 文件信息
     * @return 用户信息
     */
    @PostMapping("uploadAvatar")
    public AjaxResult uploadAvatar(@RequestParam("file") MultipartFile multipartFile,@RequestParam("userInfo") String oilUser) {
        return AjaxResult.success(oilUserService.uploadAvatar(multipartFile));
    }

4.2.2、增加七牛云依赖

经过上面改造,我们已经可以将用户头像上传到我们的后端了,接下来的任务就是将头像上传到我们的七牛云了。

现在pom.xml中增加七牛云的依赖

<!-- 七牛云-->
    <dependency>
        <groupId>com.qiniu</groupId>
        <artifactId>qiniu-java-sdk</artifactId>
        <version>7.2.28</version>
</dependency>

4.2.3、增加七牛云配置

为了方便使用,我们将七牛云的一些配置信息放入yaml文件中,方便维护。

# ========================== ↓↓↓↓↓↓ 七牛云配置 ↓↓↓↓↓↓ ==========================
qiniu:
  accessKey: XXX # Key
  secretKey: XXX # 密钥
  bucket: XXX # 空间名称
  domain: XXX # 访问域名
  dir: XXX/ # 目录

参数说明:

  • accessKey:AK,在七牛云,个人中心,密钥管理中可以看到
  • secretKey:SK,在七牛云,个人中心,密钥管理中可以看到
  • bucket:空间名称,根据自己创建的空间填写
  • domain:访问域名,根据控件绑定的域名实际填写
  • dir:存储路径,因为七牛云默认是直接存储到根目录,为了方便管理,我们可以创建子目录,比如avatar,可以填写avatar/

4.2.4、增加配置类

为了方便使用,我们将yaml的值,映射到配置类上。

/**
 * 七牛云实体
 */
@Component
@ConfigurationProperties(prefix = "qiniu")
public class QiNiuConfig {

    /**
     * Key
     */
    private static String accessKey;

    /**
     * 密钥
     */
    private static String secretKey;

    /**
     * 空间名称
     */
    private static String bucket;

    /**
     * 访问域名
     */
    private static String domain;

    /**
     * 目录
     */
    private static String dir;

    public static String getAccessKey() {
        return accessKey;
    }

    public void setAccessKey(String accessKey) {
        QiNiuConfig.accessKey = accessKey;
    }

    public static String getSecretKey() {
        return secretKey;
    }

    public void setSecretKey(String secretKey) {
        QiNiuConfig.secretKey = secretKey;
    }

    public static String getBucket() {
        return bucket;
    }

    public void setBucket(String bucket) {
        QiNiuConfig.bucket = bucket;
    }

    public static String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        QiNiuConfig.domain = domain;
    }

    public static String getDir() {
        return dir;
    }

    public void setDir(String dir) {
        QiNiuConfig.dir = dir;
    }
}

4.2.5、封装公共方法

为了方便调用,我们将上传、删除等方法封装到单独的服务中。

接口

/**
 * 七牛接口
 */
public interface IQiNiuService {

    /**
     * 以文件的形式上传
     *
     * @param file
     * @param fileName:
     * @return: java.lang.String
     */
    String uploadFile(File file, String fileName) throws QiniuException;

    /**
     * 以流的形式上传
     *
     * @param inputStream
     * @param fileName:
     * @return: java.lang.String
     */
    String uploadFile(InputStream inputStream, String fileName) throws QiniuException;

    /**
     * 删除文件
     *
     * @param key:
     * @return: java.lang.String
     */
    String delete(String key) throws QiniuException;
    
}

实现

@Service
public class QiNiuServiceImpl implements IQiNiuService, InitializingBean {

    // 七牛文件上传管理器
    private final Configuration cfg;
    private final Auth auth;

    public QiNiuServiceImpl() {
        // //构造一个带指定 Region 对象的配置类
        cfg = new Configuration(Region.huadong());
        auth = Auth.create(QiNiuConfig.getAccessKey(), QiNiuConfig.getSecretKey());

    }

    /**
     * 定义七牛云上传的相关策略
     */
    private StringMap putPolicy;

    @Override
    public String uploadFile(File file, String fileName) throws QiniuException {
        if (!StringUtils.isEmpty(QiNiuConfig.getDir())) {
            fileName = QiNiuConfig.getDir() + fileName;
        }
        UploadManager uploadManager = new UploadManager(cfg);
        Response response = uploadManager.put(file, fileName, getUploadToken());
        int retry = 0;
        while (response.needRetry() && retry < 3) {
            response = uploadManager.put(file, fileName, getUploadToken());
            retry++;
        }
        if (response.statusCode == 200) {
            return "http://" + QiNiuConfig.getDomain() + "/" + fileName;
        }
        return "上传失败!";
    }

    @Override
    public String uploadFile(InputStream inputStream, String fileName) throws QiniuException {
        if (!StringUtils.isEmpty(QiNiuConfig.getDir())) {
            fileName = QiNiuConfig.getDir() + fileName;
        }
        UploadManager uploadManager = new UploadManager(cfg);
        Response response = uploadManager.put(inputStream, fileName, getUploadToken(), null, null);
        int retry = 0;
        while (response.needRetry() && retry < 3) {
            response = uploadManager.put(inputStream, fileName, getUploadToken(), null, null);
            retry++;
        }
        if (response.statusCode == 200) {
            return "http://" + QiNiuConfig.getDomain() + "/" + fileName;
        }
        return "上传失败!";
    }


    @Override
    public String delete(String key) throws QiniuException {
        BucketManager bucketManager = new BucketManager(auth, cfg);
        Response response = bucketManager.delete(QiNiuConfig.getBucket(), key);
        int retry = 0;
        while (response.needRetry() && retry++ < 3) {
            response = bucketManager.delete(QiNiuConfig.getBucket(), key);
        }
        return response.statusCode == 200 ? "删除成功!" : "删除失败!";
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.putPolicy = new StringMap();
        putPolicy.put("insertOnly", 0);
    }

    /**
     * 获取上传凭证
     */
    private String getUploadToken() {
        return this.auth.uploadToken(QiNiuConfig.getBucket(), null, 3600, putPolicy);
    }
}

有几个需要注意的点:

  1. 在构造函数中,构造Configuration时,需要指定区域,因为我是华东区域的,因此使用的是Region.huadong(),如果使用的其他区域的,需要根据自己实际区域指定。
  2. 在指定策略时,因为我一个用户只允许一个头像,因此上传时,如果存在我们直接覆盖的,所以在afterPropertiesSet方法中,设置上传策略时,直接指定的putPolicy.put("insertOnly", 0);,即如果存在就覆盖,如果不想覆盖,可以设置putPolicy.put("insertOnly", 1);,但是此时需要注意,如果上传重名文件,会返回异常。

4.2.6、完善后用户上传头像方法

用户上传头像后,更新用户实体(但是此时不更新数据库),将更新后的实体返回到前端,点击保存时,再更新数据库。

    @Override
    public OilUser uploadAvatar(MultipartFile multipartFile, OilUser oilUser) throws IOException {
        String originalFilename = multipartFile.getOriginalFilename();
        if (originalFilename == null || !originalFilename.contains(".")) {
            throw new CustomException("文件名不正确");
        }
        String fileName = "avatar" + oilUser.getId() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String avatarUrl = qiNiuService.uploadFile(multipartFile.getInputStream(), fileName);
        oilUser.setAvatarUrl(avatarUrl);
//        LambdaUpdateWrapper<OilUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
//        userUpdateWrapper.set(OilUser::getAvatarUrl, oilUser.getAvatarUrl());
//        userUpdateWrapper.eq(OilUser::getId, oilUser.getId());
//        oilUserMapper.update(null, userUpdateWrapper);
        return oilUser;
    }

4.2.7、用户保存方法分改造

用户保存方法主要增加userUpdateWrapper.set(OilUser::getAvatarUrl, user.getAvatarUrl());,当用户有头像时,同步更新用户的头像信息。

/**
 * 新增或保存用户
 *
 * @param user 用户
 * @return 结果
 */
public OilUser saveUser(OilUser user) {
    if (user == null) {
        throw new CustomException("用户信息不能为空");
    }
    if (user.getId() == null) {
        user.setId("");
    }
    if (checkUserNameExist(user)) {
        throw new CustomException("用户名已存在");
    }
    if (StringUtils.isEmpty(user.getId())) {
        user.setId(UUID.randomUUID().toString());
        if (!StringUtils.isEmpty(user.getPassword())) {
            user.setPassword(passwordEncoder.encode(user.getPassword()));
        }
        oilUserMapper.insert(user);
    } else {
        LambdaUpdateWrapper<OilUser> userUpdateWrapper = new LambdaUpdateWrapper<>();
        userUpdateWrapper.set(OilUser::getUsername, user.getUsername());
        userUpdateWrapper.set(OilUser::getNickName, user.getNickName());
        userUpdateWrapper.set(OilUser::getTel, user.getTel());
        userUpdateWrapper.set(OilUser::getEmail, user.getEmail());
        if (!StringUtils.isEmpty(user.getPassword())) {
            userUpdateWrapper.set(OilUser::getPassword, passwordEncoder.encode(user.getPassword()));
        }
        if (!StringUtils.isEmpty(user.getProvince())) {
            userUpdateWrapper.set(OilUser::getProvince, user.getProvince());
        }
        if (!StringUtils.isEmpty(user.getCity())) {
            userUpdateWrapper.set(OilUser::getCity, user.getCity());
        }
        if (!StringUtils.isEmpty(user.getAvatarUrl())) {
            userUpdateWrapper.set(OilUser::getAvatarUrl, user.getAvatarUrl());
        }
        userUpdateWrapper.eq(OilUser::getId, user.getId());
        oilUserMapper.update(null, userUpdateWrapper);
        user.setPassword("");
    }
    return user;
}

五、效果

微信用户登录后,如果没有上传过头像,会自动跳转到【个人信息】页面

在个人信息上传头像后,自动跳转到首页。

六、其他注意事项

  1. 七牛云域名需要配置HTTPS
  2. 小程序域名白名单uploadFile合法域名需要配置后台上传附件的域名。
七牛微信小程序Spring Boot小程序uniappHBuilder
朗读
赞(3)
赞赏
感谢您的支持,我会继续努力哒!
版权属于:

香草物语

评论 (0)