示例
- 1. vscode扩展
- 2. 项目调试
- 3. 页面初始化
- 4. 原子操作
- 5. 控件
- 6. 文件转换
- 7. 文件服务
- 8. 获取ip
- 9. 自定义路由
- 10. 前置路由过滤器
- 11. 负载均衡
- 12. 两个单例服务
- 13. 第三方库的引用
1. vscode扩展
以下操作说明均以安装mmstudio扩展为前提.
2. 项目调试
2.1. 启动调试
启动项目调试的命令为npm t
,简单的,可以通过alt+d
快速打开终端并启动调试
2.2. 停止调试
找到启动命令的终端,按下ctrl+c
停止命令,注意windows上经常会出现无法停止的情况,这种情况下,杀掉该终端即可。
2.3. 重启调试
先停止调试,再次启动调试即可
需要重启调试的情况:
3. 页面初始化
3.1. 服务端渲染和浏览器端渲染的差别
比如我们要访问一个页面,这个页面初始化需要呈现出来:"render-demo",它的html片段见pages/render.html
如果是以上的html片段保存成一个文件render.html
,直接在浏览器打开就能正确呈现出来。但是它是完全静态的,我们试着把它进行动态化
我们需要先简单说明一下这个页面呈现的的详细过程
- 浏览器打开这个文件,读取文件内容,即以上代码,实际上它是一段文本
- 浏览器尝试解析这段文本,识别里面的各个标签,像
html
,body
,h1
,h2
,分析它们的结构。 - 浏览器将这些标签用正确的方式进行处理,其中会把
h1
和h2
用不同的字号以及合适的字体通过像素显示到显示设备上。 - 如果标签中有
<script>
标签,也会将其里面的脚本使用js引擎(chrome中为v8)执行。 - 在脚本执行过程中,如果脚本有对dom进行操作,页面会重新渲染
基于以上步骤,我们有两种不同的处理方式
- 如果我们在1和2之间将整个页面的文本处理好,浏览器在3就能正确解析并显示出来。
- 如果我们在4进行处理,同样在5时也能正确呈现出我们想要的效果
以上的两种不同的处理方式,第一种就是“服务端渲染”的思路,第二种就是“浏览器端”处理的逻辑
3.1.1. 在实际项目中应该选用哪种方式
- 服务端渲染任务在客户端进行,不占用服务器cpu资源
- 服务端渲染可以使用服务器缓存,大并发站点节省服务器cpu资源
- 综上,使用哪种方式需要综合评定,一般业务型站点,如无特殊需求,建议选择开发效率高,成本低的一种,无须理论拘泥。
3.2. 浏览器端渲染
相对来讲,这种方式较容易被开发人员理解和接受,其过程就是取数,然后渲染页面,如果页面有条件,则使用该条件查询和排序。以下为实战的操作顺序
-
新建页面(alt+p)pg001
-
因页面较为简单,作为示例,我们将其内容划分为一个组件
-
添加一个响应a001
-
将其设置为初始化事件,在s.ts中添加初始化事件:
'mm-events-init': 'a001'
-
将tpl中的部分内容生成渲染块,具体操作为:选中
<div><h1>mm</h1></h2>studio</h2></div>
,按下快捷键(alt+x)即可自动生成p01.tpl。注意渲染原子操作使用的是一个非常优秀的dot模板引擎 -
在响应a001.ts中引用渲染原子操作。
alt+t a
,选择页面操作类回车,再次选择渲染原子操作即可插入代码模板,填入相应参数值就可以,或者在alt+t a
直接输入原子操作编号即可快速定位至原子操作,回车插入代码 -
渲染的原子操作第二个参数为需要渲染的数据,通常这里会调用一个自定义的服务。我们来快速创建一个服务
alt+s
。 -
为了演示方便,这里我直接返回一个数组,但也模拟了排序条件改变,完整的服务代码见
pg001/zj-001/s001.ts
-
我们在组件的初始化响应中调用该服务
// 调用nodejs服务 const r1583660193 = await (() => { const service_name = 'pg001/zj-001/s001'; // 服务名称 const msg = { sort: r1583724505 }; // 参数 return aw4<string[]>(service_name, msg); })();
-
渲染的第三个参数我们通过另外一个原子操作获得
const r1583724505 = (() => { const key = 'sort'; // url参数名 const default_value = 'asc'; // 默认值,如果不希望使用默认值,可以删除该参数或者传入空值 return aw3(mm, key, default_value); })();
-
将以上步骤中服务返回的结果作为渲染的第二个参数,p01作为渲染的第三个参数,填入渲染的实参。注意渲染原子操作使用的是一个非常优秀的dot模板引擎
// html渲染 (() => { const data = r1583660193; // 要渲染的数据 const position = 'inner'; // inner 替换全部子结点 after 当前结点之后 before 当前结点之前 firstin 第一个子结点之前 lastin 最后一个子结点之后 return aw5(mm, data, p01, 'p01', position); })();
-
经过以上步骤,我们就完成了浏览器端的渲染,再复杂一些,可以使用原子操作把渲染的条件放到url参数中,渲染时取出。逻辑比较简单这里不作解释。
3.3. 服务端渲染
服务端的渲染整个思路较长,并且参数修改往往还会牵涉到一个页面生命周期迭代的问题。上面浏览器端的例子最后提到的条件放在url参数中即为这种方式的一个引子,如果各位能够理解,那么服务端渲染就好理解得多。
必须强调,虽然在调试时为了调试方便,服务端的部分代码是在浏览器中运行。但是实际运行环境中,一定一定不要在服务端的代码中使用任何浏览器特性的东西,比如alert,比如window,webStorage关键字等等,原子操作已经做过过滤,使用vscode扩展自动添加的代码不会存在这种问题,不能使用的原子操作是无法选择的。但如果存在大量开发人员手动添加的代码的情况,一定一定要保证这一点,或者出现某个页面本地调试正常,部署后无法正常加载的问题,也可以从这个思路进行排查问题。
步骤:
- 同浏览器端渲染1,2步。
- 添加服务端初始化响应,具体操作为:在组件的n.ts文件中按下快捷键
alt+a
创建响应,注意该操作一个组件只能操作一次,多次操作生成的多个响应并非系统问题,多余的响应也不会执行,请注意。设计如此,非bug。 - 创建tpl,步骤同浏览器端原子操作
- 添加一个服务
s001.ts
,方法和内容参见浏览器端原子操作 - 在响应
na001.ts
中添加服务调用并渲染,内容见pg002/zj-001/na001.ts
:
这个时候启动调试(alt+m d
)页面应该就可以看到效果了(如果之前开启过调试,也许需要先手动关闭,具体操作为在终端界面按下ctrl+d
)
4. 原子操作
在响应,服务,项目级原子操作中,均可引用原子操作。
4.1. 分类
原子操作分为通用型原子操作和项目级原子操作,通用型原子操作可以在任何项目中使用,其实现要求比较高。项目级原子操作则只要该项目可用即可。
4.2. 创建方法
通过vscode命令mmstudio: Add new atom
创建。
4.3. 通用型原子操作
注意事项:
- 最好先确定好原子操作的名称,因为它将展示给所有开发人员,尽可能将其描述精确、简练。
- 希望加入团队的人员请联系我.我将非常乐意接受社区的贡献。
- 原子操作要有单元测试,否则有可能将无法通过审核合并。
4.4. 项目级原子操作
可以在项目中实现某个通用操作,其代码实现制作为一个原子操作index.ts
(目前限定其为某一个函数),并将其插入代码模板写入use.snippet
。也可以将项目中常用的几个原子操作的使用编写为一个原子操作,在项目中快速引用。
4.5. snippet的写法
- 普通的文本按原样插入到使用位置
-
$
为一个特殊字符,如果要在代码模板中输入一个字符$
,必须加上转义符\
,即\$
方可。 - 如果
$
它后跟一个数字,如$1
,$2
,则表示一个停止符,在插入原子操作时通过tab键可以按$
后的数字切换光标位置。 -
$
后如果后跟大括号,如${1}
,${2}
,其作用同$1
,$2
。 -
$
后大括号中数字后如跟:
,如${1:val}
,则:
后为默认插入内容,当光标停留时默认内容将被选中,且可修改 -
$
后大括号中数字后如跟|
,需要保证大括号结束前也要有一个|
,如${1:|a,b,c|}
,则|
之间的内容当光标停留时将按,
分隔,作为枚举列表供选择,注意应当按实际使用场景调整顺序。 -
$CURRENT_SECONDS_UNIX
为一个数字值,通常它是唯一的,可以用它作为变量名
5. 控件
在tpl中可引用控件,在项目中可以调用控件。
5.1. 创建方法
通过vscode命令mmstudio: Add new widgets
创建。
5.2. 通用型控件
注意事项同通用型原子操作
5.3. 项目级控件
可以在项目中实现某个通用控件,控件用到一些封装方法和web组件及shadowdom的技术,不过其基础代码已添加,开发人员按照示例的方法添加自己的实现代码就好。并需其插入代码模板写入use.snippet
。也可以将项目中固定搭配的控件合并为一个,在项目中快速引用。
注意:
引用控件时请先选定要插入控件的位置,这样插入的代码模板才不会乱。
5.4. 控件方法的调用
因为typescript类型的关系,如果要使用控件mm-000001
,需在客户端响应中手动引入控件类型
import Widget1 from '@mmstudio/ww000001';
// 引入项目内控件
import Widget2 from '../../widgets/pw001';
const w1 = document.querySelector<Widget1>('#widgetid1')!;
w1.method01('foo', 'bar');
const w2 = document.querySelector<Widget2>('#widgetid2')!;
w2.method01('foo', 'bar');
6. 文件转换
6.1. 转pdf
使用服务端渲染的方法制作页面即可,然后将页面后缀(html)修改为pdf即可查看效果(如果是开发阶段,需要修改页面的端口为8889)
6.2. 转word文档
使用服务端渲染的方法制作页面,然后,然后将页面后缀(html)修改为xlsx即可查看效果(如果是开发阶段,需要修改页面的端口为8889)
6.3. 转xlsx表格
使用服务端渲染的方法制作页面,注意必须将页面使用table
渲染,然后,然后将页面后缀(html)修改为xlsx即可查看效果(如果是开发阶段,需要修改页面的端口为8889),这是一种相对简单的做法,无法满足复杂表格(如带有公式等高级功能的表格)
7. 文件服务
通常使用控件和原子操作来完成。以下为简单介绍。
7.1. 配置
需要配置一个文件数据库
mm.json
{
"minio": {
"endPoint": "127.0.0.1",
"port": 9000,
"accessKey": "mmstudio",
"secretKey": "Mmstudio111111",
"useSSL": false,
"region": "cn-north-1",
"partSize": 5242880
}
}
同时需要启动一个文件数据库minio
[sudo] docker-compose -f db.yaml up
version: '3.7'
services:
minio:
image: minio/minio
container_name: minio
command: server /data
volumes:
- /home/taoqf/data/minio:/data
ports:
- 9000:9000
environment:
MINIO_ACCESS_KEY: mmstudio
MINIO_SECRET_KEY: Mmstudio123
7.2. 上传
当前项目页面地址/fsweb/upload,支持一次上传多个文件
7.3. 下载
当前项目页面地址/fsweb/getfile?id=xxx,如果是图片,不添加download
参数直接展示在页面展示,如果要下载,请添加 download
参数,download
参数支持以下几种形态.
- /fsweb/getfile?id=xxx&downlaod
- /fsweb/getfile?id=xxx&downlaod=false
- /fsweb/getfile?id=xxx&downlaod=abc.jpe
如果一次性下载多个文件,请使用 /fsweb/getfile?id=xxx,yyy,zzz
7.4. 上传office文档,转换为ppt和图片
当前项目页面地址/fsweb/upload-office,支持选择性转换为图片。
7.5. 重新上传
当前项目页面地址/fsweb/reupload?id=xxx
7.6. 删除
当前项目页面地址/fsweb/delfile?id=xxx
8. 获取ip
在任意一个服务(s000.ts)中,可以通过msg.realip
获得客户端ip。注意,如果使用了反向代理,请设置请求头x-real-ip
或x-forwarded-for
,如果反向代理设置了其它的ip(比如真实情况的反向代理为第三方),一般也可以通过msg.headers.xxx
获取。
9. 自定义路由
在某些情况下,比如支付的回调等,我们需要第三方服务回调提供一些路由。
9.1. 添加通用服务
首先必须要先添加一个服务,由该服务响应该路由的请求,但也有可能是可能是某个组件下已完成的服务,这里以新建通用服务为例说明新建通用服务的方法。
因为我们希望把通用的服务单独列出目录来区分,比如希望通用的服务都放在src/services
目录下以方便管理。
因为新建服务的逻辑是需要打开一个可编辑文件,这样新建的服务会位于该可编辑文件相同的目录下。所以我们新建第一个服务时会用到一些技巧。
-
我们先在vscode中新建一个文件
src/services/mm
touch src/services/mm
-
点击打开这个文件.
-
alt+s
添加服务 -
删除第一次添加的文件
rm src/services/mm
-
编写服务逻辑
9.2. 添加路由
-
alt+r
添加路由 - 选中服务
services/s001
- 使用
get
请求访问该路由(也有可能是post
,put
,delete
,all
,根据第三方服务情况而定) - 重启项目调试
-
curl http://localhost:8889/r001
即可触发调用服务
9.3. 添加附加数据
有时候,一个服务的逻辑几乎能被多个路由调用,又有一些细微的差别,这个时候,为了项目维护方便,通常不建议复制并修改原服务。我们有两种方法来实现:
- 添加项目级服务端原子操作,将服务的逻辑封装起来,暴躁出某个参数,在不同服务中调用该原子操作。
- 在路由定义中附加上该参数,将多个路由同时关联同一个服务,在服务中通过msg.foo获取到参数进行逻辑判定处理。
路由定义中附加参数的方法
在mm.json中找到routers
下的多个路由,分别为它们附加参数(如foo
)
{
"routers": [
{
"method": "get",
"service": "searvices/s001",
"url": "/r001",
"data: {
"foo": "bar1"
}
},
{
"method": "get",
"service": "searvices/s001",
"url": "/r002",
"data: {
"foo": "bar2"
}
}
]
}
10. 前置路由过滤器
前置路由过滤器将提前将某个请求进行处理,其操作类似于添加路由。以下列出不同点:
-
前置路由过滤器对应的服务返回的结果中如果有
data
,该请求将提前返回,后续路由逻辑全部跳过,但可以设置cookie,和响应头header。 -
返回结果中可以有重定向
redirect
,如果有,请求也请提前返回,后续路由逻辑全部跳过,可同时设置cookie和响应头 -
前置路由示波器通常使用通配符作为url,以下几种示例,都是正确的写法
{ "filters": [ { "method": "get", "service": "searvices/s001", "url": "/*", "data: {} }, { "method": "get", "service": "searvices/s001", "url": "/*.html", "data: {} }, { "method": "get", "service": "searvices/s001", "url": "/mmstudio", "data: {} { "method": "get", "service": "searvices/s001", "url": "/m?studio", "data: {} { "method": "get", "service": "searvices/s001", "url": "/m+studio", "data: {} { "method": "get", "service": "searvices/s001", "url": "/mm(studio)?", "data: {} } ] }
11. 负载均衡
通常的nginx的反向代理,负载均衡就可以满足绝大多数大型应用(体量小的应用不用担心负载问题)
12. 两个单例服务
有些服务不能启动多个实例,所以需要独立部署为单例。这类服务在负荷大(非常大)时,将会拖慢整个应用的速度,所以在业务设计时,需要非常注意。
原理上来讲,这类服务是不可能通过增加结点实现的,除非修改设计,比如不在必要的时候不生成唯一的编码,使用uuid替代。
12.1. 编码服务
12.2. 定时任务
使用vscode扩展添加定时任务配置。
服务详情见定时任务服务
13. 第三方库的引用
使用第三方库的时候,由于第三方库的代码质量我们无法保证,所以可能会有以下问题,列出以待查阅分析:
13.1. 添加依赖
- 服务端原子操作如果引用第三方包,将务必将包依赖添加到package.json中的
dependencies
中。 - 客户端原子操作如果引用第三方包,将务必将包依赖添加到package.json中的
devDependencies
中。 - 控件如果引用第三方包,将务必将包依赖添加到package.json中的
devDependencies
中。
13.2. 全局引用
客户端原子操作或控件引用的第三包如果有全局引用,最常见的有jquery
,这一类引用在使用时通常会有两种问题:
- 需要在页面中通过script全局引入js文件
- 打包时多个对jquery依赖的控件有可能会冲突,造成找不到jquery对象的问题
- ts定义问题,使用全局还是模块引用需要权衡,多数国内jquery依赖的包质量不高,非常乱。
这里只指出解决问题的线索,具体问题请自行解决。
- 根目录下的gulpfile.js,本地调试及打包相关
- n.ts,打包相关
13.3. amd
虽然一些脚手架工具已经可以比较方便进行页面调试了,但是依然,会存在各种各样的奇怪问题,项目开发人员技术水平不过硬时,出现问题很难自行解决。并且通常地,当项目比较大时,执行速度过慢,从而导致开发效率降低。所以我们在开发过程中使用的是amd的加载方式,这种技术相对成熟,各种工具和第三方包的支持也比较好,万一出现问题也相对容易解决。但这会产生一些问题,即有部分第三方包并不提供amd的版本,虽然在打包时理论上可以使用,但无法开发调试。这就需要项目使用人员去开源贡献或是自行解决(配置amd.json文件,同时项目下添加amd的版本以供开发时使用,将commonjs版本修改umd版本并不复杂,通常只需要添加头尾即可)。amd.json的格式参看amd加载器中的相关说明。
13.4. TypeScript定义
有些第三方类库可能会缺少ts定义,如果是比较流行的类库,试试到@types
下找一找。示例(xxx为类库名称):
yarn add --dev @types/xxx
如果没有,可以到开源社区贡献。
13.5. 简单粗野的做法
如果不想贡献,还要使用第三方类库,在许可允许的范围内(哈哈),可以源码级引入,修改为ts,去掉全局依赖。当然,这种作法我个人并不推荐,但是当今国内开发的圈子里,这样做的不在少数,在项目工期比较紧且项目开发人员普遍素质不很高的情况下,这确实是项目开发成本比较低的一种方法,祝好运!