Java?處理樹形結(jié)構(gòu)數(shù)據(jù)的過(guò)程
前言
問(wèn)題的背景大概是這樣的,有下面這樣一個(gè)excel表,原始數(shù)據(jù)結(jié)構(gòu)如下:

需求點(diǎn):
- 導(dǎo)入excel的機(jī)構(gòu)層級(jí)數(shù)據(jù)到mysql的機(jī)構(gòu)表(這里假設(shè)為 depart);
- 導(dǎo)入的機(jī)構(gòu)數(shù)據(jù)以層級(jí)進(jìn)行保存和區(qū)分;
- 界面展示時(shí)需要以完整的樹形層級(jí)進(jìn)行展示;
處理過(guò)程
按照上面已知的信息,設(shè)計(jì)一個(gè)簡(jiǎn)單的機(jī)構(gòu)表
CREATE TABLE `depart` ( `depart_id` varchar(64) DEFAULT NULL, `pid` varchar(64) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `path` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
接下來(lái)分析下具體的實(shí)現(xiàn);
1、導(dǎo)入的時(shí)候以層級(jí)保存數(shù)據(jù)
有做過(guò)樹形結(jié)構(gòu)處理業(yè)務(wù)的小伙伴們對(duì)樹形結(jié)構(gòu)的處理并不陌生,主要就是在機(jī)構(gòu)表中合理運(yùn)用 id 和 pid 構(gòu)建數(shù)據(jù)層級(jí)關(guān)系,期間可能涉及到遞歸的操作(不建議在數(shù)據(jù)庫(kù)層面遞歸);
但我們說(shuō)具體問(wèn)題具體分析,比如按照上面的exce表的數(shù)據(jù)結(jié)構(gòu),以 “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” 這個(gè)機(jī)構(gòu)為例做簡(jiǎn)單說(shuō)明
- 以 “/” 區(qū)分,這條數(shù)據(jù)涉及到2個(gè)部門,“產(chǎn)品研發(fā)中心” 為一級(jí)部門,而 “業(yè)務(wù)一部” 為其子部門,即二級(jí)部門;
- 按照第一步的分析,需要將這條數(shù)據(jù)拆開來(lái)做處理,通過(guò)部門名稱以及其所在層級(jí)(業(yè)務(wù)中可能還存在其他字段),即在查庫(kù)過(guò)程中作為查詢條件;
- 需要考慮excel中的數(shù)據(jù)存在性,比如 “產(chǎn)品研發(fā)中心” 這條數(shù)據(jù)在數(shù)據(jù)庫(kù)中可能存在,也可能不存在,“業(yè)務(wù)一部” 也可能存在或不存在;
- 在第三步的基礎(chǔ)上,需要按照程序的邏輯設(shè)置一定的規(guī)則,大概如下:1、頂級(jí)部門不存在,可以認(rèn)為這條數(shù)據(jù)在數(shù)據(jù)庫(kù)中不存在,處理的時(shí)候直接按照正常的規(guī)則;2、頂級(jí)部門存在,直接處理最后一級(jí)數(shù)據(jù);
- 不考慮中間部門層級(jí),像 “/A/B/C” 中的B是否存在問(wèn)題;
2、返回樹形層級(jí)結(jié)構(gòu)數(shù)據(jù)
相比導(dǎo)入來(lái)說(shuō),返回樹形層級(jí)結(jié)構(gòu)相對(duì)來(lái)說(shuō),已經(jīng)有相對(duì)成熟的處理方式,大致思路如下:
- 查詢所有數(shù)據(jù)(如果考慮動(dòng)態(tài)加載另說(shuō));
- 構(gòu)建返回?cái)?shù)據(jù)的樹形結(jié)構(gòu)(可根據(jù) id 和 pid 的關(guān)系);
樹形結(jié)構(gòu)的返回對(duì)象結(jié)構(gòu)大致如下
public class DepartDTO {
private String departId;
private String pid;
private String name;
private String path;
private String pname;
private List<DepartDTO> children;
}返回樹形層級(jí)數(shù)據(jù)
先來(lái)做第二個(gè)需求,由于解決思路已經(jīng)給出,只需要按照思路進(jìn)行即可,下面貼出主要的邏輯代碼
public static String random() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static List<DepartDTO> getDepartTree(List<DepartDTO> allDepart) {
//查詢到的所有的部門數(shù)據(jù)
//List<DepartDTO> allDepart = getAllDepart();
//最高級(jí)別用戶集合
List<DepartDTO> roots = new ArrayList<>();
List<DepartDTO> res = new ArrayList<>();
for (DepartDTO departDto : allDepart) {
//-1表示最高級(jí)別的用戶
if (departDto.getPid().equals("0")) {
roots.add(departDto);
}
}
//從最高級(jí)別用戶開始遍歷,遞歸找到該用戶的下級(jí)用戶,將帶有下級(jí)的最高級(jí)用戶放入返回結(jié)果中
for (DepartDTO userDto : roots) {
userDto = buildUserTree(allDepart, userDto);
res.add(userDto);
}
return res;
}
public static DepartDTO buildUserTree(List<DepartDTO> allDeparts, DepartDTO departDTO) {
List<DepartDTO> children = new ArrayList<>();
//遍歷查到的所有用戶
for (DepartDTO departDTO1 : allDeparts) {
//-1代表根節(jié)點(diǎn),無(wú)需重復(fù)比較
if (departDTO1.getPid().equals("0") || departDTO1.getPname().equals("") || departDTO1.getPname() == null)
continue;
//當(dāng)前用戶的上級(jí)編號(hào)和傳入的用戶編號(hào)相等,表示該用戶是傳入用戶的下級(jí)用戶
if (departDTO1.getPname().equals(departDTO.getName())) {
//遞歸調(diào)用,再去尋找該用戶的下級(jí)用戶
departDTO1 = buildUserTree(allDeparts, departDTO1);
//當(dāng)前用戶是該用戶的一個(gè)下級(jí)用戶,放入children集合內(nèi)
children.add(departDTO1);
}
}
//給該用戶的children屬性賦值,并返回該用戶
departDTO.setChildren(children);
return departDTO;
}這里先直接模擬一部分的數(shù)據(jù),通過(guò)這部分的數(shù)據(jù)做處理
static List<String> departLists = new ArrayList<>();
static {
departLists.add("/產(chǎn)品研發(fā)中心/業(yè)務(wù)中臺(tái)部");
departLists.add("/產(chǎn)品研發(fā)中心/技術(shù)中臺(tái)部");
departLists.add("/產(chǎn)品研發(fā)中心/技術(shù)中臺(tái)部/產(chǎn)品A組");
departLists.add("/總裁辦");
departLists.add("/總裁辦/品牌管理部");
}
public static void main(String[] args) {
List<DepartDTO> allDepart = getAllDepart();
List<DepartDTO> departTree = getDepartTree(allDepart);
System.out.println(departTree);
}上面程序用到的兩個(gè)工具類
public class PathUtils {
public static List<String> getPaths(String path) {
List<String> parentPaths = getParentPaths(path);
parentPaths.add(path);
return parentPaths;
}
public static String getParentPath(String path) {
return StringUtils.substringBeforeLast(StringUtils.removeEnd(path, "/"), "/");
}
public static List<String> getParentPaths(String path) {
List<String> paths = new ArrayList<>();
while (true) {
path = getParentPath(path);
if (StringUtils.isBlank(path)) {
break;
}
paths.add(path);
}
paths.sort(String::compareTo);
return paths;
}
/**
* 拼接部門名稱完整路徑,如 湖北省,襄陽(yáng)市,谷城縣,最終組裝成 : 湖北省/襄陽(yáng)市/谷城縣
*
* @param paths
* @return
*/
public static String merge(List<String> paths) {
return merge(paths, null);
}
public static String merge(List<String> paths, Function<String, String> function) {
if (CollectionUtils.isEmpty(paths)) {
return null;
}
Stream<String> stream = paths.stream();
if (Objects.nonNull(function)) {
stream = stream.map(function);
}
return stream.filter(Objects::nonNull).collect(joining("/", "/", ""));
}
public static String getNextPath(String path) {
path = StringUtils.removeEnd(path, "/");
String parentPath = StringUtils.substringBeforeLast(path, "/");
int val = NumberUtils.toInt(StringUtils.substringAfterLast(path, "/")) + 1;
return parentPath + "/" + StringUtils.leftPad(String.valueOf(val), 4, "0");
}
public static String getNextPath(String parentPath, List<String> childPaths) {
if (CollectionUtils.isEmpty(childPaths)) {
return parentPath + "/001";
}
if (childPaths.size() + 1 >= 1000) {
throw new RuntimeException("同級(jí)機(jī)構(gòu)最多支持9999個(gè)");
}
//獲取同級(jí)最大值路徑
Collections.sort(childPaths, Comparator.reverseOrder());
String maxPath = childPaths.get(0);
if (StringUtils.isNotBlank(maxPath)) {
return PathUtils.getNextPath(maxPath);
}
return parentPath + "/001";
}
public static void main(String[] args) {
/*System.out.println(getParentPaths("/001/002/003/004/"));
List<String> childPaths = new ArrayList<>();
childPaths.add("/001");
childPaths.add("/007");
childPaths.add("/1000");
childPaths.add("/001");
childPaths.add("/901");
childPaths.add("/766");
List<Integer> result = new ArrayList<>();
childPaths.forEach(item ->{
result.add(Integer.valueOf(item.substring(1)));;
});
Integer max = Collections.max(result);
System.out.println(max);*/
String pathNames = "/產(chǎn)品研發(fā)中心/業(yè)務(wù)中臺(tái)部";
String substring = pathNames.substring(pathNames.lastIndexOf("/") + 1);
System.out.println(substring);
//String paths = "/001/002/003/004/";
String paths = "/001/001";
List<String> parentPaths = getParentPaths(paths);
System.out.println(parentPaths);
}
public static String getMaxPath(List<String> pathList) {
List<Integer> result = new ArrayList<>();
pathList.forEach(item -> {
result.add(Integer.valueOf(item.substring(1)));
;
});
Integer max = Collections.max(result);
return String.valueOf("/" + max);
}
}最后寫個(gè)接口模擬下
//localhost:8087/getAllDepart
@GetMapping("/getAllDepart")
public Object getAllDepart() {
return departService.importDepart();
}運(yùn)行上面的main程序,觀察控制臺(tái)輸出結(jié)果

用格式化工具處理下再看,這即為我們期待的結(jié)果,實(shí)際業(yè)務(wù)中,只需要在 getAllDepart 這個(gè)方法中,將獲取數(shù)據(jù)從數(shù)據(jù)庫(kù)查詢即可;
[
{
"departId":"e1c6d8ba4a504b7da85472ca713be107",
"pid":"0",
"name":"產(chǎn)品研發(fā)中心",
"path":null,
"pname":"",
"children":[
{
"departId":"8e39b272531449ca96c0668ae60d2c2f",
"pid":"e1c6d8ba4a504b7da85472ca713be107",
"name":"業(yè)務(wù)中臺(tái)部",
"path":null,
"pname":"產(chǎn)品研發(fā)中心",
"children":[
]
},
{
"departId":"ecfe24e1769248df885287c7e153f9e6",
"pid":"e1c6d8ba4a504b7da85472ca713be107",
"name":"技術(shù)中臺(tái)部",
"path":null,
"pname":"產(chǎn)品研發(fā)中心",
"children":[
{
"departId":"0218c648abdf4867ad5ea1e99098d526",
"pid":"ecfe24e1769248df885287c7e153f9e6",
"name":"產(chǎn)品A組",
"path":null,
"pname":"技術(shù)中臺(tái)部",
"children":[
]
}
]
}
]
},
{
"departId":"843bfa6b371e4d7d8d44894d939ca0a5",
"pid":"0",
"name":"總裁辦",
"path":null,
"pname":"",
"children":[
{
"departId":"12dc458b6996484394e2026d5b0f547e",
"pid":"843bfa6b371e4d7d8d44894d939ca0a5",
"name":"品牌管理部",
"path":null,
"pname":"總裁辦",
"children":[
]
}
]
}
]數(shù)據(jù)導(dǎo)入
其實(shí),只要按照上文的處理思路做即可,但是這里提一個(gè)在邏輯編寫過(guò)程中遇到的一個(gè)比較難處理的問(wèn)題,即機(jī)構(gòu)的 path 上;
這里必須要說(shuō)一下這個(gè) path 的事情,path 在真實(shí)的業(yè)務(wù)場(chǎng)景中,是一個(gè)非常重要,并且在眾多的使用場(chǎng)景中高頻使用的字段,因?yàn)閷?duì)一個(gè)機(jī)構(gòu)來(lái)說(shuō),通過(guò)業(yè)務(wù)的區(qū)分,這個(gè)path一定是唯一的存在;
仍然使用文章開頭的那些數(shù)據(jù),最終將 “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” 這樣的數(shù)據(jù)入庫(kù)時(shí),需要將數(shù)據(jù)組裝成一個(gè)個(gè)對(duì)象插入到數(shù)據(jù)庫(kù),同時(shí),插入數(shù)據(jù)之前,層級(jí)也需要組裝好,那么對(duì)于 “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” 這樣一條數(shù)據(jù),可以想象到,將會(huì)產(chǎn)生兩個(gè) depart 對(duì)象,這里我們考慮下面兩個(gè)簡(jiǎn)單的場(chǎng)景;
- 如果頂級(jí)部門不存在,全量導(dǎo)入,比如 “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” 這樣一條數(shù)據(jù),當(dāng) “/產(chǎn)品研發(fā)中心” 不存在時(shí),完整導(dǎo)入;
- “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” ,當(dāng) “產(chǎn)品研發(fā)中心” 存在時(shí),只需導(dǎo)入 “業(yè)務(wù)一部” ;
下面來(lái)看核心代碼
@Service
public class DepartTest {
@Autowired
private DepartDao departDao;
@Autowired
private TransactionUtils transactionUtils;
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
static List<String> departLists = new ArrayList<>();
private static final String tenantId = "e10adc3949ba59abbe56e057f20f88dd";
static {
departLists.add("/top1");
departLists.add("/top1/ch1");
departLists.add("/top1/ch1/ch2");
}
public static List<String> getFullNames(String departName) {
List<String> result = new ArrayList<>();
String[] splitNames = departName.split("/");
for (int i = 0; i < splitNames.length; i++) {
if (!StringUtils.isEmpty(splitNames[i])) {
result.add(splitNames[i]);
}
}
return result;
}
public List<DepartDTO> getAllDepart() {
List<DepartDTO> departDTOS = new ArrayList<>();
//保存 部門名稱和部門ID的映射關(guān)系
Map<String, String> nameDepartIdMap = new HashMap<>();
List<Depart> dbExistDepart = new ArrayList<>();
List<Depart> newDeparts = new ArrayList<>();
for (String single : departLists) {
//全部的部門名稱
List<String> fullNames = getFullNames(single);
//直接父級(jí)
String parentPath = PathUtils.getParentPath(single);
//處理頂級(jí)的部門數(shù)據(jù)【只有自己本身,比如 "/總裁辦"】
if (StringUtils.isEmpty(parentPath)) {
//1、說(shuō)明當(dāng)前只有一級(jí),即頂級(jí)數(shù)據(jù)
//2、如果是頂級(jí)數(shù)據(jù),則需要判斷數(shù)據(jù)庫(kù)是否存在,如果已經(jīng)存在,不用管,如果不存在,生成新的相關(guān)數(shù)據(jù)
Depart depart = departDao.getTopDepartByName(fullNames.get(0));
if (depart != null) {
nameDepartIdMap.put(fullNames.get(0), depart.getDepartId());
//確認(rèn)數(shù)據(jù)庫(kù)已經(jīng)存在過(guò)的,后面只需要新建部門與用戶的關(guān)系即可
dbExistDepart.add(depart);
continue;
}
//如果數(shù)據(jù)不存在,新生成
String departId = random();
Depart newDepart = new Depart();
newDepart.setDepartId(departId);
newDepart.setName(fullNames.get(0));
newDepart.setPid("0");
newDepart.setPath(DepartUtils.getNextPath(tenantId, "0"));
TransactionStatus transaction = transactionUtils.getTransaction();
try {
departDao.insert(newDepart);
//設(shè)置手動(dòng)提交事務(wù)
dataSourceTransactionManager.commit(transaction);
} catch (Exception e) {
dataSourceTransactionManager.rollback(transaction);
}
newDeparts.add(newDepart);
nameDepartIdMap.put(fullNames.get(0), departId);
continue;
}
//如果是非頂級(jí)的,則需要拆開 /產(chǎn)品研發(fā)中心/技術(shù)中臺(tái)部/產(chǎn)品A組
for (int i = 0; i < fullNames.size(); i++) {
String currentDepart = fullNames.get(i);
//遍歷的時(shí)候從頂級(jí)開始
if (nameDepartIdMap.containsKey(currentDepart))
continue;
if (i == 0) {
TransactionStatus transaction = transactionUtils.getTransaction();
//仍然是頂級(jí),需要先查數(shù)據(jù)庫(kù)
Depart topDepart = departDao.getTopDepartByName(currentDepart);
if (topDepart != null) {
nameDepartIdMap.put(fullNames.get(0), topDepart.getDepartId());
dbExistDepart.add(topDepart);
} else {
//如果數(shù)據(jù)不存在,新生成
String departId = random();
Depart _depart = new Depart();
_depart.setDepartId(departId);
_depart.setName(fullNames.get(0));
_depart.setTenantId(tenantId);
_depart.setPid("0");
_depart.setPath(DepartUtils.getNextPath(tenantId, "0"));
try {
departDao.insert(_depart);
//設(shè)置手動(dòng)提交事務(wù)
dataSourceTransactionManager.commit(transaction);
} catch (Exception e) {
dataSourceTransactionManager.rollback(transaction);
}
if (fullNames.size() == 1) {
newDeparts.add(_depart);
}
}
continue;
}
//處理其他層級(jí)數(shù)據(jù)
String parentName = parentPath.substring(parentPath.lastIndexOf("/") + 1);
//開啟一個(gè)新的事務(wù)
TransactionStatus transaction = transactionUtils.getTransaction();
//判斷自身是否已經(jīng)存在了
Depart dbCurrentDepart = departDao.getDepartByNameAndPid(currentDepart, nameDepartIdMap.get(parentName));
if (dbCurrentDepart != null) {
//如果已經(jīng)存在了,直接跳過(guò)
dbExistDepart.add(dbCurrentDepart);
nameDepartIdMap.put(currentDepart, dbCurrentDepart.getDepartId());
dataSourceTransactionManager.commit(transaction);
continue;
}
Depart _depart = new Depart();
_depart.setTenantId(tenantId);
String departId = random();
_depart.setDepartId(departId);
_depart.setName(currentDepart);
String pid = nameDepartIdMap.get(parentName);
//判斷上級(jí)部門在數(shù)據(jù)庫(kù)是否真的存在
Depart directParent = departDao.getDepartById(nameDepartIdMap.get(parentName));
boolean isCurrentDepartDbExist = false;
if (directParent != null) {
_depart.setPid(pid);
String nextPath = DepartUtils.getNextPath(tenantId, pid);
_depart.setPath(nextPath);
departDao.insert(_depart);
dataSourceTransactionManager.commit(transaction);
//父級(jí)存在時(shí)
nameDepartIdMap.put(currentDepart, departId);
}
//如果是最后的那一個(gè),才是本次實(shí)際要關(guān)聯(lián)的部門數(shù)據(jù)
if (i == fullNames.size() - 1) {
if (isCurrentDepartDbExist) {
dbExistDepart.add(_depart);
nameDepartIdMap.put(currentDepart, departId);
continue;
}
newDeparts.add(_depart);
nameDepartIdMap.put(currentDepart, departId);
}
}
}
return departDTOS;
}
public static String random() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}在代碼編寫過(guò)程中,有一個(gè)比較難處理的問(wèn)題,就是在讀取外部數(shù)據(jù),組裝depart 的path的時(shí)候,為什么這么講呢?
要知道,根據(jù)上面描述的兩種實(shí)現(xiàn)情況,path 可能需要?jiǎng)討B(tài)組裝而成,很多同學(xué)可能會(huì)說(shuō),可以先把depart對(duì)象全部組裝完成,最后再通過(guò)層級(jí)關(guān)系構(gòu)建出 path 的完整路徑;
事實(shí)上,一開始我也是這么想的,但是最終發(fā)現(xiàn)這樣走不通,原因就在于 頂級(jí)部門 “/產(chǎn)品研發(fā)中心”在數(shù)據(jù)庫(kù)中可能存在,也可能不存在,而 path的生成一定是需要結(jié)合數(shù)據(jù)庫(kù)的某些業(yè)務(wù)字段動(dòng)態(tài)查詢而構(gòu)造出來(lái)的;
所以如果先組裝完成數(shù)據(jù)再構(gòu)建path,這樣帶來(lái)的問(wèn)題復(fù)雜性將會(huì)大大增加;
那么比較可行的而且可以實(shí)現(xiàn)的方式就是,在組裝數(shù)據(jù)的過(guò)程中,動(dòng)態(tài)查庫(kù)進(jìn)行組裝數(shù)據(jù);
但是小編在編碼的時(shí)候發(fā)現(xiàn),如果使用springboot工程自身的事務(wù)管理器的話,無(wú)論是哪種事務(wù)隔離級(jí)別,都將無(wú)法滿足這樣一個(gè)需求,即 “前一步將父級(jí)部門數(shù)據(jù)插入,子部門能夠查到父級(jí)的數(shù)據(jù)”這樣一個(gè)問(wèn)題;
所以為了達(dá)到這個(gè)目的,這里采用了 jdbc 手動(dòng)管理事務(wù)的方式進(jìn)行;
那么通過(guò)上面的方式,就可以實(shí)現(xiàn)層級(jí)數(shù)據(jù)的導(dǎo)入效果了,具體邏輯可以參考注釋說(shuō)明進(jìn)行理解;
到此這篇關(guān)于Java 處理樹形結(jié)構(gòu)數(shù)據(jù)的文章就介紹到這了,更多相關(guān)java 樹形結(jié)構(gòu)數(shù)據(jù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java 遞歸查詢部門樹形結(jié)構(gòu)數(shù)據(jù)的實(shí)踐
- java返回前端樹形結(jié)構(gòu)數(shù)據(jù)的2種實(shí)現(xiàn)方式
- 詳解如何使用Java流API構(gòu)建樹形結(jié)構(gòu)數(shù)據(jù)
- java父子節(jié)點(diǎn)parentid樹形結(jié)構(gòu)數(shù)據(jù)的規(guī)整
- java遞歸實(shí)現(xiàn)樹形結(jié)構(gòu)數(shù)據(jù)完整案例
- Java樹形結(jié)構(gòu)數(shù)據(jù)生成導(dǎo)出excel文件方法記錄
- Java如何使用遞歸查詢多級(jí)樹形結(jié)構(gòu)數(shù)據(jù)(多級(jí)菜單)
相關(guān)文章
SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的項(xiàng)目實(shí)踐
在實(shí)際開發(fā)過(guò)程中,我們經(jīng)常遇到需要同時(shí)操作多個(gè)數(shù)據(jù)源的情況,本文主要介紹了SpringBoot實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換的項(xiàng)目實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2024-04-04
SpringBoot集成Redis使用Cache緩存的實(shí)現(xiàn)方法
SpringBoot通過(guò)配置RedisConfig類和使用Cache注解可以輕松集成Redis實(shí)現(xiàn)緩存,主要包括@EnableCaching開啟緩存,自定義key生成器,改變序列化規(guī)則,以及配置RedisCacheManager,本文為使用SpringBoot與Redis處理緩存提供了詳實(shí)的指導(dǎo)和示例,感興趣的朋友一起看看吧2024-10-10
SpringBoot快速接入DeepSeek?api(帶頁(yè)面)保姆級(jí)教程
這篇文章主要介紹了如何在Java端接入DeepSeek?API,包括申請(qǐng)APIkey、項(xiàng)目結(jié)構(gòu)展示、編寫controller和前端界面、以及測(cè)試啟動(dòng)項(xiàng)目的過(guò)程,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03
Java面試題沖刺第四天--數(shù)據(jù)庫(kù)
這篇文章主要為大家分享了最有價(jià)值的三道數(shù)據(jù)庫(kù)面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下2021-07-07
springboot訪問(wèn)不存在的URL時(shí)的處理方法
在前后端分離的模式下,當(dāng)Spring Boot應(yīng)用接收到一個(gè)不存在的URL請(qǐng)求時(shí),通常希望返回一個(gè)固定的JSON字符串作為響應(yīng),以便前端能夠據(jù)此進(jìn)行相應(yīng)的處理,本文給大家介紹了springboot訪問(wèn)不存在的URL時(shí)的處理方法,需要的朋友可以參考下2024-12-12

