很多时候大家都需要用到在线文档编辑服务之类的东西,今天就以WPS的在线编辑为例。【目前仅支持以企业形式申请】
链接WPS开放平台。
效果图如下
用到示例https://github.com/guolaopi/WPSOnlineDemo
改过之后的后台代码示例:
[Route("api/[controller]/[Action]")]
[ApiController]
public class WpsController : ControllerBase
{
private ILogger _logger;
private SessionManager _sessionManager;
private IWebHostEnvironment _webHostEnvironment;
private readonly UserService _userService;
public WpsController(ILogger logger, SessionManager sessionManager, IWebHostEnvironment webHostEnvironment, UserService userService)
{
_logger = logger;
_sessionManager = sessionManager;
_webHostEnvironment = webHostEnvironment;
_userService = userService;
}
///
/// 简单过滤一下HttpRequest参数
///
///
///
private RequestParam FilterRequestForWPS(HttpRequest Request)
{
var result = new RequestParam();
result.FileId = Request.Headers["x-weboffice-file-id"].ToString();
var queryStr = Request.QueryString.ToString();
queryStr = queryStr.StartsWith("?") ? queryStr.Substring(1) : queryStr;
if (string.IsNullOrEmpty(queryStr) || string.IsNullOrEmpty(result.FileId))
{
return new RequestParam
{
code = 403,
msg = "参数错误,无法打开文件",
Status = false
};
}
// url参数序列化成Dictionary
result.Params = queryStr.Split("&", StringSplitOptions.RemoveEmptyEntries).ToDictionary(p => p.Split("=")[0], p => p.Split("=")[1]);
// 此处判断是否传递了自定义的 _w_userId 参数,如果不需要此参数的话可以注释该判断
if (!result.Params.ContainsKey("_w_userId"))
{
return new RequestParam
{
code = 403,
msg = "用户异常",
Status = false
};
}
return result;
}
///
/// 生成iframe用的url(此方法非WPS官方的,主要是为了签名,也可以自己实现)
///
///
///
[Route("/api/wps/genarate")]
[HttpPost]
[Authorize]
[RoleAuthorize]
public Task GenarateWPSUrl(GenarateRequest request)
{
try
{
//请求文件是否存在
if (string.IsNullOrEmpty(request.FileName) || !System.IO.File.Exists($"{_webHostEnvironment.WebRootPath}/{request.FilePath}/{request.FileName}"))
return Task.Run(() =>
{
return new GenarateResult { Url = null };
});
else
{
return Task.Run(() =>
{
var url = WPSSignatureHelper.GenarateUrl(request.FileId,
request.FileType,
new Dictionary {
{ "_w_userId", _sessionManager.CurrUser.Id.ToString() },
{ "_w_fileName", request.FileName },
{ "_w_readOnly", request.ReadOnly },
{ "_w_filePath", request.FilePath }
});
// 上面的写法是在生成的url中带了两个自定义参数 _w_userId 和 _w_fileName,可以根据业务自己扩展,生成url是这样的:
// https://wwo.wps.cn/office/w/123?_w_appid=123456&_w_fileName=x.docx&_w_userId=5024&_w_signature=xxxxx
_logger.LogInformation($"生成URL:{url}");
// 也可以不写自定义参数,这样生成的url会只有 _w_appId 和 _w_ signatrue,例如:https://wwo.wps.cn/office/w/123?_w_appid=123456&_w_signature=xxxxx
//var url = WPSHelper.GenarateUrl(request.FileId,request.FileType);
return new GenarateResult { Url = url };
});
}
}
catch (Exception c)
{
_logger.LogError(c, "签名Url出错");
return Task.Run(() =>
{
return new GenarateResult { Url = null };
});
}
}
#region WPS官方要求实现的回调接口
///
/// 获取文件元数据
///
///
[Route("/v1/3rd/file/info")]
[HttpGet]
public FileInfoResult FileInfo()
{
// 简单的过滤下不合理的请求,可注释,以下接口基本上都有此过滤
try
{
var request = FilterRequestForWPS(Request);
if (!request.Status)
{
return new FileInfoResult { code = request.code, msg = request.msg };
}
// 获取自定义参数
var userId = request.Params["_w_userId"].ToString();
var fileName = HttpUtility.UrlDecode(request.Params["_w_fileName"].ToString());
var filePath = $"{_webHostEnvironment.WebRootPath}/{request.Params["_w_filePath"]}";
var readOnly = bool.Parse(request.Params["_w_readOnly"].ToString());
// 从数据库查询用户名、文件 等信息......
var files = new FileInfo($"{filePath}/{fileName}");//文件信息
var _user = _userService.GetSessionUserAsync(int.Parse(userId)).Result;
// 创建时间和修改时间默认全是现在,可更改,但是注意时间戳是11位的(秒)
//var now = TimestampHelper.GetCurrentTimestamp();
var downLoadUrl = $"{WPSSignatureHelper.AppRequest}/{request.Params["_w_filePath"]}/{fileName}";
var result = new FileInfoResult
{
file = new WPSFile
{
id = request.FileId,
name = files.Name,
version = 1,
size = int.Parse(files.Length.ToString()), // WPS单位是B
create_time = TimestampHelper.GetCurrentTimestamp(DateTime.Parse(files.CreationTime.ToString())),
creator = _user.NickName,
modify_time = TimestampHelper.GetCurrentTimestamp(DateTime.Parse(files.LastWriteTime.ToString())),
modifier = _user.NickName,
download_url = downLoadUrl,
user_acl = new User_acl
{
history = 1, // 允许查看历史版本
rename = 1, // 允许重命名
copy = 1 // 允许复制
},
watermark = new Watermark
{
type = 0, // 1为有水印
value = "水印文字"
}
},
user = new UserForFile()
{
id = userId,
name = _user.NickName,
permission = readOnly ? "read" : "write", // write为允许编辑,read为只能查看
avatar_url = "https://blogadmin.changqingmao.com/upload/95d5ba28-f627-437d-935a-1e51b433816b.jpg",//头像url
}
};
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "异常");
return new FileInfoResult { code = 400, msg = ex.Message };
}
}
///
/// 获取用户信息
///
/// 包含一个名为ids的字符串数组,里面是用户id
///
[Route("/v1/3rd/user/info")]
[HttpPost]
public Task GetUserInfo(GetUserInfoRequest body)
{
return Task.Run(() =>
{
var request = FilterRequestForWPS(Request);
if (!request.Status)
{
return new UserModel();
}
var userId = request.Params["_w_userId"].ToString();
var _user = _userService.GetSessionUserAsync(int.Parse(userId)).Result;
var result = new UserModel
{
id = _user.Id.ToString(),
name = _user.NickName,
avatar_url = "https://blogadmin.changqingmao.com/upload/95d5ba28-f627-437d-935a-1e51b433816b.jpg"
};
return result;
});
}
///
/// 通知此文件目前有哪些人正在协作
///
///
///
[Route("/v1/3rd/file/online")]
[HttpPost]
public Task Online(GetUserInfoRequest body)
{
return Task.Run(() =>
{
var result = new WPSBaseModel
{
code = 200,
msg = "success"
};
return result;
});
}
///
/// 上传文件新版本(保存文件)
///
/// 传来的文件流
///
[Route("/v1/3rd/file/save")]
[HttpPost]
public Task SaveFile(IFormFile file)
{
return Task.Run(async () =>
{
try
{
var request = FilterRequestForWPS(Request);
if (!request.Status)
{
return new SaveFileResult { code = request.code, msg = request.msg };
}
//保存的文件名
using (var stream = System.IO.File.Create($"{_webHostEnvironment.WebRootPath}/{request.Params["_w_filePath"]}/{HttpUtility.UrlDecode(request.Params["_w_fileName"])}"))
{
await file.CopyToAsync(stream);
}
var result = new SaveFileResult
{
file = new WPSFileModel
{
download_url = $"{WPSSignatureHelper.AppRequest}/{request.Params["_w_filePath"]}/{request.Params["_w_fileName"]}",
id = request.FileId,
name = request.Params["_w_fileName"].ToString()
}
};
return result;
}
catch (Exception ex)
{
_logger.LogError("save file failed: ", ex);
return new SaveFileResult { code = 403, msg = "保存出现异常" };
}
});
}
///
/// 获取特定版本的文件信息
///
/// 版本号
///
[Route("/v1/3rd/file/version")]
[HttpGet]
public Task Version(int version)
{
return Task.Run(() =>
{
var request = FilterRequestForWPS(Request);
if (!request.Status)
{
return new GetFileByVersionResult { code = request.code, msg = request.msg };
}
//获取参数
var userId = request.Params["_w_userId"].ToString();
var fileName = HttpUtility.UrlDecode(request.Params["_w_fileName"].ToString());
var filePath = $"{_webHostEnvironment.WebRootPath}/{request.Params["_w_filePath"]}";
// 从数据库查询文件信息......
var files = new FileInfo($"{filePath}/{fileName}");
var _user = _userService.GetSessionUserAsync(int.Parse(userId)).Result;
// 创建时间和修改时间默认全是现在
//var now = TimestampHelper.GetCurrentTimestamp();
var result = new GetFileByVersionResult
{
id = request.FileId,
name = fileName,
version = 1,
size = int.Parse(files.Length.ToString()),
create_time = TimestampHelper.GetCurrentTimestamp(DateTime.Parse(files.CreationTime.ToString())),
creator = _user.NickName,
modify_time = TimestampHelper.GetCurrentTimestamp(DateTime.Parse(files.LastWriteTime.ToString())),
modifier = _user.NickName,
download_url = $"{WPSSignatureHelper.AppRequest}/{request.Params["_w_filePath"]}/{fileName}"
};
return result;
});
}
///
/// 文件重命名
///
/// 包含一个name的字符串属性,值为保存的新文件名
///
[Route("/v1/3rd/file/rename")]
[HttpPut]
public Task RenameFile(RenameFileRequest body)
{
return Task.Run(() =>
{
var request = FilterRequestForWPS(Request);
if (!request.Status)
{
return new WPSBaseModel { code = request.code, msg = request.msg };
}
var result = new WPSBaseModel
{
code = 200,
msg = "success"
};
return result;
});
}
///
/// 获取所有历史版本文件信息
///
///
///
[Route("/v1/3rd/file/history")]
[HttpPost]
public Task GetHistory(GetHistoryRequest body)
{
return Task.Run(() =>
{
var request = FilterRequestForWPS(Request);
if (!request.Status)
{
return new GetHistoryResult { code = request.code, msg = request.msg };
}
//获取参数
var userId = request.Params["_w_userId"].ToString();
var fileName = HttpUtility.UrlDecode(request.Params["_w_fileName"].ToString());
var filePath = $"{_webHostEnvironment.WebRootPath}/{request.Params["_w_filePath"]}";
// 从数据库查询文件信息......
var files = new FileInfo($"{filePath}/{fileName}");
// 创建时间和修改时间默认全是现在
//var now = TimestampHelper.GetCurrentTimestamp();
var _user = _userService.GetSessionUserAsync(int.Parse(userId)).Result;
// 不需要使用历史版本功能的此处也请返回,如果此接口不通时,文档加载会报错:“GetFileInfoFailed”
var result = new GetHistoryResult
{
histories = new List
{
new HistroyModel
{
id=request.FileId,
name=fileName,
size=int.Parse(files.Length.ToString()), // 单位B
version=1,
download_url = $"{WPSSignatureHelper.AppRequest}/{request.Params["_w_filePath"]}/{fileName}",
create_time=TimestampHelper.GetCurrentTimestamp(DateTime.Parse(files.CreationTime.ToString())),
modify_time=TimestampHelper.GetCurrentTimestamp(DateTime.Parse(files.LastWriteTime.ToString())),
creator=new UserModel
{
id=_user.Id.ToString(),
name=_user.NickName,
avatar_url = "https://blogadmin.changqingmao.com/upload/95d5ba28-f627-437d-935a-1e51b433816b.jpg"
},
modifier=new UserModel
{
id=_user.Id.ToString(),
name=_user.NickName,
avatar_url = "https://blogadmin.changqingmao.com/upload/95d5ba28-f627-437d-935a-1e51b433816b.jpg"
}
}
}
};
return result;
});
}
///
/// 新建文件
///
///
[Route("/v1/3rd/file/new")]
[HttpPost]
public Task NewFile(CreateWPSFileRequest request)
{
return Task.Run(async () =>
{
try
{
var filterRequest = FilterRequestForWPS(Request);
if (!filterRequest.Status)
{
return new CreateWPSFileResult { code = filterRequest.code, msg = filterRequest.msg };
}
var userId = filterRequest.Params["_w_userId"].ToString();
var _user = _userService.GetSessionUserAsync(int.Parse(userId)).Result;
using (var stream = System.IO.File.Create($"{filterRequest.Params["_w_fileName"]}"))
{
await request.file.CopyToAsync(stream);
}
var result = new CreateWPSFileResult
{
redirect_url = $"{WPSSignatureHelper.AppRequest}/{filterRequest.Params["_w_filePath"]}",
user_id = _user.Id.ToString()
};
return result;
}
catch (Exception ex)
{
_logger.LogError("save file failed: ", ex);
return new CreateWPSFileResult { code = 403, msg = "新建文件出现异常" };
}
});
}
///
/// 回调通知
///
///
///
[Route("/v1/3rd/onnotify")]
[HttpPost]
public Task WPSNotify(WPSNotifyRequest body)
{
return Task.Run(() =>
{
var result = new WPSBaseModel
{
code = 200,
msg = "success"
};
return result;
});
}
#endregion
}
Model参见上述Github的model。
todo:建议将GenarateWPSUrl进行拦截,比如你的权限认证等。回调方法里面参数需要自行请求数据库或者读取指定文件夹文件(比如用户信息或者文件信息)。
我是直接读取的文件夹,所以将文件名Url解码。进行一系列操作
StartUp方法:
// 配置WPS的 AppId 和 AppSecretKey
WPSSignatureHelper.Config(Configuration["WPSConfig:AppId"], Configuration["WPSConfig:AppSecretKey"], Configuration["WPSConfig:AppRequest"]);
//wps跨域
services.AddCors(p =>
{
p.AddPolicy("wps", policy =>
{
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
//policy.AllowAnyMethod()
//.SetIsOriginAllowed(_ => true)
//.AllowAnyHeader()
//.AllowCredentials();
});
});
//
app.UseCors("wps");
todo:core 3.0及以上需参照我这样写,2.2请直接参照上述Github
js前端如下:
$.getScript("/js/jwps.js");
/**
* 发起wps请求
* @param {any} url
* @param {any} type
* @param {any} param
* @param {any} async
* @param {any} elem
*/
function Ajax(url, type, param, async, elem) {
return new Promise(function (resolve, reject) {
$.ajax({
url: url,
contentType: "application/json",
dataType: "json",
type: type,
data: JSON.stringify(param),
async: async,
success: res => {
var wpsUrl = res.url;
console.log(wpsUrl);
var wps = showWPS(wpsUrl, elem);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.status);
if (jqXHR.status === 401) {
} else {
}
}
});
});
}
/**
*
* @param {any} url
* @param {any} elem
*/
function showWPS(url, elem) {
// 初始化
var wps = WPS.config({
mount: document.querySelector(elem),
// 文字
wpsUrl: url,
headers: {
shareBtn: {
tooltip: "分享",
subscribe: function () {
console.log("click callback");
}
}
}
});
return wps;
}
//调用
//类
function Demo(fileId, fileName, fileType, readOnly, filePath) {
this.fileId = fileId;
this.fileName = fileName;
this.fileType = fileType;
this.readOnly = readOnly;
this.filePath = filePath;
}
Ajax('http://jcxt.xnxv.cn/api/wps/genarate', 'post', new Demo(fileId, fileName, 1, false, 'PactTemplate'), 'false', '#PoupHtml').then(res => {
console.log(res);
});