预计时间:25~35 分钟。
上午我们把后端理解成“监听端口、处理请求的长期运行进程”。今晚不使用 Spring Boot,直接用 JDK 21 实现项目的第一版服务,确保你能观察:
text启动 Java 进程 → 绑定端口 → 接收 HTTP 请求 → 匹配路径 → 返回状态码和 JSON → 记录访问日志
为“AI 驱动的企业任务协作系统”建立后端运行基线:
task-api0.0.0.0:8080PORT 环境变量修改端口GET /health 返回健康状态GET /api/system-info 返回服务信息405404创建 src/App.java:
javaimport com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
public class App {
private static final String SERVICE_NAME = "task-api";
private static final String VERSION = "0.1.0";
public static void main(String[] args) throws Exception {
int port = Integer.parseInt(
System.getenv().getOrDefault("PORT", "8080")
);
HttpServer server = HttpServer.create(
new InetSocketAddress("0.0.0.0", port),
0
);
server.createContext("/health", exchange -> {
logRequest(exchange);
if (!isGet(exchange)) {
sendJson(exchange, 405, """
{"error":"method_not_allowed"}
""");
return;
}
sendJson(exchange, 200, """
{
"status": "UP",
"service": "task-api"
}
""");
});
server.createContext("/api/system-info", exchange -> {
logRequest(exchange);
if (!isGet(exchange)) {
sendJson(exchange, 405, """
{"error":"method_not_allowed"}
""");
return;
}
String body = """
{
"service": "%s",
"version": "%s",
"port": %d
}
""".formatted(SERVICE_NAME, VERSION, port);
sendJson(exchange, 200, body);
});
server.createContext("/", exchange -> {
logRequest(exchange);
sendJson(exchange, 404, """
{"error":"not_found"}
""");
});
// 暂时使用默认执行器,并发模型留到第 8 周深入。
server.setExecutor(null);
server.start();
System.out.printf(
"%s %s%n",
"Server started:",
"http://127.0.0.1:" + port
);
}
private static boolean isGet(HttpExchange exchange) {
return "GET".equalsIgnoreCase(
exchange.getRequestMethod()
);
}
private static void logRequest(HttpExchange exchange) {
System.out.printf(
"%s %s%n",
exchange.getRequestMethod(),
exchange.getRequestURI()
);
}
private static void sendJson(
HttpExchange exchange,
int status,
String json
) throws IOException {
byte[] body = json.strip()
.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set(
"Content-Type",
"application/json; charset=utf-8"
);
exchange.sendResponseHeaders(status, body.length);
try (var output = exchange.getResponseBody()) {
output.write(body);
}
}
}bashmkdir -p out
javac \
--add-modules jdk.httpserver \
-d out \
src/App.java
java \
--add-modules jdk.httpserver \
-cp out \
App使用其他端口启动:
bashPORT=9090 java \
--add-modules jdk.httpserver \
-cp out \
App健康检查:
bashcurl -i http://127.0.0.1:8080/health
服务信息:
bashcurl -i http://127.0.0.1:8080/api/system-info
测试不支持的方法:
bashcurl -i \ -X POST \ http://127.0.0.1:8080/health
测试未知路径:
bashcurl -i http://127.0.0.1:8080/not-exist
最终应该分别看到:
| 请求 | 状态码 |
|---|---|
GET /health |
200 |
GET /api/system-info |
200 |
POST /health |
405 |
GET /not-exist |
404 |
还可以检查端口是否真的被进程监听:
bashlsof -nP -iTCP:8080 -sTCP:LISTEN
0.0.0.0代码绑定 0.0.0.0,表示服务接受来自所有网络接口的连接,为以后进入 Docker 做准备。
但启动提示使用的是 127.0.0.1,因为客户端不能把 0.0.0.0 当作普通服务器地址使用:
text0.0.0.0 → 服务端的监听范围 127.0.0.1 → 客户端访问本机的地址
“Address already in use”:
bashlsof -nP -iTCP:8080 -sTCP:LISTEN
说明端口已被其他进程占用,可以关闭旧进程或通过 PORT=9090 更换端口。
“Connection refused”通常意味着:
返回中文后响应异常:
发送长度必须使用:
javabyte[] body = json.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(status, body.length);不能直接使用 json.length(),因为字符数量不一定等于 UTF-8 字节数量。
增加接口:
httpGET /api/tasks
暂时不连接数据库,只返回统一结构:
json{
"data": [],
"total": 0
}同时保证:
GET 返回 200POST 返回 405127.0.0.1 时,外部流量可能无法进入;服务通常需要监听 0.0.0.0。完成今晚练习后,我们就拥有了项目的第一个后端运行基线。明天上午会继续讲:一条 HTTP 请求到底由哪些部分组成,以及前端调用接口时为什么会遇到跨域、状态码和请求头问题。

扫码关注公众号,每周更新实用前端干货