feat(android): 接入 AI 接口并重构菜单结构

main
SLMS Development Team 5 months ago
parent 8db1317404
commit 95dbd71b1f

@ -2646,56 +2646,29 @@ public class MainActivity extends AppCompatActivity {
public void run() {
HttpURLConnection conn = null;
try {
URL url = new URL(baseUrl + "/api/stats");
URL url = new URL(baseUrl + "/api/ai/analysis");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
String response = readText(conn);
final JSONObject json = new JSONObject(response);
final JSONObject book = json.optJSONObject("book");
final JSONObject user = json.optJSONObject("user");
final JSONObject flow = json.optJSONObject("flowAnalysis");
final JSONObject professional = json.optJSONObject("professionalAnalysis");
int totalBooks = book != null ? book.optInt("total", 0) : 0;
int borrowed = book != null ? book.optInt("borrowed", 0) : 0;
int userTotal = user != null ? user.optInt("total", 0) : 0;
double overdueRate = flow != null ? flow.optDouble("overdueRate", 0.0) : 0.0;
double avgBorrowPerUser = flow != null ? flow.optDouble("avgBorrowPerUser", 0.0) : 0.0;
double professionalRate = professional != null ? professional.optDouble("professionalBookRate", 0.0) : 0.0;
final StringBuilder sb = new StringBuilder();
sb.append("基于当前统计数据,为您生成简要阅读行为分析:\n\n");
sb.append("1. 馆藏与使用情况:\n");
sb.append("当前馆藏图书约 ").append(totalBooks).append(" 本,其中国借出 ")
.append(borrowed).append(" 本。")
.append("人均借阅量约 ")
.append(String.format("%.1f", avgBorrowPerUser)).append(" 本。\n\n");
sb.append("2. 逾期风险:\n");
sb.append("当前借阅逾期率约为 ")
.append(String.format("%.1f", overdueRate)).append("%。\n");
sb.append("建议加强到期提醒,鼓励读者按时归还。\n\n");
sb.append("3. 专业书籍结构:\n");
sb.append("专业类图书占馆藏比例约为 ")
.append(String.format("%.1f", professionalRate)).append("%。\n");
sb.append("可以结合学院专业结构,进一步补充相关领域的高质量书籍。\n\n");
sb.append("4. 总体建议:\n");
sb.append("• 适当增加热门类别图书库存,缓解排队与预约压力。\n");
sb.append("• 针对高逾期率人群,增加自动通知与阅读引导。\n");
sb.append("• 利用这些数据,在教学周或考试周前后开展主题荐书活动。\n");
final JSONObject json = readJson(conn);
final boolean success = json.optBoolean("success", false);
final String report = json.optString("report", json.optString("message", ""));
runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.GONE);
if (!success || report == null || report.isEmpty()) {
Toast.makeText(MainActivity.this,
"生成分析失败: " + json.optString("message", "未知错误"),
Toast.LENGTH_SHORT).show();
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("AI 阅读行为分析");
builder.setMessage(sb.toString());
builder.setMessage(report);
builder.setPositiveButton("确定", null);
builder.show();
}
@ -2782,38 +2755,111 @@ public class MainActivity extends AppCompatActivity {
}
private void showAiAssistant() {
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(50, 20, 50, 0);
final TextView tip = new TextView(this);
tip.setText("可以向我提问,也可以输入“推荐”获取个性化荐书。");
layout.addView(tip);
final EditText input = new EditText(this);
input.setHint("请输入您的问题...");
layout.addView(input);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("🤖 AI智能助手");
builder.setView(input);
builder.setView(layout);
builder.setPositiveButton("发送", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String question = input.getText().toString();
if (!question.isEmpty()) {
String response;
if (question.contains("推荐")) {
response = "根据您的借阅历史,推荐您阅读《设计模式》和《重构》";
} else if (question.contains("搜索") || question.contains("查找")) {
response = "已为您找到相关图书,请查看列表";
} else {
response = "感谢您的提问!如需帮助,可以问我:推荐图书、搜索图书等";
}
AlertDialog.Builder inner = new AlertDialog.Builder(MainActivity.this);
inner.setTitle("AI回复");
inner.setMessage(response);
inner.setPositiveButton("确定", null);
inner.show();
final String question = input.getText().toString().trim();
if (question.isEmpty()) {
Toast.makeText(MainActivity.this, "问题不能为空", Toast.LENGTH_SHORT).show();
return;
}
callAiBackend(question);
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
private void callAiBackend(final String question) {
progressBar.setVisibility(View.VISIBLE);
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection conn = null;
try {
boolean isRecommend = question.contains("推荐");
String path = isRecommend ? "/api/ai/recommend" : "/api/ai/qa";
URL url = new URL(baseUrl + path);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
JSONObject body = new JSONObject();
if (isRecommend) {
if (currentUserId != null) {
body.put("userId", currentUserId);
}
} else {
body.put("question", question);
if (currentUserId != null) {
body.put("userId", currentUserId);
}
}
writeBody(conn, body.toString());
final JSONObject json = readJson(conn);
final boolean success = json.optBoolean("success", true);
final String content = isRecommend
? json.optString("text", json.optString("message", ""))
: json.optString("answer", json.optString("message", ""));
final String title = isRecommend ? "AI 推荐" : "AI 回答";
runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.GONE);
if (!success || content == null || content.isEmpty()) {
Toast.makeText(MainActivity.this,
"AI 服务暂不可用,请稍后重试",
Toast.LENGTH_SHORT).show();
return;
}
AlertDialog.Builder inner = new AlertDialog.Builder(MainActivity.this);
inner.setTitle(title);
inner.setMessage(content);
inner.setPositiveButton("确定", null);
inner.show();
}
});
} catch (final Exception e) {
final String err = e.getMessage();
runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.GONE);
Toast.makeText(MainActivity.this,
"AI 调用失败: " + err,
Toast.LENGTH_SHORT).show();
}
});
} finally {
if (conn != null) conn.disconnect();
}
}
}).start();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
@ -2823,127 +2869,233 @@ public class MainActivity extends AppCompatActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_uml) {
startActivity(new Intent(this, UMLViewerActivity.class));
return true;
} else if (id == R.id.action_web) {
showSwitchToWebDialog();
return true;
} else if (id == R.id.action_scan_borrow) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
startScan(REQUEST_CODE_SCAN_BORROW);
}
});
return true;
} else if (id == R.id.action_scan_return) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
startScan(REQUEST_CODE_SCAN_RETURN);
}
});
return true;
} else if (id == R.id.action_reservations) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadReservationsAndShowDialog();
}
});
return true;
} else if (id == R.id.action_history) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadHistoryAndShowDialog();
}
});
if (id == R.id.menu_group_quick) {
showQuickMenu();
return true;
} else if (id == R.id.action_notifications) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadNotificationsAndShowDialog();
}
});
return true;
} else if (id == R.id.action_favorites) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadFavoritesAndShowDialog();
}
});
return true;
} else if (id == R.id.action_notes) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadNotesAndShowDialog();
}
});
} else if (id == R.id.menu_group_my) {
showMyServicesMenu();
return true;
} else if (id == R.id.action_comments) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
showCommentsBookIdInputDialog();
}
});
} else if (id == R.id.menu_group_ai) {
showAiStatsMenu();
return true;
} else if (id == R.id.action_feedback) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadFeedbackAndShowDialog();
}
});
} else if (id == R.id.menu_group_system) {
showSystemMenu();
return true;
} else if (id == R.id.action_stats) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadStatsAndShowDialog();
}
return super.onOptionsItemSelected(item);
}
private void showQuickMenu() {
final String[] items = new String[]{
"切换到 Web",
"扫码借阅",
"扫码还书",
"查看 UML 图"
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("切换与扫码");
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
showSwitchToWebDialog();
break;
case 1:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
startScan(REQUEST_CODE_SCAN_BORROW);
}
});
break;
case 2:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
startScan(REQUEST_CODE_SCAN_RETURN);
}
});
break;
case 3:
startActivity(new Intent(MainActivity.this, UMLViewerActivity.class));
break;
default:
break;
}
});
return true;
} else if (id == R.id.action_ai_analysis) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadAiAnalysisAndShowDialog();
}
});
builder.setNegativeButton("关闭", null);
builder.show();
}
private void showMyServicesMenu() {
final String[] items = new String[]{
"我的预约",
"我的借阅历史",
"通知中心",
"我的收藏",
"我的笔记",
"图书评论",
"意见反馈"
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("我的服务");
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadReservationsAndShowDialog();
}
});
break;
case 1:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadHistoryAndShowDialog();
}
});
break;
case 2:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadNotificationsAndShowDialog();
}
});
break;
case 3:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadFavoritesAndShowDialog();
}
});
break;
case 4:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadNotesAndShowDialog();
}
});
break;
case 5:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
showCommentsBookIdInputDialog();
}
});
break;
case 6:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadFeedbackAndShowDialog();
}
});
break;
default:
break;
}
});
return true;
} else if (id == R.id.action_settings) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadSystemStatusAndShowDialog();
}
});
builder.setNegativeButton("关闭", null);
builder.show();
}
private void showAiStatsMenu() {
final String[] items = new String[]{
"统计概览",
"AI 阅读分析",
"AI 助手"
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("AI 与统计");
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadStatsAndShowDialog();
}
});
break;
case 1:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadAiAnalysisAndShowDialog();
}
});
break;
case 2:
showAiAssistant();
break;
default:
break;
}
});
return true;
} else if (id == R.id.action_profile) {
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadUserProfileAndShowDialog();
}
});
builder.setNegativeButton("关闭", null);
builder.show();
}
private void showSystemMenu() {
final String[] items = new String[]{
"系统状态",
"我的账号",
"用户注册",
"账号安全",
"关于"
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("系统与账号");
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadSystemStatusAndShowDialog();
}
});
break;
case 1:
ensureLoggedIn(new Runnable() {
@Override
public void run() {
loadUserProfileAndShowDialog();
}
});
break;
case 2:
showRegisterDialogAndroid();
break;
case 3:
showAccountSecurityDialog();
break;
case 4:
showAboutDialog();
break;
default:
break;
}
});
return true;
} else if (id == R.id.action_account_security) {
showAccountSecurityDialog();
return true;
} else if (id == R.id.action_register) {
showRegisterDialogAndroid();
return true;
} else if (id == R.id.action_about) {
showAboutDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
});
builder.setNegativeButton("关闭", null);
builder.show();
}
private void showSwitchToWebDialog() {

@ -3,93 +3,23 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_uml"
android:title="UML图"
android:id="@+id/menu_group_quick"
android:title="切换与扫码"
app:showAsAction="never" />
<item
android:id="@+id/action_web"
android:title="切换到Web"
android:id="@+id/menu_group_my"
android:title="我的服务"
app:showAsAction="never" />
<item
android:id="@+id/action_scan_borrow"
android:title="扫码借阅"
android:id="@+id/menu_group_ai"
android:title="AI 与统计"
app:showAsAction="never" />
<item
android:id="@+id/action_scan_return"
android:title="扫码还书"
app:showAsAction="never" />
<item
android:id="@+id/action_reservations"
android:title="我的预约"
app:showAsAction="never" />
<item
android:id="@+id/action_history"
android:title="我的借阅历史"
app:showAsAction="never" />
<item
android:id="@+id/action_notifications"
android:title="通知中心"
app:showAsAction="never" />
<item
android:id="@+id/action_favorites"
android:title="我的收藏"
app:showAsAction="never" />
<item
android:id="@+id/action_notes"
android:title="我的笔记"
app:showAsAction="never" />
<item
android:id="@+id/action_comments"
android:title="图书评论"
app:showAsAction="never" />
<item
android:id="@+id/action_feedback"
android:title="意见反馈"
app:showAsAction="never" />
<item
android:id="@+id/action_stats"
android:title="统计概览"
app:showAsAction="never" />
<item
android:id="@+id/action_ai_analysis"
android:title="AI阅读分析"
app:showAsAction="never" />
<item
android:id="@+id/action_settings"
android:title="系统状态"
app:showAsAction="never" />
<item
android:id="@+id/action_profile"
android:title="我的账号"
app:showAsAction="never" />
<item
android:id="@+id/action_register"
android:title="用户注册"
app:showAsAction="never" />
<item
android:id="@+id/action_account_security"
android:title="账号安全"
app:showAsAction="never" />
<item
android:id="@+id/action_about"
android:title="关于"
android:id="@+id/menu_group_system"
android:title="系统与账号"
app:showAsAction="never" />
</menu>

@ -382,6 +382,42 @@ public class WebController {
return "ai";
}
// ========== AI REST API供 Android / CLI / GUI 调用) ==========
@PostMapping("/api/ai/qa")
@ResponseBody
public Map<String, Object> apiAiQa(@RequestBody AiQuestionRequest request) {
String q = request != null ? request.getQuestion() : null;
if (q == null || q.trim().isEmpty()) {
return Map.of("success", false, "message", "问题不能为空");
}
String answer = aiService.chat(q.trim());
return Map.of("success", true, "answer", answer);
}
@PostMapping("/api/ai/recommend")
@ResponseBody
public Map<String, Object> apiAiRecommend(@RequestBody(required = false) AiRecommendRequest request) {
String uid = "ANDROID_USER";
if (request != null && request.getUserId() != null && !request.getUserId().trim().isEmpty()) {
uid = request.getUserId().trim();
}
String text = aiService.getRecommendation(uid);
return Map.of(
"success", true,
"userId", uid,
"text", text
);
}
@GetMapping("/api/ai/analysis")
@ResponseBody
public Map<String, Object> apiAiAnalysis() {
List<Book> allBooks = bookService.findAllBooks();
String report = aiService.generateLibraryReport(allBooks);
return Map.of("success", true, "report", report);
}
@GetMapping("/users")
public String users(@RequestParam(required = false) String keyword,
@RequestParam(required = false, defaultValue = "ALL") String role,
@ -1241,6 +1277,23 @@ public class WebController {
public void setCategory(String category) { this.category = category; }
}
public static class AiQuestionRequest {
private String question;
private String userId;
public String getQuestion() { return question; }
public void setQuestion(String question) { this.question = question; }
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
}
public static class AiRecommendRequest {
private String userId;
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
}
public static class BorrowRequest {
private String bookId;
private String userId;

@ -6,7 +6,6 @@ import com.smartlibrary.ai.SmartAIService;
import com.smartlibrary.ai.AIIndexService;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/**

Loading…
Cancel
Save