Skip to content

Navigation Menu

Sign in
Sign up

Latest commit

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

1. Web 应用程序简介

2. 编写与设置 Servlet

2.1. 第一个 Servlet 程序

  • Servlet
@WebServlet("/hello.view")
public class HelloServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 // 设置响应内容类型器
 response.setContentType("text/html;charset=UTF-8");
 // 取得响应输出对象
 PrintWriter out = response.getWriter();
 // 取得 "请求参数"
 String name = request.getParameter("name");
 out.println("<html>");
 out.println("<head>");
 out.println("<title>HelloServlet</title>");
 out.println("</head>");
 out.println("<body>");
 out.println("<p>hello, " + name + "! </p>");
 out.println("</body>");
 out.println("</html>");
 out.close();
 }
}
  • 配置
<web-app>
 <servlet>
 <servlet-name>hello</servlet-name>
 <servlet-class>org.example.java_web.servlet.hello.HelloServlet</servlet-class>
 </servlet>
 <servlet-mapping>
 <servlet-name>hello</servlet-name>
 <url-pattern>/hello.view</url-pattern>
 </servlet-mapping>
</web-app>
@WebServlet("/hello.view")
  • 访问
/hello.view?name=zhangsan
<p>hello, zhangsan!</p>

2.2. URL 模式设置

  • requestURI = contextURI + servletURI + pathInfo
out.println("<p>request.getRequestURI(): " + request.getRequestURI() + "</p>");
out.println("<p>request.getContextPath(): " + request.getContextPath() + "</p>");
out.println("<p>request.getServletPath(): " + request.getServletPath() + "</p>");
out.println("<p>request.getPathInfo(): " + request.getPathInfo() + "</p>");
<p>request.getRequestURI(): /JavaWeb_Servlet_war/path.view</p>
<p>request.getContextPath(): /JavaWeb_Servlet_war</p>
<p>request.getServletPath(): /path.view</p>
<p>request.getPathInfo(): null</p>

3. 请求

3.1. 处理请求参数与标头

  • request.getHeaderNames()
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements()) {
 String name = names.nextElement();
 out.println("<p>" + name + ": " + request.getHeader(name) + "</p>");
}
<p>host: localhost:8080</p>
<p>connection: keep-alive</p>
<p>cache-control: max-age=0</p>
<p>sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Microsoft Edge";v="90"</p>
<p>sec-ch-ua-mobile: ?0</p>
<p>upgrade-insecure-requests: 1</p>
<p>user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46</p>
<p>accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9</p>
<p>sec-fetch-site: none</p>
<p>sec-fetch-mode: navigate</p>
<p>sec-fetch-user: ?1</p>
<p>sec-fetch-dest: document</p>
<p>accept-encoding: gzip, deflate, br</p>
<p>accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6</p>
  • request.getHeader()
out.println("<p>host: " + request.getHeader("host") + "</p>");
  • request.getHeaders()
Enumeration<String> uas = request.getHeaders("user-agent");
while (uas.hasMoreElements()) {
 String ua = uas.nextElement();
 out.println("<p>user-agent: " + ua + "</p>");
}

3.2. 请求参数编码处理

3.2.1. get 请求

response.setContentType("text/html;charset=UTF-8");

3.2.2. post 请求

request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");

3.3. 读取 Body 内容

private String readBody(HttpServletRequest request) throws IOException {
 BufferedReader reader = request.getReader();
 String input;
 StringBuilder requestBody = new StringBuilder();
 while ((input = reader.readLine()) != null) {
 requestBody.append(input).append("<br>");
 }
 return requestBody.toString();
}
<p>user=%E5%BC%A0%E4%B8%89&password=123456&btn=submit</p>

3.4. 获取上传文件

3.4.1. upload1

  • form 标签需要设置 enctype="multipart/form-data"
<form action="upload1.do" method="post" enctype="multipart/form-data">
 <div class="form-group">
 <input id="file" type="file" name="filename" value="" />
 </div>
 <button class="btn btn-primary" name="btn" value="upload">上传</button>
</form>
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 // 读取请求的 body
 byte[] body = readBody(request);
 // 取得所有 body 内容的字符串显示
 String textBody = new String(body, StandardCharsets.ISO_8859_1);
 // 取得上传的文件名称
 String fileName = getFileName(textBody);
 // 取得文件开始和结束位置
 Position p = getFilePosition(request, textBody);
 // 输出至文件
 writeTo("d:/" + fileName, body, p);
}
private byte[] readBody(HttpServletRequest request) throws IOException {
 int formDataLength = request.getContentLength();
 // 获取 ServletInputStream 对象
 DataInputStream dataStream = new DataInputStream(request.getInputStream());
 byte[] body = new byte[formDataLength];
 int totalBytes = 0;
 while (totalBytes < formDataLength) {
 int bytes = dataStream.read(body, totalBytes, formDataLength);
 totalBytes += bytes;
 }
 return body;
}
private class Position {
 int begin;
 int end;
 Position(int begin, int end) {
 this.begin = begin;
 this.end = end;
 }
}
private Position getFilePosition(HttpServletRequest request, String textBody) {
 // 取得文件区段边界位置
 String contentType = request.getContentType();
 String boundaryText = contentType.substring(contentType.lastIndexOf("=") + 1);
 // 取得实际上传文件的起始位置和结束位置
 int pos = textBody.indexOf("filename\"");
 pos = textBody.indexOf("\n", pos) + 1;
 pos = textBody.indexOf("\n", pos) + 1;
 pos = textBody.indexOf("\n", pos) + 1;
 int boundaryLoc = textBody.indexOf(boundaryText, pos) - 4;
 int begin = ((textBody.substring(0, pos).getBytes(StandardCharsets.ISO_8859_1)).length);
 int end = ((textBody.substring(0, boundaryLoc).getBytes(StandardCharsets.ISO_8859_1)).length);
 return new Position(begin, end);
}
private String getFileName(String textBody) {
 // 截掉filename前面
 String fileName = textBody.substring(textBody.indexOf("filename=\"") + 10);
 // 截掉 换行符后面的内容
 fileName = fileName.substring(0, fileName.indexOf("\n"));
 // 截掉文件所在目录 和最后一个 "
 fileName = fileName.substring(fileName.lastIndexOf("\\") + 1, fileName.indexOf("\""));
 return fileName;
}
private void writeTo(String fileName, byte[] body, Position p) throws IOException {
 FileOutputStream fos = new FileOutputStream(fileName);
 fos.write(body, p.begin, (p.end - p.begin));
 fos.flush();
 fos.close();
}

3.4.2. upload2

  • Tomcat 中必须设置 MultipartConfig 标注才能使用 getPart() 相关的 API
@MultipartConfig
@WebServlet("/upload2.do")
public class Upload2Servlet extends HttpServlet {
 // ...
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 // 使用 getPart() 取得 Part 对象
 Part part = request.getPart("filename");
 String fileName = getFilename(part);
 writeTo("d:/" + fileName, part);
}
private String getFilename(Part part) { // 取得上传文件名
 String header = part.getHeader("Content-Disposition");
 return header.substring(header.indexOf("filename=\"") + 10, header.lastIndexOf("\""));
}
private void writeTo(String fileName, Part part) throws IOException {
 InputStream in = part.getInputStream();
 OutputStream out = new FileOutputStream(fileName);
 byte[] buffer = new byte[1024];
 int length = -1;
 while ((length = in.read(buffer)) != -1) {
 out.write(buffer, 0, length);
 }
 in.close();
 out.close();
}

3.4.3. upload3

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 Part part = request.getPart("filename");
 String fileName = getFilename(part);
 part.write("d:/" + fileName);
}
private String getFilename(Part part) { // 取得上传文件名
 String header = part.getHeader("Content-Disposition");
 return header.substring(header.indexOf("filename=\"") + 10, header.lastIndexOf("\""));
}

3.4.4. upload4

@MultipartConfig(location = "d:/")
@WebServlet("/upload4.do")
public class Upload4Servlet extends HttpServlet {
 // ...
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 for (Part part : request.getParts()) {
 if (part.getName().startsWith("file")) {
 String fileName = getFilename(part);
 part.write("d:/" + fileName);
 }
 }
}

3.5. 调派请求

3.5.1. include

@WebServlet("/some.view")
public class SomeServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 PrintWriter out = response.getWriter();
 out.println("Some do one ... ");
 RequestDispatcher rd = request.getRequestDispatcher("other.view");
 rd.include(request, response);
 out.println("Some do two ... ");
 out.close();
 }
}
@WebServlet("/other.view")
public class OtherServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 PrintWriter out = response.getWriter();
 out.println("Other do one ... ");
 // 这里不要关闭 out,否则页面将无法继续写入 some.view 后面的内容
 }
}

3.5.2. forward

public class HelloModel {
 private final Map<String, String> messages = new HashMap<>();
 public HelloModel() {
 messages.put("zhangsan", "hello");
 messages.put("lisi", "welcome");
 messages.put("wangwu", "hi");
 }
 public String doHello(String user) {
 String massage = messages.get(user);
 return massage + ", " + user + "!";
 }
}
@WebServlet("/request/hello.do")
public class HelloController extends HttpServlet {
 private final HelloModel helloModel = new HelloModel();
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 String name = request.getParameter("user");
 String message = helloModel.doHello(name);
 request.setAttribute("message", message);
 request.getRequestDispatcher("hello.view").forward(request, response);
 }
}
@WebServlet("/request/hello.view")
public class HelloView extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 String user = request.getParameter("user");
 String message = (String) request.getAttribute("message");
 String htmlTemplate = ""
 + "<html>"
 + "<head>"
 + "<title>%s</title>"
 + "</head>"
 + "<body>"
 + "<p>%s</p>"
 + "</body>"
 + "</html>";
 String html = String.format(htmlTemplate, user, message);
 response.getWriter().print(html);
 }
}
/request/hello.do?user=zhangsan
<p>hello, zhangsan!</p>

4. 响应

4.1. 输出字符

response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

4.2. 输出二进制字符

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
 <locale-encoding-mapping-list>
 <locale-encoding-mapping>
 <locale>zh_CN</locale>
 <encoding>UTF-8</encoding>
 </locale-encoding-mapping>
 </locale-encoding-mapping-list>
 <mime-mapping>
 <extension>pdf</extension>
 <mime-type>application/pdf</mime-type>
 </mime-mapping>
</web-app>
@WebServlet("/download.do")
public class DownloadServlet extends HttpServlet {
 // ...
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 // 设置内容类型
 response.setContentType("application/pdf");
 // 输入串流
 InputStream in = getServletContext().getResourceAsStream("WEB-INF/content.pdf");
 // 输出串流
 OutputStream out = response.getOutputStream();
 // 读取文件并输出到浏览器
 writeBytes(in, out);
}
private void writeBytes(InputStream in, OutputStream out)
 throws IOException {
 byte[] buffer = new byte[1024];
 int length;
 while ((length = in.read(buffer)) != -1) {
 out.write(buffer, 0, length);
 }
 in.close();
 out.close();
}

4.3. 重定向

@WebServlet("/redirect.do")
public class RedirectServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 response.sendRedirect("https://www.baidu.com");
 // String msg = "bad request";
 // response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
 }
}

5. 基本会话管理

5.1. 使用隐藏域

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 PrintWriter out = response.getWriter();
 out.println("<html>");
 out.println("<head>");
 out.println("<title>QuestionServlet</title>");
 out.println("</head>");
 out.println("<body>");
 out.println("<form action='question.view' method='post'>");
 String page = request.getParameter("page");
 String nextPage = "下一页";
 String finish = "完成";
 if (page == null) {
 out.println("问题一:<input type='text' name='p1q1'><br>");
 out.println("问题一:<input type='text' name='p1q2'><br>");
 out.println("<input type='submit' name='page' value='下一页'>");
 } else if (nextPage.equals(page)) {
 String plq1 = request.getParameter("p1q1");
 String plq2 = request.getParameter("p1q2");
 out.println("问题三:<input type='text' name='p2q1'><br>");
 out.println("<input type='hidden' name='p1q1' value='" + plq1 + "'>");
 out.println("<input type='hidden' name='p1q2' value='" + plq2 + "'>");
 out.println("<input type='submit' name='page' value='完成'>");
 } else if (finish.equals(page)) {
 out.println(request.getParameter("p1q1") + "<br>");
 out.println(request.getParameter("p1q2") + "<br>");
 out.println(request.getParameter("p2q1") + "<br>");
 }
 out.println("</form>");
 out.println("</body>");
 out.println("</html>");
 out.close();
}
<form action="question.view" method="post">
 问题三:<input type="text" name="p2q1" /><br />
 <input type="hidden" name="p1q1" value="qqq" />
 <input type="hidden" name="p1q2" value="kkk" />
 <input type="submit" name="page" value="完成" />
</form>

5.2. 使用 Cookie

<form action="cookie/login.do" method="post">
 <div class="form-group">
 <label for="username">用户名: </label>
 <input id="username" class="form-control" type="text" name="username" value="" />
 </div>
 <div class="form-group">
 <label for="password">密码: </label>
 <input id="password" class="form-control" type="password" name="password" value="" />
 </div>
 <div class="form-group">
 <label for="login">记住我: </label>
 <input id="login" class="form-control" type="checkbox" name="login" value="auto" />
 </div>
 <button class="btn btn-primary" name="btn" value="submit">提交</button>
</form>
@WebServlet("/cookie/login.do")
public class LoginServlet extends HttpServlet {
 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 String username = request.getParameter("username");
 String password = request.getParameter("password");
 String trueUsername = "zhangsan";
 String truePassword = "123456";
 String loginMode = "auto";
 if (trueUsername.equals(username) && truePassword.equals(password)) {
 String login = request.getParameter("login");
 if (loginMode.equals(login)) {
 Cookie cookie = new Cookie("user", username);
 cookie.setMaxAge(60 * 60 * 24 * 7);
 response.addCookie(cookie);
 }
 request.setAttribute("username", username);
 request.getRequestDispatcher("user.view").forward(request, response);
 } else {
 response.sendRedirect("../cookie_login.html");
 }
 }
}
@WebServlet("/cookie/user.view")
public class UserServlet extends HttpServlet {
 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 String usernameKey = "username";
 if (request.getAttribute(usernameKey) == null) {
 response.sendRedirect("../cookie_login.html");
 }
 PrintWriter out = response.getWriter();
 out.println("<html>");
 out.println("<head>");
 out.println("<title>UserServlet</title>");
 out.println("</head>");
 out.println("<body>");
 out.println("<p>" + request.getAttribute(usernameKey) + "</p>");
 out.println("</body>");
 out.println("</html>");
 out.close();
 }
}
Set-Cookie: user=zhangsan; Max-Age=604800; Expires=date;

5.3. 使用 URL 重写

@WebServlet("/cookie/search.do")
public class SearchServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 PrintWriter out = response.getWriter();
 out.println("<html>");
 out.println("<head>");
 out.println("<title>SearchServlet</title>");
 out.println("</head>");
 out.println("<body>");
 int perPage = 10;
 int totalPages = 20;
 String page = request.getParameter("page");
 if (page == null) {
 page = "1";
 }
 int count = Integer.parseInt(page);
 int begin = 10 * count - 9;
 int end = 10 * count;
 out.println("<p>从 " + begin + " 到 " + end + " 的搜索结果</p>");
 out.println("<ul>");
 String line = "<li>搜索结果: %d</li>";
 for (int i = 0; i < perPage; i++) {
 out.println(String.format(line, begin + i));
 }
 out.println("</ul>");
 for (int i = 1; i <= totalPages; i++) {
 if (i == count) {
 out.print(i);
 continue;
 }
 out.println("<a href='search.do?page=" + i + "'>" + i + "</a>");
 }
 out.println("</body>");
 out.println("</html>");
 out.close();
 }
}

6. HttpSession

6.1. 实现隐藏域

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 PrintWriter out = response.getWriter();
 out.println("<html>");
 out.println("<head>");
 out.println("<title>QuestionServlet</title>");
 out.println("</head>");
 out.println("<body>");
 out.println("<form action='question.view' method='post'>");
 String page = request.getParameter("page");
 String nextPage = "下一页";
 String finish = "完成";
 if (page == null) {
 out.println("问题一:<input type='text' name='p1q1'><br>");
 out.println("问题一:<input type='text' name='p1q2'><br>");
 out.println("<input type='submit' name='page' value='下一页'>");
 } else if (nextPage.equals(page)) {
 String plq1 = request.getParameter("p1q1");
 String plq2 = request.getParameter("p1q2");
 HttpSession session = request.getSession();
 session.setAttribute("p1q1", plq1);
 session.setAttribute("p1q2", plq2);
 out.println("问题三:<input type='text' name='p2q1'><br>");
 out.println("<input type='hidden' name='p1q1' value='" + plq1 + "'>");
 out.println("<input type='hidden' name='p1q2' value='" + plq2 + "'>");
 out.println("<input type='submit' name='page' value='完成'>");
 } else if (finish.equals(page)) {
 HttpSession session = request.getSession();
 out.println(session.getAttribute("p1q1") + "<br>");
 out.println(session.getAttribute("p1q2") + "<br>");
 out.println(request.getParameter("p2q1") + "<br>");
 }
 out.println("</form>");
 out.println("</body>");
 out.println("</html>");
 out.close();
}

6.2. 实现登录

@WebServlet("/session/login.do")
public class LoginServlet extends HttpServlet {
 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws ServletException, IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 String username = request.getParameter("username");
 String password = request.getParameter("password");
 String trueUsername = "zhangsan";
 String truePassword = "123456";
 if (trueUsername.equals(username) && truePassword.equals(password)) {
 request.getSession().setAttribute("username", username);
 request.getRequestDispatcher("user.view").forward(request, response);
 } else {
 response.sendRedirect("../session_login.html");
 }
 }
}
@WebServlet("/session/user.view")
public class UserServlet extends HttpServlet {
 @Override
 protected void doPost(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 String usernameKey = "username";
 if (request.getSession().getAttribute(usernameKey) == null) {
 response.sendRedirect("../session_login.html");
 }
 PrintWriter out = response.getWriter();
 out.println("<html>");
 out.println("<head>");
 out.println("<title>UserServlet</title>");
 out.println("</head>");
 out.println("<body>");
 out.println("<p>" + request.getSession().getAttribute(usernameKey) + "</p>");
 out.println("<a href='logout.view'>注销</a>");
 out.println("</body>");
 out.println("</html>");
 out.close();
 }
}
@WebServlet("/session/logout.view")
public class LogoutServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 HttpSession session = request.getSession();
 String username = (String) session.getAttribute("username");
 session.invalidate();
 PrintWriter out = response.getWriter();
 out.println("<html>");
 out.println("<head>");
 out.println("<title>LogoutServlet</title>");
 out.println("</head>");
 out.println("<body>");
 out.println("<p>" + username + " 已注销</p>");
 out.println("</body>");
 out.println("</html>");
 out.close();
 }
}
<web-app>
 <session-config>
 <!-- minutes -->
 <session-timeout>30</session-timeout>
 <cookie-config>
 <name>sid</name>
 <http-only>true</http-only>
 </cookie-config>
 </session-config>
</web-app>

6.3. 实现 URL 重写

@WebServlet("/session/counter.do")
public class CounterServlet extends HttpServlet {
 @Override
 protected void doGet(HttpServletRequest request, HttpServletResponse response)
 throws IOException {
 request.setCharacterEncoding("UTF-8");
 response.setContentType("text/html;charset=UTF-8");
 int count = 0;
 String countStr = "count";
 HttpSession session = request.getSession();
 if (session.getAttribute(countStr) != null) {
 Integer c = (Integer) session.getAttribute(countStr);
 count = c + 1;
 }
 session.setAttribute(countStr, count);
 PrintWriter out = response.getWriter();
 out.println("<html>");
 out.println("<head>");
 out.println("<title>CounterServlet</title>");
 out.println("</head>");
 out.println("<body>");
 out.println("<p>Servlet Count: " + count + " </p>");
 out.println("<a href='" + response.encodeURL("counter") + ".do'>递增</a>");
 out.println("</body>");
 out.println("</html>");
 out.close();
 }
}

About

JavaWeb-01-Servlet基础,语雀浏览链接:https://www.yuque.com/happyflyer/javaweb-topics/ay6ztb

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /