近有一個業(yè)務是前端要上傳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 是微軟開發(fā)的基于 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++ 的復雜性、構建編譯速度很慢和對并發(fā)支持不好等原因,便決定開發(fā)一門新的語言,于是他們基于 C 語言,做了功能的刪減和新增,便有了 Go 的誕生。
2009 年 10 月 30 日,在 Google Techtalk 上,Go 語言的初始三位創(chuàng)始人之一的羅伯·派克做了一次關于 Go 語言的演講,這也是 Go 語言第一次公開露面。十天后,谷歌正式宣布 Go 語言項目開源。
2012 年 3 月 28 日,Go 1.0 版本正式發(fā)布,同時 Go 官方發(fā)布了 “Go 1 兼容性” 承諾:只要符合 Go 1 語言規(guī)范的源代碼,Go 編譯器將保證向后兼容(backwards compatible),這給開發(fā)者帶來了安全感。
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、全選所有的擴展工具,點擊確定,但這時通常會出現(xiàn)錯誤,不能正常安裝,采用下面第七步的方式可以解決這個問題:
7、在 ~/.bash_profile 文件中添加:
export GO111MODULE=on
export GOPROXY=https://goproxy.cn
執(zhí)行 source ~/.bash_profile 使配置生效,然后重新執(zhí)行第六步的擴展工具安裝。
8、到這環(huán)境就準備好了,可以開始寫代碼。
1、在 go-study 目錄中創(chuàng)建 helloworld 目錄,go-study 是我用來學習 Go 語言存放代碼的一個根目錄;
2、使用 VS Code 打開 helloworld 目錄,并在目錄中創(chuàng)建 main.go 文件,內容如下:
package main
import (
"fmt"
)
func main() {
fmt.Println("hello oec2003!")
}
3、使用 go run main.go 運行程序:
假設有這樣一個場景:
零代碼平臺涉及到很多不同的服務和中間件,在客戶服務器私有化部署時需要運維人員在服務器上進行各種配置才能搞定。
如果用 go 寫一個 web 程序,通過界面的簡單配置和 shell 腳本的相結合,可以打打降低部署的難度。
下面就來看看怎樣來做這個簡單配置的 web 程序。
1、創(chuàng)建 deploy-app 目錄,在目錄中創(chuàng)建 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 目錄中創(chuàng)建 wwwroot 目錄,在 wwwroot 中創(chuàng)建 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 功能可以將靜態(tài)資源打包到二進制的包中;
2、Go 語言編譯后的是一個二進制文件,在服務器上不需要進行運行時的安裝即可運行;
3、學習任何語言,語法部分可以通過刷刷力扣的題來進行熟悉,一個比較實用的小技巧。
本教程中,我們將看看如何使用React和HTML5的現(xiàn)代組合來處理表單提交和驗證。
當我們在Web應用程序中討論用戶輸入時,我們經常首先想到HTML表單。Web表單從HTML的第一個版本開始就已經可用。顯然,該功能已于1991年推出,并在1995年以RFC 1866標準進行了標準化。我們在任何地方都使用它們,幾乎每個庫和框架。但是React呢?Facebook就如何處理表單提供了有限的意見。主要是為交互事件訂閱表單和控件,并通過“value”屬性傳遞狀態(tài)。因此表單驗證和提交邏輯取決于您。體面的用戶界面意味著你可以覆蓋“on submit”/“on input”字段驗證,內聯(lián)錯誤消息,根據有效性切換元素,“原始”,“提交”狀態(tài)等邏輯。我們能不能抽象出這種邏輯并簡單地將它插入我們的表格中?我們當然可以。唯一的問題是采取什么方法和解決方案。
如果你喜歡去的devkit ReactBootstrap或AntDesign你很可能已經高興的形式。兩者都提供組件來構建滿足不同要求的表單。例如,在AntDesign中,我們使用元素和帶有的表單字段來定義表單,該 是集合中任何輸入控件的封裝。您可以在上設置驗證規(guī)則,如:
<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規(guī)范為我們構建表單。例如,我們可以導入 Winterfell組件并構建一個如此簡單的表單:
<Winterfell schema={loginSchema} ></Winterfell>
但是,模式可能相當復雜。此外,我們將自己綁定到自定義語法。另一個解決方案 react-jsonschema-form看起來很相似,但依賴于 JSON模式。JSON模式是一個專門用于注釋和驗證JSON文檔的項目無關詞匯表。然而,它將我們與構建器中實現(xiàn)并在架構中定義的唯一功能綁定在一起。
我寧愿使用我的任意HTML表單的包裝來處理驗證邏輯。在這里,最受歡迎的解決方案之一是 Formsy。它是什么樣子的?我們?yōu)楸韱斡騽?chuàng)建自己的組件,并用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() 道具中的 函數,我們可以使用它來進行內聯(lián)錯誤消息。
所以我們做了一個場域組件。讓我們把它放在一個表單中:
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, 我們設置所需的驗證消息,并從形式有效性狀態(tài)中接收 onValid 和 onInvalid處理程序。
這看起來簡單,干凈,靈活。但我想知道為什么我們不依賴HTML5內置表單驗證,而不是使用無數的自定義實現(xiàn)。
該技術出現(xiàn)在不久前。第一個實現(xiàn)在2008年與Opera 9.5一起實現(xiàn)。如今,它在所有現(xiàn)代瀏覽器中都可用。表單(數據)驗證引入了額外的HTML屬性和輸入類型,可用于設置表單驗證規(guī)則。驗證也可以通過使用專用API從JavaScript進行控制和定制 。
我們來看下面的代碼:
<form>
<label for="answer">What do you know, Jon Snow?</label>
<input id="answer" name="answer" required>
<button>Ask</button>
</form>
這是一個簡單的形式,除了一件事 - 輸入元素有一個 required 屬性。所以如果我們立即按下提交按鈕,表單將不會被發(fā)送到服務器。相反,我們會在輸入旁邊看到一個工具提示,說明該值不符合給定的約束(即該字段不應為空)。
現(xiàn)在我們將輸入設置為一個附加約束:
<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 輸入有效性狀態(tài)的 屬性狀態(tài)。任何時候實際值與模式不匹配,我們定義錯誤消息。如果我們 對控件有任何其他限制,我們也可以在事件處理程序中覆蓋它們。
對工具提示不滿意?是的,他們在不同的瀏覽器中看起來不一樣。我們添加下面的代碼:
<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組件)。在用戶事件中,我們可以請求表單驗證,并獲得更新后的狀態(tài) 和組件,因此,對底層輸入有效性。 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" ) );
該作用域接收具有屬性的狀態(tài)對象:
這里我們使用just pristine 和submitting properties來將提交按鈕切換到禁用狀態(tài)。
為了在提供表單內容時注冊輸入進行驗證,我們將它們包裝在一起 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" ] 意味著我們有唯一的輸入,名稱為“電子郵件”。
在范圍中,我們收到具有以下屬性的狀態(tài)對象:
渲染之后,我們得到一個帶有電子郵件字段的表單。如果該值為空或在提交時包含無效的電子郵件地址,則會在輸入旁邊顯示相應的驗證消息。
請記住,我們在使用本機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;
}
}}>
...
在這種情況下,我們提供了一個映射,而不是一組輸入名稱,其中鍵是輸入名稱,值是驗證處理程序。處理程序檢查輸入值(可以異步完成)并將有效性狀態(tài)作為布爾值返回。使用 input.setCustomValidity, 我們分配一個特定于案例的驗證消息。
提交時的驗證并不總是我們想要的。我們來實現(xiàn)一個“即時”驗證。首先,我們?yōu)檩斎胧录x一個事件處理程序:
const onInput = ( e, inputGroup ) => {
inputGroup.checkValidityAndUpdate();
};
實際上,我們只是在用戶輸入輸入時重新驗證輸入組。我們認為控制如下:
<input type="email" required name="email" onInput={( e ) => onInput( e, inputGroup, form ) } id="emailInput" />
從現(xiàn)在開始,只要我們改變輸入值就會被驗證,如果它無效,我們會立即收到錯誤信息。
你可以從上面的例子中找到演示的源代碼。
順便說一句,你喜歡將組件派生的表單狀態(tài)樹連接到Redux存儲嗎?我們也可以做到這一點。
該軟件包公開了html5form 包含所有注冊表單的狀態(tài)樹的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" ) );
現(xiàn)在,當我們運行應用程序時,我們可以在商店中找到所有與表單相關的狀態(tài)。
這是一個專用演示的源代碼。
React沒有內置的表單驗證邏輯。但我們可以使用第三方解決方案。所以它可以是一個DevKit,它可以是一個表單生成器,它可以是一個HOC或包裝器組件,將表單驗證邏輯混合到任意形式的內容中。我個人的方法是一個包裝組件,它依賴于HTML內置的表單驗證API并在表單和表單域的范圍內公開有效性狀態(tài)。
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。