原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/12192425.html,否则将追究法律责任!!!

一、Apache ftpserver相关简介

  Apache FtpServer是100%纯Java FTP服务器。它被设计为基于当前可用的开放协议的完整且可移植的FTP服务器引擎解决方案。FtpServer可以作为Windows服务或Unix / Linux守护程序独立运行,也可以嵌入Java应用程序中。我们还提供对Spring应用程序内集成的支持,并以OSGi捆绑软件的形式提供我们的发行版。默认的网络支持基于高性能异步IO库Apache MINA。使用MINA,FtpServer可以扩展到大量并发用户。

二、Apache ftpserver相关特性

  • 100%纯Java,免费的开源可恢复FTP服务器
  • 多平台支持和多线程设计。
  • 用户虚拟目录,写入权限,空闲超时和上载/下载带宽限制支持。
  • 匿名登录支持。
  • 上传和下载文件都是可恢复的。
  • 处理ASCII和二进制数据传输。
  • 支持IP限制以禁止IP。
  • 数据库和文件可用于存储用户数据。
  • 所有FTP消息都是可定制的。
  • 隐式/显式SSL / TLS支持。
  • MDTM支持-您的用户可以更改文件的日期时间戳。
  • “模式Z”支持更快地上传/下载数据。
  • 可以轻松添加自定义用户管理器,IP限制器,记录器。
  • 可以添加用户事件通知(Ftplet)。

三、Apache ftpserver简单部署使用(基于windows下,linux大同小异)

  • 1、根据需要下载对应版本的部署包:https://mina.apache.org/ftpserver-project/downloads.html
  • 2、解压部署包并调整.\res\conf\users.properties和.\res\conf\ftpd-typical.xml配置文件


users.properties文件配置

    例如配置一个bxl用户:#密码 配置新的用户ftpserver.user.bxl.userpassword=123456#主目录,这里可以自定义自己的主目录ftpserver.user.bxl.homedirectory=./res/bxl-home#当前用户可用ftpserver.user.bxl.enableflag=true#具有上传权限ftpserver.user.bxl.writepermission=true#最大登陆用户数为20ftpserver.user.bxl.maxloginnumber=20#同IP登陆用户数为2ftpserver.user.bxl.maxloginperip=2#空闲时间为300秒ftpserver.user.bxl.idletime=300#上传速率限制为480000字节每秒ftpserver.user.bxl.uploadrate=48000000#下载速率限制为480000字节每秒ftpserver.user.bxl.downloadrate=48000000

ftpd-typical.xml文件配置

    <server xmlns="http://mina.apache.org/ftpserver/spring/v1"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://mina.apache.org/ftpserver/spring/v1 http://mina.apache.org/ftpserver/ftpserver-1.0.xsd" id="myServer"><listeners><nio-listener name="default" port="2121"><ssl><keystore file="./res/ftpserver.jks" password="password" /></ssl><!--注意:如果要支持外网连接,需要使用被动模式passive,默认开启主动模式--><data-connection idle-timeout="60"><active enabled="true" ip-check="true" /><!-- <passive ports="2000-2222" address="0.0.0.0" external-address="xxx.xxx.xxx.xxx" /> --></data-connection><!--添加ip黑名单--><blacklist>127.0.0.1</blacklist></nio-listener></listeners><!--这里添加encrypt-passwords="clear",去掉密码加密--><file-user-manager file="./res/conf/users.properties" encrypt-passwords="clear" /></server>
  • 3、启动并访问

    • 首先启动服务,打开cmd并cd到bin路径执行.\ftpd.bat res/conf/ftpd-typical.xml,看到如下状态说明启动成功

    • 测试访问,打开浏览器输入:ftp://localhost:2121/就会看到你的文件目录了,如果没有配置匿名用户,则会要求你输入用户名密码,正是你在user.properties中配置的

四、Springboot整合Apache ftpserver(重点)

方式一:独立部署ftpserver服务

  这种方式比较简单,只要把服务部署好即可,然后通过FtpClien来完成相关操作,同jedis访问redis服务一个道理,没啥可说的。主要注意一下ftpserver的访问模式,如果要支持外网连接,需要使用被动模式passive。

方式二:将ftpserver服务内嵌到springboot服务中

  这种方式需要和springboot整合在一起,相对比较复杂,但这种方式下ftpserver会 随着springboot服务启动或关闭而开启或销毁。具体使用哪种方式就看自己的业务需求了。

  简单说一下我的实现的方案,ftpserver支持配置文件和db两种方式来保存账号信息和其它相关配置,如果我们的业务系统需要将用户信息和ftp的账号信息打通,并且还有相关的业务统计,比如统计系统中每个人上传文件的时间、个数等等,那么使用数据库来保存ftp账号信息还是比较方便灵活的。我这里就选择使用mysql了。

开始整合

  • 1、项目添加依赖

    //这些只是apache ftpserver相关的依赖,springboot项目本身的依赖大家自己添加即可
    <dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25 </version>
    </dependency>
    <dependency><groupId>org.apache.ftpserver</groupId><artifactId>ftpserver-core</artifactId><version>1.1.1</version>
    </dependency>
    <dependency><groupId>org.apache.ftpserver</groupId><artifactId>ftplet-api</artifactId><version>1.1.1</version>
    </dependency><dependency><groupId>org.apache.mina</groupId><artifactId>mina-core</artifactId><version>2.0.16</version>
    </dependency>   
  • 2、数据库建表用来保存相关的账户信息(大家可以手动添加几条用来测试),具体字段意思参考users.properties文件配置(可以想象一下以后我们的系统每注册一个用户都可以为其添加一条ftp_user信息,用来指定保存用户的上传数据等等)

    CREATE TABLE FTP_USER (      userid VARCHAR(64) NOT NULL PRIMARY KEY,       userpassword VARCHAR(64),      homedirectory VARCHAR(128) NOT NULL,             enableflag BOOLEAN DEFAULT TRUE,    writepermission BOOLEAN DEFAULT FALSE,       idletime INT DEFAULT 0,             uploadrate INT DEFAULT 0,             downloadrate INT DEFAULT 0,maxloginnumber INT DEFAULT 0,maxloginperip INT DEFAULT 0
    );
  • 3、配置ftpserver,提供ftpserver的init()、start()、stop()方法

    import com.mysql.cj.jdbc.MysqlDataSource;
    import com.talkingdata.tds.ftpserver.plets.MyFtpPlet;
    import org.apache.commons.io.IOUtils;
    import org.apache.ftpserver.DataConnectionConfigurationFactory;
    import org.apache.ftpserver.FtpServer;
    import org.apache.ftpserver.FtpServerFactory;
    import org.apache.ftpserver.ftplet.FtpException;
    import org.apache.ftpserver.ftplet.Ftplet;
    import org.apache.ftpserver.listener.Listener;
    import org.apache.ftpserver.listener.ListenerFactory;
    import org.apache.ftpserver.ssl.SslConfigurationFactory;
    import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;
    import org.apache.ftpserver.usermanager.DbUserManagerFactory;
    import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.stereotype.Component;import javax.sql.DataSource;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;/*** 注意:被@Configuration标记的类会被加入ioc容器中,而且类中所有带 @Bean注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例。* ftp服务访问地址:*      ftp://localhost:3131/*/
    @Configuration("MyFtp")
    public class MyFtpServer {private static final Logger logger = LoggerFactory.getLogger(MyFtpServer.class);//springboot配置好数据源直接注入即可@Autowiredprivate DataSource dataSource;protected FtpServer server;//我们这里利用spring加载@Configuration的特性来完成ftp server的初始化public MyFtpServer(DataSource dataSource) {this.dataSource = dataSource;initFtp();logger.info("Apache ftp server is already instantiation complete!");}/*** ftp server init* @throws IOException*/public void initFtp() {FtpServerFactory serverFactory = new FtpServerFactory();ListenerFactory listenerFactory = new ListenerFactory();//1、设置服务端口listenerFactory.setPort(3131);//2、设置被动模式数据上传的接口范围,云服务器需要开放对应区间的端口给客户端DataConnectionConfigurationFactory dataConnectionConfFactory = new DataConnectionConfigurationFactory();dataConnectionConfFactory.setPassivePorts("10000-10500");listenerFactory.setDataConnectionConfiguration(dataConnectionConfFactory.createDataConnectionConfiguration());//3、增加SSL安全配置
    //        SslConfigurationFactory ssl = new SslConfigurationFactory();
    //        ssl.setKeystoreFile(new File("src/main/resources/ftpserver.jks"));
    //        ssl.setKeystorePassword("password");//ssl.setSslProtocol("SSL");// set the SSL configuration for the listener
    //        listenerFactory.setSslConfiguration(ssl.createSslConfiguration());
    //        listenerFactory.setImplicitSsl(true);//4、替换默认的监听器Listener listener = listenerFactory.createListener();serverFactory.addListener("default", listener);//5、配置自定义用户事件Map<String, Ftplet> ftpLets = new HashMap();ftpLets.put("ftpService", new MyFtpPlet());serverFactory.setFtplets(ftpLets);//6、读取用户的配置信息//注意:配置文件位于resources目录下,如果项目使用内置容器打成jar包发布,FTPServer无法直接直接读取Jar包中的配置文件。//解决办法:将文件复制到指定目录(本文指定到根目录)下然后FTPServer才能读取到。
    //        PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory();
    //        String tempPath = System.getProperty("java.io.tmpdir") + System.currentTimeMillis() + ".properties";
    //        File tempConfig = new File(tempPath);
    //        ClassPathResource resource = new ClassPathResource("users.properties");
    //        IOUtils.copy(resource.getInputStream(), new FileOutputStream(tempConfig));
    //        userManagerFactory.setFile(tempConfig);
    //        userManagerFactory.setPasswordEncryptor(new ClearTextPasswordEncryptor());  //密码以明文的方式
    //        serverFactory.setUserManager(userManagerFactory.createUserManager());//6.2、基于数据库来存储用户实例DbUserManagerFactory dbUserManagerFactory = new DbUserManagerFactory();//todo....dbUserManagerFactory.setDataSource(dataSource);dbUserManagerFactory.setAdminName("admin");dbUserManagerFactory.setSqlUserAdmin("SELECT userid FROM FTP_USER WHERE userid='{userid}' AND userid='admin'");dbUserManagerFactory.setSqlUserInsert("INSERT INTO FTP_USER (userid, userpassword, homedirectory, " +"enableflag, writepermission, idletime, uploadrate, downloadrate) VALUES " +"('{userid}', '{userpassword}', '{homedirectory}', {enableflag}, " +"{writepermission}, {idletime}, uploadrate}, {downloadrate})");dbUserManagerFactory.setSqlUserDelete("DELETE FROM FTP_USER WHERE userid = '{userid}'");dbUserManagerFactory.setSqlUserUpdate("UPDATE FTP_USER SET userpassword='{userpassword}',homedirectory='{homedirectory}',enableflag={enableflag},writepermission={writepermission},idletime={idletime},uploadrate={uploadrate},downloadrate={downloadrate},maxloginnumber={maxloginnumber}, maxloginperip={maxloginperip} WHERE userid='{userid}'");dbUserManagerFactory.setSqlUserSelect("SELECT * FROM FTP_USER WHERE userid = '{userid}'");dbUserManagerFactory.setSqlUserSelectAll("SELECT userid FROM FTP_USER ORDER BY userid");dbUserManagerFactory.setSqlUserAuthenticate("SELECT userid, userpassword FROM FTP_USER WHERE userid='{userid}'");dbUserManagerFactory.setPasswordEncryptor(new ClearTextPasswordEncryptor());serverFactory.setUserManager(dbUserManagerFactory.createUserManager());//7、实例化FTP Serverserver = serverFactory.createServer();}/*** ftp server start*/public void start(){try {server.start();logger.info("Apache Ftp server is starting!");}catch(FtpException e) {e.printStackTrace();}}/*** ftp server stop*/public void stop() {server.stop();logger.info("Apache Ftp server is stoping!");}}
  • 4、配置监听器,使spring容器启动时启动ftpserver,在spring容器销毁时停止ftpserver

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.context.WebApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;@WebListener
    public class FtpServerListener implements ServletContextListener {private static final Logger logger = LoggerFactory.getLogger(MyFtpServer.class);private static final String SERVER_NAME="FTP-SERVER";@Autowiredprivate MyFtpServer server;//容器关闭时调用方法stop ftpServerpublic void contextDestroyed(ServletContextEvent sce) {
    //        WebApplicationContext ctx= WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
    //        MyFtpServer server=(MyFtpServer)ctx.getServletContext().getAttribute(SERVER_NAME);server.stop();sce.getServletContext().removeAttribute(SERVER_NAME);logger.info("Apache Ftp server is stoped!");}//容器初始化调用方法start ftpServerpublic void contextInitialized(ServletContextEvent sce) {
    //        WebApplicationContext ctx= WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
    //        MyFtpServer server=(MyFtpServer) ctx.getBean("MyFtp");sce.getServletContext().setAttribute(SERVER_NAME,server);try {//项目启动时已经加载好了server.start();logger.info("Apache Ftp server is started!");} catch (Exception e){e.printStackTrace();throw new RuntimeException("Apache Ftp server start failed!", e);}}}
  • 5、通过继承DefaultFtplet抽象类来实现一些自定义用户事件(我这里只是举例)

    import org.apache.ftpserver.ftplet.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;import java.io.IOException;public class MyFtpPlet extends DefaultFtplet {private static final Logger logger = LoggerFactory.getLogger(MyFtpPlet.class);@Overridepublic FtpletResult onUploadStart(FtpSession session, FtpRequest request)throws FtpException, IOException {//获取上传文件的上传路径String path = session.getUser().getHomeDirectory();//获取上传用户String name = session.getUser().getName();//获取上传文件名String filename = request.getArgument();logger.info("用户:'{}',上传文件到目录:'{}',文件名称为:'{}',状态:开始上传~", name, path, filename);return super.onUploadStart(session, request);}@Overridepublic FtpletResult onUploadEnd(FtpSession session, FtpRequest request)throws FtpException, IOException {//获取上传文件的上传路径String path = session.getUser().getHomeDirectory();//获取上传用户String name = session.getUser().getName();//获取上传文件名String filename = request.getArgument();logger.info("用户:'{}',上传文件到目录:'{}',文件名称为:'{},状态:成功!'", name, path, filename);return super.onUploadEnd(session, request);}@Overridepublic FtpletResult onDownloadStart(FtpSession session, FtpRequest request) throws FtpException, IOException {//todo servies...return super.onDownloadStart(session, request);}@Overridepublic FtpletResult onDownloadEnd(FtpSession session, FtpRequest request) throws FtpException, IOException {//todo servies...return super.onDownloadEnd(session, request);}}
  • 6、 配置springboot静态资源的访问

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
    public class FtpConfig implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {//可以通过os来判断String os = System.getProperty("os.name");//linux设置
    //        registry.addResourceHandler("/ftp/**").addResourceLocations("file:/home/pic/");//windows设置//第一个方法设置访问路径前缀,第二个方法设置资源路径,既可以指定项目classpath路径,也可以指定其它非项目路径registry.addResourceHandler("/ftp/**").addResourceLocations("file:D:\\apache-ftpserver-1.1.1\\res\\bxl-home\\");registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");}}
  • 7、以上6步已经完成ftpserver的配置,随着springboot项目的启动就会开启ftpserver服务,下面在给大家贴一下客户端的访问的util,大家可以自行封装一下即可。

    import org.apache.commons.net.ftp.FTPClient;
    import org.apache.commons.net.ftp.FTPFile;
    import org.apache.commons.net.ftp.FTPReply;
    import org.apache.commons.net.ftp.FTPSClient;import java.io.*;public class FtpClientUtil {// ftp服务器ip地址private static String FTP_ADDRESS = "localhost";// 端口号private static int FTP_PORT = 3131;// 用户名private static String FTP_USERNAME = "bxl";// 密码private static String FTP_PASSWORD = "123456";// 相对路径private static String FTP_BASEPATH = "";public static boolean uploadFile(String remoteFileName, InputStream input) {boolean flag = false;FTPClient ftp = new FTPClient();ftp.setControlEncoding("UTF-8");try {int reply;ftp.connect(FTP_ADDRESS, FTP_PORT);// 连接FTP服务器ftp.login(FTP_USERNAME, FTP_PASSWORD);// 登录reply = ftp.getReplyCode();System.out.println("登录ftp服务返回状态码为:" + reply);if (!FTPReply.isPositiveCompletion(reply)) {ftp.disconnect();return flag;}ftp.setFileType(FTPClient.BINARY_FILE_TYPE);//设置为被动模式ftp.enterLocalPassiveMode();ftp.makeDirectory(FTP_BASEPATH);ftp.changeWorkingDirectory(FTP_BASEPATH);//originFilePath就是上传文件的文件名,建议使用生成的唯一命名,中文命名最好做转码boolean a = ftp.storeFile(remoteFileName, input);
    //            boolean a = ftp.storeFile(new String(remoteFileName.getBytes(),"iso-8859-1"),input);System.out.println("要上传的原始文件名为:" + remoteFileName + ", 上传结果:" + a);input.close();ftp.logout();flag = true;} catch (IOException e) {e.printStackTrace();} finally {if (ftp.isConnected()) {try {ftp.disconnect();} catch (IOException ioe) {}}}return flag;}//    public static Boolean uploadFile(String remoteFileName, InputStream inputStream, String ftpAddress, int ftpPort,
    //                                     String ftpName, String ftpPassWord, String ftpBasePath) {
    //        FTP_ADDRESS = ftpAddress;
    //        FTP_PORT = ftpPort;
    //        FTP_USERNAME = ftpName;
    //        FTP_PASSWORD = ftpPassWord;
    //        FTP_BASEPATH = ftpBasePath;
    //        uploadFile(remoteFileName,inputStream);
    //        return true;
    //    }public static boolean deleteFile(String filename) {boolean flag = false;FTPClient ftpClient = new FTPClient();try {// 连接FTP服务器ftpClient.connect(FTP_ADDRESS, FTP_PORT);// 登录FTP服务器ftpClient.login(FTP_USERNAME, FTP_PASSWORD);// 验证FTP服务器是否登录成功int replyCode = ftpClient.getReplyCode();if (!FTPReply.isPositiveCompletion(replyCode)) {return flag;}// 切换FTP目录ftpClient.changeWorkingDirectory(FTP_BASEPATH);ftpClient.dele(filename);ftpClient.logout();flag = true;} catch (Exception e) {e.printStackTrace();} finally {if (ftpClient.isConnected()) {try {ftpClient.logout();} catch (IOException e) {}}}return flag;}public static boolean downloadFile(String filename, String localPath) {boolean flag = false;
    //        FTPSClient ftpClient = new FTPSClient("TLS", true);FTPClient ftpClient = new FTPClient();try {// 连接FTP服务器ftpClient.connect(FTP_ADDRESS, FTP_PORT);// 登录FTP服务器ftpClient.login(FTP_USERNAME, FTP_PASSWORD);// 验证FTP服务器是否登录成功int replyCode = ftpClient.getReplyCode();if (!FTPReply.isPositiveCompletion(replyCode)) {return flag;}// 切换FTP目录ftpClient.changeWorkingDirectory(FTP_BASEPATH);//此处为demo方法,正常应该到数据库中查询fileNameFTPFile[] ftpFiles = ftpClient.listFiles();for (FTPFile file : ftpFiles) {if (filename.equalsIgnoreCase(file.getName())) {File localFile = new File(localPath + "/" + file.getName());OutputStream os = new FileOutputStream(localFile);ftpClient.retrieveFile(file.getName(), os);os.close();}}ftpClient.logout();flag = true;System.out.println("文件下载完成!!!");} catch (Exception e) {e.printStackTrace();} finally {if (ftpClient.isConnected()) {try {ftpClient.logout();} catch (IOException e) {}}}return flag;}
    }

五、总结

到此,所有的配置已经完成,我们的业务系统也同时也承担了一个角色,那就是ftp服务器,整个配置是没有加入SSL/TLS安全机制的,大家如果感兴趣可以自行研究下。我代码中注释那那部分,只是注意下通过客户端访问时,需要使用FtpsCliet,而非FtpCliet。当然还需要配置你自己的ftpserver.jks文件,也就是java key store。百度下一下如何生成,很简单哦!

个人博客地址:

cnblogs:https://www.cnblogs.com/baixianlong/

github:https://github.com/xianlongbai

springboot整合apache ftpserver详细教程(看这一篇就够了)相关推荐

  1. mock详细教程入门这一篇就够了(*)

    前言 1.什么是mock测试 Mock 测试就是在测试活动中,对于某些不容易构造或不容易获取的比较复杂的数据/场景,用一个虚拟的对象(Mock对象)来创建用于测试的测试方法. 2.为什么要进行Mock ...

  2. SpringBoot整合MyBatis-Plus3.1详细教程

    一.说明 Mybatis-Plus是一个Mybatis框架的增强插件,根据官方描述,MP只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑.并且只需简单配置,即可快速进行 CRUD 操作,从而 ...

  3. 《SpringBoot篇》26.SpringBoot整合Jackson超详细教程(附Jackson工具类)

    陈老老老板

  4. Windows系统 电脑系统重装详细图文教程(绝对够详细,看这一篇就够)

    电脑装机教程    在装机之前,请备份好自己的宝贵的数据资料,一般装机会装在C盘,C盘在装机的时候会格式化,请注意自己的数据资料是否保存了!!    安装前准备 1.U盘一个(尽量使用8G以上的U盘) ...

  5. Linux_进程管理详解《鸟哥的Linux私房菜》学习笔记(极其详细,看完这篇就够了)

    前言 当一个程序被载入到内存中运行,那么在内存中的那个程序就被称为进程(process).进程是操作系统上非常重要的概念, 所有系统上面跑的数据都会以进程的形态存在. 那么系统的进程有哪些状态?不同的 ...

  6. Linux 系统基础 — 用户和组(吐血总结,超详细,看这一篇就够了!dog)

    Linux 用户和组 Linux中用户和组的相关文件 /etc/passwd /etc/shadow /etc/group Linux用户 Linux中的组 用户操作 useradd的更多使用方式 p ...

  7. bilibili小电视桌面天气站(esp8266+微信小程序控制)超详细,看这一篇就够了

    目录 ​编辑 一.前言 二.教程 1.项目结构 2.材料清单 3.模型设计 4.电路设计 5.代码设计 以下是资料链接 一.前言 这个项目是今年6月份就在开始做的,但是中途由于实习等一些事情耽搁了,一 ...

  8. 【C/C++】内存对齐(超详细,看这一篇就够了)

    目录 一.为什么要内存对齐 二.基本变量类型所占大小 三.影响内存对齐的情况 四.先说结论(重要) 五.举亿点例子(以下内容均实际运行过,质量保证) 例1:研究结构体最后一个成员内存对齐问题1 例2: ...

  9. Git 教程看这一篇就够了

    基础杂项 Git基础概念 * 客户端并不只是提取最新版本的文件快照,而是把代码仓库完整的镜像下来 * git核心本质上是一个键值对数据库.可以向该数据库插入任意类型的内容,他会返回一个键值,通过该值可 ...

  10. SpringBoot整合Mybatis超详细流程

    SpringBoot整合Mybatis超详细流程 文章目录 SpringBoot整合Mybatis超详细流程 前言 详细流程 0.引入Mybatis 1.创建数据 2.创建程序目录 3.理解后台访问流 ...

最新文章

  1. 深度学习常用数据集资源(计算机视觉领域)
  2. 一步一步学JVM-垃圾回收器
  3. 产品经理真的是「背锅侠」吗?
  4. C++map容器-构造和赋值
  5. css中变量_CSS中的变量
  6. ssh 脚本 空格字符转换
  7. Qt文档阅读笔记-QThreadPool官方解析及实例
  8. 32乘法运算_算术运算指令
  9. Hyperledger Fabric、Corda和以太坊对比
  10. 有的数字不适合作版本号
  11. 小程序停止html5音乐,微信小程序-音乐播放器+背景播放
  12. Nexus下载、安装与使用
  13. 【概念】椭球面在球面上的投影
  14. leaflet保存pm绘图geojson数据
  15. jle汇编_x86汇编指令集大全(带注释)
  16. 惊恐,还真有大神把地府后台管理系统做出来了,“阎王爷”疯狂点
  17. 制药企业的发展趋势--行业公司数据调研
  18. 山东大学软件质量保证与测试技术复习纲要
  19. [唐诗]秋夜喜遇王处士-王绩
  20. Citrix 知识中心Top10 - 2012年9月 包括KB、白皮书、补丁、演讲以及工具。

热门文章

  1. python中print输出变量_python中如何使用print函数?
  2. c++11总结19——std::mutex
  3. pegasus 简介
  4. LOL自制皮肤0-介绍我自己
  5. 联想计算机不能进入系统桌面,联想电脑无法重装系统-联想电脑怎么重装系统我的电脑启动后到不了桌面,最后屏幕上没有任何 爱问知识人...
  6. EXCEL将汉字转化为拼音简写
  7. C语言判断节日思路,C语言 程序设计 节日查询和任务提醒系统-万年历参考.doc
  8. 验证happenBefore
  9. 表格识别综述与相关实战
  10. migration php,Lavarel常用语句之Migration