精华内容
下载资源
问答
  • 一次segfault错误的排查过程

    万次阅读 多人点赞 2015-04-24 14:58:12
    一次segfault错误的排查过程 正常运行了几年的程序忽然崩溃了,由于机器没有设置CORE文件,无法从CORE中取得错误信息,程序运行在centOS 7上, 本来对centOS用的也是不熟,只能边查资料边查问题。 首先、我需要...

                                                                                                        一次segfault错误的排查过程

    正常运行了几年的程序忽然崩溃了,由于机器没有设置CORE文件,无法从CORE中取得错误信息,程序运行在centOS 7上, 本来对centOS用的也是不熟,只能边查资料边查问题。

    首先、我需要确认程序是否真的崩溃了,还是别人误操作关闭了。如果程序真的崩溃了,会在系统中留下痕迹,我查了一下,在messages文件中发现了一条信息:

    xxxxx.o[2374]: segfault at7f0ed0bfbf70 ip 00007f0edd646fe7 sp 00007f0ed3603978 error 4 inlibc-2.17.so[7f0edd514000+1b6000]

    由上面信息看出,系统确实是崩溃了,发生了段错误。

    查看messages需要root权限,用命令:cat /var/log/messages 就可以了,还有一个命令dmesg也可以查到上面的信息。

    从上面的信息,我们可以得到以下信息:

    1、从libc-2.17.so[7f0edd514000+1b6000]可以看出错误发生在libc上,libc在此程序中映射的内存基址为7f0edd514000,这个信息是个坏消息,这个so上的东西太多了;

    2、segfault at和error 4这两条信息可以得出是内存读出错,4的意义如下,可以对照参考:

    bit2:值为1表示是用户态程序内存访问越界,值为0表示是内核态程序内存访问越界
    bit1:
    值为1表示是写操作导致内存访问越界,值为0表示是读操作导致内存访问越界
    bit0:
    值为1表示没有足够的权限访问非法地址的内容,值为0表示访问的非法地址根本没有对应的页面,也就是无效地址

    4正好为用户态内存读操作访问出界。

    3、7f0ed0bfbf70,00007f0edd646fe7,00007f0ed3603978这三个值:第一个值为出错的地址,用处不大;第二个值为发生错误时指令的地址,这个值在有些错误时是错误的,下面会讲一下,第三个值为堆栈指针。

    除了以上信息,就是六七万行的代码。感觉没有太大的指望。

    C++段错误就几类,读写错误,这个主要是参数没有控制好,这种错误比较常见,我们经常把NULL指针、未初始化或非法值的指针传递给函数,从而引出此错误;指令地址出错,这类错误主要是由虚函数,回调函数引起,最常出现的是虚函数,由于虚函数保存在类变量中,如果不小心用了非安全函数,就可能把虚数指针覆盖掉,从而影响出现错误。但指令地址出错的情况相对参数出错来讲还是要少很多的,因为用到此功能的还是需要一定的水平的,不容易犯一些低级错误。

    从上面分析的第二点来看,基本上属于读写错误,但从六七万行代码找出问题,可能性不大,只能缩小范围,我决定从上面提到的三点,找到出错的函数,然后再从代码中找出所有出错函数调用的地方来定位问题。由于错误指出出错的组件为libc,而且基本上是参数出现,所以发现错误的指令地址应该是可信的,我们可以根据指令地址查出是哪个函数。指令地址为:00007f0edd646fe7 ,libc指令的基地址为:7f0edd514000,可以根据这两个值计算一下该指令的相对地址为132FE7,下面我们需要找到相对代码段地址为132FE7的地方为什么函数。

    开始我想得到反汇编代码,但这个组件代码太多,看不到头,于是我找了个取巧的办法,查看导出函数和基地址,结果所以还是很多,我就用132和133进行了一下过滤,得出以下信息

    [root@localhostlib64]# objdump -tT libc-2.17.so | grep 132

    000000000008284fl     F .text  000000000000001b              _L_unlock_1325

    0000000000082ebfl     F .text  000000000000001c              _L_lock_11322

    000000000010b952l     F .text  000000000000001b              _L_unlock_132

    000000000010ce62l     F .text  000000000000001b              _L_unlock_132

    00000000001132e0l     F .text  00000000000001cf              ruserok2_sa

    00000000000f1320l     F .text  00000000000001ce              __ecvt_r

    00000000000bf370l     F .text  0000000000000132              __statfs_link_max

    0000000000132080l     F .text  0000000000000068              __nss_gshadow_lookup

    0000000000132f50l     F .text  0000000000000fd9              __strncmp_sse42

    00000000001320f0l     F .text  00000000000000a5              __strchr_sse42

    0000000000132020l     F .text  000000000000005e              __nss_aliases_lookup

    00000000001321a0l     F .text  0000000000000da9              __strcmp_sse42

    00000000001153b0g     F .text  0000000000000132              setnetgrent

    00000000000f1320g     F .text  00000000000001ce              ecvt_r

    0000000000112b50g     F .text  0000000000000132              ether_ntohost

    00000000000f1320g    DF .text  00000000000001ce  GLIBC_2.2.5 ecvt_r

    0000000000112b50g    DF .text  0000000000000132  GLIBC_2.2.5 ether_ntohost

    00000000001153b0g    DF .text  0000000000000132  GLIBC_2.2.5 setnetgrent

    [root@localhostlib64]# objdump -tT libc-2.17.so | grep 133

    000000000006e3cal     F .text  000000000000001b              _L_unlock_133

    0000000000075055l     F .text  0000000000000018              _L_unlock_133

    000000000008286al     F .text  000000000000001f              _L_unlock_1335

    000000000008305al     F .text  000000000000001b              _L_lock_13385

    0000000000133f30l     F .text  000000000000019a              __strrchr_sse42

    注意,我标红的部分,132f50和132fe7很接近,很大可能是这个函数出现,而且又是读地址非法,这个函数有可能会出错这个问题,__strncmp_sse42这个函数是被strncmp调用的,看到这个函数基本上可以确定应该是这个函数惹得祸。

    我又写了两行代码确认了一下

    strncmp(0,“1234”, 5);

    strncmp(“1234”,0, 5);

    分别编绎成两个应用运行,出错,messages中的错误信息如下:

    Apr23 01:38:03 localhost kernel: a.out[3254]: segfault at 0 ip 00007f58a5386f7c sp00007fffb3de5d18 error 4 in libc-2.17.so[7f58a5254000+1b6000]

    Apr23 01:39:34 localhost kernel: a.out[3267]: segfault at 0 ip 00007f8bb1908f80 sp00007fff61695408 error 4 in libc-2.17.so[7f8bb17d6000+1b6000]

    计算了一下,出错的相对地址为:132F80和132F7C,和我们遇到的错误地址很接近,于是,把__strncmp_sse42的汇编代码打印了部分如下:

    Dump of assemblercode for function __strncmp_sse42:

       0x00007ffff732ef50 <+0>: test   %rdx,%rdx

       0x00007ffff732ef53 <+3>: je     0x7ffff732ff14<__strncmp_sse42+4036>

       0x00007ffff732ef59 <+9>: cmp    $0x1,%rdx

       0x00007ffff732ef5d <+13>: je     0x7ffff732ff20<__strncmp_sse42+4048>

       0x00007ffff732ef63 <+19>: mov    %rdx,%r11

       0x00007ffff732ef66 <+22>: mov    %esi,%ecx

       0x00007ffff732ef68 <+24>: mov    %edi,%eax

       0x00007ffff732ef6a <+26>: and    $0x3f,%rcx

       0x00007ffff732ef6e <+30>: and    $0x3f,%rax

       0x00007ffff732ef72 <+34>: cmp    $0x30,%ecx

       0x00007ffff732ef75 <+37>: ja     0x7ffff732efc0 <__strncmp_sse42+112>

       0x00007ffff732ef77 <+39>: cmp    $0x30,%eax

       0x00007ffff732ef7a <+42>: ja     0x7ffff732efc0 <__strncmp_sse42+112>

               0x00007ffff732ef7c <+44>: movdqu(%rdi),%xmm1

               0x00007ffff732ef80 <+48>:movdqu (%rsi),%xmm2

       0x00007ffff732ef84 <+52>: pxor   %xmm0,%xmm0

       0x00007ffff732ef88 <+56>: pcmpeqb%xmm1,%xmm0

       0x00007ffff732ef8c <+60>: pcmpeqb%xmm2,%xmm1

       0x00007ffff732ef90 <+64>: psubb  %xmm0,%xmm1

       0x00007ffff732ef94 <+68>: pmovmskb%xmm1,%edx

       0x00007ffff732ef98 <+72>: sub    $0xffff,%edx

       0x00007ffff732ef9e <+78>: jne    0x7ffff732ff00 <__strncmp_sse42+4016>

       0x00007ffff732efa4 <+84>: sub    $0x10,%r11

       0x00007ffff732efa8 <+88>: jbe    0x7ffff732ff14 <__strncmp_sse42+4036>

       0x00007ffff732efae <+94>: add    $0x10,%rsi

       0x00007ffff732efb2 <+98>: add    $0x10,%rdi

       0x00007ffff732efb6 <+102>: nopw   %cs:0x0(%rax,%rax,1)

       0x00007ffff732efc0 <+112>: and    $0xfffffffffffffff0,%rsi

       0x00007ffff732efc4 <+116>: and    $0xfffffffffffffff0,%rdi

       0x00007ffff732efc8 <+120>: mov    $0xffff,%edx

       0x00007ffff732efcd <+125>: xor    %r8d,%r8d

       0x00007ffff732efd0 <+128>: and    $0xf,%ecx

       0x00007ffff732efd3 <+131>: and    $0xf,%eax

       0x00007ffff732efd6 <+134>: pxor   %xmm0,%xmm0

       0x00007ffff732efda <+138>: cmp    %eax,%ecx

       0x00007ffff732efdc <+140>: je     0x7ffff732f010 <__strncmp_sse42+192>

       0x00007ffff732efde <+142>: ja     0x7ffff732efe7 <__strncmp_sse42+151>

       0x00007ffff732efe0 <+144>: mov    %edx,%r8d

       0x00007ffff732efe3 <+147>: xchg   %eax,%ecx

       0x00007ffff732efe4 <+148>: xchg   %rsi,%rdi

           0x00007ffff732efe7<+151>: movdqa (%rdi),%xmm2

           0x00007ffff732efeb <+155>: movdqa(%rsi),%xmm1

       0x00007ffff732efef <+159>: lea    0xf(%rax),%r9

       0x00007ffff732eff3 <+163>: sub    %rcx,%r9

       0x00007ffff732eff6 <+166>: lea    0x4d4c3(%rip),%r10

    红色部分为我自己写的测试程序出错的地址,

    绿色部分为所要查的程序出错的地址,从这部分可以看到,出错的参数为strncmp的第二个参数。

    四、多线程

    由于用UE对源代码进行查找,还好只有335处调用,对第二个参数为常量和不被调用的代码再排除,只有20多处可疑。

    现在需要再分析一下,第二个参数读非法,那么变量如果在栈中话,需要访问到栈外的空间,这样,如果是第二个参数为栈变量的话,第三个参数值应该会很大。如果在堆变量,情况就比较复杂了,由于第三个参数值都很少,最大的为128,所以又排除了栈变量出错的可能性,从上面的数据来看,出错的地址比栈小了很多,也证明了不是栈变量出错。最后只剩下8处,我对这8处仔细查了一下,感觉都没有问题,参数控制,变量长度,控制得都没有问题。

    如果代码上没有问题,那么问题就比较难查了,我再进行分析一下,函数用户和变量上表面上没有问题,那么就可能出现几种可能:变量指针覆盖,内存移动。变量指针覆盖在实际中出现的概率比较大,往往使用了strcpy和memcpy之类的函数,没有进行边界值检查,把一些指针的值覆盖了,对于这种情况,我对代码进行了检查,可能性很小,而且上面给出的出错的参数值不像随意覆盖的值。如果这种情况可能性很小,对于一个变量只在单个线程被访问的情况出错的可能性也很小,于是,我又排除了6处。

    剩下的两处代码在一起的,参数为一个类的内部变量,这个类可能会被多个线程访问,所以此此出现的可能性很大,我仔细看了一下,边界检查也做了,线程互斥访问控制也进行了处理,没有明显的问题,还好用到这个变量的只有一个文件,我对整个文件对这个变量进行搜索,发现了一个函数realloc。感觉可能是这个函数导致的问题,此变量存放的是一个数组,数组会不断增长,当空间不够时,就用realloc再重申请,看到这个函数后,心中一阵激动,感觉问题就在这儿,我把这几段代码仔细看了几遍,访问控制,锁住修改,动态申请,最终发现了一个问题,在使用realloc时,临界区没有保护好。

    事情似乎比较明朗了,在一个线程对这个变量指针进行访问时(即用strnmcp处),线程切换到另一个线程对这个变量指针进行了realloc操作,realloc重新申请了新的空间,把老的数据移动的新的空间,然后把旧空间处的内存彻底释放掉了,然后又切换到原先的线程,执行strncmp函数,这个函数用的指针参数还是旧的空间地址,而旧的空间已经被释放了,所以出现了访问非法的错误。

    我查了一下程序日志,发现出现错误时,正好需要重新申请空间,应该是这个问题导致的。

    这种情况发生的概率很低,很难重现,因为realloc和strncmp函数执行时间都很短,而且realloc的概率不高,所以运行了4,5年才出现错误,但是总体来讲,还是比较幸运的,如果旧的空间没有被释放,那么接下来的各种操作全在旧的空间上,可能会导致更大的损失。

     

    ———赵海杰

    20150424

    展开全文
  • 一个错误,第一次犯情有可原,但是第二次再犯就是认知、反省不到位,不要重复错误,白白浪费付出代价学到的教训。 今天和政治汪老师聊天的时候,中间我曾经不是非常礼貌的打断了3次她的讲话,虽然个人只是很普通的...

    一个错误,第一次犯情有可原,但是第二次再犯就是认知、反省不到位,不要重复错误,白白浪费付出代价学到的教训。

    今天和政治汪老师聊天的时候,中间我曾经不是非常礼貌的打断了3次她的讲话,虽然两个人只是很普通的聊天,但是我这样做是不够礼貌的,所以我要承认这是个错误。中间我及时发现了这个问题,后面的对话我都全部耐心听她说完才表达了自己的观点。

    总之,以后在面对面交流过程中,不能打断对方的话。

    展开全文
  • 答曰,前端会莫名其妙发送个一模一样的请求过来导致数据会增加条,让前端查过找不到原因,所以就提升隔离级别。 顿时,内心犹如万只草泥马奔腾而过,本着码农应有的探索精神(闲的蛋疼),我决定探究竟。 ...

    FBIWarning

    大神勿进,写的啰嗦且浅显,免得浪费您的时间。

     

    前言

    维护同事的一段代码,发现他使用的事务隔离级别为SERIALIZABLE,读了代码,发现完全没必要,遂问之。
    答曰,前端会莫名其妙发送两个一模一样的请求过来导致数据会增加两条,让前端查过找不到原因,所以就提升隔离级别。
    顿时,内心犹如一万只草泥马奔腾而过,本着码农应有的探索精神(闲的蛋疼),我决定一探究竟。

    正题

    异常

    would dispatch back to the current handler URL [/xxx.html] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)

    导致“一次请求多次调用”这个现象可能有各种各样意想不到的原因,但如果控制台打印了类似上面的错误信息,那基本没跑了,就是咱今天要讲的这个问题导致的。

     

    复现问题

    这个问题咋一听很诡异,那先复现问题再说吧。于是打断点,Debug。好家伙!果然走了两次!

    秋多麻袋!但前端并没有发送两次请求呀,所以这问题基本上锁定了是后端哪儿个地方没弄对导致的。

     

    伪代码说明

    @Controller
    @RequestMapping("api/xxx")
    public class XXXController {
    
        @Autowired
        XXXService xxxService;
    
        @RequestMapping(value = "xxx", method = RequestMethod.GET)
        public XXX doXXX(Integer xxxId, ...) throws Exception {
            ...
    
            XXX xxx = xxxService.doXXX(xxxId, ...);
            
            ...
    
            return xxx;
        }
    }
    
    
    public class XXXServiceImpl implements XXXService {
    
        @Autowired
        XXXMapper xxxMapper;
    
        @Transactional(rollbackFor = Exception.class, isolation = Isolation.SERIALIZABLE)
        public XXX doXXX(Integer xxxId, ...) throws Exception {
            ...
            
            XXXDbo xxxDbo = xxxMapper.selectById(xxxId);
            if (xxxDbo != null) {
                throw new XXXException("data already exists");
            }
    
            ...
        }
    }

     

    单步调试

    不知道各位大神看了如上代码之后能看出问题吗?

    emmmm...好吧,开始说正题。

    这个就要从盘古开天辟地之时说起...咳咳,给我回来!

    这个就要从@Controller和@RestController注解说起,一般(我个人)在开发API的时候都使用后者,开发未前后端分离的管理系统的时候都用前者,为什么呢?

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {
        ...
    }

    看到没,RestController相当于Controller和ResponseBody注解的组合,换言之,两者之间不外乎差了一个ResponseBody,那这个ResponseBody小哥哥又是干什么用的呢?

    这个就要稍微涉及SpringMVC的处理流程了,而这个话题怎么都绕不开DispatcherServlet,所有的请求都得经过他的手进行“分发”,才会最终走到我们的Controller里面。

    好了,不废话,直接贴代码,看看关键的调用链吧!

    //DispatcherServlet.java
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ...
    
        // Actually invoke the handler.
        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    
        ...
    
        //为什么要贴这句代码?留个伏笔有木有[阴笑]
        applyDefaultViewName(processedRequest, mv);
    
        ...
    }
    
    //AbstractHandlerMethodAdapter.java
    @Override
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    			throws Exception {
        return handleInternal(request, response, (HandlerMethod) handler);
    }
    
    //RequestMappingHandlerAdapter.java
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
    			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ...
        
        mav = invokeHandlerMethod(request, response, handlerMethod);
    
        ...
    }
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ...
    
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
    
        ...
    
        //伏笔+1
        return getModelAndView(mavContainer, modelFactory, webRequest);
    
        //还有finally块
        ...
    }
    
    //ServletInvocableHandlerMethod.java
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer  mavContainer, Object... providedArgs) throws Exception {
        //这段接着走,会最终走到我们的Controller里面
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        
        ...
    
        //这段就是开始处理我们Controller里面的返回值
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    
        ...
    }

    最后那个returnValue就是我们Controller里面返回的xxx,也就说走到这儿,我们的业务代码实际上就执行完了,而接下来才是重头戏,接着看代码!

    //HandlerMethodReturnValueHandlerComposite.java
    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
    			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws  Exception {
        HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    
        ...
    }
    private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
        ...
    
        //这里的returnValueHandlers是spring给我们提供的针对各种类型返回数据的处理器
        //如果没有业务需要的,也可以实现其接口然后注入即可使用自定义的处理器
        for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
            ...
    
            //终于要说到ResponseBody了
            if (handler.supportsReturnType(returnType)) {
                return handler;
            }
        }
        return null;
    }

    看到handler.supportsReturnType(returnType)这句代码了吗,这就是在判断返回的数据类型需要由哪个处理器来进行处理,Spring预设了有好几个,我们重点关注其中两个:RequestResponseBodyMethodProcessor和ModelAttributeMethodProcessor,为什么呢?

    我们分别来看他们的supportsReturnType方法

    //RequestResponseBodyMethodProcessor.java
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));
    }
    
    //ModelAttributeMethodProcessor.java
    @Override
    public boolean supportsReturnType(MethodParameter parameter) {
        return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
    				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
    }

    很激动有木有,看了辣么久的代码,总算见到了我们可爱又帅气的ResponseBody小哥哥了,这句代码意思也很明显了吧:

    只要所在的class或者所在的method添加了ResponseBody注解,那么就用我来作为处理器!

    而下面那个则是:

    没有相应的注解,且不是常规的数据类型,那么就选我!

    (其实这里还涉及先后顺序的问题,就不展开说了,你懂得[滑稽])

    如果到这里就结束了,你可能要给差评了,这扯了一大堆,其实就说了一点,有没有ResponseBody其实就是对应的返回数据Processor不同而已,为什么这个不同会导致再发起一次请求还没说呀!别急,接下来就要说到。(其实急的很是吧[吐舌])

    细心点的同学可能看到前面RequestMappingHandlerAdapter中的getModelAndView(mavContainer, modelFactory, webRequest)方法,看看里面是怎么来构造的吧。

    private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
    			ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
        ...
    
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
        if (!mavContainer.isViewReference()) {
    	mav.setView((View) mavContainer.getView());
        }
    
        ...
    }

    很好,很强大,直接new出来的。紧接着就会对ModelAndView设置View属性(如/login.jsp这种,很熟悉吧?),很不幸,经过咱ModelAttributeMethodProcessor大哥处理的后的,这个属性是null。那么null又会导致什么问题呢?

    又要回到我们熟悉的DispatcherServlet中,看到applyDefaultViewName(processedRequest, mv)方法没

    //DispatcherServlet.java
    private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
        if (mv != null && !mv.hasView()) {
            mv.setViewName(getDefaultViewName(request));
        }
    }
    protected String getDefaultViewName(HttpServletRequest request) throws Exception {
        return this.viewNameTranslator.getViewName(request);
    }
    
    //DefaultRequestToViewNameTranslator.java
    public String getViewName(HttpServletRequest request) {
    		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
        return (this.prefix + transformPath(lookupPath) + this.suffix);
    }

    看到没,他给咱的ModelAndView设置了ViewName,而且值就是我们请求的路径,所以后面处理的时候当然就会再调用一次啦!因为他把原来的接口当做处理页面来进行调用了。(这句描述可能不准确,海涵)

     

    结语

    罗里吧嗦讲了一大堆,不知道各位看官看明白没有?其实就很简单一事,只是为了搞清来龙去脉所以才贴了这么多代码的。

    最后来一句可能不是很准确的结论吧:

    没有加ResponseBody会导致返回对象的Processor使用ModelAttributeMethodProcessor,而他会构造一个ModelAndView对象,且对象会最终设置调用接口的路径为view属性。

    加了ResponseBody就会使用RequestResponseBodyMethodProcessor,他不会构造ModelAndView(另外的逻辑,不展开了),也就不会有这个问题。

     

    最后再抛个问题,知道为什么同事通过提升事务的隔离级别也能解决一次请求会增加两条数据的问题吗?且待下回(再次闲的蛋疼时)分解[滑稽]

    展开全文
  • 类似的功能还得写两次。上网查了下,果然有人在喷微信文档多次误导第三方sdk写错了:)  上次接到个任务,PC网站要支持微信第三方登陆功能。一直以为是在服务号那边做相应的配置和处理,看的是这边的文档 。...

         最近一直在研究围绕微信平台的相关开发,其中踩到了不少坑,特来此记录一番,也方便交流。真想吐槽为什么微信弄个开放平台还要加一个公众平台呢?两边文档还不一样。类似的功能还得写两次。上网查了下,果然有人在喷微信文档多次误导第三方sdk写错了:)

         上次接到一个任务,PC网站要支持微信第三方登陆功能。一直以为是在服务号那边做相应的配置和处理,看的是这边的文档 。尝试了多次都是提示scope没有相关权限,授权地址格式如下:

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE&connect_redirect=1#wechat_redirect

    后面去分析了下第三方网站的微信授权地址,正确的格式如下:

    https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
        上面是我遇到的第一个坑,糗大了:) .微信就是比较特别,其他SNS账户叫client_id 微信这边叫appid, 还要在URL末尾搞个#wechat_redirect,刚开始也没太在意它的作用,为了保持代码的一致整洁,授权地址里面的client_id我没有去掉,这样一来,URL里面的参数个数实际上比微信需要的多一个,这样也为后面的BUG埋下了伏笔。

        微信开放平台的文档在这。整体来说比较顺利,网站在这里:爱玩儿HTML5.欢迎体验。

        PC端微信登陆做好了,接下来是微信浏览器里面的微信登陆。如果没有对PC端微信登陆和微信环境下登陆做区分,在微信环境下点击授权登陆,你将看到的是一个扫码页面,类似这样:



          在移动端扫码,这样的体验很糟糕。一些朋友想到的可能是跳过扫码的页面,直接让用户点击确认授权的页面,殊不知,PC端扫码也是微信授权流程的一部分,这个扫码并确认的过程就是获取授权码code的过程。有了之前的经验,我知道必须在公众号后台来完成授权登陆的过程。授权地址如下:

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&client_id=xxx&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
    忘记 把client_id去掉了,导致跳转后页面一片空白,也没有提示错误信息。经过多种尝试,确认appid是没问题的,redirect_uri也是合法域名下的地址。问题得不到解决,把想过的法子都尝试了,只好把第三方授权地址拷贝出来看,严格按照微信文档的GET参数顺序和个数来填写,问题得到了解决:)。

        这里主要有两个问题,一个问题是多了一个client_id,另外一个问题是URI参数的顺序问题。必须严格按照微信官方的格式来。
        最后想说以下URL里面#号参数的作用,#可以为网页位置指定标识符,作为超链接标签<a>的一个锚点。#并不是HTTP协议的内容。单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。


        

    展开全文
  • 一次HTTP返回400的错误

    千次阅读 2018-03-13 16:59:14
    今天在一次访问后台中,服务器总是返回400,通过wireshark抓包显示如下:POST/shsys-web/cc/module/v3/sub/sync?seq=37670&amp;id=0077a33dbbd6a5839713&amp;sn=15414b2300793583&amp;d=aqeGAnlrIzg=...
  • 实现网关加密后,发现一次加密请求后,紧接着的非加密GET请求,就会出现400的错误。再发一次相同的GET请求,就会正常,观察后端微服务的收到网关请求的accessLog,发现接收到的请求解析有问题: ## 400的请求 - -...
  • 维护我司之前的老代码,更换自己修改后的DLL插件后,exe加载过程中Log一直打印LoadLibrary Error,GetLastError()打印的错误码是127,查看MSDN,该错误码对应的含义是已经找到了需要查找的DLL,但是不是正确的版本。...
  • spring mvc一次提交两次请求问题

    千次阅读 2017-08-18 17:34:16
    宽为限 紧用功 工夫到 滞塞通 这里我只有一次商品添加请求,数据库里却添加了两条数据,第二条是空的,说明程序执行了两次add方法
  • 一次蓝屏的解决,错误代码IRQL_NOT_LESS_OR_EQUAL

    万次阅读 多人点赞 2019-09-21 18:10:29
    天不知为何,电脑总是正在运行时自动重启,今天忍无可忍,百度了...不设置还好,没想到设置,重启的问题是解决了,但天内电脑还蓝屏了4,而且蓝屏界面提示的终止代码都是IRQL_NOT_LESS_OR_EQUAL。看来问题...
  • 文章目录 错误描述 解决办法: 总结(这个方法会执行两次操作) 解决更新数据库出现两次的情况 解决办法 pom.xml下的build标签 错误描述   idea创建maven项目,导入依赖都是成功的,但是运行就会报找不到对应jar包的...
  • 微信退款,开始是好的,但由于业务需要实现多商户号的收款和退款, 故在商户端后台上传证书和私钥的pem文件传到oss上,退款时下载到本地使用实现支付,curl一直出现58的错误, 网上的解决方法都是路径使用绝对...
  • 我们项目前端部分分为PC端、手机端个项目,然后共用个web端,手机端已经上线,手机端中的充值支付模块也已经正常在使用;但这次PC端项目上线的时候,充值却出现了问题,支付接口返回INVALID_PARAMETER的错误;而...
  • 怎样把WORD中的错误一次性全部忽略

    万次阅读 2016-09-09 15:17:54
    1、审阅 选项----拼写和... 2、或者打开word选项----校对----将键入时检查拼写勾选项去掉,或者勾选下面的项例外项,不显示拼写和语法错误,确定。 这样就不会再检查你的拼写,不用忽略了,但是这样容易写错
  • 张动图-彻底明白TCP的三握手与四挥手

    万次阅读 多人点赞 2017-06-04 21:53:54
    通过上篇中网络模型中的IP层的介绍,我们知道网络层,可以实现个主机之间的通信。但是这并不具体,因为,真正进行通信的实体是在主机中的进程,是个主机中的个进程与另外个主机中的个进程在交换数据。...
  • 我的项目刚开始做没多久,原本一切都是正常的,最近加多了个TreeDao,然后启动的时候就报了以下错误: Field baseMapper in ...
  • 最近公司的项目中,在个功能模块的查询中本地查询没有问题,但是部署到测试服务器上面访问的时候使用Chrome访问就会提示“net::ERR_INCOMPLETE_CHUNKED_ENCODING”,用Chrome自带的工具看了下原来这个在调用ajax...
  • 自从学习微信开发就一直遇到大大小小的bug,每次的问题都是绞尽脑汁啊。...现在终于得到完全解决,给走在路上或正在路上出现问题的伙伴个走捷径的方法。 问题描述:网页授权获取微信用户信息 错误40029:不
  • 关于 eclipse启动卡死的问题(eclipse上一次没有正确关闭,导致启动的时候卡死错误解决方法),自己常用的解决方法: 方案一(推荐使用,如果没有这个文件,就使用方案二): 到\.metadata\.plugins\org.eclipse....
  • andorid:百度地图定位:第一次定位成功之后 再次定位一直返回:505 错误locType:505locType description:NetWork location failed because baidu location service check the key is unlegal,…或者:locType:61
  • 用异或来交换个变量是错误

    万次阅读 多人点赞 2010-01-09 22:43:00
    用异或来交换变量是错误的 陈硕 (giantchen_AT_gmail)Blog.csdn.net/Solstice 翻转个字符串,例如把 "12345" 变成 "54321",这是个最简单的不过的编码任务,即便是 C 语言初学者的也能毫不费力地写出类似如下...
  • 网站正常访问,但是其中个连接点击等1分钟后直接出一下错误: Anerror occurred. Sorry,the page you are looking for is currently unavailable. Pleasetry again later. Ifyou are the system administrator of ...
  • 在有些系统上下载时,可能会碰到弹出“一些网站不允许请求个文件两次”的错误提示(如下图),而导致有些文件无法正常下载,及时是换了其他下载器(如迅雷),或者换了浏览器,换了电脑还是出现同样的问题,那么就...
  • 求助错误

    2021-01-08 16:05:34
    错误,每隔一段时间就弹一次 JsonParseException: The parser encountered an invalid or unexpected character. Unity.Messenger.Json.Serialization.JsonReader.ReadJsonValue () (at Library/Package...
  • 统计学第错误和第二类错误

    万次阅读 2015-11-22 11:03:14
    统计学中存在错误错误主要是在统计学假设检验中所出现的,因此,先要了解假设检验的基本概念。 假设检验(Hypothesis Testing)是数理统计学中根据一定假设条件由样本推断总体的种方法。具体作法是:根据...
  • 使用axios发现每次调用接口都会有个请求,第个请求时OPTIONS请求,只有OPTIONS请求通过才会发送实际的请求。  为什么XMLHttpRequest的POST请求会变OPTIONS请求-XMLHttpRequest对象对HTTP请求的访问控制...
  • 内容摘要:本文描述了一次典型的域问题故障处理过程,现记录如下以供同僚们参考 背景 起因 失败状态 解决思路 Google搜索引擎 协议分析 域认证流程 抓包分析 解决 结论背景起因公司内有个独立的域分别为:domA....
  • 我很久以前就看过三次握手和四次挥手的博客,但根本没懂,直到最近为了面试再看,也是仅仅知道过程,至于面试中的为什么需要三次握手,而不是四次或两次?,网上的博客千千万,国内外的,我还是一直没有理解,知道今天看了学校...
  • 如果broker恰好在消息已经成功写入Kafka topic后,发送ack前,出了故障,生产者的重试机制就会导致这条消息被写入Kafka两次,从而导致同样的消息会被消费者消费不止一次。每个人都喜欢一个兴高采烈的给予者,但是...
  • 错误指示很明确:通信端口被占用了,导致通信无法进行。 但为什么端口被占用了呢?什么端口被占用了? 这里其实有个大前提:你的程序中有没有指定socket通信端口? 如果指定了,解决办法很简单,每次通信...
  • 最近在做web项目时遇到了个奇葩问题: 1、先遇到了这个缓存问题:系统参数对象缓存到了静态变量里,并且用个通知型任务重新加载数据库修改后的参数,之后奇葩问题便出现了,重新加载后再调用系统参数常量,有时...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,699,913
精华内容 679,965
关键字:

一次是错误两次是选择