服务程序需要注意的地方

2024-04-11 00:32

本文主要是介绍服务程序需要注意的地方,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

如何编写NT Service在MSDN->Platform SDK->DLLs, Processes, and Threads->Service中说得很清楚了,在这里我就不多说了,这里我就只说一些我个人认为的在编写service过程中要注意的地方。

 

0、在我们通过控制面板或NET START命令启动一个service时, Service Control Manager (SCM)从注册表里拿到service的可执行程序名,然后运行这个程序,从程序的入口main方法里得到service的service_main方法,然后就进入service_main运行。

一个service程序包含最少包含三个部分,一个是main方法,通常的工作是设置service_main方法和处理命令行参数(例如根据不同的参数执行其他的动作,象安装卸载service,手动启动停止service等等);一个是service_main方法,service程序具体要做的工作就写在这个方法里;一个是ServiceCtrlHandler方法,这个方法在service_main里设置,用来处理由SCM发给service的消息,例如service停止,暂停,系统关机等等。

 

1、手动启动一个service的方法是在service的main方法里调用StartService,StartService根据service的SERVICE_TABLE_ENTRY将程序转入相应的service_main。启动service时所要作的工作是写在service_main里的。通常的模式是在进入service_main后设状态为SERVICE_START_PENDING,然后进行一些初始化动作,然后执行一个线程或进程,在其中进行service的工作,然后设状态为SERVICE_RUNNING。一定要注意的是,启动service后,必须保证在两分钟内service的状态就会被设成SERVICE_RUNNING,否则会报错。

 

2、停止一个service的方法是在service的main方法里调用ControlService(hService,SERVICE_CONTROL_STOP,&ServStat)发送SERVICE_CONTROL_STOP消息到Service Control Manager (SCM),SCM收到这个消息后就会通知service,执行ServiceCtrlHandler里case SERVICE_CONTROL_STOP:内的工作。结束动作通常也不能太久,因为在关机时,系统会给每个service大概20秒时间清场(这个时间可以在注册表中设置)

 

3、安装和卸载一个service的方法是在service的main方法里调用CreateService方法和DeleteService方法,卸载方法前先要判断service是否在运行,如果在运行要先将服务停止

否则无法删除。

 

4、设置service状态的方法是调用SetServiceStatus,在写service的启动和停止动作是要设置其状态,分别在上面提到的service_main和ServiceCtrlHandler里。查询service状态的方法是QueryServiceStatus,在停止服务时,ControlService方法只是发送了一个消息后就立即返回了,因此通常要在执行完ControlService后,利用while、sleep和QueryServiceStatus来不断查询service的状态,直到状态为SERVICE_STOPPEDStartService与ControlService不同,并不是立即返回的,它会直到service_main内的代码执行完后才返回。

 

5、service_main即使返回,service的程序进程也不会退出,直到service的状态为SERVICE_STOPPED时,才会终止并退出。

 

6、在设置service状态时,通过指定dwWin32ExitCode和dwServiceSpecificExitCode可以设定当service在此状态下出错时的弹出式Message。不过只能设定一个错误代码。

 

7、使用ChangeServiceConfig2添加修改service的描述。

 

8、默认情况下service程序是不能与桌面交互的,即不能打开窗口。通过ChangeServiceConfig函数设定SERVICE_INTERACTIVE_PROCESS属性,或通过控制面板选中"允许服务与桌面交互",则服务程序可以打开窗口。例如在服务中使用CreateProcess创建了一个进程,只要在STARTUPINFO的wShowWindow和dwFlags设定了SW_SHOW和STARTF_USESHOWWINDOW,则进程就会在打开的一个新窗口中运行。

 

9、不论是否设定SERVICE_INTERACTIVE_PROCESS,在service中都可以通过MessageBox方法弹出MessageBox。这个函数的第一个参数指定为NULL,表示不指定父窗口。在第四个参数中指定MB_DEFAULT_DESKTOP_ONLY或MB_SERVICE_NOTIFICATION表示以桌面为父窗口。

 

10.如果要停止的一个Service上有其他正在运行的服务依赖着,这时直接停止这个服务就会出错,因此如果需要停止的服务有可能被其他服务所依赖,在停止前必须用EnumDependentServices()方法取得所有依赖于这个服务的服务,将这些服务依次停止后才行。具体代码示例请看MSDN->HOWTO->ID:Q245230。

 

11、启动service时可以使用启动参数,在定义service_main方法时其两个参数(DWORD argc, LPTSTR *argv),第一个即为参数个数,第二个则是一个参数数组指针。如何传入参数呢,由于service_main并不是程序入口,因此参数是通过main从命令行传至StartService方法,而调用StartService时,StartService的第二第三个参数,就是传入service_main的参数。如果service是auto-started的,每次开机时就会自动启动,没有给我们手工通过命令行传入参数的机会,这时我们只有在安装service的时候,就把参数传入。在CreateService时,将CreateService的lpBinaryPathName值设成例如SimpleService.exe arg1 arg2的样子,这样每当SCM启动service时SCM就会从main方法中获得参数并将其传入这个service的service_main中。

 

12.在启动一个service之后,在service的状态是”已启动(SERVICE_RUNNING)”之前,这段时间内,是无法启动另一个service的。

 

例子代码:

#include #include #include #include #include #include #include

#define SERVICE_NAME "TSService"#define SERVICE_DISPNAME "vPBX PathFinder TSService"#define SERVICE_DESCRIPTIONNAME "Start vPBX PathFinder service"#define SERVICE_EXENAME "\ServiceFrame.exe"#define LOG_FILENAME "\Service.log"#define START_EXENAME "\vpbxw.exe"#define STOP_EXENAME "\StopServer.exe"#define CMD_EXENAME " -svc"#define REG_ITEM "SOFTWARE\VisionNex\vPBX_Server"#define REG_KEY "home"#define FLAG_FILENAME "\PortKeeper.svc"#define START_DELAY 10000#define STOP_DELAY 2000#define COUNT_DELAY 50#define TIME_DELAY 2000

SERVICE_STATUS m_ServiceStatus;SERVICE_STATUS_HANDLE m_ServiceStatusHandle;BOOL bRunning=false;char Path[256];

void WINAPI ServiceMain(DWORD argc, LPTSTR *argv);void WINAPI ServiceCtrlHandler(DWORD Opcode);

BOOL InstallService(int flag);BOOL DeleteService();BOOL StartupService();BOOL StopService();BOOL ChangeService();

BOOL EndService();BOOL QueryReg ();DWORD GetStatus(SC_HANDLE service);void LogService(char* error);BOOL TestTs(int sleep, int count, int sec);BOOL WaitTsStartup(int sleep, int count, int sec);

 

int main(int argc, char* argv[]){  if(!QueryReg()) return 1;  if(argc>1) {  if(strcmp(argv[1],"-i")==0) {   InstallService(0);  }    else if (strcmp(argv[1],"-id")==0){      InstallService(1);    }  else if(strcmp(argv[1],"-d")==0) {   DeleteService();  }    else if(strcmp(argv[1],"-r")==0) {   StartupService();  }    else if(strcmp(argv[1],"-s")==0) {   StopService();  }    else if(strcmp(argv[1],"-c")==0) {      ChangeService();    }    else if(strcmp(argv[1],"-v")==0) {   printf("serivce frame version:1.0.0.5: debug=pipe(limit -0.3)");  }  else {   printf("Unknown Switch Usage For install use -i, for uninstall use -d, for run use -r, for stop use -s ");  } } else {  SERVICE_TABLE_ENTRY DispatchTable[]={{SERVICE_NAME, ServiceMain},{NULL,NULL}};    StartServiceCtrlDispatcher(DispatchTable);  } return 0;}

void WINAPI ServiceMain(DWORD argc, LPTSTR *argv){  DWORD status;   DWORD specificError;   LogService("Service Startup...");  m_ServiceStatus.dwServiceType        = SERVICE_WIN32;   m_ServiceStatus.dwCurrentState       = SERVICE_START_PENDING;   m_ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;   m_ServiceStatus.dwWin32ExitCode      = 0;   m_ServiceStatus.dwServiceSpecificExitCode = 0;   m_ServiceStatus.dwCheckPoint         = 0;   m_ServiceStatus.dwWaitHint           = 0;   m_ServiceStatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrlHandler);    if (m_ServiceStatusHandle == (SERVICE_STATUS_HANDLE)0) {       LogService("Error: RegisterServiceCtrlHandler");      return;   }     /*  //create pipe  SECURITY_ATTRIBUTES sa; HANDLE hRead,hWrite; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE;/* if (!CreatePipe(&hRead,&hWrite,&sa,0)) {   LogService("Error On CreatePipe()"); }   //---------  hWrite = CreateFile("d:\process.log",GENERIC_WRITE, FILE_SHARE_READ, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);  */

  PROCESS_INFORMATION pinfo;  STARTUPINFO sinfo;   ZeroMemory(&sinfo, sizeof(sinfo));  sinfo.cb = sizeof(sinfo);  char strDir[256];  char strDir_stop[256];  strcpy(strDir,Path);  strcpy(strDir_stop,Path);  strcat(strDir,START_EXENAME);  strcat(strDir_stop,STOP_EXENAME);  LPCTSTR lpszBinaryPathName=strDir;    LPCTSTR lpszBinaryPathName_stop=strDir_stop; 

  LogService("Start Create StopServer process");  if(!CreateProcess(lpszBinaryPathName_stop, CMD_EXENAME, NULL,NULL,FALSE,0,NULL,Path,&sinfo,&pinfo)) {    LogService("Error: CreateProcess:stop befor start");  return;  }  Sleep(STOP_DELAY);

  ZeroMemory(&sinfo, sizeof(sinfo));  sinfo.cb = sizeof(sinfo);  //Set Process output  //sinfo.hStdError = hWrite; //sinfo.hStdOutput = hWrite; sinfo.wShowWindow = SW_SHOW;  sinfo.dwFlags = STARTF_USESHOWWINDOW ;//| STARTF_USESTDHANDLES;  LogService("Start Create vPBXW process");  if(!CreateProcess(lpszBinaryPathName, CMD_EXENAME, NULL,NULL,TRUE,0,NULL,Path,&sinfo,&pinfo)) {    LogService("Error: CreateProcess:start");    m_ServiceStatus.dwCurrentState       = SERVICE_STOPPED;     m_ServiceStatus.dwCheckPoint         = 0;     m_ServiceStatus.dwWaitHint           = 0;     m_ServiceStatus.dwWin32ExitCode      = ERROR_SERVICE_SPECIFIC_ERROR;     m_ServiceStatus.dwServiceSpecificExitCode = 0;     SetServiceStatus (m_ServiceStatusHandle, &m_ServiceStatus);     return;  }    //CloseHandle(hWrite);  LogService("Created vPBXW process");  //read pipe    /*        char buffer[4096] = {0};      DWORD bytesRead = 0;      int i =1;      while (3)       {        i = i -1;        BOOL ret = ReadFile(hRead,buffer,4095,&bytesRead,NULL);        if (ret == NULL){          LogService("Read return NULL");          break;        }        if(ret == 0){          LogService("Read return 0");          break;        }        if(bytesRead == 0){          LogService("Read size 0");        }        else{          LogService("Read Success");          buffer[bytesRead]=0;          LogService(buffer);        }      }     //-----*/

  if(!TestTs(START_DELAY,COUNT_DELAY,TIME_DELAY)){    CloseHandle(pinfo.hThread);    CloseHandle(pinfo.hProcess); // CloseHandle(hRead);    return;  }

  m_ServiceStatus.dwCurrentState       = SERVICE_RUNNING;   m_ServiceStatus.dwCheckPoint         = 0;   m_ServiceStatus.dwWaitHint           = 0;    if (!SetServiceStatus (m_ServiceStatusHandle, &m_ServiceStatus)) {       LogService("Error: SetServiceStatus:SERVICE_RUNNING");      return;   }

  CloseHandle(pinfo.hThread);  CloseHandle(pinfo.hProcess);//CloseHandle(hRead);  return; }

BOOL EndService(){  PROCESS_INFORMATION pinfo;  STARTUPINFO sinfo;   ZeroMemory(&sinfo, sizeof(sinfo));  sinfo.cb = sizeof(sinfo);   char strDir[256];  strcpy(strDir,Path);  strcat(strDir,STOP_EXENAME);  LPCTSTR lpszBinaryPathName=strDir;      if(!CreateProcess(lpszBinaryPathName, CMD_EXENAME, NULL,NULL,FALSE,0,NULL,Path,&sinfo,&pinfo)) {    LogService("Error: CreateProcess:stop");  return false;  }   LogService("Service Stop...");  LogService("Service Stop OK");  CloseHandle(pinfo.hThread);  CloseHandle(pinfo.hProcess); return true;}

void WINAPI ServiceCtrlHandler(DWORD Opcode){    switch(Opcode)     {          case SERVICE_CONTROL_STOP:            m_ServiceStatus.dwWin32ExitCode = 0;             m_ServiceStatus.dwCurrentState  = SERVICE_STOPPED;             m_ServiceStatus.dwCheckPoint    = 0;             m_ServiceStatus.dwWaitHint      = 0;             SetServiceStatus (m_ServiceStatusHandle,&m_ServiceStatus);          bRunning=false;            EndService();         break;                case SERVICE_CONTROL_SHUTDOWN:             bRunning=false;            EndService();            char strDir[256];            strcpy(strDir,Path);            strcat(strDir,FLAG_FILENAME);            remove(strDir);            break;         case SERVICE_CONTROL_INTERROGATE:             break;     }      return; }

BOOL InstallService(int flag){ HANDLE schSCManager,schService;

 schSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);   if (schSCManager == NULL) {  printf("Error Installing Service ");    LogService("Error: Installing Service:OpenSCManager");  return false;  }

  char strDir[256];  strcpy(strDir,Path);  strcat(strDir,SERVICE_EXENAME);  LPCTSTR lpszBinaryPathName=strDir;      schService = CreateService(schSCManager,                             SERVICE_NAME,                              SERVICE_DISPNAME,                             SERVICE_ALL_ACCESS,        // desired access                              SERVICE_WIN32_OWN_PROCESS, // service type                              SERVICE_AUTO_START,        // start type                              SERVICE_ERROR_NORMAL,      // error control type                              lpszBinaryPathName,        // service's binary                              NULL,                      // no load ordering group                              NULL,                      // no tag identifier                              NULL,                      // no dependencies                              NULL,                      // LocalSystem account                              NULL);                     // no password

  if (schService == NULL) {  printf("Error Installing Service ");    LogService("Error: Installing Service:CreateService");   CloseServiceHandle(schSCManager);    return false;    }  printf("Service Installed OK ");  LogService("Service Installed OK");

  if(flag == 1){    ChangeServiceConfig(schService,SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, NULL, NULL,NULL,NULL,NULL,NULL,NULL);  }

  char description[] = SERVICE_DESCRIPTIONNAME;  SERVICE_DESCRIPTION svrDescription;  svrDescription.lpDescription = description;  ChangeServiceConfig2(schService,SERVICE_CONFIG_DESCRIPTION,&svrDescr



 用VC++建立Service服务应用程序

       

        为什么要使用服务应该程序呢?服务程序就像系统的一些服务一样,能够自动地启动,并执行相应的操作;而且因为服务程序的在层次上和一般的应用程序不同,其能够在系统启动时就自动地运行,而不像一般的应用程序那样一定要在登陆后才能运行,这些就是服务的一些好处了,如果你也想你的程序具有这样的功能,那么你就可以建立一个服务应用程序了。 

  下面就跟着我一步一步地教你怎么去创建一个服务应用程序吧。

  本文主要介绍了OpenSCManager、CreateService、OpenService、ControlService、DeleteService、RegisterServiceCtrlHandler、SetServiceStatus、StartServiceCtrlDispatcher等操作服务程序的主要几个API的用法,具体的函数参数大家可以查阅MSDN。

  一、建立Win32 Application应用程序(当然你也可以建立其它的应用程序,但服务一般是没有用户界面的),并命名为ServiceTest。

  二、定义全局函数变量

//定义全局函数变量

void Init();
BOOL IsInstalled();
BOOL Install();
BOOL Uninstall();
void LogEvent(LPCTSTR pszFormat, ...);
void WINAPI ServiceMain();
void WINAPI ServiceStrl(DWORD dwOpcode);

TCHAR szServiceName[] = _T("ServiceTest");
BOOL bInstall;
SERVICE_STATUS_HANDLE hServiceStatus;
SERVICE_STATUS status;
DWORD dwThreadID;

   三、添加Init初始化函数

  这里主要是设置服务句柄和状态。

hServiceStatus = NULL;
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
status.dwCurrentState = SERVICE_STOPPED;
tatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
status.dwWin32ExitCode = 0;
status.dwServiceSpecificExitCode = 0;
status.dwCheckPoint = 0;
status.dwWaitHint = 0;

   四、添加安装和删除服务函数

  这里主要是用到了五个函数OpenSCManager、CreateService、OpenService、ControlService、DeleteService。OpenSCManager用于打开服务控制管理器;CreateService用于创建服务;OpenService用于打开已有的服务,返回该服务的句柄;ControlService则用于控制已打开的服务状态,这里是让服务停止后才删除;DeleteService用于删除指定服务。

BOOL Install();
{
 //这里列出主要的两个函数,其它的可以在代码里找。
 //打开服务控制管理器

 OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

 //创建服务
 
 SC_HANDLE hService = ::CreateService(
  hSCM, szServiceName, szServiceName,
  SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
  SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
  szFilePath, NULL, NULL, _T(""), NULL, NULL);

 ::CloseServiceHandle(hService);
 ::CloseServiceHandle(hSCM);
}

BOOL Uninstall();
{
 //这里列出主要的两个函数,其它的可以在代码里找。
 //打开服务控制管理器

 OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

 //打开服务

 OpenService(hSCM, szServiceName, SERVICE_STOP | DELETE);

 //停止服务

 ControlService(hService, SERVICE_CONTROL_STOP, &status);

 //删除服务

 DeleteService(hService);

 …
}

 

五、添加服务主线程函数和控制函数

  这里调用RegisterServiceCtrlHandler来注册服务的控制函数,这里要设置status.dwControlsAccepted为SERVICE_ACCEPT_STOP,否则你不能控制这个服务的状态。

void WINAPI ServiceMain()
{
 // Register the control request handler

 status.dwCurrentState = SERVICE_START_PENDING;
 status.dwControlsAccepted = SERVICE_ACCEPT_STOP;//这个要使用,否则你不能控制

 //注册服务控制

 hServiceStatus = RegisterServiceCtrlHandler(szServiceName, ServiceStrl);
 if (hServiceStatus == NULL)
 {
  LogEvent(_T("Handler not installed"));
  return;
 }
 SetServiceStatus(hServiceStatus, &status);
 status.dwWin32ExitCode = S_OK;
 status.dwCheckPoint = 0;
 status.dwWaitHint = 0;
 status.dwCurrentState = SERVICE_RUNNING;
 SetServiceStatus(hServiceStatus, &status);

 //模拟服务的运行,10后自动退出。应用时将主要任务放于此即可

 int i = 0;
 while (i < 10)
 {
  Sleep(1000);
  i++;
 }
 //

 status.dwCurrentState = SERVICE_STOPPED;
 SetServiceStatus(hServiceStatus, &status);
 LogEvent(_T("Service stopped"));
}

   六、在主线程函数里注册控制函数和程序执行主体

void WINAPI ServiceMain()
{
 …

 //如上,这里主要是说明这就是程序的执行体
 //模拟服务的运行,10后自动退出。应用时将主要任务放于此即可

 int i = 0;
 while (i < 10)
 {
  Sleep(1000);
  i++;
 }
 …
}

   七、在main函数里注册添加安装、删除、注册主函数

int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,

int nCmdShow)
{
 Init();
 dwThreadID = ::GetCurrentThreadId();
 SERVICE_TABLE_ENTRY st[] =
 {
  { szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain },
  { NULL, NULL }
 };

 if (stricmp(lpCmdLine, "/install") == 0)
 {
  Install();
 }
 else if (stricmp(lpCmdLine, "/uninstall") == 0)
 {
  Uninstall();
 }
 else
 {
  if (!:Tongue TiedtartServiceCtrlDispatcher(st))
  {
   LogEvent(_T("Register Service Main Function Error!"));
  }
 }
 return 0;
}

   八、总结

  其实做一个服务程序并不难,主要是懂得程序的执行体放于哪里?注册程序的主函数和注册控制函数,如果这两个没有注册的话,你的程序就不知道如何去控制了。status.dwControlsAccepted = SERVICE_ACCEPT_STOP;这个也重要,如果你没有设置的话,那么服务就不会受你控制了。
 

这篇关于服务程序需要注意的地方的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/892586

相关文章

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

SpringMVC入参绑定特别注意

1.直接在controller中定义一个变量,但是此种传输方式有一个限制就是参数名和请求中的参数名必须保持一致,否则失效。 @RequestMapping("test2")@ResponseBodypublic DBHackResponse<UserInfoVo> test2(String id , String name){UserInfoVo userInfoVo = new UserInf

argodb自定义函数读取hdfs文件的注意点,避免FileSystem已关闭异常

一、问题描述 一位同学反馈,他写的argo存过中调用了一个自定义函数,函数会加载hdfs上的一个文件,但有些节点会报FileSystem closed异常,同时有时任务会成功,有时会失败。 二、问题分析 argodb的计算引擎是基于spark的定制化引擎,对于自定义函数的调用跟hive on spark的是一致的。udf要通过反射生成实例,然后迭代调用evaluate。通过代码分析,udf在

Vue2电商项目(二) Home模块的开发;(还需要补充js节流和防抖的回顾链接)

文章目录 一、Home模块拆分1. 三级联动组件TypeNav2. 其余组件 二、发送请求的准备工作1. axios的二次封装2. 统一管理接口API----跨域3. nprogress进度条 三、 vuex模块开发四、TypeNav三级联动组件开发1. 动态展示三级联动数据2. 三级联动 动态背景(1)、方式一:CSS样式(2)、方式二:JS 3. 控制二三级数据隐藏与显示--绑定styl

使用WebP解决网站加载速度问题,这些细节你需要了解

说到网页的图片格式,大家最常想到的可能是JPEG、PNG,毕竟这些老牌格式陪伴我们这么多年。然而,近几年,有一个格式悄悄崭露头角,那就是WebP。很多人可能听说过,但到底它好在哪?你的网站或者项目是不是也应该用WebP呢?别着急,今天咱们就来好好聊聊WebP这个图片格式的前世今生,以及它值不值得你花时间去用。 为什么会有WebP? 你有没有遇到过这样的情况?网页加载特别慢,尤其是那

插件maven-search:Maven导入依赖时,使用插件maven-search拷贝需要的依赖的GAV

然后粘贴: <dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>8.0.26</version> </dependency>

js基础需要注意的点

1 js中单引号和双引号都能创建字符串,但是html的元素属性规定必须用双引号,所以js优先用单引号定义字符串。

用ajax json给后台action传数据要注意的问题

必须要有get和set方法   1 action中定义bean变量,注意写get和set方法 2 js中写ajax方法,传json类型数据 3 配置action在struts2中