You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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后,页面切换到猜数字界面并正常交互。

错误分析

  1. 端口不匹配:后端监听8081端口,但前端fetch请求未指定端口(直接打开本地文件时用file://协议,无法发送请求到后端),同时表单action写的是http://localhost:8080,与后端端口不一致。
  2. 前端响应处理错误:猜数字接口后端返回status字段,但前端代码判断的是data.result,导致结果无法显示;且未处理HTTP错误状态码(如404、401)。
  3. 后端认证逻辑存在并发问题:全局变量authenticated为所有请求共享,多用户登录会互相干扰,且未验证token有效性。
  4. 样式未生效: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;
}

验证步骤

  1. 启动后端:go run main.go
  2. 打开index.html页面(或用本地服务器托管前端文件,避免file://协议跨域问题)
  3. 输入用户名admin、密码password,点击登录,页面会切换到猜数字界面
  4. 输入数字提交,会显示对应的提示,猜对后会重置游戏

内容的提问来源于stack exchange,提问作者Queen Kajee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 17:01:13