1 importjson2 importos3 importshelve4 importstruct5 importsubprocess6

7 from conf importsettings8 from lib importcommon9

10

11 classHandlerRequest:12 """处理用户请求."""

13 max_packet_size = 8192

14 encoding = 'utf-8'

15

16 struct_fmt = 'i'

17 fixed_packet_size = 4

18

19 logger =common.load_my_logging_cfg()20

21 def __init__(self, request, address):22 self.request =request23 self.address =address24

25 self.residual_space_size =None26

27 self.breakpoint_resume =None28

29 self.username =None30 self.user_obj =None31 self.user_current_dir =None32

33 defclient_close(self):34 """关闭客户端连接."""

35 self.request.close()36

37 defhandle_request(self):38 """处理客户端请求."""

39 count =040 while count < 3: #连接循环

41 try:42 ifself.auth():43 #收消息

44 user_dic =self.receive_header()45 action_type = user_dic.get('action_type')46 ifaction_type:47 if hasattr(self, '_%s' %action_type):48 func = getattr(self, '_%s' %action_type)49 func(user_dic)50 else:51 self.send_header(status_code=201)52 #发消息

53 else:54 self.send_header(status_code=200)55 else:56 count += 1

57 self.send_header(status_code=199)58 exceptConnectionResetError:59 break

60 #关闭客户端连接

61 self.logger.info('----连接断开---- ip:%s port:%s' %self.address)62 self.client_close()63

64 defunfinished_file_check(self):65 self.logger.info('#执行unfinished_file_check命令# ip:%s port:%s' %self.address)66

67 if notlist(self.breakpoint_resume.keys()):68 self.send_header(status_code=851)69 return

70

71 #self.breakpoint_resume[file_path] =

72 #{'file_size': _file_size, 'unfinished_file_path': unfinished_file_path, 'file_name': _file_name}

73 msg_list =[]74

75 for index, abs_path in enumerate(self.breakpoint_resume.keys(), 1):76

77 user_path = '/'.join(abs_path.split(self.username)[-1].split(os.sep))78 print('abs_path:', user_path)79 file_name = self.breakpoint_resume[abs_path]['file_name']80 src_file_size = self.breakpoint_resume[abs_path]['file_size']81 unfinished_file_size = os.path.getsize(self.breakpoint_resume[abs_path]['unfinished_file_path'])82 percent = unfinished_file_size / src_file_size * 100

83

84 msg = """

85 数量: %s 文件路径: %s 文件名: %s86 文件原大小: %s字节 未完成的文件大小: %s字节 上传的百分比: %.2f%%87 """ %(index, user_path, file_name, src_file_size, unfinished_file_size, percent)88

89 msg_list.append(msg)90 #msg_dic['/03_函数调用的三种形式.mp4'] = 5772100

91 #msg_dic[user_path] = unfinished_file_size

92 #self.send_header(status_code=850, msg_list=msg_list, msg_dic=msg_dic)

93 self.send_header(status_code=850, msg_list=msg_list)94

95 defauth(self):96 """用户登陆认证."""

97 ifself.user_current_dir:98 returnTrue99

100 #涉及到交叉导入

101 from core importmain102 #收消息

103 auth_dic =self.receive_header()104

105 user_name = auth_dic.get('username')106 user_password = auth_dic.get('password')107 md5_password = common.md5('password', password=user_password)108

109 #print(user_name, user_password, md5_password)

110

111 accounts =main.FTPServer.load_accounts()112 if user_name inaccounts.sections():113 if md5_password == accounts[user_name]['password']:114 self.send_header(status_code=100)115

116 self.username =user_name117 self.user_obj =accounts[user_name]118 self.user_obj['home'] =os.path.join(settings.USER_HOME_DIR, user_name)119 self.user_current_dir = self.user_obj['home']120

121 #print('self.user_obj:', self.user_obj)

122 #print("self.user_obj['home']:", self.user_obj['home'])

123

124 self.residual_space_size =common.conversion_quota(125 self.user_obj['quota']) - common.get_size(self.user_obj['home'])126

127 breakpoint_resume_dir_path = os.path.join(self.user_obj['home'], '.upload')128 if notos.path.isdir(breakpoint_resume_dir_path):129 os.mkdir(breakpoint_resume_dir_path)130 self.breakpoint_resume = shelve.open(os.path.join(breakpoint_resume_dir_path, '.upload.shv'))131 self.unfinished_file_check()132

133 self.logger.info('#认证成功# ip:%s port:%s' %self.address)134 returnTrue135 self.logger.info('#认证失败# ip:%s port:%s' %self.address)136 returnFalse137

138 def_ls(self, cmd_dic):139 """

140 运行dir命令将结果发送到客户端.141 :param cmd_dic: {'path': [], 'action_type': 'ls'}142 或 {'path': ['目录1', '目录2', '目录xxx'], 'action_type': 'ls'}143 或 {'path': ['?'], 'action_type': 'ls'}144 :return: None145 """

146 #print('_ls:', cmd_dic)

147 self.logger.info('#执行ls命令# ip:%s port:%s' %self.address)148

149 #核验路径

150 dir_path =self.verify_path(cmd_dic)151 if notdir_path:152 dir_path =self.user_current_dir153

154 if cmd_dic.get('path') == ['?']: #为用户提供ls /?命令

155 dir_path = '/?'

156

157 sub_obj =subprocess.Popen(158 'dir %s' %dir_path,159 shell=True,160 stderr=subprocess.PIPE,161 stdout=subprocess.PIPE162 )163 stderr_bytes, stdout_bytes =sub_obj.stderr.read(), sub_obj.stdout.read()164 cmd_size = len(stderr_bytes) +len(stdout_bytes)165

166 #发报头

167 self.send_header(status_code=301, cmd_size=cmd_size)168 #发消息

169 self.request.sendall(stderr_bytes)170 self.request.sendall(stdout_bytes)171

172 def_cd(self, cmd_dic):173 """

174 根据用户的目标目录, 改变用户的当前目录的值.175 :param cmd_dic: {'action_type': 'cd', 'path': ['..']}176 或 {'action_type': 'cd', 'path': ['目录1', '目录2', '目录xxx'], }177 :return: None178 Z:\pycharm\开发FTP程序之路\第2次FTP_第四模块作业\FUCK_FTP\server\home\egon\目录1179 """

180 #print('_cd:', cmd_dic)

181 self.logger.info('#执行cd命令# ip:%s port:%s' %self.address)182

183 #核验路径

184 dir_path =self.verify_path(cmd_dic)185 ifdir_path:186 if os.path.isdir(dir_path): #判断用户切换的路径是否存在

187 self.user_current_dir =dir_path188 if dir_path == self.user_obj['home']:189 current_dir = '~'

190 else:191 join_dir = ''.join(dir_path.split('%s' % self.username)[1:])192 current_dir = '/'.join(join_dir.split('\'))193 self.send_header(status_code=400, current_dir=current_dir)194 else:195 self.send_header(status_code=499)196 else:197 self.send_header(status_code=498)198

199 def_mkdir(self, cmd_dic):200 """

201 更具用户的目标目录, 且目录不存在, 创建目录标目录, 生成多层递归目录.202 :param cmd_dic: {'action_type': 'mkdir', 'path': ['目录1']}203 或 {'action_type': 'mkdir', 'path': ['目录2', '目录3', '目录xxx']}204 :return: None205 """

206 #print('_mkdir:', cmd_dic)

207 self.logger.info('#执行mkdir命令# ip:%s port:%s' %self.address)208

209 dir_path =self.verify_path(cmd_dic)210 ifdir_path:211 if not os.path.isdir(dir_path): #判断用户要创建的目录时否存在

212 os.makedirs(dir_path)213 self.send_header(status_code=500)214 else:215 self.send_header(status_code=599)216 else:217 self.send_header(status_code=598)218

219 def_rmdir(self, cmd_dic):220 """

221 更具用户的目标目录, 删除不为空的目录.222 :param cmd_dic: {'path': ['目录1', '目录xxx', '空目录'], 'action_type': 'rmdir'}223 :return: None224 """

225 #print('_rmdir:', cmd_dic)

226 self.logger.info('#执行rmdir命令# ip:%s port:%s' %self.address)227

228 dir_path =self.verify_path(cmd_dic)229 ifdir_path:230 ifos.path.isdir(dir_path):231 ifos.listdir(dir_path):232 self.send_header(status_code=699)233 else:234 os.rmdir(dir_path)235 self.send_header(status_code=600)236 else:237 self.send_header(status_code=698)238 else:239 self.send_header(status_code=697)240

241 def_remove(self, cmd_dic):242 """

243 更具用户的目标文件, 删除该文件244 :param cmd_dic: {'path': ['目录1', '目录xxx', '文件'], 'action_type': 'remove'}245 :return:246 """

247 #print('_remove:', cmd_dic)

248 self.logger.info('#执行remove命令# ip:%s port:%s' %self.address)249 file_path =self.verify_path(cmd_dic)250

251 iffile_path:252 ifos.path.isfile(file_path):253 #判断用户删除的文件是否是要续传的文件, 如果是则先把把续传的记录删除

254 if file_path inself.breakpoint_resume.keys:255 delself.breakpoint_resume[file_path]256 os.remove(file_path)257 self.send_header(status_code=700)258 else:259 self.send_header(status_code=799)260 else:261 self.send_header(status_code=798)262

263 def_resume_upload(self, cmd_dic):264 """

265 860: '您正在继续上传文件, 在您继传之前, 您的目前空间:%s!',266 869: '您选择文件路径中没有要续传的文件, 请核对!',267 :param cmd_dic:268 :return:269 """

270 #print('def _resume_upload ===> cmd_args', cmd_dic)

271 self.logger.info('#执行resume_upload命令# ip:%s port:%s' %self.address)272 self._upload(cmd_dic, resume_upload=True)273

274 def _upload(self, cmd_dic, resume_upload=False):275 """客户端276 800: '你可以上传文件, 在您上传之前, 您的目前空间:%s!',277 801: '上传文件成功, 您上传完后的剩余空间:%s!',278 850: '您的还有为上传完的文件, 是否继续上传!',279 851: '检测您不存在未上传完成的文件!',280 852: '您不能进行续传, 因为该文件是完整文件!',281 860: '您正在继续上传文件, 在您继传之前, 您的目前空间:%s!',282 869: '您选择文件路径中没有要续传的文件, 请核对!',283 894: '您不需要再对本路径下上传文件, 该文件在您的当前路径下已经存在!',284 895: '上传文件失败, md5效验不一致, 部分文件内容在网络中丢失, 请重新上传!',285 896: '上传文件失败, 您的空间不足, 您的上传虚假文件大小, 您的剩余空间:%s!',286 897: '上传文件失败, 您的空间不足, 您的剩余空间:%s!',287 898: '上传文件失败, 上传命令不规范!',288 899: '上传文件必须要有文件的md5值以及文件名!',289 """

290 #print('_upload:', cmd_dic)

291 if notresume_upload:292 self.logger.info('#执行upload命令# ip:%s port:%s' %self.address)293

294 #效验: 897, 898, 899

295 _path, _file_md5, _file_name, _file_size = cmd_dic.get('path'), cmd_dic.get('file_md5'), cmd_dic.get(296 'file_name'), cmd_dic.get('file_size')297 file_path = self.verify_upload_action(cmd_dic, _path=_path, _file_md5=_file_md5, _file_name=_file_name,298

299 _file_size=_file_size)300

301 if resume_upload: #断点续传时执行

302 if not file_path or file_path not inself.breakpoint_resume.keys():303 #869: '您选择文件路径中没有要续传的文件, 请核对!',

304 self.send_header(status_code=869)305 return

306

307 #找到之前未穿完的文件名

308 unfinished_file_path = self.breakpoint_resume[file_path]['unfinished_file_path']309 already_upload_size =os.path.getsize(unfinished_file_path)310

311 #效验成功通知续传信号

312 #860: '您正在继续上传文件, 在您继传之前, 您的目前空间:%s!',

313 self.send_header(status_code=860, residual_space_size=self.residual_space_size,314 already_upload_size=already_upload_size)315

316 total_size = _file_size -already_upload_size317 mode = 'a'

318 else: #正常上传执行

319 if notfile_path:320 return

321

322 #判断用户上传的文件是否重复

323 ifos.path.isfile(file_path):324 #894: '您不需要再对本路径下上传文件, 该文件在您的当前路径下已经存在!',

325 self.send_header(status_code=894)326 return

327 else:328 unfinished_file_path = '%s.%s' % (file_path, 'upload')329

330 #效验成功通知上传信号: 800

331 #800: '你可以上传文件, 在您上传之前, 您的目前空间:%s!',

332 self.send_header(status_code=800, residual_space_size=self.residual_space_size)333

334 total_size =_file_size335 mode = 'w'

336

337 #记录断点的功能: 在服务端用户的路径, 记录文件大小, 加上后缀的路径, 文件名

338 #或再次为未传完的文件记录断点

339 self.breakpoint_resume[file_path] = {'file_size': _file_size, 'unfinished_file_path': unfinished_file_path,340 'file_name': _file_name}341

342 #开始接收文件

343 receive_size =0344 with open(unfinished_file_path, '%sb' %mode) as f:345 while receive_size <346 data_bytes="self.request.recv(self.max_packet_size)347" receive_size f.write>

350 os.rename(unfinished_file_path, file_path)351 #删除记录断点的功能

352 delself.breakpoint_resume[file_path]353

354 #801, 895, 896

355 #效验用户端发送的md5于本次上传完毕的md5值

356 upload_file_md5 = common.md5(encryption_type='file', path=file_path)357 if upload_file_md5 !=_file_md5:358 #print('def _upload ===> upload_file_md5:%s, _file_md5:%s' % (upload_file_md5, _file_md5))

359 #895: '上传文件失败, md5效验不一致, 部分文件内容在网络中丢失, 请重新上传!',

360 self.send_header(status_code=895)361 os.remove(file_path)362 return

363

364 #安全性问题: 再次判断用户是否以假的文件大小来跳出服务端限制的配额

365 if receive_size >self.residual_space_size:366 #896: '上传文件失败, 您的空间不足, 您的上传虚假文件大小, 您的剩余空间:%s!',

367 self.send_header(status_code=896, residual_space_size=self.residual_space_size)368 os.remove(file_path)369 return

370 else:371 self.residual_space_size = self.residual_space_size -receive_size372 #print('def _upload ===> receive_size:', receive_size)

373 #print('def _upload ===> os.path.getsize(file_path)', os.path.getsize('%s' % file_path))

374 #801: '上传文件成功, 您上传完后的剩余空间:%s!',

375 self.send_header(status_code=801, residual_space_size=self.residual_space_size)376

377 def_resume_download(self, cmd_dic):378 self._download(cmd_dic, resume_download=True)379

380 def _download(self, cmd_dic, resume_download=False):381 self.logger.info('#执行download命令# ip:%s port:%s' %self.address)382

383 file_path =self.verify_path(cmd_dic)384 if notfile_path:385 #999: '下载文件失败, 您要下载的文件路径不规范!',

386 self.send_header(status_code=999)387 return

388

389 if notos.path.isfile(file_path):390 #998: '下载文件失败, 您要下载的文件路径不存在!',

391 self.send_header(status_code=998)392 return

393

394 #通知可以开始下载

395 #900: '准备开始下载文件!'.

396 file_name = file_path.split(os.sep)[-1]397 file_size =os.path.getsize(file_path)398 file_md5 = common.md5('file', file_path)399 unfinished_file_size = cmd_dic.get('unfinished_file_size')400 ifresume_download:401 #950: '准备开始续传文件!',

402 self.send_header(status_code=950, file_name=file_name, file_size=file_size, file_md5=file_md5)403 else:404 #900: '准备开始下载文件!'.

405 self.send_header(status_code=900, file_name=file_name, file_size=file_size, file_md5=file_md5)406

407 #打开文件发送给客户端

408 with open(file_path, 'rb') as f:409 ifresume_download:410 f.seek(unfinished_file_size)411 for line inf:412 self.request.sendall(line)413

414 def verify_upload_action(self, cmd_dic, *, _path, _file_name, _file_md5, _file_size):415 """

416 核验上传功能.417 897: '上传文件失败, 您的空间不足, 您的剩余空间:%s!',418 898: '上传文件失败, 上传命令不规范!',419 899: '上传文件必须要有文件的md5值以及文件名!',420 """

421 #_path=['03_函数调用的三种形式.mp4']

422 if _path isNone:423 if _file_name and _file_md5 and_file_size:424 if _file_size >self.residual_space_size:425 #print('def _upload ===> self.residual_space_size:', self.residual_space_size)

426

427 #897: '上传文件失败, 您的空间不足, 您的剩余空间:%s!',

428 self.send_header(status_code=897, residual_space_size=self.residual_space_size)429 returnFalse430 else:431 #Z:pycharm开发FTP程序之路第2次FTP_第四模块作业FUCK_FTPserverhomeegon 3_函数调用的三种形式.mp4

432 file_path =os.path.join(self.user_current_dir, _file_name)433 else:434 #899: '上传文件必须要有文件的md5值以及文件名!',

435 self.send_header(status_code=899)436 returnFalse437 else:438 path =self.verify_path(cmd_dic)439

440 if notpath:441 #898: '上传文件失败, 上传命令不规范!',

442 self.send_header(status_code=898)443 returnFalse444 else:445 #Z:pycharm开发FTP程序之路第2次FTP_第四模块作业FUCK_FTPserverhomeegon 3_函数调用的三种形式.mp4

446 file_path =os.path.join(path, _file_name)447 returnfile_path448

449 defverify_path(self, cmd_dic):450 """

451 核验客户端传过来的路径.452 :param cmd_dic: {'action_type': 'ls', 'path': []}453 或 {'action_type': 'ls', 'path': ['目录1', '目录xxx']}454 或 {action_type': 'cd', 'path': ['目录2', '目录xxx']}455 :return: None456 Z:\pycharm\开发FTP程序之路\第2次FTP_第四模块作业\FUCK_FTP\server\home\egon\目录1457 Z:\pycharm\开发FTP程序之路\第2次FTP_第四模块作业\FUCK_FTP\server\home\egon\目录1458 """

459 #print(cmd_dic)

460 path = cmd_dic.get('path')461 ifpath:462 ifisinstance(path, list):463 for element inpath:464 if notisinstance(element, str):465 path =None466 returnpath467 abspath = os.path.normpath(os.path.join(self.user_current_dir, *path))468 #print('def verify_path() ===> abspath:', abspath)

469 if abspath.startswith(self.user_obj['home']):470 path =abspath471 else:472 path = None #用户目录超出限制

473 else:474 path = None #不是列表类型例: '字符串'

475 else:476 path = None #[]

477 #print('def verify_path() ====> path', path)

478 returnpath479

480 defreceive_header(self):481 """

482 接收客户端数据.483 :return: {'action_type': 'cd', 'path': ['目录1', '目录xxx']}484 """

485 header_bytes =self.request.recv(self.fixed_packet_size)486 request_dic_json_length =struct.unpack(self.struct_fmt, header_bytes)[0]487 #print('request_dic_json_length:', request_dic_json_length)

488 #接收报头

489 request_dic_json =self.request.recv(request_dic_json_length).decode(self.encoding)490 request_dic =json.loads(request_dic_json)491

492 #print('request_dic:', request_dic)

493

494 if notrequest_dic:495 return{}496 #print("def receive_header():", request_dic)

497 returnrequest_dic498

499 def send_header(self, *, status_code, **kwargs):500 """

501 发送数据给客户端.502 :param status_code: 400503 :param kwargs: {'current_dir': '/home/egon/目录1/目录xxx'}504 :return: None505 """

506 #print(status_code)

507 #print(kwargs)

508 from core importmain509

510 response_dic =kwargs511 response_dic['status_code'] =status_code512 response_dic['status_msg'] =main.FTPServer.STATUS_CODE[status_code]513 response_dic.update(kwargs)514

515 response_dic_json_bytes =json.dumps(response_dic).encode(self.encoding)516 response_dic_json_bytes_length =len(response_dic_json_bytes)517 header_bytes =struct.pack(self.struct_fmt, response_dic_json_bytes_length)518

519 #print('header_bytes:', header_bytes)

520

521 #发送报头

522 self.request.sendall(header_bytes)523 #发送json后bytes后的字典response_dic

524 self.request.sendall(response_dic_json_bytes)

346>

用C语言创建多个用户,实现支持多用户在线的FTP程序(C/S)相关推荐

  1. 在docker容器中创建用户组和用户,并且多用户共用一个anaconda环境

    转载自:https://www.cnblogs.com/devilmaycry812839668/p/13760815.html 在docker容器中创建用户组和用户,并且多用户共用一个anacond ...

  2. SQL Server数据库创建数据库、用户、表和插入数据(Transact-SQL)

    SQL Server 基本操作 一.建数据库并创建用户 二.创建模式 三.建表并插入数据 一.建数据库并创建用户 首先使用 create database <表名> 创建数据库,这里使用如 ...

  3. AD域建设管理(二)| python3+ldap3管理AD域实践(批量创建OU、用户、改密码、更新OU与用户)

    AD域建设管理(二)| python3+ldap3管理AD域实践(批量创建OU.用户.改密码.更新OU与用户) 1.使用前提 1.1.关于程序运行环境 1.2.关于AD域的准备和配置 2.问题和解决方 ...

  4. Java语言springboot开发框架实现个性化美食推荐网 在线美食推荐系统 基于用户、物品的协同过滤推荐算法实现

    Java语言springboot开发框架实现个性化美食推荐网 在线美食推荐系统 基于用户.物品的协同过滤推荐算法实现WebFoodRecSystem 一.项目简介 1.开发工具和使用技术 IDEA/E ...

  5. Python 语言创建 HyerMesh Tcl 命令流

    目 录 Blog Links 一.前言 二.Tcl 命令 2.1 帮助文档 2.2 命令窗口 2.3 运行脚本 2.4 基本语法 三.模型组件 四.几何模型 4.1 几何点/Point 4.2 几何线 ...

  6. 在windows中创建一个影子用户

    在windows中创建一个影子用户(看不到图请下载附件) 我们可以在windows操作系统中建立一个影子用户,也就是它是实际存在的,但是不会在登录时或者用户组中显示,我们可以赋予影子用户管理员权限,可 ...

  7. R语言创建自定义颜色(分类变量与颜色形成稳定映射)实战:设置因子变量(分类变量)到可视化颜色的稳定映射

    R语言创建自定义颜色(分类变量与颜色形成稳定映射)实战:设置因子变量(分类变量)到可视化颜色的稳定映射 目录

  8. Wix 安装部署教程(十四) -- 多语言安装包之用户许可协议

    Wix 安装部署教程(十四) -- 多语言安装包之用户许可协议 原文:Wix 安装部署教程(十四) -- 多语言安装包之用户许可协议 在上一篇中,留下了许可协议的问题,目前已经解决.感谢网友武全的指点 ...

  9. SQLServer创建用户自定义数据库用户

    创建用户自定义数据库用户注意事项 如果已忽略 FOR LOGIN,则新的数据库用户将被映射到同名的SQL Server登录名. 默认架构将是服务器为此数据库用户解析对象名时将搜索的第一个架构. 除非另 ...

最新文章

  1. 引入 ServletContextListener @Autowired null 解决办法
  2. Tableau系列之构建和浏览数据视图
  3. mq同步mysql数据 duplicate entry_MySQL数据同步之otter
  4. 有时间担心中年危机,还不如用忧虑的时间来提升自己——再论程序员该如何避免所谓的中年危机...
  5. OpenCV常见的优化方法和技巧总结
  6. 第三周项目5-数组作数据成员
  7. Java Stream API性能测试
  8. WPF MVVM实例三
  9. python a and b_python-尽管Numpy建议a.b,为什么a.dot(b)比a @ b更...
  10. 六西格玛dfss_向六西格玛质量水平进攻!
  11. Webpack 中 css import 使用 alias 相对路径
  12. 语义分割Swin Transformer
  13. Entry name ‘classes.dex‘ collided
  14. linux 命令修改IP(最有效方法)
  15. 获取当前的时间是第几周
  16. Exploitation and Exploration
  17. 4G车牌识别摄像机 瞬间启动 快速唤醒抓拍 超低功耗硬件方案
  18. golang 将EBCDIC转成ASCII
  19. 暗组免杀php,那些强悍的PHP一句话后门
  20. PAT甲级1100 Mars Numbers (20 分)题解

热门文章

  1. android平板 深度学习,这款叫Remix的设备,或许可以拯救安卓平板
  2. export function函数传参_从底层看前端(七)—— JavaScript到底有多少种函数?
  3. php 连接符.,PHP怎么在数字之间添加连接符
  4. ipv6地址格式_IPV6与IPV4的差异
  5. shell 获取家目录_一篇教会你写90%的shell脚本
  6. Linux伪终端怎么退出,什么是linux里的终端和伪终端
  7. xilinx芯片管脚使用限制_修复焊接BGA芯片过程
  8. 计算机用户win7修改不,Win7电脑时间改不了的解决方法
  9. html日期只显示7天,vue+elementui 只能选7天内的日期
  10. ebay注册流程_跨境电商平台eBay企业入驻流程