stm32f407 FATFS+FTP服务器

在这里插入图片描述
FTP命令端口21 数据端口2100

(没有读卡器 o(╥﹏╥)o)

在Filezila里面 一直读不到目录,仔细检查LIST_Commond函数也没有问题;

原先使用Filezila随意拖动了一些文件进去,得不到响应,

后来才想起来这种最原始的调试方法:

很神奇的是每次断开2100端口连接,再点击连接,数据端口的信息就打印出来了

[2023-04-22 23:30:45.897]# The server is connected from local 192.168.0.4:10202

[2023-04-22 23:30:45.966]# RECV ASCII>
0-0-1980 0:0:0   154394 SWUST.jpg
0-0-1980 0:0:0    <DIR> CYDZ
0-0-1980 0:0:0    <DIR> dir
0-0-1980 0:0:0      452 TESTCS~1.USE
0-0-1980 0:0:0    <DIR> a
0-0-1980 0:0:0     5198 TEST~1.CSP
0-0-1980 0:0:0    11627 FORMTE~1.CS
0-0-1980 0:0:0    37825 FORMTEST.CS
0-0-1980 0:0:0     6211 FORMTE~1.RES
0-0-1980 0:0:0     2864 RESOUR~1.CS
0-0-1980 0:0:0     5612 RESOUR~1.RES
0-0-1980 0:0:0     1089 SETTIN~1.CS
0-0-1980 0:0:0     4286 swust.ico

[2023-04-22 23:30:45.991]# The server is disconnected and a reconnection is scheduled in 5 seconds.

[2023-04-22 23:30:50.987]# Reconnecting to the server...

[2023-04-22 23:30:51.001]# The server is connected from local 192.168.0.4:10203

这应该是FTP中DOS类型的数据格式

命令端口

LIST / 

[2023-04-22 23:28:27.169]# RECV ASCII>
150 Here comes the directory listing.

[2023-04-22 23:28:28.595]# RECV ASCII>
226 Transfer complete. Closing data connection

[2023-04-22 23:29:23.765]# SEND ASCII>
LIST /CYDZ 

[2023-04-22 23:29:23.774]# RECV ASCII>
150 Here comes the directory listing.

[2023-04-22 23:29:25.552]# RECV ASCII>
226 Transfer complete. Closing data connection

[2023-04-22 23:29:31.121]# SEND ASCII>
LIST /CYDZ 

[2023-04-22 23:29:31.128]# RECV ASCII>
150 Here comes the directory listing.

[2023-04-22 23:29:33.532]# RECV ASCII>
226 Transfer complete. Closing data connection

[2023-04-22 23:29:49.618]# SEND ASCII>
CWD CYDZ 

[2023-04-22 23:29:49.633]# RECV ASCII>
250 Directory changed to /CYDZ 

[2023-04-22 23:30:00.787]# SEND ASCII>
pwd 

[2023-04-22 23:30:00.793]# RECV ASCII>
257 "/CYDZ " is your current location

[2023-04-22 23:30:07.665]# SEND ASCII>
LIST 

[2023-04-22 23:30:07.674]# RECV ASCII>
150 Here comes the directory listing.

[2023-04-22 23:30:09.366]# RECV ASCII>
226 Transfer complete. Closing data connection

[2023-04-22 23:30:35.002]# SEND ASCII>
CDUP 

[2023-04-22 23:30:35.019]# RECV ASCII>
250 Directory changed to /

[2023-04-22 23:30:43.991]# SEND ASCII>
LIST 

[2023-04-22 23:30:44.007]# RECV ASCII>
150 Here comes the directory listing.

[2023-04-22 23:30:45.966]# RECV ASCII>
226 Transfer complete. Closing data connection

命令端口的LIST、CDUP、pwd、CWD都没有问题

这是list命令解析函数

int16_t cmd_LIST(char *outbuffer)
{
	if (outbuffer) {
	  #if TCP_SERVICE
		#if !FTP_ANONYMOUS
		if (!tcpsrv_status.loginOK)
			return cmd_530(outbuffer);
		#endif
		
	char buffer[MAX_PATH];
	int ret;
	FRESULT fr;		
		
      ret = ftpd_fullpath(buffer, MAX_PATH, NULL, NULL);
      if (ret == -1)
        goto listerr;
      
      fr = f_opendir(&dir, buffer);
      if (fr != FR_OK)
        goto listerr;
      cwdir_ptr = &dir; // 文件夹打开了之后才能赋给state->dp
      
//      ret = ftpd_prepare_data(state);
//      if (ret == -1)
//        goto listerr2;
	  strcpy(outbuffer,"150 Here comes the directory listing.\r\n");
	  tcpsrv_status.data_state = 1;  //  :1 is list info the data channel take some action.
	  tcpsrv_status.LISTcontinue = 0; 	// reset Listing :ready to list in datachannel
	  return strlen(outbuffer);
listerr:
		sprintf(outbuffer,"550 Failed to open file.\r\n");
		return strlen(outbuffer);
	  #else
		return cmd_502(outbuffer); //模式未实现
	  #endif
  }
	else
	{
		return 0;
	}
}

遍历读取文件目录

int16_t cmd_MMCdir(char *outbuffer)
{
#if USE_MMC
	if (!cwdir_ptr)
		return 0;

	#if !FTP_ANONYMOUS
	if (outbuffer && !tcpsrv_status.loginOK)
		return cmd_530(outbuffer);
	#endif

	#if TCP_SERVICE
	tcpsrv_status.LISTcontinue = 0;	// continue Flag zur點ksetzen
	#endif
	if (!outbuffer) {
		//usart_write("\n\rSD-Karte:");
	}

	char *pstr = outbuffer;  
	uint16_t year;
	uint8_t month;
	uint8_t day;
	uint8_t hour;
	uint8_t min;
	uint8_t sec;


    
    while(f_readdir(cwdir_ptr, &fileinfo) == FR_OK)  //f_read is ok
    {
		if(fileinfo.fname[0] == '\0')
		{
			break;
		}
		
		if (strcmp(fileinfo.fname, ".") == 0 || strcmp(fileinfo.fname, "..") == 0) //.和..是本级可以跳过
		{
		  continue;
		}		
		
		fat16_get_file_modification_date(&fileinfo, &year, &month, &day);
		fat16_get_file_modification_time(&fileinfo, &hour, &min, &sec);

		if (outbuffer) {
			#if TCP_SERVICE
			char tmpbuf[12];

			#ifdef UNIX_LIST
			/*
			*  UNIX style Dateiliste
			*/
			char mstr[4];

			if (dir_entry.fattrib & AM_DIR) {
				strcpy((char *)tmpbuf,("drwxr-xr-x"));
			}
			else {
				strcpy((char *)tmpbuf,("-rw-rw-rw-"));
			}

			strncpy(mstr,&US_Monate[(month-1)*3],3);
			mstr[3] = '\0';
			sprintf((char *)pstr,("%s 1 ftp ftp %ld %s/%i/%i %i:%i %s\r\n"),tmpbuf, fileinfo.file_size,
																mstr, day, year, hour, min, fileinfo.lfname);
			#endif

			#ifdef DOS_LIST
			/*
			*  DOS style Dateiliste
			* (bei FileZilla nennt sich das DOS - woanders Windows_NT(?) ...)
			*/
			if (fileinfo.fattrib & AM_DIR) {
				strcpy((char *)tmpbuf,("<DIR>"));
			}
			else {
				sprintf(tmpbuf,("%ld"),fileinfo.fsize);
			}

			sprintf((char *)pstr,("%i-%i-%i %i:%i:%i %8s %s\r\n"),month, day, year, hour, min, sec, tmpbuf,fileinfo.fname);
			#endif

			while (*pstr++);	// 把结束符消除掉
			--pstr;

			if (pstr > (outbuffer + MAX_WINDOWS_SIZE - 64)) {	// 大于滑动窗口
				tcpsrv_status.LISTcontinue = 1;					// continue Flag setzen
				break;
			}
			#endif
		}
		else {
//			usart_write("\r\n%2i.%2i.%i %2i:%2i ",day, month, year, hour, min);
//			if (fileinfo.fattrib & AM_DIR)
//				usart_write("    <DIR> ");
//			else
//				usart_write("%9l ",fileinfo.fsize);

//			usart_write("%s",fileinfo.lfname);
		}
    }

	#if TCP_SERVICE
	if (!tcpsrv_status.LISTcontinue)
		f_rewinddir(cwdir_ptr);
	#endif

	if (outbuffer)
		return strlen(outbuffer);
	else
#endif
		return 0;
}

命令格式


1.LIST
  • DOS类型

DOS格式共分四段,其中第一段为日期,第二段为时间,第三段为文件长度,第四段为文件名称。

m-d-y[space]h-m-s[space][fisesize/<DIR][space][filename]
0-0-1980 0:0:0 11627 FORMTE~1.CS
0-0-1980 0:0:0 37825 FORMTEST.CS
0-0-1980 0:0:0 6211 FORMTE~1.RES
0-0-1980 0:0:0 2864 RESOUR~1.CS
0-0-1980 0:0:0 5612 RESOUR~1.RES
0-0-1980 0:0:0 1089 SETTIN~1.CS
0-0-1980 0:0:0 4286 swust.ico

代码如下:

char	US_Monate[] = "JanFebMarAprMayJunJulAugSepOctNovDec";

int16_t cmd_MMCdir(char *outbuffer)
{
#if USE_MMC
	if (!cwdir_ptr)
		return 0;

	#if !FTP_ANONYMOUS
	if (outbuffer && !tcpsrv_status.loginOK)
		return cmd_530(outbuffer);
	#endif

	#if TCP_SERVICE
	tcpsrv_status.LISTcontinue = 0;	// continue Flag zur點ksetzen
	#endif
	if (!outbuffer) {
		//usart_write("\n\rSD-Karte:");
	}

	char *pstr = outbuffer;  
	uint16_t year;
	uint8_t month;
	uint8_t day;
	uint8_t hour;
	uint8_t min;
	uint8_t sec;


    /* Verzeichniseintr鋑e lesen */
    while(f_readdir(cwdir_ptr, &fileinfo) == FR_OK)  //f_read is ok
    {
		if(fileinfo.fname[0] == '\0')
		{
			break;
		}
		
		if (strcmp(fileinfo.fname, ".") == 0 || strcmp(fileinfo.fname, "..") == 0) //.和..是本级可以跳过
		{
		  continue;
		}		
		
		fat16_get_file_modification_date(&fileinfo, &year, &month, &day);
		fat16_get_file_modification_time(&fileinfo, &hour, &min, &sec);

		if (outbuffer) {
			#if TCP_SERVICE
			char tmpbuf[12];

			#ifdef UNIX_LIST
			/*
			*  UNIX style Dateiliste
			*/
			char mstr[4];

			if (fileinfo.fattrib & AM_DIR) {
				strcpy((char *)tmpbuf,("drwxr-xr-x"));
			}
			else {
				strcpy((char *)tmpbuf,("-rw-rw-rw-"));
			}

			strncpy(mstr,&US_Monate[(month-1)*3],3);
			mstr[3] = '\0';
			sprintf((char *)pstr,"%s 1 ftp ftp %ld %s/%i/%i %i:%i %s\r\n",tmpbuf, fileinfo.fsize,
																mstr, day, year, hour, min, fileinfo.fname);
			
			#endif

			#ifdef DOS_LIST
			/*
			*  DOS style Dateiliste
			* (bei FileZilla nennt sich das DOS - woanders Windows_NT(?) ...)
			*/
			if (fileinfo.fattrib & AM_DIR) {
				strcpy((char *)tmpbuf,("<DIR>"));
			}
			else {
				sprintf(tmpbuf,("%ld"),fileinfo.fsize);
			}

			sprintf((char *)pstr,("%i-%i-%i %i:%i:%i %8s %s\r\n"),month, day, year, hour, min, sec, tmpbuf,fileinfo.fname);
			#endif

			while (*pstr++);	// 把结束符消除掉
			--pstr;

			if (pstr > (outbuffer + MAX_WINDOWS_SIZE - 64)) {	// 大于滑动窗口
				tcpsrv_status.LISTcontinue = 1;					// continue Flag setzen
				break;
			}
			#endif
		}
		else {
//			usart_write("\r\n%2i.%2i.%i %2i:%2i ",day, month, year, hour, min);
//			if (fileinfo.fattrib & AM_DIR)
//				usart_write("    <DIR> ");
//			else
//				usart_write("%9l ",fileinfo.fsize);

//			usart_write("%s",fileinfo.lfname);
		}
    }

	#if TCP_SERVICE
	if (!tcpsrv_status.LISTcontinue)
		f_rewinddir(cwdir_ptr);
	#endif

	if (outbuffer)
		return strlen(outbuffer);
	else
#endif
		return 0;
}
  • UNIX类型

好消息是,unix格式可以被成功解析,但是格式还是有一些问题:

-rw-rw-rw- 1 ftp ftp 14240 /0/1980 0:0 SWUST.jpg
drwxr-xr-x 1 ftp ftp 0 /0/1980 0:0 CYDZ
drwxr-xr-x 1 ftp ftp 0 /0/1980 0:0 dir
-rw-rw-rw- 1 ftp ftp 452 /0/1980 0:0 TESTCS~1.USE
drwxr-xr-x 1 ftp ftp 0 /0/1980 0:0 a
-rw-rw-rw- 1 ftp ftp 5198 /0/1980 0:0 TEST~1.CSP
-rw-rw-rw- 1 ftp ftp 11627 /0/1980 0:0 FORMTE~1.CS
-rw-rw-rw- 1 ftp ftp 37825 /0/1980 0:0 FORMTEST.CS
-rw-rw-rw- 1 ftp ftp 6211 /0/1980 0:0 FORMTE~1.RES
-rw-rw-rw- 1 ftp ftp 2864 /0/1980 0:0 RESOUR~1.CS
-rw-rw-rw- 1 ftp ftp 5612 /0/1980 0:0 RESOUR~1.RES
-rw-rw-rw- 1 ftp ftp 1089 /0/1980 0:0 SETTIN~1.CS
-rw-rw-rw- 1 ftp ftp 4286 /0/1980 0:0 swust.ico

Serv-U:
-rwxrw-r-- 1 user group 3014 Nov 12 14:57 cwinvnc337.ESn
-rwxrw-r-- 1 user group 20480 Mar 3 11:25 inmcsvr更新说明.ESn
-rwxrw-r-- 1 user group 450 Apr 13 11:39 对话框中加入工具条.ESn
Windows自带FTP:
-rwxrwxrwx 1 owner group 19041660 May 25 2004 VC.ESn
-rwxrwxrwx 1 owner group 450 Apr 6 15:04 对话框中加入工具条.ESn

再看UNIX格式,也拿出一条文件信息来讲:
-rwxrw-r-- 1 user group 3014 Nov 12 14:57 cwinvnc337.ESn
unix我不熟,每一段的意义不太清楚。但以上的格式分解为:第一段为-rwxrw-r–,第二段为1,第三段为user,第四段为group,第五段为文件长度,第六段为月,第七段为日,第八段为时间,第九段为文件名称。
需要注意的是:如果格式串的第一个字符为d,表示为一个目录信息,比如drwxrw-r–
另外,第八段有可能不是时间,而是年份,比如2005,从上面的例子中你可以发现。

很明显我这里格式错乱了,而且时间解析不对!

在FileZilla里可以显示出来了
在这里插入图片描述

可是!,文件名还是有问题,再度检查一下源代码!


引入time.h

https://www.jb51.net/article/238029.htm

strftime函数主要用于时间格式化,它的函数原型如下:

size_t __cdecl strftime(char * restrict _Buf,size_t _SizeInBytes,const char * restrict _Format,const struct tm * restrict _Tm);

  • _Buf, 表示返回的时间字符串
  • _SizeInBytes, 要写入的字节的最大数量
  • _Format, 这是 C 字符串,包含了普通字符和特殊格式说明符的任何组合。
  • _Tm, 输入时间结构体

在这里插入图片描述
时间结构体格式如下:

> struct tm {
   int tm_sec;         /* 秒,范围从 0 到 59                */
   int tm_min;         /* 分,范围从 0 到 59                */
   int tm_hour;        /* 小时,范围从 0 到 23                */
   int tm_mday;        /* 一月中的第几天,范围从 1 到 31     */
   int tm_mon;         /* 月份,范围从 0 到 11                */
   int tm_year;        /* 自 1900 起的年数                */
   int tm_wday;        /* 一周中的第几天,范围从 0 到 6   */
   int tm_yday;        /* 一年中的第几天,范围从 0 到 365  */
   int tm_isdst;       /* 夏令时                        */    
};

也就是说strftime函数的功能就是将时间结构体转换为指定的字符串格式。下面通过一个简单例子来演示strftime函数的用法。

 #include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char** argv)
{
	time_t now_time;
	struct tm *info;
	char buffer[80];
	time( &now_time );
	info = localtime( &now_time );
	strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", info);
	printf("格式化的日期和时间 : %s \n", buffer );
	return 0;
}

首先使用time函数获取当前时间,然后在使用strftime函数将时间通过指定的字符串格式打印出来,打印结果如下:
在这里插入图片描述
通过打印的结果可以看出,打印字符串的格式和函数中指定的字符串格式是一样的,这样通过对字符串格式的设置,就可按照自己的要求打印出时间和日期的字符串,使用起来更加灵活和方便。


2.PASV(被动模式)

站点发送PASV命令,尝试被动连接模式,FTP server返回IP地址与数据端口号;
站点连接FTP server返回的端口号(FTP server被动等待FTP client站点的连接)
命令: PASV
响应: 227 Entering Passive Mode (192,168,0,91,8,52)

代码实现:

int16_t cmd_PASV(char *outbuffer)
{
	if (outbuffer) {
		sprintf(outbuffer,("227 Entering Passive Mode (%i,%i,%i,%i,8,52)\r\n"),eeprom.myip[0][0],eeprom.myip[0][1],eeprom.myip[0][2],eeprom.myip[0][3]);
		return strlen(outbuffer);
	}
	else {
		return 0;
	}
}

其中端口号的计算方式为:最后两位8、52 DATA PORT = 8*256+52


最新调试bug描述:
在这里插入图片描述
文件和目录可以正常浏览:也就是LIST CWD CDUP 指令通了

对于mkdir 好像没有设置到正确的返回指令;
删除文件指令是正常的.
删除文件夹指令:正常


存文件时:(STOR指令)

在这里插入图片描述

状态:	开始上传 E:\Test\Test.sln
状态:	文件传输成功,传输了 897 字节 (用时1)
状态:	读取“/CYDZ”的目录列表...
命令:	TYPE I
响应:	200 Using BINARY mode to transfer data.
命令:	PASV
响应:	227 Entering Passive Mode (192,168,0,91,8,52)
命令:	LIST
响应:	150 Here comes the directory listing.
响应:	226 Transfer complete. Closing data connection
错误:	传输连接被打断: ECONNABORTED - 连接中止
错误:	读取目录列表失败
状态:	读取“/CYDZ”的目录列表...
状态:	列出“/CYDZ”的目录成功
状态:	已从服务器断开
状态:	读取“/CYDZ”的目录列表...
状态:	列出“/CYDZ”的目录成功
状态:	读取“/CYDZ”的目录列表...
状态:	列出“/CYDZ”的目录成功

从这里看是文件传输完成从新发起了连接,并且完成PASV LIST的操作
但是这个时候又提示传输连接被打断:ECONNABORTED

-连接中止,这是怎么个情况呢?
-读取目录列表失败

对于RETR取文件指令的测试:

状态:	开始下载 /dir/swust.ico
命令:	CWD /dir
响应:	250 Directory changed to /dir
命令:	PWD
响应:	257 "/dir" is your current location
命令:	TYPE A
响应:	200 Using ASCII mode to transfer data.
命令:	PASV
响应:	227 Entering Passive Mode (192,168,0,91,8,52)
命令:	RETR swust.ico
响应:	150 Opening ASCII mode data connection for  (4286 bytes).
错误:	传输连接被打断: ECONNABORTED - 连接中止

不容乐观,传输没有成功

在这里插入图片描述
用调试助手看一看是怎么一回事:

确认当前目录正确,指令RETR swust.ico ,数据端口没有收到任何数据!

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 工程下载步骤如下: 1. 确保计算机已经安装了适当的开发环境,包括Keil MDK和ST-Link驱动程序。 2. 在ST官方网站上下载STM32F407 Discovery板的相关固件库,包括FATFS、LWIP和FreeRTOS。 3. 解压下载的文件,并将它们放在适当的文件夹中。 4. 打开Keil MDK软件,并选择File > Open Project from Project 进行工程文件加载。 5. 浏览并选择STM32F407 Discovery板上的FATFS、LWIP和FreeRTOS所对应的示例工程。 6. 在工程文件加载完毕之后,打开Project > Options for Target 进入选项设置。 7. 在选项设置中,确认芯片型号选择为STM32F407xx,并选择正确的编译器。 8. 在选项设置中,检查链接脚本和其他设置是否与您的目标硬件一致。 9. 配置编译器和工程选项,根据需要对工程进行修改。 10. 设置好工程选项后,点击Build按钮进行编译。如果编译成功,则生成可执行文件。 11. 将ST-Link连接到计算机,并将STM32F407 Discovery板与ST-Link进行连接。 12. 在Keil MDK软件中,点击Flash按钮,将生成的可执行文件下载到STM32F407 Discovery板中。 13. 下载完成后,重新启动STM32F407 Discovery板以使程序生效。 这样,您就成功地完成了STM32F407 Discovery板上FATFS、LWIP和FreeRTOS的工程下载。您可以调试和测试您的应用程序,确保它能够正常运行。 ### 回答2: 在STM32F407 MCU上实现一个工程下载的过程包括以下几个步骤: 1. 配置STM32F407的引脚和外设:首先,我们需要在STM32F407上配置SPI接口来连接SD卡模块,以便进行存储文件系统。还需要配置以太网接口来连接到LAN网络以进行文件传输。 2. 初始化FATFS和LWIP库:使用FATFS库来实现文件系统,它可以在SD卡或其他存储设备上创建文件和读取文件。LWIP库提供了TCP/IP协议栈,用于建立和维护与服务器之间的通信。 3. 创建任务和队列:使用FreeRTOS来管理系统中的任务。我们可以创建多个任务来实现不同的功能,例如文件传输、网络通信等。还可以使用队列来实现不同任务之间的数据传输。 4. 实现文件下载功能:创建一个任务,在该任务中读取服务器上的文件并将其写入SD卡或其他存储设备中。任务可以使用LWIP库中提供的TCP/IP协议栈来与服务器建立连接,并发送相应的请求和接收文件。 5. 实现文件传输功能:创建另一个任务,在该任务中使用LWIP库中提供的TCP/IP协议栈与服务器建立连接,并发送文件到服务器。任务可以将所需的文件从SD卡中读取,并使用LWIP库提供的函数将其发送到服务器上。 6. 启动任务调度器:在main函数中启动任务调度器,将所有任务加入任务队列,并启动系统运行。任务调度器将以合适的方式调度每个任务的执行,以确保系统的正常运行。 总结起来,STM32F407工程下载的过程涉及初始化外设和库、创建任务和队列、实现文件下载和传输功能等步骤。通过这些步骤,我们可以实现在STM32F407 MCU上进行文件下载的功能。 ### 回答3: STM32F407是一种高性能的微控制器,具有多种外设和功能模块,可以用于各种应用领域。在STM32F407上实现FATFS,LWIP和FreeRTOS的工程下载可以分为以下几个步骤。 首先,需要准备好STM32F407的开发环境,包括编译器、调试工具和开发板等。可以选择使用Keil MDK或者IAR Embedded Workbench等集成开发环境进行开发。 接下来,需要将FATFS、LWIP和FreeRTOS的库文件添加到工程中。可以从官方网站或者开发者社区获取这些库文件,并按照相应的教程进行安装和配置。 然后,需要编写应用程序的代码。可以根据实际需求和功能来设计和实现相应的任务和功能模块,例如文件系统操作、网络通信和任务调度等。 完成代码编写后,需要进行编译和链接。在编译过程中,编译器会将源代码转化为目标代码,并生成相应的可执行文件。在链接过程中,链接器会将不同的目标代码和库文件进行合并,生成可执行文件。 最后,将生成的可执行文件下载到STM32F407的开发板上进行验证和调试。可以通过调试工具或者串口等方式进行下载和调试,确保程序的正确性和稳定性。 总结来说,STM32F407上实现FATFS、LWIP和FreeRTOS的工程下载需要准备开发环境,添加库文件,编写代码,进行编译和链接,最后下载到开发板上进行验证和调试。这样可以实现丰富的功能和应用,提高开发效率和产品质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值