0

0

微信开发之获取OAuth2.0网页授权认证和获取用户信息进行关联的实例详解

Y2J

Y2J

发布时间:2017-04-26 14:58:05

|

4167人浏览过

|

来源于php中文网

原创

        最近有做了关于微信公众号和自己网站用户进行用户关联授权登录的一个功能,主要是用户关注该公众号,点击会员中心,则会弹出需要关联授权的网页授权:oauth2.0网页授权,然后用户同意获取用户信息,进行用户和网站的关联,然后用户则可以使用微信进行登录。

        本次做的是一个在Java的Action层处理各个返回参数获取数据。

       一、 使用到的工具:

            1、ngrok,将你自己的本机映射到公网,这样保证可以随时测试开发;

                   1、下载ngrok,网址:www.tunnel.mobi/

                   2、将文件放到Tomcat目录下,在cmd中运行ngrok -config ngrok.cfg -subdomain xinzhi 8080

                   3、ngrok工具为在慕课网@LAOBI 看到的

            2、微信公众号测试账号,随时测试,首先保证在测试账号下没有问题后在进行公众号的移植。

       二、使用到在Java中发送一个Http请求,然后返回JSON参数,获得JSON参数,然后进行处理。

           首先,获取将公众号测试号放到properties文件中,以便我们进行调用或者更换,如:url请用https

Properties代码  收藏代码

  1. AppID = wxf00**c3dd2ebfa0  
    AppSecret = 3cb220755f****506dc35391aa5c03ec  
    url = https://xinzhi.tunnel.mobi

           这里url为我们映射到外网的地址,一会需要用到。然后需要两个工具类,该工具类作用是在Java的Action中发送http请求后获取到去返回值

            这里使用的是@柳峰,关于服务器请求的代码http://blog.csdn.net/lyq8479/article/details/9841371,启用自己有相应的改动,以适应本项目的需求:

           WeixinUtil.java 和 MyX509TrustManager.java

Java代码  收藏代码

  1. package com.zhtx.common.util;  
    import java.io.BufferedReader;  
    import java.io.InputStream;  
    import java.io.InputStreamReader;  
    import java.io.OutputStream;  
    import java.net.ConnectException;  
    import java.net.URL;  
    import javax.net.ssl.HttpsURLConnection;  
    import javax.net.ssl.SSLContext;  
    import javax.net.ssl.SSLSocketFactory;  
    import javax.net.ssl.TrustManager;  
    import org.slf4j.Logger;  
    import org.slf4j.LoggerFactory;  
    /**
     * 公众平台通用接口工具类
     * 
     * @author xinz
     * @date 2015-10-14
     */
    public class WeixinUtil {  
        private static Logger log = LoggerFactory.getLogger(WeixinUtil.class);  
        /**
         * 发起https请求并获取结果
         * 
         * @param requestUrl 请求地址
         * @param requestMethod 请求方式(GET、POST)
         * @param outputStr 提交的数据
         * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
         */
        public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {  
            StringBuffer buffer = new StringBuffer();  
            try {  
                // 创建SSLContext对象,并使用我们指定的信任管理器初始化
                TrustManager[] tm = { new MyX509TrustManager() };  
                SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");  
                sslContext.init(null, tm, new java.security.SecureRandom());  
                // 从上述SSLContext对象中得到SSLSocketFactory对象
                SSLSocketFactory ssf = sslContext.getSocketFactory();  
                URL url = new URL(requestUrl);  
                HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();  
                httpUrlConn.setSSLSocketFactory(ssf);  
                httpUrlConn.setDoOutput(true);  
                httpUrlConn.setDoInput(true);  
                httpUrlConn.setUseCaches(false);  
                // 设置请求方式(GET/POST)
                httpUrlConn.setRequestMethod(requestMethod);  
                if ("GET".equalsIgnoreCase(requestMethod))  
                    httpUrlConn.connect();  
                // 当有数据需要提交时
                if (null != outputStr) {  
                    OutputStream outputStream = httpUrlConn.getOutputStream();  
                    // 注意编码格式,防止中文乱码
                    outputStream.write(outputStr.getBytes("UTF-8"));  
                    outputStream.close();  
                }  
                // 将返回的输入流转换成字符串
                InputStream inputStream = httpUrlConn.getInputStream();  
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");  
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  
                String str = null;  
                while ((str = bufferedReader.readLine()) != null) {  
                    buffer.append(str);  
                }  
                bufferedReader.close();  
                inputStreamReader.close();  
                // 释放资源
                inputStream.close();  
                inputStream = null;  
                httpUrlConn.disconnect();  
            } catch (ConnectException ce) {  
                log.error("Weixin server connection timed out.");  
            } catch (Exception e) {  
                log.error("https request error:{}", e);  
            }  
            return buffer.toString();  
        }  
    }

 对于https请求,我们需要一个证书信任管理器,这个管理器类需要自己定义,但需要实现X509TrustManager接口,代码如下:

Java代码  收藏代码

  1. package com.zhtx.common.util;  
    import java.security.cert.CertificateException;  
    import java.security.cert.X509Certificate;  
    import javax.net.ssl.X509TrustManager;  
    /**
     * 证书信任管理器(用于https请求)
     * 
     * @author xinz
     * @date 2015-10-14
     */
    public class MyX509TrustManager implements X509TrustManager {  
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
        }  
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
        }  
        public X509Certificate[] getAcceptedIssuers() {  
            return null;  
        }  
    }

 微信返回参数的一个POJO类:

天工大模型
天工大模型

中国首个对标ChatGPT的双千亿级大语言模型

下载

Java代码  收藏代码

  1. private String  openid;  //用户的唯一标识 
    private String  nickname;//用户昵称 
    private Integer sex;// 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 
    private String  province;//用户个人资料填写的省份 
    private String  city;//普通用户个人资料填写的城市 
    private String  country;// 国家,如中国为CN 
    private String  headimgurl;  // 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。 
    private String  privilege;// 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom) 
    private String  unionid;// 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。详见:获取用户个人信息(UnionID机制) 
    private String access_token;

 授权凭证验证的类:

Java代码  收藏代码

  1. private String errcode;  
    private String errmsg;

 通过code换取网页授权access_token

Java代码  收藏代码

  1. private String access_token;  
    private String expires_in;  
    private String refresh_token;  
    private String openid;  
    private String scope;  
    private String unionid;

 关于微信头像的,获取的是一个http的url,则需要将图片下载到服务器存储,然后获得相对路径:

Java代码  收藏代码

  1. /**
         * 使用url或者http存入文件
         * @Title: fileUpload
         * @param @param fileUrl  文件url,可以是http
         * @param @param path     文件存储路径
         * @return void
         * @throws xinz
         */
        public static void fileUpload (String fileUrl,String path){  
             //读取文件
              String s1 = fileUrl;     
              java.io.InputStream is = null; //定义一个输入流。
              BufferedInputStream bis = null;//定义一个带缓冲的输入流 。 
            //写到本地 
              BufferedOutputStream bos = null; //定义一个带缓冲的输出流。
              try{   
                java.net.URL url = new java.net.URL(s1);//创建一个URL对象。
                is = url.openStream();//打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。
                bis = new java.io.BufferedInputStream(is);       
                File file = new File(path);     
                if(!file.exists()){ //测试此抽象路径名表示的文件或目录是否存在。  
                    file.createNewFile();   //创建此抽象路径名表示的文件或目录。
                }     
              bos = new BufferedOutputStream(new FileOutputStream(file));;       
              byte[] b = new byte[1024]; //创建字节数组。
              while(bis.read(b)!=-1){//输入流中的数据如果还有下一行(!=-1)将继续循环
                  bos.write(b);//将字节数组写入输出流。    
              }   
              }catch(Exception   e){       
                  System.out.println(e.toString());         
              }finally{       
                  try{       
                      bos.flush();//刷新此缓冲的输出流。 
                      bis.close(); //关闭此输入流 。 
                  }catch(Exception   e){       
                      System.out.println(e.toString());         
                  }       
              }    
        }

现在是基础工作都做完了,现在开发代码的开发,在微信开发文档中 http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html 有提到每一个步骤,然后我们按照这个步骤开发:

  • 1第一步:用户同意授权,获取code

  • 2第二步:通过code换取网页授权access_token

  • 3第三步:刷新access_token(如果需要)

  • 4第四步:拉取用户信息(需scope为 snsapi_userinfo)

  • 5附:检验授权凭证(access_token)是否有效

第一步:用户同意授权,获取code

  这里的url就是前面所准备在properties中的url了。

Java代码  收藏代码

  1. /**
         * 微信用户授权
         * @Title: wechatOauth
         * @param @param request
         * @param @param response
         * @param @param model
         * @param @return
         * @return String
         * @throws xinz
         */
        @RequestMapping("wechatOauth")  
        public String wechatOauth(HttpServletRequest request,HttpServletResponse response,Model model)  {  
            /**
             *  1 第一步:用户同意授权,获取code
             */
            //首先拿到微信公众号的AppID、AppSecret等参数
            String AppID = ZhtxHelper.getApplicationResourcesProp("sendSms","AppID");  
            String urlOpen = ZhtxHelper.getApplicationResourcesProp("sendSms","url");  
            //如果用户授权成功则跳转到此url
            String loginUrl = ""+urlOpen+"/zhtx-wap/weixin/getAccessToken";  
            //用户授权,获取code
            String url = "https://open.weixin.qq.com/connect/oauth2/authorize?"
                        + "appid="+AppID+""
                        + "&redirect_uri="+loginUrl+""
                        + "&response_type=code"
                        + "&scope=snsapi_userinfo"
                        + "&state=123#wechat_redirect";  
            //forward redirect
            return "redirect:"+url+"";   
        }

第二步:通过code换取网页授权access_token

Java代码  收藏代码

  1. /**
         * 通过code换取网页授权access_token
         * @Title: getAccessToken
         * @param @param request
         * @param @param response
         * @param @param model
         * @param @return
         * @return String
         * @throws xinz
         */
        @RequestMapping("getAccessToken")  
        public String getAccessToken(HttpServletRequest request,HttpServletResponse response,Model model) {  
            //获取到返回的参数
            try {  
                //首先拿到微信公众号的AppID、AppSecret等参数
                String AppID = ZhtxHelper.getApplicationResourcesProp("sendSms","AppID");  
                String AppSecret = ZhtxHelper.getApplicationResourcesProp("sendSms","AppSecret");  
                String code = request.getParameter("code");  
                String url = null;  
                if(code!=null){  
                    /**
                     *  2 第二步:通过code换取网页授权access_token
                     */
                    //用户授权,获取code
                    url = "https://api.weixin.qq.com/sns/oauth2/access_token?"
                            + "appid="+AppID+""
                            + "&secret="+AppSecret+""
                            + "&code="+code+""
                            + "&grant_type=authorization_code";  
                    String requestMethod = "GET";  
                    String outputStr = "";  
                    String httpRequest = WeixinUtil.httpRequest(url, requestMethod, outputStr);  
                    System.out.println("通过code换取网页授权access_token="+httpRequest);  
                    AccessTokenModel accTok = JSON.parseObject(httpRequest, AccessTokenModel.class);  
                    /**
                     *  4 第四步:拉取用户信息(需scope为 snsapi_userinfo)
                     */
                    //用户授权,获取code
                    String urlUser = "https://api.weixin.qq.com/sns/userinfo?"
                            + "access_token="+accTok.getAccess_token()+""
                            + "&openid="+accTok.getOpenid()+""
                            + "&lang=zh_CN";  
                    String httpUser = WeixinUtil.httpRequest(urlUser, requestMethod, outputStr);  
                    System.out.println("拉取用户信息=="+httpUser);  
                    WechatUser wechatUser = JSON.parseObject(httpUser, WechatUser.class);  
                    wechatUser.setAccess_token(accTok.getAccess_token());  
                    /**
                     *  5 附:检验授权凭证(access_token)是否有效
                     */
                    WechatMsg checkAccessToken = checkAccessToken(wechatUser.getAccess_token(), wechatUser.getOpenid());  
                    if(checkAccessToken.getErrcode().equals("0")){  
                        CurrentSession.setAttribute("wechatUser", wechatUser);  
                        WechatUser wechatU = new WechatUser();  
                        wechatU.setOpenid(wechatUser.getOpenid());  
                        List<WechatUser> findWechatUser = wechatUserService.findWechatUser(wechatU);  
                        if(findWechatUser.size()>0){  
                            UserRegister userRegister = userService.findUserByOpenid(wechatUser.getOpenid());  
                            CurrentSession.setAttribute("user", userRegister);  
                            return "redirect:/user/userCenter";  
                        }else{  
                            return "/jsp/wechat/wechatregister";   
                        }  
                    }else{  
                        //如果access_token失效,则再次进行调用,并存储access_token值,access_token有效期为2个小时
                        this.wechatOauth(request, response, model);   
                    }  
                }  
            } catch (Exception e) {  
                System.out.println("===拉取用户出错===");  
                e.printStackTrace();  
            }  
            //forward redirect
            return "/jsp/wechat/wechatregister";   
        }

第四步:拉取用户,和自己网站用户绑定

Java代码  收藏代码

  1. /**
         * 微信关联用户
         * @Title: saveWechatUser
         * @param @param mobilePhone
         * @param @param password
         * @param @param validataCode
         * @param @return
         * @return String
         * @throws xinz
         */
        @RequestMapping("saveWechatUser")  
        public String saveWechatUser(HttpServletResponse response,String mobilePhone,String password,String validataCode){  
            //使用手机号来判断该手机是否在注册
            UserRegister userRegister = userService.findUserByPhone(mobilePhone);  
            WechatUser wechatUser = (WechatUser)CurrentSession.getAttribute("wechatUser");  
            WechatUser wechatU = new WechatUser();  
            wechatU.setOpenid(wechatUser.getOpenid());  
            List<WechatUser> findWechatUser = wechatUserService.findWechatUser(wechatU);  
            if(findWechatUser.size()>0 && userRegister.getOpenid()!=null){  
                CurrentSession.setAttribute("user", userRegister);  
                return "redirect:/user/userCenter";  
            }else{  
                //如果没有注册,开始注册
                if(userRegister==null){  
                    Result<UserRegister> saveUserInfoApp = userRegisterService.saveUserInfoApp(mobilePhone, password, validataCode,wechatUser);  
                    if(saveUserInfoApp.getState()==1){  
                        //进行微信和用户的关联
                        wechatUserService.saveWechatUser(wechatUser);  
                        CurrentSession.setAttribute("user", userRegister);  
                        return "redirect:/user/userCenter";  
                    }  
                }else if(userRegister.getOpenid()==null || userRegister.getOpenid().equals("")){  
                //否则,查询出用户信息,放入session中,关联微信,跳转到用户中心    
                    UserRegister userReg = new UserRegister();  
                    userReg.setId(userRegister.getId());  
                    //存入微信openid
                    userReg.setOpenid(wechatUser.getOpenid());  
                    userService.upUser(userReg);  
                    UserInfo user = new UserInfo();  
                    //存入微信头像
                    //图片类型
                    String dateStr =DateUtil.format(DateUtil.getCurrentDate(), "yyyyMMdd")  + "/";  
                    //图片类型
                    String imgType = "JPG";  
                    //微信头像名称
                    String app2DBarNameAndType = UuidUtil.getUUID()+"."+imgType;  
                    //微信头像路径
                    String path =   ZhtxHelper.getApplicationResourcesProp("application","app.img.projectpath")+ SysConstant.GOODS2DBARPATH + dateStr;  
                    File file1 = new File(path);  
                    file1.mkdirs();  
                    //图片全路径
                    String imgUrl = SysConstant.GOODS2DBARPATH + dateStr+app2DBarNameAndType;  
                    FileUtil.fileUpload(wechatUser.getHeadimgurl(), path);  
                    user.setRegisterId(userRegister.getId());  
                    user.setImageUrl(imgUrl);  
                    userInfoService.updateUserInfo(user);  
                    //存入微信用户
                    wechatUserService.saveWechatUser(wechatUser);  
                    UserRegister userW = userService.findUserByPhone(mobilePhone);  
                    CurrentSession.setAttribute("user", userW);  
                    return "redirect:/user/userCenter";  
                }else{  
                    CurrentSession.setAttribute("user", userRegister);  
                    return "redirect:/user/userCenter";  
                }  
            }  
            return "redirect:/user/userCenter";  
        }

附:检验授权凭证(access_token)是否有效

Java代码  收藏代码

  1. /**
         * 检验授权凭证(access_token)是否有效
         * @Title: checkAccessToken
         * @param @param access_token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 
         * @param @param openid 用户的唯一标识 
         * @return WechatMsg   返回消息实体
         * @throws xinz
         */
        public static WechatMsg checkAccessToken(String access_token,String openid){  
             String requestMethod = "GET";  
             String outputStr = "";   
             String url = "https://api.weixin.qq.com/sns/auth?"
                    + "access_token="+access_token+""
                    + "&openid="+openid+"";  
             String httpmsg = WeixinUtil.httpRequest(url, requestMethod, outputStr);  
             System.out.println("拉取用户信息返回消息=="+httpmsg);  
             WechatMsg msg = JSON.parseObject(httpmsg, WechatMsg.class);  
             return msg;  
        }

 然后在网页端,则是需要编写H5页面,进行自己网站和微信用户的关联,我这里是使用手机号,用户输入手机号,进行判断,如果注册过就直接关联,如果用户没有注册则进行注册后关联,完成后跳转到会员中心。



微信app下载
微信app下载

微信是一款手机通信软件,支持通过手机网络发送语音短信、视频、图片和文字。微信可以单聊及群聊,还能根据地理位置找到附近的人,带给大家全新的移动沟通体验,有需要的小伙伴快来保存下载体验吧!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

38

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

83

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

97

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

223

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

458

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

169

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

246

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

34

2026.03.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP的速通之路
PHP的速通之路

共129课时 | 16.5万人学习

微信开发视频教程
微信开发视频教程

共24课时 | 5.5万人学习

微信开发入门视频教程
微信开发入门视频教程

共31课时 | 7.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号