微信小程序Token登录验证

2023-11-12

img

上图是微信开发文档提供的图。
最近开发一款小程序,看了许久的微信文档,这里来记录一下其中的登录与授权过程。
总体流程:

  1. 前端执行wx.login()获取code传给后端
  2. 后端通过微信官方的登录凭证校验接口获取到session_key与openid,将session_key与openid保存下来。然后自定义登录状态(一开始我也先不明白这里该怎么做,后面我会介绍我的做法,欢迎大佬指正)并返回给前端。
  3. 前端以后每次请求都会携带该自定义登录状态,后端进行登录状态的判断,正常就返回业务数据,否则重新登陆,获取新的登录状态。

接下来看几个官方的文档:

一、理论

1、前端:wx.login(Object object)

本接口从基础库版本 2.3.1 起支持在小程序插件中使用

调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户的唯一标识(openid)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。更多使用方法详见 小程序登录

在小程序插件中使用时,需要在用户信息功能页中获得用户授权之后调用。否则将返回 fail。详见 用户信息功能页

参数
Object object
属性 类型 默认值 必填 说明 最低版本
timeout number 超时时间,单位ms 1.9.90
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)
object.success 回调函数

参数

Object res
属性 类型 说明
code string 用户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调用 auth.code2Session,使用 code 换取 openid 和 session_key 等信息
示例代码
wx.login({
  success (res) {
    if (res.code) {
      //发起网络请求
      wx.request({
        url: 'https://test.com/onLogin',
        data: {
          code: res.code
        }
      })
    } else {
      console.log('登录失败!' + res.errMsg)
    }
  }
})

2、后端:auth.code2Session

本接口应在服务器端调用,详细说明参见服务端API

登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。更多使用方法详见 小程序登录

请求地址
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
请求参数
属性 类型 默认值 必填 说明
appid string 小程序 appId
secret string 小程序 appSecret
js_code string 登录时获取的 code
grant_type string 授权类型,此处只需填写 authorization_code
返回值
Object

返回的 JSON 数据包

属性 类型 说明
openid string 用户唯一标识
session_key string 会话密钥
unionid string 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见 UnionID 机制说明
errcode number 错误码
errmsg string 错误信息
errcode 的合法值
说明 最低版本
-1 系统繁忙,此时请开发者稍候再试
0 请求成功
40029 code 无效
45011 频率限制,每个用户每分钟100次

3、前端:wx.checkSession(Object object)

检查登录态是否过期。

通过 wx.login 接口获得的用户登录态拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效。具体时效逻辑由微信维护,对开发者透明。开发者只需要调用 wx.checkSession 接口检测当前用户登录态是否有效。

登录态过期后开发者可以再调用 wx.login 获取新的用户登录态。调用成功说明当前 session_key 未过期,调用失败说明 session_key 已过期。更多使用方法详见 小程序登录

参数
Object object
属性 类型 默认值 必填 说明
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)
示例代码
wx.checkSession({
  success () {
    //session_key 未过期,并且在本生命周期一直有效
  },
  fail () {
    // session_key 已经失效,需要重新执行登录流程
    wx.login() //重新登录
  }
})
会话密钥 session_key 有效性

开发者如果遇到因为 session_key 不正确而校验签名失败或解密失败,请关注下面几个与 session_key 有关的注意事项。

  1. wx.login 调用时,用户的 session_key 可能会被更新而致使旧 session_key 失效(刷新机制存在最短周期,如果同一个用户短时间内多次调用 wx.login,并非每次调用都导致 session_key 刷新)。开发者应该在明确需要重新登录时才调用 wx.login,及时通过 auth.code2Session 接口更新服务器存储的 session_key。
  2. 微信不会把 session_key 的有效期告知开发者。我们会根据用户使用小程序的行为对 session_key 进行续期。用户越频繁使用小程序,session_key 有效期越长。
  3. 开发者在 session_key 失效时,可以通过重新执行登录流程获取有效的 session_key。使用接口 wx.checkSession可以校验 session_key 是否有效,从而避免小程序反复执行登录流程。
  4. 当开发者在实现自定义登录态时,可以考虑以 session_key 有效期作为自身登录态有效期,也可以实现自定义的时效性策略。

4、后端:auth.checkSessionKey

本接口应在服务器端调用,详细说明参见服务端API

校验服务器所保存的登录态 session_key 是否合法。为了保持 session_key 私密性,接口不明文传输 session_key,而是通过校验登录态签名完成。

请求地址
GET https://api.weixin.qq.com/wxa/checksession?access_token=ACCESS_TOKEN&signature=SIGNATURE&openid=OPENID&sig_method=SIG_METHOD
请求参数
属性 类型 默认值 必填 说明
access_token string 接口调用凭证
openid string 用户唯一标识符
signature string 用户登录态签名
sig_method string 用户登录态签名的哈希方法,目前只支持 hmac_sha256
返回值
Object

返回的 JSON 数据包

属性 类型 说明
errcode number 错误码
errmsg string 错误信息

errcode 的合法值

说明 最低版本
0 ok 请求成功
87009 invalid signature 签名错误
调用示例
curl -G 'https://api.weixin.qq.com/wxa/checksession?access_token=OsAoOMw4niuuVbfSxxxxxxxxxxxxxxxxxxx&signature=fefce01bfba4670c85b228e6ca2b493c90971e7c442f54fc448662eb7cd72509&openid=oGZUI0egBJY1zhBYw2KhdUfwVJJE&sig_method=hmac_sha256'
返回示例

正确时的返回JSON数据包如下:

{"errcode": 0, "errmsg": "ok"}

错误时的返回JSON数据包如下(示例为签名错误):

{"errcode": 87009, "errmsg": "invalid signature"}

5、前端:wx.authorize(Object object)

基础库 1.2.0 开始支持,低版本需做兼容处理

提前向用户发起授权请求。调用后会立刻弹窗询问用户是否同意授权小程序使用某项功能或获取用户的某些数据,但不会实际调用对应接口。如果用户之前已经同意授权,则不会出现弹窗,直接返回成功。更多用法详见 用户授权。 > 小程序插件可以使用 wx.authorizeForMiniProgram

参数
Object object
属性 类型 默认值 必填 说明
scope string 需要获取权限的 scope,详见 scope 列表
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)
示例代码
// 可以通过 wx.getSetting 先查询一下用户是否授权了 "scope.record" 这个 scope
wx.getSetting({
  success(res) {
    if (!res.authSetting['scope.record']) {
      wx.authorize({
        scope: 'scope.record',
        success () {
          // 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问
          wx.startRecord()
        }
      })
    }
  }
})

6、权限

部分接口需要经过用户授权同意才能调用。我们把这些接口按使用范围分成多个 scope ,用户选择对 scope 来进行授权,当授权给一个 scope 之后,其对应的所有接口都可以直接使用。

此类接口调用时:

  • 如果用户未接受或拒绝过此权限,会弹窗询问用户,用户点击同意后方可调用接口;
  • 如果用户已授权,可以直接调用接口;
  • 如果用户已拒绝授权,则不会出现弹窗,而是直接进入接口 fail 回调。请开发者兼容用户拒绝授权的场景。
获取用户授权设置

开发者可以使用 wx.getSetting 获取用户当前的授权状态。

打开设置界面

用户可以在小程序设置界面(「右上角」 - 「关于」 - 「右上角」 - 「设置」)中控制对该小程序的授权状态。

开发者可以调用 wx.openSetting 打开设置界面,引导用户开启授权。

提前发起授权请求

开发者可以使用 wx.authorize 在调用需授权 API 之前,提前向用户发起授权请求。

scope 列表
scope 对应接口 描述
scope.userInfo wx.getUserInfo 用户信息
scope.userLocation wx.getLocation 地理位置
scope.werun wx.getWeRunData 微信运动步数
scope.writePhotosAlbum wx.saveImageToPhotosAlbum 保存到相册
授权有效期

一旦用户明确同意或拒绝过授权,其授权关系会记录在后台,直到用户主动删除小程序。

最佳实践

在真正需要使用授权接口时,才向用户发起授权申请,并在授权申请中说明清楚要使用该功能的理由。

注意事项
  1. wx.authorize({scope: "scope.userInfo"}),不会弹出授权窗口,请使用 wx.createUserInfoButton
  2. 需要授权 scope.userLocation 时必须配置地理位置用途说明

我根据对上面文档的理解,重新介绍一下最开始的那幅图,以及我的解决方案:

  1. 前端发起wx.login请求获得code(5分钟内有效)
  2. 前端携带code请求后端登录接口,后端拿到code去请求登录品质校验接口,校验code,正常则返回session_key与openid。
  3. 根据openid生成token返回给前端(这里的token就是我定义的登录状态)
  4. 前端以后每次请求都要携带上我的token
  5. session_key失效这需要重新登录
  1. 为什么利用openid生产token?
    session_key微信官方强烈要求不能返回给前端,如果使用session_key生产token,会有被破译token的危险,openid是用户唯一标识,比起session_key没那么重要,session_key还会用于对微信敏感数据进行解密,所以session_key很重要。
  2. token时效怎么设置?
    在生成token时,不设置失效时间,让token与session_key同步,前端每次进入小程序时使用wx.checkSession检验session_key是否失效,失效则重新登陆,生成新的session_key与token,token与session_key以及openid都被我存入了数据库中。
  3. 每次生成的token一样?
    前面说到了我是使用openid生成token的,那么如果每次的密文一样,则生成的token就会不变,这是可以考虑使用uuid作为密文,或者使用session_key作为密文。

授权过程:前端授权,存入用户信息进入数据库。(后端使用session_key验证数据一致性,一致则存入数据库,否则返回异常)

二、代码

1、登录请求处理:


@Value("${wechat.appid}")
private String appid;

@Value("${wechat.secret}")
private String secret;

@Override
public String onLogin(String code) throws IOException, BaseException {
    String url = "https://api.weixin.qq.com/sns/jscode2session" +
            "?appid="+appid+"&secret="+secret+"&js_code="+code+"&grant_type=authorization_code";
    String result = "";
    BufferedReader in = null;

    try {
        URL url1 = new URL(url);
        URLConnection urlConnection = url1.openConnection();
        in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
        String line;
        while ((line = in.readLine()) != null) {
            result += line;
        }
    } catch (Exception e) {
        throw e;
    } finally {
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    JSON parse = JSONUtil.parse(result);
    Integer errcode = parse.getByPath("errcode", Integer.class);
    if (errcode == null){
        // 用户唯一标识
        String openid = parse.getByPath("openid", String.class);
        // 会话密钥
        String sessionKey = parse.getByPath("session_key", String.class);
        // 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回
        String unionid = parse.getByPath("unionid", String.class);
        // 通过oppenid与session_key计算token
        String token = JwtUtils.getToken(openid, sessionKey);

        SignaturePO signaturePO = signatureDAO.queryById(openid);

        if (signaturePO != null){
            // 该用户以及注册过了
            // 更新session_key与token
            signaturePO.setSessionKey(sessionKey);
            signaturePO.setToken(token);
            int update = signatureDAO.update(signaturePO);
            if (update != 1){
                throw new BaseException(500, "更新session_key与token失败");
            }
            return token;
        }else {
            // 该用户未被注册,将该用户的session_key与token添加到数据库
            SignaturePO po = new SignaturePO();
            po.setOpenid(openid);
            po.setSessionKey(sessionKey);
            po.setToken(token);

            int insert = signatureDAO.insert(po);
            if (insert != 1){
                throw new BaseException(500, "更新session_key与token失败");
            }
            return token;
        }

    }else if (errcode == -1){
        throw new BaseException(errcode, "系统繁忙,稍候再试");
    }else if (errcode == 40029){
        throw new BaseException(errcode, "code无效");
    }else if (errcode == 45011){
        throw new BaseException(errcode, "频率限制,每个用户每分钟100次");
    }else {
        throw new BaseException(500, "服务器异常");
    }
}

2、拦截器拦截

@Component
public class JWTInterceptor implements HandlerInterceptor {

    @Resource
    private SignatureDAO signatureDAO;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token != null && !"".equals(token)) {

            // 查询数据库中是否有该token
            SignaturePO signaturePO = signatureDAO.queryByToken(token);
            if (signaturePO != null){
                // 该token可以正常使用
                request.setAttribute(SESSIONKEY, signaturePO.getSessionKey());
                return true;
            }else {
                throw new BaseException(ResultEnum.CHECK_EROR);
            }
        }
        return false;
    }
}

注意:这里会出现依赖注入问题。解决办法

3、生产token

 public static String getToken(String openId, String session_key) throws UnsupportedEncodingException {
     String token = JWT.create()
             .withKeyId(openId)
             .withIssuer("weixin")
             .withIssuedAt(new Date())
             .withClaim("openid", openId)
             .sign(Algorithm.HMAC256(session_key));
     return token;
 }

4、sha1计算签名

public static String getSha1(String str) {

        char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f' };
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));
            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            return null;
        }
    }

5、授权校验数据一致性

@Override
    public void authorize(ResDTO resDTO, HttpServletRequest request) throws BaseException {
        String sessionKey = JwtUtils.getSessionKey(request);

        //signature = sha1( rawData + session_key )
        String signature = resDTO.getSignature();
        String signature2  = Sha1.getSha1(resDTO.getRawData() + sessionKey);

        if (signature.equals(signature2)){
            // 数据一致
            String encryptedData = resDTO.getEncryptedData();
            String iv = resDTO.getIv();
            // 解密敏感数据
            // 格式:
            // {'openId': 'oGZUI0egBJY1zhBYw2KhdUfwVJJE',
            // 'nickName': 'Band', 'gender': 1, 'language': 'zh_CN',
            // 'city': 'Guangzhou', 'province': 'Guangdong', 'country': 'CN',
            // 'avatarUrl': 'http://wx.qlogo.cn/mmopen/vi_32/aSKcBBPpibyKNicHNTMM0qJVh8Kjgiak2AHWr8MHM4WgMEm7GFhsf8OYrySdbvAMvTsw3mo8ibKicsnfN5pRjl1p8HQ/0',
            // 'unionId': 'ocMvos6NjeKLIBqg5Mr9QjxrP1FA',
            // 'watermark': {'timestamp': 1477314187, 'appid': 'wx4f4bc4dec97d474b'}}
            JSONObject userInfoByEncryptedData = WXDecryptUtil.getUserInfoByEncryptedData(encryptedData, sessionKey, iv);
            String openId = userInfoByEncryptedData.getString("openId");

            UserInfoPO userInfoPO = resDTO.getUserInfoPO();
            // 设置openid
            userInfoPO.setOpenid(openId);

            //将用户信息添加到数据库
            int insert = userInfoDAO.insert(userInfoPO);
            if (insert != 1){
                //添加失败
                throw new BaseException(500, "添加用户信息失败");
            }
        }else {
            throw new BaseException(500, "数据不一致");
        }
    }

6、微信实现解密敏感数据

public class WXDecryptUtil {

    public static JSONObject getUserInfoByEncryptedData(String encryptedData, String sessionKey, String iv){
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);

        try {
            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + 1;
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, StandardCharsets.UTF_8);
                return JSONObject.parseObject(result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

微信小程序Token登录验证 的相关文章

  • SPNEGO 密码身份验证问题

    我已将我的应用程序配置为通过 SPNEGO 与 Websphere 使用 Kerberos 身份验证 这是详细信息 krb5 conf libdefaults default realm ABC MYCOMPANY COM default
  • 用于解析和构建逻辑表达式的 Java 库

    我正在寻找一个 Java 开源库来解析和构建类似 SQL 的表达式 例如评估表达式的有效性 例如 a x or y and b z 另外我想要一个用于构建或扩展表达式的 API 就像是 Expression exp new Expressi
  • 在 Java 中使用 Batik 检查和删除 SVG 中的属性

    这个问题基本上说明了一切 如何检查 SVG 是否具有 viewBox 属性 我正在使用蜡染库 我需要这个 因为我需要 至少 通知用户有一个 viewBox 属性 我可以删除它吗 使用 org w3c dom 类 您可以按照以下方式做一些事情
  • 对象数组的数组(二维数组)JNI

    我正在努力创建自定义对象类型 ShareStruct 的二维数组 jobjectArray ret jobjectArray ins jobjectArray outs jclass myClass env gt FindClass env
  • 如何作为应用程序发布到页面?

    所以 我有一个应用程序 Facebook 应用程序实体 并且我有一个页面 我想使用应用程序通过java代码 通过restfb或任何其他建议 发布到页面 看起来我错过了页面授予应用程序发布权限的阶段 不知道该怎么做 谢谢你们 乌里 您只能 作
  • 从 Spring MVC XML 文件转移到 javaconfig。我真的对我的数据库 XML 文件感到困惑

    我从 Spring MVC XML 文件转移到 javaconfig 我真的对我的数据库 XML 文件感到困惑 我不知道如何让 Hibernate4 工作以及我的 JBoss JNDI 数据源工作 有人可以告诉我如何使 javaconfig
  • 查询 MongoDB 集合中的字段。

    我正在尝试查询 mongodb 集合中的特定字段 这是我的代码和输出 Mongo m new Mongo DB db m getDB mydb DBCollection coll db getCollection student addin
  • 迭代函数可以调用自身吗?

    当观看下面的 MIT 6 001 课程视频时 讲师在 28 00 将此算法标记为迭代 但是 在 30 27 他说这个算法和实际的 递归 算法都是递归的 该函数正在使用基本情况调用自身 那么这次迭代情况如何 private int itera
  • 在哪里保存选项值、重要文件的路径等[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我正在创建一个程序 需要设置一些选项值以及图像文件的一些路径 SQLite 数据库的路径 有关各种按钮上文本的一些信息 有关要使用哪个数据库的信
  • 这个等待通知线程语义的真正目的是什么?

    我刚刚遇到一些代码 它使用等待通知构造通过其其他成员方法与类中定义的线程进行通信 有趣的是 获取锁后 同步范围内的所有线程都会在同一锁上进行定时等待 请参见下面的代码片段 随后 在非同步作用域中 线程执行其关键函数 即 做一些有用的事情1
  • 可访问数据的 Java 约定。 (公共访问器和 Getter/命名)

    通过 Java API 您会看到大量冲突的命名和实践 这让我感到非常困惑 例如 The String http grepcode com file repository grepcode com java root jdk openjdk
  • java setFullScreenWindow 在 Mac 中隐藏登录对话框

    我使用的是全屏窗口 类似于屏幕保护程序 使用这里的方法 GraphicsEnvironment getLocalGraphicsEnvironment getDefaultScreenDevice setFullScreenWindow t
  • 纱线上的火花,连接到资源管理器 /0.0.0.0:8032

    我正在我的开发机器 Mac 上编写 Spark 程序 hadoop的版本是2 6 spark的版本是1 6 2 hadoop集群有3个节点 当然都在linux机器上 我在idea IDE中以spark独立模式运行spark程序 它运行成功
  • 在 java 中运行外部应用程序但不要等待它完成

    我正在用java编写一个应用程序 允许我运行其他应用程序 为此 我使用了 Process 类对象 但当我这样做时 应用程序会等待进程结束 然后再退出 有没有办法在 Java 中运行外部应用程序 但不等待它完成 public static v
  • 如何从字符串中解析一个大整数? [复制]

    这个问题在这里已经有答案了 我有一个这样的方法 Integer parseInt myInt 不是这个整数变得很长 我得到以下异常 java lang NumberFormatException For input string 40001
  • 使用单独的线程在java中读取和写入文件

    我创建了两个线程并修改了 run 函数 以便一个线程读取一行 另一个线程将同一行写入新文件 这种情况会发生直到整个文件被复制为止 我遇到的问题是 即使我使用变量来控制线程一一执行 但线程的执行仍然不均匀 即一个线程执行多次 然后控制权转移
  • BadPaddingException:无效的密文

    我需要一些帮助 因为这是我第一次编写加密代码 加密代码似乎工作正常 但解密会引发错误 我得到的错误是 de flexiprovider api exceptions BadPaddingException 无效的密文 in the 解密函数
  • java.lang.IllegalStateException - 提交响应后无法创建会话

    我在我的项目中使用 JSF PrimeFaces 我为此准备了一个Maven项目 当我编译项目并加载主页后 我收到以下异常 java lang IllegalStateException Cannot create a session af
  • Axis2 错误:要输出的文本中的空白字符 (0x4) 无效

    我创建了一个 Java 客户端 使用 Axis2 1 7 6 作为代码生成器与 SOAP Web 服务进行交互 问题在于客户端的某些输入抛出异常并显示以下消息 org apache axis2 AxisFault Invalid white
  • Java/MongoDB 按日期查询

    我将一个值作为 java util Date 存储在我的集合中 但是当我查询以获取两个特定日期之间的值时 我最终得到的值超出了范围 这是我的代码 插入 BasicDBObject object new BasicDBObject objec

随机推荐

  • 在dos下使用debug被提示incorrect ms dos version的解决办法

    原创 在dos下使用debug被提示incorrect ms dos version的解决办法 首先可以确定一点 直接使用debug时它是直接在当前目录下寻找的 一旦当前目录下没有debug或debug版本不合适 则会出错 直接在 开始 中
  • Unity normalized的坑

    注意 向量太小会返回0 所以要考虑等于000的情况 我们的项目恰恰会用到 normalized返回的值传给 Quaternion LookRotation 结果造成000错误 切记要考虑到 normalized 为0 的情况
  • 嵌入式(TCP、IP协议原理)

    TCP IP协议网络封包格式 以太网头 IP头 TCP头 TCP是一种面向连接的 可靠的数据传输 一 TCP的可靠传输 通过确认和重发机制 1 TCP把所有要发送的数据进行编号 每一个字节用一个号 2 发送时从当前数据位置 发送window
  • 【图片标注】推荐一款特别好用的图片分割标注工具EIseg

    前言 最近在百度飞浆的公众号看到一个特别有趣的图片标注工具EIseg 它labelme等标记工具不同的是 它是一个通过深度学习来标注的工具 使用鼠标点一下就能完成标注任务 下面是工具的演示 本文章仅供学习 操作演示 标注效果 绿色的点为正样
  • java jps监控_java性能监控工具jps-windows

    jps Lists the instrumented Java Virtual Machines JVMs on the target system This command is experimental and unsupported
  • mybatis jdbcType: DATE ,TIMESTAMP 区别:

    1 mybatis中 jdbcType 时间类型 当jdbcType DATE 时 只传入了 年月日 jdbcType TIMESTAMP 年月日 时分秒 2 jdbcType 是否必须 使用时 没有加jdbcType 正常 加上jdbcT
  • 【零知ESP8266教程】AP模式下WIFI UDP协议通信示例

    本帖主要讲解ESP8266 WIFI功能关于UDP协议网络传输的应用 这里演示了ESP8266在AP模式下UDP通信的示例 1 硬件 零知ESP8266开发板 2 软件 1 代码如下 文件 udp server ino by 零知实验室 u
  • 为什么 DeFi 需要探索以太坊外的世界?

    由于DeFi生态系统的总锁仓价值超过130亿美元 DeFi创造了最近几周的最高纪录 可以说 DeFi是当前加密货币行业发展最快的一个领域 在短短几个月内吸引了大量资本和市场参与者 在这一强劲势头下 以太坊仍是DeFi市场的主宰 占据了96
  • 联想网络同传系统_皮层网络结构的联想学习“理论”

    本文来自公众号 吴思Lab计算神经科学及类脑计算 AI科技评论 获授权转载 如需转载 请联系吴思Lab 编者按 张单可博士是我们课题组已毕业的博士生 最近刚从美国东北大学做完博士后回来 在中国科学院深圳先进技术研究院工作 这是他在博士后期间
  • 平台使用篇

    本课程提供的实验平台总体可以分成两个部分 硬件平台和软件平台 本讲简要介绍各个部分的基本组成及实验开发流程 平台使用篇 RflySim飞控底层实验平台配置介绍 01 电脑配置 1 1推荐配置 系统 Windows 10 x64系统 版本大于
  • MySQL如何查询表中重复的数据

    文章目录 一 查询重复记录 二 总结 一 查询重复记录 例 查询员工表里出现重复姓名的记录 思路 1 查看重复记录 首先要使用分组函数 group by 再用聚合函数中的计数函数count name 给姓名列计数 且使用group by 后
  • 【论文阅读】BGRL:Bootstrapped Representation Learning on Graphs

    目录 摘要 1 引言 2 Bootstrapped Graph Latents BGRL 2 1 BGRL组件 2 2 BGRL更新步骤 2 2 1 更新 2 2 2 更新
  • 解密全产业供应关系,助力企业寻找客户资源,洞察商机

    在当今商业竞争日益激烈的时代 企业要实现长期可持续的发展 需要深入了解供应链关系 抢先捕获商机 拓展优质的客户资源 然而 供应链关系错综复杂 商机 客户隐藏在其中 如何挖掘和洞察成为了企业亟需解决的难题 五度易链基于产业链上下游的视角 帮助
  • 合并两个有序数组(go实现

    合并两个有序数组 go实现 题目描述 解题思路 代码示例 执行结果 更多 题目描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2 另有两个整数 m 和 n 分别表示 nums1 和 nums2 中的元素数目 请你 合并
  • 机器学习python实现,自我总结

    机器学习代码及套用 网上很多机器学习的资料 我看了很多 最后发现用的时候其实没有那么复杂 也不需要了解很多的数学知识 也庆幸自己没有在上机器学习的课时放弃 写篇文章来总结总结吧 本文使用python来套 导入机器学习模块 自己随便百度一下机
  • centos网页/phpmyadmin 打不开_苹果手机Safari打不开网页?按下一个键,马上就能用...

    Safari 苹果手机自带的一个浏览器 很多人都认为是个垃圾 甚至把它卸载 但其实 Safari是当时乔布斯最看重的一个苹果武器 如果真要好好利用起来 这个自带软件远远比你想象的厉害
  • 下载LAMBDA Group的代码

    LAMBDA Group 的文章在其主页有公布代码和数据集 具体在其 主页 gt 数据与代码 下面的 代码 栏列了文章 比如点开第一篇 AcMR 里面有个下载代码的链接 code 但点开会发现 无法链接到服务器 根据杨嘉祺的邮件回复 在网址
  • Google前工程经理王忻:如何准备软件工程师的面试

    2010 10 20 10 48 4639次阅读 来源 伯乐在线 职场博客 已有0条评论 发表评论 关键词 Google 软件工程师 面试 作者 人力资源 收藏这篇资讯 导读 原文作者王忻 Google前工程经理 2003年月加入Googl
  • 【华为OD机试真题2023B卷 JAVA&JS】人气最高的店铺

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 人气最高的店铺 知识点贪心排序 时间限制 1s 空间限制 32MB 限定语言 不限 题目描述 某购物城有m个商铺 现决定举办一场活动选出人气最高店铺 活动共有n位市民参与 每位市民只能
  • 微信小程序Token登录验证

    上图是微信开发文档提供的图 最近开发一款小程序 看了许久的微信文档 这里来记录一下其中的登录与授权过程 总体流程 前端执行wx login 获取code传给后端 后端通过微信官方的登录凭证校验接口获取到session key与openid