求大佬指点 我调用秋叶启动器的Webui API一直报401的错怎么办?

前端文生图代码

<script setup>
import {ref} from 'vue'

import {
QuestionFilled
} from '@element-plus/icons-vue'
import {useRouter} from 'vue-router'
const router = useRouter()
import {userStore} from '../stores/user.js';
const ui = userStore()
const textAigc = () => {
  router.push('/textAigc')
}

const imgaigc = () => {
  router.push("/imgaigc")
} 
const Loginjump = async () => {
  if (ui.userinfo.username === 'GunChan') {
    await router.push('/title/manage')
  } else {
    await router.push('/aigc/manage')
  }
}

//定义数据模型
const textmodel = ref(
    {
      "enable_hr": false,
      "denoising_strength": 0.78,
      "firstphase_width": 0,
      "firstphase_height": 0,
      "hr_scale": 2,
      "hr_upscaler": null,
      "hr_second_pass_steps": 0,
      "hr_resize_x": 0,
      "hr_resize_y": 0,
      "prompt": "",
      "seed": -1,
      "subseed": -1,
      "subseed_strength": 0,
      "seed_resize_from_h": -1,
      "seed_resize_from_w": -1,
      "sampler_name": "DPM++ 2S a",
      "batch_size": 1,
      "n_iter": 1,
      "steps": 20,
      "cfg_scale": 7,
      "width": 512,
      "height": 512,
      "restore_faces": false,
      "tiling": false,
      "do_not_save_samples": false,
      "do_not_save_grid": false,
      "negative_prompt": "nsfw,naked,nude,topless,(worst quality:2),",
      "eta": 0,
      "s_churn": 0,
      "s_tmax": 0,
      "s_tmin": 0,
      "s_noise": 1,
      "sampler_index": "DPM++ 2S a",
      "script_name": null,
      "send_images": true,
      "save_images": false,
      "task_id": "lsh4ksce44q5egvtchy0m3jmjkoypz6l"
    }
)

const re = ref('');
const imageData = ref(null);
const errorMessage = ref('');

const fetchImageData = async (text) => {
  console.log(text.value)
  console.log(text)
  const keys = Object.entries(text.value);
  keys.forEach(([key, value]) => {
    re[key] = value;
  });
  console.log(re);
  try {
    const response = await fetch('http://localhost:8080/sdapi/v1/txt2img', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization':' Basic MTIzNDU2OjEyMzQ1Ng=='
      },
      body: JSON.stringify(re)
    });
    if (!response.ok) {
      throw new Error('获取图片数据失败1');
    }
    console.log(response)
    return response.json();
  } catch (error) {
    throw new Error('获取图片数据失败2');
  }
};

const processTextAndFetchImage = async () => {
    console.log(textmodel)
  textmodel.value.sampler_index=textmodel.value.sampler_name
    try {
      const imageDataValue = await fetchImageData(textmodel);
      console.log(imageDataValue)
      const cleanedImage = imageDataValue.images[0].replace(/["\[\]]/g, '');
      console.log(cleanedImage)
      imageData.value = `data:image/png;base64,${cleanedImage}`;
    } catch (error) {
      errorMessage.value = '生成图片失败1';
      console.error(error);
    }

};
const con =  () => {
  console.log()
};
const radio1 = ref('New York')
const value1 = ref(0)
const formatTooltip = (val) => {
  return val;
}
// const download = () => {
//   const link = document.createElement('a');
//   link.herf = imageData.value;
//   console.log(imageData.value)
//   link.download = 'downloaded_image.jpg'; // 设置下载时的默认文件名
//   document.body.appendChild(link);
//   link.click();
//   document.body.removeChild(link);
// }
const download = () => {
  console.log(typeof imageData.value)
  console.log(imageData.value)
  const base64Data = imageData.value.replace(/^data:image\/(png|jpg|jpeg);base64,/, '');
  const blob = base64toBlob(base64Data, 'image/png');
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'downloaded_image.png';
  document.body.appendChild(a);
  a.click();
  window.URL.revokeObjectURL(url);
  document.body.removeChild(a);
}
const base64toBlob = (base64Data, contentType) => {
  const sliceSize = 512;
  const byteCharacters = atob(base64Data);
  const byteArrays = [];
  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);
    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }
    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }
  return new Blob(byteArrays, { type: contentType });
}
const random =() =>{
  const randomNumber = Math.floor(Math.random() * 9999) + 1;
  textmodel.value.seed = randomNumber;
}
const showbox = ref(false)
const showbox1 = ref(false)
const showbox2 = ref(false)
const showbox3 = ref(false)
</script>
<template>

  <body>
  <el-container>
<!--    头部区域-->
    <el-header>
      <div class="container">
      <!-- 导航栏 -->
      <nav class="nav__container">
        <div class="logo">
          <img src="../assets/logo.png" alt="Logo" class="logo-image" />
          <router-link to="/IndexView" class="logo-text">壮锦艺坊</router-link>
        </div>

        <!-- 导航菜单 -->
        <div class="nav__menu" id="nav-menu">
          <ul class="nav__list">
            <li class="nav__item">
              <router-link to="/IndexView" class="nav__link" active-class="active-link">首页</router-link>
            </li>
            <li class="nav__item">
              <router-link to="/Zhuangview" class="nav__link" active-class="active-link">壮锦背景</router-link>
            </li>
            <li class="nav__item">
              <router-link to="/ZhuangPattern" class="nav__link" active-class="active-link">壮锦纹样</router-link>
            </li>
            <li class="nav__item">
              <router-link to="/textAigc" class="nav__link" active-class="active-link">AIGC创作</router-link>
            </li>
            <li class="nav__item">
              <router-link to="/ProductList" class="nav__link" active-class="active-link">在线定制</router-link>
            </li>
          </ul>
        </div>

        <div class="user-profile">
          <img src="../assets/01.png" alt="Avatar" class="avatar" />
          
          <a class="nav__link" @click="Loginjump">用户名</a>
        </div>
      </nav>
    </div>
    </el-header>
    <el-container>
      <!--    左部区域-->
      <el-aside class="aside">
        <el-row class="login-page">
          <el-col :span="6" :offset="3" class="form">
            <el-button class="imgaigc" @click="textAigc">文生图</el-button>
            <el-button class="imgaigc" @click="imgaigc">图生图</el-button>
            <!-- 表单 -->
            <el-form ref="form" size="large" autocomplete="off" >
              <el-form-item>
                <h4>正向提示词</h4>
                <el-icon size="large" class="question1" @click="showbox= true;"><QuestionFilled /></el-icon>
                <div class="text-box">
                  <el-dialog v-model="showbox" type="text" >正向提示词:正向提示词是用来规定生成图片时,图片所呈现的相关特征。添加&ltlora:xiu:1&gt可以使用壮锦的Lora模型</el-dialog>
                </div>
              </el-form-item>
              <el-form-item prop="prompt" class="textInput">
                <textarea class="text__ai" placeholder="请输入正向提示词(英文)" v-model="textmodel.prompt"
                          type="textarea"></textarea>
              </el-form-item>
              <el-form-item>
                <h4>反向提示词</h4>
                <el-icon size="large" class="question1" @click="showbox1= true;"><QuestionFilled /></el-icon>
                <div class="text-box">
                  <el-dialog v-model="showbox1" type="text" >反向提示词:反向提示词是用来规定生成图片时,图片所避免的特征。</el-dialog>
                </div>
              </el-form-item>
              <el-form-item prop="prompt" class="textInput1">
                <textarea class="text__ai" placeholder="请输入反向提示词(英文)" v-model="textmodel.negative_prompt"
                          type="textarea"></textarea>
              </el-form-item>
              <!-- 按钮 -->
              <el-form-item>
                <el-button class="button" type="primary" auto-insert-space @click="processTextAndFetchImage">生成
                </el-button>
              </el-form-item>
            </el-form>
          </el-col>
        </el-row>
<!--        图像算法-->
        <div class="radio_div">
          <h6>图像采样算法</h6>
          <el-icon size="large" class="question2" @click="showbox2= true;"><QuestionFilled /></el-icon>
          <div class="text-box">
            <el-dialog v-model="showbox2" type="text" >图像采样算法:不同的图像采样算法会对生成图片产生轻微的影响。</el-dialog>
          </div>
          <el-radio-group v-model="textmodel.sampler_name">
            <el-radio label="DPM++ 2M Karras" value="DPM++ 2M Karras" border/>
            <el-radio label="DPM++ 2S a" value="DPM++ 2S a" border/>
            <el-radio label="DPM++ 2M" value="DPM++ 2M" border/>
            <el-radio label="DPM++ 2S a Karras" value="DPM++ 2S a Karras" border/>
            <el-radio label="DPM2 a Karras" value="DPM2 a Karras" border/>
            <el-radio label="DPM++ SDE Karras" value="DPM++ SDE Karras" border/>
          </el-radio-group>
        </div>
        <div class="slider">
            <h5>图片分辨率宽度</h5>
            <el-slider v-model="textmodel.width" :format-tooltip="formatTooltip" :min="0" :max="1024" :step="1" show-input/>
            <h5>图片分辨率高度</h5>
            <el-slider v-model="textmodel.height" :format-tooltip="formatTooltip" :min="0" :max="1024" :step="1" show-input/>
          </div>
        <div class="seed_div">
          <h4>随机种子 </h4>
          <el-icon size="large" class="question3" @click="showbox3= true;"><QuestionFilled /></el-icon>
          <div class="text-box">
            <el-dialog v-model="showbox3" type="text" >随机种子:随机数种子控制了图像生成的底层形状,类似于画画时最开始的线稿。当遇到喜欢的图片风格时可以保存当前种子。当种子为-1时,为随机种子。</el-dialog>
          </div>
          <el-input class="input_div"v-model="textmodel.seed"/>
          <img class="touzi" src="../assets/骰子.png"@click="random">
        </div>
<!--        <div >-->
<!--          <h4>fenbianlv </h4>-->
<!--          <el-input v-model="value"/>-->
<!--          <el-input v-model="value"/>-->
<!--        </div>-->
      </el-aside>
      <!--    显示区域-->
      <el-container>
        <el-main class="main">
          <!-- 展示生成的图片或返回的消息 -->
          <div class="img_ai_div">
            <img v-if="imageData" :src="imageData" alt="Generated Image" id="aigenerate" class="img_ai">
            <img v-else src="../assets/img/397x300/02-397x300.png"alt="备用" class="img_ai">
          </div>
          <div v-if="errorMessage" class="error">{{ errorMessage }}</div>
          <div>

            <el-button class="download" @click="download">下载</el-button>
          </div>
        </el-main>
      </el-container>
    </el-container>
  </el-container>
  </body>
</template>

<style lang="scss" scoped>
body {
  font-family: "Roboto", sans-serif;
  background-color: #f8f8f8;
  color: #333;
  margin: 0;
  padding: 0;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
}

/* 导航栏样式 */
.nav__container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  background-color: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  z-index: 1000;
}

.logo {
  display: flex;
  align-items: center;
}

.logo-image {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  margin-right: 10px;
}

.logo-text {
  font-size: 1.5rem;
  font-weight: bold;
  color: #333;
  text-decoration: none;
}

.nav__menu {
  display: flex;
  align-items: center;
}

.nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
}

.nav__item {
  margin: 0 15px;
}

.nav__link {
  font-size: 1rem;
  color: #333;
  text-decoration: none;
  transition: color 0.3s ease;
}

.nav__link:hover,
.nav__link.active-link {
  color: #b43149;
}

.user-profile {
  display: flex;
  align-items: center;
}

.avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  margin-right: 10px;
}

.username-link {
  font-size: 1rem;
  color: #333;
  text-decoration: none;
  transition: color 0.3s ease;
}

.username-link:hover {
  color: #b43149;
}


/* 深度设计系统 */
:root {
  --primary-color: #4A90E2;
  --secondary-color: #6C5CE7;
  --success-color: #00C853;
  --background-color: #F8F9FC;
  --surface-color: #FFFFFF;
  --text-primary: #2D3436;
  --text-secondary: #636E72;
}

/* 主布局重构 */
.el-container {
  padding-top: 20px;
  background: var(--background-color);
  min-height: 100vh;
}

/* 智能侧边栏 */
.el-aside {
  width: 800px !important;
  background: var(--surface-color);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
  padding: 24px;
  border-radius: 16px;
  margin: 20px;
  height: calc(100vh - 120px);
  overflow-y: auto;
  
  /* 动态表单区域 */
  .form-section {
    background: rgba(245, 247, 250, 0.6);
    border-radius: 12px;
    padding: 16px;
    margin-bottom: 24px;
    
    .text__ai {
      width: 100%;
      min-height: 100px;
      padding: 12px;
      border: 1px solid #E3E8F0;
      border-radius: 8px;
      background: var(--surface-color);
      transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      
      &:focus {
        border-color: var(--primary-color);
        box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.15);
      }
    }
  }

  /* 智能按钮 */
  .button {
    width: 100%;
    height: 48px;
    background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
    border-radius: 8px;
    font-weight: 600;
    letter-spacing: 0.5px;
    transition: transform 0.2s ease;
    background-color: #4A90E2;
    
    &:active {
      transform: scale(0.98);
    }
  }
}

/* 参数控制面板 */
.control-panel {
  background: var(--surface-color);
  border-radius: 12px;
  padding: 20px;
  margin-top: 16px;
  
  .param-group {
    margin-bottom: 24px;
    
    h4 {
      color: var(--text-primary);
      font-size: 14px;
      font-weight: 600;
      margin-bottom: 12px;
      display: flex;
      align-items: center;
      gap: 8px;
    } 
    
    /* 现代单选组 */
    .el-radio-group {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
      gap: 8px;
      
      .el-radio {
        margin: 0;
        padding: 10px;
        border-radius: 8px;
        background: #F8FAFF;
        border: 1px solid #E4E9F2;
        transition: all 0.2s ease;
        
        &.is-checked {
          background: var(--primary-color);
          border-color: var(--primary-color);
          color: white;
        }
      }
    }
  }
}

/* 动态生成区域 */
.el-main {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 24px;

  .generation-area {
    flex: 1;
    background: var(--surface-color);
    border-radius: 16px;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
    padding: 24px;
    display: flex;
    flex-direction: column;
    
    .preview-container {
      flex: 1;
      background: #F8FAFF;
      border-radius: 12px;
      position: relative;
      overflow: hidden;
      
      img {
        width: 100%;
        height: 100%;
        object-fit: contain;
        transition: opacity 0.3s ease;
      }
      
      .loading-overlay {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: rgba(255, 255, 255, 0.9);
        display: flex;
        align-items: center;
        justify-content: center;
      }
    }

    .action-bar {
      margin-top: 24px;
      display: flex;
      gap: 12px;
      
      .el-button {
        flex: 1;
        height: 48px;
        border-radius: 8px;
        font-weight: 600;
        
        &.download-btn {
          background: linear-gradient(135deg, var(--success-color), #00B248);
        }
        
        &:hover {
          box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
        }
      }
    }
  }
}

/* 高级控件样式 */
.smart-control {
  .el-slider {
    --slider-height: 6px;
    
    .el-slider__runway {
      height: var(--slider-height);
      background: #E3E8F0;
    }
    
    .el-slider__bar {
      height: var(--slider-height);
      background: var(--primary-color);
    }
    
    .el-slider__button {
      width: 16px;
      height: 16px;
      border: 2px solid var(--primary-color);
      background: var(--surface-color);
    }
  }

  .seed-control {
    display: flex;
    gap: 12px;
    align-items: center;
    
    .el-input {
      flex: 1;
       
      input {
        text-align: center;
        font-family: monospace;
      }
    } 
    
    img {
      width: 24px;
      height: 24px;
      cursor: pointer;
      transition: all 0.3s ease;
      
      &:hover {
        transform: rotate(180deg);
        filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
      }
    }
  }
}

/* 响应式适应 */
@media (max-width: 1024px) {
  .el-container {
    flex-direction: column;
  }

  .el-aside {
    width: 100% !important;
    height: auto;
    margin: 16px;
  }

  .generation-area {
    min-height: 500px;
  }
}

@media (max-width: 768px) {
  .param-group .el-radio-group {
    grid-template-columns: 1fr;
  }
  
  .action-bar {
    flex-direction: column;
  }
}

.el-form-item {
  textarea.text__ai {
    width: 100%;
    min-width: 600px; /* 增加高度 */
    max-height: 300px; /* 可以输入更多内容 */
    padding: 12px;
    border: 1px solid #E3E8F0;
    border-radius: 8px;
    background: var(--surface-color);
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    
    &:focus {
      border-color: var(--primary-color);
      box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.15);
    }
  }
}
.touzi{
  width: 20px;
}

</style>

token.js

import {defineStore} from "pinia";
import {ref} from 'vue'
// 参数一:名字
// 参数二:函数
//
// 返回值


export const userTokenStore = defineStore('token', () => {
        //定义状态内容

        // 响应式变量
        const token = ref('')

        // 定义一个函数 修改token数据
        const tokenset = (newtoken) => {
            token.value = newtoken
        }
        // 移除token
        const tokenremove = () => {
            token.value = ''
        }
        return {token, tokenset, tokenremove}
    },
    {
        persist: true //刷新保留Authorization
    });

aigc.js

//定制请求的实例

//导入axios  npm install axios
import axios from 'axios';
//定义一个变量,记录公共的前缀  ,  baseURL
const baseURL = '/api';
const instance = axios.create({baseURL})


//添加响应拦截器
// instance.interceptors.response.use(
//     result=>{
//         return result.data;
//     },
//     err=>{
//         alert('服务异常');
//         return Promise.reject(err);//异步的状态转化成失败的状态
//     }
// )
instance.interceptors.request.use(
    (config) => {
        const token1 = userTokenStore();
        if (token1.token) {
            config.headers.Authorization = `Bearer ${token1.token}`; // 确保格式正确
        }
        return config;
    },
    (err) => {
        Promise.reject(err);
    }
);

export default instance;

request.js

//定制请求的实例

//导入axios  npm install axios
import axios from 'axios';

import {ElMessage} from "element-plus";
//定义一个变量,记录公共的前缀  ,  baseURL
// const baseURL = 'http://localhost:8080';
const baseURL = '/api';
const instance = axios.create({baseURL})

//添加请求拦截器 其实就是简化Auyhorization的获取方法
import {userTokenStore} from "../stores/token.js";

instance.interceptors.request.use(
    (config) => {
        //请求前的回调
        //添加token
        const token1 = userTokenStore();
        if (token1.token) {
            config.headers.Authorization = token1.token
        }
        return config;
    },
    (err) => {
        //请求错误的回调
        Promise.reject(err)
    }
)
//添加响应拦截器
// import {useRouter} from 'vue-router'
// const router = useRouter();
import router from "../router/index.js";
// import router from "../router/";
instance.interceptors.response.use(
    result => {
        //判断业务状态
        if (result.data.code === 0) {
            return result.data;
        }
        //操作失败
        // alert(result.data.msg?result.data.msg:'服务异常')
        ElMessage.error(result.data.msg ? result.data.msg : '服务异常')
        return Promise.reject(result.data);//异步的状态转化成失败的状态
    },
    err => {
        //若401未连接 强制登陆页面
        if (err.response.status===401) {
            ElMessage.error('未登录,请登录账号')
            router.push('/userlogin')
        } else {
            ElMessage.error('服务异常')
        }
        // alert('服务异常');
        return Promise.reject(err);//异步的状态转化成失败的状态
    }
)

export default instance;

SDController

package com.Controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.pojo.Imgai;
import com.pojo.Result;
import com.pojo.Textai;
import com.pojo.prompt;
import com.service.impl.StableDiffusionImpl;
import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

@RestController
@RequestMapping("/sdapi")
@Slf4j
public class StableDiffusionController {

    private final StableDiffusionImpl stableDiffusionImpl;

    @Autowired
    public StableDiffusionController(StableDiffusionImpl stableDiffusionImpl) {
        this.stableDiffusionImpl = stableDiffusionImpl;
    }
    // 在你的类中定义一个ObjectMapper实例
    private static final ObjectMapper objectMapper = new ObjectMapper();

    @PostMapping("/v1/txt2img")
    public ResponseEntity txt2img(@RequestBody Textai textai) throws IOException {
        // 调用服务类处理文本转图片的逻辑

        log.info("text{}",textai);

        // 创建 Gson 对象
        Gson gson = new Gson();
        // 将 prompt 字符串转换为 JSON 对象
        String json = gson.toJson(textai);


        // 创建HttpURLConnection对象
        HttpURLConnection connection = null;

        // 设置URL
        URL url = new URL("http://127.0.0.1:7860/sdapi/v1/txt2img");
        connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setDoOutput(true);

        // 将JSON字符串作为请求体写入连接
        try (OutputStream os = connection.getOutputStream()) {
            byte[] input = json.toString().getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }


        // 获取响应
        try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) {
            StringBuilder response = new StringBuilder();
            String responseLine = null;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
            // 处理响应
            return ResponseEntity.ok(response);
        } finally {
            // 关闭连接
            connection.disconnect();
        }


        // 将头部和主体数据包装在ResponseEntity中返回

    }
    @PostMapping("/v1/img2img")
    public ResponseEntity img2img(@RequestBody Imgai imgai) throws IOException {

        Gson gson = new Gson();
        // 将 prompt 字符串转换为 JSON 对象
        String json = gson.toJson(imgai);
        log.info("json{}",json);
        // 创建HttpURLConnection对象
        HttpURLConnection connection = null;

        // 设置URL
        URL url = new URL("http://127.0.0.1:7860/sdapi/v1/img2img");
        connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setDoOutput(true);

        // 将JSON字符串作为请求体写入连接
        try (OutputStream os = connection.getOutputStream()) {
            byte[] input = json.toString().getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }


        // 获取响应
        try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"))) {
            StringBuilder response = new StringBuilder();
            String responseLine = null;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
            // 处理响应
            return ResponseEntity.ok(response);
        } finally {
            // 关闭连接
            connection.disconnect();
        }


        // 将头部和主体数据包装在ResponseEntity中返回

    }
}

后端目录我在尝试调用秋叶启动器的SD的API到我的网站,我已经在秋叶启动器里设置了用户名和密码,都是123456。
前后端都是我从别人那里得到的,他之前成功调用过实现了在自己网站进行文生图和图生图。
我的登录注册功能可以实现,但是我拷过来后一直报401,我在postman测试http://127.0.0.1:7860/sdapi/v1/txt2img是可以的,搞了好久了还是不行,有没有大佬指点啊

阅读 1k
avatarAI BotBETA

从代码和问题描述来看,401错误主要出现在身份验证环节。以下是关键分析点和解决方案:

问题原因分析:

  1. 前端Basic认证编码有误
  2. 后端未正确处理身份验证传递
  3. 接口请求头未正确携带凭证

解决方案:

  1. 前端修正(aigc.js):

    // 修改请求拦截器中的认证头设置
    instance.interceptors.request.use(
     (config) => {
         // 直接使用Basic Auth
         config.headers.Authorization = 'Basic ' + btoa('123456:123456') // 使用实际的账号密码
         return config
     },
     (error) => {
         return Promise.reject(error)
     }
    )
  2. 后端修正(SDController.java):

    // 在转发请求时添加认证头
    @PostMapping("/v1/txt2img")
    public ResponseEntity txt2img(@RequestBody Textai textai, @RequestHeader HttpHeaders headers) throws IOException {
     // 获取原始请求的认证头
     String authHeader = headers.getFirst(HttpHeaders.AUTHORIZATION);
     
     // ... [原有代码] ...
     
     // 设置到新请求头
     connection.setRequestProperty("Authorization", authHeader); // 添加这一行
     connection.setRequestProperty("Content-Type", "application/json");
     
     // ... [后续代码] ...
    }
  3. 验证步骤:

    # 用curl测试认证
    curl -X POST -H "Authorization: Basic MTIzNDU2OjEyMzQ1Ng==" \
    -H "Content-Type: application/json" \
    -d '{"prompt":"a dog"}' \
    http://localhost:8080/sdapi/v1/txt2img

补充建议:

  1. 检查秋叶启动器的API设置:

    • 确认已启用--api-auth 用户名:密码启动参数
    • 验证监听端口(默认7860)是否开放
  2. 网络调试建议:

    // 在前端请求处添加调试输出
    const response = await fetch('http://localhost:8080/sdapi/v1/txt2img', {
     method: 'POST',
     headers: {
         'Content-Type': 'application/json',
         'Authorization': 'Basic MTIzNDU2OjEyMzQ1Ng=='
     },
     body: JSON.stringify(re)
    });
    console.log('Request Headers:', [...response.headers.entries()]); // 查看实际发送的headers
  3. 后端可添加调试日志:

    // 在SDController中添加
    log.info("Received auth header: {}", authHeader);
    log.info("Forwarding to: {} with headers: {}", url, connection.getRequestProperties());

如果问题仍未解决,建议按以下顺序排查:

  1. 直接访问http://127.0.0.1:7860/sdapi/v1/txt2img是否返回401
  2. 检查秋叶启动器的控制台输出是否有认证错误
  3. 使用Wireshark或Fiddler抓包验证请求头完整性
  4. 测试Basic Auth的Base64编码准确性(MTIzNDU2OjEyMzQ1Ng==对应123456:123456)
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题