近有一个业务是前端要上传word格式的文稿,然后用户上传完之后,可以用浏览器直接查看该文稿,并且可以在富文本框直接引用该文稿,所以上传word文稿之后,后端保存到db的必须是html格式才行,所以涉及到word格式转html格式。
通过调查,这个word和html的处理,有两种方案,方案1是前端做这个转换。方案2是把word文档上传给后台,后台转换好之后再返回给前端。至于方案1,看到大家的反馈都说很多问题,所以就没采用前端转的方案,最终决定是后端转化为html格式并返回给前段预览,待客户预览的时候,确认格式没问题之后,再把html保存到后台(因为word涉及到的格式太多,比如图片,visio图,表格,图片等等之类的复杂元素,转html的时候,可能会很多格式问题,所以要有个预览的过程)。
对于word中普通的文字,问题倒不大,主要是文本之外的元素的处理,比如图片,视频,表格等。针对我本次的文章,只处理了图片,处理的方式是:后台从word中找出图片(当然引入的jar包已经带了获取word中图片的功能),上传到服务器,拿到绝对路径之后,放入到html里面,这样,返回给前端的html内容,就可以直接预览了。
maven引入相关依赖包如下:
<poi-scratchpad.version>3.14</poi-scratchpad.version>
<poi-ooxml.version>3.14</poi-ooxml.version>
<xdocreport.version>1.0.6</xdocreport.version>
<poi-ooxml-schemas.version>3.14</poi-ooxml-schemas.version>
<ooxml-schemas.version>1.3</ooxml-schemas.version>
<jsoup.version>1.11.3</jsoup.version>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>${poi-scratchpad.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi-ooxml.version}</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>xdocreport</artifactId>
<version>${xdocreport.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>${poi-ooxml-schemas.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>${ooxml-schemas.version}</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
word转html,对于word2003和word2007转换方式不一样,因为word2003和word2007的格式不一样,工具类如下:
使用方法如下:
public String uploadSourceNews(MultipartFile file) {
String fileName = file.getOriginalFilename();
String suffixName = fileName.substring(fileName.lastIndexOf("."));
if (!".doc".equals(suffixName) && !".docx".equals(suffixName)) {
throw new UploadFileFormatException();
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM");
String dateDir = formatter.format(LocalDate.now());
String directory = imageDir + "/" + dateDir + "/";
String content = null;
try {
InputStream inputStream = file.getInputStream();
if ("doc".equals(suffixName)) {
content = wordToHtmlUtil.Word2003ToHtml(inputStream, imageBucket, directory, Constants.HTTPS_PREFIX + imageVisitHost);
} else {
content = wordToHtmlUtil.Word2007ToHtml(inputStream, imageBucket, directory, Constants.HTTPS_PREFIX + imageVisitHost);
}
} catch (Exception ex) {
logger.error("word to html exception, detail:", ex);
return null;
}
return content;
}
关于doc和docx的一些存储格式介绍:
docx 是微软开发的基于 xml 的文字处理文件。docx 文件与 doc 文件不同, 因为 docx 文件将数据存储在单独的压缩文件和文件夹中。早期版本的 microsoft office (早于 office 2007) 不支持 docx 文件, 因为 docx 是基于 xml 的, 早期版本将 doc 文件另存为单个二进制文件。
DOCX is an XML based word processing file developed by Microsoft. DOCX files are different than DOC files as DOCX files store data in separate compressed files and folders. Earlier versions of Microsoft Office (earlier than Office 2007) do not support DOCX files because DOCX is XML based where the earlier versions save DOC file as a single binary file.
可能你会问了,明明是docx结尾的文档,怎么成了xml格式了?
很简单:你随便选择一个docx文件,右键使用压缩工具打开,就能得到一个这样的目录结构:
所以你以为docx是一个完整的文档,其实它只是一个压缩文件。
参考:
https://www.cnblogs.com/ct-csu/p/8178932.html
者:oec2003
公众号:不止dotNET
最近学习了 Go 语言,做下记录。
和 C、C++ 相比, Go 是一门很年轻的语言。2007 年,在 Google 的内部,有三位大佬因为 C++ 的复杂性、构建编译速度很慢和对并发支持不好等原因,便决定开发一门新的语言,于是他们基于 C 语言,做了功能的删减和新增,便有了 Go 的诞生。
2009 年 10 月 30 日,在 Google Techtalk 上,Go 语言的初始三位创始人之一的罗伯·派克做了一次关于 Go 语言的演讲,这也是 Go 语言第一次公开露面。十天后,谷歌正式宣布 Go 语言项目开源。
2012 年 3 月 28 日,Go 1.0 版本正式发布,同时 Go 官方发布了 “Go 1 兼容性” 承诺:只要符合 Go 1 语言规范的源代码,Go 编译器将保证向后兼容(backwards compatible),这给开发者带来了安全感。
1、在 https://go.dev/dl 页面下载 Mac 版本的 pkg 安装文件:
2、根据向导进行 Go 的安装,程序会安装到 /usr/local/go 目录中;
3、重启终端、输入命令 go version ,如果能正确显示版本号说明安装成功;
4、在 VS Code 中安装 go 扩展:
5、在 VS Code 中敲 cmd+shift+p ,然后输入 Go:Install ,选择下图红框部分进行扩展工具的安装;
6、全选所有的扩展工具,点击确定,但这时通常会出现错误,不能正常安装,采用下面第七步的方式可以解决这个问题:
7、在 ~/.bash_profile 文件中添加:
export GO111MODULE=on
export GOPROXY=https://goproxy.cn
执行 source ~/.bash_profile 使配置生效,然后重新执行第六步的扩展工具安装。
8、到这环境就准备好了,可以开始写代码。
1、在 go-study 目录中创建 helloworld 目录,go-study 是我用来学习 Go 语言存放代码的一个根目录;
2、使用 VS Code 打开 helloworld 目录,并在目录中创建 main.go 文件,内容如下:
package main
import (
"fmt"
)
func main() {
fmt.Println("hello oec2003!")
}
3、使用 go run main.go 运行程序:
假设有这样一个场景:
零代码平台涉及到很多不同的服务和中间件,在客户服务器私有化部署时需要运维人员在服务器上进行各种配置才能搞定。
如果用 go 写一个 web 程序,通过界面的简单配置和 shell 脚本的相结合,可以打打降低部署的难度。
下面就来看看怎样来做这个简单配置的 web 程序。
1、创建 deploy-app 目录,在目录中创建 main.go 文件,内容如下:
package main
import (
"embed"
"io/fs"
"log"
"net"
"net/http"
"os"
)
func main() {
http.Handle("/", http.FileServer(getFileSystem()))
ip, err := getLocalIP()
if err != nil {
return
}
log.Println("启动成功,通过 http://" + ip + ":10002 访问")
server := http.Server{
Addr: ":10002",
Handler: nil,
}
server.ListenAndServe()
}
//go:embed wwwroot
var embededFiles embed.FS
func getFileSystem(useOS bool) http.FileSystem {
if useOS {
return http.FS(os.DirFS("wwwroot"))
}
fsys, err := fs.Sub(embededFiles, "wwwroot")
if err != nil {
panic(err)
}
return http.FS(fsys)
}
func getLocalIP() (ip string, err error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return
}
for _, addr := range addrs {
ipAddr, ok := addr.(*net.IPNet)
if !ok {
continue
}
if ipAddr.IP.IsLoopback() {
continue
}
if !ipAddr.IP.IsGlobalUnicast() {
continue
}
return ipAddr.IP.String(), nil
}
return
}
2、在 deploy-app 目录中创建 wwwroot 目录,在 wwwroot 中创建 idnex.html 文件:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>瞬翕云私有部署版</title>
<link href="css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
</style>
</head>
<body>
<div>
<div class="text">私有化部署</div>
</div>
<div>
<h2 class="ant-typography">访问地址设置</h2>
<div>例:http://fwhyy.com、http://10.211.55.3:9000(支持设置 域名、域名+端口、IP+端口)</div>
<div>访问地址</div>
<div class="input-group mb-3">
<span class="input-group-text" id="inputGroup-sizing-default">http://</span>
<input type="text" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default" value="">
</div>
<button type="button" class="btn btn-primary">下一步</button>
</div>
</body>
</html>
3、使用 go run main.go 运行看看效果:
在浏览器中使用 http://192.168.1.7:10002 进行访问,如下图:
4、使用命令 GOOS=linux GOARCH=amd64 go build main.go 进行编译构建,构建完成后会在 deploy-app 目录中生成一个名为 main 的二进制文件,如下图:
5、将 main 文件拷贝到 CentOS 虚拟机中,使用 ./main 命令运行,如下图:
可以看到运行效果和本机运行的效果相同:
1、使用 embed 功能可以将静态资源打包到二进制的包中;
2、Go 语言编译后的是一个二进制文件,在服务器上不需要进行运行时的安装即可运行;
3、学习任何语言,语法部分可以通过刷刷力扣的题来进行熟悉,一个比较实用的小技巧。
本教程中,我们将看看如何使用React和HTML5的现代组合来处理表单提交和验证。
当我们在Web应用程序中讨论用户输入时,我们经常首先想到HTML表单。Web表单从HTML的第一个版本开始就已经可用。显然,该功能已于1991年推出,并在1995年以RFC 1866标准进行了标准化。我们在任何地方都使用它们,几乎每个库和框架。但是React呢?Facebook就如何处理表单提供了有限的意见。主要是为交互事件订阅表单和控件,并通过“value”属性传递状态。因此表单验证和提交逻辑取决于您。体面的用户界面意味着你可以覆盖“on submit”/“on input”字段验证,内联错误消息,根据有效性切换元素,“原始”,“提交”状态等逻辑。我们能不能抽象出这种逻辑并简单地将它插入我们的表格中?我们当然可以。唯一的问题是采取什么方法和解决方案。
如果你喜欢去的devkit ReactBootstrap或AntDesign你很可能已经高兴的形式。两者都提供组件来构建满足不同要求的表单。例如,在AntDesign中,我们使用元素和带有的表单字段来定义表单,该 是集合中任何输入控件的封装。您可以在上设置验证规则,如:
<FormItem>
{getFieldDecorator('select', {
rules: [
{ required: true, message: 'Please select your country!' },
],
})(
<Select placeholder="Please select a country">
<Option value="china">China</Option>
<Option value="use">U.S.A</Option>
</Select>
)}
</FormItem>
然后,例如,在表单提交处理程序中,您可以运行this.props.form.validateFields()以应用验证。它看起来像一切照顾。然而,解决方案是特定于框架的。如果您不使用DevKit,则无法从其功能中受益。
或者,我们可以使用独立的组件,根据提供的JSON规范为我们构建表单。例如,我们可以导入 Winterfell组件并构建一个如此简单的表单:
<Winterfell schema={loginSchema} ></Winterfell>
但是,模式可能相当复杂。此外,我们将自己绑定到自定义语法。另一个解决方案 react-jsonschema-form看起来很相似,但依赖于 JSON模式。JSON模式是一个专门用于注释和验证JSON文档的项目无关词汇表。然而,它将我们与构建器中实现并在架构中定义的唯一功能绑定在一起。
我宁愿使用我的任意HTML表单的包装来处理验证逻辑。在这里,最受欢迎的解决方案之一是 Formsy。它是什么样子的?我们为表单域创建自己的组件,并用HOC包装它 withFormsy:
import { withFormsy } from "formsy-react";
import React from "react";
class MyInput extends React.Component {
changeValue = ( event ) => {
this.props.setValue( event.currentTarget.value );
}
render() {
return (
<div>
<input
onChange={ this.changeValue }
type="text"
value={ this.props.getValue() || "" }
/>
<span>{ this.props.getErrorMessage() }</span>
</div>
);
}
}
export default withFormsy( MyInput );
正如你所看到的,组件接收getErrorMessage() 道具中的 函数,我们可以使用它来进行内联错误消息。
所以我们做了一个场域组件。让我们把它放在一个表单中:
import Formsy from "formsy-react";
import React from "react";
import MyInput from "./MyInput";
export default class App extends React.Component {
onValid = () => {
this.setState({ valid: true });
}
onInvalid = () => {
this.setState({ valid: false });
}
submit( model ) {
//...
}
render() {
return (
<Formsy onValidSubmit={this.submit} onValid={this.onValid} onInvalid={this.onInvalid}>
<MyInput
name="email"
validations="isEmail"
validationError="This is not a valid email"
required
></MyInput>
<button type="submit" disabled={ !this.state.valid }>Submit</button>
</Formsy>
);
}
}
我们用validations 属性指定所有必需的字段验证器 (请参阅 可用验证器列表)。用 validationError, 我们设置所需的验证消息,并从形式有效性状态中接收 onValid 和 onInvalid处理程序。
这看起来简单,干净,灵活。但我想知道为什么我们不依赖HTML5内置表单验证,而不是使用无数的自定义实现。
该技术出现在不久前。第一个实现在2008年与Opera 9.5一起实现。如今,它在所有现代浏览器中都可用。表单(数据)验证引入了额外的HTML属性和输入类型,可用于设置表单验证规则。验证也可以通过使用专用API从JavaScript进行控制和定制 。
我们来看下面的代码:
<form>
<label for="answer">What do you know, Jon Snow?</label>
<input id="answer" name="answer" required>
<button>Ask</button>
</form>
这是一个简单的形式,除了一件事 - 输入元素有一个 required 属性。所以如果我们立即按下提交按钮,表单将不会被发送到服务器。相反,我们会在输入旁边看到一个工具提示,说明该值不符合给定的约束(即该字段不应为空)。
现在我们将输入设置为一个附加约束:
<form>
<label for="answer">What do you know, Jon Snow?</label>
<input id="answer" name="answer" required pattern="nothing|nix">
<button>Ask</button>
</form>
所以这个值不仅仅是必需的,而且必须遵从给定的正则表达式pattern。
错误信息虽然不是那种信息,是吗?我们可以对其进行定制(例如,解释我们对用户的期望)或者只是翻译:
<form>
<label for="answer">What do you know, Jon Snow?</label>
<input id="answer" name="answer" required pattern="nothing|nix">
<button>Ask</button>
</form>
const answer = document.querySelector( "[name=answer]" );
answer.addEventListener( "input", ( event ) => {
if ( answer.validity.patternMismatch ) {
answer.setCustomValidity("Oh, it's not a right answer!");
} else {
answer.setCustomValidity( "" );
}
});
所以基本上,在输入事件中,它检查patternMismatch 输入有效性状态的 属性状态。任何时候实际值与模式不匹配,我们定义错误消息。如果我们 对控件有任何其他限制,我们也可以在事件处理程序中覆盖它们。
对工具提示不满意?是的,他们在不同的浏览器中看起来不一样。我们添加下面的代码:
<form novalidate>
<label for="answer">What do you know, Jon Snow?</label>
<input id="answer" name="answer" required pattern="nothing|nix">
<div data-bind="message"></div>
<button>Ask</button>
</form>
const answer = document.querySelector( "[name=answer]" ),
answerError = document.querySelector( "[name=answer] + [data-bind=message]" );
answer.addEventListener( "input", ( event ) => {
answerError.innerHTML = answer.validationMessage;
});
即使只是这个超级简短的介绍,你可以看到技术的力量和灵活性。本机表单验证非常好。那么,为什么我们要依靠无数的定制库?为什么不使用内置验证?
react-html5-form将React(以及可选的Redux)连接到HTML5 Form Validation API。它公开组件Form 和 InputGroup (类似于Formsy的自定义输入或 FormItem 在AntDesign中)。因此, Form 定义了表单及其范围,并 定义了可以包含一个或多个输入的字段的范围。我们只是用这些组件包装任意形式的内容(只是简单的HTML或React组件)。在用户事件中,我们可以请求表单验证,并获得更新后的状态 和组件,因此,对底层输入有效性。 InputGroupFormInputGroup
那么,让我们在实践中看到它。首先,我们定义表单范围:
import React from "react";
import { render } from "react-dom";
import { Form, InputGroup } from "Form";
const MyForm = props => (
<Form>
{({ error, valid, pristine, submitting, form }) => (
<>
Form content
<button disabled={ ( pristine || submitting ) } type="submit">Submit</button>
</>
)}
</Form>
);
render( <MyForm ></MyForm>, document.getElementById( "app" ) );
该作用域接收具有属性的状态对象:
这里我们使用just pristine 和submitting properties来将提交按钮切换到禁用状态。
为了在提供表单内容时注册输入进行验证,我们将它们包装在一起 InputGroup
<InputGroup validate={[ "email" ]} }}>
{({ error, valid }) => (
<div>
<label htmlFor="emailInput">Email address</label>
<input
type="email"
required
name="email"
id="emailInput" />
{ error && (<div className="invalid-feedback">{error}</div>) }
</div>
)}
</InputGroup>
通过validate 道具,我们指定了团队的哪些输入应该被注册。[ "email" ] 意味着我们有唯一的输入,名称为“电子邮件”。
在范围中,我们收到具有以下属性的状态对象:
渲染之后,我们得到一个带有电子邮件字段的表单。如果该值为空或在提交时包含无效的电子邮件地址,则会在输入旁边显示相应的验证消息。
请记住,我们在使用本机Form Validation API时正在努力处理自定义错误消息?在以下情况下更容易 InputGroup:
<InputGroup
validate={[ "email" ]}
translate={{
email: {
valueMissing: "C'mon! We need some value",
typeMismatch: "Hey! We expect an email address here"
}
}}>
...
我们可以为每个输入指定一个映射,其中键是有效性属性,值是自定义消息。
那么,消息定制很容易。自定义验证怎么样?我们可以通过validate 道具做到这一点 :
<InputGroup validate={{
"email": ( input ) => {
if ( !EMAIL_WHITELIST.includes( input.current.value ) ) {
input.setCustomValidity( "Only whitelisted email allowed" );
return false;
}
return true;
}
}}>
...
在这种情况下,我们提供了一个映射,而不是一组输入名称,其中键是输入名称,值是验证处理程序。处理程序检查输入值(可以异步完成)并将有效性状态作为布尔值返回。使用 input.setCustomValidity, 我们分配一个特定于案例的验证消息。
提交时的验证并不总是我们想要的。我们来实现一个“即时”验证。首先,我们为输入事件定义一个事件处理程序:
const onInput = ( e, inputGroup ) => {
inputGroup.checkValidityAndUpdate();
};
实际上,我们只是在用户输入输入时重新验证输入组。我们认为控制如下:
<input type="email" required name="email" onInput={( e ) => onInput( e, inputGroup, form ) } id="emailInput" />
从现在开始,只要我们改变输入值就会被验证,如果它无效,我们会立即收到错误信息。
你可以从上面的例子中找到演示的源代码。
顺便说一句,你喜欢将组件派生的表单状态树连接到Redux存储吗?我们也可以做到这一点。
该软件包公开了html5form 包含所有注册表单的状态树的reducer 。我们可以像这样将它连接到商店:
import React from "react";
import { render } from "react-dom";
import { createStore, combineReducers } from "redux";
import { Provider } from "react-redux";
import { App } from "./Containers/App.jsx";
import { html5form } from "react-html5-form";
const appReducer = combineReducers({
html5form
});
// Store creation
const store = createStore( appReducer );
render( <Provider store={store}>
<App ></App>
</Provider>, document.getElementById( "app" ) );
现在,当我们运行应用程序时,我们可以在商店中找到所有与表单相关的状态。
这是一个专用演示的源代码。
React没有内置的表单验证逻辑。但我们可以使用第三方解决方案。所以它可以是一个DevKit,它可以是一个表单生成器,它可以是一个HOC或包装器组件,将表单验证逻辑混合到任意形式的内容中。我个人的方法是一个包装组件,它依赖于HTML内置的表单验证API并在表单和表单域的范围内公开有效性状态。
*请认真填写需求信息,我们会在24小时内与您取得联系。