记录第一次SpringBoot开发的失败经历

一个小功能的失败的开发经历

一、开发前的想法

​ 刚开始的时候,乌蒙地插传分的时候每次都要一步步输入Bot的指令,我就想了以下,刚好最近几天入门了Java,想着搞个神秘的玩意儿,来自己构造一个简单的controller来自动生成Bot指令。

以下是我的开发失败经过

二、前端和前端渲染问题

​ 因为有过一些 Python 的 Flask 框架的基础,所以我知道一个东西叫模板渲染,这个可以保证后端代码的简洁,所以找ai生成了如下代码:

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>音游传分工具 | 支持歌曲名/版本</title>
<link rel="stylesheet" th:href="@{/css/style.css}">
</head>

<body>
<div class="card">
<div class="header">
<h1>
🎵 舞萌·传分器
<span>扩展版</span>
</h1>
<p>选择「传分」填写成绩 → 提交后端 → 获取指令</p>
</div>

<div class="mode-section">
<div class="mode-label">
<span>📌 模式选择:</span>
<select id="modeSelect">
<option value="transmit">✨ 传分</option>
<option value="buried">🕳️ 下埋</option>
</select>
<span class="badge-hint" style="background:#eef2ff;">传分包含9项参数</span>
</div>
</div>

<!-- 传分表单区域 (默认显示,下埋时隐藏) -->
<div id="scoreFormContainer" class="form-container">
<div class="form-grid">
<!-- 1. 乐曲ID (可选) -->
<div class="input-group">
<label>🎼 乐曲ID <span class="badge-hint">可选,空则传None</span></label>
<input type="text" id="musicId" placeholder="例: 11624 (可不填)" autocomplete="off">
</div>

<!-- 2. 歌曲名字 (可选) -->
<div class="input-group">
<label>📝 歌曲名字 <span class="badge-hint">可选,空则传None</span></label>
<input type="text" id="songName" placeholder="例: 夜に駆ける (可不填)" autocomplete="off">
</div>

<!-- 3. 歌曲版本 (必选,默认标准谱) -->
<div class="input-group">
<label>🎚️ 歌曲版本</label>
<select id="songVersion">
<option value="标准谱">标准谱</option>
<option value="DX谱">DX谱</option>
</select>
</div>

<!-- 4. 等级 (0-4 绿黄红紫白谱) -->
<div class="input-group">
<label>📊 等级 <span class="badge-hint">0绿 1黄 2红 3紫 4白</span></label>
<select id="levelSelect">
<option value="0">0 - 🟢 绿谱 (BASIC)</option>
<option value="1">1 - 🟡 黄谱 (ADVANCED)</option>
<option value="2">2 - 🔴 红谱 (EXPERT)</option>
<option value="3">3 - 🟣 紫谱 (MASTER)</option>
<option value="4">4 - ⚪ 白谱 (RE:MASTER)</option>
</select>
<div class="small-note">※ 0→绿 / 1→黄 / 2→红 / 3→紫 / 4→白</div>
</div>

<!-- 5. 游玩次数 -->
<div class="input-group">
<label>🔄 游玩次数</label>
<input type="number" id="playCount" placeholder="例: 3" min="0" step="1" value="1">
</div>

<!-- 6. 成绩分数 -->
<div class="input-group">
<label>🏆 成绩分数</label>
<input type="number" id="score" placeholder="例: 1010000" value="1010000" step="1">
<div class="small-note">理论值 1010000</div>
</div>

<!-- 7. 连击数 -->
<div class="input-group">
<label>⚡ 连击数 (Combo)</label>
<input type="number" id="combo" placeholder="最大连击" min="0" value="1250" step="1">
</div>

<!-- 8. 同步分数 (0-5) -->
<div class="input-group">
<label>🎯 同步分数 <span class="badge-hint">0无 → 5sync</span></label>
<select id="syncSelect">
<option value="0">0 - 无 (None)</option>
<option value="1">1 - fs</option>
<option value="2">2 - fs+</option>
<option value="3">3 - fdx</option>
<option value="4">4 - fdx+</option>
<option value="5">5 - sync</option>
</select>
<div class="small-note">0:无 / 1:fs / 2:fs+ / 3:fdx / 4:fdx+ / 5:sync</div>
</div>

<!-- 9. DX分 -->
<div class="input-group">
<label>✨ DX分</label>
<input type="number" id="dxScore" placeholder="DX 达成率分数" value="2845" step="any">
<div class="small-note">例如 2870</div>
</div>
</div>
</div>

<!-- 提交按钮与输出区域 -->
<div class="action-area">
<button class="submit-btn" id="submitDataBtn">📡 提交并生成指令</button>
</div>

<!-- 后端返回指令展示区 -->
<div class="result-area">
<div class="result-header">📟 后端返回指令</div>
<pre id="resultOutput" class="result-content">等待提交...</pre>
</div>
</div>

<div id="toastMessage" class="toast-msg"></div>

<script th:src="@{/js/script.js}" defer></script>
</body>

</html>

style.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: system-ui, 'Segoe UI', 'Poppins', 'Noto Sans', sans-serif;
}

body {
background: linear-gradient(145deg, #f0f4fa 0%, #d9e2ef 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 2rem 1.5rem;
}

.card {
max-width: 960px;
width: 100%;
background: rgba(255, 255, 255, 0.96);
backdrop-filter: blur(2px);
border-radius: 2rem;
box-shadow: 0 20px 35px -12px rgba(0, 0, 0, 0.2), 0 1px 3px rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: all 0.2s ease;
}

.header {
background: #1a2c3e;
padding: 1.5rem 2rem;
color: white;
}

.header h1 {
font-size: 1.8rem;
font-weight: 600;
letter-spacing: -0.3px;
display: flex;
align-items: center;
gap: 0.5rem;
}

.header h1 span {
background: #ffb347;
font-size: 0.85rem;
padding: 0.2rem 0.7rem;
border-radius: 40px;
color: #1a2c3e;
font-weight: 500;
}

.header p {
font-size: 0.85rem;
opacity: 0.8;
margin-top: 0.5rem;
}

.mode-section {
padding: 1.8rem 2rem 0.8rem 2rem;
border-bottom: 1px solid #e9edf2;
background: #ffffff;
}

.mode-label {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
font-weight: 600;
color: #1e2f3f;
}

.mode-label span {
font-size: 1rem;
}

select {
background-color: #f8fafc;
border: 1px solid #cbd5e1;
border-radius: 60px;
padding: 0.6rem 1.8rem 0.6rem 1.2rem;
font-size: 1rem;
font-weight: 500;
color: #0f172a;
cursor: pointer;
transition: all 0.2s;
appearance: none;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23334155' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'></polyline></svg>");
background-repeat: no-repeat;
background-position: right 1rem center;
background-size: 1rem;
}

select:focus {
outline: none;
border-color: #ff8c42;
box-shadow: 0 0 0 3px rgba(255, 140, 66, 0.2);
}

.form-container {
padding: 1.8rem 2rem 2rem 2rem;
transition: all 0.2s ease;
}

.form-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem 2rem;
}

.input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.input-group label {
font-weight: 600;
font-size: 0.9rem;
color: #1e2f3f;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}

.badge-hint {
background: #eef2ff;
font-size: 0.7rem;
font-weight: normal;
padding: 0.2rem 0.6rem;
border-radius: 30px;
color: #2c3e66;
letter-spacing: 0.3px;
}

input,
select {
background: #ffffff;
border: 1px solid #cfdee9;
border-radius: 1rem;
padding: 0.7rem 1rem;
font-size: 0.95rem;
transition: all 0.2s;
color: #0a1c2a;
}

input:focus,
select:focus {
outline: none;
border-color: #ff8c42;
box-shadow: 0 0 0 3px rgba(255, 140, 66, 0.15);
}

input::placeholder {
color: #9aaebf;
font-weight: 400;
font-size: 0.85rem;
}

.action-area {
padding: 0rem 2rem 1rem 2rem;
display: flex;
justify-content: flex-end;
border-top: 1px solid #eef2f8;
margin-top: 0.2rem;
}

.submit-btn {
background: #1e2f3f;
border: none;
padding: 0.75rem 1.8rem;
border-radius: 2rem;
font-weight: 600;
font-size: 0.9rem;
color: white;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 0.5rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}

.submit-btn:hover {
background: #ff8c42;
transform: translateY(-2px);
box-shadow: 0 8px 18px rgba(255, 140, 66, 0.25);
}

.result-area {
margin: 0 2rem 2rem 2rem;
background: #f8fafc;
border-radius: 1.2rem;
border: 1px solid #e2e8f0;
overflow: hidden;
}

.result-header {
background: #eef2ff;
padding: 0.7rem 1.2rem;
font-weight: 600;
color: #1e2f3f;
border-bottom: 1px solid #e2e8f0;
font-size: 0.85rem;
}

.result-content {
background: #ffffff;
padding: 1rem 1.2rem;
font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 0.9rem;
white-space: pre-wrap;
word-wrap: break-word;
color: #0f172a;
min-height: 100px;
max-height: 200px;
overflow-y: auto;
margin: 0;
}

.toast-msg {
position: fixed;
bottom: 25px;
left: 50%;
transform: translateX(-50%) scale(0.9);
background: #1e293b;
color: #f1f5f9;
padding: 0.7rem 1.6rem;
border-radius: 60px;
font-size: 0.85rem;
font-weight: 500;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
opacity: 0;
transition: opacity 0.2s, transform 0.2s;
pointer-events: none;
z-index: 1000;
backdrop-filter: blur(4px);
white-space: nowrap;
}

.toast-msg.show {
opacity: 1;
transform: translateX(-50%) scale(1);
}

.hidden-form {
display: none;
}

.small-note {
font-size: 0.7rem;
color: #6c86a3;
margin-top: 0.2rem;
}

@media (max-width: 680px) {
.card {
border-radius: 1.5rem;
}

.form-container {
padding: 1.5rem;
}

.mode-section,
.action-area,
.result-area {
padding-left: 1.5rem;
padding-right: 1.5rem;
margin-left: 0;
margin-right: 0;
}

.form-grid {
grid-template-columns: 1fr;
gap: 1.2rem;
}

.toast-msg {
white-space: normal;
text-align: center;
max-width: 85%;
font-size: 0.75rem;
padding: 0.6rem 1.2rem;
}
}

script.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
(function () {
// DOM 元素
const modeSelect = document.getElementById('modeSelect');
const formContainer = document.getElementById('scoreFormContainer');
const submitBtn = document.getElementById('submitDataBtn');
const toastEl = document.getElementById('toastMessage');
const resultOutput = document.getElementById('resultOutput');

// 后端接口地址(请根据实际情况修改)
const API_URL = '/api/score/submit';

// 展示短暂提示
function showMessage(msg, isError = false) {
toastEl.textContent = msg;
toastEl.classList.add('show');
if (isError) {
toastEl.style.backgroundColor = '#b91c1c';
toastEl.style.color = '#fff5f0';
} else {
toastEl.style.backgroundColor = '#1e293b';
toastEl.style.color = '#f1f5f9';
}
setTimeout(() => {
toastEl.classList.remove('show');
setTimeout(() => {
toastEl.style.backgroundColor = '#1e293b';
toastEl.style.color = '#f1f5f9';
}, 200);
}, 2500);
}

// 控制传分表单显示/隐藏
function toggleFormByMode(mode) {
if (mode === 'transmit') {
formContainer.classList.remove('hidden-form');
formContainer.style.display = '';
} else if (mode === 'buried') {
formContainer.classList.add('hidden-form');
formContainer.style.display = 'none';
}
}

function initMode() {
const currentMode = modeSelect.value;
if (currentMode === 'buried') {
formContainer.classList.add('hidden-form');
formContainer.style.display = 'none';
} else {
formContainer.classList.remove('hidden-form');
formContainer.style.display = '';
}
}

modeSelect.addEventListener('change', (e) => {
toggleFormByMode(e.target.value);
resultOutput.textContent = '等待提交...';
});

// 收集传分数据(包含新增的歌曲名字和版本)
function collectTransmitData() {
const mode = modeSelect.value;
if (mode !== 'transmit') {
return { success: false, reason: '当前为「下埋」模式,无传分数据。' };
}
if (formContainer.classList.contains('hidden-form') || getComputedStyle(formContainer).display === 'none') {
return { success: false, reason: '传分表单未激活,请重新选择「传分」模式' };
}

// 乐曲ID(可选,空则 "None")
let musicId = document.getElementById('musicId').value.trim();
if (musicId === "") musicId = "None";

// 歌曲名字(可选,空则 "None")
let songName = document.getElementById('songName').value.trim();
if (songName === "") songName = "None";

// 歌曲版本(必选,直接取值)
const songVersion = document.getElementById('songVersion').value;

const level = parseInt(document.getElementById('levelSelect').value, 10);
const playCount = parseInt(document.getElementById('playCount').value, 10);
if (isNaN(playCount) || playCount < 0) {
return { success: false, reason: '游玩次数必须是有效数字且≥0' };
}

const score = parseInt(document.getElementById('score').value, 10);
if (isNaN(score) || score < 0) {
return { success: false, reason: '成绩分数必须是有效整数' };
}

const combo = parseInt(document.getElementById('combo').value, 10);
if (isNaN(combo) || combo < 0) {
return { success: false, reason: '连击数必须是非负整数' };
}

const sync = parseInt(document.getElementById('syncSelect').value, 10);
const dxScoreRaw = document.getElementById('dxScore').value;
let dxScore = dxScoreRaw.trim() === "" ? null : parseFloat(dxScoreRaw);
if (dxScoreRaw.trim() !== "" && isNaN(dxScore)) {
return { success: false, reason: 'DX分格式错误,请输入数字' };
}

return {
success: true,
data: {
musicId: musicId,
songName: songName,
songVersion: songVersion,
level: level,
playCount: playCount,
score: score,
combo: combo,
sync: sync,
dxScore: dxScore
}
};
}

// 发送 POST 请求
async function sendToBackend(payload) {
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}

const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const jsonResult = await response.json();
return { success: true, data: jsonResult };
} else {
const textResult = await response.text();
return { success: true, data: textResult };
}
} catch (error) {
console.error('POST 请求失败:', error);
return { success: false, error: error.message };
}
}

// 渲染后端返回内容
function renderCommandFromBackend(responseData) {
if (typeof responseData === 'string') {
resultOutput.textContent = responseData;
return;
}
if (responseData && typeof responseData === 'object') {
if (responseData.command && typeof responseData.command === 'string') {
resultOutput.textContent = responseData.command;
return;
}
resultOutput.textContent = JSON.stringify(responseData, null, 2);
return;
}
resultOutput.textContent = String(responseData);
}

// 提交主逻辑
async function handleSubmit() {
const currentMode = modeSelect.value;

if (currentMode === 'buried') {
showMessage('🕳️ 下埋模式:未发送请求,可自行扩展', false);
resultOutput.textContent = '下埋模式,无后端请求。';
return;
}

const collectResult = collectTransmitData();
if (!collectResult.success) {
showMessage(`❌ ${collectResult.reason}`, true);
return;
}

const payload = collectResult.data;
resultOutput.textContent = '⏳ 正在向后端请求指令...';

const response = await sendToBackend(payload);
if (response.success) {
showMessage('✅ 后端处理成功,指令已显示', false);
renderCommandFromBackend(response.data);
} else {
showMessage(`❌ 请求失败: ${response.error}`, true);
resultOutput.textContent = `错误:${response.error}\n请检查后端接口是否可用。`;
}
}

submitBtn.addEventListener('click', handleSubmit);
initMode();
})();

​ 模板渲染则是使用 thymeleaf 框架,maven 的pom.xml中加入这个就可以了:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

​ 前置就解决了。

三、小知识点

​ 在写这个的时候,我发现,导入了thymeleaf之后,创建一个controller的java文件之后,如果采用@Controller这个注解的话,直接return "index;"的时候,就算是把index.html给返回了,如果是@RestController的话,之后返回index这一个字符串给浏览器。

​ 这个控制器的后端代码比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.g01den.demo01.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;


@Controller
public class divingFishController {
@GetMapping("/divingFish")
public String divingFishGet() {
return "index";
}
}

四、关于歌曲别名的api和相关解析的代码

​ 之后就是api了,看前端js代码可以知道,那里通过js请求了后端中中构造command的api,然后获得返回值之后渲染到输出框里。

​ 那么关于这里,找可以通过歌曲别名搜索歌曲信息的api就是难点,本来我确实找到了一个,就是这个https://oss.lista233.cn/alias.json,本来确实开发完成了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.g01den.demo01.controller;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@RestController
@RequestMapping("/api/score")
public class divingFishApiDemo {

public String aliaGetID(String alias, String version) throws IOException, JSONException {
String id = "";
int tmp = -1;
String aliasUrl = "https://oss.lista233.cn/alias.json";

HttpURLConnection conn = null;
BufferedReader reader = null;
StringBuilder response = new StringBuilder();

try {
URL url = new URL(aliasUrl);
conn = (HttpURLConnection) url.openConnection();

conn.setRequestMethod("GET");
conn.setConnectTimeout(15000);
conn.setReadTimeout(15000);

conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
conn.setRequestProperty("Accept", "application/json, text/plain, */*");
conn.setRequestProperty("Connection", "keep-alive");

try (InputStreamReader isr = new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr)) {
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
}

JSONObject jsonResponse = new JSONObject(response.toString()) ;

if (!jsonResponse.has("content")) {
throw new JSONException("JSON数据格式错误:缺少 content 字段");
}

JSONArray aliasData = jsonResponse.getJSONArray("content");

for (int i = 0; i < aliasData.length(); i++) {
JSONObject item = aliasData.getJSONObject(i);

if (!item.has("SongID") || !item.has("Alias")) {
continue;
}

String songId = item.getString("SongID");
JSONArray aliases = item.getJSONArray("Alias");

for (int j = 0; j < aliases.length(); j++) {
if (alias.equals(aliases.getString(j))) {
try {
tmp = Integer.parseInt(songId);
} catch (NumberFormatException e) {
continue;
}

if (version.equals("DX谱")) {
if (tmp >= 10000) {
id = songId;
}
} else if (version.equals("标准谱")) {
if (tmp < 10000) {
id = songId;
}
}
}
}
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
return id;
}

@PostMapping("/submit")
public String submitScore(@RequestBody Map<String, Object> payload) throws JSONException, IOException {
String musicId = payload.getOrDefault("musicId", "0").toString();
String songName = payload.getOrDefault("songName", "").toString();
String songVersion = payload.getOrDefault("songVersion", "").toString();
int level = ((Number) payload.getOrDefault("level", 0)).intValue();
int playCount = ((Number) payload.getOrDefault("playCount", 0)).intValue();
int score = ((Number) payload.getOrDefault("score", 0)).intValue();
int combo = ((Number) payload.getOrDefault("combo", 0)).intValue();
int sync = ((Number) payload.getOrDefault("sync", 0)).intValue();
Object dxScoreObj = payload.get("dxScore");
String dxScore = (dxScoreObj == null) ? "0" : dxScoreObj.toString();

if (musicId.equals("None") && !songName.equals("None")) {
musicId = aliaGetID(songName, songVersion);
if (musicId == null || musicId.isEmpty()) {
return "{\"command\": \"未找到对应歌曲,请检查别名或版本\"}";
}
} else if (musicId.equals("None") && songName.equals("None")) {
return "{\"command\": \"ID和别名不能为空\"}";
}
String command = String.format("传分 %s %d %d %d %d %d %s",
musicId, level, playCount, score, combo, sync, dxScore);
return "{\"command\": \"" + command + "\"}";
}
}

五、部署到Windows服务器上:

​ 通过maven打包了jar包之后,写三个bat文件:

start.bat

1
2
3
4
5
6
7
8
9
10
@echo off
set APP_NAME=demo01-0.0.1-SNAPSHOT
set LOG_FILE=app.log

echo Starting %APP_NAME%...

REM 后台运行应用,并将控制台输出重定向到日志文件
start /b javaw -jar %APP_NAME%.jar > %LOG_FILE% 2>&1

echo Application started. Check %LOG_FILE% for details.

stop.bat

1
2
3
4
5
6
7
@echo off
echo Stopping Java applications...

REM 查找并终止所有 javaw.exe 进程
taskkill /f /im javaw.exe

echo All Java processes have been stopped.

restart.bat

1
2
3
4
@echo off
call stop.bat
timeout /t 3 > nul
call start.bat

​ 之后安装jdk之后运行对应bat代码即可。

六、部署之后服务器上DNS解析不了

​ 本来感觉成功了,本地都跑起来了,并且正常运行了,结果上了香港那边的服务器之后发现DNS解析不了,这个不难解决,通过clash之类的搞一个大陆的代理即可。

七、第二天直接本地都无法访问api了

​ 第二天,本来想安装个clash去换个代理的,结果就出现了,本地测试的时候突然别名的那个api无法使用了,直接无法访问了,这就很尴尬,本来就是第一个SpringBoot小功能,结果炸了,用不了了,我一直哭。

八、🐧我感觉我被世界针对了🐧(开始发癫)

我操了老铁,我感觉我被世界针对了,你搓码子好不容易搓出来,本地能跑起来,都部署到服务器了,结果上了服务器之后,因为不知道什么原因,DNS解析不上,布什戈门?我本地都能调用api,结果服务器因为DNS解析不上调用不了api。这我忍了,给程序上个proxy就好了,但是,最让人气愤的来了,第二天本来想着修这个bug的,结果本地用python和java写的测试程序都访问不了这个api了,但是浏览器能访问,本以为加上user-agent能解决,结果来来回回换了好几次都解决不了。我真求你了,每次写的程序本来能跑,上了远程就跑不了了,然后过不了多久,本地也跑不了了。老天爷,我不想活了,你下叼啊,操死我。
!!!