应公司要求,需要使用odoo系统作为文档管理,知识库管理,并且经个人分析出以下开发要点:

ps: 我不是产品经理,但是自己开发没产品经理给具体怎么开发的方案,那一切重点都要想清楚再动手

此篇博客长篇大论,本人希望自己以及后来人能明白这个的实现原理, 所以分析需要细致,中间存在的问题也尽量提一下

前提条件,已有基础:

odoo12原生企业版,自带文档功能

一、需求分析

1.公司要求文档功能支持在线预览

分析:

原生文档功能不支持多种格式在线预览的,只自带支持图片和mp4的在线预览,其他类似文档格式的文件,根本没有办法预览

个人分析,一个文档管理系统,公司使用者大概会上传和想要预览哪些文件?

答案是:office文档,图片,媒体,压缩包

office文档在线预览的话,通常要支持 doc,docx,xls,xlsx,ppt,pdf等格式

图片预览的话一般要支持png,jpg,jped,gif等常规图片格式

媒体预览的话一般要支持mp4,mp3,flv等常见视频音频格式

压缩包在线预览,在线解压缩和内部文件预览的话,抱歉,公司没给我那么多研发时间,直接不考虑,这块儿分析到了,但是我不做

2.公司数据库有经常挪动位置的需求

分析:

odoo系统的文件大部分是以base64格式存在数据库的binary字段里面的,并且原生功能不支持存储位置切换

odoo系统文件只在数据库字段还不行,还记录在data_dir配置参数所在目录,只搬移数据库不搬移data_dir目录的话,数据库里面的文件会丢失

odoo系统数据库和data_dir即使都搬移了,如果此数据库改了一下库名,那么对不起,文件也会丢失

造成的影响:

1.数据库持续存储文件,会越来越大,给数据库备份造成压力,到最后甚至无法备份

2.数据库迁移后,文件基本上都会丢失,影响恶劣

二、解决办法

1.针对原生系统无法在线预览多格式文件的问题,我考虑接入外部预览系统,本地只提取文件访问直链给该系统,即可实现预览

问:为什么不自己开发一个在线预览功能?

答:你开发一个试试?人家是一套专门的预览系统,让你自己去写个原生的office预览,各种算法,没个半年以上即使你是大牛那也得掂量掂量

既然人家有专业的系统了,咱们就不要重复造轮子了好不,即使造出来也没人家的优秀,公司也不可能给你那么多时间去搞这个

问:那需要自己本地搭建这个在线预览系统不?

答:那是肯定的,在线预览系统不是我开发的,但一定要在我本地用,别糊弄我丢个别人的在线预览接口,如果问为什么,那就是文档经过别人的服务器了,那不安全懂不

别人随便动动手就能把你的文件拿走去看看里面有什么

2.针对原生系统文件存储问题,我考虑接入外部存储系统。本地数据库只存文件的详情信息,不存文件的二进制数据

问:怎么又是接入外部存储系统,不自己开发一个

答:以后不要问这种问题了,鄙视重复造轮子懂不(其实是自己技术不够,公司也不给这么多时间啊)

问:接入外部存储系统你是怎么考虑的

答:我考虑过另一个服务器搭建一个 ftp,sftp,或者webdav,甚至去购买七牛云,阿里云,又拍云,鸡毛云的oos对象云储存来对接,但是最后都被我全部打死了

为什么呢?

1. frp文件传输方式老套,个人觉得它跟对象存储比起来,上传下载速度不是一个级别的,我称他龟速没毛病吧

2.webdav,新型的文件传输服务,但是速度嘛,只比ftp好一丢丢,名字厉害而已

3.对接对象云存储,这个好使啊,问题是烧钱。七牛云10g免费,最后实际用起来如果搞个几个TB,存储空间和流量费,一年不得几万?咱是穷人,公司也不愿意花这个成本的

最好的方案是自己出一台服务器,自己搭建个存储系统可以用就行了。勤俭持家的开发人员,处处为公司成本考虑,虽然节省的这费用公司也不会考虑给我加加工资,但是我也愿意这么去做

三、方案落实

思路前面讲的很清楚了,需要两条开发线

1.对接在线文件预览

2.对接外部文件存储

文件预览系统选择:

1. office预览选择 onlyoffice

支持几乎所有office格式及pdf在线预览

2.图片预览选择 viewer.js

支持几乎所有图片预览,可在线放大,旋转等等

3.媒体音视频在线播放选择 dplayer

支持大部分主流视频在线播放,可以倍速

外部文件存储选择:

minio,我叫他米牛,一个开源的类似亚马逊aws3的对象存储系统

四、系统搭建

图片预览和媒体预览,都是人家提供的一个js插件就行了,本地下载下来网页上嵌入使用

具体文档接入流程看上面的链接点进去就行了。这里只详细讲述office预览和文件存储系统的搭建教程

这里都按照docker容器化搭建,docker且安装了portainer面板 详情如下

1. onlyoffice搭建教程

在portainerUI中,创建名为mynet的网络,类型为bridge。网关为 172.20.10.11 ,子网掩码 172.20.0.0/16 ,范围内172.20.10.28/25 。
具体可根据实际情况设置。

在运行docker环境的服务器主机上依次敲命令完成下面镜像的运行:

安装redis redis的服务器地址为172.20.10.9 ,也可以根据容器名访问。 ping 容器名和ip试试。

docker run --name some-redis --restart=always --net mynet --ip 172.20.10.9 -d redis 

安装rabbitmq 地址为172.20.10.8

docker run --name some-rabbitmq --restart=always --net mynet --ip 172.20.10.8 -d rabbitmq 

安装postgresql 地址为172.20.10.1

docker run --name some-postgres --restart=always -p 5432:5432 --net mynet --ip 172.20.10.1 -e POSTGRES_PASSWORD=mypassword -d postgres

用navicate软件登陆数据库,数据库类型选postgres,地址是你的这台服务器ip,端口是5432,需要防火墙开放端口5432。然后新建查询执行下面的命令

CREATE DATABASE onlyoffice --创建数据库
CREATE USER onlyoffice WITH password 'onlyoffice' --创建账号
GRANT ALL privileges ON DATABASE onlyoffice TO onlyoffice --设置账号和数据库的关联权限

安装 onlyoffice 选择合适的镜像 alehoho/oo-ce-docker-license

docker run --name=onlyoffice --restart=always --detach --publish=8033:80 --net mynet --ip 172.20.10.5 -e LANGUAGE=zh_CN:zh -e JWT_ENABLED=true -e JWT_IN_BODY=true -e JWT_SECRET=secret -e DB_TYPE=postgres -e DB_HOST=172.20.10.1 -e DB_PORT=5432 -e DB_NAME=onlyoffice -e DB_USER=onlyoffice -e DB_PWD=onlyoffice -e AMQP_URI=amqp://guest:guest@172.20.10.8:5672 -e REDIS_SERVER_HOST=172.20.10.9 -e REDIS_SERVER_PORT=6379 alehoho/oo-ce-docker-license

上面这个镜像如果有问题的话,还可以用下面的备用。我自己pull到本地再push到自己仓库的

docker pull hjdhnx/onlyoffice:210425docker run --name=onlyoffice --restart=always --detach --publish=8033:80 --net mynet --ip 172.20.10.5 -e LANGUAGE=zh_CN:zh -e JWT_ENABLED=true -e JWT_IN_BODY=true -e JWT_SECRET=secret -e DB_TYPE=postgres -e DB_HOST=172.20.10.1 -e DB_PORT=5432 -e DB_NAME=onlyoffice -e DB_USER=onlyoffice -e DB_PWD=onlyoffice -e AMQP_URI=amqp://guest:guest@172.20.10.8:5672 -e REDIS_SERVER_HOST=172.20.10.9 -e REDIS_SERVER_PORT=6379 hjdhnx/onlyoffice:210425

特别要注意的是:-e JWT_SECRET=secret 这个是秘钥,不要暴露给别人知道。 变量的配置文件在容器中的位置/etc/onlyoffice/documentserver/local.json
这个秘钥的值,要和实例代码配置中的值对应。 如果不设置jwt的验证功能(环境变量 JWT_ENABLED JWT_IN_BODY JWT_SECRET 都不设,且代码settings.config文件中的files.docservice.secret为空)则不进行身份验证

官方提供的各种语言对接的demo地址

进入后下载python的

会python开发的我想大家都懂django吧,它这个例子很简单,随便看看代码就会了,然后自己开发odoo模块实现类型的功能和接口

最后讲一下必不可少的https搭建访问

将上面运行docker的内置端口映射改一下即可

重点就是你在 docker run镜像images,生成了一个容器container对吧,这里特别注意,平常我们映射容器的80端口出来8033,而现在,你必须映射443端口出来8033哦。

docker run --name=onlyoffice --restart=always --detach --publish=8033:443 --net mynet --ip 172.20.10.5 -e LANGUAGE=zh_CN:zh -e JWT_ENABLED=true -e JWT_IN_BODY=true -e JWT_SECRET=secret -e DB_TYPE=postgres -e DB_HOST=172.20.10.1 -e DB_PORT=5432 -e DB_NAME=onlyoffice -e DB_USER=onlyoffice -e DB_PWD=onlyoffice -e AMQP_URI=amqp://guest:guest@172.20.10.8:5672 -e REDIS_SERVER_HOST=172.20.10.9 -e REDIS_SERVER_PORT=6379 hjdhnx/onlyoffice:210425

下面是推荐docker容器内文件挂载到本地的完整配置,将容器内的 /var/log/onlyoffice 和  /var/www/onlyoffice/Data 目录映射出来

完整代码

cd /home
mkdir office
cd office
mkdir log
mkdir datadocker run --name=onlyoffice --restart=always --detach --publish=8033:443 --net mynet --ip 172.20.10.5 -v /home/office/log:/var/log/onlyoffice -v /home/office/data:/var/www/onlyoffice/Data -e LANGUAGE=zh_CN:zh -e JWT_ENABLED=true -e JWT_IN_BODY=true -e JWT_SECRET=secret -e DB_TYPE=postgres -e DB_HOST=172.20.10.1 -e DB_PORT=5432 -e DB_NAME=onlyoffice -e DB_USER=onlyoffice -e DB_PWD=onlyoffice -e AMQP_URI=amqp://guest:guest@172.20.10.8:5672 -e REDIS_SERVER_HOST=172.20.10.9 -e REDIS_SERVER_PORT=6379 hjdhnx/onlyoffice:210425
这样容器启动后,你在宿主机 /home/office/data目录里面执行操作,创建certs目录
mkdir certs

将自己已有的https证书私钥文件和公钥文件拷贝进certs目录

并且分别改名为  onlyoffice.key 和 onlyoffice.crt,最后重启容器,即可通过 https://域名:8033 访问到onlyoffice的服务了,正常情况访问能看到welcome就是搭建成功了。

请注意,我图中展示的路径和文件位置,是我宿主机上的,把容器内的  /var/www/onlyoffice/Data 映射到的本地宿主机的 /home/office/data

后续可能出现的维护性问题补充:

预览文件提示现在无法打开文件,且去看onlyoffice容器内日志显示

Error: getaddrinfo ENOTFOUND yourdomain.com

解决办法,配置host映射,涉及命令

vi /etc/hosts # 编辑host文件在里面添加映射 如 ip domain
14.215.177.39 www.baidu.com然后执行重启网络
network restart完毕后重启docker服务
service docker restart最后看看docker容器是否正常,应该就没有问题了

2.minio搭建教程

这个很简单,一堆docker命令敲就完了,顺着敲。至于什么意思,自己理解每条命令看着改就行了

cd /home
mkdir minio
cd minio
mkdir data
mkdir config
docker run --name minio -v /home/minio/data:/data -v /home/minio/config:/root/.minio -p 8039:9000 --restart=always -e "MINIO_ACCESS_KEY=admin" -e "MINIO_SECRET_KEY=openerphk" minio/minio server /data
Ctr + C
docker start minio

然后这个东西界面上可以进去访问,类似个网盘,没啥复杂的操作

在这里操作也不是要点,只是表示搭建完了。

这文件预览系统和文件存储系统如果要上公司的生产环境,记得喊运维大哥给这俩地址配置一下ssl证书,能支持https访问才行。

你自己玩的话也可以自己配

正式环境minio系统配置https教程很简单,主要理解官方文档才行:

MinIO | Learn how to secure access to MinIO server with TLS

官方文档推荐的2种方式

1.使用已有的CA证书

如果都是按照上面的搭建教程来的话,请把已经有的证书放在 /home/minio/config/certs 目录,详情见图:

其中 私钥的文件名一定是 private.key ,公钥的文件名一定是 public.crt

根据官方的说法,某些机制颁发的证书,因为生成工具不同,文件名也有所差别

将 cert.pem 重命名为public.crt,将key.pem 重命名为 private.key.

注意,证书位置放好过后直接重启容器就好,不需要自己配置反向代理,不需要配置反向代理,不需要配置反向代理,重要的事情说三遍。

如何访问?

重启容器后,地址栏按原来访问形式,把http改成https即可。

比如:  https://minio.baidu.com:8039/ ,你没有看错,https的端口号自定义了,不是默认的443,请不要惊慌,这就是你docker容器映射到宿主机 的minio服务的端口

其中 minio.baidu.com 是你自己设置的米牛服务器所在的的二级域名指向,改成你自己的。

这里配置成功后,sdk调用米牛云的api,记得把 secure 参数改成true

client = Minio('minio.baidu.com:8039',# mini服务的地址加端口,注意不要在前面写http之类的东西 https://minio.baidu.com/access_key="admin",  # 账号secret_key="passwd",  # 密码secure=True  # 设置非https,默认是https)

2.使用openssl生成一个免费的证书

注意,第一步能解决的就不需要再进行第二步了,第二步是根据没钱买证书的人的

1.生成私钥

openssl genrsa -out private.key 2048

2.编辑保存配置文件

vi openssl.conf

下面是配置文件内容,改一改粘贴进openssl.conf里面

官方说的是 Create a file named openssl.conf with the content below. Set IP.1 and/or DNS.1 to point to the correct IP/DNS addresses:

[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no[req_distinguished_name]
C = US
ST = VA
L = Somewhere
O = MyOrg
OU = MyOU
CN = MyServerName[v3_req]
subjectAltName = @alt_names[alt_names]
IP.1 = 127.0.0.1
DNS.1 = localhost

3.根据配置生成公钥

openssl req -new -x509 -nodes -days 730 -key private.key -out public.crt -config openssl.conf

剩下的就跟第一步差不多了,复制文件到  /home/minio/config/certs 再重启容器

好了,https正常访问了,本人亲自动手成功的,你失败的话可不怪我。

五、核心代码对接部分示例

1.minio配置储存桶策略的两种方式,客户端配置或代码配置

客户端配置的话,去这里下载windows客户端

然后cmd要敲的代码,记得自己替换 米牛系统主页地址 为你搭建好的那个minio主页地址:

mc alias set minio 米牛系统主页地址 admin openerphk --api s3v4
mc --json ls play
mc policy
mc policy set download minio/test
mc config host ls

客户端配置策略啥的,作为我这样的开发人员是不需要的,一般代码里动态配置就行了,更简单

贴一段mino工具的python核心代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File  : minioUpload.py
# Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
# Date  : 2021-04-19import logging
from minio import Minio
from minio.commonconfig import REPLACE, CopySource
from minio.error import S3Error
import json_logger = logging.getLogger(__name__ + '米牛上传')class minioUploadClass:def __init__(self, cfg):"""初始化配置,储存桶不存在则顺带创建桶:param cfg:"""global configconfig = cfgself.config = cfg# print(config.minio_host.rstrip('/'),config.minio_access_key,config.minio_secret_key,config.minio_secure)minio_host = config.minio_host.rstrip('/').split('//')[1] if '//' in config.minio_host else config.minio_host.rstrip('/')self.client = Minio(minio_host,  # mini服务的地址加端口,注意不要在前面写http之类的东西,如 dz.mudery.com:8039access_key=config.minio_access_key,  # 账号secret_key=config.minio_secret_key,  # 密码secure=config.minio_secure  # 设置非https,默认是https)bucket = config.minio_bucket  # 储存桶found = self.client.bucket_exists(bucket)if not found:self.client.make_bucket(bucket, 'cn-north-1')  # 创建中国北部的桶self.client.set_bucket_policy(bucket, self.gen_bucket_policy(bucket))_logger.info(f'bucket_policy for {bucket} set ok')@staticmethoddef gen_bucket_policy(bucket):"""生成桶策略:param bucket::return:"""policy = {"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"AWS": "*"},"Action": ["s3:GetBucketLocation","s3:ListBucket","s3:ListBucketMultipartUploads",],"Resource": f"arn:aws:s3:::{bucket}",},{"Effect": "Allow","Principal": {"AWS": "*"},"Action": ["s3:GetObject","s3:PutObject","s3:DeleteObject","s3:ListMultipartUploadParts","s3:AbortMultipartUpload",],"Resource": f"arn:aws:s3:::{bucket}/images/*",},],}return json.dumps(policy, ensure_ascii=False)def put_object(self,bucket_name,object_name,data,length,content_type='application/octet-stream',metadata=None):"""以二进制字节流形式上传文件:param bucket_name:  存储桶名称。:param object_name:   对象名称。:param data: 任何实现了io.RawIOBase的python对象。 如 io.BytesIO(bytes):param length: 对象的总长度。 len(bytes):param content_type: 对象的Content type。(可选,默认是“application/octet-stream”)。:param metadata: 其它元数据。(可选,默认是None)。:return: etag 对象的etag值。"""return self.client.put_object(bucket_name,object_name,data,length,content_type,metadata)def copy_object(self,bucket_name,object_name,object_source,copy_conditions=None, metadata=None):"""拷贝对象存储服务上的源对象到一个新对象。注意:本API支持的最大文件大小是5GB。:param bucket_name: 新对象的存储桶名称。:param object_name: 新对象的名称。:param object_source: 要拷贝的源对象的存储桶名称+对象名称。/winbao/1.doc:param copy_conditions:拷贝操作需要满足的一些条件(可选,默认为None)。:param metadata::return:"""return self.client.copy_object(bucket_name,object_name,object_source,copy_conditions,metadata)def fput_object(self,*args):return self.client.fput_object(*args)def stat_object(self,bucket_name, object_name):"""获取对象的元数据。:param bucket_name:   存储桶名称。:param object_name:  文件名称:return: 对象的统计信息,格式如下:obj.size int 对象的大小。obj.etag  string  对象的etag值。obj.content_type   string  对象的Content-Type。obj.last_modified   time.time   UTC格式的最后修改时间。obj.metadata   dict    对象的其它元数据信息。"""return self.client.stat_object(bucket_name, object_name)def remove_object(self,bucket_name, object_name):"""删除一个对象。:param bucket_name: 存储桶名称。:param object_name: 对象名称。:return:"""return self.client.remove_object(bucket_name, object_name)def move_object(self,bucket_name, object_name,bucket_name_new,object_name_new):"""移动一个对象:param bucket_name: 原桶:param object_name: 原文件名:param bucket_name_new: 新桶:param object_name_new: 新文件名:return: boolean 是否移动成功"""found = self.client.bucket_exists(bucket_name_new)if not found:self.client.make_bucket(bucket_name_new, 'cn-north-1')  # 创建中国北部的桶self.client.set_bucket_policy(bucket_name_new, self.gen_bucket_policy(bucket_name_new))_logger.info(f'bucket_policy for {bucket_name_new} set ok')try:# object_source = f'/{bucket_name}/{object_name}'object_source = CopySource(bucket_name,object_name)# print(object_source)self.copy_object(bucket_name_new,object_name_new,object_source)self.remove_object(bucket_name,object_name)return Trueexcept Exception as e:# print(e)_logger.error(f'{e}')return Falsedef remove_objects(self,*args):return self.client.remove_objects(*args)

其中关键的初始化参数 cfg是数据库的一条对象记录,也就是odoo文档增强的配置表的记录,仅供参考,配置表minio相关的部分截图参考。

然后我推荐大家做好文件防盗链相关的安全服务,我这里做了一个简单的rsa加密防盗链,接入自己系统的短链接api,实现文件直链的过期时间验证与自定义

2. onlyoffice相关的配置,大概也需要搞个数据库表的字段来这样设置,至于怎么对接,前面说了参考官方python的demo,django代码通俗易懂

都是html代码,没人想看,我这里就不贴代码了,最后补充一下完整的odoo上配置表的界面与成品,有点繁琐,仅作参考

odoo系统这边需要改造原生的文档 上传,下载,分享,替换等功能,左上角增加在线预览按钮

配置增加全局存储方式,附件字段扩展 文件桶,存储方式等字段。就不细说了

最后效果是,在odoo文档功能里面上传文件,如果全局存储方式配置的minio,自动把文件传到预览设置的minio默认桶里面去了

odoo数据库里面只存文件除二进制值以外的信息,数据库不会增大体积,文件在minio那边也不会丢失

在odoo文档里面点击选中文档后点击在线预览按钮,会根据文件类型跳到不同的预览界面,office预览,图片预览,视频在线播放

好了,教程到此结束,只做自己的记录以及其他后来者的参考,伸手党要代码的别来找我,代码是不会给的。谢谢大家观看此博客,我写这篇博客也花了不少时间,但是以后回来看就方便了。

2021-06-21后续补充:

图片存储时进行选择性压缩。压缩图片核心代码如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# File  : imgConvertUtils.py
# Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------
# Date  : 2021-06-21from PIL import Image
from PIL import ImageFile
import math
import ioImageFile.LOAD_TRUNCATED_IMAGES = Trueclass ImageConvert:def __init__(self):passdef convert2rb(self,input_rb,targetWidth,quality=95):input_file = io.BytesIO(input_rb)sImg = Image.open(input_file)ext = sImg.formatw, h = sImg.sizerate = round(targetWidth / w, 4)height = math.floor(rate * h)dImg = sImg.resize((targetWidth, height), Image.ANTIALIAS)output = io.BytesIO()flag = 'RGBA' if sImg.format.lower() == 'png' else 'RGB'dImg.convert(flag).save(output, format=ext, quality=quality)return output.getvalue()if __name__ == '__main__':with open(r'D:\Desktop\报销发票附件样例\2.jpg',mode='rb') as f:input_rb = f.read()img = ImageConvert()output_rb = img.convert2rb(input_rb,1080)with open(r'D:\Desktop\报销发票附件样例\输出4.jpg','wb+') as f:f.write(output_rb)

odoo本地文档功能开发记录相关推荐

  1. 国内研发!适用于安卓应用程序的 Word文档功能开发组件来啦!

    你是否在寻找一款工具能够在Android应用程序中的Word文档管理的开发工具?那么,好消息来啦!Spire系列文档开发组件又添新成员,专门用于在 Android 手机应用程序中创建.读取.操作和转换 ...

  2. 好程序员技术文档HTML5开发中的javascript闭包

    好程序员技术文档HTML5开发中的javascript闭包,事实上,通过使用闭包,我们可以做很多事情.比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率,同时避免对命 ...

  3. 开发接口文档_更优更稳更好,看文档驱动开发模式在AIMS中的优势

    ​[摘要]程序员常会说:我最讨厌别人写的代码没有文档,我也最讨厌自己需要写文档. 有一个很老的梗: 我最讨厌别人写的代码没有文档,我也最讨厌自己需要写文档. 有这种想法的程序员应该算是一个老鸟了,对于 ...

  4. spring容器_Spring容器文档阅读要点记录

    Spring容器文档阅读要点记录 相关的库代码位于 org.springframework.beans 和 org.springframework.context包下面 容器的基本的接口 基本接口:B ...

  5. 【Java实现导出Word文档功能 XDocReport +FreeMarker】

    Java实现导出Word文档功能(XDocReport +FreeMarker) 前言 在日常的开发工作中,我们时常会遇到导出Word文档报表的需求,比如公司的财务报表.医院的患者统计报表.电商平台的 ...

  6. android 读取wps_Android 默认使用wps打开本地文档

    最近开发一个需求是使用wps打开本地的文档,所以记录一下方便以后查阅 对于Android 7.0 以后文件的读写 请参照一下链接 https://www.jianshu.com/p/5ebfa842e ...

  7. Tips--git bash管理本地文档的常用命令

    git bash在管理本地文档中的常用命令 1. 序 2. 初始化本地仓库 3. 创建master主干支 4. 创建分支 5. 切换分支 6. 提交分支修改 7. 合并分支 8. GitLab 配置过 ...

  8. python生成word文档_python实现的生成word文档功能示例

    本文实例讲述了python实现的生成word文档功能.分享给大家供大家参考,具体如下: 每月1次的测试费用报销,需要做一个文档.干脆花点时间写个程序吧. # -*- coding: utf-8 -*- ...

  9. Unity 之 实现读取代码写进Word文档功能实现 -- 软著脚本生成工具

    Unity 之 实现读取代码写进Word文档功能 前言 一,实现步骤 1.1 逻辑梳理 1.2 用到工具 二,实现读写文件 2.1 读取目录相关 2.2 读写文件 三,编辑器拓展 3.1 编辑器拓展介 ...

最新文章

  1. linux串口驱动分析
  2. 布兰森:激励是最好的加速器
  3. navcat设置oracle表主键自增_初识 Oracle 表空间设置与管理
  4. 基于spring boot的统一异常处理
  5. SQL结构化查询语言中的LIKE语句
  6. php smarty 限制显示字数,smarty现在显示字数的各种写法
  7. drools6.5_Drools 6.2.0.Final发布
  8. Django运维后台的搭建之四:用bootstrap模板让运维前台变得更漂亮
  9. c#语言程序设计上机实验,C#语言程序设计基础实验指导(第3版)
  10. python使用格式化教程_软件测试教程之python格式化输出format用法
  11. 远程控制篇:抓取远程屏幕图像
  12. 全新版本的Tidy Up 5 Mac最新版!重复文件查找和磁盘清理工具
  13. 编程语言新宠儿——Julia诞生记(转)
  14. 道路照明之电缆线路 - 设计笔记
  15. 联想计算机M.2固态银盘,联想ThinkPad T14拆机加装内存和M.2固态硬盘
  16. QAbstractItemModel+qtreeview
  17. ACL2021 | 任务型和开放域对话系统
  18. SpringCloudAlibaba——Nacos实现原理详解
  19. ipa文件反编译_iOS 逆向工程-反编译ipa包
  20. FastStone注册码

热门文章

  1. ue4 联机烘焙出现问题和解决方式
  2. 正则表达式验证邮箱格式
  3. Windows环境下(64bit,内存4G),安装虚拟机(VM12.5),Ubuntu(17.04),anaconda(Python3.6)和TensorFlow(1.3)
  4. 微信小程序带给我们哪些便利
  5. Lucene随笔-Lucene的索引文件格式
  6. Java基础学习笔记4
  7. realtek没有禁用前面板_教你win7系统realtek禁用前面板插孔检测的具体步骤
  8. mysql设置约束大于0_MySQL约束
  9. 给程序员的 8 个提高开发效率的建议
  10. 实用工具---各种工具安装使用