精华内容
下载资源
问答
  • 文章目录[隐藏] 前言错误代码示例正确代码示例总结 前言 ...但是微信开发第一步就给我难住了,微信公众号Token 一直配置失败, 于是上网搜索了下,找到一段可用的代码,结果比对后发现:我的代码中变量的大小写...
    文章目录[隐藏]

    前言

    今天生病在家,由于我的微信公众号被投诉了,因此影视站接口被迫关闭,于是就想着给大家分享 APP,于是就用了下微信的自动回复,
    想着能否把这个自动回复对接到 MIPCMS 上,于是就开干。但是微信开发第一步就给我难住了,微信公众号Token 一直配置失败,
    于是上网搜索了下,找到一段可用的代码,结果比对后发现:我的代码中变量的大小写问题,在 echo 的时候找不到变量,因此报错。
    

    错误代码示例

    <?php
    // 接收参数
    
    // 加密签名
    $signature = $_GET['signature'];
    
    // 时间戳
    $timestamp = $_GET['timestamp'];
    
    // 随机数
    $nonce = $_GET['nonce'];
    
    // 随机字符串
    $echostr = $_GET['echostr']; // 此处为小写,调用时是大写,主要原因在此。
    
    // TOKEN
    define('TOKEN', 'weixin');
    
    // 字典序排序
    $tempArr = array(TOKEN, $timestamp, $nonce);
    sort($tempArr, SORT_STRING);
    
    // 拼接字符串,sha1 加密
    $tempStr = join($tempArr);
    $tempStr = sha1($tempStr);
    
    if($tempStr == $signature) {
      echo $echoStr; // 此处 echo 大小写有误
    } else {
      echo 'error';
      exit;
    }
    

    正确代码示例

    <?php
    // 接收参数
    // TOKEN
    define('TOKEN', 'test123456');
    
    // 加密签名
    $signature = $_GET["signature"];
    
    // 时间戳
    $timestamp = $_GET['timestamp'];
    
    // 随机数
    $nonce = $_GET['nonce'];
    
    // 随机字符串
    $echoStr = $_GET['echostr'];
    
    // 字典序排序
    $tempArr = array(TOKEN, $timestamp, $nonce);
    sort($tempArr, SORT_STRING);
    
    // 拼接字符串,sha1 加密
    $tempStr = join($tempArr);
    $tempStr = sha1($tempStr);
    // $tmpStr = implode( $tempArr );
    // $tmpStr = sha1( $tmpStr );
    
    if($tempStr == $signature) {
      echo $echoStr;
    } else {
      echo 'error';
      exit;
    }
    

    总结

    开发需要仔细呀,因为一个变量名搞这么,时间白白浪费了。
    
    展开全文
  • 微信小程序Token登录验证

    千次阅读 2021-02-24 23:28:48
    然后自定义登录状态(一开始我也先明白这里该怎么做,后面我会介绍我的做法,欢迎大佬指正)并返回给前端。 前端以后每次请求都会携带该自定义登录状态,后端进行登录状态的判断,正常就返回业务数据,否则重新...

    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
    属性类型默认值必填说明最低版本
    timeoutnumber超时时间,单位ms1.9.90
    successfunction接口调用成功的回调函数
    failfunction接口调用失败的回调函数
    completefunction接口调用结束的回调函数(调用成功、失败都会执行)

    object.success 回调函数

    参数

    Object res
    属性类型说明
    codestring用户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调用 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
    

    请求参数

    属性类型默认值必填说明
    appidstring小程序 appId
    secretstring小程序 appSecret
    js_codestring登录时获取的 code
    grant_typestring授权类型,此处只需填写 authorization_code

    返回值

    Object

    返回的 JSON 数据包

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

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

    检查登录态是否过期。

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

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

    参数

    Object object
    属性类型默认值必填说明
    successfunction接口调用成功的回调函数
    failfunction接口调用失败的回调函数
    completefunction接口调用结束的回调函数(调用成功、失败都会执行)

    示例代码

    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_tokenstring接口调用凭证
    openidstring用户唯一标识符
    signaturestring用户登录态签名
    sig_methodstring用户登录态签名的哈希方法,目前只支持 hmac_sha256

    返回值

    Object

    返回的 JSON 数据包

    属性类型说明
    errcodenumber错误码
    errmsgstring错误信息

    errcode 的合法值

    说明最低版本
    0ok 请求成功
    87009invalid 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
    属性类型默认值必填说明
    scopestring需要获取权限的 scope,详见 scope 列表
    successfunction接口调用成功的回调函数
    failfunction接口调用失败的回调函数
    completefunction接口调用结束的回调函数(调用成功、失败都会执行)

    示例代码

    // 可以通过 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.userInfowx.getUserInfo用户信息
    scope.userLocationwx.getLocation地理位置
    scope.werunwx.getWeRunData微信运动步数
    scope.writePhotosAlbumwx.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;
        }
    }
    
    展开全文
  • #spaCy V3.0 基于规则匹配(1)----基于词符(Token)的匹配 用于发现短语、词符(tokens)、实体 部分示例内容是针对中文并结合电力行业领域具体应用原创的

    #spaCy V3.0 基于规则匹配(1)----基于词符(Token)的匹配

    用于发现短语、词符(tokens)、实体

    相比于在普通文本上使用正则表达式,spaCy基于规则的匹配引擎和组件不仅可以找到要查找的单词和短语,还可以访问文档中的词符(tokens)及其关系。这意味着可以轻松地访问和分析被查找词符(tokens)周围的词符,将spans合并为单个Token,或者向doc.ents中的命名实体添加条目.

    在介绍spaCy基于规则匹配的内容之前,首先回答两个问题。

    • 1 使用规则还是训练模型?
      .
      对于复杂的任务,通常更好的做法是训练一个统计实体识别模型。然而,统计模型需要大量训练数据,因此,在许多情况下,基于规则的方法更为实用,尤其对一个新开始的项目。可以使用基于规则的方法作为数据收集过程的一部分,采用“自举法”启动统计模型。
      .
      如果希望系统能够基于现有示例类推到更大范围时,那么训练模型是非常有用的。要是有局部上下文线索,效果更好。例如,如果你试图发现人名或公司名,则此应用会得益于统计命名实体识别模型。
      .
      如果要从数据中找到的示例数量是有限的,或这些示例具有非常清晰、结构化模式,可以使用标记规则或正则表达式来表示,那么基于规则的系统就是一个不错的选择。例如,使用纯基于规则的方法,或许就能够很好地处理国家名称、IP地址或URL。
      .
      当然也可以将这两种方法结合起来,并使用规则来改进统计模型,以处理非常具体的案例提高准确性。有关详细信息,请参见基于第4节《基于规则的实体识别》。

    • 2 使用词符匹配器(Token Mather)还是短语匹配器(PhraseMatcher)?
      .
      如果你已经有了一个由单个或多个Token短语组成的大型术语列表或地名录,并且希望在数据中找到其准确实例,那么PhraseMatcher非常有用。
      .
      Matcher(Token Mather)没有PhraseMatcher快,因为它是对各个Token属性进行比较。但是,它能够对所需查找的Tokens编写非常抽象的表示,比如:词汇属性、模型预测的语言特征、运算符、集合操作和丰富的比较操作。例如,你可以找到一个名词,后跟一个动词“爱”或“喜欢”,再跟一个或不跟限定词,以及另一个至少10个字符长的Token。

    1 基于词符(Token)的匹配

    spaCy提供了一个很有特色的Token规则匹配引擎—Matcher,类似于正则表达式。这些规则可以引用Token注释(例如,Token的text、tag_),以及标志(例如,IS_PUNCT)。此规则匹配器还允许传入一个自定义回调函数来对匹配项进行操作。例如,合并实体和应用自定义标签。您还可以将模式与实体id相关联,进行一些基本的实体链接或消除歧义任务。对于大型术语列表匹配,可以使用PhraseMatcher,它可以使用Doc对象作为匹配模式。

    1.1 添加模式

    例如:写一个模板,匹配到’母管’ 后面跟着一个名词"NOUN",

    pattern = [{'ORTH':'母管'},{'POS':'NOUN'},{'POS':'VERB','OP':"?"},{'ORTH':'报警'}]
    

    重要提示:
    在编写模式时,请记住模式中的每个字典代表一个词符(Token)。如果spaCy的词符(Token)与模式中定义的词符(Token)不匹配,那么该模式将不会产生任何结果。开发复杂模式时,请确保参照spaCy的词符(Token)来检查示例:

    import spacy
    from spacy.matcher import Matcher
    
    nlp = spacy.load('zh_core_web_trf')
    doc = nlp('辅机冷却水母管压力低报警。')
    #分词结果 
    [t for t in doc]
    #[辅机, 冷却, 水母, 管, 压力, 低, 报警, 。]
    
    patterns = [{'ORTH':'母管'},{'POS':'NOUN'},{'POS':'VERB','OP':"?"},{'ORTH':'报警'}]
    matcher = Matcher(nlp.vocab)
    matcher.add("MY_PATTERN", [patterns])
    matches = matcher(doc)
    
    for match_id, start, end in matches:
    	string_id = nlp.vocab.strings[match_id]  # Get string representation
    	span = doc[start:end]  # The matched span
    	print(match_id, string_id, start, end, span.text)
    	
    #结果为空,没有找到匹配的模式!!!
    
    
    #结合上篇《spaCy V3.0.0 专业领域中文分词问题》,改写代码:
    
    nlp = spacy.load('zh_core_web_trf')
    #添加用户专有名词
    proper_nouns = ['冷却水', '母管']
    nlp.tokenizer.pkuseg_update_user_dict(proper_nouns)
    
    doc = nlp('辅机冷却水母管压力低报警。')
    #分词结果 
    [t for t in doc]
    #[辅机, 冷却水, 母管, 压力, 低, 报警, 。]
    
    patterns = [{'ORTH':'母管'},{'POS':'NOUN'},{'POS':'VERB','OP':"?"},{'ORTH':'报警'}]
    matcher = Matcher(nlp.vocab)
    matcher.add("MY_PATTERN", [patterns])
    matches = matcher(doc)
    
    for match_id, start, end in matches:
    	string_id = nlp.vocab.strings[match_id]  # Get string representation
    	span = doc[start:end]  # The matched span
    	print(match_id, string_id, start, end, span.text)
    	
    #结果:
    #5420578651535644415 MY_PATTERN 2 6 母管压力低报警
    

    首先,我们用vocab初始化匹配器。匹配器必须与其操作的文档共享同一个vocab。接着调用matcher.add(),需要传入两个参数:一个是唯一的ID用来识别匹配的是哪一个模板,另一个是需要匹配的模式列表。

    matcher返回一个(match_id,start,end)三元组列表,在本例中是[(‘5420578651535644415’,2,6)],即原始文档的span:doc[0:3]。match_id是字符串ID“MY_PATTERN”的哈希值。

    默认情况下,matcher只返回匹配项,不执行任何其他操作,如合并实体或指定标签。但这些操作完全可以通过给add()的on_match参数传入回调函数,为每个模式分别定义。这对于编写完全自定义和特定于模式的逻辑非常有用。例如,你可能希望将一些模式合并到一个Token中,同时为其他模式类型添加实体标签。这样就不必为每个处理创建不同的匹配器。

    属性说明
    ORTHtoken的文本
    str
    TEXTtoken的文本
    str
    LENGTHtoken文本的长度
    int
    IS_ALPHA, IS_ASCII, IS_DIGITToken文本是否由字母、ASCII字符、数字组成
    bool
    IS_PUNCT, IS_SPACE, IS_STOPToken文本是否为标点、空格、停用词
    bool
    IS_SENT_STARTToken是否是句子开头
    bool
    LIKE_NUM, LIKE_URL, LIKE_EMAILToken文本是否类似数字、URL、email
    bool
    POS, TAG, MORPH, DEP, LEMMA, SHAPEToken的注释属性
    str
    ENT_TYPEToken的实体标签
    str
    _Token的用户扩展属性
    Dict[str, Any]
    OP运算符,用于确定匹配标记模式的频率
    str
    • 为什么不支持Token的所有属性?
      .
      spaCy并不能访问Token的所有属性,因为Matcher在Cython数据上循环的,而不是在Python对象上循环的。在matcher中,我们处理的是TokenC结构----此时我们还没有Token的实例。这意味着无法访问那些引用计算属性的属性。

    1.2 pattern语法和属性的扩展

    token patterns除了映射单值,也可以映射一个属性字典。例如:

    # 匹配 "爱猫" 或 "喜欢花"
    pattern1 = [{"TEXT": {"IN": ["爱", "喜欢"]}},
    			{"POS": "NOUN"}]
    
    # 匹配Token长度 >= 10
    pattern2 = [{"LENGTH": {">=": 10}}]
    
    属性说明
    IN属性值在列表中
    Any
    NOT_IN属性值在列表中
    Any
    ==, >=, <=, >, <属性值逻辑比较
    Union[int, float]

    1.3 正则表达式

    在某些情况下,仅仅匹配Token和Token属性还不能满足我们的需求----–例如,您可能希望几种泵,而不必为每种泵写添加新的模式。你就可以利用正则表达式:

    pattern = [{"TEXT": {"REGEX": "[水油气]泵"}}]
    

    而以下正则表达式表示可以匹配tag属性以“V”开头的Token:

    pattern = [{"TAG": {"REGEX": "^V"}}]
    

    重要提示:
    在使用REGEX操作符时,一定要记住:这些匹配操作是在单个Token上进行的!!!!

    • 全文匹配正则表达式

    如果表达式应用于多个标记,一个简单的解决方案是在doc.text上用re.finditer进行匹配,并使用匹配到的字符索引通过Doc.char_span方法来创建一个Span。但是,如果匹配的字符索引没有对应有效的Token边界,则Doc.char_span返回None。

    如:对于TEXT=‘辅机冷却水母管压力低报警。’,要在TEXT全文匹配“母管”,匹配到的字符索引为(start=5,end=6)。
    而分词是 [辅机, 冷却, 水母, 管, 压力, 低, 报警, 。],
    则doc.char_span(5,7),返回None

    • 如何将这种通过全文匹配得到的结果扩展为有效的Token序列呢?
      .
      一个简单的方法是对doc创建一个字符索引到Token索引的映射字典:

      chars_to_tokens = {}
      for token in doc:
      for i in range(token.idx, token.idx + len(token.text)):
      chars_to_tokens[i] = token.i

      #chars_to_tokens:{0: 0, 1: 0, 2: 1, 3: 1, 4: 2, 5: 2, 6: 3, 7: 4, 8: 4, 9: 5, 10: 6, 11: 6, 12: 7}

    这样就可以在给定位置查找字符,并获得该字符所属的相应Token的索引。你的Span就是doc[token_start:token_end]。但这样的结果只能是最近似的Token序列。

    span = doc.char_span(5, 7)
    if span is not None:
    	print("Found match:", span.text)
    else:
    	start_token = chars_to_tokens.get(start)
    	end_token = chars_to_tokens.get(end)
    	if start_token is not None and end_token is not None:
    		span = doc[start_token:end_token + 1]
    		print("Found closest match:", span.text)
    		
    #结果为:Found closest match: 水母管
    

    1.4 数量操作符

    在matcher中还可以使用“OP”数量限定关键字。能够用来定义要匹配的tokens序列,例如一个或多个标点符号,或指定token是否可选。注意,不能嵌套或限定数量范围,但可以通过给on_match参数指定回调函数来实现这些操作。

    OP说明
    !否定模式,只能0次匹配
    ?可选, 匹配0或1次
    +匹配1或多次
    *匹配0到多次

    1.5 使用通配符

    虽然token属性为编写特定模式提供了许多选项,但也可以使用空字典{}作为表示任何Token的通配符。主要用于知道要匹配的内容的上下文,但对其本身却不清楚。例如,假设要从数据中提取用户的用户名。只知道被表示为“User name:{username}”。用户名本身可以包含任何字符,但不能包含空格,因此只将其看作一个Token。

    [{"ORTH": "User"}, {"ORTH": "name"}, {"ORTH": ":"}, {}]
    

    1.6 patterns的验证和调试

    matcher = Matcher(nlp.vocab, validate=True)
    
    展开全文
  • 接口对接实质:1、登录成功php生成token储存到数据库表中,2、返回到前端页面储存cookie的值‘token’3、axios提交头部信息,头部信息的值用token4、后台PHP接收头部信息,接收到了获取用户id在做登录页面的时候后台...

    接口对接实质:

    1、登录成功php生成token储存到数据库表中,

    2、返回到前端页面储存cookie的值‘token’

    3、axios提交头部信息,头部信息的值用token

    4、后台PHP接收头部信息,接收到了获取用户id

    在做登录页面的时候后台会返回一个token的值,此时前端拿到token值之后要储存在本地存储中,之后再第一时间拿到的值中做全局配置,具体代码如下:

    var storage=window.localStorage;//在react组件之外定义一个变量为本地存储//请求接口的方法 userOnLine=()=>{

    axios

    .post("/safemgmt/api/admin/login",{

    username:this.state.userName,

    password:this.state.password,

    })

    .then(res=>{

    if(res.data.code==="0"){

    window.location.href="#/admin/home"//点击之后跳转到的组件 }

    storage=res.data.result.token;//这块是从后台获取到的token值赋值给storage //--------------------下面这块获取全局缓存然后全局加入token参数------------------------ axios.interceptors.request.use(function (config) {

    config.withCredentials = true

    config.headers = {

    token:storage

    }

    return config;

    }, function (error) {

    return Promise.reject(error);

    })

    })

    }

    }

    匹配完之后,如果请求后台的格式不统一(比如有的时候可以是json格式有的时候是form格式),这种情况下可能会出现一个问题,form格式的请求后台发现参数由冒号变成了等号,(axios传给后台多了一个等号)如果在post请求中加入headers,发现不起作用,因为上面全局headers覆盖掉了。(如下)

    //错误代码import axios from 'axios';

    let baseURL='http://192.168.1.1:8000/user/login';

    axios.post(

    baseURL,

    qs.stringify({id:41}), //qs.stringify是将json转化为string格式 {headers: {'Content-Type': 'application/x-www-form-urlencoded'}}

    //这种格式的是form表单格式,).then(result => {

    // 代码}

    正确代码:去掉 qs.stringify和headers,并且和后台协调,让后台把请求格式变为统一:如都统一为json格式

    //正确代码import axios from 'axios';

    let baseURL='http://192.168.1.1:8000/user/login';

    axios.post(baseURL,{id:41}, //去掉qs.stringify).then(result => {

    // 代码}

    PS:本人只是一枚废柴小码农,在实战中积累经验,只是将所踩过的坑分享出来给遇到同样坑的小伙伴们提个醒,可能会少走些弯路。如果能帮助到你解决实际问题,我将更加坚定分享的初衷:一起成长。 目前只在知乎上和简书上更新文章,准备在这两个地方持续更新文章,您的关注对我可能是莫大的鼓励。

    简书用户名:废柴码农

    微博用户名:有温度的壁纸

    哈哈,交个朋友啦~

    展开全文
  • 常见问题(1)问题:/etc/passwd, /etc/shadow文件被锁住,允许修改。lsattr /etc/passwd----i-------- /etc/passwdlsattr /etc/shadow----i-------- /etc/shadow[root@shanxi Desktop]# passwd tomChanging ...
  • 生成 Token

    2021-03-09 02:05:46
    Token,也称动态密钥,是 app 用户在加入频道或登录服务系统时采用的一种鉴权方式。本文展示如何使用 Agora 提供的代码在服务端生成 Token。根据你使用的 Agora SDK,需要生成不同的 Token:RTC Token:如果你使用的...
  • 风傲天p.ID=input....Scans the next token of the input as an int.An invocation of this method of the form nextInt() behaves in exactly the same way as the invocation nextInt(radix), where radix is...
  • 生成Token

    2021-02-09 13:01:52
    生成 RTM TokenRTM Token 是 app 用户在登录 RTM 系统时采用的一种鉴权方式。RTM Token 在你的业务服务端生成。用户登录 RTM 系统时,客户端需要向服务端申请 RTM Token;服务器生成 RTM Token 后,再将其传给客户端...
  • Web项目中经常会用token来进行用户的访问验证,那么在获得token之后,如果有很多地方需要根据token获得对应的用户信息,你会怎么获取? 本文给大家提供N种方式,对照一下,看看你的项目中所使用的方式属于哪个Level...
  • 写这一篇文章的来源是因为某一天的我被面试官提问:让你设计一个登录页面,你会如何设计?我当时的脑子只有???...我还没领悟到面试官想让我回答的是Token。。。。参考链接:https://juejin.i...
  • 对于初学者来说,可能很难会理解Token,经常会在互联网上看到出售各种渠道的token,那么token到底是什么?其原理又是什么?首先和Token相对应的有一个Session, what?怎么又懵逼了?Session又是什么?当我们理解完Session的...
  • 过滤器验证Token

    2021-03-08 20:12:46
    RedisUtils.exists(TOKEN_SHOP +tokenId)){throw new RuntimeException("匹配异常"); } String redisToken= RedisUtils.get(TOKEN_SHOP +tokenId);if (!Objects.equals(token, redisToken) && !("-1".equals(redis...
  • JWT之token机制与双token详解

    千次阅读 2021-01-13 21:12:38
    token机制 何为tokentoken即为令牌,是服务器生成的一串字符串,作为客户端向服务器进行请求的“通行证”。在客户端进行初次登陆后由服务器返回,之后的每次请求只需要携带token进行请求即可,而无需携带密码等...
  • session占用空间,但是可以管理过期时间,token管理部了过期时间,但是占用空间.sessionId失效问题和token内包含。session基于cookie,app请求并没有cookie 。token更加安全(每次请求都需要带上)。开始正文了......
  • springmvc跨域+token验证

    2021-03-05 13:11:57
    1)app后台跨域设置2)拦截器中设置http报文header中token3)token的生成实现====================================================================================================1,app后台跨域的设置1.1 ...
  • 客户端模式验证token

    2021-05-26 11:45:28
    客户端模式验证token (A)客户端向认证服务器进行身份认证,并要求一个访问令牌。  向服务器发送请求,附带信息进行身份校验,校验通过后返回一个令牌。 (B)认证服务器确认无误后,向客户端提供访问令牌。 获取...
  • 1.场景还原可能还有很多小伙伴对token概念朦朦胧胧,今天笔者以项目中的用户登录的token验证需求跟大家讲讲其中的来龙去脉,希望能够理清大伙的思路。2.需求分析这个需求可能早已是老生常谈,但我觉得它永远也不会...
  • 框架介绍 Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、分布式Session会话、单点登录、OAuth2.0 等一系列权限相关问题。...新增:新增SaRouter.stop()函数,用于一次性跳出匹配
  • Postman接口之间参数化Token. 很多地方,有专门的API接口获取Token,然后其他的API在访问服务的时候使用​该Token。​ 1、POST 请求获取Token 比如下面这样子的一个POST请求,专门去获取Token,即返回的Json内容里面...
  • PHP Token(令牌)设计

    2021-04-10 14:34:34
    请大家看代码,感觉哪里有合理的地方,还请赐教!谢谢.加密我是找的网上的一个方法,稍作了一下修改.GEncrypt.inc.php:class GEncrypt extends GSuperclass {protected static function keyED($txt,$encrypt_key){$...
  • //通过String对象的indexOf()来检查这个cookie是否存在,存在就为 -1 if (c_start != -1) { c_start = c_start + c_name.length + 1; //最后这个+1其实就是表示"="号啦,这样就获取到了cookie值的开始位置 c_end ...
  • 展开全部Struts的Token(令牌)机制能够很好的解决表单重复提交的问题,62616964757a686964616fe4b893e5b19e31333332643862基本原理是:服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中...
  • // 当token验证出现异常返回错误信息,token不匹配 responseData=ResponseData.customerError(); } } if(responseData!=null) {//如果有错误信息 httpServletResponse.getWriter().write(JSON....
  • spring-security 基于session实现 当用户登录后会在session中存储认证信息 当用户访问接口时会从session中取出认证...当访问需要认证访问的接口时,根据token找到认证信息,将认证信息塞到SecurityContext中,Securi
  • 鉴于以上弊端进行思考 ①:如果token遵从一定规律,使用对称加密算法来加密用户id生成token,服务器端只要解密该token,就能知道用户id了,需要额外的开销。但是,如果对称加密算法泄露了,别人也可以伪造token了...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 82,306
精华内容 32,922
关键字:

token不匹配