【Spring Boot 快速入門】七、阿里云 OSS 文件上傳

這里寫自定義目錄標題

  • 準備阿里云 OSS
  • 參照官方 SDK 編寫入門程序
  • 案例數據準備
  • 案例集成阿里云 OSS
  • 前端測試代碼
    • app.js
    • style.css
    • index.html
    • 效果圖

準備阿里云 OSS

  1. 注冊登錄阿里云,然后點擊控制臺,在左上角菜單欄搜索對象存儲 OSS,點擊并開通
  2. 點擊 Bucket 列表并新建一個 Bucket,填寫 Bucket 名稱和地域在這里插入圖片描述
  3. 點擊頭像下拉框,點擊 AccessKey在這里插入圖片描述
  4. 創建 AccessKey,獲取并保存 AccessKey ID 和 AccessKey Secret
  5. 記下 Bucket 名稱、Endpoint、AccessKey ID 和 AccessKey Secret,后續配置要使用

參照官方 SDK 編寫入門程序

SDK 文檔地址:對象存儲 SDK

在代碼中引入依賴:

<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.17.4</version>
</dependency>

如果使用的是Java 9及以上的版本,則需要添加以下JAXB相關依賴:

<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version>
</dependency>
<dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version>
</dependency>

使用文檔中的示例代碼:

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.FileInputStream;
import java.io.InputStream;public class Demo {public static void main(String[] args) throws Exception {// Endpoint以華東1(杭州)為例,其它Region請按實際情況填寫。String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";// 從環境變量中獲取訪問憑證。運行本代碼示例之前,請確保已設置環境變量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();// 填寫Bucket名稱,例如examplebucket。String bucketName = "examplebucket";// 填寫Object完整路徑,完整路徑中不能包含Bucket名稱,例如exampledir/exampleobject.txt。String objectName = "exampledir/exampleobject.txt";// 填寫本地文件的完整路徑,例如D:\\localpath\\examplefile.txt。// 如果未指定本地路徑,則默認從示例程序所屬項目對應本地路徑中上傳文件流。String filePath= "D:\\localpath\\examplefile.txt";// 填寫Bucket所在地域。以華東1(杭州)為例,Region填寫為cn-hangzhou。String region = "cn-hangzhou";// 創建OSSClient實例。// 當OSSClient實例不再使用時,調用shutdown方法以釋放資源。ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);        OSS ossClient = OSSClientBuilder.create().endpoint(endpoint).credentialsProvider(credentialsProvider).clientConfiguration(clientBuilderConfiguration).region(region)               .build();try {InputStream inputStream = new FileInputStream(filePath);// 創建PutObjectRequest對象。PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);// 創建PutObject請求。PutObjectResult result = ossClient.putObject(putObjectRequest);} catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (ClientException ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} finally {if (ossClient != null) {ossClient.shutdown();}}}
} 

案例數據準備

數據庫表結構:

CREATE TABLE book_category (id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主鍵ID',name VARCHAR(100) NOT NULL COMMENT '分類名稱',description VARCHAR(255) COMMENT '分類描述'
) COMMENT='圖書分類表';CREATE TABLE book_info (id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '圖書ID',book_name VARCHAR(200) NOT NULL COMMENT '書名',author VARCHAR(100) COMMENT '作者',isbn VARCHAR(20) UNIQUE COMMENT 'ISBN編號',publisher VARCHAR(100) COMMENT '出版社',publish_date DATE COMMENT '出版日期',category_id BIGINT NOT NULL COMMENT '分類ID',image VARCHAR(255) COMMENT '圖書封面',description TEXT COMMENT '簡介',create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '添加時間',update_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '更新時間',CONSTRAINT fk_info_category FOREIGN KEY (category_id) REFERENCES book_category(id)
) COMMENT='圖書信息表';

圖書分類的相關代碼如下:

  1. 實體類 BookCategory:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class BookCategory {private Long id;private String name;private String description;
}
  1. controller 類 BookCategoryController:
import com.Scarletkite.pojo.BookCategory;
import com.Scarletkite.response.Result;
import com.Scarletkite.service.BookCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
public class BookCategoryController {@Autowiredprivate BookCategoryService bookCategoryService;// 查詢所有圖書分類@GetMapping("/getAllBookCategory")public Result getAllBookCategory() {List<BookCategory> bookCategoryList = bookCategoryService.getAllBookCategory();return Result.success(bookCategoryList);}// 刪除圖書分類@DeleteMapping("/deleteBookCategory/{id}")public Result deleteBookCategory(@PathVariable Long id) {bookCategoryService.deleteBookCategory(id);return Result.success();}// 新增圖書分類@PostMapping("/addBookCategory")public Result addBookCategory(@RequestBody BookCategory bookCategory) {bookCategoryService.addBookCategory(bookCategory);return Result.success();}// 修改圖書分類@PutMapping("/updateBookCategory{id}")public Result updateBookCategory(@PathVariable Long id) {bookCategoryService.updateBookCategory(id);return Result.success();}
}
  1. service 接口 BookCategoryService:
import com.Scarletkite.pojo.BookCategory;import java.util.List;public interface BookCategoryService {// 查詢所有圖書分類List<BookCategory> getAllBookCategory();// 添加圖書類別void addBookCategory(BookCategory bookCategory);// 刪除圖書分類void deleteBookCategory(Long id);// 修改圖書分類void updateBookCategory(Long id);
}
  1. service 接口實現類 BookCategoryServiceImp:
import com.Scarletkite.mapper.BookCategoryMapper;
import com.Scarletkite.pojo.BookCategory;
import com.Scarletkite.service.BookCategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class BookCategoryServiceImp implements BookCategoryService {@Autowiredprivate BookCategoryMapper bookCategoryMapper;// 查詢所有圖書分類@Overridepublic List<BookCategory> getAllBookCategory() {return bookCategoryMapper.getAllBookCategory();}// 新增圖書分類@Overridepublic void addBookCategory(BookCategory bookCategory){bookCategoryMapper.addBookCategory(bookCategory);}// 刪除圖書分類@Overridepublic void deleteBookCategory(Long id) {bookCategoryMapper.deleteBookCategory(id);}// 修改圖書分類@Overridepublic void updateBookCategory(Long id) {bookCategoryMapper.updateBookCategory(id);}
}
  1. mapper 接口 BookCategoryMapper:
import com.Scarletkite.pojo.BookCategory;
import org.apache.ibatis.annotations.*;import java.util.List;@Mapper
public interface BookCategoryMapper {// 查詢所有圖書分類@Select("select * from book_category")List<BookCategory> getAllBookCategory();// 新增圖書分類@Insert("insert into book_category (name, description) values (#{name}, #{description})")void addBookCategory(BookCategory bookCategory);// 刪除圖書分類@Delete("delete from book_category where id = #{id}")void deleteBookCategory(Long id);// 修改圖書分類@Update("update book_category set name = #{name}, description = #{description} where id = #{id}")void updateBookCategory(Long id);
}

圖書信息相關代碼如下:

  1. 實體類 BookInfo:
import java.time.LocalDateTime;
import java.util.Date;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class BookInfo {private Long id;private String bookName;private String author;private String isbn;private String publisher;private Date publishDate;private Long categoryId;private String image;private String description;private LocalDateTime createTime;private LocalDateTime updateTime;
}
  1. controller 類:
import com.Scarletkite.pojo.BookInfo;
import com.Scarletkite.pojo.PageBean;
import com.Scarletkite.response.Result;
import com.Scarletkite.service.BookInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.Date;@RestController
public class BookInfoController {@Autowiredprivate BookInfoService bookInfoService;// 新增圖書信息@PostMapping("/addBookInfo")public Result addBookInfo(@RequestBody BookInfo bookInfo) {bookInfoService.addBookInfo(bookInfo);return Result.success();}// 查詢所有圖書信息@GetMapping("/getAllBookInfo")public Result getAllBookInfo(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "10") Integer pageSize,@RequestParam(required = false) String bookName,@RequestParam(required = false) String author,@RequestParam(required = false) String isbn,@RequestParam(required = false) String publisher,@RequestParam(required = false) Date publishDate,@RequestParam(required = false) Long categoryId) {PageBean pageBean = bookInfoService.getAllBookInfo(page, pageSize, bookName, author, isbn, publisher, publishDate, categoryId);return Result.success(pageBean);}// 根據id回顯圖書信息@GetMapping("/getBookInfoById/{id}")public Result getBookInfoById(@PathVariable Long id) {BookInfo bookInfo = bookInfoService.getBookInfoById(id);return Result.success(bookInfo);}// 更新圖書信息@PutMapping("/updateBookInfo")public Result updateBookInfo(@RequestBody BookInfo bookInfo) {//bookInfo.setId(id);bookInfoService.updateBookInfo(bookInfo);return Result.success();}// 刪除圖書信息@DeleteMapping("/deleteBookInfo/{id}")public Result deleteBookInfo(@PathVariable Long id) {bookInfoService.deleteBookInfo(id);return Result.success();}
}
  1. service 接口 BookInfoService:
import com.Scarletkite.pojo.BookInfo;
import com.Scarletkite.pojo.PageBean;
import java.util.Date;public interface BookInfoService {// 新增圖書信息void addBookInfo(BookInfo bookInfo);// 查詢所有圖書信息PageBean getAllBookInfo(Integer page, Integer pageSize, String bookName, String author,String isbn, String publisher, Date publishDate, Long categoryId);// 根據id查詢圖書信息BookInfo getBookInfoById(Long id);// 更新圖書信息void updateBookInfo(BookInfo bookInfo);// 刪除圖書信息void deleteBookInfo(Long id);
}
  1. service 接口實現類 BookInfoServiceImp:
import com.Scarletkite.mapper.BookInfoMapper;
import com.Scarletkite.pojo.BookInfo;
import com.Scarletkite.pojo.PageBean;
import com.Scarletkite.service.BookInfoService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;@Service
public class BookInfoServiceImp implements BookInfoService {@Autowiredprivate BookInfoMapper bookInfoMapper;// 新增圖書信息@Overridepublic void addBookInfo(BookInfo bookInfo) {bookInfo.setCreateTime(LocalDateTime.now());bookInfo.setUpdateTime(LocalDateTime.now());bookInfoMapper.addBookInfo(bookInfo);}// 查詢所有圖書信息@Overridepublic PageBean getAllBookInfo(Integer page, Integer pageSize, String bookName, String author,String isbn, String publisher, Date publishDate, Long categoryId) {// 1. 設置分頁參數PageHelper.startPage(page, pageSize);// 2. 執行查詢List<BookInfo> bookInfoList = bookInfoMapper.getAllBookInfo(bookName, author, isbn, publisher, publishDate, categoryId);Page<BookInfo> p = (Page<BookInfo>) bookInfoList;// 3. 封裝PageBeanPageBean pageBean = new PageBean(p.getTotal(), p.getResult());return pageBean;}// 根據id查詢圖書信息@Overridepublic BookInfo getBookInfoById(Long id) {return bookInfoMapper.getBookInfoById(id);}// 更新圖書信息@Overridepublic void updateBookInfo(BookInfo bookInfo) {bookInfo.setUpdateTime(LocalDateTime.now());bookInfoMapper.updateBookInfo(bookInfo);}// 刪除圖書信息@Overridepublic void deleteBookInfo(Long id) {bookInfoMapper.deleteBookInfo(id);}
}
  1. mapper 接口:
import com.Scarletkite.pojo.BookInfo;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.Date;
import java.util.List;@Mapper
public interface BookInfoMapper {// 新增圖書信息@Insert("insert into book_info (book_name, author, isbn, publisher, " +"publish_date, category_id, image, description, create_time, update_time) " +"values (#{bookName}, #{author}, #{isbn}, #{publisher}, #{publishDate}, " +"#{categoryId}, #{image}, #{description}, #{createTime}, #{updateTime})")void addBookInfo(BookInfo bookInfo);// 查詢所有圖書信息//@Select("select * from book_info")List<BookInfo> getAllBookInfo(String bookName, String author, String isbn,String publisher, Date publishDate, Long categoryId);// 根據id查詢圖書信息@Select("select * from book_info where id = #{id}")BookInfo getBookInfoById(Long id);// 更新圖書信息void updateBookInfo(BookInfo bookInfo);// 刪除圖書信息@Delete("delete from book_info where id = #{id}")void deleteBookInfo(Long id);
}
  1. XML 映射文件 BookInfoMapper:
<update id="updateBookInfo">update book_info<set><if test="bookName != null">book_name = #{bookName},</if><if test="author != null">author = #{author},</if><if test="publisher != null">publisher = #{publisher},</if><if test="publishDate != null">publish_date = #{publishDate},</if><if test="categoryId != null">category_id = #{categoryId},</if><if test="image != null">image = #{image},</if><if test="description != null">description = #{description},</if>update_time = #{updateTime}</set>where id = #{id}
</update><select id="getAllBookInfo" resultType="com.Scarletkite.pojo.BookInfo">select * from book_info<where><if test="bookName != null and bookName != ''">book_name like concat('%', #{bookName}, '%')</if><if test="author != null and author != ''">and author like concat('%', #{author}, '%')</if><if test="isbn != null and isbn != ''">and isbn like concat('%', #{isbn}, '%')</if><if test="publisher != null and publisher != ''">and publisher like concat('%', #{publisher}, '%')</if><if test="publishDate != null">and publish_date = #{publishDate}</if><if test="categoryId != null">and category_id = #{categoryId}</if></where>order by id desc
</select>

案例集成阿里云 OSS

以下是上傳功能的邏輯圖:
在這里插入圖片描述

  1. 首先上傳圖片,然后通過 UploadController 上傳到阿里云 OSS 中,并返回訪問圖片的 URL
  2. 點擊添加,通過 BookInfoController 來進行新增操作

將文檔中的示例代碼改為一個工具類 AliOSSUtils:

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;import java.io.InputStream;
import java.util.UUID;@Component
public class AliOSSUtils {private String endpoint = "";private String accessKeyId = "";private String accessKeySecret = "";private String bucketName = "";public  String upload(MultipartFile file) throws Exception {// 獲取上傳文件的輸入流InputStream inputStream = file.getInputStream();// 避免文件覆蓋String originalFilename = file.getOriginalFilename();String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));// 上傳文件到OSSOSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);ossClient.putObject(bucketName, fileName, inputStream);//文件訪問路徑String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;// 關閉ossClientossClient.shutdown();return url;// 把上傳到oss的路徑返回}
}

新增一個 controller 類 UploadController:

import com.Scarletkite.response.Result;
import com.Scarletkite.utils.AliOSSUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;@RestController
public class UploadController {@Autowiredprivate AliOSSUtils aliOSSUtils;@PostMapping("/upload")public Result upload(MultipartFile image) throws Exception {// 調用阿里云OSS工具類來進行上傳String url = aliOSSUtils.upload(image);return Result.success(url);}
}

這樣就實現了文件的上傳功能

前端測試代碼

以下是用來對后端進行測試用的前端代碼

app.js

// 全局變量
let currentPage = 1;
let pageSize = 10;
let categories = [];// 頁面加載完成后初始化
document.addEventListener('DOMContentLoaded', function() {loadCategories();loadBooks();
});// 顯示不同的功能區域
function showSection(section) {// 隱藏所有區域document.getElementById('books-section').style.display = 'none';document.getElementById('categories-section').style.display = 'none';// 顯示選中的區域document.getElementById(section + '-section').style.display = 'block';// 更新導航狀態document.querySelectorAll('.nav-link').forEach(link => {link.classList.remove('active');});event.target.classList.add('active');// 根據選中的區域加載數據if (section === 'books') {loadBooks();} else if (section === 'categories') {loadCategories();}
}// 加載圖書分類
async function loadCategories() {try {console.log('請求分類數據...');const response = await fetch('/getAllBookCategory');console.log('分類響應狀態:', response.status);if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}const result = await response.json();console.log('分類響應數據:', result);if (result.code === 200) {categories = result.data || [];updateCategorySelects();updateCategoriesTable();} else {showAlert('加載分類失敗: ' + result.message, 'danger');}} catch (error) {console.error('加載分類失敗:', error);showAlert('加載分類失敗: ' + error.message, 'danger');}
}// 更新分類下拉選擇框
function updateCategorySelects() {const selects = [document.getElementById('searchCategory'),document.querySelector('select[name="categoryId"]'),document.getElementById('editCategoryId')];selects.forEach(select => {if (select) {// 保留第一個選項const firstOption = select.firstElementChild;select.innerHTML = '';select.appendChild(firstOption);// 添加分類選項categories.forEach(category => {const option = document.createElement('option');option.value = category.id;option.textContent = category.name;select.appendChild(option);});}});
}// 更新分類表格
function updateCategoriesTable() {const tbody = document.getElementById('categoriesTableBody');tbody.innerHTML = '';categories.forEach(category => {const row = document.createElement('tr');row.innerHTML = `<td>${category.id}</td><td>${category.name}</td><td>${category.description || '-'}</td><td><button class="btn btn-sm btn-outline-danger" onclick="deleteCategory(${category.id})"><i class="bi bi-trash"></i> 刪除</button></td>`;tbody.appendChild(row);});
}// 加載圖書列表
async function loadBooks(page = 1) {try {currentPage = page;const bookName = document.getElementById('searchBookName')?.value || '';const author = document.getElementById('searchAuthor')?.value || '';const categoryId = document.getElementById('searchCategory')?.value || '';const params = new URLSearchParams({page: currentPage,pageSize: pageSize});// 只添加非空參數if (bookName.trim()) {params.append('bookName', bookName.trim());}if (author.trim()) {params.append('author', author.trim());}if (categoryId) {params.append('categoryId', categoryId);}console.log('請求URL:', `/getAllBookInfo?${params}`);const response = await fetch(`/getAllBookInfo?${params}`);console.log('響應狀態:', response.status);if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}const result = await response.json();console.log('響應數據:', result);if (result.code === 200) {updateBooksTable(result.data.rows || []);updatePagination(result.data.total, currentPage, pageSize);} else {showAlert('加載圖書失敗: ' + result.message, 'danger');}} catch (error) {console.error('加載圖書失敗:', error);showAlert('加載圖書失敗: ' + error.message, 'danger');}
}// 更新圖書表格
// 處理圖片URL的輔助函數
function processImageUrl(imageUrl) {// 添加調試信息console.log('原始圖片URL:', imageUrl);if (!imageUrl) {console.log('圖片URL為空,使用占位符');return 'https://via.placeholder.com/60x80?text=No+Image';}// 如果是完整的HTTP/HTTPS URL(OSS),直接使用if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {console.log('檢測到完整URL(OSS),直接使用:', imageUrl);return imageUrl;}// 如果是相對路徑(本地存儲),確保以/開頭if (!imageUrl.startsWith('/')) {imageUrl = '/' + imageUrl;}console.log('處理后的本地路徑URL:', imageUrl);return imageUrl;
}function updateBooksTable(books) {const tbody = document.getElementById('booksTableBody');tbody.innerHTML = '';books.forEach(book => {const category = categories.find(c => c.id === book.categoryId);const categoryName = category ? category.name : '-';const publishDate = book.publishDate ? new Date(book.publishDate).toLocaleDateString() : '-';const imageUrl = processImageUrl(book.image);const row = document.createElement('tr');row.innerHTML = `<td><img src="${imageUrl}" alt="封面" class="book-image" onerror="this.src='https://via.placeholder.com/60x80?text=No+Image'"></td><td>${book.bookName}</td><td>${book.author}</td><td>${book.isbn}</td><td>${book.publisher}</td><td>${publishDate}</td><td>${categoryName}</td><td><button class="btn btn-sm btn-outline-primary me-1" onclick="editBook(${book.id})"><i class="bi bi-pencil"></i></button><button class="btn btn-sm btn-outline-danger" onclick="deleteBook(${book.id})"><i class="bi bi-trash"></i></button></td>`;tbody.appendChild(row);});
}// 更新分頁
function updatePagination(total, pageNum, pageSize) {const pagination = document.getElementById('booksPagination');pagination.innerHTML = '';const totalPages = Math.ceil(total / pageSize);// 上一頁const prevLi = document.createElement('li');prevLi.className = `page-item ${pageNum <= 1 ? 'disabled' : ''}`;prevLi.innerHTML = `<a class="page-link" href="#" onclick="loadBooks(${pageNum - 1})">上一頁</a>`;pagination.appendChild(prevLi);// 頁碼for (let i = Math.max(1, pageNum - 2); i <= Math.min(totalPages, pageNum + 2); i++) {const li = document.createElement('li');li.className = `page-item ${i === pageNum ? 'active' : ''}`;li.innerHTML = `<a class="page-link" href="#" onclick="loadBooks(${i})">${i}</a>`;pagination.appendChild(li);}// 下一頁const nextLi = document.createElement('li');nextLi.className = `page-item ${pageNum >= totalPages ? 'disabled' : ''}`;nextLi.innerHTML = `<a class="page-link" href="#" onclick="loadBooks(${pageNum + 1})">下一頁</a>`;pagination.appendChild(nextLi);
}// 搜索圖書
function searchBooks() {loadBooks(1);
}// 添加圖書
async function addBook() {const form = document.getElementById('addBookForm');// 驗證必填字段if (!form.bookName.value.trim()) {alert('請輸入圖書名稱!');return;}if (!form.author.value.trim()) {alert('請輸入作者!');return;}if (!form.isbn.value.trim()) {alert('請輸入ISBN!');return;}if (!form.publisher.value.trim()) {alert('請輸入出版社!');return;}if (!form.categoryId.value) {alert('請選擇圖書分類!');return;}try {let imageUrl = null;// 如果有圖片文件,先上傳圖片if (form.image.files[0]) {const imageFormData = new FormData();imageFormData.append('image', form.image.files[0]);const uploadResponse = await fetch('/upload', {method: 'POST',body: imageFormData});if (!uploadResponse.ok) {throw new Error('圖片上傳失敗');}const uploadResult = await uploadResponse.json();if (uploadResult.code === 200) {imageUrl = uploadResult.data;} else {throw new Error('圖片上傳失敗: ' + uploadResult.message);}}// 構建圖書信息JSON對象const bookData = {bookName: form.bookName.value.trim(),author: form.author.value.trim(),isbn: form.isbn.value.trim(),publisher: form.publisher.value.trim(),publishDate: form.publishDate.value || null,categoryId: parseInt(form.categoryId.value),description: form.description.value || '',image: imageUrl};console.log('發送添加圖書請求...', bookData);const response = await fetch('/addBookInfo', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(bookData)});console.log('響應狀態:', response.status);if (!response.ok) {const errorText = await response.text();console.log('錯誤響應內容:', errorText);throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);}const result = await response.json();console.log('響應數據:', result);if (result.code === 200) {showAlert('添加圖書成功!', 'success');bootstrap.Modal.getInstance(document.getElementById('addBookModal')).hide();form.reset();loadBooks();} else {showAlert('添加圖書失敗: ' + result.message, 'danger');}} catch (error) {console.error('添加圖書失敗:', error);showAlert('添加圖書失敗: ' + error.message, 'danger');}
}// 編輯圖書
async function editBook(id) {try {// 使用后端接口獲取圖書詳情const response = await fetch(`/getBookInfoById/${id}`);const result = await response.json();if (result.code === 200) {const bookInfo = result.data;console.log('獲取到的圖書信息:', bookInfo);// 填充編輯表單document.getElementById('editBookId').value = id;document.getElementById('editBookName').value = bookInfo.bookName || '';document.getElementById('editAuthor').value = bookInfo.author || '';document.getElementById('editIsbn').value = bookInfo.isbn || '';document.getElementById('editPublisher').value = bookInfo.publisher || '';// 處理日期格式if (bookInfo.publishDate) {const date = new Date(bookInfo.publishDate);const formattedDate = date.toISOString().split('T')[0];document.getElementById('editPublishDate').value = formattedDate;} else {document.getElementById('editPublishDate').value = '';}// 先更新分類下拉框選項updateCategorySelects();// 然后設置分類值和描述document.getElementById('editCategoryId').value = bookInfo.categoryId || '';document.getElementById('editDescription').value = bookInfo.description || '';// 清空文件輸入框document.getElementById('editImage').value = '';// 處理圖片顯示const imagePreview = document.getElementById('editImagePreview');const noImageText = document.getElementById('editNoImage');if (bookInfo.image) {// 使用統一的圖片URL處理函數imagePreview.src = processImageUrl(bookInfo.image);imagePreview.style.display = 'block';noImageText.style.display = 'none';} else {imagePreview.style.display = 'none';noImageText.style.display = 'block';}// 顯示編輯模態框new bootstrap.Modal(document.getElementById('editBookModal')).show();} else {showAlert('獲取圖書信息失敗: ' + result.message, 'danger');}} catch (error) {console.error('獲取圖書信息失敗:', error);showAlert('獲取圖書信息失敗: ' + error.message, 'danger');}
}// 更新圖書
async function updateBook() {const id = document.getElementById('editBookId').value;const imageInput = document.getElementById('editImage');try {let imageUrl = null;// 如果用戶選擇了新的圖片文件,先上傳圖片if (imageInput.files[0]) {const imageFormData = new FormData();imageFormData.append('image', imageInput.files[0]);const uploadResponse = await fetch('/upload', {method: 'POST',body: imageFormData});if (!uploadResponse.ok) {throw new Error('圖片上傳失敗');}const uploadResult = await uploadResponse.json();if (uploadResult.code === 200) {imageUrl = uploadResult.data;} else {throw new Error('圖片上傳失敗: ' + uploadResult.message);}}// 構建更新數據const bookData = {id: parseInt(id),bookName: document.getElementById('editBookName').value,author: document.getElementById('editAuthor').value,isbn: document.getElementById('editIsbn').value,publisher: document.getElementById('editPublisher').value,publishDate: document.getElementById('editPublishDate').value || null,categoryId: parseInt(document.getElementById('editCategoryId').value),description: document.getElementById('editDescription').value};// 如果上傳了新圖片,則更新圖片URLif (imageUrl) {bookData.image = imageUrl;}console.log('發送更新圖書請求...', bookData);const response = await fetch('/updateBookInfo', {method: 'PUT',headers: {'Content-Type': 'application/json'},body: JSON.stringify(bookData)});const result = await response.json();if (result.code === 200) {showAlert('更新圖書成功!', 'success');bootstrap.Modal.getInstance(document.getElementById('editBookModal')).hide();loadBooks();} else {showAlert('更新圖書失敗: ' + result.message, 'danger');}} catch (error) {console.error('更新圖書失敗:', error);showAlert('更新圖書失敗: ' + error.message, 'danger');}
}// 刪除圖書
async function deleteBook(id) {if (!confirm('確定要刪除這本圖書嗎?')) {return;}try {const response = await fetch(`/deleteBookInfo/${id}`, {method: 'DELETE'});const result = await response.json();if (result.code === 200) {showAlert('刪除圖書成功!', 'success');loadBooks();} else {showAlert('刪除圖書失敗: ' + result.message, 'danger');}} catch (error) {console.error('刪除圖書失敗:', error);showAlert('刪除圖書失敗: ' + error.message, 'danger');}
}// 添加分類
async function addCategory() {const form = document.getElementById('addCategoryForm');const formData = new FormData(form);const categoryData = {name: formData.get('name'),description: formData.get('description')};try {const response = await fetch('/addBookCategory', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify(categoryData)});const result = await response.json();if (result.code === 200) {showAlert('添加分類成功!', 'success');bootstrap.Modal.getInstance(document.getElementById('addCategoryModal')).hide();form.reset();loadCategories();} else {showAlert('添加分類失敗: ' + result.message, 'danger');}} catch (error) {console.error('添加分類失敗:', error);showAlert('添加分類失敗: ' + error.message, 'danger');}
}// 刪除分類
async function deleteCategory(id) {if (!confirm('確定要刪除這個分類嗎?')) {return;}try {const response = await fetch(`/deleteBookCategory/${id}`, {method: 'DELETE'});const result = await response.json();if (result.code === 200) {showAlert('刪除分類成功!', 'success');loadCategories();} else {showAlert('刪除分類失敗: ' + result.message, 'danger');}} catch (error) {console.error('刪除分類失敗:', error);showAlert('刪除分類失敗: ' + error.message, 'danger');}
}// 圖片預覽功能
document.addEventListener('DOMContentLoaded', function() {// 添加圖書時的圖片預覽功能const imageInput = document.getElementById('image');if (imageInput) {imageInput.addEventListener('change', function(e) {const file = e.target.files[0];const preview = document.getElementById('imagePreview');if (file) {const reader = new FileReader();reader.onload = function(e) {preview.src = e.target.result;preview.style.display = 'block';};reader.readAsDataURL(file);} else {preview.style.display = 'none';}});}// 編輯圖書時的圖片預覽功能const editImageInput = document.getElementById('editImage');if (editImageInput) {editImageInput.addEventListener('change', function(e) {const file = e.target.files[0];const preview = document.getElementById('editImagePreview');const noImageText = document.getElementById('editNoImage');if (file) {const reader = new FileReader();reader.onload = function(e) {preview.src = e.target.result;preview.style.display = 'block';if (noImageText) {noImageText.style.display = 'none';}};reader.readAsDataURL(file);}});}
});// 顯示提示消息
function showAlert(message, type = 'info') {// 創建提示框const alertDiv = document.createElement('div');alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';alertDiv.innerHTML = `${message}<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;document.body.appendChild(alertDiv);// 3秒后自動消失setTimeout(() => {if (alertDiv.parentNode) {alertDiv.remove();}}, 3000);
}

style.css

/* 自定義樣式 */
:root {--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);--primary-color: #667eea;--secondary-color: #764ba2;--success-color: #28a745;--danger-color: #dc3545;--warning-color: #ffc107;--info-color: #17a2b8;--light-color: #f8f9fa;--dark-color: #343a40;
}/* 全局樣式 */
body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background-color: #f5f6fa;
}/* 側邊欄樣式 */
.sidebar {min-height: 100vh;background: var(--primary-gradient);box-shadow: 2px 0 10px rgba(0,0,0,0.1);
}.sidebar h4 {font-weight: 600;text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}.nav-link {color: rgba(255,255,255,0.8) !important;transition: all 0.3s ease;border-radius: 8px;margin-bottom: 5px;font-weight: 500;
}.nav-link:hover, .nav-link.active {color: white !important;background-color: rgba(255,255,255,0.15);transform: translateX(5px);
}.nav-link i {width: 20px;text-align: center;
}/* 卡片樣式 */
.card {border: none;box-shadow: 0 4px 15px rgba(0,0,0,0.08);transition: all 0.3s ease;border-radius: 12px;
}.card:hover {transform: translateY(-3px);box-shadow: 0 8px 25px rgba(0,0,0,0.15);
}.card-header {background: var(--primary-gradient);color: white;border-radius: 12px 12px 0 0 !important;font-weight: 600;
}/* 按鈕樣式 */
.btn-primary {background: var(--primary-gradient);border: none;border-radius: 8px;font-weight: 500;transition: all 0.3s ease;
}.btn-primary:hover {background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);transform: translateY(-2px);box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}.btn-outline-primary {border-color: var(--primary-color);color: var(--primary-color);border-radius: 6px;transition: all 0.3s ease;
}.btn-outline-primary:hover {background: var(--primary-gradient);border-color: var(--primary-color);transform: translateY(-1px);
}.btn-outline-danger {border-radius: 6px;transition: all 0.3s ease;
}.btn-outline-danger:hover {transform: translateY(-1px);
}/* 表格樣式 */
.table {border-radius: 8px;overflow: hidden;
}.table thead th {background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);border: none;font-weight: 600;color: var(--dark-color);padding: 15px;
}.table tbody tr {transition: all 0.2s ease;
}.table tbody tr:hover {background-color: rgba(102, 126, 234, 0.05);transform: scale(1.01);
}.table tbody td {padding: 12px 15px;vertical-align: middle;border-color: #f1f3f4;
}/* 圖書封面樣式 */
.book-image {width: 60px;height: 80px;object-fit: cover;border-radius: 6px;box-shadow: 0 2px 8px rgba(0,0,0,0.15);transition: transform 0.2s ease;
}.book-image:hover {transform: scale(1.1);
}/* 表格容器 */
.table-container {max-height: 600px;overflow-y: auto;border-radius: 8px;
}.table-container::-webkit-scrollbar {width: 6px;
}.table-container::-webkit-scrollbar-track {background: #f1f1f1;border-radius: 3px;
}.table-container::-webkit-scrollbar-thumb {background: var(--primary-color);border-radius: 3px;
}.table-container::-webkit-scrollbar-thumb:hover {background: var(--secondary-color);
}/* 分頁樣式 */
.pagination .page-link {border: none;color: var(--primary-color);border-radius: 6px;margin: 0 2px;transition: all 0.2s ease;
}.pagination .page-link:hover {background: var(--primary-gradient);color: white;transform: translateY(-1px);
}.pagination .page-item.active .page-link {background: var(--primary-gradient);border-color: var(--primary-color);
}/* 模態框樣式 */
.modal-content {border: none;border-radius: 12px;box-shadow: 0 10px 40px rgba(0,0,0,0.2);
}.modal-header {background: var(--primary-gradient);color: white;border-radius: 12px 12px 0 0;border-bottom: none;
}.modal-header .btn-close {filter: invert(1);
}.modal-body {padding: 30px;
}.modal-footer {border-top: 1px solid #e9ecef;padding: 20px 30px;
}/* 表單樣式 */
.form-control, .form-select {border: 2px solid #e9ecef;border-radius: 8px;padding: 12px 15px;transition: all 0.3s ease;
}.form-control:focus, .form-select:focus {border-color: var(--primary-color);box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}.form-label {font-weight: 600;color: var(--dark-color);margin-bottom: 8px;
}/* 搜索區域樣式 */
.search-card {background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);border: 1px solid #e9ecef;
}/* 提示框樣式 */
.alert {border: none;border-radius: 8px;font-weight: 500;box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}.alert-success {background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);color: #155724;
}.alert-danger {background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);color: #721c24;
}.alert-info {background: linear-gradient(135deg, #d1ecf1 0%, #bee5eb 100%);color: #0c5460;
}/* 響應式設計 */
@media (max-width: 768px) {.sidebar {min-height: auto;}.table-container {max-height: 400px;}.modal-dialog {margin: 10px;}.card {margin-bottom: 20px;}
}/* 加載動畫 */
.loading {display: inline-block;width: 20px;height: 20px;border: 3px solid rgba(255,255,255,.3);border-radius: 50%;border-top-color: #fff;animation: spin 1s ease-in-out infinite;
}@keyframes spin {to { transform: rotate(360deg); }
}/* 空狀態樣式 */
.empty-state {text-align: center;padding: 60px 20px;color: #6c757d;
}.empty-state i {font-size: 4rem;margin-bottom: 20px;opacity: 0.5;
}.empty-state h5 {margin-bottom: 10px;color: #495057;
}/* 統計卡片 */
.stats-card {background: var(--primary-gradient);color: white;border-radius: 12px;padding: 20px;text-align: center;
}.stats-card h3 {font-size: 2.5rem;font-weight: 700;margin-bottom: 5px;
}.stats-card p {margin: 0;opacity: 0.9;
}

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>圖書管理系統測試界面</title><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"><link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css" rel="stylesheet"><link href="style.css" rel="stylesheet">
</head>
<body><div class="container-fluid"><div class="row"><!-- 側邊欄 --><div class="col-md-2 sidebar p-3"><h4 class="text-white mb-4"><i class="bi bi-book"></i> 圖書管理系統</h4><ul class="nav nav-pills flex-column"><li class="nav-item mb-2"><a class="nav-link active" href="#" onclick="showSection('books')"><i class="bi bi-book-fill me-2"></i>圖書信息管理</a></li><li class="nav-item mb-2"><a class="nav-link" href="#" onclick="showSection('categories')"><i class="bi bi-tags-fill me-2"></i>分類管理</a></li></ul></div><!-- 主內容區 --><div class="col-md-10 p-4"><!-- 圖書管理區域 --><div id="books-section"><div class="d-flex justify-content-between align-items-center mb-4"><h2><i class="bi bi-book-fill text-primary me-2"></i>圖書管理</h2><button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addBookModal"><i class="bi bi-plus-circle me-1"></i>添加圖書</button></div><!-- 搜索過濾器 --><div class="card mb-4"><div class="card-body"><div class="row g-3"><div class="col-md-3"><input type="text" class="form-control" id="searchBookName" placeholder="圖書名稱"></div><div class="col-md-3"><input type="text" class="form-control" id="searchAuthor" placeholder="作者"></div><div class="col-md-3"><select class="form-select" id="searchCategory"><option value="">選擇分類</option></select></div><div class="col-md-3"><button class="btn btn-outline-primary w-100" onclick="searchBooks()"><i class="bi bi-search me-1"></i>搜索</button></div></div></div></div><!-- 圖書列表 --><div class="card"><div class="card-body"><div class="table-container"><table class="table table-hover"><thead class="table-light"><tr><th>封面</th><th>書名</th><th>作者</th><th>ISBN</th><th>出版社</th><th>出版日期</th><th>分類</th><th>操作</th></tr></thead><tbody id="booksTableBody"><!-- 動態加載圖書數據 --></tbody></table></div><!-- 分頁 --><nav aria-label="Page navigation" class="mt-3"><ul class="pagination justify-content-center" id="booksPagination"><!-- 動態生成分頁 --></ul></nav></div></div></div><!-- 分類管理區域 --><div id="categories-section" style="display: none;"><div class="d-flex justify-content-between align-items-center mb-4"><h2><i class="bi bi-tags-fill text-primary me-2"></i>分類管理</h2><button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCategoryModal"><i class="bi bi-plus-circle me-1"></i>添加分類</button></div><!-- 分類列表 --><div class="card"><div class="card-body"><table class="table table-hover"><thead class="table-light"><tr><th>ID</th><th>分類名稱</th><th>描述</th><th>操作</th></tr></thead><tbody id="categoriesTableBody"><!-- 動態加載分類數據 --></tbody></table></div></div></div></div></div></div><!-- 添加圖書模態框 --><div class="modal fade" id="addBookModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">添加圖書</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><form id="addBookForm" enctype="multipart/form-data"><div class="row g-3"><div class="col-md-6"><label class="form-label">圖書名稱 *</label><input type="text" class="form-control" name="bookName" required></div><div class="col-md-6"><label class="form-label">作者 *</label><input type="text" class="form-control" name="author" required></div><div class="col-md-6"><label class="form-label">ISBN *</label><input type="text" class="form-control" name="isbn" required></div><div class="col-md-6"><label class="form-label">出版社 *</label><input type="text" class="form-control" name="publisher" required></div><div class="col-md-6"><label class="form-label">出版日期</label><input type="date" class="form-control" name="publishDate"></div><div class="col-md-6"><label class="form-label">分類 *</label><select class="form-select" name="categoryId" required><option value="">選擇分類</option></select></div><div class="col-12"><label class="form-label">封面圖片</label><input type="file" class="form-control" name="image" accept="image/*"></div><div class="col-12"><label class="form-label">描述</label><textarea class="form-control" name="description" rows="3"></textarea></div></div></form></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button type="button" class="btn btn-primary" onclick="addBook()">添加</button></div></div></div></div><!-- 編輯圖書模態框 --><div class="modal fade" id="editBookModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">編輯圖書</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><form id="editBookForm"><input type="hidden" id="editBookId"><div class="row g-4"><!-- 左側:當前封面 --><div class="col-md-4"><div class="text-center"><label class="form-label fw-bold">當前封面</label><div id="editCurrentImage" class="border rounded p-3" style="min-height: 300px; display: flex; align-items: center; justify-content: center;"><img id="editImagePreview" src="" alt="圖書封面" class="img-fluid rounded" style="max-width: 100%; max-height: 280px; display: none;"><p id="editNoImage" class="text-muted mb-0">暫無封面圖片</p></div></div></div><!-- 右側:表單字段 --><div class="col-md-8"><div class="row g-3"><div class="col-md-6"><label class="form-label">圖書名稱 *</label><input type="text" class="form-control" id="editBookName" required></div><div class="col-md-6"><label class="form-label">作者 *</label><input type="text" class="form-control" id="editAuthor" required></div><div class="col-md-6"><label class="form-label">ISBN *</label><input type="text" class="form-control" id="editIsbn" required></div><div class="col-md-6"><label class="form-label">出版社 *</label><input type="text" class="form-control" id="editPublisher" required></div><div class="col-md-6"><label class="form-label">出版日期</label><input type="date" class="form-control" id="editPublishDate"></div><div class="col-md-6"><label class="form-label">分類 *</label><select class="form-select" id="editCategoryId" required><option value="">選擇分類</option></select></div><div class="col-12"><label class="form-label">更換封面圖片</label><input type="file" class="form-control" id="editImage" accept="image/*"><small class="form-text text-muted">如需更換封面,請選擇新的圖片文件</small></div><div class="col-12"><label class="form-label">描述</label><textarea class="form-control" id="editDescription" rows="4"></textarea></div></div></div></div></form></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button type="button" class="btn btn-primary" onclick="updateBook()">保存</button></div></div></div></div><!-- 添加分類模態框 --><div class="modal fade" id="addCategoryModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">添加分類</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><form id="addCategoryForm"><div class="mb-3"><label class="form-label">分類名稱 *</label><input type="text" class="form-control" name="name" required></div><div class="mb-3"><label class="form-label">描述</label><textarea class="form-control" name="description" rows="3"></textarea></div></form></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button type="button" class="btn btn-primary" onclick="addCategory()">添加</button></div></div></div></div><script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script><script src="app.js"></script>
</body>
</html>

效果圖

在這里插入圖片描述
在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/92108.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/92108.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/92108.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

分布式微服務--Nacos作為配置中心(二)

前言&#xff1a;Nacos 是什么&#xff1f; Nacos&#xff08;Naming and Configuration Service&#xff09;是阿里巴巴開源的一個更易于構建云原生應用的動態服務發現、配置管理和服務管理平臺。我們可以使用它&#xff1a; ?作為注冊中心&#xff08;服務發現&#xff09; …

家庭/公司內部網絡內網穿透:無公網IP怎么設置外網遠程訪問?

家庭寬帶內網穿透如何實現&#xff1f;需公網IP嗎&#xff1f;公司內部的網址服務怎么提供互聯網訪問&#xff1f;相信很多人都有遇到家庭網和公司內部網下&#xff0c;搭建了服務器&#xff0c;或網絡硬件設備&#xff0c;需要在異地遠程訪問使用的情況。家庭和公司內部寬帶內…

水庫防洪安全雨水情監測預警系統

水庫防洪安全雨水情監測預警系統是一種高度集成現代信息技術與水利工程管理的綜合性智能化管理平臺&#xff0c;該系統主要應用于水庫及其周邊流域的實時水情監測與預警工作。通過部署先進的傳感設備和監測網絡&#xff0c;該系統能夠全天候不間斷地采集水庫庫區及周邊區域的降…

【論文閱讀】Editing Large Language Models: Problems, Methods, and Opportunities

Editing Large Language Models: Problems, Methods, and Opportunities原文摘要研究背景與問題提出核心問題&#xff1a;盡管LLM已具備強大的能力&#xff0c;但如何長期維持其時效性并修正錯誤仍缺乏系統方法論。現狀&#xff1a;近年來&#xff0c;針對LLMs的模型編輯技術興…

金融數據可視化的強力引擎 —— QtitanDataGrid在金融行業的應用實踐

QtitanDataGrid是一款適用于Qt的商業化DataGrid 組件&#xff0c;它使得表格數據可以直接面向終端用戶。這個組件吸收了用戶界面結構顯示表格方面所有的現代化技術的精華&#xff0c;是目前Qt市場上唯一一款擁有如此高級功能和出色性能的網格組件。這個Qt數據網格組件使用純C創…

玩轉 InfluxDB 3:用 HTTP API 快速創建高效數據表

前言 說起時間序列數據庫,InfluxDB 絕對是業界響當當的明星。數據源源不斷涌入,能否高效存儲和查詢,直接決定你的業務能不能飛速跑起來。可你還在用客戶端或者命令行一點一點手動操作?朋友,這操作太老土,分分鐘拖慢節奏。 現在是 API 自動化時代,HTTP API 可幫你輕松搞…

stc32g利用硬件I2C配合中斷實現高效率異步無阻塞讀寫方法

I2C讀寫巨慢, 即使在400kbit/s下, 讀寫一個字節數據也要花費20多us, 這太慢了, 每讀寫一次設備的寄存器數據, 還要設備地址和寄存器地址, 又加了兩個字節數據, 我就讀了個傳感器的兩個字節數據而已, 動輒還要花費100us的阻塞時間, 這太浪費資源了針對這個問題, 我利用硬件I2C及…

生成式 AI 重塑自動駕駛仿真:4D 場景生成技術的突破與實踐

近年來&#xff0c;伴隨自動駕駛技術的快速發展&#xff0c;行業對于仿真測試平臺的精度、覆蓋率和可擴展性提出了更高要求。尤其在數據閉環迭代、長尾場景驗證及安全冗余驗證等關鍵環節中&#xff0c;高保真、高復雜度的場景生成能力正在成為測試體系的核心支撐。 傳統場景生…

Java 啟動命令的完整解析

以下為您提供的 Java 啟動命令的完整解析和優化建議: nohup java -server \ -XX:+PrintGCDateStamps \ -XX:+PrintGCTimeStamps \ -Xlogger:/home/logs/gc_`date +%Y%m%d_%H%M`.log \ -jar ytr.jar > /dev/null & 一、命令逐行解析 命令部分 功能說明 技術原理 nohup …

JVM中的垃圾回收暫停是什么,為什么會出現暫停,不同的垃圾回收機制暫停對比

JVM中的垃圾回收暫停是什么&#xff1f; 在Java虛擬機&#xff08;JVM&#xff09;中&#xff0c;垃圾回收暫停&#xff08;Garbage Collection Pause&#xff09;&#xff0c;也稱為“Stop-The-World”事件&#xff0c;是指當垃圾收集器執行特定階段時&#xff0c;所有應用程序…

Spearman 相關系數與 Pearson 相關系數的區別

核心區別對比表特征Pearson 相關系數Spearman 相關系數相關性類型線性相關單調相關計算基礎原始數據值數據排名&#xff08;秩&#xff09;公式數據要求連續變量&#xff0c;近似正態分布有序數據或連續變量異常值敏感性高度敏感不敏感取值范圍[-1, 1][-1, 1]單調關系檢測僅檢測…

sqli-labs靶場less36-less40

less361.我們打開靶場之后打開來看一下&#xff0c;輸入的內容會被轉義&#xff0c;依舊是寬字節注入2.使用以下寬字節注入&#xff0c;使用的是%df?id-1%df%27%20union%20select%201,database(),3--3.剩余內容與前面關卡基本一樣&#xff0c;只要使用上面的方法合成寬字節即可…

企業級 TinyMCE Vue 編輯器解決方案 – 配置優化與性能提升指南、自定義插件

## 簡介TinyMCE Vue 是官方提供的 TinyMCE 富文本編輯器的 Vue 組件封裝&#xff0c;支持 Vue 2 和 Vue 3。它讓你可以在 Vue 項目中快速集成強大的富文本編輯能力&#xff0c;支持多種插件、主題和自定義擴展&#xff0c;適用于博客、內容管理、后臺系統等多種場景。主要特性&…

【模電筆記】—— 直流穩壓電源——穩壓電路

Tips&#xff1a;本章節筆記建議讀者綜合學習&#xff0c;內容較多&#xff0c;可謂是模電相當重要的部分&#xff0c;因此部分知識點沒有做到詳細解釋。 1.穩壓電路的性能指標 &#xff08;同上節直流穩壓電源的主要技術指標【模電筆記】—— 直流穩壓電源——整流、濾波電路…

C++——設計模式

文章目錄一、面向對象的優點和缺點1.1 回答重點1.2 擴展知識二、面向對象的三大特點2.1 回答重點2.2 擴展知識三、設計模式的六大原則3.1 回答重點3.1.1 單一職責原則&#xff08;Single Responsibility Principle, SRP&#xff09;3.1.2 開放 - 封閉原則&#xff08;Open-Clos…

Android PDFBox 的使用指南

Android PDFBox 使用指南 概述 PDFBox是一個強大的PDF處理庫&#xff0c;在Android平臺上也有對應的實現。本指南將介紹如何在Android項目中使用PDFBox進行PDF文件的加載、讀取、修改等操作。 依賴配置 在 app/build.gradle 中添加PDFBox依賴&#xff1a; dependencies {i…

TFTP: Linux 系統安裝 TFTP,文件系統啟動后TFTP使用

安裝 TFTP 服務器 sudo apt update sudo apt install tftpd-hpa配置 TFTP 服務器 編輯配置文件 /etc/default/tftpd-hpa&#xff1a; sudo nano /etc/default/tftpd-hpa 修改內容如下&#xff1a; TFTP_USERNAME"tftp" TFTP_DIRECTORY"/srv/tftp" TFTP_ADD…

昇思+昇騰開發板+DeepSeek模型LoRA微調

昇思昇騰開發板DeepSeek模型LoRA微調 LoRA微調原理核心思想&#xff1a;凍結預訓練模型權重&#xff0c;僅訓練橙色的低秩適配矩陣&#xff08;A/B矩陣&#xff09;優勢&#xff1a; 訓練參數量減少至全量微調的0.5%顯存占用降低50%以上適配器權重僅需保存3MB&#xff08;原模型…

計算機網絡:詳解網絡地址的計算步驟

網絡地址計算是網絡規劃與配置的基礎,核心是通過IP地址和子網掩碼確定網絡標識、廣播地址、可用主機范圍等關鍵信息。以下是詳細的計算步驟,配合實例說明(以IPv4為例): 一、明確基礎概念 在計算前,需先明確3個核心概念: IP地址:標識網絡中主機的32位二進制數,通常以…

Spring AI 系列之三十五 - Spring AI Alibaba-Graph框架之MCP

之前做個幾個大模型的應用&#xff0c;都是使用Python語言&#xff0c;后來有一個項目使用了Java&#xff0c;并使用了Spring AI框架。隨著Spring AI不斷地完善&#xff0c;最近它發布了1.0正式版&#xff0c;意味著它已經能很好的作為企業級生產環境的使用。對于Java開發者來說…