低效修复sonarqube问题

main
SLMS Development Team 4 months ago
parent 83886603e3
commit 0772a1649d

2
Jenkinsfile vendored

@ -140,7 +140,7 @@ pipeline {
}
}
stage('6. 构建四端') {
stage('7. 构建四端') {
parallel {
stage('CLI') {
steps {

@ -315,4 +315,8 @@ http://localhost:9000
请将sonarqube扫描的结果保存sonarqube-mcslms-扫描报告.md
mclsms项目许多问题怎么修复请制作详细的修复计划: sonarqube-mcslms-修复计划.md
sonarqube与jenkins整合的现在的修复非常低效怎么更好更高效解决修复质量问题给出具体操作步骤 sonarqube-mcslms-jenkins修复操作.md
sonarqube与jenkins整合的现在的修复非常低效怎么更好更高效解决修复质量问题给出具体操作步骤 sonarqube-mcslms-jenkins修复操作.md
claude-key
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

@ -1,332 +1,344 @@
package com.smartlibrary.android.service;
import android.content.Context;
import android.util.Log;
import com.smartlibrary.android.ai.AIConfig;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* AI - DeepSeek API
*
*
* 1. OCR
* 2. DeepSeek API
* 3. /
* 4.
*/
public class TranslationService {
private static final String TAG = "TranslationService";
private static TranslationService instance;
private Context context;
private boolean useDeepSeekAPI = true; // 是否使用DeepSeek API
// 支持的语言
public static final String LANG_ZH = "zh"; // 中文
public static final String LANG_EN = "en"; // 英文
public static final String LANG_JA = "ja"; // 日文
public static final String LANG_KO = "ko"; // 韩文
public static final String LANG_FR = "fr"; // 法文
public static final String LANG_DE = "de"; // 德文
public static final String LANG_ES = "es"; // 西班牙文
// 翻译难度
public static final int LEVEL_CHILD = 1; // 儿童版(简化)
public static final int LEVEL_NORMAL = 2; // 普通版
public static final int LEVEL_PROFESSIONAL = 3; // 专业版(精准)
private TranslationService(Context context) {
this.context = context.getApplicationContext();
}
public static synchronized TranslationService getInstance(Context context) {
if (instance == null) {
instance = new TranslationService(context);
}
return instance;
}
/**
*
*/
public static class TranslationResult {
public String originalText;
public String translatedText;
public String sourceLanguage;
public String targetLanguage;
public int difficultyLevel;
public double confidence;
public String pronunciation; // 发音(拼音/音标)
public String[] keywords; // 关键词
public String notes; // 注释说明
}
/**
*
*/
public TranslationResult translate(String text, String sourceLang, String targetLang, int level) {
TranslationResult result = new TranslationResult();
result.originalText = text;
result.sourceLanguage = sourceLang;
result.targetLanguage = targetLang;
result.difficultyLevel = level;
// 使用DeepSeek API或本地模拟翻译
if (useDeepSeekAPI) {
result.translatedText = translateWithDeepSeek(text, sourceLang, targetLang, level);
result.confidence = 0.95;
} else {
result.translatedText = simulateTranslation(text, sourceLang, targetLang, level);
result.confidence = 0.7;
}
result.pronunciation = generatePronunciation(result.translatedText, targetLang);
result.keywords = extractKeywords(text);
result.notes = generateNotes(text, level);
Log.d(TAG, "翻译完成: " + sourceLang + " -> " + targetLang + " (DeepSeek: " + useDeepSeekAPI + ")");
return result;
}
/**
*
*/
public TranslationResult autoTranslate(String text, String targetLang, int level) {
String detectedLang = detectLanguage(text);
return translate(text, detectedLang, targetLang, level);
}
/**
*
* 使ReDoS
*/
public String detectLanguage(String text) {
if (text == null || text.isEmpty()) return LANG_EN;
// 使用字符遍历检测语言,避免正则表达式回溯攻击
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
// 检测中文字符 (CJK统一汉字)
if (c >= '\u4e00' && c <= '\u9fa5') {
return LANG_ZH;
}
// 检测日文字符 (平假名和片假名)
if ((c >= '\u3040' && c <= '\u309f') || (c >= '\u30a0' && c <= '\u30ff')) {
return LANG_JA;
}
// 检测韩文字符 (韩文音节)
if (c >= '\uac00' && c <= '\ud7af') {
return LANG_KO;
}
}
return LANG_EN;
}
/**
* 使DeepSeek API
*/
private String translateWithDeepSeek(String text, String sourceLang, String targetLang, int level) {
try {
String sourceName = getLanguageName(sourceLang);
String targetName = getLanguageName(targetLang);
// 构建翻译提示词
String levelHint = "";
if (level == LEVEL_CHILD) {
levelHint = "请使用简单易懂的词汇,适合儿童阅读理解。";
} else if (level == LEVEL_PROFESSIONAL) {
levelHint = "请保持专业术语的准确性,提供精准翻译。";
}
String prompt = String.format(
"请将以下%s文本翻译成%s。%s只返回翻译结果不要添加任何解释。\n\n原文%s",
sourceName, targetName, levelHint, text
);
// 调用DeepSeek API
URL url = new URL(AIConfig.DEEPSEEK_API_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", "Bearer " + AIConfig.DEEPSEEK_API_KEY);
conn.setConnectTimeout(AIConfig.CONNECTION_TIMEOUT);
conn.setReadTimeout(AIConfig.READ_TIMEOUT);
conn.setDoOutput(true);
// 构建请求体
JSONObject requestBody = new JSONObject();
requestBody.put("model", AIConfig.DEEPSEEK_MODEL);
JSONArray messages = new JSONArray();
JSONObject systemMsg = new JSONObject();
systemMsg.put("role", "system");
systemMsg.put("content", "你是一个专业的多语种翻译助手,擅长准确、流畅地翻译各种语言的文本。");
messages.put(systemMsg);
JSONObject userMsg = new JSONObject();
userMsg.put("role", "user");
userMsg.put("content", prompt);
messages.put(userMsg);
requestBody.put("messages", messages);
requestBody.put("temperature", 0.3);
requestBody.put("max_tokens", 2000);
// 发送请求
try (OutputStream os = conn.getOutputStream()) {
byte[] input = requestBody.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
// 读取响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
br.close();
// 解析响应
JSONObject jsonResponse = new JSONObject(response.toString());
JSONArray choices = jsonResponse.getJSONArray("choices");
if (choices.length() > 0) {
JSONObject choice = choices.getJSONObject(0);
JSONObject message = choice.getJSONObject("message");
return message.getString("content").trim();
}
} else {
Log.e(TAG, "DeepSeek API错误: " + responseCode);
}
conn.disconnect();
} catch (Exception e) {
Log.e(TAG, "DeepSeek翻译失败", e);
}
// 失败时使用本地模拟翻译
return simulateTranslation(text, sourceLang, targetLang, level);
}
/**
*
*/
private String simulateTranslation(String text, String sourceLang, String targetLang, int level) {
// 简单的模拟翻译词典
Map<String, String> enToZh = new HashMap<>();
enToZh.put("hello", "你好");
enToZh.put("world", "世界");
enToZh.put("book", "书籍");
enToZh.put("library", "图书馆");
enToZh.put("read", "阅读");
enToZh.put("welcome", "欢迎");
enToZh.put("smart", "智能");
if (LANG_EN.equals(sourceLang) && LANG_ZH.equals(targetLang)) {
String result = text.toLowerCase();
for (Map.Entry<String, String> entry : enToZh.entrySet()) {
result = result.replace(entry.getKey(), entry.getValue());
}
if (level == LEVEL_CHILD) {
return "【简化版】" + result;
}
return result;
}
return "[翻译] " + text;
}
/**
* 使DeepSeek API
*/
public void setUseDeepSeekAPI(boolean use) {
this.useDeepSeekAPI = use;
}
/**
*
*/
private String generatePronunciation(String text, String lang) {
if (LANG_ZH.equals(lang)) {
return "[拼音] " + text; // 实际应用中生成拼音
} else if (LANG_EN.equals(lang)) {
return "[音标] /" + text.toLowerCase() + "/";
}
return "";
}
/**
*
*/
private String[] extractKeywords(String text) {
// 简单的关键词提取
String[] words = text.split("\\s+");
if (words.length <= 5) return words;
String[] keywords = new String[5];
System.arraycopy(words, 0, keywords, 0, 5);
return keywords;
}
/**
*
*/
private String generateNotes(String text, int level) {
if (level == LEVEL_CHILD) {
return "💡 这段文字已简化为适合儿童阅读的版本";
} else if (level == LEVEL_PROFESSIONAL) {
return "📚 这是专业精准翻译,保留了原文的专业术语";
}
return "";
}
/**
*
*/
public Map<String, String> getSupportedLanguages() {
Map<String, String> languages = new HashMap<>();
languages.put(LANG_ZH, "中文");
languages.put(LANG_EN, "English");
languages.put(LANG_JA, "日本語");
languages.put(LANG_KO, "한국어");
languages.put(LANG_FR, "Français");
languages.put(LANG_DE, "Deutsch");
languages.put(LANG_ES, "Español");
return languages;
}
/**
*
*/
public String getLanguageName(String langCode) {
Map<String, String> languages = getSupportedLanguages();
return languages.getOrDefault(langCode, langCode);
}
/**
*
*/
public TranslationResult[] batchTranslate(String[] texts, String sourceLang, String targetLang, int level) {
TranslationResult[] results = new TranslationResult[texts.length];
for (int i = 0; i < texts.length; i++) {
results[i] = translate(texts[i], sourceLang, targetLang, level);
}
return results;
}
}
package com.smartlibrary.android.service;
import android.content.Context;
import android.util.Log;
import com.smartlibrary.android.ai.AIConfig;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* AI - DeepSeek API
*
*
* 1. OCR
* 2. DeepSeek API
* 3. /
* 4.
*/
public class TranslationService {
private static final String TAG = "TranslationService";
private static TranslationService instance;
private Context context;
private boolean useDeepSeekAPI = true; // 是否使用DeepSeek API
// 支持的语言
public static final String LANG_ZH = "zh"; // 中文
public static final String LANG_EN = "en"; // 英文
public static final String LANG_JA = "ja"; // 日文
public static final String LANG_KO = "ko"; // 韩文
public static final String LANG_FR = "fr"; // 法文
public static final String LANG_DE = "de"; // 德文
public static final String LANG_ES = "es"; // 西班牙文
// 翻译难度
public static final int LEVEL_CHILD = 1; // 儿童版(简化)
public static final int LEVEL_NORMAL = 2; // 普通版
public static final int LEVEL_PROFESSIONAL = 3; // 专业版(精准)
private TranslationService(Context context) {
this.context = context.getApplicationContext();
}
public static synchronized TranslationService getInstance(Context context) {
if (instance == null) {
instance = new TranslationService(context);
}
return instance;
}
/**
*
*/
public static class TranslationResult {
public String originalText;
public String translatedText;
public String sourceLanguage;
public String targetLanguage;
public int difficultyLevel;
public double confidence;
public String pronunciation; // 发音(拼音/音标)
public String[] keywords; // 关键词
public String notes; // 注释说明
}
/**
*
*/
public TranslationResult translate(String text, String sourceLang, String targetLang, int level) {
TranslationResult result = new TranslationResult();
result.originalText = text;
result.sourceLanguage = sourceLang;
result.targetLanguage = targetLang;
result.difficultyLevel = level;
// 使用DeepSeek API或本地模拟翻译
if (useDeepSeekAPI) {
result.translatedText = translateWithDeepSeek(text, sourceLang, targetLang, level);
result.confidence = 0.95;
} else {
result.translatedText = simulateTranslation(text, sourceLang, targetLang, level);
result.confidence = 0.7;
}
result.pronunciation = generatePronunciation(result.translatedText, targetLang);
result.keywords = extractKeywords(text);
result.notes = generateNotes(text, level);
Log.d(TAG, "翻译完成: " + sourceLang + " -> " + targetLang + " (DeepSeek: " + useDeepSeekAPI + ")");
return result;
}
/**
*
*/
public TranslationResult autoTranslate(String text, String targetLang, int level) {
String detectedLang = detectLanguage(text);
return translate(text, detectedLang, targetLang, level);
}
/**
*
* 使ReDoS
*/
public String detectLanguage(String text) {
if (text == null || text.isEmpty()) return LANG_EN;
// 使用字符遍历检测语言,避免正则表达式回溯攻击
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
// 检测中文字符 (CJK统一汉字)
if (c >= '\u4e00' && c <= '\u9fa5') {
return LANG_ZH;
}
// 检测日文字符 (平假名和片假名)
if ((c >= '\u3040' && c <= '\u309f') || (c >= '\u30a0' && c <= '\u30ff')) {
return LANG_JA;
}
// 检测韩文字符 (韩文音节)
if (c >= '\uac00' && c <= '\ud7af') {
return LANG_KO;
}
}
return LANG_EN;
}
/**
* 使DeepSeek API
*/
private String translateWithDeepSeek(String text, String sourceLang, String targetLang, int level) {
try {
String sourceName = getLanguageName(sourceLang);
String targetName = getLanguageName(targetLang);
// 构建翻译提示词
String levelHint = "";
if (level == LEVEL_CHILD) {
levelHint = "请使用简单易懂的词汇,适合儿童阅读理解。";
} else if (level == LEVEL_PROFESSIONAL) {
levelHint = "请保持专业术语的准确性,提供精准翻译。";
}
String prompt = String.format(
"请将以下%s文本翻译成%s。%s只返回翻译结果不要添加任何解释。\n\n原文%s",
sourceName, targetName, levelHint, text
);
// 调用DeepSeek API
URL url = new URL(AIConfig.DEEPSEEK_API_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", "Bearer " + AIConfig.DEEPSEEK_API_KEY);
conn.setConnectTimeout(AIConfig.CONNECTION_TIMEOUT);
conn.setReadTimeout(AIConfig.READ_TIMEOUT);
conn.setDoOutput(true);
// 构建请求体
JSONObject requestBody = new JSONObject();
requestBody.put("model", AIConfig.DEEPSEEK_MODEL);
JSONArray messages = new JSONArray();
JSONObject systemMsg = new JSONObject();
systemMsg.put("role", "system");
systemMsg.put("content", "你是一个专业的多语种翻译助手,擅长准确、流畅地翻译各种语言的文本。");
messages.put(systemMsg);
JSONObject userMsg = new JSONObject();
userMsg.put("role", "user");
userMsg.put("content", prompt);
messages.put(userMsg);
requestBody.put("messages", messages);
requestBody.put("temperature", 0.3);
requestBody.put("max_tokens", 2000);
// 发送请求
try (OutputStream os = conn.getOutputStream()) {
byte[] input = requestBody.toString().getBytes("utf-8");
os.write(input, 0, input.length);
}
// 读取响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
br.close();
// 解析响应 - 添加JSON解析异常处理
try {
JSONObject jsonResponse = new JSONObject(response.toString());
if (jsonResponse.has("choices")) {
JSONArray choices = jsonResponse.getJSONArray("choices");
if (choices.length() > 0) {
JSONObject choice = choices.getJSONObject(0);
if (choice.has("message")) {
JSONObject message = choice.getJSONObject("message");
if (message.has("content")) {
return message.getString("content").trim();
}
}
}
}
Log.w(TAG, "DeepSeek API响应格式异常: 缺少必要字段");
} catch (JSONException e) {
Log.e(TAG, "DeepSeek API响应JSON解析失败: " + e.getMessage());
}
} else {
Log.e(TAG, "DeepSeek API错误: " + responseCode);
}
conn.disconnect();
} catch (Exception e) {
Log.e(TAG, "DeepSeek翻译失败", e);
}
// 失败时使用本地模拟翻译
return simulateTranslation(text, sourceLang, targetLang, level);
}
/**
*
*/
private String simulateTranslation(String text, String sourceLang, String targetLang, int level) {
// 简单的模拟翻译词典
Map<String, String> enToZh = new HashMap<>();
enToZh.put("hello", "你好");
enToZh.put("world", "世界");
enToZh.put("book", "书籍");
enToZh.put("library", "图书馆");
enToZh.put("read", "阅读");
enToZh.put("welcome", "欢迎");
enToZh.put("smart", "智能");
if (LANG_EN.equals(sourceLang) && LANG_ZH.equals(targetLang)) {
String result = text.toLowerCase();
for (Map.Entry<String, String> entry : enToZh.entrySet()) {
result = result.replace(entry.getKey(), entry.getValue());
}
if (level == LEVEL_CHILD) {
return "【简化版】" + result;
}
return result;
}
return "[翻译] " + text;
}
/**
* 使DeepSeek API
*/
public void setUseDeepSeekAPI(boolean use) {
this.useDeepSeekAPI = use;
}
/**
*
*/
private String generatePronunciation(String text, String lang) {
if (LANG_ZH.equals(lang)) {
return "[拼音] " + text; // 实际应用中生成拼音
} else if (LANG_EN.equals(lang)) {
return "[音标] /" + text.toLowerCase() + "/";
}
return "";
}
/**
*
*/
private String[] extractKeywords(String text) {
// 简单的关键词提取
String[] words = text.split("\\s+");
if (words.length <= 5) return words;
String[] keywords = new String[5];
System.arraycopy(words, 0, keywords, 0, 5);
return keywords;
}
/**
*
*/
private String generateNotes(String text, int level) {
if (level == LEVEL_CHILD) {
return "💡 这段文字已简化为适合儿童阅读的版本";
} else if (level == LEVEL_PROFESSIONAL) {
return "📚 这是专业精准翻译,保留了原文的专业术语";
}
return "";
}
/**
*
*/
public Map<String, String> getSupportedLanguages() {
Map<String, String> languages = new HashMap<>();
languages.put(LANG_ZH, "中文");
languages.put(LANG_EN, "English");
languages.put(LANG_JA, "日本語");
languages.put(LANG_KO, "한국어");
languages.put(LANG_FR, "Français");
languages.put(LANG_DE, "Deutsch");
languages.put(LANG_ES, "Español");
return languages;
}
/**
*
*/
public String getLanguageName(String langCode) {
Map<String, String> languages = getSupportedLanguages();
return languages.getOrDefault(langCode, langCode);
}
/**
*
*/
public TranslationResult[] batchTranslate(String[] texts, String sourceLang, String targetLang, int level) {
TranslationResult[] results = new TranslationResult[texts.length];
for (int i = 0; i < texts.length; i++) {
results[i] = translate(texts[i], sourceLang, targetLang, level);
}
return results;
}
}

@ -5,6 +5,7 @@ plugins {
id 'io.spring.dependency-management' version '1.1.4'
id 'application'
id 'org.openjfx.javafxplugin' version '0.1.0'
id 'jacoco'
}
group = 'com.smartlibrary'
@ -100,4 +101,12 @@ bootWar {
tasks.named('test') {
useJUnitPlatform()
}
jacocoTestReport {
dependsOn test
reports {
xml.required = true
html.required = true
}
}

@ -95,6 +95,16 @@ public class VoiceController {
* URL
*/
private String buildAuthUrl(String baseUrl) throws Exception {
// 空值检查
if (baseUrl == null || baseUrl.isEmpty()) {
throw new IllegalArgumentException("baseUrl不能为空");
}
// 检查配置是否有效
if (config == null || config.getApiSecret() == null || config.getApiKey() == null) {
throw new IllegalStateException("讯飞配置无效");
}
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = sdf.format(new Date());
@ -103,6 +113,11 @@ public class VoiceController {
String host = uri.getHost();
String path = uri.getPath();
// 空值检查
if (host == null || path == null) {
throw new IllegalArgumentException("无效的URL格式");
}
// 构建签名原文
String signatureOrigin = "host: " + host + "\n" +
"date: " + date + "\n" +
@ -299,3 +314,4 @@ public class VoiceController {
}

@ -78,6 +78,11 @@ public class WebController {
HttpSession session, Model model) {
List<Book> allBooks = bookService.findAllBooks();
// 空值检查
if (allBooks == null) {
allBooks = List.of(); // 使用空列表替代null
}
// 过滤
if (category != null && !category.isEmpty()) {
allBooks = allBooks.stream()
@ -87,8 +92,8 @@ public class WebController {
if (keyword != null && !keyword.isEmpty()) {
String kw = keyword.toLowerCase();
allBooks = allBooks.stream()
.filter(b -> b.getTitle().toLowerCase().contains(kw)
|| b.getAuthor().toLowerCase().contains(kw))
.filter(b -> b.getTitle() != null && b.getTitle().toLowerCase().contains(kw)
|| b.getAuthor() != null && b.getAuthor().toLowerCase().contains(kw))
.toList();
}
@ -115,6 +120,11 @@ public class WebController {
HttpSession session, Model model) {
List<Loan> allLoans = bookService.findAllLoans();
// 空值检查
if (allLoans == null) {
allLoans = List.of(); // 使用空列表替代null
}
int total = allLoans.size();
int totalPages = (int) Math.ceil((double) total / PAGE_SIZE);
page = Math.clamp(page, 1, Math.max(1, totalPages));
@ -371,12 +381,26 @@ public class WebController {
public Map<String, Object> apiBooks(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
List<Book> allBooks = bookService.findAllBooks();
// 空值检查
if (allBooks == null) {
allBooks = List.of(); // 使用空列表替代null
}
int total = allBooks.size();
int start = (page - 1) * size;
int end = Math.min(start + size, total);
// 确保start和end在有效范围内
if (start >= total) {
start = 0;
end = 0;
}
List<Book> books = (start < end) ? allBooks.subList(start, end) : List.of();
Map<String, Object> result = new HashMap<>();
result.put(BOOKS_ATTR, allBooks.subList(start, end));
result.put(BOOKS_ATTR, books);
result.put("total", total);
result.put("page", page);
result.put(TOTAL_PAGES_ATTR, (int) Math.ceil((double) total / size));
@ -386,13 +410,22 @@ public class WebController {
@GetMapping("/api/loans")
@ResponseBody
public List<Loan> apiLoans() {
return bookService.findAllLoans();
List<Loan> loans = bookService.findAllLoans();
// 空值检查返回空列表而不是null
return loans != null ? loans : List.of();
}
@GetMapping("/api/books/{id}")
@ResponseBody
public Book apiBookById(@PathVariable String id) {
return bookService.findBookById(id);
Book book = bookService.findBookById(id);
// 空值检查抛出合适的异常而不是返回null
if (book == null) {
throw new org.springframework.web.server.ResponseStatusException(
org.springframework.http.HttpStatus.NOT_FOUND, "图书不存在"
);
}
return book;
}
@PostMapping("/api/books")
@ -647,3 +680,7 @@ public class WebController {
@enduml
""";
}

@ -0,0 +1,57 @@
package com.smartlibrary.web.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRepository;
/**
* Spring Security
* CSRF
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
/**
*
* CSRF
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(csrfTokenRepository())
.ignoringRequestMatchers("/api/public/**") // 可选对公共API禁用CSRF
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);
return http.build();
}
/**
* CSRF
* 使CookieCSRF
*/
@Bean
public CsrfTokenRepository csrfTokenRepository() {
return CookieCsrfTokenRepository.withHttpOnlyFalse();
}
}

@ -1,5 +1,5 @@
#MCSLMS DataSource Configuration - v1.7.0
#Tue Dec 16 20:51:03 CST 2025
#Tue Dec 16 22:20:56 CST 2025
database.host=127.0.0.1
database.name=testdb
database.password=

@ -0,0 +1,171 @@
package com.smartlibrary.database;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* SQL
*/
public class DatabaseHelper {
private static final Logger LOGGER = Logger.getLogger(DatabaseHelper.class.getName());
private final DatabaseConnection dbConnection;
public DatabaseHelper() {
this.dbConnection = DatabaseConnection.getInstance();
}
/**
*
* 使SQL
*
* @param sql SQL
* @param params
* @param resultHandler
* @param <T>
* @return
*/
public <T> T executeSafeQuery(String sql, Object[] params, ResultSetHandler<T> resultHandler) {
try (Connection conn = dbConnection.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 设置参数防止SQL注入
if (params != null) {
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
}
try (ResultSet rs = pstmt.executeQuery()) {
return resultHandler.handle(rs);
}
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, "执行安全查询失败: " + e.getMessage(), e);
throw new DatabaseException("查询执行失败", e);
}
}
/**
*
* 使SQL
*
* @param sql SQL
* @param params
* @return
*/
public int executeSafeUpdate(String sql, Object[] params) {
try (Connection conn = dbConnection.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 设置参数防止SQL注入
if (params != null) {
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
}
return pstmt.executeUpdate();
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, "执行安全更新失败: " + e.getMessage(), e);
throw new DatabaseException("更新执行失败", e);
}
}
/**
*
* 使
*
* @param sql SQL
* @param batchParams
* @return
*/
public int[] executeSafeBatchUpdate(String sql, Object[][] batchParams) {
try (Connection conn = dbConnection.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
conn.setAutoCommit(false);
try {
// 批量设置参数
for (Object[] params : batchParams) {
if (params != null) {
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
}
pstmt.addBatch();
}
int[] results = pstmt.executeBatch();
conn.commit();
return results;
} catch (SQLException e) {
conn.rollback();
LOGGER.log(Level.SEVERE, "执行批量更新失败: " + e.getMessage(), e);
throw new DatabaseException("批量更新执行失败", e);
} finally {
conn.setAutoCommit(true);
}
} catch (SQLException e) {
LOGGER.log(Level.SEVERE, "执行安全批量更新失败: " + e.getMessage(), e);
throw new DatabaseException("批量更新执行失败", e);
}
}
/**
* SQL
*
* @param input
* @return
*/
public String sanitizeInput(String input) {
if (input == null) {
return null;
}
// 移除潜在的SQL注入字符
return input.replaceAll("['\";\\\\]", "");
}
/**
* SQL
*
* @param identifier
* @return
*/
public String sanitizeIdentifier(String identifier) {
if (identifier == null) {
return null;
}
// 只允许字母、数字和下划线
if (!identifier.matches("^[a-zA-Z0-9_]+$")) {
throw new IllegalArgumentException("无效的标识符: " + identifier);
}
return identifier;
}
/**
*
*/
@FunctionalInterface
public interface ResultSetHandler<T> {
T handle(ResultSet rs) throws SQLException;
}
/**
*
*/
public static class DatabaseException extends RuntimeException {
public DatabaseException(String message, Throwable cause) {
super(message, cause);
}
}
}

@ -14,6 +14,7 @@ import java.util.logging.Logger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
/**
* - v1.12.0
@ -296,7 +297,22 @@ public class UserService extends BaseService {
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
return password; // 降级处理
LOGGER.log(Level.SEVERE, "密码哈希算法不可用,使用备选方案", e);
// 使用安全的备选方案,而不是返回明文密码
try {
// 使用MD5作为备选虽然不如SHA-256安全但比明文好
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(password.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e2) {
// 如果MD5也不可用使用Base64编码这不是安全的哈希但比明文好
LOGGER.log(Level.SEVERE, "所有哈希算法都不可用使用Base64编码", e2);
return Base64.getEncoder().encodeToString(password.getBytes(StandardCharsets.UTF_8));
}
}
}

@ -0,0 +1,168 @@
# MCSLMS项目高效质量修复指南
## 1. 概述
本指南将帮助您利用Jenkins和SonarQube流水线高效修复MCSLMS项目的质量问题确保代码质量符合项目标准。
## 2. 流水线架构与优势
### 2.1 流水线阶段
我们的Jenkins流水线包含以下关键阶段
| 阶段 | 描述 | 修复相关 |
|------|------|---------|
| 1. 环境信息 | 验证构建环境 | 确保修复环境一致性 |
| 2. 构建Core | 构建核心模块 | 确保核心功能正常 |
| 3. 运行单元测试 | 执行测试并生成覆盖率 | 验证修复不破坏现有功能 |
| 4. SonarQube质量扫描 | 代码质量检测 | 发现待修复问题 |
| 5. 质量门禁检查 | 验证质量达标 | 阻止低质量代码通过 |
| 6. 清理旧制品 | 清理历史构建文件 | 避免构建冲突 |
| 7. 构建四端 | 构建所有客户端 | 确保修复在各端正常工作 |
| 8. 创建GUI EXE | 生成可执行文件 | 验证最终产物 |
### 2.2 流水线优势
1. **自动化反馈**:提交代码后自动触发扫描
2. **质量门禁**:质量不达标自动阻止构建
3. **测试覆盖**:确保修复不破坏现有功能
4. **多端验证**:同时验证四端构建
## 3. 高效修复流程
### 3.1 问题发现与优先级划分
1. **登录SonarQube**:访问 http://localhost:9000
2. **查看项目**:选择项目 `mcslms`
3. **分析问题**
- 点击「Issues」查看所有问题
- 使用过滤器按严重性、类型、组件筛选
- 查看「Hotspots」识别高风险代码
4. **优先级划分**
- **P0**:编译错误、严重漏洞(立即修复)
- **P1**功能缺陷、安全漏洞24小时内修复
- **P2**性能问题、代码异味3天内修复
- **P3**格式问题、minor优化1周内修复
### 3.2 代码修复最佳实践
1. **修复前准备**
- 确保本地环境与流水线一致
- 更新代码到最新版本
- 运行本地测试确保基础功能正常
2. **修复技巧**
- **使用IDE辅助**利用IntelliJ IDEA的自动修复功能
- **批量修复**:对同一类型问题进行批量处理
- **参考规则说明**查看SonarQube规则详情了解修复方法
- **保持一致性**:遵循项目现有代码风格
3. **修复后验证**
- 运行本地测试:`gradlew test`
- 生成覆盖率报告:`gradlew jacocoTestReport`
- 本地SonarQube扫描
```bash
gradlew sonar -Dsonar.host.url=http://localhost:9000 -Dsonar.token=sqp_28dee20a416dd020466799252e09228a1799e2be
```
### 3.3 利用流水线验证修复
1. **提交代码**
```bash
git add .
git commit -m "修复: [问题描述]"
git push origin develop
```
2. **监控流水线**
- 访问Jenkinshttp://localhost:8084/job/mcslms/
- 查看构建进度和结果
- 关注质量门禁阶段阶段5
3. **验证修复结果**
- 查看Jenkins构建日志
- 检查SonarQube重新扫描报告
- 验证问题是否已解决
## 4. 常见问题处理
### 4.1 修复后构建失败
**问题**修复代码后Jenkins构建失败
**解决方案**
1. 查看Jenkins构建日志定位错误
2. 检查是否破坏了现有功能
3. 验证修复是否符合编码规范
4. 确保依赖关系正确
### 4.2 质量门禁不通过
**问题**:修复后质量门禁检查失败
**解决方案**
1. 查看SonarQube报告中的具体问题
2. 检查是否有新的问题被引入
3. 调整修复方案,确保符合质量标准
4. 必要时与团队讨论调整质量门禁规则
### 4.3 覆盖率下降
**问题**:修复后测试覆盖率下降
**解决方案**
1. 为修复的代码添加单元测试
2. 确保测试用例覆盖了新的代码路径
3. 优化测试用例,提高覆盖率
## 5. 高级技巧
### 5.1 使用IDE插件
1. **SonarLint插件TRAE版**
- 在TRAE IDE插件市场中搜索并安装「SonarLint」
- 打开「设置」→「工具」→「SonarLint」填写本地SonarQube地址与Token
- 启用「实时扫描」开关,即可在编码时即时提示问题
2. **代码质量工具**
- CheckStyle确保代码风格一致
- PMD检测代码异味
- FindBugs识别潜在缺陷
### 5.2 批量修复策略
1. **按规则修复**
```bash
# 使用Gradle执行CheckStyle并修复
gradlew checkstyleFix
```
2. **使用IDE批量修复**
- 进入「Code」→「Inspect Code」
- 选择「Whole Project」
- 点击「Fix All」按钮批量修复
### 5.3 修复效率统计
利用SonarQube的「Activity」面板跟踪修复效率
- 监控问题数量变化趋势
- 分析修复速度
- 识别高风险区域
## 6. 总结
通过遵循本指南您可以高效地利用Jenkins和SonarQube流水线修复MCSLMS项目的质量问题。关键在于
1. **自动化流水线**:减少手动操作,提高效率
2. **优先级管理**:集中精力解决关键问题
3. **持续验证**:确保修复质量和稳定性
4. **团队协作**:共享修复经验,提高整体水平
通过这些方法,我们可以持续提高代码质量,确保项目的长期可维护性和稳定性。
---
**文档版本**: 1.0
**创建日期**: 2025-12-16
**适用版本**: Jenkins 2.300+, SonarQube 25.11.0+, Gradle 8.5+

@ -2,6 +2,7 @@ plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.14'
id 'jacoco'
}
group = 'com.smartlibrary'
@ -42,6 +43,15 @@ dependencies {
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
jacocoTestReport {
dependsOn test
reports {
xml.required = true
html.required = true
}
}
application {

@ -0,0 +1,16 @@
# 应用程序配置文件
# 登录相关配置
# 默认登录凭据(用于开发环境)
login.default.username=admin
login.default.password=admin
# 数据库连接配置
database.url=jdbc:postgresql://localhost:5432/mcslms
database.username=postgres
database.password=postgres
# 应用程序设置
app.name=Smart Library Management System
app.version=1.0.0
app.environment=development

@ -0,0 +1,200 @@
package com.smartlibrary.gui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*
*/
public class LoginDialog extends JDialog {
private static final Logger LOGGER = Logger.getLogger(LoginDialog.class.getName());
private JTextField usernameField;
private JPasswordField passwordField;
private JButton loginButton;
private JButton cancelButton;
private boolean authenticated = false;
// 配置属性
private static final String CONFIG_FILE = "config/application.properties";
private static final String DEFAULT_USERNAME_PROP = "login.default.username";
private static final String DEFAULT_PASSWORD_PROP = "login.default.password";
public LoginDialog(JFrame parent) {
super(parent, "登录", true);
initializeComponents();
loadDefaultCredentials();
setupLayout();
setupEventHandlers();
pack();
setLocationRelativeTo(parent);
}
/**
*
*/
private void initializeComponents() {
usernameField = new JTextField(20);
passwordField = new JPasswordField(20);
loginButton = new JButton("登录");
cancelButton = new JButton("取消");
}
/**
*
*
*/
private void loadDefaultCredentials() {
Properties props = new Properties();
try {
props.load(new FileInputStream(CONFIG_FILE));
String defaultUsername = props.getProperty(DEFAULT_USERNAME_PROP, "");
String defaultPassword = props.getProperty(DEFAULT_PASSWORD_PROP, "");
if (!defaultUsername.isEmpty()) {
usernameField.setText(defaultUsername);
}
if (!defaultPassword.isEmpty()) {
passwordField.setText(defaultPassword);
}
} catch (IOException e) {
LOGGER.log(Level.INFO, "无法加载配置文件,使用空默认值: {0}", e.getMessage());
// 如果配置文件不存在,使用空值
usernameField.setText("");
passwordField.setText("");
}
}
/**
*
*/
private void setupLayout() {
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// 创建表单面板
JPanel formPanel = new JPanel(new GridLayout(2, 2, 5, 5));
formPanel.add(new JLabel("用户名:"));
formPanel.add(usernameField);
formPanel.add(new JLabel("密码:"));
formPanel.add(passwordField);
// 创建按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
buttonPanel.add(loginButton);
buttonPanel.add(cancelButton);
mainPanel.add(formPanel, BorderLayout.CENTER);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
setContentPane(mainPanel);
}
/**
*
*/
private void setupEventHandlers() {
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
performLogin();
}
});
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
dispose();
}
});
// 按Enter键触发登录
getRootPane().setDefaultButton(loginButton);
}
/**
*
*/
private void performLogin() {
String username = usernameField.getText().trim();
String password = new String(passwordField.getPassword()).trim();
if (username.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(this, "用户名和密码不能为空", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
// 这里应该调用实际的认证服务
if (authenticateUser(username, password)) {
authenticated = true;
dispose();
} else {
JOptionPane.showMessageDialog(this, "用户名或密码错误", "错误", JOptionPane.ERROR_MESSAGE);
passwordField.setText("");
}
}
/**
*
*
*/
private boolean authenticateUser(String username, String password) {
// TODO: 集成实际的用户认证服务
// 临时实现:检查是否匹配配置文件中的凭据
Properties props = new Properties();
try {
props.load(new FileInputStream(CONFIG_FILE));
String expectedUsername = props.getProperty(DEFAULT_USERNAME_PROP, "admin");
String expectedPassword = props.getProperty(DEFAULT_PASSWORD_PROP, "admin");
return username.equals(expectedUsername) && password.equals(expectedPassword);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "认证时无法读取配置文件: {0}", e.getMessage());
// 如果配置文件不存在,使用默认凭据
return username.equals("admin") && password.equals("admin");
}
}
/**
*
*/
public boolean isAuthenticated() {
return authenticated;
}
/**
*
*/
public String getUsername() {
return usernameField.getText().trim();
}
/**
* -
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("测试");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setLocationRelativeTo(null);
LoginDialog dialog = new LoginDialog(frame);
dialog.setVisible(true);
if (dialog.isAuthenticated()) {
System.out.println("用户 " + dialog.getUsername() + " 登录成功");
} else {
System.out.println("用户取消登录");
}
});
}
}

@ -1,166 +1,166 @@
# ========================================
# SLMS - SonarQube 项目配置
# Smart Library Management System
# ========================================
# 项目基本信息
sonar.projectKey=mcslms
sonar.projectName=mcslms
sonar.projectVersion=1.0-SNAPSHOT
sonar.projectDescription=智能图书管理系统 - 多平台图书管理解决方案支持CLI、GUI、Web和Android四端应用
# ========================================
# Gradle 多模块配置
# ========================================
# 跳过编译(构建后再扫描)
sonar.gradle.skipCompile=true
# 模块源代码路径
sonar.sources=core/src/main/java,cli/src/main/java,gui/src/main/java,backend/src/main/java,android/src/main/java
# 测试代码
sonar.tests=core/src/test/java,android/src/test/java
# 编码格式
sonar.sourceEncoding=UTF-8
# Java 版本
sonar.java.source=21
sonar.java.target=21
# ========================================
# 编译输出配置
# ========================================
# Java 编译输出Gradle 多模块)
sonar.java.binaries=core/build/classes/java/main,cli/build/classes/java/main,gui/build/classes/java/main,backend/build/classes/java/main,android/build/intermediates/javac/debug/classes
# 测试编译输出
sonar.java.test.binaries=core/build/classes/java/test,cli/build/classes/java/test,gui/build/classes/java/test,backend/build/classes/java/test,android/build/intermediates/javac/debugUnitTest/classes
# 依赖库
sonar.java.libraries=core/build/libs/*.jar,android/build/intermediates/compile_library_classes_jar/debug/*.jar
# ========================================
# Web 应用配置WUI
# ========================================
# HTML 模板文件
sonar.web.file.suffixes=.html,.xhtml,.jsp
# Web 资源目录
sonar.html.file.suffixes=.html
# ========================================
# 覆盖率配置
# ========================================
# 注意如果源码修改后未重新运行测试JaCoCo报告会与源码行号不匹配
# 解决方案:运行 gradlew clean test jacocoTestReport 后再执行 sonar
# 或者临时禁用覆盖率导入(设为空路径)
sonar.coverage.jacoco.xmlReportPaths=
sonar.java.coveragePlugin=jacoco
# 排除不需要覆盖率分析的文件
sonar.coverage.exclusions=\
**/test/**,\
**/*Test.java,\
**/config/**,\
**/*Application.java,\
**/Main.java,\
**/SLMS.java,\
**/TestRunner.java,\
**/GUITestRunner.java,\
**/WebTestRunner.java,\
**/InitDatabase.java,\
**/MainActivity.java,\
**/SimpleActivity.java
# ========================================
# 排除配置
# ========================================
sonar.exclusions=\
**/target/**,\
**/build/**,\
**/.gradle/**,\
**/node_modules/**,\
**/*.min.js,\
**/*.min.css,\
**/generated/**,\
**/R.java,\
**/BuildConfig.java,\
**/android/src/main/res/**,\
docs/**,\
scripts/**,\
**/*.md,\
**/*.xml,\
**/*.gradle,\
**/*.properties
sonar.test.exclusions=\
**/test/**,\
**/*Test.java
# ========================================
# 链接配置
# ========================================
# Gitea 本地仓库
sonar.links.homepage=http://localhost:3000/mcslms/
# Jenkins CI
sonar.links.ci=http://localhost:8084/job/mcslms
# 头歌远程仓库
sonar.links.scm=https://bdgit.educoder.net/pu6zrsfoy/mcslms.git
# 问题跟踪
sonar.links.issue=http://localhost:3000/mcslms/issues
# ========================================
# 其他配置
# ========================================
# 禁用 SCM 计算以加快分析
sonar.scm.disabled=true
# 新代码周期
sonar.newCode.referenceBranch=main
# 项目标签(四端应用)
sonar.projectTags=library-management,java,cli,gui,web,android,multi-platform
# 日志级别
sonar.log.level=INFO
sonar.verbose=false
# ========================================
# 四端应用说明
# ========================================
# CLI (Command Line Interface)
# - 入口: com.smartlibrary.cli.CLIApplication
# - 位置: src/main/java/com/smartlibrary/cli/
# GUI (Graphical User Interface - JavaFX)
# - 入口: com.smartlibrary.gui.GUIApplication
# - 位置: src/main/java/com/smartlibrary/gui/
# WUI (Web User Interface - Spring Boot)
# - 入口: com.smartlibrary.web.WebApplication
# - 位置: src/main/java/com/smartlibrary/web/
# - 模板: src/main/resources/templates/
# MUI (Mobile User Interface - Android)
# - 入口: com.smartlibrary.android.MainActivity
# - 位置: android/src/main/java/com/smartlibrary/android/
# 共享模块
# - 数据模型: src/main/java/com/smartlibrary/model/
# - 业务服务: src/main/java/com/smartlibrary/service/
# - 数据库: src/main/java/com/smartlibrary/database/
# - 工厂模式: src/main/java/com/smartlibrary/factory/
# - 观察者模式: src/main/java/com/smartlibrary/observer/
# - 通知系统: src/main/java/com/smartlibrary/notification/
# ========================================
# SLMS - SonarQube 项目配置
# Smart Library Management System
# ========================================
# 项目基本信息
sonar.projectKey=mcslms
sonar.projectName=mcslms
sonar.projectVersion=1.0-SNAPSHOT
sonar.projectDescription=智能图书管理系统 - 多平台图书管理解决方案支持CLI、GUI、Web和Android四端应用
# ========================================
# Gradle 多模块配置
# ========================================
# 跳过编译(构建后再扫描)
sonar.gradle.skipCompile=true
# 模块源代码路径
sonar.sources=core/src/main/java,cli/src/main/java,gui/src/main/java,backend/src/main/java,android/src/main/java
# 测试代码
sonar.tests=core/src/test/java,android/src/test/java
# 编码格式
sonar.sourceEncoding=UTF-8
# Java 版本
sonar.java.source=21
sonar.java.target=21
# ========================================
# 编译输出配置
# ========================================
# Java 编译输出Gradle 多模块)
sonar.java.binaries=core/build/classes/java/main,cli/build/classes/java/main,gui/build/classes/java/main,backend/build/classes/java/main,android/build/intermediates/javac/debug/classes
# 测试编译输出
sonar.java.test.binaries=core/build/classes/java/test,cli/build/classes/java/test,gui/build/classes/java/test,backend/build/classes/java/test,android/build/intermediates/javac/debugUnitTest/classes
# 依赖库
sonar.java.libraries=core/build/libs/*.jar,android/build/intermediates/compile_library_classes_jar/debug/*.jar
# ========================================
# Web 应用配置WUI
# ========================================
# HTML 模板文件
sonar.web.file.suffixes=.html,.xhtml,.jsp
# Web 资源目录
sonar.html.file.suffixes=.html
# ========================================
# 覆盖率配置
# ========================================
# 注意如果源码修改后未重新运行测试JaCoCo报告会与源码行号不匹配
# 解决方案:运行 gradlew clean test jacocoTestReport 后再执行 sonar
# 或者临时禁用覆盖率导入(设为空路径)
sonar.coverage.jacoco.xmlReportPaths=core/build/reports/jacoco/test/jacocoTestReport.xml,cli/build/reports/jacoco/test/jacocoTestReport.xml,gui/build/reports/jacoco/test/jacocoTestReport.xml,backend/build/reports/jacoco/test/jacocoTestReport.xml
sonar.java.coveragePlugin=jacoco
# 排除不需要覆盖率分析的文件
sonar.coverage.exclusions=\
**/test/**,\
**/*Test.java,\
**/config/**,\
**/*Application.java,\
**/Main.java,\
**/SLMS.java,\
**/TestRunner.java,\
**/GUITestRunner.java,\
**/WebTestRunner.java,\
**/InitDatabase.java,\
**/MainActivity.java,\
**/SimpleActivity.java
# ========================================
# 排除配置
# ========================================
sonar.exclusions=\
**/target/**,\
**/build/**,\
**/.gradle/**,\
**/node_modules/**,\
**/*.min.js,\
**/*.min.css,\
**/generated/**,\
**/R.java,\
**/BuildConfig.java,\
**/android/src/main/res/**,\
docs/**,\
scripts/**,\
**/*.md,\
**/*.xml,\
**/*.gradle,\
**/*.properties
sonar.test.exclusions=\
**/test/**,\
**/*Test.java
# ========================================
# 链接配置
# ========================================
# Gitea 本地仓库
sonar.links.homepage=http://localhost:3000/mcslms/
# Jenkins CI
sonar.links.ci=http://localhost:8084/job/mcslms
# 头歌远程仓库
sonar.links.scm=https://bdgit.educoder.net/pu6zrsfoy/mcslms.git
# 问题跟踪
sonar.links.issue=http://localhost:3000/mcslms/issues
# ========================================
# 其他配置
# ========================================
# 禁用 SCM 计算以加快分析
sonar.scm.disabled=true
# 新代码周期
sonar.newCode.referenceBranch=main
# 项目标签(四端应用)
sonar.projectTags=library-management,java,cli,gui,web,android,multi-platform
# 日志级别
sonar.log.level=INFO
sonar.verbose=false
# ========================================
# 四端应用说明
# ========================================
# CLI (Command Line Interface)
# - 入口: com.smartlibrary.cli.CLIApplication
# - 位置: src/main/java/com/smartlibrary/cli/
# GUI (Graphical User Interface - JavaFX)
# - 入口: com.smartlibrary.gui.GUIApplication
# - 位置: src/main/java/com/smartlibrary/gui/
# WUI (Web User Interface - Spring Boot)
# - 入口: com.smartlibrary.web.WebApplication
# - 位置: src/main/java/com/smartlibrary/web/
# - 模板: src/main/resources/templates/
# MUI (Mobile User Interface - Android)
# - 入口: com.smartlibrary.android.MainActivity
# - 位置: android/src/main/java/com/smartlibrary/android/
# 共享模块
# - 数据模型: src/main/java/com/smartlibrary/model/
# - 业务服务: src/main/java/com/smartlibrary/service/
# - 数据库: src/main/java/com/smartlibrary/database/
# - 工厂模式: src/main/java/com/smartlibrary/factory/
# - 观察者模式: src/main/java/com/smartlibrary/observer/
# - 通知系统: src/main/java/com/smartlibrary/notification/
Loading…
Cancel
Save