Go全栈猜数字游戏登录接口404错误求助
Go全栈猜数字游戏登录无响应问题修复
问题描述
使用Go开发全栈猜数字游戏,启动前后端后打开index.html点击登录按钮无反应,后端日志显示:
[2023-02-14T07:20:33.479Z] "POST /login" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Safari/605.1.15" [2023-02-14T07:20:33.480Z] "POST /login" Error (404): "Not found"
期望输入用户名admin、密码password后,页面切换到猜数字界面并正常交互。
错误分析
- 端口不匹配:后端监听8081端口,但前端
fetch请求未指定端口(直接打开本地文件时用file://协议,无法发送请求到后端),同时表单action写的是http://localhost:8080,与后端端口不一致。 - 前端响应处理错误:猜数字接口后端返回
status字段,但前端代码判断的是data.result,导致结果无法显示;且未处理HTTP错误状态码(如404、401)。 - 后端认证逻辑存在并发问题:全局变量
authenticated为所有请求共享,多用户登录会互相干扰,且未验证token有效性。 - 样式未生效:HTML中的容器未使用
style.css定义的.container类,导致布局异常。
修复方案
1. 统一前后端端口,修正请求地址
前端fetch请求指定后端完整地址http://localhost:8081,移除表单action属性(JS已处理提交逻辑)。
2. 修复前端响应处理逻辑
- 猜数字部分将
data.result改为data.status,根据不同状态生成提示文本。 - 在
fetch流程中先检查响应是否成功,否则抛出错误并提示用户。
3. 改进后端认证逻辑,基于token验证
- 用
map存储有效token,替代全局authenticated变量。 - 猜数字接口从请求头获取token并验证有效性。
4. 修正样式类,让布局生效
给login-container和guess-container添加container类。
修正后的完整代码
main.go
package main import ( "encoding/json" "log" "math/rand" "net/http" "strings" "time" "github.com/gorilla/mux" ) type GuessRequestBody struct { Guess int `json:"guess"` } type GuessResponseBody struct { Status string `json:"status"` NumGuesses int `json:"numGuesses,omitempty"` ErrorMessage string `json:"errorMessage,omitempty"` } type LoginRequestBody struct { Username string `json:"username"` Password string `json:"password"` } type LoginResponseBody struct { Token string `json:"token"` } var numToGuess int var numGuesses int var validTokens = make(map[string]bool) // 存储有效token func main() { r := mux.NewRouter() r.HandleFunc("/login", loginHandler).Methods("POST") r.HandleFunc("/guess", guessHandler).Methods("POST") log.Fatal(http.ListenAndServe(":8081", r)) } func loginHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") // 开发环境允许跨域 w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization") var requestBody LoginRequestBody err := json.NewDecoder(r.Body).Decode(&requestBody) if err != nil { http.Error(w, "Bad Request", http.StatusBadRequest) return } if requestBody.Username == "admin" && requestBody.Password == "password" { token := "my-random-token-" + time.Now().Format(time.RFC3339Nano) // 生成唯一token validTokens[token] = true responseBody := LoginResponseBody{Token: token} json.NewEncoder(w).Encode(responseBody) } else { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } } func guessHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization") // 验证token authHeader := r.Header.Get("Authorization") if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } token := strings.TrimPrefix(authHeader, "Bearer ") if !validTokens[token] { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } var requestBody GuessRequestBody err := json.NewDecoder(r.Body).Decode(&requestBody) if err != nil { http.Error(w, "Bad Request", http.StatusBadRequest) return } if numToGuess == 0 { numToGuess = rand.Intn(100) + 1 } numGuesses++ if requestBody.Guess < numToGuess { responseBody := GuessResponseBody{Status: "tooLow"} json.NewEncoder(w).Encode(responseBody) } else if requestBody.Guess > numToGuess { responseBody := GuessResponseBody{Status: "tooHigh"} json.NewEncoder(w).Encode(responseBody) } else { responseBody := GuessResponseBody{Status: "success", NumGuesses: numGuesses} json.NewEncoder(w).Encode(responseBody) numToGuess = 0 numGuesses = 0 } } func init() { rand.Seed(time.Now().UnixNano()) }
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Fullstack Guessing Game</title> <link rel="stylesheet" href="style.css"> </head> <body> <div id="login-container" class="container"> <h1>Login</h1> <form id="login-form"> <label for="username">Username:</label> <input type="text" id="username" name="username"> <br> <label for="password">Password:</label> <input type="password" id="password" name="password"> <br> <button type="submit">Login</button> </form> </div> <div id="guess-container" class="container" style="display:none;"> <h1>Guess the Number</h1> <p>Guess a number between 1 and 100:</p> <input type="number" id="guess" name="guess" min="1" max="100"> <br> <button type="button" id="submit-guess">Submit</button> <p id="result" style="display:none;"></p> </div> <script src="app.js"></script> </body> </html>
app.js
const loginForm = document.getElementById('login-form'); const loginContainer = document.getElementById('login-container'); const guessContainer = document.getElementById('guess-container'); const submitGuessBtn = document.getElementById('submit-guess'); const result = document.getElementById('result'); const BASE_URL = 'http://localhost:8081'; loginForm.addEventListener('submit', async (event) => { event.preventDefault(); const username = document.getElementById('username').value; const password = document.getElementById('password').value; try { const response = await fetch(`${BASE_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({username, password}) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); if (data.token) { localStorage.setItem('token', data.token); loginContainer.style.display = 'none'; guessContainer.style.display = 'flex'; } } catch (error) { console.error('Login failed:', error); alert('Login failed, check username/password'); } }); submitGuessBtn.addEventListener('click', async () => { const guess = parseInt(document.getElementById('guess').value); const token = localStorage.getItem('token'); if (!token) { alert('Please login first'); return; } try { const response = await fetch(`${BASE_URL}/guess`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({guess}) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); let message = ''; switch(data.status) { case 'tooLow': message = 'Your guess is too low!'; break; case 'tooHigh': message = 'Your guess is too high!'; break; case 'success': message = `Congratulations! You guessed it in ${data.numGuesses} tries!`; document.getElementById('guess').value = ''; break; default: message = 'Unknown response'; } result.innerHTML = message; result.style.display = 'block'; } catch (error) { console.error('Guess failed:', error); alert('Guess failed, please try again'); } });
style.css
.container { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; } h1 { font-size: 2em; } form { display: flex; flex-direction: column; align-items: center; margin-top: 2em; } label { font-size: 1.5em; margin-bottom: 1em; } input[type="text"], input[type="password"], input[type="number"] { font-size: 1.5em; padding: 0.5em; margin-bottom: 1em; } button { font-size: 1.5em; padding: 0.5em 1em; background-color: #4CAF50; color: white; border: none; border-radius: 0.3em; cursor: pointer; } p#result { font-size: 1.5em; margin-top: 2em; text-align: center; }
验证步骤
- 启动后端:
go run main.go - 打开
index.html页面(或用本地服务器托管前端文件,避免file://协议跨域问题) - 输入用户名
admin、密码password,点击登录,页面会切换到猜数字界面 - 输入数字提交,会显示对应的提示,猜对后会重置游戏
内容的提问来源于stack exchange,提问作者Queen Kajee
相关产品推荐
相关产品推荐

