仿 360 導航首頁:輸入框輸入中文/拼音首字母/全拼,實時下拉聯想匹配「資料、音樂、新聞、小說、日誌」五大分類;用戶可自定義背景皮膚並即時生效。
public class ChinesePinYinTool {
// 全拼數組:["hao", "de"]
public static String[] stringToPinyin(String src) { ... }
// 首字母數組:["h", "d"]
public static String[] getHeadByString(String src) { ... }
// 全拼字符串:"haode"
public static String hanziToPinyin(String hanzi) { ... }
}
Maven 依賴:<dependency><groupId>com.belerweb</groupId><artifactId>pinyin4j</artifactId><version>2.5.0</version></dependency>
-- 1. 首字母函數:返回單字首字母
CREATE FUNCTION `fristPinyin`(P_NAME VARCHAR(255)) RETURNS VARCHAR(255)
BEGIN
DECLARE V_RETURN VARCHAR(255);
SET V_RETURN = ELT(INTERVAL(CONV(HEX(LEFT(CONVERT(P_NAME USING gbk),1)),16,10),
0xB0A1,0xB0C5,0xB2C1,0xB4EE,0xB6EA,0xB7A2,0xB8C1,0xB9FE,0xBBF7,
0xBFA6,0xC0AC,0xC2E8,0xC4C3,0xC5B6,0xC5BE,0xC6DA,0xC8BB,
0xC8F6,0xCBFA,0xCDDA,0xCEF4,0xD1B9,0xD4D1),
'A','B','C','D','E','F','G','H','J','K','L','M','N','O','P','Q','R','S','T','W','X','Y','Z');
RETURN V_RETURN;
END;
-- 2. 字符串首字母拼接:如 "好的" -> "hd"
CREATE FUNCTION `pinyin`(P_NAME VARCHAR(255)) RETURNS VARCHAR(255)
BEGIN
DECLARE V_RETURN VARCHAR(255) DEFAULT '';
DECLARE I INT DEFAULT 1;
WHILE I <= LENGTH(P_NAME) DO
SET V_RETURN = CONCAT(V_RETURN, fristPinyin(SUBSTR(P_NAME, I, 1)));
SET I = I + 1;
END WHILE;
RETURN IFNULL(NULLIF(V_RETURN, ''), P_NAME);
END;
-- 3. 全拼拼接:如 "好的" -> "haode" (需建拼音映射表 t_base_pinyin)
CREATE FUNCTION `to_pinyin`(NAME VARCHAR(255) CHARSET gbk) RETURNS VARCHAR(255) CHARSET gbk
BEGIN
-- 實現略:遍歷 GBK 雙字節,查表拼接拼音
RETURN LOWER(mypy);
END;
CREATE TABLE t_song (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200) NOT NULL,
pinyin_head VARCHAR(200) GENERATED ALWAYS AS (pinyin(name)) VIRTUAL, -- 首字母索引
pinyin_full VARCHAR(500) GENERATED ALWAYS AS (to_pinyin(name)) VIRTUAL, -- 全拼索引
patent_id INT, -- 專輯外鍵
INDEX idx_pinyin_head (pinyin_head),
INDEX idx_pinyin_full (pinyin_full)
);
關鍵點:使用 GENERATED ALWAYS AS ... VIRTUAL 虛擬列,寫入時自動計算拼音,查詢時直接走索引,無需觸發器維護。
@PostMapping("/search/suggest")
@ResponseBody
public String suggest(@RequestParam String keyword, @RequestParam String type) {
// 1. Java 端轉拼音 (用於 LIKE 模糊匹配)
String[] fullArr = ChinesePinYinTool.stringToPinyin(keyword);
String fullPinyin = String.join("", fullArr); // 全拼 "haode"
String headPinyin = Arrays.stream(ChinesePinYinTool.getHeadByString(keyword))
.map(String::toLowerCase).collect(Collectors.joining()); // 首字母 "hd"
// 2. 構建 HQL/SQL (以音樂為例)
String hql = "FROM Song WHERE to_pinyin(name) LIKE :full OR pinyin(name) LIKE :head";
List<Song> list = songDao.findByHql(hql, Map.of("full", "%" + fullPinyin + "%", "head", "%" + headPinyin + "%"), 0, 10);
// 3. 組裝前端下拉 HTML
StringBuilder sb = new StringBuilder();
for (Song s : list) {
sb.append("<li><a href='/music/detail/").append(s.getPatent().getId())
.append("' target='_blank'>").append(s.getName()).append("</a></li>");
}
if (list.isEmpty()) sb.append("<li><a>無匹配結果</a></li>");
sb.append("<li><a href='javascript:void(0)' onclick='hideSuggest()'>關閉</a></li>");
return sb.toString();
}
// 頁面輸入框綁定
$('#searchInput').on('keyup', function() {
var kw = $(this).val().trim();
var type = $('#searchForm').attr('name'); // 當前搜索類型
if (!kw) { $('#suggestBox').empty().hide(); return; }
$.post('/search/suggest', {keyword: kw, type: type}, function(html) {
$('#suggestBox').html(html).show();
});
});
// 點擊建議項填充輸入框並提交 (委託事件)
$('#suggestBox').on('click', 'a', function(e) {
var text = $(this).text();
if (text !== '無匹配結果' && text !== '關閉') {
$('#searchInput').val(text);
$('#searchForm').submit();
}
$('#suggestBox').hide();
e.preventDefault();
});
// 1. 皮膚表
CREATE TABLE t_skin (id INT PK, name VARCHAR(50), photo VARCHAR(100)); -- photo 存背景圖文件名
// 2. 用戶表關聯皮膚
ALTER TABLE t_user ADD skin_id INT DEFAULT 1;
// 3. 切換皮膚接口
@PostMapping("/skin/change")
@ResponseBody
public void changeSkin(@RequestParam int skinId, HttpSession session) {
Skin skin = skinDao.get(skinId);
User user = (User) session.getAttribute("loginUser");
user.setSkin(skin);
userDao.update(user);
session.setAttribute("loginUser", user); // 更新 Session
}
// 4. 頁面 head.jsp 動態注入背景
<c:if test="${loginUser.skin != null}">
<script>
$(function() {
var bg = '/images/pf/${loginUser.skin.photo}';
$('body').css('background', '#f6f6f6 url(' + bg + ') top repeat');
});
</script>
</c:if>
| 問題 | 解決方案 |
|---|---|
| LIKE '%...%' 不走索引 | MySQL 8.0+ 支持函數索引;或用全文索引/Elasticsearch 替代 |
| 多音字處理 | pinyin4j 默認取第一個讀音;搜索時同時匹配首字母+全拼覆蓋大多數場景 |
| 前端請求過於頻繁 | 輸入框防抖 200-300ms:_.debounce(doSearch, 300) |
| 大數據量下聯想慢 | 限制返回 10 條;必要時引入 Redis 緩存熱門搜索詞 |
| 換膚圖片 404 | 部署時同步皮膚圖片到靜態資源服務器/CDN |
本方案融合 pinyin4j 本地轉換 + MySQL 虛擬列索引 + 前端防抖聯想 + Session 驅動換膚,實現了類 360 導航的智能搜索與個性化皮膚。核心亮點在於「寫時計算、讀時索引」的數據庫層拼音索引設計,兼顧了開發效率與查詢性能。