开发FTP服务接口,对外提供接口服务
注意:本文只适合小文本文件的上传下载,因为post请求是有大小限制的。默认大小是2m,虽然具体数值可以调节,但不适合做大文件的传输
最近公司有这么个需求:以后所有的项目开发中需要使用ftp服务器的地方都不能直接操作ftp服务器,而是通过调用一个统一的接口去操作文件的上传下载等功能。
其实就是针对ftp服务器封装一个项目,所有的针对ftp的操作都通过这个项目来实现。
技术点:接口调用必然存在各种参数、错误信息、成功信息等。那么我们的文件要通过什么形式放到参数中在网络传递呢?
文件可以转换成字节流在网络中传输没毛病,但是转换成二进制流之后放到我们封装的对象中再次序列化在网络中传输就有问题了。
所以关键是将我们的文件以何种数据结构封装在我们的参数对象中在网络里传输。答案是 字节数组;
目录结构:
入参对象: Vsftpd
package com.ch.vsftpd.common.pojo; /**
* @Auther: 011336
* @Date: 2019/4/24 11:04
*/
public class Vsftpd {
/**
* 请求类型[必填]
* upload: 上传 文件已存在上传失败
* replace:上传 文件已存在则覆盖
* download: 下载
* display: 查看文件列表
* delete: 删除文件
*/
private String optionType;
/**
* 请求者在接口管理系统中维护的项目编码[必填]
*/
private String projectCode;
/**
* 上传/下载 文件名[非必填]
*/
private String fileName;
/**
* 上传/下载 文件的字节数组[非必填]
*/
private byte[] byteArry; ...
}
返回对象: VsftpResult
package com.ch.vsftpd.common.pojo; import java.util.List; /**
* @Auther: 011336
* @Date: 2019/4/24 11:03
*/
public class VsftpResult { private boolean status; private byte[] byteArry; private String[] fileNames; private List<ErrorInfo> errors; }
错误信息: ErrorInfo
package com.ch.vsftpd.common.pojo; /**
* @Auther: 011336
* @Date: 2019/3/28 11:32
*/
public class ErrorInfo { private String errorCode; private String errorMsg; public ErrorInfo() {
} public ErrorInfo(String errorCode) {
this.errorCode = errorCode;
} public ErrorInfo(String errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}
对外提供接口: FtpController
/**
* ftp服务,对外提供统一接口地址,通过控制传递的参数实现 上传、覆盖上传、获取文件列表和下载4个功能
* 具体参数参考【Vsftpd.java, FtpConstants】
* @before AuthorityInterceptor.java 拦截器做权限校验
* @Auther: 011336
* @Date: 2019/4/24 11:21
*/
@Controller
@RequestMapping("/vsftpdService")
public class ftpController { private static Logger LOGGER = LoggerFactory.getLogger(ftpController.class); @ResponseBody
@RequestMapping(path = "/vsftpd", method = RequestMethod.POST)
public VsftpResult getAuthInfo(@RequestBody Vsftpd vsftpd){
LOGGER.info("ftpController.getAuthInfo start");
IFtpUploadStrategy strategy = null;
List<ErrorInfo> errors = new ArrayList<>();
VsftpResult result = new VsftpResult();
//第一步校验参数是否合法
if (StringUtils.isEmpty(vsftpd.getOptionType())) {
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","调用参数[type]不能为空!");
errors.add(errorInfo);
}
if (StringUtils.isEmpty(vsftpd.getProjectCode())) {
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","参数[projectCode]不能为空!");
errors.add(errorInfo);
}
//根据请求类型使用不同策略
if(FtpConstants.UP_LOAD.equals(vsftpd.getOptionType())){
strategy = new FtpUploadStrategy();
}else if(FtpConstants.REPLACE.equals(vsftpd.getOptionType())){
strategy = new FtpUploadStrategy();
}else if(FtpConstants.DOWAN_LOAD.equals(vsftpd.getOptionType())){
strategy = new FtpDownLoadStrategy();
}else if(FtpConstants.DISPLAY.equals(vsftpd.getOptionType())){
strategy = new FtpDisplayStrategy();
}else if(FtpConstants.DELETE.equals(vsftpd.getOptionType())){
strategy = new FtpDeleteStrategy();
}else {
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","调用参数[type]错误!");
errors.add(errorInfo);
}
if (CollectionUtils.isEmpty(errors)) {
result = strategy.vsftpMethod(vsftpd);
}else{
result.setStatus(false);
result.setErrors(errors);
}
return result;
}
}
四种策略:
文件上传: FtpUploadStrategy
/**
* 文件上传
* 通过参数vsftpd.getOptionType()分为普通上传 和 覆盖上传两种
* 第一种若文件已存在则返回错误信息提示文件已存在
* 第二种则直接覆盖
* @Auther: 011336
* @Date: 2019/4/24 10:59
*/
public class FtpUploadStrategy implements IFtpUploadStrategy { private static Logger LOGGER = LoggerFactory.getLogger(FtpUploadStrategy.class); @Override
public VsftpResult vsftpMethod(Vsftpd vsftpd){
LOGGER.info("FtpUploadStrategy.vsftpMethod start");
VsftpResult result = new VsftpResult();
List<ErrorInfo> errors = new ArrayList<>();
if (StringUtils.isEmpty(vsftpd.getFileName())) {
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","参数[fileName]不能为空!");
errors.add(errorInfo);
}
if (vsftpd.getByteArry()==null) {
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","参数[byteArry]不能为空!");
errors.add(errorInfo);
}
//当不强制上传的时候 文件若已存在则上传失败
boolean flag = false;
try {
if(FtpConstants.UP_LOAD.equals(vsftpd.getOptionType())) {
//判断文件是否存在
boolean b = FtpUtil.fileExist(vsftpd.getProjectCode(), vsftpd.getFileName());
if (b) {
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL", "文件[" + vsftpd.getFileName() + "]已存在!");
errors.add(errorInfo);
}
}
//上传文件(文件若存在则覆盖)
if(CollectionUtils.isEmpty(errors)){
flag = FtpUtil.uploadFile(vsftpd.getFileName(),vsftpd.getByteArry(),vsftpd.getOptionType());
}
} catch (Exception e) {
e.printStackTrace();
ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","下载失败!服务端异常!");
errors.add(errorInfo);
}
if(!flag){
ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","上传失败!系统异常!");
errors.add(errorInfo);
}
if(CollectionUtils.isEmpty(errors)){
result.setStatus(true);
}else{
result.setStatus(false);
result.setErrors(errors);
}
LOGGER.info("FtpUploadStrategy.vsftpMethod end");
return result;
}
}
文件下载: FtpDownLoadStrategy
/**
* 文件下载
* 若文件不存在 返回文件不存在的错误信息
* @Auther: 011336
* @Date: 2019/4/24 10:59
*/
public class FtpDownLoadStrategy implements IFtpUploadStrategy { private static Logger LOGGER = LoggerFactory.getLogger(FtpDownLoadStrategy.class); @Override
public VsftpResult vsftpMethod(Vsftpd vsftpd){
LOGGER.info("FtpDownLoadStrategy.vsftpMethod start");
VsftpResult result = new VsftpResult();
List<ErrorInfo> errors = new ArrayList<>();
byte[] arryArry = null;
if (StringUtils.isEmpty(vsftpd.getFileName())) {
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","参数[fileName]不能为空!");
errors.add(errorInfo);
}
//判断文件是否存在
try {
boolean b = FtpUtil.fileExist(vsftpd.getProjectCode(),vsftpd.getFileName());
if (!b){
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","文件[fileName]不存在!");
errors.add(errorInfo);
}
//下载
if(CollectionUtils.isEmpty(errors)){
arryArry = FtpUtil.downloadFile(vsftpd.getProjectCode(),vsftpd.getFileName());
}
} catch (Exception e) {
e.printStackTrace();
ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","下载失败!服务端异常!");
errors.add(errorInfo);
}
if(arryArry == null){
ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","下载失败!系统异常!");
errors.add(errorInfo);
}
if(CollectionUtils.isEmpty(errors)){
result.setStatus(true);
result.setByteArry(arryArry);
}else{
result.setStatus(false);
result.setErrors(errors);
}
LOGGER.info("FtpDownLoadStrategy.vsftpMethod end");
return result;
}
}
文件删除: FtpDeleteStrategy
/**
* 文件删除
* 文件若不存在则返回删除成功
* @Auther: 011336
* @Date: 2019/4/24 10:59
*/
public class FtpDeleteStrategy implements IFtpUploadStrategy { private static Logger LOGGER = LoggerFactory.getLogger(FtpDeleteStrategy.class); @Override
public VsftpResult vsftpMethod(Vsftpd vsftpd){
LOGGER.info("FtpDeleteStrategy.vsftpMethod start");
VsftpResult result = new VsftpResult();
List<ErrorInfo> errors = new ArrayList<>();
if (StringUtils.isEmpty(vsftpd.getFileName())) {
ErrorInfo errorInfo = new ErrorInfo("PARAMETER.FAIL","参数[fileName]不能为空!");
errors.add(errorInfo);
}
//删除文件
try {
if(CollectionUtils.isEmpty(errors)){
FtpUtil.deleteFile(vsftpd.getProjectCode(),vsftpd.getFileName());
}
} catch (Exception e) {
e.printStackTrace();
ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","删除失败!服务端异常!");
errors.add(errorInfo);
} if(CollectionUtils.isEmpty(errors)){
result.setStatus(true);
}else{
result.setStatus(false);
result.setErrors(errors);
}
LOGGER.info("FtpDeleteStrategy.vsftpMethod end");
return result;
}
}
获取文件列表:FtpDisplayStrategy
/**
* 获取文件列表
* @Auther: 011336
* @Date: 2019/4/24 10:59
*/
public class FtpDisplayStrategy implements IFtpUploadStrategy { private static Logger LOGGER = LoggerFactory.getLogger(FtpDisplayStrategy.class); @Override
public VsftpResult vsftpMethod(Vsftpd vsftpd){
LOGGER.info("FtpDisplayStrategy.vsftpMethod start");
VsftpResult result = new VsftpResult();
List<ErrorInfo> errors = new ArrayList<>();
String[] fileNames = new String[0];
try {
fileNames = FtpUtil.displayFile(vsftpd.getProjectCode());
} catch (Exception e) {
e.printStackTrace();
ErrorInfo errorInfo = new ErrorInfo("FTP.ERROR","获取文件列表失败!服务端异常!");
errors.add(errorInfo);
}
if(CollectionUtils.isEmpty(errors)){
result.setStatus(true);
result.setFileNames(fileNames);
}else{
result.setStatus(false);
result.setErrors(errors);
}
LOGGER.info("FtpDisplayStrategy.vsftpMethod end");
return result;
}
}
提供一个策略接口:
/**
* @Auther: 011336
* @Date: 2019/4/24 11:21
*/
public interface IFtpStrategy {
/**
* 统一处理用户请求
* @param vsftpd
* @return
*/
VsftpResult vsftpMethod(Vsftpd vsftpd);
}
最主要是我们的 FtpUtil 工具类
/**
* @author 王未011336
* @date 2018/11/04
* ftp服务器文件上传下载
*/
public class FtpUtil {
private static Logger LOGGER = LoggerFactory.getLogger(FtpUtil.class);
private static String LOCAL_CHARSET = "GBK";
private static String SERVER_CHARSET = "ISO-8859-1";
private static String host;
private static String port;
private static String username;
private static String password;
private static String basePath;
private static String filePath;
private static String localPath; /**
*读取配置文件信息
* @return
*/
public static void getPropertity(){
Properties properties = new Properties();
ClassLoader load = FtpUtil.class.getClassLoader();
InputStream is = load.getResourceAsStream("conf/vsftpd.properties");
try {
properties.load(is);
host=properties.getProperty("vsftpd.ip");
port=properties.getProperty("vsftpd.port");
username=properties.getProperty("vsftpd.user");
password=properties.getProperty("vsftpd.pwd");
//服务器端 基路径
basePath=properties.getProperty("vsftpd.remote.base.path");
//服务器端 文件路径
filePath=properties.getProperty("vsftpd.remote.file.path");
//本地 下载到本地的目录
localPath=properties.getProperty("vsftpd.local.file.path");
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 上传重载
* @param filename 上传到服务器端口重命名
* @param buffer byte[] 文件流
* @return
*/
public static boolean uploadFile(String filename, byte[] buffer, String optionType) throws Exception{
getPropertity();
return uploadFile( host, port, username, password, basePath,
filePath, filename, buffer, optionType);
} /**
* 获取文件列表
* @param filePath
* @return
* @throws Exception
*/
public static String[] displayFile(String filePath) throws Exception{
getPropertity();
return displayFile(host, port, username, password, basePath, filePath);
} /**
* 删除文件
* @param filePath
* @return
*/
public static boolean deleteFile(String filePath, String fileName) throws Exception{
getPropertity();
return deleteFile(host, port, username, password, basePath, filePath, fileName);
} /**
* 判断文件是否存在
* @param filePath
* @return
* @throws Exception
*/
public static boolean fileExist(String filePath,String filename) throws Exception{
if(StringUtils.isEmpty(filename)){
return false;
}
getPropertity();
String[] names = displayFile(filePath);
for (String name : names) {
if(filename.equals(name)){
return true;
}
}
return false;
} /**
*下载重载
* @param filePath 要下载的文件所在服务器的相对路径
* @param fileName 要下载的文件名
* @return
*/
public static byte[] downloadFile(String filePath,String fileName) throws Exception{
getPropertity();
return downloadFile( host, port, username, password, basePath,
filePath, fileName);
} /**
* Description: 向FTP服务器上传文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath
* @param fileName 上传到FTP服务器上的文件名
* @return 成功返回true,否则返回false
*/
public static boolean uploadFile(String host, String port, String username, String password, String basePath,
String filePath, String fileName, byte[] buffer, String optionType) throws Exception{
FTPClient ftp = new FTPClient();
try {
fileName = new String(fileName.getBytes(LOCAL_CHARSET));
boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath);
if(!result){
return result;
}
//为了加大上传文件速度,将InputStream转成BufferInputStream , InputStream input
InputStream inputStream = new ByteArrayInputStream(buffer);
//加大缓存区
ftp.setBufferSize(1024*1024);
//设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
if(FtpConstants.REPLACE.equals(optionType)){
ftp.deleteFile(fileName);
}
//上传文件
if (!ftp.storeFile(fileName, inputStream)) {
return false;
}
inputStream.close();
ftp.logout();
} catch (IOException e) {
LOGGER.info(e.getMessage());
e.printStackTrace();
throw e;
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return true;
} /**
* Description: 从FTP服务器下载文件
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器上的相对路径
* @param fileName 要下载的文件名
* @return
*/
public static byte[] downloadFile(String host, String port, String username, String password, String basePath,
String filePath, String fileName) throws Exception{
FTPClient ftp = new FTPClient();
try {
fileName = new String(fileName.getBytes(LOCAL_CHARSET));
boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath);
if(!result){
return null;
}
FTPFile[] fs = ftp.listFiles();
boolean flag = true;
for (FTPFile ff : fs) {
if (ff.getName().equals(fileName)) {
InputStream input = ftp.retrieveFileStream(ff.getName());
BufferedInputStream in = new BufferedInputStream(input);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while((len = in.read(buffer)) != -1){
outStream.write(buffer, 0, len);
}
outStream.close();
in.close();
byte[] arryArry = outStream.toByteArray();
return arryArry;
}
}
if(flag) {
LOGGER.info("服务器端文件不存在...");
return null;
}
ftp.logout();
} catch (IOException e) {
LOGGER.info(e.getMessage());
e.printStackTrace();
throw e;
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return null;
} /**
* 获取服务器文件名列表
* @param host
* @param port
* @param username
* @param password
* @param basePath
* @param filePath
* @return
* @throws Exception
*/
public static String[] displayFile(String host, String port, String username, String password, String basePath,
String filePath) throws Exception{
FTPClient ftp = new FTPClient();
try {
boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath);
if(!result){
return null;
}
String[] names = ftp.listNames();
ftp.logout();
return names;
} catch (IOException e) {
LOGGER.info(e.getMessage());
e.printStackTrace();
throw e;
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
} /**
* 删除文件
* @param host
* @param port
* @param username
* @param password
* @param basePath
* @param filePath
* @return
*/
public static boolean deleteFile(String host, String port, String username, String password, String basePath,
String filePath,String fileName) throws Exception{
FTPClient ftp = new FTPClient();
boolean b = false;
try {
boolean result = connectFtp(ftp, host, port, username, password, basePath, filePath);
if(!result){
return b;
}
b = ftp.deleteFile(fileName);
ftp.logout();
} catch (IOException e) {
LOGGER.info(e.getMessage());
e.printStackTrace();
throw e;
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return b;
} /**
* 连接ftp服务器并切换到目的目录
* 调用此方法需手动关闭ftp连接
* @param ftp
* @param host
* @param port
* @param username
* @param password
* @param basePath
* @param filePath
* @return
*/
private static boolean connectFtp( FTPClient ftp,String host, String port, String username,
String password, String basePath, String filePath) throws Exception{
boolean result = false;
try {
int portNum = Integer.parseInt(port);
int reply;
// 连接FTP服务器
ftp.connect(host, portNum);
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
if (FTPReply.isPositiveCompletion(ftp.sendCommand("OPTS UTF8", "ON"))) {
LOCAL_CHARSET = "UTF-8";
}
ftp.setControlEncoding(LOCAL_CHARSET);
//切换到上传目录
if (!ftp.changeWorkingDirectory(basePath+filePath)) {
//如果目录不存在创建目录
String[] dirs = filePath.split("/");
String tempPath = basePath;
for (String dir : dirs) {
if (null == dir || "".equals(dir)) {
continue;
}
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}
}
result = true;
} catch (IOException e) {
LOGGER.info(e.getMessage());
e.printStackTrace();
throw e;
}
return result;
}
}
拦截器权限校验这边涉及到太多业务,读者可止步于此,但我还是要贴一下代码
配置拦截器: FilterManager
/**
* @Auther: 011336
* @Date: 2019/3/28 19:17
*/
@Configuration
public class FilterManager implements WebMvcConfigurer { @Autowired
private AuthorityInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");
}
}
拦截器具体实现: AuthorityInterceptor
/**
* 拦截器做权限验证
* 权限信息接SI接口管理系统,调用者需提供白名单用户名,和白名单密码(MD5加密)
* 本服务根据调用者在SI中的项目编码projectCode为调用者创建独立目录地址
* requestUri 接口地址
* onlineInterfaceCode 接口线上编码
* 从SI同步而来的白名单信息包含此接口的线上接口编码onlineInterfaceCode,本项目维护了onlineInterfaceCode与requestUri之间的对应关系
* 目的:使系统兼容提供多个接口的情况
* @Auther: 011336
* @Date: 2019/4/28 19:11
*/
@Component
public class AuthorityInterceptor implements HandlerInterceptor { private static Logger LOGGER = LoggerFactory.getLogger(AuthorityInterceptor.class); @Autowired
private IWhiteListService whiteListService;
/**
* 拦截器做权限验证
* @param request
* @param response
* @param handler
* @return
* @author 011336
* @date 2019/04/24
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
String whiteListUsername = request.getHeader("whiteListUsername");
String whiteListPassword = request.getHeader("whiteListPassword");
String projectCode = request.getHeader("projectCode");
if(StringUtils.isNotEmpty(projectCode)){
Optional<String> userName = Optional.ofNullable(whiteListUsername);
Optional<String> passWord = Optional.ofNullable(whiteListPassword);
String requestUri = request.getRequestURI();
Map<String,Object> condition = new HashMap<>();
condition.put("requestUri",requestUri);
condition.put("projectCode",projectCode);
WhiteList whiteList = whiteListService.query(condition);
Optional<WhiteList> whiteOptional = Optional.ofNullable(whiteList);
WhiteList white = whiteOptional.orElse(new WhiteList());
if(userName.orElse(UUID.randomUUID().toString()).equals(white.getWhiteListUsername()) &&
passWord.orElse(UUID.randomUUID().toString()).equals(white.getWhiteListPassword())) {
LOGGER.info("["+projectCode+"]权限认证成功!");
return true;
}
}
//重置response
response.reset();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
PrintWriter pw = response.getWriter();
VsftpResult result = new VsftpResult();
result.setStatus(false);
ErrorInfo errorInfo = new ErrorInfo("AUTHORITY.FAIL", "权限验证失败!");
List<ErrorInfo> errors = new ArrayList<>();
errors.add(errorInfo);
result.setErrors(errors);
pw.write(JsonUtils.objectToJson(result));
pw.flush();
pw.close();
LOGGER.info("["+projectCode+"]权限认证失败!"+ JsonUtils.objectToJson(result));
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler,ModelAndView model) throws Exception{
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler,Exception ex) throws Exception{
}
}
现这样吧...心情不好
开发FTP服务接口,对外提供接口服务的更多相关文章
- springboot+CXF开发webservice对外提供接口(转)
文章来源:http://www.leftso.com/blog/144.html 1.项目要对外提供接口,用webservcie的方式实现 2.添加的jar包 maven: <dependenc ...
- 使用WCF对外提供接口
本篇将通过WCF以webservices的方式对外提供接口.同时使用NUnit对webservices中的方法进行单元测试. 开发契约 contract Contract项目为类库项目,该项目下会包含 ...
- Frp内网穿透搭建,家庭主机对外提供接口,支持ssh访问
Frp内网穿透搭建,家庭主机对外提供接口,支持ssh访问 1.使用场景: 需求1.家中服务器 ubuntu 主机,跑接口服务,需要对外暴漏, 需求2.同时需要在外网ssh远程 关键词: frp内网 ...
- grpc-gateway:grpc对外提供http服务的解决方案
我所在公司的项目是采用基于Restful的微服务架构,随着微服务之间的沟通越来越频繁,就希望可以做成用rpc来做内部的通讯,对外依然用Restful.于是就想到了google的grpc. 使用grpc ...
- Java服务器对外提供接口以及Android端向服务器请求数据
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/5056780.html 讲解下java服务器是如何对移动终端提供接口的,以什么数据格式提供出去,移动端又是怎么 ...
- WPF内嵌WCF服务对外提供接口
要测试本帖子代码请记得管理员权限运行vs. 我写这个帖子的初衷是在我做surface小车的时候有类似的需求,感觉这个功能还挺有意思的,所以就分享给大家,网上有很多关于wcf的文章 我就不一一列举了.公 ...
- windows服务中对外提供API接口
public class SendMqService { private static bool isExcute = true; private static HttpListener listen ...
- Windows 端口和所提供的服务
一 .端口大全 端口:0 服务:Reserved 说明:通常用于分析操作系统.这一方法能够工作是因为在一些系统中“0”是无效端口,当你试图使用通常的闭合端口连接它时将产生不同的结果.一种典型的扫描,使 ...
- C++中模块(Dll)对外暴露接口的方式
总结下C++中模块(Dll)对外暴露接口的方式: (1)导出API函数的方式这种方式是Windows中调用DLL接口的最基本方式,GDI32.dll, User32.dll都是用这种方式对外暴露系统A ...
随机推荐
- 深入理解JVM(一)--Java 内存区域
一. 运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. Java虚拟机所管理的内存将会包括以下几个运行时数据区域: ...
- Oracle11G DG 搭建及管理
一.准备工作 环境准备 主数据库Oracle Database安装 备服务器Oracle Database software 安装 二.正式配置 三.基本管理 -------------------- ...
- php curl 检测网页是否被百度收录
<?php /* * 检测百度是否收录网页 curl模式 * @ param string $url传入的url* return int (1 收录 0 不收录) */ function che ...
- RTEMS进程同步机制
互斥量 好像没有互斥量,信号量接收那儿有个图,互斥量似乎术语一类特殊的信号量. 信号量 12. Semaphore Manager 12.1. Introduction The semaphore m ...
- Linux Shell 计算脚本执行过程用了多长时间
#!/bin/bash starttime=`date +'%Y-%m-%d %H:%M:%S'` #执行程序 endtime=`date +'%Y-%m-%d %H:%M:%S'`start_sec ...
- Day2前端学习之路——HTML基本知识
课程目标: 通过制作自己的简历,更加清楚地了解HTML是什么,HTML5是什么.学习基本的HTML标签,理解HTML语义化概念 任务一:回答问题 1.HTML是什么,HTML5是什么? HTML是一种 ...
- oracle表空间位置迁移
1.先登录sqlplus: C:\Documents and Settings\jbdu>sqlplus “/as sysdba”2.修改表空间为Offline: SQL> alter t ...
- Linux运维---1.磁盘相关知识
一 磁盘物理结构 (1) 盘片:硬盘的盘体由多个盘片叠在一起构成. 在硬盘出厂时,由硬盘生产商完成了低级格式化(物理格式化),作用是将空白的盘片(Platter)划分为一个个同圆心.不同半径的磁道(T ...
- 回到未来:Smalltalk 编程系统
Smalltalk 是19世纪70年代由 Alan Kay 设计的,第一个以面向对象(Object-Orientation)为主要范式的编程语言 1.Smalltalk 具有大量首创的特性,深刻影响了 ...
- leaflet结合turf.js实现多边形分割(附源码下载)
前言 leaflet 入门开发系列环境知识点了解: leaflet api文档介绍,详细介绍 leaflet 每个类的函数以及属性等等 leaflet 在线例子 leaflet 插件,leaflet ...