Spring Web MVC练习

一:开发程序常见问题

(1)学会定位前后端问题

💗通过看日志,查看到底是前端问题还是后端问题


①前端:F12查看网页的控制台


②后端:测试后端接口参数;查看IDEA的控制台

(测试后端接口参数,即去访问后端写的路径,给参数,看看是否能返回正确结果)


🌟查看请求是否到达了后端,如果没有那就是前端问题,如果有那就是后端问题

(查看方法:在后端的第一行代码上随机打印,如果请求进来了,控制台会有打印的日志)

(2)缓存问题

💗比如说我的前端代码明明写得很完整,但是运行网址的时候没有出现效果,此时右键网页点击"查看页面源代码",发现是空的,那这就是缓存问题


🌟解决办法:点击IDEA右侧的Maven,选择Lifecycle目录下的clean,清除缓存

二:学会使用Lombok

(1)Lombok定义

💗Lombok是一个工具包,它提供了一组注解,用于自动生成常见的Java代码,如getter和setter方法、构造函数、equals和hashCode方法等

(2)Lombok好处

🌟使用Lombok可减少编写样板代码的工作量,提高开发效率,不用每次都setter和getter

(3)Lombok引入(中央仓库方法)

①打开中央仓库,搜索lombok


②选择1.18.24版本

(小tips:当我们选择不熟悉的第三方工具包时,不知道什么版本好,就选择使用人数最多的)


③复制Maven到pom.xml即可

(但凡导入了新的包,一定要记得刷新)

(4)Lombok引入(SpringBoot方法)

①点击左上角的File➜Settings➜Plugins➜搜索EditStarters


②点击install进行安装并重启IDEA即可


③右键pom.xml代码页面,选择Generate


④按照下图所示点击,然后点击OK即可


⑤此时在pom.xml上就有Lombok依赖了,然后刷新Maven即可

(这个连版本都没有,因为Spring已经帮我们搞定好了)

(5)Lombok常用注解

1.Lombok常用注解图


2.@Getter和@Setter

 💗@Getter和@Setter在哪个属性上,就仅代表只有这个属性有getter和setter方法


💙比如:当我把@Getter放在from属性上,就代表只有from属性具有getter方法

3.@Data

 💗使用@Data注解,放在类上,此时所有的属性都有getter和setter方法、构造函数、equals和hashCode方法等


💙比如:我这里有个MessageInfo类,里面的属性我不想手动setter和getter,就用@Data

(此时get和set在其他类直接用即可,比如getfrom、setmessage等等,不用再加什么setter和getter方法)

4.@ToString

💗使用@ToString注解,放在类上,此时说明整个类已经重写了toString方法


三:加法计算器

(1)前端代码

💗前端代码网盘链接:前端代码( 提取码:lzh7)​​​​​​

(2)准备工作

 🌟①将加法计算器的前端代码引入到static目录下


 🌟②初学阶段,建议每做一步就去运行后端然后访问一下,确保没有错误

(static目录下放的是静态文件,可以直接访问)

(3)约定前后端交互接口

(4)后端代码

package com.example.demo.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RequestMapping("/calc")
@RestController
public class CalcController {
    @RequestMapping("/sum")
    public String sum(Integer num1 , Integer num2){
        Integer sum = num1 + num2;
        return "计算结果为:"+sum;
    }
}

(5)注意事项

(6)效果展示

四:用户登录

(1)前端代码

💗前端代码网盘链接:前端代码( 提取码:lzh7)​​​​​​

(2)准备工作

 🌟①将用户登录的前端代码引入到static目录下


 🌟②运行后端并访问,确保没有错误

(1)login.html

(2)index.html

(3)约定前后端交互接口

1.需求分析

①登录页面(login.html):通过账号和密码,校验输入的账号密码是否正确,并告知前端


②首页(index.html):告知前端当前的登录用户是谁

(如果当前已有用户登录,返回登录的账号;如果没有,返回空)

2.登录页面的交互接口

3.首页的交互接口

(4)后端代码

package com.example.demo.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;


@RequestMapping("/user")
@RestController
public class UserController {
    //登录页
    @RequestMapping("/login")
    public boolean login(String username, String password, HttpSession session) {
        //第一步:先去校验参数的合法性
        //if(!username==null || username.length()==0 || password==null || password.length()==0){
        //return false;
        //}
        if (StringUtils.hasLength(username) || !StringUtils.hasLength(password)){
            return false;
        }
        //第二步:进行用户名和密码的校验(这里因为是初学,不涉及mybatis数据库)
        if ("zhangsan".equals(username) && "123".equals(password)){
            //设置Session
            session.setAttribute("username","zhangsan");
            return true;
        }
        return false;
    }


    //主页
    @RequestMapping("/getUserInfo")
    public String getUserInfo(HttpServletRequest request){
        //从Seesion中获取登录用户
        HttpSession session = request.getSession(false);
        String userName = null;
        if (session!=null){
            userName = (String) session.getAttribute("username");
        }
        return userName;
    }
}

(5)测试后端代码

①登录页


②主页

(6)修改前端代码

①使用VSCode打开并修改登录页login.html

(修改的是script部分,并且要记得保存)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>登录页面</title>
</head>

<body>
  <h1>用户登录</h1>
  用户名:<input name="userName" type="text" id="userName"><br>
  密码:<input name="password" type="password" id="password"><br>
  <input type="button" value="登录" onclick="login()">
  
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
  <script>
    function login() {
      $.ajax({
        url:"/user/login",
        type:"post",
        data:{
          //这是一个 jQuery 代码片段
          //'#'表示获取id;'.'表示获取class
          //因此"username":$("#userName").val()表示获取 id 为 "userName" 的输入框的值
          //其中 "$" 符号是 jQuery 的简写,相当于调用 jQuery 函数
          //".val()" 是 jQuery 提供的方法,用于获取或设置表单元素的值
          "username":$("#userName").val(),   
          "password":$("#password").val()
        },
        success:function(result){    //这个result参数名字可以任取;它表示后端返回的结果
          if(result){
            location.href="/index.html";  //为true则跳转到主页
          }else{
            alert("密码错误!");
          }
        }
      });
    }

  </script>
</body>

</html>

②使用VSCode打开并修改主页index.html

(修改的是script部分,并且要记得保存)

<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>用户登录首页</title>
</head>

<body>
    登录人: <span id="loginUser"></span>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
        //页面被加载时就去调用后端请求
        //$.ajax写在哪就在哪里被调用(比如登录页,写在login里,就是点击的时候调用ajax)
        $.ajax({
            url:"/user/getUserInfo",
            type:"get",
            success:function(username){
                $("#loginUser").text(username);
            }
        });
    </script>
</body>

</html>

(7)效果展示

五:留言板

(1)前端代码

💗前端代码网盘链接:前端代码( 提取码:lzh7)​​​​​​

(2)准备工作

 🌟①将留言板的前端代码引入到static目录下


 🌟②运行后端并访问,确保没有错误

(3)约定前后端交互接口

1.需求分析

🌟后端需要提供两个服务


①提交留言:用户输⼊留言信息之后,后端需要把留言信息保存起来

(因为还没有学到mybatis,先保存到内存中)


②展示留言:页面展示时,需要从后端获取到所有的留言信息

2.获取所有留言的交互接口

🌟所有的留言信息,我们用List来表示,可以用JSON来描述这个List数据


3.发表新留言的交互接口

(4)后端代码

①需要先写一个MessageInfo类,用来存储留言

package com.example.demo.Controller;
import lombok.Data;

@Data
public class MessageInfo {
    private String from;
    private String to;
    private String message;
}

②编写后端代码

package com.example.demo.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;


@RequestMapping("/message")
@RestController
public class MessageController {
    private List<MessageInfo> messageInfos = new ArrayList<>();

    //发表新留言的后端代码
    @RequestMapping("/publish")
    public Boolean publishMessage(MessageInfo messageInfo){
        //第一步:进行参数的校验
        if (!StringUtils.hasLength(messageInfo.getFrom()) || !StringUtils.hasLength(messageInfo.getTo()) || !StringUtils.hasLength(messageInfo.getMessage())) {
            return false;
        }
        //第二步:添加留言到内存中,即List中
        messageInfos.add(messageInfo);
        return true;
    }


    //查看所有留言的后端代码
    @RequestMapping("/getMessageInfo")
    public List<MessageInfo> getMessageInfo(){
        return messageInfos;
    }
}

(5)测试后端代码

①发表新留言


②获取所有留言

(6)修改前端代码

💚修改的是script部分,记得保存


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言板</title>
    <style>
        .container {
            width: 350px;
            height: 300px;
            margin: 0 auto;
            /* border: 1px black solid; */
            text-align: center;
        }

        .grey {
            color: grey;
        }

        .container .row {
            width: 350px;
            height: 40px;

            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .container .row input {
            width: 260px;
            height: 30px;
        }

        #submit {
            width: 350px;
            height: 40px;
            background-color: orange;
            color: white;
            border: none;
            margin: 10px;
            border-radius: 5px;
            font-size: 20px;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>留言板</h1>
        <p class="grey">输入后点击提交, 会将信息显示下方空白处</p>
        <div class="row">
            <span>谁:</span> <input type="text" name="" id="from">
        </div>
        <div class="row">
            <span>对谁:</span> <input type="text" name="" id="to">
        </div>
        <div class="row">
            <span>说什么:</span> <input type="text" name="" id="say">
        </div>
        <input type="button" value="提交" id="submit" onclick="submit()">
        <!-- <div>A 对 B 说: hello</div> -->
    </div>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
        //页面一加载时,就请求后端,获取所有留言内容,显示在页面下边
        $.ajax({
            url:"/message/getMessageInfo",      //与后端获取所有留言定义的路径要一样
            type:"get", 
            success:function(messagesList){
                for(var m of messagesList){
                    //拼接显示留言记录
                    //构造节点,即拼接节点的HTML
                    var divE = "<div>"+m.from +"对" + m.to + "说:" + m.message+"</div>";
                    //把节点添加到页面上    
                    $(".container").append(divE);
                }
            }
        });

        function submit(){
            //1. 获取留言的内容
            var from = $('#from').val();
            var to = $('#to').val();
            var say = $('#say').val();
            if (from== '' || to == '' || say == '') {
                return;
            }
            //2.提交留言
            $.ajax({
               url:"/message/publish",          //与后端发布新留言定义的路径要一样
               type:"post", 
               data:{             //data的属性名要和MessageInfo类的属性名一样
                "from":from,
                "to":to,
                "message":say
               },
               success:function(result){
                 if(result){
                    //result为true认为添加成功
                    //3. 构造节点,即拼接节点的HTML
                    var divE = "<div>"+from +"对" + to + "说:" + say+"</div>";
                    //4. 把节点添加到页面上    
                    $(".container").append(divE);
                    //5. 清空输入框的值
                    $('#from').val("");
                    $('#to').val("");
                    $('#say').val("");
                 }else{
                    alert("添加留言失败");
                 }
               }
            });
        }  
    </script>
</body> 
</html>

(7)效果展示

🌟即使点击了刷新,下面的留言依旧会保存

六:图书管理系统

🌟图书管理系统是相对较大的案例,现在只学了Spring Web MVC入门,因此先实现用户登录和图书列表展示的功能,后期再不断完善

(1)前端代码

💗前端代码网盘链接:前端代码( 提取码:lzh7)​​​​​​

(2)准备工作

 🌟①将图书管理系统的前端代码引入到static目录下


 🌟②运行后端并访问,确保没有错误

(1)login.html(登录页)


(2)book_list.html(图书列表展示页)

(3)约定前后端交互接口

1.需求分析

🌟后端需要提供两个服务


①用户登录:账号密码校验接口;根据输入用户名和密码校验登录是否通过


②图书列表:提供图书列表信息

2.用户登录
3.图书列表

(4)后端代码

①需要先写一个BookInfo类,用来存储图书

package com.hlizoo.book;
import lombok.Data;
import java.math.BigDecimal;

@Data
public class BookInfo {
    //图书ID
    private Integer id;
    //图书名字
    private String bookName;
    //图书作者
    private String author;
    //图书数量
    private Integer count;
    //图书定价
    private BigDecimal price;
    //图书出版社
    private String publish;
    //图书借阅状态(状态这里我们用1表示可借阅,其他表示不可借阅)
    private Integer status;
    //图书借阅状态(返回中文)
    private String statusCN;
}

②用户登录

package com.hlizoo.book;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;


@RequestMapping("/user")
@RestController
public class UserController {
    @RequestMapping("/login")
    public Boolean login(String userName, String password, HttpSession session){
        //第一步:校验参数
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return false;
        }
        //第二步:验证账户密码是否正确
        if("zhangsan".equals(userName) && "123".equals(password)){
            //此时说明账号密码正确,然后存Session
            session.setAttribute("userName",userName); 
            return true;
        }
        return false;
    }
}

③图书列表展示

package com.hlizoo.book;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;


@RequestMapping("/book")
@RestController
public class BookController {
    @RequestMapping("/getBookList")
    public List<BookInfo> getBookList(){
        //第一步:获取图书的数据(这里的数据是虚拟数据)
        List<BookInfo> bookInfos = mockData();
        //第二步:对图书数据进行处理,比如说状态码返回中文
        for (BookInfo bookInfo : bookInfos) {
            if (bookInfo.getStatus()==1){
                bookInfo.setStatusCN("可借阅!");
            }else {
                bookInfo.setStatusCN("不可借阅!");
            }
        }
        //第三步:返回图书的数据
        return bookInfos;
    }

    //mockData表示虚拟的数据,假的,只是用来测试
    private List<BookInfo> mockData(){
        //关于arraylist的优化:如果大概知道这个集合的容量,就在创建list时去初始化它的容量
        List<BookInfo> bookInfos = new ArrayList<>(15);
        //造15条数据
        for (int i =0;i<15;i++){
            BookInfo bookInfo = new BookInfo();
            bookInfo.setId(i);
            bookInfo.setBookName("图书"+i);
            bookInfo.setAuthor("作者"+i);
            bookInfo.setCount(new Random().nextInt(200));
            bookInfo.setPrice(new BigDecimal(new Random().nextInt(100)));
            bookInfo.setPublish("出版社"+i);
            bookInfo.setStatus(i%5==0?2:1);
            bookInfos.add(bookInfo);
        }
        return bookInfos;
    }
}

(5)测试后端代码

①用户登录


②获取图书列表

(6)修改前端代码

💚修改的是script部分,记得保存


①登录页login.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">
    <link rel="stylesheet" href="css/login.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
</head>

<body>
    <div class="container-login">
        <div class="container-pic">
            <img src="pic/computer.png" width="350px">
        </div>
        <div class="login-dialog">
            <h3>登陆</h3>
            <div class="row">
                <span>用户名</span>
                <input type="text" name="userName" id="userName" class="form-control">
            </div>
            <div class="row">
                <span>密码</span>
                <input type="password" name="password" id="password" class="form-control">
            </div>
            <div class="row">
                <button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button>
            </div>
        </div>
    </div>
    <script>
        function login() {
            $.ajax({
                url:"/user/login",
                type:"post",
                data:{
                    "userName":$("#userName").val(),
                    "password":$("#password").val(),
                },
                success:function(result){
                    if(result){
                        //如果登录成功,result为true,此时跳转到图书列表页
                        location.href = "book_list.html";
                    }else{
                        alert("用户名或密码输入错误!");
                    }
                }
            });
        }
    </script>
</body>

</html>

②获取图书列表页book_list.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图书列表展示</title>
    <link rel="stylesheet" href="css/bootstrap.min.css">

    <link rel="stylesheet" href="css/list.css">
    <script type="text/javascript" src="js/jquery.min.js"></script>
    <script type="text/javascript" src="js/bootstrap.min.js"></script>
    <script src="js/jq-paginator.js"></script>

</head>

<body>
    <div class="bookContainer">
        <h2>图书列表展示</h2>
        <div class="navbar-justify-between">
            <div>
                <button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button>
                <button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button>
            </div>
        </div>

        <table>
            <thead>
                <tr>
                    <td>选择</td>
                    <td class="width100">图书ID</td>
                    <td>书名</td>
                    <td>作者</td>
                    <td>数量</td>
                    <td>定价</td>
                    <td>出版社</td>
                    <td>状态</td>
                    <td class="width200">操作</td>
                </tr>
            </thead>
            <tbody>
                <!--留着这段代码是为了下面的拼接Html方便一点 -->
                <!-- <tr>
                    <td><input type="checkbox" name="selectBook" value="1" id="selectBook" class="book-select"></td>
                    <td>4</td>
                    <td>大秦帝国第四册</td>
                    <td>我是作者</td>
                    <td>23</td>
                    <td>33.00</td>
                    <td>北京出版社</td>
                    <td>可借阅</td>
                    <td>
                        <div class="op">
                            <a href="book_update.html?bookId=4">修改</a>
                            <a href="javascript:void(0)" onclick="deleteBook(4)">删除</a>
                        </div>
                    </td>
                </tr> -->
            </tbody>
        </table>

        <div class="demo">
            <ul id="pageContainer" class="pagination justify-content-center"></ul>
        </div>
        <script>

            getBookList();
            function getBookList() {
                $.ajax({
                    url:"/book/getBookList",
                    type:"get",
                    success:function(books){
                        var finalHtml = "";
                        for(var book of books){
                            //book拿的是后端的值,因此要和BookInfo的属性名要一致
                            //根据每一条记录去拼接html,也就是一个<tr>标签
                            finalHtml += '<tr>';
                            finalHtml += '<td><input type="checkbox" name="selectBook" value="'+book.id+'" id="selectBook" class="book-select"></td>';
                            finalHtml += '<td>'+book.id+'</td>';
                            finalHtml += '<td>'+book.bookName+'</td>';
                            finalHtml += '<td>'+book.author+'</td>';
                            finalHtml += '<td>'+book.count+'</td>';
                            finalHtml += '<td>'+book.price+'</td>';
                            finalHtml += '<td>'+book.publish+'</td>';
                            finalHtml += '<td>'+book.statusCN+'</td>';
                            finalHtml += '<td><div class="op">';
                            finalHtml += '<a href="book_update.html?bookId='+book.id+'">修改</a>';
                            finalHtml += '<a href="javascript:void(0)" onclick="deleteBook('+book.id+')">删除</a>';
                            finalHtml += '</div></td></tr>';  
                        }
                        console.log(finalHtml);
                        $("tbody").html(finalHtml);
                    }
                });
            }
    
            //翻页信息
            $("#pageContainer").jqPaginator({
                totalCounts: 100, //总记录数
                pageSize: 10,    //每页的个数
                visiblePages: 5, //可视页数
                currentPage: 1,  //当前页码
                first: '<li class="page-item"><a class="page-link">首页</a></li>',
                prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页<\/a><\/li>',
                next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页<\/a><\/li>',
                last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页<\/a><\/li>',
                page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',
                //页面初始化和页码点击时都会执行
                onPageChange: function (page, type) {
                    console.log("第"+page+"页, 类型:"+type);
                }
            });
            function deleteBook(id) {
                var isDelete = confirm("确认删除?");
                if (isDelete) {
                    //删除图书
                    alert("删除成功");
                }
            }
            function batchDelete() {
                var isDelete = confirm("确认批量删除?");
                if (isDelete) {
                    //获取复选框的id
                    var ids = [];
                    $("input:checkbox[name='selectBook']:checked").each(function () {
                        ids.push($(this).val());
                    });
                    console.log(ids);
                    alert("批量删除成功");
                }
            }

        </script>
    </div>
</body>

</html>

(7)效果展示

七:应用分层

(1)问题引入

通过上面的练习,我们学习了Spring MVC简单功能的开发,但是我们也发现了⼀些问题


🌟问题:目前我们程序的文件和代码有点"杂乱",然而当前只是"⼀点点功能"的开发;如果我们把整个项目功能完成呢? 代码会更加的"杂乱无章"!

(2)应用分层概念

💗应用分层是⼀种软件开发设计思想


💜它将应用程序分成N个层次,这N个层次分别负责各自的职责,多个层次之间协同提供完整的功能;根据项目的复杂度,把项目分成三层,四层或者更多层

(3)应用分层作用

💗软件设计原则:高内聚低耦合


①高内聚:一个板块中各个元素之间联系的紧密程度越高越好;如果一个板块中的元素、语句、程序段等等它们的联系度越高,则内聚性越高


②低耦合:一个软件中的各个层或者模块之间依赖的关联程度越低越好,比如一个模块修改一处代码,其他模块代码的改动越少越好


💚应用分层好处总结:

(4)如何分层

💞三层架构


💗代码分层逻辑:先由Controller去调用Service,然后Service去调用Dao

①Controller(表现层):接受请求,返回结果


②Service(业务逻辑层):主要处理业务相关逻辑


③Dao(数据层):处理数据的,包含数据的存储,增删改查


💚还有一个叫model的包,我们一般放的是实体类

(5)代码重构

💛以图书管理系统的代码为例,进行代码重构

1.创建包

💗创建四个包,分别取名为Controller、Service、Dao、model


2.代码分类

①将BookController、UserController放到Controller包


②将BookInfo放到model包

3.举例代码分层

🌟我们以BookController为例,说明代码分层


4.代码分层

①在Dao包下创建一个BookDao文件,用来存放数据

🖤把BookController里的mockData方法移到BookDao去


②在Service包下创建一个BookService文件

🖤将BookController里的业务逻辑层移过去,然后进行改造


③在BookController进行代码重构

(由Controller去调用Service,然后Service去调用Dao)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值