前言
最近换新公司了,一直挺忙的,好久没正儿八经的吹牛逼了!新公司技术部门有定期学习计划,和技术储备计划,这不来了个在线预览文档的活么,作为公司技术储备,需求产生的背景:公司Word、Excel、PDF等文档挺多的,基本上都是展示文件名,然后点击下载到电脑才能查看的这种,不是很方便,那么直接搞成在线预览的可能比较香吧!
正文
本文将是全网最详细的Office Online Server 2016搭建教程,提供服务器版本选择,Windows Server 启动盘制作,Windows服务器搭建,域服务器配置,Windows服务器组网,Office Online Server2016 前置环境搭建、Office Online Server 2016 安装,Wopi服务搭建,以及一些坑的解决!首先需要准备两套服务器,而且都必须是Windows Server 2012 R2版本的服务器,推荐使用下方红框内的ISO镜像,其他版本的安装更新时会报错
实现效果
docx
xlsx
ppt
等其他的格式我这里就不易伊一列出来了!
服务器版本选择
这里版本一定要搞对,不然各种问题,选择windows_server_2012_r2,看到我这里三个不同的Windows Server版本就知道这里版本不对有多少坑了吧,我这里不跟你们说建议用那个版本,你们就用cn_windows_server_2012_r2_vl_with_update_x64_dvd_4051059.iso版本,如果想采坑的自便!
迅雷下载地址
ed2k://|file|cn_windows_server_2012_r2_vl_with_update_x64_dvd_4051059.iso|4683122688|BD0B95997679F83A4EE2D062865D8E64|/
4个多G,用迅雷下载不慢亲测5分钟!
Windows Server 系统启动盘制作
镜像下载下来后,我们开始制作Windows Server 2016的启动盘制作
启动盘制作工具我们选用U深度装机U深度装机官网
插入U盘
启动U深度装机
选择ISO模式
选择镜像
注意镜像千万别搞错了,是这个 cn_windows_server_2012_r2_x64_dvd_2707961.iso
开始制作
注意这里选择否,详细问题请见我的往期文章有提到这里为什么不选择是!Linux系统安装
写入镜像
启动盘最后大小
Windows Server 系统安装
这个每个电脑主板不同进入方式和安装方式都不同,我这里就以戴尔、麦本本举例吧!
戴尔
开机后长按F12,进入主板,选择USB模式,然后就会进入Windows Server 系统安装引导界面,然后傻瓜式操作点点点就行!
麦本本
开机后长按F2,进入主板,这里和戴尔的有点不同,需要切换启动盘顺序,详细查阅官网吧!这个我怕用文字描述吓到你们。然后就是傻瓜式操作了!
搭建域控服务器
打开服务器管理器,添加角色和功能;
出现“添加角色和功能”界面,下一步 。
根据提示操作,下一步。
根据提示操作,下一步。
选择添加AD域服务(Active Directory 域服务),同时添加所需功能。
根据提示操作,下一步
安装完成 。
配置域
点击服务器管理器左侧“AD DS” 、点击黄色提示部分中的更多。
点击“将此服务器升级为域控制器” 。
进入AD域服务器配置向导,选择 “添加新林” ,输入域,点击下一步。
域填写这个,contoso.com不要问我为什么,照着弄就行!
填写密码,下一步 。
注意一下,这里两台服务器密码最好保持一致,服务器时间要校准
提示DNS无法创建,不用管,继续下一步 。
安装路径,默认,下一步 。
查看选项,默认,下一步 。
点击安装 。此过程可能会报错,提示登陆用户没有设置密码,去设置给当前登陆用户(Administrator)设置一个密码,然后回来点击 “重新运行先决条件检查” 即可!
安装完成,会提示注销重启 (此过程比较漫长,耐心等待)。
Windows服务器组网
打开转换服务器,修改转换服务器的NDS,指向域控服务器(刚才的服务器ip地址)。
在计算机属性中,修改计算机名称(随意),并添加到域控服务器(域为“添加新林”时设置的域名),确定后提示注销重启服务器。
打开域控服务器,点击管理 > 添加服务器,输入转换服务器修改后的计算机名称,立即查找。将搜索到的服务器双击添加到右边,点击确定。
在域控服务器中,所有服务器显示两台服务器,并都是联机状态则表示成功 。
Office Online Server2016 前置环境搭建
Office Online Server 官网
知道你们不喜欢看官网,那就为难一下我搬运一下官网吧!不过微软的门户做的挺好的,尤其是下载软件的通道,但是他们注册的渠道是真TM恶心,机器人验证的时候简直就是智障,不信你们可以体验一下!
以管理员身份打开 Microsoft PowerShell 提示符,然后运行此命令示例来安装必需的角色和服务。
Add-WindowsFeature Web-Server,Web-Mgmt-Tools,Web-Mgmt-Console,Web-WebServer,Web-Common-Http,Web-Default-Doc,Web-Static-Content,Web-Performance,Web-Stat-Compression,Web-Dyn-Compression,Web-Security,Web-Filtering,Web-Windows-Auth,Web-App-Dev,Web-Net-Ext45,Web-Asp-Net45,Web-ISAPI-Ext,Web-ISAPI-Filter,Web-Includes,InkandHandwritingServices,NET-Framework-Features,NET-Framework-Core,NET-HTTP-Activation,NET-Non-HTTP-Activ,NET-WCF-HTTP-Activation45,Windows-Identity-Foundation,Server-Media-Foundation
安装完成后重启。
NET Framework 4.5.2
点击下载即可,后面几个环节都是!顺序不能错哦!
Visual C++ Redistributable Packages for Visual Studio 2013
Visual C++ Redistributable for Visual Studio 2015
Microsoft.IdentityModel.Extention.dll
Office Online Server 2016 安装
Office Online Server 2016这个安装包还挺难找的,如果从官网上下在,那就会碰到那个智障机器人检验,注册入口我放着,有想体验的自己可以试试!访问批量许可服务中心
百度网盘下载密码: guc8Office Online Server 2016
解压
再解压n-moos16.rar得到n-moos16.iso
下面进入主题了,安装office online。
双击下载好的office online 安装包,进行安装。
安装成功。
安装语言包。
安装成功。
启动office Online 服务器场
打开安装office Online 的主机,打开powerShell。
Import-Module OfficeWebApps
部署服务器场:
New-OfficeWebAppsFarm -InternalURL “http://officeserver.contoso.com” -ExternalUrl “http://192.168.1.24” -AllowHttp –EditingEnabled
-InternalURL:内网浏览地址,http://xx.domin.com 其中 xx表示计算机名 domin.com 表示域名 也可以设置为对应的IP地址
-ExternalURL:外网浏览地址
-AllowHttp: 允许80端口访问
-OpenFromUrlEnabled:允许通过url方式进行预览
-CacheLocation: 缓存文件存放路径 默认是C:\ProgramData\Microsoft\OfficeWebApps\Working\d
-CacheSizeInGB: 最大缓存文件大小 单位GB 默认为15GB
关闭两台服务器防火墙
访问http:// 192.168.1.24/hosting/discovery 测试是否启动
若http:// 192.168.1.24/hosting/discovery 能登录,http://192.168.1.24/op/generate.aspx显示“服务器错误”,控制台输入Set-OfficeWebAppsFarm -OpenFromUrlEnabled:$true即可访问成功
访问http://officeserver.contoso.com/op/generate.aspx测试 http://officeserver.contoso.com为officeserver地址
ok
Wopi服务搭建
上代码吧,做点程序员该干的活吧!
使用SpringBoot脚手架创建一个web工程
FileInfo
package com.tao.wopihost.entity;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
/**
* @author TAO
* @description: TODO
* @date 2021/6/26 20:48
*/
public class FileInfo implements Serializable {
/**
* 必须字段
*/
// 包含扩展的文件名
@JsonProperty("BaseFileName")
private String baseFileName;
// 文件大小(单位:byte)
@JsonProperty("Size")
private long size;
// 唯一标识文件所有者
@JsonProperty("OwnerId")
private String ownerId;
// 文件版本号,文件如果被编辑,版本号也要跟着改变
@JsonProperty("Version")
private long version;
// SHA-2 256位散列编码值(获取该文件的文件流->SHA256计算文件流Hash值->将Hash值转换为Base64String)
@JsonProperty("SHA256")
private String sha256;
/**
* 编辑时可能用到的字段
*/
// 是否允许连接到文件中引用的外部服务(例如,可嵌入javascript应用程序)
@JsonProperty("AllowExternalMarketplace")
private boolean allowExternalMarketplace = true;
// 是否有权限修改
@JsonProperty("UserCanWrite")
private boolean userCanWrite = true;
// 是否支持更新
@JsonProperty("SupportsUpdate")
private boolean supportsUpdate = true;
// 是否支持锁定
@JsonProperty("SupportsLocks")
private boolean supportsLocks = true;
/**
* 其他字段
*/
// 是否支持使用WOPI客户端创建新文件
private boolean SupportsFileCreation = true;
// 是否支持用户可以通过受限制的URL以有限的方式对文件进行操作的方案
private boolean SupportsScenarioLinks = true;
public String getBaseFileName() {
return baseFileName;
}
public void setBaseFileName(String baseFileName) {
this.baseFileName = baseFileName;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public String getOwnerId() {
return ownerId;
}
public void setOwnerId(String ownerId) {
this.ownerId = ownerId;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public String getSha256() {
return sha256;
}
public void setSha256(String sha256) {
this.sha256 = sha256;
}
public boolean isAllowExternalMarketplace() {
return allowExternalMarketplace;
}
public void setAllowExternalMarketplace(boolean allowExternalMarketplace) {
this.allowExternalMarketplace = allowExternalMarketplace;
}
public boolean isUserCanWrite() {
return userCanWrite;
}
public void setUserCanWrite(boolean userCanWrite) {
this.userCanWrite = userCanWrite;
}
public boolean isSupportsUpdate() {
return supportsUpdate;
}
public void setSupportsUpdate(boolean supportsUpdate) {
this.supportsUpdate = supportsUpdate;
}
public boolean isSupportsLocks() {
return supportsLocks;
}
public void setSupportsLocks(boolean supportsLocks) {
this.supportsLocks = supportsLocks;
}
public boolean isSupportsFileCreation() {
return SupportsFileCreation;
}
public void setSupportsFileCreation(boolean supportsFileCreation) {
SupportsFileCreation = supportsFileCreation;
}
public boolean isSupportsScenarioLinks() {
return SupportsScenarioLinks;
}
public void setSupportsScenarioLinks(boolean supportsScenarioLinks) {
SupportsScenarioLinks = supportsScenarioLinks;
}
}
注意属性名称大写转一下!
WopiHostContrller
package com.tao.wopihost.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tao.wopihost.entity.FileInfo;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author TAO
* @description: WOPI HOST
* @date 2021/6/26 20:48
*/
@RestController
@RequestMapping(value = "/wopi")
public class WopiHostContrller {
@Value("${file.path}")
private String filePath;
/**
* 获取文件流
*
* @param name
* @param response
*/
@GetMapping("/files/{name}/contents")
public void getFile(@PathVariable(name = "name") String name, HttpServletResponse response) {
System.out.println("GET获取文件啦!!!!");
InputStream fis = null;
OutputStream toClient = null;
try {
// 文件的路径
String path = filePath + name;
File file = new File(path);
// 取得文件名
String filename = file.getName();
// 以流的形式下载文件
fis = new BufferedInputStream(new FileInputStream(path));
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
// 清空response
response.reset();
// 设置response的Header
response.addHeader("Content-Disposition", "attachment;filename=" +
new String(filename.getBytes("utf-8"), "ISO-8859-1"));
response.addHeader("Content-Length", "" + file.length());
toClient = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
toClient.write(buffer);
toClient.flush();
System.out.println("GET获取文件Contents结束!!!!");
} catch (IOException ex) {
ex.printStackTrace();
} finally {
try {
fis.close();
toClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 保存更新文件
*
* @param name
* @param content
*/
@PostMapping("/files/{name}/contents")
public void postFile(@PathVariable(name = "name") String name, @RequestBody byte[] content) {
System.out.println("POST获取文件Contents啦!!!!");
// 文件的路径
String path = filePath + name;
File file = new File(path);
FileOutputStream fop = null;
try {
if (!file.exists()) {
file.createNewFile();//构建文件
}
fop = new FileOutputStream(file);
fop.write(content);
fop.flush();
System.out.println("POST获取文件结束!!!!");
System.out.println("------------ save file ------------ ");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fop.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取文件信息
*
* @param request
* @param response
* @return
* @throws UnsupportedEncodingException
*/
@GetMapping("/files/{name}")
public void getFileInfo(HttpServletRequest request, HttpServletResponse response) {
System.out.println("获取文件啦!!!!");
String uri = request.getRequestURI();
FileInfo info = new FileInfo();
try {
// 获取文件名, 防止中文文件名乱码
String fileName = URLDecoder.decode(uri.substring(uri.indexOf("wopi/files/") + 11, uri.length()), "UTF-8");
if (fileName != null && fileName.length() > 0) {
System.out.println("文件不为空啊");
File file = new File(filePath + fileName);
if (file.exists()) {
// 取得文件名
info.setBaseFileName(file.getName());
info.setSize(file.length());
info.setOwnerId("admin");
info.setVersion(file.lastModified());
info.setSha256(getHash256(file));
info.setAllowExternalMarketplace(true);
info.setUserCanWrite(true);
info.setSupportsUpdate(true);
info.setSupportsLocks(true);
}
}
ObjectMapper mapper = new ObjectMapper();
String Json = mapper.writeValueAsString(info);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(Json);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取文件的SHA-256值
*
* @param file
* @return
*/
public static String getHash256(File file) {
String value = "";
// 获取hash值
try {
byte[] buffer = new byte[1024];
int numRead;
InputStream fis = new FileInputStream(file);
//如果想使用SHA-1或SHA-256,则传入SHA-1,SHA-256
MessageDigest complete = MessageDigest.getInstance("SHA-256");
do {
//从文件读到buffer
numRead = fis.read(buffer);
if (numRead > 0) {
//用读到的字节进行MD5的计算,第二个参数是偏移量
complete.update(buffer, 0, numRead);
}
} while (numRead != -1);
fis.close();
value = new String(Base64.encodeBase64(complete.digest()));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return value;
}
}
application.yml
server:
port: 80
file:
path: E:\file\
path是域服务器上文件存放位置
打包
放到域服务器上运行!
创建测试文档
注意这里两台服务器需要互相能ping通,
文档地址配置
1.由于微软这款软件对IP有访问限制,所以需将IP转化为域名进行访问,所以需要进行配置,来让软件自动进行域名转化为IP,具体路径如下
192.168.1.24 officeserver.contoso.com
192.168.1.16 contoso.com
完整性测试
docx
http://officeserver.contoso.com/op/view.aspx?src=http://contoso.com/wopi/files/test1.docx/contents
xlsx
http://officeserver.contoso.com/op/view.aspx?src=http://contoso.com/wopi/files/test3.xlsx/contents
ppt
http://officeserver.contoso.com/op/view.aspx?src=http://contoso.com/wopi/files/111.ppt/contents
搞定,睡了睡了。