springboot的web开发

 一、 静态资源映射规则


只要静态资源放在类路径下: called /static (or /public or /resources or /METAINF/resources 访问 : 当前项目根路径/ + 静态资源名

二、 enjoy模板引擎


1.将页面保存在templates目录下

2.添加坐标
<dependency>
            <groupId>com.jfinal</groupId>
            <artifactId>enjoy</artifactId>
            <version>5.0.3</version>
        </dependency>
3.开启配置
package com.ztt.springboot_web_01.config;

import com.jfinal.template.Engine;
import com.jfinal.template.ext.spring.JFinalViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @version 1.0
 * @Author 甜甜
 * @since 2024/8/10
 */
@Configuration
public class EnjoyConfig {

    @Bean(name = "jfinalViewResolver")
    public JFinalViewResolver getJFinalViewResolver() {

        // 创建用于整合 spring boot 的 ViewResolver 扩展对象
        JFinalViewResolver jfr = new JFinalViewResolver();

        // 对 spring boot 进行配置
        jfr.setSuffix(".html");
        jfr.setContentType("text/html;charset=UTF-8");
        jfr.setOrder(0);

        // 设置在模板中可通过 #(session.value) 访问 session 中的数据
        jfr.setSessionInView(true);

        // 获取 engine 对象,对 enjoy 模板引擎进行配置,配置方式与前面章节完全一样
        Engine engine  = JFinalViewResolver.engine;

        // 热加载配置能对后续配置产生影响,需要放在最前面
        engine.setDevMode(true);

        // 使用 ClassPathSourceFactory 从 class path 与 jar 包中加载模板文件
        engine.setToClassPathSourceFactory();

        // 在使用 ClassPathSourceFactory 时要使用 setBaseTemplatePath
        // 代替 jfr.setPrefix("/view/")
        engine.setBaseTemplatePath("/templates/");


        // 更多配置与前面章节完全一样
        // engine.addDirective(...)
        // engine.addSharedMethod(...);

        return jfr;
    }
}
4.编写代码

@RequestMapping

意义:处理用户的请求,相似于doget与dopost
位置:
类上:一级目录
方法:二级目录
例如:

  • user/save
  • user/delete
  • student/save
  • student/delete


属性:
value = "",path = ""
表示请求路径
=========================
method=常量,此请求的类型(get,post),若不设置则此请求适配所有的请求方式
=========================
params = ""
限制请求参数,例如:params={"msg1","msg2"}表示请求路径中必须携带参数名为msg1与msg2的参数
注意:1.超链接默认发送的是get请求
2.所有请求所携带的参数格式均为:key = value
@DeleteMapping删除
@PutMapping 修改
@GetMapping 查询
@PostMapping 新增


@RequestMapping可以点击查看源码
@Target({ElementType.METHOD, ElementType.TYPE})
METHOD代表修饰方法,TYPE代表修饰类

userController:

package com.ztt.springboot_web_01.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

/**
 * @version 1.0
 * @Author 甜甜
 * @since 2024/8/10
 */
@Controller
public class UserController {
        @RequestMapping(value="/init")//二级目录
        public String userInit(){
            System.out.println("==========进入了springMVC的控制器=========");
            System.out.println("调用业务层,调用持久层");
            return "success";//返回方法执行完要跳转的页面名称
        }

        @RequestMapping(value="/show1",method ={RequestMethod.POST})
        public String show1(){
            System.out.println("==========进入了springMVC的控制器=========");
            System.out.println("使用post方式发送请求进入");
            return "success";//返回方法执行完要跳转的页面名称
        }


        @RequestMapping(value="/show2",params = {"msg1=aa","msg2=bb"})
        public String show2(){
            System.out.println("==========进入了springMVC的控制器=========");
            System.out.println("限制请求携带的参数");
            return "success";//返回方法执行完要跳转的页面名称
        }


        //使用postman测试
        @GetMapping("/show3")
        public String show3(){
            System.out.println("==========进入了springMVC的控制器=========");
            System.out.println("必须使用get方式请求");
            return "success";//返回方法执行完要跳转的页面名称
        }

        @PostMapping("/show4")
        public String show4(){
            System.out.println("==========进入了springMVC的控制器=========");
            System.out.println("必须使用post方式请求");
            return "success";//返回方法执行完要跳转的页面名称
        }


        @DeleteMapping("/show5")
        public String show5(){
            System.out.println("==========进入了springMVC的控制器=========");
            System.out.println("必须使用delete方式请求");
            return "success";//返回方法执行完要跳转的页面名称
        }

        @PutMapping("/show6")
        public String show6(){
            System.out.println("==========进入了springMVC的控制器=========");
            System.out.println("必须使用put方式请求");
            return "success";//返回方法执行完要跳转的页面名称
        }

}

三、 springMVC


1.请求处理
2.参数绑定

springMVC请求参数的绑定

绑定的机制:SpringMVC 绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的

一.支持数据类型:
1.1 基本类型参数:

包括基本类型和 String 类型

public class Emp implements Serializable {


    private int eid;
    private String ename;
    private String esex;
public class Dep implements Serializable {
    private int did;
    private String dname;
<!DOCTYPE html>
<html lang="cn" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Title</title>
</head>
<body>

    <h1>springMVC控制器方法参数作用:接受用户请求中的数据</h1>
    <hr/>

    <h3>基本类型和 String 类型作为参数</h3>
    <a href="/one/show1?msg1=9527">发送请求1</a>
    <a href="/one/show2?msg1=jdk&msg2=9527">发送请求2</a>
<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>spring成功页面</h1>
</body>
</html>
@Controller
@RequestMapping("/one")
public class OneController {

    /**
     * 进入one.html页面
     * */
    @RequestMapping("/show")
    public String show(){
        return "one";
    }

    /***********************基本类型和 String 类型作为参数*********************************/
    @RequestMapping("/show1")
    public String show1(String msg1){
        System.out.println("=====接受到用户发送数据为:"+msg1+"=======");
        return "success";
    }


    @RequestMapping("/show2")
    public String show2(String msg1,int msg2){
        System.out.println("=====接受到用户发送数据为:"+msg1+"=======");
        System.out.println("=====接受到用户发送数据为:"+msg2+"=======");
        return "success";
    }

1.2 POJO类型参数:

包括实体类,以及关联的实体类

public class Emp implements Serializable {


    private int eid;
    private String ename;
    private String esex;


    //emp依赖的dep对象
    private Dep dept;
 <h3>POJO 类型作为参数</h3>
    <a href="/one/show3?eid=1&ename=甜甜&esex=女">发送请求3</a>
    

    <form action="/one/show4" method="post">
        员工编号:<input type="text" name="eid" ><br/>
        员工姓名:<input type="text" name="ename" ><br/>
        员工性别:<input type="text" name="esex" ><br/>
        部门编号:<input type="text" name="dept.did" ><br/>
        部门名称:<input type="text" name="dept.dname" ><br/>
        <input type="submit" value="发送请求4"/>
    </form>

    <form action="/one/map" method="post">
        员工编号:<input type="text" name="eids"><br/>
        员工姓名:<input type="text" name="enames"><br/>
        员工性别:<input type="text" name="esexs"><br/>
        <input type="submit" value="发送请求4(map)"/>
    </form>
/***********************POJO 类型作为参数*********************************/
    //单一对象
    @RequestMapping("/show3")
    public String show3(Emp emp){
        System.out.println("=====接受到用户发送数据为:"+emp+"=======");
        return "success";
    }

    //对象嵌套
    @RequestMapping("/show4")
    public String show4(Emp emp){
        System.out.println("=====接受到用户发送数据为:"+emp+"=======");
        return "success";
    }

    //@RequestParam
    @RequestMapping("/map")
    public String map(@RequestParam Map map){
        System.out.println(map);
        return "success";
    }

1.3 数组和集合类型参数:

包括 List 结构和 Map 结构的集合(包括数组)

public class Dep implements Serializable {
    private int did;
    private String dname;

    //依赖员工集合
    private List<Emp> mylist;
    private Map<String, Emp> myMap;
<h3>POJO 类中包含集合类型参数</h3>
    <form action="/one/show5" method="post">
        部门编号:<input type="text" name="did" ><br/>
        部门名称:<input type="text" name="dname" ><br/>
        员工编号1:<input type="text" name="mylist[0].eid" ><br/>
        员工姓名1:<input type="text" name="mylist[0].ename" ><br/>
        员工性别1:<input type="text" name="mylist[0].esex" ><br/>
        员工编号2:<input type="text" name="mylist[1].eid" ><br/>
        员工姓名2:<input type="text" name="mylist[1].ename" ><br/>
        员工性别2:<input type="text" name="mylist[1].esex" ><br/>

        员工编号3:<input type="text" name="myMap['one'].eid" ><br/>
        员工姓名3:<input type="text" name="myMap['one'].ename" ><br/>
        员工性别3:<input type="text" name="myMap['one'].esex" ><br/>
        员工编号4:<input type="text" name="myMap['two'].eid" ><br/>
        员工姓名4:<input type="text" name="myMap['two'].ename" ><br/>
        员工性别4:<input type="text" name="myMap['two'].esex" ><br/>
        <input type="submit" value="发送请求5"/>
    </form>
/*********************POJO 类中包含集合类型参数*********************************/
    @RequestMapping("/show5")
    public String show5(Dep dep){
        System.out.println("=====接受到用户发送数据为:"+dep+"=======");
        return "success";
    }
=====接受到用户发送数据为:Dep{did=1, dname='测试部', mylist=[Emp{eid=1, ename='熊大', esex='男', dept=null}, Emp{eid=2, ename='熊二', esex='女', dept=null}], myMap={one=Emp{eid=3, ename='光头强', esex='男', dept=null}, two=Emp{eid=4, ename='翠花', esex='女', dept=null}}}=======
  @RequestMapping("/show6")
    public String show8(int[] nums){
        System.out.println("=====接受到用户发送数据为:"+ Arrays.toString(nums) +"=======");
        return "success";
    }
  <a href="/one/show6?nums=123&nums=456&nums=789">发送请求6</a>

1.4 使用 ServletAPI 对象作为方法参数
  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • java.security.Principal
  • Locale
  • InputStream
  • OutputStream
  • Reader
  • Writer

/*********************使用 ServletAPI 对象作为方法参数*********************************/
    @RequestMapping("/show7")
    public String show7(HttpServletRequest request, HttpServletResponse response){


//        request.setCharacterEncoding("UTF-8");
//        response.setCharacterEncoding("UTF-8");
        System.out.println(request);
        System.out.println(response);
        request.getParameter("msg1");

        HttpSession session =     request.getSession();
        System.out.println(session);
        session.setAttribute("","");

        try {
            response.sendRedirect("重定向");
        } catch (IOException e) {
            e.printStackTrace();
        }

        ServletContext applaction =  session.getServletContext();

        return "success";
    }
}

    <h3>使用 ServletAPI 对象作为方法参数</h3>
    <a href="/one/show7">发送请求7</a>
二.使用要求
  • 如果页面标签名称和方法参数名称不一致,可以使用此注解实现
1.2 属性:
  • name属性:设置参数名称
  • defaultValue属性:设置默认值
  • required属性:设置是否为必传

案例:

@Controller
@RequestMapping("/one")
public class OneController {
    /**
     * @RequestParam("名称必须与页面标签或者url地址key名称一致")
     * */
    @RequestMapping("/show1")
    public String show1(@RequestParam(name="msg1") String msg){
        System.out.println("=====接受到用户发送数据为:"+msg+"=======");
        return "success";
    }

    @RequestMapping("/show2")
    public String show2(@RequestParam("msg1") String msg, @RequestParam("msg2") int num){
        System.out.println("=====接受到用户发送数据为:"+msg+"=======");
        System.out.println("=====接受到用户发送数据为:"+num+"=======");
        return "success";
    }


    @RequestMapping("/show3")
    public String show4(@RequestParam(name = "uname",defaultValue = "暂无用户") String name){
        System.out.println("账号:"+name);
        return "success";
    }

二.@RequestBody
2.1 作用:
  • 用于获取"请求体"内容。直接使用得到是 key=value&key=value...
  • 结构的数据,并可以转换为对象
2.2 属性:
  • required:是否必须有请求体。默认值是:true。
/**
     * 前后端分离
     * @RequestBody可以将json ===》 javaBean
     * 注意:
     *  1.前端不能使用GET方式提交数据,GET方式无请求体
     * {
     * 	"eid":101,
     * 	"ename":"詹姆斯邦德",
     * 	"esex":"绅士"
     * }
     *
     * * */
@RequestMapping("/show4")
public String show4(@RequestBody Emp emp){
    System.out.println("=========="+emp+"==========");
    return "success";
}

三.@PathVaribale
3.1 作用:
  • 用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},
  • 这个{id}就是 url 占位符。url 支持占位符是 spring3.0 之
  • 后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志
3.2 属性:
  • value:用于指定 url 中占位符名称。
  • required:是否必须提供占位符。
  • Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。
  • 主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,
  • 更易于实现缓存机制等。

Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:

  • GET:用于获取资源
  • POST:用于新建资源
  • PUT:用于更新资源
  • DELETE:用于删除资源

例如:

新增 POST http://localhost:8080/user/用户名/用户密码/用户性别

查询 GET http://localhost:8080/user/用户ID

删除 delete http://localhost:8080/user/用户ID

修改 put http://localhost:8080/user/用户ID/用户名/用户密码/用户性别

localhost:8080/login?uname=王老师&upwd=123

localhost:8080/login/王老师/123

@PostMapping("/show5/{uname}/{pwd}")
public String show5(@PathVariable("uname") String msg1, @PathVariable("pwd") String msg2){
    System.out.println(msg1);
    System.out.println(msg2);
    return "success";
}

@PostMapping("/show6/{uname}/{pwd}")
public String show6(@PathVariable String uname, @PathVariable String pwd){
    System.out.println(uname);
    System.out.println(pwd);
    return "success";
}

}

四.@RequestHeader
4.1 作用:
  • 用于获取请求消息头。
4.2 属性:
  • value:提供消息头名称
  • required:是否必须有此消息头
@Controller
@RequestMapping("/two")
public class TwoController {


    /**
     * 获取头信息
     * 只获取头信息中的Accept-Language对应的数据(记得使用浏览器测试)
     * */
    @RequestMapping("/show1")
    public String show1(@RequestHeader(value="msg1") String msg){
        System.out.println(msg);
        return "success";
    }
}

4.数据传递
返回值为:字符串
/**
 * 返回值为:字符串
 * */
@Controller
@RequestMapping("/string")
public class StringController_01 {

    /**
     * 进入首页
     * */
    @RequestMapping("/show")
    public String show(){
        return "index";
    }

    /*
    * 充当试图的逻辑名称,默认页面跳转为请求转发方式
    * */
    @RequestMapping("/show1")
    public String show1(){
        System.out.println("=========show1=========");
        return "success_String";
    }


      /*
      * 作充当一次请求转发或重定向
      * */
    @RequestMapping("/show2")
    public String show2(){
        System.out.println("=========show2=========");
        return "redirect:show1";
    }

    @RequestMapping("/show3")
    public String show3(){
        System.out.println("=========show3=========");
        return "forward:show1";
    }


    @RequestMapping("/show4")
    public String show4(HttpServletRequest request){
        System.out.println("=========show4=========");
        //1.查询数据库(模拟)
        Emp emp = new Emp(1,"张毅老师","男");
        //2.获取session
        request.getSession().setAttribute("emp",emp);
        return "success_String";
    }

}

返回值为:json

@ResponseBody 对象====>json

位置:1.类

2.方法

@RequestBody json====>对象

位置:方法参数

@RestController = @Controller + @ResponseBody

@Controller
@RequestMapping("/json")
public class JsonController_02 {

    /**
     *
     * @ResponseBody   对象====>json
     *  位置:1.类
     *      2.方法
     *
     *
     * @RequestBody    json====>对象
     * 位置:方法参数
     *
     * @RestController   =  @Controller  +  @ResponseBody
     *
     * */

    @RequestMapping("/show1")
    @ResponseBody
    public List<Emp> show1(){
        //1模拟数据库
        Emp emp1 = new Emp(1,"张毅老师","男");
        Emp emp2 = new Emp(2,"张毅老师","男");
        Emp emp3 = new Emp(3,"张毅老师","男");
        List<Emp> list = new ArrayList<>();
        list.add(emp1);
        list.add(emp2);
        list.add(emp3);
        return list;
    }

    @RequestMapping("/show2")
    @ResponseBody
    public String show2(){
        return "helloWorld";
    }

}

5.文件上传

5.1 添加坐标

<!--文件上传-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.3</version>
        </dependency>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
    文件上传:
    <ol>
        <li>坐标</li>
        <li>制作页面-form表单编码</li>
        <li>通过*****接受文件</li>
    </ol>
    <hr/>
    <form action="fileupload" method="post" enctype="multipart/form-data">
        用户名:<input name="uname"/><br/>
        图片:<input name="upic" type="file"/><br/>
        <input type="submit" value="上传"/>
    </form>

</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
    成功页面
<!--    <span>#(session.picname)</span>-->
</body>
</html>
package com.ztt.springboot_web_05.controller;

import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;



/**
 * @author 甜甜
 * @version 1.0
 * @since 2024/8/12
 */
@Controller
public class UserController {

    //进入测试页面
    @RequestMapping("/show")
    public String show(){
        return "index";
    }
    //文件上传
    @RequestMapping("/fileupload")
    public String fileupload(String uname, MultipartFile upic, HttpServletRequest request){
        System.out.println("用户名:"+uname);
        System.out.println(upic);
        System.out.println(upic.getOriginalFilename());
        System.out.println(upic.getName());

        return "success";
    }
}

6.注册Servlet三大组件 Servlet/Filter/Listener

而由于 Spring Boot 默认是以 jar 包的方式运行嵌入式Servlet容器来启动应用,没有web.xml文件, Spring提供以下Bean来注册三大组件

ServletRegistrationBean 注册自定义

Servlet FilterRegistrationBean 注册自定义Filter

ServletListenerRegistrationBean 注册自定义Listener

MyFilter:
package com.ztt.springboot_web_06.Filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = {"/*"})
public class MyFilter implements Filter{


    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        System.out.println("============请求过滤");

        request.setCharacterEncoding("utf-8");

        //分水岭
        chain.doFilter(request, response);


        response.setCharacterEncoding("utf-8");

        System.out.println("============响应过滤");
    }

}
MyListener:
package com.ztt.springboot_web_06.listener;


import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionAttributeListener;

@WebListener
public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContextListener.super.contextInitialized(sce);
        System.out.println("-------------MyListener inited !");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContextListener.super.contextDestroyed(sce);
        System.out.println("----------------MyListener Destroy !");
    }
}
MyServlet:
package com.ztt.springboot_web_06.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/myServlet")
public class MyServlet extends HttpServlet {

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {

        System.out.println("进入servlet");
        resp.getWriter().println("<h1>hello world</h1>");
    };

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);

    }
}

如果使用传统 @WebFilter...实现注册也可以 条件:

1.一定是自定义组件

2.启动类添加@ServletComponentScan

7.切换为其他嵌入式Servlet容器

SpringBoot 默认针对Servlet容器提供以下支持:

  • Tomcat(默认使用)
  • Jetty :支持长连接项目(如:聊天页面)[ˈdʒeti]
  • Undertow : 不支持 JSP , 但是并发性能高,是高性能非阻塞的容器[ˈʌndətəʊ]

默认Tomcat容器

在spring-boot-starter-web启动器中默认引入了tomcat容器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.0.RELEASE</version>
<scope>compile</scope>
</dependency>

切换 Jetty 容器

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除tomcat容器 -->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入其他的Servlet容器-->
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>

使用外置Servlet容器Tomcat9.x

嵌入式Servlet容器:运行启动类就可启动,或将项目打成可执行的 jar 包

优点:简单、快捷;

缺点:默认不支持JSP、优化定制比较复杂使用定制器, 还需要知道 每个功能 的底层原理

外置Servlet容器:配置 Tomcat, 将项目部署到Tomcat中运行

当前课程中博客项目的实战源码是我在 GitHub上开源项目 My-Blog,目前已有 3000 多个 star:本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 大部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 个人博客项目功能的讲解,通过本课程的学习,不仅仅让你掌握基本的 Spring Boot 开发能力以及 Spring Boot 项目的大部分开发使用场景,同时帮你提前甄别和处理掉将要遇到的技术难点,认真学完这个课程后,你将会对 Spring Boot 有更加深入而全面的了解,同时你也会得到一个大家都在使用的博客系统源码,你可以根据自己的需求和想法进行改造,也可以直接使用它来作为自己的个人网站,这个课程一定会给你带来巨大的收获。作者寄语本课程录制于 2020 年,代码基于 Spring Boot 2.x 版本。到目前为止,Spring Boot 技术栈也有一些版本升级,比如 Spring Boot 2.7 发版、Spring Boot 3.x 版本发布正式版本。对于这些情况,笔者会在本课程实战项目的开源仓库中创建不同的代码分支,保持实战项目的源码更新,保证读者朋友们不会学习过气的知识点。课程特色 课程内容紧贴 Spring Boot 技术栈,涵盖大部分 Spring Boot 使用场景。开发教程详细完整、文档资源齐全、实验过程循序渐进简单明了。实践项目页面美观且实用,交互效果完美。包含从零搭建项目、以及完整的后台管理系统和博客展示系统两个系统的功能开发流程。技术栈新颖且知识点丰富,学习后可以提升大家对于知识的理解和掌握,对于提升你的市场竞争力有一定的帮助。实战项目预览    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值