Java 微信二维码支付

技术标签: Java  java  微信支付  二维码  

本文代码并非原创,是根据网上一篇博客修改而成,留作备忘。
微信支付有2种模式,第一种模式略微复杂,本文采用第二种模式;

 微信扫码模式二
     业务流程说明:
     (1)商户后台系统根据用户选购的商品生成订单。
     (2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
     (3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
     (4)商户后台系统根据返回的code_url生成二维码。
     (5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
     (6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
     (7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
     (8)微信支付系统根据用户授权完成支付交易。
     (9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
     (10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
     (11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
     (12)商户确认订单已支付后给用户发货

     参阅:
        微信官方说明
        - https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
        - http://www.demodashi.com/demo/10268.html

        CSDN实例
        - http://blog.csdn.net/wangqiuyun/article/details/51241064
        - http://blog.csdn.net/bjlf_1989/article/details/51829557

根据订单生成微信支付二维码

本部分包含了微信支付模式二的1、2、3、4步骤,APPID、商户号、key等参数需要到微信公众号中查找
WXPayController代码

    @Controller
    @RequestMapping(value = "${adminPath}/wxpay")
    public class WXPayController extends BaseController {
    

    @Autowired
    private OrderService orderService;
    @Autowired
    private ProductService productService;
    @Autowired
    private WXPayService wxPayService;

 /**
     *  生成微信支付二维码图片
     *
     * @param orderId   订单ID
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping(value = "/wxqrcode.jpg")
    public void wxqrcode(String orderId, HttpServletRequest request, HttpServletResponse response, HttpSession session) throws Exception{

        // 根据传入的订单号获取到订单的金额、订单内容等参数,此处自行编码实现
        Order order = orderService.findById(orderId);
        if (null == order){
            throw new NullPointerException("订单不存在!");
        }
        if (!Order.STATUS_CREATE.equals(order.getStatus())){
            throw new NullPointerException("订单已结算或状态错误!");
        }

        List<OrderDetail> orderDetails = order.getOrderDetails();
        if (null == orderDetails || orderDetails.size() < 1){
            throw new NullPointerException("订单错误,订单中没有商品!");
        }

        // 微信支付URL需要传入的金额单位是分,此处将订单金额转换成'分'单位
        BigDecimal fen = order.getTotalAmount().multiply(new BigDecimal(100)); 
        fen = fen.setScale(0, BigDecimal.ROUND_HALF_UP);
        String order_price = fen.toString();

        // 微信支付显示标题
        String body = "微信支付测试demo";
        /*if (order.getTotalNumber() > 1){
            StringBuilder sb = new StringBuilder();
            sb = sb.append(orderDetails.get(0).getProduct().getName())
                    .append(" 等")
                    .append(order.getTotalNumber())
                    .append("件商品");
            body = sb.toString();
        } else {
            body = orderDetails.get(0).getProduct().getName();
        }*/

        // 微信支交易订单号,不能重复
        String out_trade_no = "" + System.currentTimeMillis();

        // 组装参数
        Map<String, Object> param = new HashMap<>();
        param.put("order_price", order_price);
        param.put("body", body);
        param.put("out_trade_no", out_trade_no);
        param.put("attach", orderId);

        // 生成微信支付二维码链接
        Map<String, String> result = wxPayService.doUnifiedOrder(param, request);
        if ("FAIL".equals(result.get("return_code"))){

            logger.error("生成二维码错误: " + result.get("return_msg"));
            session.setAttribute("create_wx_qrcode_error_msg",  result.get("return_msg"));
        } else {
            String urlCode = result.get("code_url");

            // 生成微信二维码,输出到response流中
            String icon = WXPayController.class.getClassLoader().getResource("coffee_icon.png").getPath();
            BufferedImage bufferedImage = MatrixToImageWriterWithLogo.genBarcode(urlCode, 512, 512, icon); // 二维码的内容,宽,高,二维码中心的图片地址
            ImageIO.write(bufferedImage, "jpg", response.getOutputStream());
        }
    }

}

WXPayService代码

    @Service
    @Transactional(readOnly = true)
    public class WXPayService {

    / **
     * 调用微信支付接口返回URL
     * @param param 订单价格,订单显示内容,订单号
     * @param request
     * @return
     * @throws Exception
     */
    public Map<String, String> doUnifiedOrder(Map<String, Object> param, HttpServletRequest request) throws Exception {

        String appid    = PayConfigUtil.APP_ID;      // appid
        String mch_id   = PayConfigUtil.MCH_ID;      // 商户号
        String key      = PayConfigUtil.API_KEY;     // key

        String trade_type = "NATIVE";
        String spbill_create_ip = PayCommonUtil.getIpAddress(request);      // 获取发起电脑 ip
        String notify_url = PayConfigUtil.NOTIFY_URL;                       // 回调接口

        String currTime = PayCommonUtil.getCurrTime();
        String strTime = currTime.substring(8, currTime.length());
        String strRandom = PayCommonUtil.buildRandom(4) + "";
        String nonce_str = strTime + strRandom;                             // 随机字符串

        String order_price = (String) param.get("order_price");             // 价格   注意:价格的单位是分
        String body = (String) param.get("body");                           // 商品名称
        String out_trade_no = (String) param.get("out_trade_no");           // 订单号

        String attach = (String) param.get("attach");                       // 附加参数,这里传的是我们的订单号orderId

        SortedMap<Object,Object> packageParams = new TreeMap<>();
        packageParams.put("appid", appid);
        packageParams.put("mch_id", mch_id);
        packageParams.put("nonce_str", nonce_str);
        packageParams.put("body", body);
        packageParams.put("out_trade_no", out_trade_no);
        packageParams.put("total_fee", order_price);
        packageParams.put("spbill_create_ip", spbill_create_ip);
        packageParams.put("notify_url", notify_url);
        packageParams.put("trade_type", trade_type);
        packageParams.put("attach", attach);

        // 签名
        String sign = PayCommonUtil.createSign("UTF-8", packageParams, key);
        packageParams.put("sign", sign);

        // 微信支付接口传输数据使用xml方式进行的,此处将参数装换为xml
        // map --> xml
        String requestXML = PayCommonUtil.getRequestXml(packageParams);
        System.out.println("---------- Request XML: " + requestXML);

        String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);
        System.out.println("---------- Response XML: " + resXml);

        // xml --> map
        return XMLUtil.doXMLParse(resXml);
    }   
}

用户扫描二维码支付完成,微信回调我们的接口, 完成相应业务

用户支付成功后,微信会通知此接口传给我们必要的参数,此处用户处理我们的逻辑如:给用户配货发货等,本部分包含了微信支付模式二的10、12步,也可以不使用本接口通过模式二第11步主动查询微信支付结果

在WXPayController中添加微信支付成功后的回调接口及相应的方法

  /**
     *  微信支付成功回调方法,在此方法中修改订单为已付款给用户发货等操作,将此URL配置到微信公众号的支付成功的回调接口中处理支付成功的业务逻辑
     *
     * @param request
     * @param response
     * @throws Exception
     */
    @RequestMapping(value = "wxnotify")
    public void wxnotify(HttpServletRequest request, HttpServletResponse response) throws Exception{

        System.out.println("-------------------------- wxnotify ---------------------------------");

        //读取参数
        StringBuffer sb = new StringBuffer();
        InputStream inputStream = request.getInputStream();
        BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));

        String s ;
        while ((s = in.readLine()) != null){
            sb.append(s);
        }
        in.close();
        inputStream.close();

        //解析xml成map
        Map<String, String> m = XMLUtil4jdom.doXMLParse(sb.toString());
        if (null == m){
            throw new NullPointerException("微信服务器未返回任何数据!");
        }

        //过滤空 设置 TreeMap
        SortedMap<Object,Object> packageParams = new TreeMap<>();
        Iterator it = m.keySet().iterator();
        while (it.hasNext()) {
            String parameter = (String) it.next();
            String parameterValue = m.get(parameter);

            String v = "";
            if(null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }
        logger.debug(packageParams.toString());

        // 账号信息
        String key = PayConfigUtil.API_KEY; //key

        //判断签名是否正确
        if(PayToolUtil.isTenpaySign("UTF-8", packageParams,key)) {
            //------------------------------
            //处理业务开始
            //------------------------------
            String resXml = "";
            if("SUCCESS".equals((String)packageParams.get("result_code"))){ // 支付成功
                try {
                    //////////执行自己的业务逻辑////////////////
                    //此处自行编码完成相应的支付成功后的业务逻辑
                   wxPayService.wxnotify(packageParams);  
                } catch (Exception e){ // 退款
                    e.printStackTrace();
                    logger.error(e.getMessage());
                }
                //////////执行自己的业务逻辑////////////////

                //暂时使用最简单的业务逻辑来处理:只是将业务处理结果保存到session中
                //(根据自己的实际业务逻辑来调整,很多时候,我们会操作业务表,将返回成功的状态保留下来)
                request.getSession().setAttribute("_PAY_RESULT", "OK");
                System.out.println("-------------------------- 支付成功 ---------------------------------");

                //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            } else {
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";

                System.out.println("-------------------------- 支付失败 ---------------------------------");

            }
            //------------------------------
            //处理业务完毕
            //------------------------------
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
            out.write(resXml.getBytes());
            out.flush();
            out.close();
        } else{
            logger.debug("通知签名验证失败");
        }
    }

在WXPayService中添加支付成功的业务逻辑

 @Transactional(readOnly = false)
    public void wxnotify(SortedMap<Object,Object> packageParams) throws Exception{

          String orderId = (String) packageParams.get("attach");
          String realPay = (String) packageParams.get("total_fee"); // 分为单位
          BigDecimal pay = new BigDecimal(realPay).divide(new BigDecimal(100));
          System.out.println("订单" + orderId + "支付成功,支付金额为:¥" + pay.floatValue() + "元");
    }

如何通知前台用户已经支付

前台web页面可用通过ajax轮训,根据订单id检测订单的状态是否是已经支付

代码中用到的工具类

PayConfigUtil工具类,保存了一些微信支付的参数

public class PayConfigUtil {
    

  // 微信支付 公众号ID 
  public static final String APP_ID = "wx3c3a******a41b"; 
  // 商户号ID
  public static final String MCH_ID = "132****1";
  // key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
  public static final String API_KEY = "12345678***********9012";

  // 支付成功回调接口,我们在WXPayController中编写的回调接口的公网地址
  // # 微信支付成功回调地址:微信公众平台(mp.weixin.qq.com) --> 微信支付 --> 扫码支付 --> 支付回调URL
  public static final String NOTIFY_URL = "http://1*0.2**.**.*2:80**/wxpay/wxnotify";

  // 微信支付官方接口
  public static final String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

}

XMLUtil4jdom类用于将map参数与xml互转

public class XMLUtil4jdom {
    

    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     * @param strxml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");

        if(null == strxml || "".equals(strxml)) {
            return null;
        }

        Map<String, String> m = new HashMap<String, String>();
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while(it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if(children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = XMLUtil4jdom.getChildrenText(children);
            }

            m.put(k, v);
        }

        //关闭流
        in.close();

        return m;
    }

    /**
     * 获取子结点的xml
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if(!children.isEmpty()) {
            Iterator it = children.iterator();
            while(it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if(!list.isEmpty()) {
                    sb.append(XMLUtil4jdom.getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }

        return sb.toString();
    }

}

PayCommonUtil类里包装了微信支付签名算法和一些小工具

public class PayCommonUtil {
    

    /**
     * 微信支付签名算法sign
     * @param characterEncoding
     * @param parameters
     * @return
     */
    @SuppressWarnings("unchecked")
    public static String createSign(String characterEncoding, SortedMap<Object,Object> parameters, String API_KEY){

        StringBuffer sb = new StringBuffer();
        Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
        Iterator it = es.iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String k = (String)entry.getKey();
            Object v = entry.getValue();
            if(null != v && !"".equals(v)
                    && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + API_KEY);
        String sign = PayCommonUtil.MD5Encode(sb.toString(), characterEncoding).toUpperCase();

        return sign;
    }

    /**
     * @Description:将请求参数转换为xml格式的string
     * @param parameters
     *            请求参数
     * @return
     */
    public static String getRequestXml(SortedMap<Object, Object> parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
                sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
            } else {
                sb.append("<" + k + ">" + v + "</" + k + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 获取当前时间 yyyyMMddHHmmss
     *
     * @return String
     */
    public static String getCurrTime() {
        Date now = new Date();
        SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        String s = outFormat.format(now);
        return s;
    }

    /**
     * 取出一个指定长度大小的随机正整数.
     *
     * @param length
     *            int 设定所取出随机数的长度。length小于11
     * @return int 返回生成的随机数。
     */
    public static int buildRandom(int length) {
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }

    /**
     * 获取IP地址
     *
     * @param request
     * @return
     */
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    /**
     * 计算字符串MD5摘要值
     *
     * @param origin 原始字符串
     * @param charsetname 字符集
     * @return
     */
    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            if (charsetname == null || "".equals(charsetname))
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes()));
            else
                resultString = byteArrayToHexString(md.digest(resultString
                        .getBytes(charsetname)));
        } catch (Exception exception) {
        }
        return resultString;
    }

    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}

MatrixToImageWriterWithLogo类是用来生成微信二维码的类,在Java 利用google.zxing类生成的BitMatrix二维码添加logo图标中有具体的代码,此处就不写了

至此全部代码就已经完成了

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_18252605/article/details/75276190

智能推荐

完美洗牌_MoonMoon29的博客-程序员宅基地

代码实现:https://github.com/julycoding/The-Art-Of-Programming-By-July/blob/master/ebook/zh/02.09.md阿里2017题目:一、考虑一种常见的扑克牌洗牌方法,是将扑克牌(54张)平均分成两份,然后随机的将两份扑克牌随机的合并到一起。请问:1、经过这样一次洗牌之后,扑克牌的顺序最多有多

【转】javascript面向对象编程_weixin_33862041的博客-程序员宅基地

摘要:本文本来是想自己写的,奈何花了好长时间写好之后忘记保存,还按了刷新键,一键回到解放前,索性不写了,所以本文是转载的。 面向对象编程是用抽象方式创建基于现实世界模型的一种编程模式,主要包括模块化、多态、和封装几种技术。 对JavaScript而言,其核心是支持面向对象的,同时它也提供了强大灵活的基于原型的面向对象编程能力。 本文将会深入的探讨有关使用JavaScrip...

AVL树_Lzz688的博客-程序员宅基地

AVL树基础知识平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡因子某结点的左子树与右子树的高度(深度)差即为该结点的平衡因子(BF,Balance Factor)。平衡二叉树上所有结点的平衡因子只...

java swing panel问题_java-JPanel添加问题_有书的博客-程序员宅基地

我是向JFrame添加JPanel的新手,我需要一些帮助.在我上的一堂课中,我画了一个大矩形.此类是JPanel的子类.另一个类是JFrame的子类.当我创建该类的新JPanel对象时,矩形会显示在框架上,但是它比平常小得多,并且位置不正确.这是代码,怎么了?public void gameRender() {if( dbImage == null ) {dbImage = createImage...

[JavaWeb-HTTP]request对象和response对象的原理_唐火的博客-程序员宅基地

request对象和response对象的原理 1. request和response对象是由服务器创建的。我们来使用它们 2. request对象是来获取请求消息,response对象是来设置响应消息

【牛客网】网易2017内推笔试编程题合集(二)_小拳头的博客-程序员宅基地

1、【*】[编程题]混合颜料你就是一个画家!你现在想绘制一幅画,但是你现在没有足够颜色的颜料。为了让问题简单,我们用正整数表示不同颜色的颜料。你知道这幅画需要的n种颜色的颜料,你现在可以去商店购买一些颜料,但是商店不能保证能供应所有颜色的颜料,所以你需要自己混合一些颜料。混合两种不一样的颜色A和颜色B颜料可以产生(A XOR B)这种颜色的颜料(新产生的颜料也可以用作继续混合产生新的颜

随便推点

cf鬼跳_aiguozhe20008的博客-程序员宅基地

废话我就不多说(看LG跳1-3代跳的.你一定要看后面的重点.LG1-3代跳本来就是同一种跳.只是跳的时间.把握不一样所以跳出来的效果也不一样): 方法: 1代跳法:先按着蹲[ctrl].在按:S+空格.(给人一个往后的力)然后立刻按:空格+S.空格+S.空格+S....一直循环.(在落地的瞬间按跳接着就是后跳后跳如此反复) 重点: 是跳起后再按后 并且大家要掌握好每跳之间...

魔兽世界最新服务器推荐,[大陆]《魔兽世界》全新第六大区推荐服务器_妮可蹦蹦的博客-程序员宅基地

亲爱的玩家:全新第六大区(网通—北京)自20日开放以来,广大玩家热情高涨,部分服务器已经全面爆满,燃烧军团、安其拉等服务器的排队现象已经较为严重,包括黑手军团、血羽等在内的热门PvP服务器均不同程度出现部落阵营人数过多的现象。而PvE服务器瓦利马萨斯的联盟阵营人数则超过了部落人数的一倍。我们建议广大玩家选择以下列出的服务器,享受最为流畅的游戏体验和平衡的阵营。推荐服务器:库尔提拉斯——吉安娜·普罗...

docker-machine create -d generic 运行的波折过程及遇见的问题_weixin_33889665的博客-程序员宅基地

这是一个愚蠢的学习过程,但是因为觉得过程还是值得记录的,还是写了下来2》driver = generic1)在这个过程中使用的都是本地的mac系统,然后尝试在mac本地create -d generic一直不成功,出现下面的错误:Error creating machine: Error waiting for machine to be running: Maximum nu...

Android之UI学习篇十三:Gallery控件学习_wulianghuan的博客-程序员宅基地

Gallery组件主要用于横向显示图像列表,不过按常规做法。Gallery组件只能有限地显示指定的图像。也就是说,如果为Gallery组件指定了9张图像,那么当Gallery组件显示到第9张时,就不会再继续显示了。这虽然在大多数时候没有什么关系,但在某些情况下,我们希望图像显示到最后一张时再重第1张开始显示,也就是循环显示。要实现这种风格的Gallery组件,就需要对Gallery的Adapter

Hibernate框架基础_weisian151的博客-程序员宅基地

Hibernate框架概述Hibernate,翻译过来是冬眠的意思,正好现在已经进入秋季,世间万物开始准备冬眠了。对于对象来说就是持久化。持久化(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的对象存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。持久化是将程序数据在持久状态和瞬时状态间转换

HTML和小程序的 12 种 flex 布局_CGGAO的博客-程序员宅基地

目录 排列方向及换行 flex 属性:flex-direction flex 属性:flex-wrap flex 属性:flex-flow 项目伸缩及宽度 flex 属性:flex-grow flex 属性:flex-sh...

推荐文章

热门文章

相关标签