feat: 更新AI服务、数据管理和借阅历史等功能

main
SLMS Development Team 4 months ago
parent 4047dacd35
commit 1b02cd8b48

@ -293,9 +293,10 @@ public class DataManager {
* @return null
*/
public Book getBookById(String bookId) {
if (bookId == null) return null;
if (useNetworkMode) {
for (Book book : books) {
if (book.getId().equals(bookId)) {
if (bookId.equals(book.getId())) {
return book;
}
}
@ -414,9 +415,11 @@ public class DataManager {
* @param book
*/
public void updateBook(Book book) {
if (book == null || book.getId() == null) return;
String bookId = book.getId();
if (useNetworkMode) {
for (int i = 0; i < books.size(); i++) {
if (books.get(i).getId().equals(book.getId())) {
if (bookId.equals(books.get(i).getId())) {
books.set(i, book);
break;
}
@ -445,9 +448,10 @@ public class DataManager {
* @param bookId ID
*/
public void deleteBook(String bookId) {
if (bookId == null) return;
if (useNetworkMode) {
for (int i = 0; i < books.size(); i++) {
if (books.get(i).getId().equals(bookId)) {
if (bookId.equals(books.get(i).getId())) {
books.remove(i);
break;
}
@ -490,8 +494,9 @@ public class DataManager {
* @return null
*/
public Loan getLoanById(String loanId) {
if (loanId == null) return null;
for (Loan loan : loans) {
if (loan.getId().equals(loanId)) {
if (loanId.equals(loan.getId())) {
return loan;
}
}
@ -514,8 +519,10 @@ public class DataManager {
* @param loan
*/
public void updateLoan(Loan loan) {
if (loan == null || loan.getId() == null) return;
String loanId = loan.getId();
for (int i = 0; i < loans.size(); i++) {
if (loans.get(i).getId().equals(loan.getId())) {
if (loanId.equals(loans.get(i).getId())) {
loans.set(i, loan);
saveDataToLocal();
propertyChangeSupport.firePropertyChange("loan_updated", null, null);
@ -530,8 +537,9 @@ public class DataManager {
* @param loanId ID
*/
public void deleteLoan(String loanId) {
if (loanId == null) return;
for (int i = 0; i < loans.size(); i++) {
if (loans.get(i).getId().equals(loanId)) {
if (loanId.equals(loans.get(i).getId())) {
Loan loan = loans.remove(i);
saveDataToLocal();
propertyChangeSupport.firePropertyChange("loan_deleted", null, null);
@ -716,8 +724,9 @@ public class DataManager {
* @return null
*/
public User getUserById(String userId) {
if (userId == null) return null;
for (User user : users) {
if (user.getId().equals(userId)) {
if (userId.equals(user.getId())) {
return user;
}
}
@ -740,8 +749,10 @@ public class DataManager {
* @param user
*/
public void updateUser(User user) {
if (user == null || user.getId() == null) return;
String userId = user.getId();
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getId().equals(user.getId())) {
if (userId.equals(users.get(i).getId())) {
users.set(i, user);
saveDataToLocal();
propertyChangeSupport.firePropertyChange("user_updated", null, null);
@ -756,8 +767,9 @@ public class DataManager {
* @param userId ID
*/
public void deleteUser(String userId) {
if (userId == null) return;
for (int i = 0; i < users.size(); i++) {
if (users.get(i).getId().equals(userId)) {
if (userId.equals(users.get(i).getId())) {
User user = users.remove(i);
saveDataToLocal();
propertyChangeSupport.firePropertyChange("user_deleted", null, null);
@ -888,8 +900,9 @@ public class DataManager {
}
public void deleteBookmark(String bookmarkId) {
if (bookmarkId == null) return;
for (int i = 0; i < bookmarks.size(); i++) {
if (bookmarks.get(i).getId().equals(bookmarkId)) {
if (bookmarkId.equals(bookmarks.get(i).getId())) {
bookmarks.remove(i);
Log.d(TAG, "删除书签: " + bookmarkId);
return;

@ -307,7 +307,8 @@ public class AIRecommendService {
String author = sourceBook.getAuthor();
for (Book book : dataManager.getBooks()) {
if (book.getId().equals(bookId)) continue;
String currentBookId = book.getId();
if (bookId.equals(currentBookId)) continue;
if (!book.isAvailable()) continue;
Recommendation rec = new Recommendation();

@ -45,22 +45,38 @@ public class SmartAIAssistantActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_RECORD = 100;
private static final int REQUEST_SPEECH_INPUT = 200;
// 帮助信息常量
private static final String HELP_MESSAGE = """
🤖 SLMS使
📚
🔍
Java
📋
📅
🎤
💡 ~""";
private DataManager dataManager;
private VoiceService voiceService;
// UI组件
private TextView tvTitle;
private ScrollView scrollView;
private LinearLayout chatContainer;
private EditText etInput;
private ImageButton btnVoice;
private ImageButton btnSend;
private Button btnBack;
private LinearLayout quickActionsLayout;
private String userId = "default_user";
private boolean isListening = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -78,13 +94,16 @@ public class SmartAIAssistantActivity extends AppCompatActivity {
}
private void initViews() {
tvTitle = findViewById(R.id.tvTitle);
// 局部变量用于只在初始化时使用的组件
TextView tvTitle = findViewById(R.id.tvTitle);
ImageButton btnVoice = findViewById(R.id.btnVoice);
ImageButton btnSend = findViewById(R.id.btnSend);
Button btnBack = findViewById(R.id.btnBack);
// 成员变量用于需要在其他方法中使用的组件
scrollView = findViewById(R.id.scrollView);
chatContainer = findViewById(R.id.chatContainer);
etInput = findViewById(R.id.etInput);
btnVoice = findViewById(R.id.btnVoice);
btnSend = findViewById(R.id.btnSend);
btnBack = findViewById(R.id.btnBack);
quickActionsLayout = findViewById(R.id.quickActionsLayout);
btnBack.setOnClickListener(v -> finish());
@ -168,9 +187,13 @@ public class SmartAIAssistantActivity extends AppCompatActivity {
response.substring(0, 100) + "..." : response;
voiceService.speak(shortResponse, new VoiceService.TTSCallback() {
@Override
public void onSuccess() {}
public void onSuccess() {
// 语音播报成功,无需额外处理
}
@Override
public void onError(String error) {}
public void onError(String error) {
// 语音播报失败,静默处理,不影响用户体验
}
});
}
}
@ -239,8 +262,10 @@ public class SmartAIAssistantActivity extends AppCompatActivity {
for (String keyword : keywords) {
if (input.contains(keyword)) {
for (Book book : books) {
if (book.getTitle().contains(keyword) ||
book.getCategory().contains(keyword)) {
String title = book.getTitle();
String category = book.getCategory();
if ((title != null && title.contains(keyword)) ||
(category != null && category.contains(keyword))) {
recommended.add(book);
}
}
@ -283,9 +308,13 @@ public class SmartAIAssistantActivity extends AppCompatActivity {
List<Book> results = new ArrayList<>();
for (Book book : books) {
if (book.getTitle().toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT)) ||
book.getAuthor().toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT)) ||
book.getCategory().toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT))) {
String title = book.getTitle();
String author = book.getAuthor();
String category = book.getCategory();
String keywordLower = keyword.toLowerCase(Locale.ROOT);
if ((title != null && title.toLowerCase(Locale.ROOT).contains(keywordLower)) ||
(author != null && author.toLowerCase(Locale.ROOT).contains(keywordLower)) ||
(category != null && category.toLowerCase(Locale.ROOT).contains(keywordLower))) {
results.add(book);
}
}
@ -408,25 +437,7 @@ public class SmartAIAssistantActivity extends AppCompatActivity {
}
private String getHelpMessage() {
return """
🤖 SLMS使
📚
🔍
Java
📋
📅
🎤
💡 ~""";
return HELP_MESSAGE;
}
private String generateSmartReply(String input) {

@ -148,11 +148,18 @@ public class CLIApplication {
if (user != null) {
currentUser = user;
String roleDesc = user.getUserType() != null ? user.getUserType() : switch (user.getRole()) {
case ADMIN -> "管理员";
case LIBRARIAN -> "馆员";
case READER -> "读者";
};
String roleDesc;
if (user.getUserType() != null) {
roleDesc = user.getUserType();
} else if (user.getRole() != null) {
roleDesc = switch (user.getRole()) {
case ADMIN -> "管理员";
case LIBRARIAN -> "馆员";
case READER -> "读者";
};
} else {
roleDesc = "读者";
}
System.out.println("╔════════════════════════════════════╗");
System.out.println("║ ✓ 登录成功! ║");
System.out.printf("║ 欢迎 %s (%s)%n", user.getName(), roleDesc);
@ -446,8 +453,9 @@ public class CLIApplication {
private void showLoanHistory() {
System.out.println("\n===== 借阅记录 =====");
String userId = currentUser.getId();
List<Loan> loans = bookService.findAllLoans().stream()
.filter(loan -> currentUser.getId().equals(loan.getUserId()))
.filter(loan -> userId != null && userId.equals(loan.getUserId()))
.toList();
if (loans.isEmpty()) {
System.out.println("暂无借阅记录。");

@ -1,5 +1,5 @@
#MCSLMS DataSource Configuration - v1.7.0
#Mon Dec 22 15:09:14 CST 2025
#Mon Dec 22 17:14:31 CST 2025
database.host=localhost
database.name=slms
database.password=

Binary file not shown.

@ -58,9 +58,11 @@ public interface AIService {
* @return
*/
default List<Book> findSimilarBooks(Book book, List<Book> allBooks) {
String bookCategory = book.getCategory();
String bookId = book.getId();
return allBooks.stream()
.filter(b -> !b.getId().equals(book.getId()))
.filter(b -> b.getCategory().equals(book.getCategory()))
.filter(b -> bookId != null && !bookId.equals(b.getId()))
.filter(b -> bookCategory != null && bookCategory.equals(b.getCategory()))
.limit(5)
.toList();
}

@ -299,8 +299,8 @@ public class SmartAIService implements AIService {
@Override
public BookAnalysis analyzeBook(Book book) {
String title = book.getTitle().toLowerCase();
String category = book.getCategory().toLowerCase();
String title = book.getTitle() != null ? book.getTitle().toLowerCase() : "";
String category = book.getCategory() != null ? book.getCategory().toLowerCase() : "";
// 基于标题和分类智能分析
String difficulty;
@ -539,10 +539,15 @@ public class SmartAIService implements AIService {
@Override
public List<Book> findSimilarBooks(Book targetBook, List<Book> allBooks) {
// 简单的相似性算法:基于分类和标题关键词
String targetId = targetBook.getId();
return allBooks.stream()
.filter(book -> !book.getId().equals(targetBook.getId()))
.filter(book -> targetId != null && !targetId.equals(book.getId()))
.filter(Book::isAvailable)
.filter(book -> book.getCategory().equals(targetBook.getCategory()))
.filter(book -> {
String targetCategory = targetBook.getCategory();
String bookCategory = book.getCategory();
return targetCategory != null && targetCategory.equals(bookCategory);
})
.limit(3)
.toList();
}

@ -15,8 +15,14 @@ import java.util.logging.Logger;
public class DatabaseMigrationTool {
private static final Logger LOGGER = Logger.getLogger(DatabaseMigrationTool.class.getName());
// 表名常量
private static final String TABLE_BOOKS = "books";
private static final String TABLE_USERS = "users";
private static final String TABLE_LOANS = "loans";
private static final String COLUMN_COUNT = "count";
// 允许操作的表白名单防止SQL注入
private static final Set<String> ALLOWED_TABLES = Set.of("books", "users", "loans");
private static final Set<String> ALLOWED_TABLES = Set.of(TABLE_BOOKS, TABLE_USERS, TABLE_LOANS);
private final DatabaseAdapter sourceAdapter;
private final DatabaseAdapter targetAdapter;
@ -45,15 +51,15 @@ public class DatabaseMigrationTool {
try {
// 1. 迁移图书数据
int booksCount = migrateBooks();
LOGGER.info("已迁移" + booksCount + "本图书");
LOGGER.log(Level.INFO, "已迁移{0}本图书", booksCount);
// 2. 迁移用户数据
int usersCount = migrateUsers();
LOGGER.info("已迁移" + usersCount + "个用户");
LOGGER.log(Level.INFO, "已迁移{0}个用户", usersCount);
// 3. 迁移借阅记录
int loansCount = migrateLoans();
LOGGER.info("已迁移" + loansCount + "条借阅记录");
LOGGER.log(Level.INFO, "已迁移{0}条借阅记录", loansCount);
// 4. 验证数据完整性
boolean isValid = validateDataIntegrity();
@ -68,7 +74,7 @@ public class DatabaseMigrationTool {
return true;
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, "数据库迁移失败: " + e.getMessage(), e);
LOGGER.log(Level.SEVERE, () -> "数据库迁移失败: " + e.getMessage());
return false;
}
}
@ -161,23 +167,23 @@ public class DatabaseMigrationTool {
Statement selectStmt = sourceConn.createStatement();
PreparedStatement insertStmt = targetConn.prepareStatement(insertSQL)) {
ResultSet rs = selectStmt.executeQuery(selectSQL);
while (rs.next()) {
mapper.map(rs, insertStmt);
insertStmt.addBatch();
count++;
try (ResultSet rs = selectStmt.executeQuery(selectSQL)) {
while (rs.next()) {
mapper.map(rs, insertStmt);
insertStmt.addBatch();
count++;
// 每1000条记录执行一次批处理
if (count % 1000 == 0) {
insertStmt.executeBatch();
}
}
// 每1000条记录执行一次批处理
if (count % 1000 == 0) {
// 执行剩余的批处理
if (count % 1000 != 0) {
insertStmt.executeBatch();
}
}
// 执行剩余的批处理
if (count % 1000 != 0) {
insertStmt.executeBatch();
}
}
return count;
@ -190,22 +196,10 @@ public class DatabaseMigrationTool {
*/
public boolean validateDataIntegrity() {
try {
// 验证记录数量
boolean countValid = validateRecordCounts();
if (!countValid) {
return false;
}
// 验证外键关联
boolean fkValid = validateForeignKeys();
if (!fkValid) {
return false;
}
return true;
// 验证记录数量和外键关联
return validateRecordCounts() && validateForeignKeys();
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, "数据完整性验证失败: " + e.getMessage());
LOGGER.log(Level.SEVERE, () -> "数据完整性验证失败: " + e.getMessage());
return false;
}
}
@ -217,14 +211,15 @@ public class DatabaseMigrationTool {
* @throws SQLException
*/
private boolean validateRecordCounts() throws SQLException {
String[] tables = {"books", "users", "loans"};
String[] tables = {TABLE_BOOKS, TABLE_USERS, TABLE_LOANS};
for (String table : tables) {
int sourceCount = getRecordCount(sourceAdapter, table);
int targetCount = getRecordCount(targetAdapter, table);
if (sourceCount != targetCount) {
LOGGER.severe("表" + table + "记录数不一致: 源=" + sourceCount + ", 目标=" + targetCount);
LOGGER.log(Level.SEVERE, "表{0}记录数不一致: 源={1}, 目标={2}",
new Object[]{table, sourceCount, targetCount});
return false;
}
}
@ -253,17 +248,19 @@ public class DatabaseMigrationTool {
Statement stmt = conn.createStatement()) {
// 检查book_id外键
ResultSet rs1 = stmt.executeQuery(checkBookFK);
if (rs1.next() && rs1.getInt("count") > 0) {
LOGGER.severe("发现无效的book_id外键");
return false;
try (ResultSet rs1 = stmt.executeQuery(checkBookFK)) {
if (rs1.next() && rs1.getInt(COLUMN_COUNT) > 0) {
LOGGER.severe("发现无效的book_id外键");
return false;
}
}
// 检查user_id外键
ResultSet rs2 = stmt.executeQuery(checkUserFK);
if (rs2.next() && rs2.getInt("count") > 0) {
LOGGER.severe("发现无效的user_id外键");
return false;
try (ResultSet rs2 = stmt.executeQuery(checkUserFK)) {
if (rs2.next() && rs2.getInt(COLUMN_COUNT) > 0) {
LOGGER.severe("发现无效的user_id外键");
return false;
}
}
}
@ -292,7 +289,7 @@ public class DatabaseMigrationTool {
ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next()) {
return rs.getInt("count");
return rs.getInt(COLUMN_COUNT);
}
return 0;
}
@ -304,9 +301,9 @@ public class DatabaseMigrationTool {
*/
private String getCountSqlForTable(String tableName) {
return switch (tableName) {
case "books" -> "SELECT COUNT(*) as count FROM books";
case "users" -> "SELECT COUNT(*) as count FROM users";
case "loans" -> "SELECT COUNT(*) as count FROM loans";
case TABLE_BOOKS -> "SELECT COUNT(*) as count FROM books";
case TABLE_USERS -> "SELECT COUNT(*) as count FROM users";
case TABLE_LOANS -> "SELECT COUNT(*) as count FROM loans";
default -> throw new IllegalArgumentException("非法表名: " + tableName);
};
}
@ -318,7 +315,7 @@ public class DatabaseMigrationTool {
* @return
*/
public boolean exportToSQL(String outputFile) {
LOGGER.info("正在导出数据到SQL文件: " + outputFile);
LOGGER.log(Level.INFO, "正在导出数据到SQL文件: {0}", outputFile);
try (FileWriter writer = new FileWriter(outputFile);
Connection conn = sourceAdapter.getConnection()) {
@ -329,19 +326,19 @@ public class DatabaseMigrationTool {
writer.write("-- 源数据库: " + sourceAdapter.getDatabaseType() + "\n\n");
// 导出books表
exportTable(conn, writer, "books");
exportTable(conn, writer, TABLE_BOOKS);
// 导出users表
exportTable(conn, writer, "users");
exportTable(conn, writer, TABLE_USERS);
// 导出loans表
exportTable(conn, writer, "loans");
exportTable(conn, writer, TABLE_LOANS);
LOGGER.info("数据导出完成");
return true;
} catch (IOException | SQLException e) {
LOGGER.log(Level.SEVERE, "数据导出失败: " + e.getMessage());
LOGGER.log(Level.SEVERE, () -> "数据导出失败: " + e.getMessage());
return false;
}
}
@ -402,9 +399,9 @@ public class DatabaseMigrationTool {
*/
private String getSelectSqlForTable(String tableName) {
return switch (tableName) {
case "books" -> "SELECT * FROM books";
case "users" -> "SELECT * FROM users";
case "loans" -> "SELECT * FROM loans";
case TABLE_BOOKS -> "SELECT * FROM books";
case TABLE_USERS -> "SELECT * FROM users";
case TABLE_LOANS -> "SELECT * FROM loans";
default -> throw new IllegalArgumentException("非法表名: " + tableName);
};
}
@ -416,7 +413,7 @@ public class DatabaseMigrationTool {
* @return
*/
public boolean importFromSQL(String inputFile) {
LOGGER.info("正在从SQL文件导入数据: " + inputFile);
LOGGER.log(Level.INFO, "正在从SQL文件导入数据: {0}", inputFile);
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
Connection conn = targetAdapter.getConnection();
@ -440,11 +437,11 @@ public class DatabaseMigrationTool {
}
}
LOGGER.info("数据导入完成,共执行" + count + "条SQL语句");
LOGGER.log(Level.INFO, "数据导入完成,共执行{0}条SQL语句", count);
return true;
} catch (IOException | SQLException e) {
LOGGER.log(Level.SEVERE, "数据导入失败: " + e.getMessage());
LOGGER.log(Level.SEVERE, () -> "数据导入失败: " + e.getMessage());
return false;
}
}

@ -207,12 +207,12 @@ public class LoanHistoryService extends BaseService {
// 由于我们已经修改了BaseService中的executeUpdateInTransaction方法
// 现在它会返回false而不是null来表示事务执行失败
if (transactionSuccess) {
if (Boolean.TRUE.equals(transactionSuccess)) {
// 记录历史
recordAction(loanId, loan.getBookId(), loan.getUserId(),
LoanAction.RENEWAL, "续借成功,新应还日期: " + newDueDate, 0);
LOGGER.info("✓ 续借成功: " + loanId + " -> " + newDueDate);
LOGGER.log(Level.INFO, "✓ 续借成功: {0} -> {1}", new Object[]{loanId, newDueDate});
return new RenewalResult(true, "续借成功,新应还日期: " + newDueDate, newDueDate);
} else {
return new RenewalResult(false, "续借失败: 事务执行失败", null);
@ -291,15 +291,16 @@ public class LoanHistoryService extends BaseService {
recordAction(loanId, loan.getBookId(), loan.getUserId(),
LoanAction.FINE_PAID, "缴纳罚款: " + fineAmount + "元", fineAmount);
LOGGER.info("✓ 罚款缴纳: " + loanId + " -> " + fineAmount + "元");
LOGGER.log(Level.INFO, "✓ 罚款缴纳: {0} -> {1}元", new Object[]{loanId, fineAmount});
return true;
}
// ==================== 辅助方法 ====================
private Loan findLoanById(String loanId) {
if (loanId == null) return null;
return bookService.findAllLoans().stream()
.filter(loan -> loan.getId().equals(loanId))
.filter(loan -> loanId.equals(loan.getId()))
.findFirst()
.orElse(null);
}

@ -517,7 +517,11 @@ public class UserService extends BaseService {
*/
public boolean changePassword(String userId, String oldPassword, String newPassword) {
User user = findUserById(userId);
if (user == null || !user.getPassword().equals(hashPassword(oldPassword))) {
if (user == null) {
return false;
}
String storedPassword = user.getPassword();
if (storedPassword == null || !storedPassword.equals(hashPassword(oldPassword))) {
return false;
}

@ -311,7 +311,7 @@ public class GUIApplication extends Application {
TableView<Loan> loanTable = createLoanTable();
// 操作按钮
HBox buttonBox = createLoanOperationButtons(loanTable);
HBox buttonBox = createLoanOperationButtons();
content.getChildren().addAll(loanTable, buttonBox);
@ -578,7 +578,7 @@ public class GUIApplication extends Application {
/**
*
*/
private HBox createLoanOperationButtons(TableView<Loan> table) {
private HBox createLoanOperationButtons() {
HBox buttonBox = new HBox(10);
buttonBox.setPadding(new Insets(10));
@ -879,7 +879,7 @@ public class GUIApplication extends Application {
loadUserData();
// 读者注册按钮事件
addBtn.setOnAction(e -> showAddUserDialog(userTable));
addBtn.setOnAction(e -> showAddUserDialog());
// 读者注销按钮事件
deleteBtn.setOnAction(e -> {
@ -901,7 +901,7 @@ public class GUIApplication extends Application {
modifyBtn.setOnAction(e -> {
User selected = userTable.getSelectionModel().getSelectedItem();
if (selected != null) {
showModifyUserDialog(userTable, selected);
showModifyUserDialog(selected);
} else {
showAlert(Alert.AlertType.WARNING, "提示", "请先选择要维护的读者!");
}
@ -959,7 +959,7 @@ public class GUIApplication extends Application {
/**
*
*/
private void showAddUserDialog(TableView<User> table) {
private void showAddUserDialog() {
Dialog<User> dialog = new Dialog<>();
dialog.setTitle("读者注册");
dialog.setHeaderText("请输入读者信息");
@ -1025,7 +1025,7 @@ public class GUIApplication extends Application {
/**
*
*/
private void showModifyUserDialog(TableView<User> table, User selectedUser) {
private void showModifyUserDialog(User selectedUser) {
Dialog<ButtonType> dialog = new Dialog<>();
dialog.setTitle("读者信息维护");
dialog.setHeaderText("请维护读者信息");

Loading…
Cancel
Save