请求参数分析

拿到滑块图片

接口:https://captcha1.fengkongcloud.cn/ca/v1/register?

参数

1
2
3
4
5
6
7
8
9
10
model: slide
lang: zh-cn
channel: DEFAULT
appId: default
data: {}
organization: d6tpAY1oV0Kv5jRSgxQr
rversion: 1.0.4
sdkver: 1.1.3
captchaUuid: 20231018105415RxPwHEF6nFydcrMkXp
callback: sm_1697597844207

响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sm_1697597844207({
"code": 1100,
"message": "success",
"requestId": "3c06d0c895558145092809fc53b3224f",
"riskLevel": "PASS",
"score": 0,
"detail": {
"bg": "/crb/set-000006/v2/c7e8581ea957ce5b18ebdf9dc623eae4_bg.jpg",
"bg_height": 300,
"bg_width": 600,
"domains": ["castatic.fengkongcloud.cn", "castatic.fengkongcloud.com", "castatic-a.fengkongcloud.com", "castatic2.fengkongcloud.com"],
"fg": "/crb/set-000006/v2/c7e8581ea957ce5b18ebdf9dc623eae4_fg.png",
"k": "w94WeW6DzZY=",
"l": 8,
"rid": "20231018105723994ca4bcdd165667e6"
}
})

经过测试organization 是不能忽略的,但是captchaUuid 可以不设置

搜索发现organization来自于请求https://www.ishumei.com/static/js/trial/captcha_7867b6f.js的返回值

保险起见,我们找一下captchaUuid的来源

搜索发现在这里是拿到的CaptchaUuid

image-20231018110544086

混淆代码看着太麻烦了,我们先解混淆一下(字符串替换—>数组替换—>去控制流),然后本地替换该文件

打断点调试

image-20231018114302634

我们可以把函数改一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function _0x377dfb() {
var _0xf7a45e = new Date(),
_0x2257b4 = function _0x51d189(_0x2bb897) {
return +_0x2bb897 < 10 ? '0' + _0x2bb897 : _0x2bb897['toString']();
};
return _0xf7a45e["getFullYear"]()["toString"]() + _0x2257b4(_0xf7a45e['getMonth']() + 1) + _0x2257b4(_0xf7a45e["getDate"]()) + _0x2257b4(_0xf7a45e["getHours"]()) + _0x2257b4(_0xf7a45e["getMinutes"]()) + _0x2257b4(_0xf7a45e["getSeconds"]());
}
function _0x1b55e2() {
var _0x2468bc = "4|2|0|1|3"["split"]('|'),
_0x22d1a6 = 0;
var _0x1c9d91 = '';
var _0x5b1e90 = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
var _0x3c1b53 = _0x5b1e90["length"];
for (var _0x41ee97 = 0; _0x41ee97 < 18; _0x41ee97++) {
_0x1c9d91 += _0x5b1e90["charAt"](Math["floor"](Math["random"]() * _0x3c1b53));
}
return _0x377dfb() + _0x1c9d91;
}

image-20231018114432949

然后直接执行_0x1b55e2()就能得到一个CaptchaUuid了

这样我们就能得到一个滑块了。

校验参数分析

接口https://captcha1.fengkongcloud.cn/ca/v2/fverify

参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
jn: w6ook9DZFNo=
act.os: web_pc
captchaUuid: 20231018114334JRrwA5NQjS7TXWEhkQ
qu: Q/IW6xhk8TI=
ra: 45xO9mJgzm0=
organization: d6tpAY1oV0Kv5jRSgxQr
rid: 202310181143409ba3155bef7381f25a
us: zY8brT9SISY=
ee: 4npBSAu34PH3ZHg0mS4Tikw5sA5JGQ92EwGQZWBmBLkQe99zAf5LX89047T2JGRsffdD2yOq4GwGbvEv/+lR6i9nx97W85Lj2FFg8n/ThUN0auGouDcVAg==
xy: xIAv2QAUoJA=
jv: tnws0FUkt6c=
sdkver: 1.1.3
ma: Ku1yrQmmWo8=
protocol: 179
rversion: 1.0.4
xc: MPQBHp3MK74=
callback: sm_1697600768421
rj: LpMN9yrHH3I=
ml: 1lymMSNqdDgh5oekvc4+3FBT7V3GKiK7
hd: w6ArMUdGI6s=
ostype: web

我们逐个分析

首先直接搜参数名字,我们定位到了js

image-20231018114909250

也是一个混淆后的js,那就还先解混淆替换一下,解混淆放到别的文章说

一下是解混淆之后的代码

image-20231019092538713

getEncryptContent

我们直接把this[“getEncryptContent”]扣出来看看是个什么

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
_0x4db346["prototype"]["getEncryptContent"] = function _0x51d516(_0x21ff8d, _0x18574e) {
var _0x14d7af = "3|8|4|0|5|1|6|7|2"['split']('|')
, _0x1df0e0 = 0;
while (true) {
switch (_0x14d7af[_0x1df0e0++]) {
case '0':
var _0x854c56 = typeof _0x21ff8d === "string" ? true : false;
continue;
case '1':
var _0x3ba722 = '';
continue;
case '2':
return _0x3ba722;
case '3':
var _0x58c8b7 = this['_data']["__key"];
continue;
case '4':
_0x4d5773['default']["isJsFormat"]() && (_0x3296bb = _0x2f8e69);
continue;
case '5':
var _0x48bcbb = _0x854c56 ? _0x21ff8d : _0x4d5773['default']["smStringify"](_0x21ff8d);
continue;
case '6':
_0x3ba722 = _0x5c06a2['default']["DES"](_0x3296bb, _0x48bcbb, 1, 0);
continue;
case '7':
_0x3ba722 = _0x5c06a2['default']["base64Encode"](_0x3ba722);
continue;
case '8':
var _0x3296bb = _0x18574e || _0x58c8b7;
continue;
}
break;
}
}

其实是一个DES加密,我们改写一下即可

先定位到_0x4d5773_0x5c06a2是个什么东西,因为看到调用了它们的一些函数

_0x4d5773['default']["isJsFormat"]()

1
2
3
'isJsFormat': function _0x3f98b1() {
return _0x7052eb['toString']()["split"]("\n")["length"] > 2;
},

image-20231019095711053

这个返回值调试得出结果固定为true

_0x4d5773['default']["smStringify"]

image-20231019095232450

其实就是JSON.stringify

this['_data']["__key"]

然后我们看__key是怎么来的

image-20231019100251375

其实就是_0x5c06a2['default']['DES'](_0x11a125, _0x324a10, 0, 0)["substr"](0, _0x27ee4e)

DES加密之后截取了8个字符长度

可以看到传入的参数是

image-20231019100427795

我们直接把他们自写的一个DES加密扣出来

image-20231019100809086

image-20231019101030105

结果完全一致

那么__key我们也搞定了,顺带把DES和base64Decode也扣了出来

至此getEncryptContent搞定

轨迹数组

也就是这堆东西

image-20231019101757521

其中startTime,endTime很好理解,就是滑块开始和结束的时间戳

mouseEndX就是滑动的距离

trueWidth,trueHeight,blockWidth均是固定值

_0x4b9967也是固定值web_pc

我们主要来看mouseData

image-20231019102232826

定位到这里的代码块,我们让gpt解读一下它是怎么搜集滑块轨迹的吧~

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
这段代码是一个JavaScript函数,用于收集滑块轨迹数据。下面是逐行解读该函数的逻辑:

var _0x97c351 = this['_data']:将this['_data']赋值给变量_0x97c351,this表示函数所属的对象。

var _0x578ed3 = _0x97c351["startTime"]:将_0x97c351["startTime"]赋值给变量_0x578ed3。这里假设_0x97c351是一个对象,包含了一个名为startTime的属性。

_0x9edfdf = _0x578ed3 === undefined ? +new Date() : _0x578ed3:如果_0x578ed3是undefined,则将+new Date()的结果赋值给_0x9edfdf,否则将_0x578ed3的值赋值给_0x9edfdf。这里使用了条件运算符(三元运算符)。

var _0x1dbee8 = _0x97c351["mouseMoveX"]:将_0x97c351["mouseMoveX"]赋值给变量_0x1dbee8。假设_0x97c351是一个对象,包含了一个名为mouseMoveX的属性。

var _0x17400c = _0x1dbee8 === undefined ? 0 : _0x1dbee8:如果_0x1dbee8是undefined,则将0赋值给_0x17400c,否则将_0x1dbee8的值赋值给_0x17400c。

var _0x41556e = _0x97c351["mouseMoveY"]:将_0x97c351["mouseMoveY"]赋值给变量_0x41556e。假设_0x97c351是一个对象,包含了一个名为mouseMoveY的属性。

var _0x23762d = _0x41556e === undefined ? 0 : _0x41556e:如果_0x41556e是undefined,则将0赋值给_0x23762d,否则将_0x41556e的值赋值给_0x23762d。

var _0x46405e = _0x97c351["mouseData"]:将_0x97c351["mouseData"]赋值给变量_0x46405e。假设_0x97c351是一个对象,包含了一个名为mouseData的属性。

var _0x5df8e6 = _0x46405e === undefined ? [] : _0x46405e:如果_0x46405e是undefined,则将一个空数组赋值给_0x5df8e6,否则将_0x46405e的值赋值给_0x5df8e6。

var _0x1ffa1d = _0x97c351["intervalTimer"]:将_0x97c351["intervalTimer"]赋值给变量_0x1ffa1d。假设_0x97c351是一个对象,包含了一个名为intervalTimer的属性。

var _0x2b531f = +new Date():将+new Date()的结果赋值给变量_0x2b531f,表示当前时间的时间戳。

_0x5df8e6["length"] <= 100:检查数组_0x5df8e6的长度是否小于等于100。

(_0x5df8e6["push"]([_0x17400c, _0x23762d, _0x2b531f - _0x9edfdf]), this["_data"]["mouseData"] = _0x5df8e6):如果数组_0x5df8e6的长度小于等于100,则将一个包含_0x17400c、_0x23762d和_0x2b531f - _0x9edfdf的数组添加到_0x5df8e6中,并将_0x5df8e6赋值给this["_data"]["mouseData"]。

clearInterval(_0x1ffa1d):如果数组_0x5df8e6的长度大于100,则清除定时器_0x1ffa1d。

this["_data"]["intervalTimer"] = 0:将this["_data"]["intervalTimer"]的值设置为0。

总体来说,这段代码的逻辑是检查滑块轨迹数据的长度,如果小于等于100,则将新的数据添加到数组中;如果大于100,则停止收集数据。




根据代码的命名和上下文推测,各个字段的意思可能如下:

_data: 一个包含数据的对象,可能是函数所属对象的属性之一。
startTime: 表示开始时间的字段,用于记录滑块轨迹数据的起始时间。
mouseMoveX: 表示鼠标在水平方向上的移动距离的字段。
mouseMoveY: 表示鼠标在垂直方向上的移动距离的字段。
mouseData: 一个数组,用于存储滑块轨迹数据的集合。
intervalTimer: 表示定时器的字段,可能用于控制滑块轨迹数据的收集间隔时间。
请注意,这些字段的具体含义可能需要根据代码的上下文和使用方式进行确认。如果这段代码是从一个更大的代码块中提取出来的,可能还需要查看其他部分的代码才能完全理解这些字段的作用和含义。



根据代码中的赋值语句,可以推测以下内容:

_0x17400c: 表示鼠标在水平方向上的移动距离。根据代码的逻辑,如果_0x1dbee8(即_0x97c351["mouseMoveX"])是undefined,则将0赋值给_0x17400c,否则将_0x1dbee8的值赋值给_0x17400c。
_0x23762d: 表示鼠标在垂直方向上的移动距离。根据代码的逻辑,如果_0x41556e(即_0x97c351["mouseMoveY"])是undefined,则将0赋值给_0x23762d,否则将_0x41556e的值赋值给_0x23762d。
_0x2b531f - _0x9edfdf: 表示时间差,即当前时间戳减去起始时间戳。根据代码的逻辑,_0x2b531f表示当前时间的时间戳,_0x9edfdf表示起始时间的时间戳。这个表达式计算的结果是鼠标移动的时间间隔。
请注意,由于这些变量名是经过压缩或混淆的,无法准确确定其具体含义。因此,以上推测是根据代码的上下文和常见的命名习惯进行的推测,具体含义可能需要根据代码的其他部分或上下文进行确认。

总结来说就是搜集鼠标在运行过程中的x,y坐标和一个时间间隔,放到数组里,这个数组长度大于100的话就停止搜集轨迹

我们在这埋下一个日志点

image-20231019103245824

image-20231019103334987

这下是不是很清楚了,时间间隔就是当前的时间减去开始时间,开始的时间是固定的,所以间隔会越来越大

我们自己写一个模拟轨迹流程的算法,假设滑块里是120

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
function randomNum(min, max) {
return parseInt(Math.random() * (max - min + 1) + min);
}

function aesEncrypt(as, e) {
var t = as + "appsapi0"
, n = CryptoJS.enc.Utf8.parse(t)
, i = CryptoJS.enc.Utf8.parse(e)
, r = CryptoJS.AES.encrypt(i, n, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return r.toString();
}


function mouseAction(distance) {
start_time = (new Date()).getTime()
// 点击的位置
var click = {
"x": 0,
"y": randomNum(1, 3)
}
jiange= 100
// part3: 开始滑动,次数根据需要滑动的距离(distance)而定
var move_data = [[click.x, click.y, 0]]
var last_x = click.x + Math.round(distance)
var now_x = click.x + randomNum(0, 2)
// 滑动第一个坐标,应该与点击的坐标点相近或者相等
move_data.push([now_x, click.y + randomNum(0, 2), jiange])
while (now_x < last_x) {
jiange += randomNum(99, 103)

now_x += randomNum(5, 20)
move_data.push([now_x, click.y + randomNum(0, 3), jiange])
}
// 滑动最后一个坐标,应该与 last_x 一致
move_data.push([now_x, click.y + randomNum(0, 2), jiange])
end_time = start_time + jiange

return {
'mouseData': move_data,
'startTime': start_time,
'endTime': end_time,
'mouseEndX': distance,
'trueWidth': 300,
'trueHeight': 150,
'selectData': [],
'blockWidth': 40,
}
}
console.log(mouseAction(120));

拿到轨迹之后就很简单了,参照js代码,写出我们模拟的代码

image-20231019111012143

结果:

image-20231019111037381

验证

我们按照思路写python代码

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
import requests
import re
import execjs
import json
import time
from loguru import logger
import numpy as np
import cv2
import random
import math
debug=True # 调试的时候打开
headers = {
'authority': 'www.ishumei.com',
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'cache-control': 'no-cache',
'pragma': 'no-cache',
'referer': 'https://www.ishumei.com/trial/captcha.html',
'sec-ch-ua': '"Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'script',
'sec-fetch-mode': 'no-cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
}
Jsfile = open('main.js', 'r', encoding='utf-8')
JsData = Jsfile.read()
def getUUID():
return execjs.compile(JsData).call('getUUID')

def getParams(k,l,distance,move_lis,use_time):
return execjs.compile(JsData).call('getParams', k, l, distance,move_lis,use_time)



def get_trace_and_times(distance):
x = [0, 0]
y = [0, 0, 0]
z = [0]
count = np.linspace(-math.pi / 2, math.pi / 2, random.randrange(10, 20))
func = list(map(math.sin, count))
nx = [i + 1 for i in func]
add = random.randrange(10, 15)
sadd = distance + add
x.extend(list(map(lambda x: x * (sadd / 2), nx)))
x.extend(np.linspace(sadd, distance, 3 if add > 12 else 2))
x = [math.floor(i) for i in x]
for i in range(len(x) - 2):
if y[-1] < 30:
y.append(y[-1] + random.choice([0, 0, 1, 1, 2, 2, 1, 2, 0, 0, 3, 3]))
else:
y.append(y[-1] + random.choice([0, 0, -1, -1, -2, -2, -1, -2, 0, 0, -3, -3]))
for i in range(len(x) - 1):
z.append((z[-1] // 100 * 100) + 100 + random.choice([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2]))
trace = list(map(list, zip(x, y, z)))
times = trace[-1][-1] + random.randint(1, 5)
# logger.debug('轨迹数组:{}'.format(trace))
return trace, times
class ShuMei:
def __init__(self) -> None:
self.UUID = getUUID()
logger.success(f'获取到UUID: {self.UUID}')

def get_organization(self):
url = 'https://www.ishumei.com/static/js/trial/captcha_7867b6f.js'
resp = requests.get(url, headers=headers)
organization = re.findall('organization:"(.*?)",', resp.text)[0]
self.organization = organization
logger.success(f'获取到organization: {organization}')

def get_distance(self, slice_content, bg_content):
if debug:
with open('slice.png', 'wb') as f:
f.write(slice_content)
f.close()
with open('bg.png', 'wb') as f:
f.write(bg_content)
f.close()

"""
:param bg_url: 背景图地址
:param slice_url: 滑块图地址
:return: distance
:rtype: Integer
"""
slice_image = np.asarray(bytearray(slice_content), dtype=np.uint8)
slice_image = cv2.imdecode(slice_image, 1)
slice_image = cv2.Canny(slice_image, 22, 22) # 不准的话就调这几个参数,慢慢调

bg_image = np.asarray(bytearray(bg_content), dtype=np.uint8)
bg_image = cv2.imdecode(bg_image, 1)
bg_image = cv2.pyrMeanShiftFiltering(bg_image, 20, 11)# 不准的话就调这几个参数,慢慢调
bg_image = cv2.Canny(bg_image, 20, 20)# 不准的话就调这几个参数,慢慢调

bg_image = cv2.cvtColor(bg_image, cv2.COLOR_GRAY2RGB)
slice_image = cv2.cvtColor(slice_image, cv2.COLOR_GRAY2RGB)

result = cv2.matchTemplate(bg_image, slice_image, cv2.TM_CCOEFF_NORMED)

min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
if debug:
th, tw = slice_image.shape[:2]
tl = max_loc # 左上角点的坐标
br = (tl[0] + tw, tl[1] + th) # 右下角点的坐标
cv2.rectangle(bg_image, tl, br, (0, 0, 255), 2) # 绘制矩形
cv2.imwrite('out.jpg', bg_image) # 保存在本地

return [max_loc[0]]

def getContentOfImg(self, url):
resp = requests.get(url, headers=headers)
return resp.content
def Register(self):
params = {
'rversion': '1.0.4',
'model': 'slide',
'appId': 'default',
'data': '{}',
'captchaUuid': self.UUID,
'organization': self.organization,
'sdkver': '1.1.3',
'lang': 'zh-cn',
'channel': 'DEFAULT',
'callback': f'sm_{int(time.time())}',
}

response = requests.get('https://captcha1.fengkongcloud.cn/ca/v1/register', params=params, headers=headers,impersonate="chrome101")
Jstext = response.text.split('(')[1].split(')')[0]
Jstext = json.loads(Jstext)
# 拿到图片地址
self.img_bg = 'https://castatic.fengkongcloud.cn'+Jstext['detail']['bg']
# self.img_bg = 'https://castatic.fengkongcloud.cn/crb/set-000006/v2/ecb528a9e5a17657cbd2f15910cedf7b_bg.jpg'
self.img_fg = 'https://castatic.fengkongcloud.cn'+Jstext['detail']['fg']
# self.img_fg = 'https://castatic.fengkongcloud.cn/crb/set-000006/v2/ecb528a9e5a17657cbd2f15910cedf7b_fg.png'
# 拿到k,l,rid
self.k = Jstext['detail']['k']
self.l = Jstext['detail']['l']
self.rid = Jstext['detail']['rid']
logger.success(f'获取到背景图片地址: {self.img_bg}')
logger.success(f'获取到滑块图片地址: {self.img_fg}')
logger.success(f'获取到k: {self.k}')
logger.success(f'获取到l: {self.l}')
logger.success(f'获取到rid: {self.rid}')

def getDistance(self):
slice_content = self.getContentOfImg(self.img_fg)
bg_content = self.getContentOfImg(self.img_bg)
distance = int(self.get_distance(slice_content, bg_content)[0]/2)+3
logger.success(f'获取到distance: {distance}')
return distance

def getVerifyParams(self):
distance = self.getDistance()
move_lis,use_time = get_trace_and_times(distance)
params = getParams(self.k, self.l, distance,move_lis,use_time)
params = {**params, ** {
'sdkver': '1.1.3',
'callback': f'sm_{int(time.time()*1000)}',
'act.os': 'web_pc',
'organization': self.organization,
'rversion': '1.0.4',
'ostype': 'web',
'rid': self.rid,
'captchaUuid': self.UUID,
'protocol': '179',
}}
logger.debug(f'获取到params: {params}')
return params
def verify(self,params):
headers = {
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Origin': 'https://www.ishumei.com',
'Pragma': 'no-cache',
'Referer': 'https://www.ishumei.com/trial/captcha.html',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
}

response = requests.get('https://captcha1.fengkongcloud.cn/ca/v2/fverify', params=params, headers=headers,impersonate="chrome101")
if 'REJECT' in response.text:
logger.error(f'获取到response: {response.text}')
else:
logger.success(f'获取到response: {response.text}')

for i in range(100):
shumei = ShuMei()
shumei.get_organization()
shumei.Register()
params = shumei.getVerifyParams()
shumei.verify(params)
break

结果

image-20231019214434696

踩的坑

  1. AST混淆顺序ob 对象还原需要两次
  2. 控制流还原需要还原两次
  3. js代码里尽量不要直接写固定值,哪怕调试的时候是固定的,需要再三确认是固定的,再写死