热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

app调用微信支付疯狂踩坑(前端uniapp后端java)

之前有做了小程序的微信支付以为app的小程序的流程应该差不多,就直接把代码c过来然后发现不行,踩了一堆坑,硬生生耗了俩周(中

之前有做了小程序的微信支付以为app的小程序的流程应该差不多,就直接把代码c过来然后发现不行,踩了一堆坑,硬生生耗了俩周(中间还有其他事情耽搁了)
小程序调起微信支付

 


目录标题


    • 准备工作
    • 后台代码
    • 前端代码
    • 踩的坑

 


准备工作

微信商户平台注册
微信开放平台注册
注册会需要营业执照,申请支付接口的话会需要300元的审核费用,具体就不多说了,这个不难而且网上教程一堆。

先上代码吧,最后再说踩的坑


后台代码

package com.ruoyi.web.controller.defalt;import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.COrder;
import com.ruoyi.system.domain.CUser;
import com.ruoyi.system.service.ICOrderService;
import com.ruoyi.system.service.ICUserService;
import com.ruoyi.util.RedisUtil;
import com.ruoyi.util.wx.PayForUtil;
import com.ruoyi.util.wx.WXPayUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import java.io.*;
import java.util.*;/*** 登录注册Controller* * @author 浮生* @date 2019-09-16*/
@Controller
@RequestMapping("/front")
public class FrontBuyController extends BaseController {@AutowiredRedisUtil redis;@Autowiredprivate ICOrderService orderService;@Autowiredprivate ICUserService cUserService;String appId = "微信开放平台申请的移动端的appid";// 商户号String partnerId = "微信商户平台的商户号";//商户号String mch_id = partnerId;//扩展字段,暂填写固定值Sign=WXPay,java无法使用package关键字作为变量名所以用packageValue替代String packageValue = "Sign=WXPay";// 第一次发起支付请求的接口地址String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";// key为商户平台设置的密钥keyString WXKeHukey = "商户平台设置的密钥";// 本机的ip地址String spbill_create_ip = "本机的ip地址";// 支付回调地址String notify_url = "支付回调地址";// 调起支付方式String trade_type = "APP";// 签名类型String SIGNTYPE = "MD5";/*** a调起微信预支付接口* * @param money:支付金额* @return* @throws UnsupportedEncodingException */@GetMapping("/buyStepOne")@ResponseBody@CrossOrigin(allowCredentials = "true")public AjaxResult buyStepOne(Long money,String redisKey) throws UnsupportedEncodingException {if(redis.hasKey(redisKey)) {//获取当前用户idLong userId = Long.parseLong(redis.get(redisKey).toString());// 通过course_id查询购买的课程信息System.out.println(money);// 生成的随机字符串String nonce_str = StringUtils.getRandomString(32);// 商品名称String body = new String("shuangxi-huiyuan".getBytes("ISO-8859-1"), "UTF-8");// 商户订单号(先用时间加上3位随机数代替)Date date = new Date();String orderNo = date.getTime() + StringUtils.getRandomString(3) + "";Map packageParams = new HashMap();// 小程序ID,微信分配的小程序IDpackageParams.put("appid", appId);// 小程序ID,微信分配的小程序IDpackageParams.put("attach", userId.toString());// 商户号,微信支付分配的商户号packageParams.put("mch_id", mch_id);// 随机字符串,长度要求在32位以内。packageParams.put("nonce_str", nonce_str);// 商品简单描述packageParams.put("body", body);// 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一packageParams.put("out_trade_no", orderNo);// 商户订单号// 订单总金额,单位为分packageParams.put("total_fee", money.toString());// 终端IP,支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IPpackageParams.put("spbill_create_ip", spbill_create_ip);// 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。packageParams.put("notify_url", notify_url);// 交易类型(JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付,不同trade_type决定了调起支付的方式,请根据支付产品正确上传)packageParams.put("trade_type", trade_type);// 除去数组中的空值和签名参数packageParams = WXPayUtils.paraFilter(packageParams);String prestr = WXPayUtils.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串System.out.println("签名数据"+prestr);// MD5运算生成签名,这里是第一次签名,用于调用统一下单接口,key为商户平台设置的密钥keyString mysign = WXPayUtils.sign(prestr, WXKeHukey, "utf-8").toUpperCase();System.out.println("=======================第一次签名:" + mysign + "=====================");// 拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去String xml = "" + "" + appId + "" + "" + userId + "" + "" + "" + mch_id + "" + "" + nonce_str + ""+ "" + notify_url + "" + "" + orderNo + ""+ "" + spbill_create_ip + "" + "" + money + "" + "" + trade_type + "" + "" + mysign + ""+ "";System.out.println("调试模式_统一下单接口 请求XML数据:" + xml);// 调用统一下单接口,并接受返回的结果String result = WXPayUtils.httpRequest(pay_url, "POST", xml);System.out.println("调试模式_统一下单接口 返回XML数据:" + result);// 将解析结果存储在HashMap中
// Map map = doXMLParse(result);// String return_code = (String) map.get("return_code");//返回状态码// 解析结果,将结果存在map中Map map = WXPayUtils.doXMLToMap(result);System.out.println(map);String return_code = (String) map.get("return_code");// 返回给移动端需要的参数Map response = new HashMap();if (return_code == "SUCCESS" || return_code.equals(return_code)) {// 业务结果String prepay_id = (String) map.get("prepay_id");// 返回的预付单信息response.put("nonceStr", (String) map.get("nonceStr"));response.put("package", "prepay_id=" + prepay_id);Long timeStamp = System.currentTimeMillis() / 1000;response.put("timeStamp", timeStamp + "");// 这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误String stringSignTemp = "appId=" + appId + "&nonceStr=" + (String) map.get("nonceStr") + "&package=prepay_id=" + prepay_id+ "&signType=" + SIGNTYPE + "&timeStamp=" + timeStamp;// 再次签名,这个签名用于小程序端调用wx.requesetPayment方法
// String paySign = WXPayUtils.sign(stringSignTemp, WXKeHukey, "utf-8").toUpperCase();
// System.out.println("=======================第二次签名:" + paySign + "=====================");// response.put("paySign", paySign);response.put("appid", appId);response.put("sign", (String) map.get("sign"));response.put("mysign",mysign);// 将订单和报名信息添加到数据库COrder c_order = new COrder();c_order.setCreateTime(new Date());c_order.setOrderMoney(money);c_order.setOrderFlag(0);c_order.setOrderNumber(orderNo);c_order.setUserId(userId);orderService.insertCOrder(c_order);}System.out.println(response);AjaxResult resultall = new AjaxResult();return resultall.success(response);}else {return error("未登陆");}}/*** 微信支付回调方法*/@GetMapping("/buySteptwo")@ResponseBody@CrossOrigin(allowCredentials = "true")public AjaxResult buySteptwo() {try {//读取参数 InputStream inputStream ; StringBuffer sb = new StringBuffer(); inputStream = getRequest().getInputStream(); String s ; BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); while ((s = in.readLine()) != null){ sb.append(s); } in.close(); inputStream.close(); //解析xml成map Map m = new HashMap(); m = doXMLParse(sb.toString());//过滤空 设置 TreeMap SortedMap packageParams = new TreeMap(); Iterator it = m.keySet().iterator(); while (it.hasNext()) { String parameter = it.next(); String parameterValue = m.get(parameter); String v = ""; if(null != parameterValue) { v = parameterValue.trim(); } packageParams.put(parameter, v); } // 微信支付的API密钥 String key = WXKeHukey; // key //判断签名是否正确 if(PayForUtil.isTenpaySign("UTF-8", packageParams,key)) { //------------------------------ //处理业务开始 //------------------------------ String resXml = ""; if("SUCCESS".equals((String)packageParams.get("result_code"))){ System.out.println("+++++++++++++++++++回调参数+++++++++++++++++++");System.out.println(packageParams);// 这里是支付成功 //执行自己的业务逻辑开始String app_id = (String)packageParams.get("appid");String mch_id = (String)packageParams.get("mch_id"); String openid = (String)packageParams.get("openid"); String is_subscribe = (String)packageParams.get("is_subscribe");//是否关注公众号//公用回传参数 我设置的是 "优惠券ID;课程Type:用户ID"String attach = (String)packageParams.get("attach");//商户订单号String out_trade_no = ((String)packageParams.get("out_trade_no")); out_trade_no = out_trade_no.replace(" ", "");//付款金额【以分为单位】String total_fee = (String)packageParams.get("total_fee"); //微信生成的交易订单号String transaction_id = (String)packageParams.get("transaction_id");//微信支付订单号//支付完成时间String time_end=(String)packageParams.get("time_end");
// //回调一次后进行判断,查询是否以及添加记录到数据库
// Record huidiao = Db.findById("c_order" , "order_number", transaction_id);
// if(huidiao != null) {
// redirect("/");
// return;
// }//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了. resXml &#61; "" &#43; "" &#43; "" &#43; " "; /*** 支付成功后的处理*/// 更新订单数据Long userId &#61; Long.parseLong(attach);COrder c_order &#61; new COrder();c_order.setOrderNumber(out_trade_no);List orderList &#61; orderService.selectCOrderList(c_order);if(orderList.size()>&#61;1) {c_order &#61; orderList.get(0);c_order.setCreateTime(new Date());c_order.setOrderMoney(Long.parseLong(total_fee));c_order.setOrderFlag(1);c_order.setUserId(userId);orderService.updateCOrder(c_order);//查询会员月费Long monthMoney &#61; orderService.selectMonthMoney();//计算充值多少个月的会员long month &#61; Long.parseLong(total_fee)/monthMoney;//查询用户信息CUser cUser &#61; new CUser();cUser.setUserId(userId);List userList &#61; cUserService.selectUserInfoList(cUser);//根据用户vip剩余时间处理业务if(userList.size()<&#61;0) {cUser &#61; userList.get(0);Calendar userVip &#61; Calendar.getInstance();userVip.setTime(cUser.getUserVip());Calendar now &#61; Calendar.getInstance();userVip.setTime(new Date());if(userVip.after(now)) {//会员未过期}else {//会员已过期&#xff0c;或者未充值过会员userVip &#61; now;}userVip.add(Calendar.MONTH, (int)month);cUser.setUserVip(userVip.getTime());cUserService.updateCUser(cUser);return success();}else {System.out.println("查询不到用户");return error("查询不到用户");}}else {System.out.println("获取订单失败请与管理员联系");return error("获取订单失败请与管理员联系");}} else { System.out.println("支付失败,错误信息&#xff1a;" &#43; packageParams.get("err_code")); resXml &#61; "" &#43; "" &#43; "" &#43; " "; //TODO 支付失败要做什么 我这里先不写//------------------------------ //处理业务完毕 //------------------------------ BufferedOutputStream out &#61; new BufferedOutputStream( getResponse().getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close();} } else{ System.out.println("通知签名验证失败");return error("通知签名验证失败");} }catch(IOException e) {e.printStackTrace();return error("IO处理失败&#xff0c;请联系管理员&#xff01;");} catch (JDOMException e) {e.printStackTrace();return error("JDOM处理失败&#xff0c;请联系管理员&#xff01;");}return error(); }/** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点&#xff0c;则此节点的值是子节点的xml数据。 * &#64;param strxml * &#64;return * &#64;throws JDOMException * &#64;throws IOException */ &#64;SuppressWarnings({ "rawtypes", "unchecked" })public static Map doXMLParse(String strxml) throws JDOMException, IOException { strxml &#61; strxml.replaceFirst("encoding&#61;\".*\"", "encoding&#61;\"UTF-8\""); if(null &#61;&#61; strxml || "".equals(strxml)) { return null; } Map m &#61; new HashMap(); InputStream in &#61; new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder &#61; new SAXBuilder(); Document doc &#61; builder.build(in); Element root &#61; doc.getRootElement(); List list &#61; root.getChildren(); Iterator it &#61; list.iterator(); while(it.hasNext()) { Element e &#61; (Element) it.next(); String k &#61; e.getName(); String v &#61; ""; List children &#61; e.getChildren(); if(children.isEmpty()) { v &#61; e.getTextNormalize(); } else { v &#61; getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } /** * 获取子结点的xml * &#64;param children * &#64;return String */ &#64;SuppressWarnings("rawtypes")public static String getChildrenText(List children) { StringBuffer sb &#61; new StringBuffer(); if(!children.isEmpty()) { Iterator it &#61; children.iterator(); while(it.hasNext()) { Element e &#61; (Element) it.next(); String name &#61; e.getName(); String value &#61; e.getTextNormalize(); List list &#61; e.getChildren(); sb.append("<" &#43; name &#43; ">"); if(!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append(""); } } return sb.toString(); } }

pom文件
可以参考下我之前的小程序的代码的pom文件&#xff0c;小程序调起微信支付&#xff0c;基本没做更改&#xff0c;只是用的spring-boot&#xff0c;本地打包加入外部jar包太麻烦就&#xff0c;直接走pom引入了外部的包&#xff0c;就多了下面一个包

org.jdomjdom1.1

用到的外部jar包和工具类&#xff0c;jar包好像都没用到就导了pom文件就可以了&#xff0c;添加pom文件后还是有问题&#xff0c;可以把jar包导下&#xff0c;maven项目导外部jar包&#xff0c;打jar包会有问题&#xff0c;需要自己去解决下&#xff0c;我就是解决不了&#xff0c;所以选择了直接找网上的pom文件直接导
https://download.csdn.net/download/qq_40488121/11704169


前端代码

&#xff08;前端不是我写的&#xff0c;我找前端要了份代码&#xff0c;全不全就不晓得了&#xff09;

uni.request({url:baseUrl&#43;&#39;/front/NOyStepOne&#39;,// method: &#39;GET&#39;,data: { redisKey:this.rediskey,money:30},success: res &#61;> {console.log(res.data.data)var str&#61;res.data.data.packagevar arr&#61;str.split("&#61;")[1]//胜利ar as1 &#61;arr.toUpperCase()//console.log(as1)var obj&#61;{appid:res.data.data.appid, //id 应用idpartnerid:&#39;1557620991&#39;, //商户号 prepayid:arr, //预支付package:&#39;Sign&#61;WXPay&#39;,noncestr:res.data.data.nonceStr,timestamp:res.data.data.timeStamp, //时间戳sign:res.data.data.sign}var orderInfo&#61;JSON.stringify(obj)console.log("222")console.log(obj)var test&#61;true;uni.requestPayment({provider: &#39;wxpay&#39;,orderInfo:orderInfo, //微信、支付宝订单数据success: function (res) {console.log(&#39;success:&#39; &#43; JSON.stringify(res));},fail: function (err) {console.log(&#39;fail:&#39; &#43; JSON.stringify(err));}});},fail: () &#61;> {},complete: () &#61;> {}});

踩的坑

说下我踩的坑吧


  1. 生成第一次签名的sign和xml里的参数要一模一样&#xff0c;我最开始就是不一致导致统一下单接口一直没调通&#xff0c;具体代码位置如下俩处

Map packageParams &#61; new HashMap();// 小程序ID,微信分配的小程序IDpackageParams.put("appid", appId);// 小程序ID,微信分配的小程序IDpackageParams.put("attach", userId.toString());// 商户号,微信支付分配的商户号packageParams.put("mch_id", mch_id);// 随机字符串&#xff0c;长度要求在32位以内。packageParams.put("nonce_str", nonce_str);// 商品简单描述packageParams.put("body", body);// 商户系统内部订单号&#xff0c;要求32个字符内&#xff0c;只能是数字、大小写字母_-|*且在同一个商户号下唯一packageParams.put("out_trade_no", orderNo);// 商户订单号// 订单总金额&#xff0c;单位为分packageParams.put("total_fee", money.toString());// 终端IP,支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IPpackageParams.put("spbill_create_ip", spbill_create_ip);// 异步接收微信支付结果通知的回调地址&#xff0c;通知url必须为外网可访问的url&#xff0c;不能携带参数。packageParams.put("notify_url", notify_url);// 交易类型(JSAPI--JSAPI支付&#xff08;或小程序支付&#xff09;、NATIVE--Native支付、APP--app支付&#xff0c;MWEB--H5支付&#xff0c;不同trade_type决定了调起支付的方式&#xff0c;请根据支付产品正确上传)packageParams.put("trade_type", trade_type);// 除去数组中的空值和签名参数packageParams &#61; WXPayUtils.paraFilter(packageParams);String prestr &#61; WXPayUtils.createLinkString(packageParams); // 把数组所有元素&#xff0c;按照“参数&#61;参数值”的模式用“&”字符拼接成字符串

// 拼接统一下单接口使用的xml数据&#xff0c;要将上一步生成的签名一起拼接进去String xml &#61; "" &#43; "" &#43; appId &#43; "" &#43; "" &#43; userId &#43; "" &#43; "" &#43; "" &#43; mch_id &#43; "" &#43; "" &#43; nonce_str &#43; ""&#43; "" &#43; notify_url &#43; "" &#43; "" &#43; orderNo &#43; ""&#43; "" &#43; spbill_create_ip &#43; "" &#43; "" &#43; money &#43; "" &#43; "" &#43; trade_type &#43; "" &#43; "" &#43; mysign &#43; ""&#43; "";System.out.println("调试模式_统一下单接口 请求XML数据&#xff1a;" &#43; xml);// 调用统一下单接口&#xff0c;并接受返回的结果String result &#61; WXPayUtils.httpRequest(pay_url, "POST", xml);

这俩处的参数要一模一样&#xff0c;我就是因为多了个参数加在xml里&#xff0c;签名那里漏了&#xff0c;导致一直不行
2. app是不需要进行第二次签名的&#xff0c;前台需要的sign参数直接是在调用统一下单接口后放回的
3. 随机字符串也是在调用统一下单接口后放回的&#xff0c;不是用的我传给微信的&#xff0c;而是要用微信那传回来的


推荐阅读
  • 深入浅出 webpack 系列(二):实现 PostCSS 代码的编译与优化
    在前一篇文章中,我们探讨了如何通过基础配置使 Webpack 完成 ES6 代码的编译。本文将深入讲解如何利用 Webpack 实现 PostCSS 代码的编译与优化,包括配置相关插件和加载器,以提升开发效率和代码质量。我们将详细介绍每个步骤,并提供实用示例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 本文探讨了如何利用Java代码获取当前本地操作系统中正在运行的进程列表及其详细信息。通过引入必要的包和类,开发者可以轻松地实现这一功能,为系统监控和管理提供有力支持。示例代码展示了具体实现方法,适用于需要了解系统进程状态的开发人员。 ... [详细]
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 如何使用ES6语法编写Webpack配置文件? ... [详细]
  • 在Android应用开发中,实现与MySQL数据库的连接是一项重要的技术任务。本文详细介绍了Android连接MySQL数据库的操作流程和技术要点。首先,Android平台提供了SQLiteOpenHelper类作为数据库辅助工具,用于创建或打开数据库。开发者可以通过继承并扩展该类,实现对数据库的初始化和版本管理。此外,文章还探讨了使用第三方库如Retrofit或Volley进行网络请求,以及如何通过JSON格式交换数据,确保与MySQL服务器的高效通信。 ... [详细]
  • 优化后的标题:深入解析09版Jedis客户端
    深入解析09版Jedis客户端,本文将详细介绍如何在Java项目中正确配置Jedis以操作Redis。首先,确保项目的JDK版本和编译器设置正确。接着,通过Maven或Gradle导入必要的依赖项,如 `redis.clients:jedis`。此外,文章还将探讨Jedis连接池的配置与优化,以及常见问题的解决方案,帮助开发者高效使用Jedis进行Redis操作。 ... [详细]
  • 在使用群报数小程序进行高效接龙与统计时,可以通过创建 `LinkedList` 对象并利用 `for` 循环生成指定数量的 `Person` 对象,为每个人员分配唯一的编号,并将其添加到 `LinkedList` 集合中。这一过程确保了数据的有序性和高效管理,便于后续的接龙和统计操作。此外,该小程序还支持实时更新和查看参与人员的状态,进一步提升了活动组织的便利性和准确性。 ... [详细]
  • 本文深入探讨了 Vue.js 中异步组件的应用与优化策略。首先,文章介绍了异步组件的基本概念及其在现代前端开发中的重要性。为了确保最佳实践,建议使用 Webpack 作为模块打包工具,因为 Browserify 默认不支持异步组件的加载。接着,详细解释了异步组件的使用方法,并提供了官方文档的相关链接以供参考。此外,文章还讨论了多种优化技巧,包括代码分割、懒加载和性能调优,以提升应用的整体性能和用户体验。 ... [详细]
  • Android 构建基础流程详解
    Android 构建基础流程详解 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
  • 如何使用 `org.apache.poi.openxml4j.opc.PackagePart` 类中的 `loadRelationships()` 方法及其代码示例详解 ... [详细]
  • 为了在Hadoop 2.7.2中实现对Snappy压缩和解压功能的原生支持,本文详细介绍了如何重新编译Hadoop源代码,并优化其Native编译过程。通过这一优化,可以显著提升数据处理的效率和性能。此外,还探讨了编译过程中可能遇到的问题及其解决方案,为用户提供了一套完整的操作指南。 ... [详细]
  • 每年,意甲、德甲、英超和西甲等各大足球联赛的赛程表都是球迷们关注的焦点。本文通过 Python 编程实现了一种生成赛程表的方法,该方法基于蛇形环算法。具体而言,将所有球队排列成两列的环形结构,左侧球队对阵右侧球队,首支队伍固定不动,其余队伍按顺时针方向循环移动,从而确保每场比赛不重复。此算法不仅高效,而且易于实现,为赛程安排提供了可靠的解决方案。 ... [详细]
  • Vue应用预渲染技术详解与实践 ... [详细]
  • 本文探讨了如何利用 jQuery 的 JSONP 技术实现跨域调用外部 Web 服务。通过详细解析 JSONP 的工作原理及其在 jQuery 中的应用,本文提供了实用的代码示例和最佳实践,帮助开发者解决跨域请求中的常见问题。 ... [详细]
author-avatar
odile微笑头
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有