Upload
others
View
9
Download
0
Embed Size (px)
Citation preview
目录ThinkItCMS架构简介ThinkItCMS开发环境准备ThinkItCMS服务端部署ThinkItCMS前端项目部署ThinkItCMS如何访问门户ThinkItCMS开发说明
工具类说明目录结构简介ThinkItCMS接口格式约定ThinkItCMS缓存技术的使用
ThinkItCMS功能开发详解如何扩展一个自定义标签
xxxToMap二次封装说明如何扩展自定义定时任务如何切换第三方对象存储
ThinkItCMS如何实现断点续传ThinkItCMS讲解文章的生成过程
ThinkItCMS三大组件分析从一个请求分析文章的创建过程
ThinkItCMS设计模式的应用案例ThinkItCMS标签指令详解
-2-本文档使用看云构建
ThinkItCMS架构简介ThinkItCMS技术栈简介
ThinkItCMS采用目前最流行的JEE架构SpringBoot开发的一个CMS系统其中涉及到不少开源的技术本次说明
一下其中的技术框架有如下:
数据库:mysql+druid连接池
ORM技术:Mybatis+MybatisPlus
权限技术:SpringSecurity+OAuth2
模板技术:Freemark
定时任务技术:Quartz
缓存技术:Redis
日志技术:Slf4j
检索技术:Solr
文件服务器:Fastdfs
消息通知:WebSocket
项目部署容器:nginx(nginx用于部署静态页和管理端页面)可通过反向代理访问server以上就ThinkItCMS
用到的一些技术框架
PS:本文档为收费文档(不定期完善更新,一次付费终身使用),本人制作也需要花费大量的时间和精力,希望广大朋友支持一下。
ThinkItCMS架构简介
-3-本文档使用看云构建
ThinkItCMS开发环境准备特别说明
请务必按照本文档部署运行章节进行操作,减少踩坑弯路!!
环境说明
中间件 版本 备注JDK 1.8 强制要求MySQL 5.7+ 强制要求Redis 3.2+ 没测试以下版本node 8.0+npm 6.0+
本人采用idea开发其中用到了Lombok插件,如果不安装插件会导致代码异常无法编译
Lombok插件
如果当前你使用的ide未安装lombok会导致代码异常无法编译.lombok能够达到的效果就是在源码中不需要写
一些通用的方法,但是在编译生成的字节码文件中会帮我们生成这些方法,减少代码冗余.
安装插件方法取自csdn可以自行查看
IntellijIDEA安装lombok及使用详解
原创zhglance最后发布于2017-02-0819:15:40阅读数198892收藏
展开
项目中经常使用bean,entity等类,绝大部分数据类类中都需要get、set、toString、equals和hashCode
方法,虽然eclipse和idea开发环境下都有自动生成的快捷方式,但自动生成这些代码后,如果bean中的属性一
旦有修改、删除或增加时,需要重新生成或删除get/set等方法,给代码维护增加负担。而使用了lombok则不一
样,使用了lombok的注解
(@Setter,@Getter,@ToString,@@RequiredArgsConstructor,@EqualsAndHashCode或@Data)之后,就不
需要编写或生成get/set等方法,很大程度上减少了代码量,而且减少了代码维护的负担。故强烈建议项目中使用
lombok,去掉bean中get、set、toString、equals和hashCode等方法的代码。
LombokIDEA安装方法
LombokECLIPSE安装方法
文件服务器安装教程
文件服务器安装教程所需要资源
LinuxJdk安装教程
Redis安装教程
ThinkItCMS开发环境准备
-4-本文档使用看云构建
solr安装solr是一款Solr是一个高性能,采用Java开发,
Solr
基于Lucene的全文搜索服务器。同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配
置、可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎。
ThinkItCMS采用solr作为全文检索服务器其中一些字段已经配置过了,如果你需要扩展字段可以自行设置。因
为solr提供了内置的jetty我们可以直接以jetty容器运行。下载ThinkItCMS提供的ThinkItCMS进入bin
目录后执行linux使用solrstart启动linux下使用solrstart-force启动solr当看到以下界面说明solr启动
成功
接下来我们可以通过访问http://127.0.0.1:8983/solr/#/
后会看到如下:
说明成功部署。后就可以直使用了
ThinkItCMS开发环境准备
-5-本文档使用看云构建
ThinkItCMS服务端部署环境说明
中间件 版本 备注
JDK 1.8强制要求,1.8以上版本请自行添加JavaEE相关jar包
MySQL 5.7.8+ 强制要求,至少5.7!当然8.0也没有问题
Redis 3.2+windows版只能使用Redis3.2,类Unix系统使用最新的5.0也没有关系
node 10.0+ 本人使用v10.15.0npm 6.0+maven 3.5+
一、项目下载
gitclonehttps://gitee.com/slfj/lb-cms-service.git
二、solr部署
下载solr文件开始部署链接:https://pan.baidu.com/s/1VOGREGugl9snE-sH2BYIXg
提取码:i2lk
三、初始化数据库
参数说明
新建数据库xxx找到cms.sql导入sql文件版本:mysql5.7.8+
默认字符集:utf8mb4
默认排序规则:utf8mb4_general_ci
修改yml文件spring:
datasource:
url:jdbc:mysql://127.0.0.1:3306/xxx?serverTimezone=Asia/Shanghai&allowMult
iQueries=true&zeroDateTimeBehavior=CONVERT_TO_NULL
ThinkItCMS服务端部署
-6-本文档使用看云构建
username:yourdbusername
password:yourdbpassword
type:com.alibaba.druid.pool.DruidDataSource
driverClassName:com.mysql.cj.jdbc.Driver
redis也一样自行修改成你自己的参数
redis:
host:127.0.0.1
port:6379
database:0
timeout:10000s
password:
jedis:
pool:
max-idle:20
min-idle:5
max-active:20
max-wait:10000s
fastdfs也需要改成你自己的配置
fdfs:
so-timeout:1501
connect-timeout:601
thumb-image:#缩略图生成参数width:150
height:150
tracker-list:#TrackerList参数,支持多个-xxx.xxx.xxx.xxx:22122
接下来注意了:
thinkitcms:
sourceRootPath:资源的根目录用于存放模板和页面片段以及静态页等资源(如:E:\thinkitcms
)sourceTempPath:${thinkitcms.sourceRootPath}/templates(新建的模板都会存放在该目录)sourceFragmentFilePath:${thinkitcms.sourceTempPath}/fragment(新建的页面片段都会存放在该目录)siteStaticFileRootPath:${thinkitcms.sourceRootPath}/webfile(生成的静态页将会自动将该目录作为根目录,后期通过nginx部署静态网站时可以通过该目录作为根目录指向部署)siteStaticBackupRootPath:${thinkitcms.sourceRootPath}/backup(静态资源备份目录没什么用该功能将会在下一个版本中取消)siteDomain:http://www.thinkitcms.top(填写你自己的域名或者ip用于在生成静态页时定位资源用)
ThinkItCMS服务端部署
-7-本文档使用看云构建
serverApi:http://s.thinkitcms.top(填写你自己的域名或者ip用于在生成静态页时js调用后台接口时用)siteFdfsDomain:(用于上传文件完成后自动拼接地址供前端访问)baiDuTongJiUrl:(百度统计pc端地址)baiDuTongjiUrlM:(百度统计mobile端地址)
项目启动后会在sourceRootPath配置的目录下创建一些文件夹就是sourceTempPath以及
sourceFragmentFilePath配置的目录等都会在项目启动时创建。
上诉环境配置完成后如上图:运行框红文件的main方法
当控制台出现类似于下面表明后台启动成功
2020-03-2523:09:44,378-Recovering0jobsthatwerein-progressatthetimeofthelastshut-down.
2020-03-2523:09:44,378-Recoverycomplete.
2020-03-2523:09:44,379-Removed0'complete'triggers.
2020-03-2523:09:44,382-Removed0stalefiredjobentries.
2020-03-2523:09:44,385-SchedulerclusteredScheduler00_$_NON_CLUSTEREDstarted.
2020-03-2523:09:44,399-StartingProtocolHandler["http-nio-8081"]
2020-03-2523:09:44,454-Tomcatstartedonport(s):8081(http)withcontextpath''
2020-03-2523:09:44,458-StartedThinkItCMSApplicationin22.405seconds(JVMrunningfor26.035)
ThinkItCMS服务端部署
-8-本文档使用看云构建
如何访问管理端呢?下一篇介绍
ThinkItCMS服务端部署
-9-本文档使用看云构建
ThinkItCMS前端项目部署特别说明
请务必按照本文档部署运行章节进行操作,减少踩坑弯路!!
安装node&npm
官网下载node安装包,内置npm
https://nodejs.org/zh-cn/
检查安装是否正常
下载前端代码
gitclonehttps://gitee.com/slfj/lb-cms-vue.git
安装cnpm镜像
可以省略此步骤主要是阿里的仓库快一些,如果不着急的话用自带的地址就行,我没测试过阿里的仓库不知道会不会有不存在的依赖直接执行:npminstall或者更换镜像后在install
如下更换成阿里的镜像
npminstall-gcnpm--registry=https://registry.npm.taobao.org
安装依赖
如果没有切换cnpm的话就用npminstall否则的话cnpminstall
cnpm/npminstall
没有更换阿里的话可以直接npminstall
ThinkItCMS前端项目部署
-10-本文档使用看云构建
复制
启动
npmrunserve
打包
npmrunbuild
特别说明
npminstall过程中可能由于网络关系等,提示报错,请删除
如何修改接口请求地址如下所示:
将接口地址改成你的服务端地址和端口号
ThinkItCMS前端项目部署
-11-本文档使用看云构建
ThinkItCMS如何访问门户门户的访问和生成
在访问门户之前我们需要先了解一下门户文件是怎么生成的,
首先门户文件不和咱们的springmvc或者其他的springboot的项目采用jsp或者themleaf模板直接访问后台
服务地址就能访问的。
门户是静态的,是通过系统生成的静态html文件,你可以把这些文件放到tomcat或者nginx等容器下就可以
访问,每次更改数据都会重新生成html,那么总不能手动去复制到tomcat容器或者nginx容器吧?
是的,所以我们需要配置,本系统默认采用nginx作为容器来进行访问,也就是说我们把静态文件生成到指定位
置
~~~
thinkitcms:
sourceRootPath:E:\blog
sourceTempPath:${thinkitcms.sourceRootPath}/templates
sourceFragmentFilePath:${thinkitcms.sourceTempPath}/fragment
siteStaticFileRootPath:${thinkitcms.sourceRootPath}/webfile
siteDomain:http://127.0.0.1/
serverApi:http://127.0.0.1/
siteFdfsDomain:http://129.211.25.158/
baiDuTongJiUrl:https://tongji.baidu.com/web/29891141/overview/index
baiDuTongjiUrlM:https://tongji.baidu.com/m/welcome#/report/14347968
allowMultiLogin:true
startSolr:true
licensePath:${thinkitcms.sourceRootPath}/license
pluginsBasePath:${thinkitcms.sourceRootPath}/plugins
~~~
这里的siteStaticFileRootPath就是默认的生成位置,然后我们可以通过配置nginx指定该位置为server的
root路径这样就可以自动发布了
在这里我分享一下我的nginx的配置文件你值得拥有:
#usernobody;
worker_processes1;
#error_loglogs/error.log;
#error_loglogs/error.lognotice;
#error_loglogs/error.loginfo;
#pidlogs/nginx.pid;
events{
worker_connections1024;
ThinkItCMS如何访问门户
-12-本文档使用看云构建
}
http{
includemime.types;
default_typeapplication/octet-stream;
log_formatmain'$remote_addr-$remote_user[$time_local]"$request"'
'$status$body_bytes_sent"$http_referer"'
'"$http_user_agent""$http_x_forwarded_for"';
access_loglogs/access.logmain;
sendfileon;
#tcp_nopushon;
#keepalive_timeout0;
keepalive_timeout65;
client_header_buffer_size512k;
large_client_header_buffers4512k;
client_max_body_size10M;
gzipon;
server{
listen80;
server_names.thinkitcms.com;
#charsetkoi8-r;
#access_loglogs/host.access.logmain;
location/{
proxy_set_headerHost$host;
proxy_set_headerX-Real-IP$remote_addr;
proxy_set_headerX-Forwarded-For$proxy_add_x_forwarded_for;
proxy_set_headerX-NginX-Proxytrue;
proxy_http_version1.1;
proxy_set_headerUpgrade$http_upgrade;
proxy_set_headerConnection"upgrade";
proxy_passhttp://127.0.0.1:8081;
}
location/group1/M00{
root/usr/fastdfs/fastdfs_storage_data/data/;
ngx_fastdfs_module;
}
error_page500502503504/50x.html;
location=/50x.html{
roothtml;
ThinkItCMS如何访问门户
-13-本文档使用看云构建
}
}
server{
listen80;
server_namewww.thinkitcms.com;
#charsetkoi8-r;
#access_loglogs/host.access.logmain;
location/{
#roothtml;
gzipon;
gzip_min_length10k;
gzip_comp_level8;
gzip_typestext/plainapplication/javascriptapplication/x-javascript
text/cssapplication/xmltext/javascriptapplication/x-httpd-phpimage/jpegima
ge/gifimage/pngimage/x-icon;
gzip_buffers416k;#buffer不用修改root/home/apps/blog/webfile;
indexindex.htmlindex.htm;
}
location/group1/M00{
root/usr/fastdfs/fastdfs_storage_data/data/;
ngx_fastdfs_module;
}
error_page404/404.html;
#redirectservererrorpagestothestaticpage/50x.html
#
error_page500502503504/50x.html;
location=/50x.html{
roothtml;
}
}
server{
listen80;
server_namem.thinkitcms.com;
#charsetkoi8-r;
#access_loglogs/host.access.logmain;
ThinkItCMS如何访问门户
-14-本文档使用看云构建
location/{
#roothtml;
gzipon;
gzip_staticon;
gzip_min_length10k;
gzip_comp_level8;
gzip_typestext/cssapplication/javascript;
gzip_buffers416k;#buffer不用修改root/home/apps/blog/dist;
indexindex.htmlindex.htm;
}
location/group1/M00{
root/usr/fastdfs/fastdfs_storage_data/data/;
ngx_fastdfs_module;
}
error_page500502503504/50x.html;
location=/50x.html{
roothtml;
}
}
}
ThinkItCMS如何访问门户
-15-本文档使用看云构建
ThinkItCMS开发说明
ThinkItCMS开发使用使用说明在ThinkItCMS项目中有一些特别注意的地方再次说明一下,给广大朋友做一个说明,也减少入坑次数。
ThinkItCMS开发说明
-16-本文档使用看云构建
工具类说明
统一工具类使用说明如果你想要在项目中使用一些工具类先不要着急自己去扩展去写,可以使用如下开源工具类,如果在Hutool提供
的工具类中没有在考虑自行扩展
统一工具类的意义
Hutool帮助我们简化每一行代码,减少每一个方法,然代码可读性、容错性更高。完整文档方便使用hutool-
doc,避免每个开发乱引入造成辣鸡代码。
强制使用hutool工具类
hutool提供类哪些功能
一个Java基础工具类,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工
具类,同时提供以下组件:
hutool-aopJDK动态代理封装,提供非IOC下的切面支持
hutool-bloomFilter布隆过滤,提供一些Hash算法的布隆过滤
hutool-cache缓存
hutool-core核心,包括Bean操作、日期、各种Util等
hutool-cron定时任务模块,提供类Crontab表达式的定时任务
hutool-crypto加密解密模块
hutool-dbJDBC封装后的数据操作,基于ActiveRecord思想
hutool-dfa基于DFA模型的多关键字查找
hutool-extra扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码等)
hutool-http基于HttpUrlConnection的Http客户端封装
hutool-log自动识别日志实现的日志门面
hutool-script脚本执行封装,例如Javascript
hutool-setting功能更强大的Setting配置文件和Properties封装
hutool-system系统参数调用封装(JVM信息等)
hutool-jsonJSON实现
hutool-captcha图片验证码实现
hutool-poi针对POI中Excel的封装
可以根据需求对每个模块单独引入,也可以通过引入hutool-all方式引入所有模块。
ThinkItCMS中的使用
工具类说明
-17-本文档使用看云构建
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
工具类说明
-18-本文档使用看云构建
目录结构简介
一共分为9个依赖
think-web:主要是web层文件以及web的config文件
think-seo:用于seo工具扩展的依赖,感觉比较鸡肋,后期可能有优化或者移除,看需求大不大
think-security:权限依赖模块,SpringSecurity+OAuth2实现token认证
think-provide-api-service:文章、分类等模块的接口
think-provide-api-system:用户、组织权限、基础数据等模块的接口
think-provide-service:文章、分类等模块的接口的实现
think-provide-system:用户、组织权限、基础数据等模块的接口的实现
think-freemark:用于模板生成技术的封装和应用等
think-core:核心依赖模块
具体的依赖关系自己通过idea查看即可
目录结构简介
-19-本文档使用看云构建
ThinkItCMS接口格式约定
统一接口返回值约定1、我们规定了接口的返回格式通过ApiResult返回格式为:
1. code:0
2. msg:"操作成功"
3. res:{total:1,…}
code:当其等于0时表示接口正常返回数据否则的话不正常,在接口正常的情况下res属性是一定存在的
否则的话不保证res属性是否存在
msg:code对应的提示我们通过ApiResult.result(code)code对应的msg在ApiResult下维护了一个
map可以自定义code和提醒如下:
put(401,"授权失败,请确认账号密码是否正确");
put(421,"用户已被锁定!");
put(403,"权限不足");
put(404,"接口不存在");
res:当返回结果为单条记录时其返回的json数据为该对象,当返回结果为对条记录或者分页时
其返回的json数据为
`1.hasNext:true
2.pageCount:1
3.pageNo:1
4.pageSize:10
5.rows:[{id:"158",createId:"1",gmtCreate:"2019-11-11T17:15:44.000+0000",modifiedId:"1",…}]
1. 0:{id:"158",createId:"1",gmtCreate:"2019-11-11T17:15:44.000+0000",modifiedId:"1",…}
2. total:1`
其中rows是一个数组在前台遍历循环
当我们要返回初次之外的其他数据怎么办?
List<CmsDefaultModelFieldDto>allFields=DynamicFieldUtil.formaterField(modelD
to.getCheckedFieldList(),modelDto.getExtendFieldList());
ApiResultapiResult=ApiResult.result(allFields);
apiResult.put("hasFiles",modelDto.getHasFiles());
我们可以通过上面这种形式来实现因为ApiResult是继承自HasmMap的它具有map的功能剩下的你自己随便玩。
ThinkItCMS接口格式约定
-20-本文档使用看云构建
统一接口调用约定2、我们调用接口的时候一般遵循reset风格设计,查询方法采用get请求修改采用put请求
删除采用delete请求
其中的分页查询采用post请求这样的话统一调用方式对后期维护或者扩展有较大的好处,比如现在ThinkItCMS
的manager售卖演示功能只需要添加一个简单的拦截器根据请求方式和方法名既可以完成同一拦截,如果随意
采用请求方式后期做统一操作的时候就比较麻烦
同时ThinkItCMS才用的校验为hibernatevalidate在当前的controller层添加注解@Validated
在入controller层根据需要添加相应的注解用于非空非null等的校验。具体参考hibernatevalidate的使用
以及本系统的案例
业务日志:ThinkItCMS通过spring提供的aop实现了全局的日志拦截只需要将相应的注解
@Logs(module=LogModule.CONTENT,operation="查看内容详情")
放在方法上即可实现日志拦截同时ThinkItCMS提供了丰富的数据库操作接口可根据意思自行调用参考其他模块
ThinkItCMS接口格式约定
-21-本文档使用看云构建
ThinkItCMS缓存技术的使用
缓存技术在系统中的应用由于缓存在系统中使用可能大大的提高系统的执行效率可以说还是挺重要的,ThinkItCMS采用的#Spring缓存其中redis作为缓存的中间件需要配合#Spring缓存注解来完成如下演示:不熟悉Spring缓存注解的朋友可以先去了解一下
@Cacheable(value=Constants.cacheName,key="#root.targetClass+'.'+#root.methodNa
me+'.'+#root.args[0]",unless="#result==null")
另外缓存的清理也很重要spring自带的清理缓存的注解满补不了目前的业务需求,ThinkItCMS扩展了自定义的
缓存清理注解
@CacheClear(keys={"getNextOrPreviousContentByIdAndCateg"},clz={A.class,B.cla
ss})
以上注解表明要清除:
A和B类下的getNextOrPreviousContentByIdAndCateg方法的缓存,
如果getNextOrPreviousContentByIdAndCateg这个方法不存在则清理A和B的所有缓存
缓存清除的原则是:宁可错杀一千绝不放过一个
ThinkItCMS会自动匹配keys对应的方法名和clz的对应关系比如
@CacheClear(keys={"getUser","getContent"},clz={A.class,B.class})
比如A类下面有一个方法getUserB类下面有一个方法getContent就可以这样写,会自动匹配清除,所有的
清除缓存注解都是添加在接口的实现类上。如果不写clz属性如下
@CacheClear(keys={"getNextOrPreviousContentByIdAndCateg","getTopContent"})
则默认清除当前类的getNextOrPreviousContentByIdAndCateg和getTopContent方法的缓存
ThinkItCMS缓存技术的使用
-22-本文档使用看云构建
ThinkItCMS功能开发详解ThinkItCMS在生成静态页时都是通过标签抓取数据,通过标签将对应的数据生成静态页在页面上展现出来,这
样是模板开发的有点,可以自定义样式显示,无需修改后台代码就可以完成网站的开发。接下来介绍一下如何使
用标签以及如何自己扩展一个自定义标签。由于ThinkItCMS自带的标签或许满足不了用户的需求,那么你可以
自己扩展一个标签用于抓取数据。接下里介绍一下如何自定义扩展标签。
ThinkItCMS功能开发详解
-23-本文档使用看云构建
如何扩展一个自定义标签
接下来介绍一下如何自定义扩展标签
上图展示的是ThinkItCMS自带的标签存放的位置,你可以在此添加自己的标签
@Component
publicclassCmsContentDirectiveextendsAbstractTemplateDirective{
@Autowired
如何扩展一个自定义标签
-24-本文档使用看云构建
ContentServicecontentService;
@Override
publicvoidexecute(RenderHandlerhandler)throwsIOException,Exception{
StringcontentId=handler.getString(Constants.contentId);
if(Checker.BeNotBlank(contentId)){
ContentDtocontentDto=contentService.getContentById(contentId);
handler.beanToMap(contentDto).render();
}
}
@PostConstruct
publicvoidinitName(){
this.setDirectiveName(DirectiveNameEnum.CMS_CONTENT_DIRECTIVE);
}
}
参考实例你只需要自定义一个类添加@Component注解放到被spring扫描到的目录下然后继承
AbstractTemplateDirective这个类实现execute这个方法
然后给你自己的标签取一个名字
@PostConstruct
publicvoidinitName(){
this.setDirectiveName(DirectiveNameEnum.CMS_CONTENT_DIRECTIVE);
}
其中
publicenumDirectiveNameEnum{
CMS_CONTENT_DIRECTIVE("_d_content","content","根据文章id获取详情"),
}
第一个参数为你的标签名字,第二个参数为当前标签取到值后的默认的key(注意该key可有可无)其中主要决
定以你在拿到数据后handler.beanToMap(contentDto)的时候是否设置key
参考下图打个比方如果通过handler.beanToMap(contentDto)转化后其中的title、clicks、text属性都可以直
接获取如果你想自定义key的话可以通过handler.beanToMap("key",contentDto)这下面的取值方式就需要通
过key.title,key.clicks,key.text的方式来获取
ContentDtocontentDto=contentService.getContentById(contentId);
handler.beanToMap(contentDto).render();
我们取到了contentDto对象后通过标签可以直接获取contentDto对象里的属性如下:<@_d_contentcontentId=contentId>
如何扩展一个自定义标签
-25-本文档使用看云构建
<h1class="con_tilte">${title!'暂无标题'}</h1>
<pclass="bloginfo"><iclass="avatar">
<spanid="clicks">${clicks!'0'}</span>次浏览</p>
<pclass="con_info"><b>简介</b>${description!'暂无描述!'}</p>
<divclass="con_text">
${text!'暂无内容!'}
</@_d_content>
handler.beanToMap(contentDto)
如果是获取的数据为list的时候则要求必须填写key
@Override
publicvoidexecute(RenderHandlerhandler)throwsException{
IntegerpageNo=handler.getInteger(Constants.PAGE_NO);
IntegerpageSize=handler.getInteger(Constants.PAGE_SIZE);
IntegerpageCount=handler.getInteger(Constants.PAGE_COUNT);
StringcategoryId=handler.getString(Constants.categoryId);
if(Checker.BeNotNull(pageNo)){
PageDto<ContentDto>pageDto=newPageDto<>();
ContentDtocontentDto=newContentDto();
contentDto.setCategoryId(categoryId);
pageDto.setPageSize(pageSize).setPageNo(pageNo).setPageCount(pageCount).
setDto(contentDto);
List<ContentDto>contents=contentService.pageContentForCategoryGen(page
Dto);
handler.listToMap(this.getDirectiveName().getCode(),contents).render();
}
}
其中handler.listToMap必须有一个keyname这个key我推荐采用this.getDirectiveName().getCode()的方
式来定义key这个code就是下面的第二个参数
publicenumDirectiveNameEnum{
CMS_CONTENT_DIRECTIVE("_d_content","content","根据文章id获取详情"),
}
这个时候就可以实现自己的标签来提供前台获取其中
handler提供了很多获取数据的方法数据来源于模板中设置的变量或者常量
如何扩展一个自定义标签
-26-本文档使用看云构建
这这取决于你在生成模板的是存放什么变量到前台,那么前台就可以获取后在传到后台了用于数据的获取。
注意
handler.beanToMap(contentDto).render();
handler.listToMap(this.getDirectiveName().getCode(),contents).render();
该方法的作用在子章节(xxxToMap二次封装说明)说明
如何扩展一个自定义标签
-27-本文档使用看云构建
xxxToMap二次封装说明
模板数据自封装说明为什么在取得数据后要进行
handler.beanToMap(contentDto).render();
handler.listToMap(this.getDirectiveName().getCode(),contents).render();
该封装是将取得contentDto数据进行一个封装后在提供给模板用于数据展示。
Q:为什么要这样做?我直接用contentDto.xxx不可以吗?
A:当然可以但是不太好,因为contentDto对象里的属性是固定的,如果我在模板里面想要自定义属性怎么
办?比如contentDto有个属性title如果直接用contentDto渲染就只能contentDto.title如果我想自定义
contentDto.biaoti来获取title的属性怎么办?没法实现,其次如果contentDto有个属性map其中map有
包含一些属性,那么久需要contentDto.map.xxx.xxx这样的话看起来有点长感觉不太美观,综合以上我觉得在转
化一下比较合理
你也可以不转化来实现,其中转化的时候需要在dto类上添加@DirectMark注解标记这是一个需要取值的属
性只有添加@DirectMark这个属性时才能正常获取到值
@Data
@Accessors(chain=true)
@JsonInclude(JsonInclude.Include.NON_NULL)
publicclassCmsCategoryDtoextendsBaseModel{
privatestaticfinallongserialVersionUID=1L;
privateStringid;
/**
*名称*/
@DirectMark
@NotBlank(message="名称不能为空")
privateStringname;
xxxToMap二次封装说明
-28-本文档使用看云构建
如何扩展自定义定时任务
如何自定义定时任务在ThinkItCMS内置了一些自定义的定时任务,但是有的时候可能满足不了需求,这个时候你可以自己扩展一个定时任务类
我们以日志清理为案例分析一下如何扩展自定义定时任务
/**
*定时清理日志记录*/
@Component
publicclassClearLogJobextendsQuartzJobBean{
@Autowired
LogServicelogService;
publicvoidexecuteInternal(JobExecutionContextjobExecutionContext)throws
JobExecutionException{
JobDataMapparams=jobExecutionContext.getTrigger().getJobDataMap();
JobDataMapjobData=jobExecutionContext.getJobDetail().getJobDataMap();
booleanckIsLegal=Checker.BeNotNull(jobData)&&!jobData.isEmpty()&&j
obData.containsKey("param")&&
Checker.BeNotNull(jobData.get("param"));
if(ckIsLegal){
Integermonth=Integer.valueOf(jobData.get("param").toString());
logService.deleteLogBeforeMonth(month);
}
}
}
代码如上,首先创建一个被spring容器管理的类
然后继承QuartzJobBean实现executeInternal方法
通过JobDataMapparams=jobExecutionContext.getTrigger().getJobDataMap();
获取参数,该参数是在启动定时任务的缺省参数,可以为空,该参数哪里来的呢?
我们需要配置一下基础数据
如何扩展自定义定时任务
-29-本文档使用看云构建
如上图所示:我们需要在基础数据中配置一条记录,其中标签名和数据值必填标签名最好是唯一,数据值为
你刚才新建的任务类的全路径创建好了以后我们在创建定时任务如下图所示:
如何扩展自定义定时任务
-30-本文档使用看云构建
首先任务名称和任务组可以自定义但需要保障任务名称和任务组的结合后形成的字符串需要唯一因为其将作
为定时任务的触发器唯一名称可以理解为主键
任务类型选择你你刚才创建的基础数据,执行时间也是来自于基础数据,你可以自己在基础数据中添加任务表达
式作为时间的选项之一,接下来看一下有一个初始化参数,该参数就是上面所说的默认的缺省参数,然后你就
可以创建一个定时任务了,可以开启和暂停任务。至此,就完成了扩展自己的定时任务,定时任务的逻辑需要你
自己去开发了。
如何扩展自定义定时任务
-31-本文档使用看云构建
如何切换第三方对象存储
如何切换第三方平台作为默认的文件服务器ThinkItCMS采用默认的fastdfs作为文件服务器,有些用户可能是才用的第三方文件存储平台,比如阿里云,腾讯云,七牛云等等平台那如何切换第三方平台作为默认的文件服务器呢?
接下来说一下如何快速开发切换第三方文件服务器,首先我们还是看着demo说一下
@DefaultUploadClient()
@Component
publicclassQiNiuYunClientextendsUploadClient{
@Override
publicApiResultuploadFile(MultipartFilefile){
returnnull;
}
@Override
publicApiResultkeepUploadFile(Chunkchunk){
returnnull;
}
@Override
publicApiResultreadyUpload(MultipartFilefile){
returnnull;
}
@Override
publicApiResultuploadSuccess(ApiResultapiResult,MultipartFilefile){
returnnull;
}
@Override
publicvoiduploadError(ApiResultresult){
}
@Override
publicApiResultdeleteFile(Map<String,String>params){
returnnull;
}
@Override
publicMap<String,Object>checkFileIsExistByMd5(Stringmd5){
returnnull;
如何切换第三方对象存储
-32-本文档使用看云构建
}
}
看代码首先我们创建一个类QiNiuYunClient该类必须纳入spring管理同时需要标记一下该类作为LBCSM唯
一的文件上传client的类即
@DefaultUploadClient()
@Component
需要添加上面2个注解,且需要继承UploadClient注意:如果有别的类也继承了UploadClient那么确保该类没
有@DefaultUploadClient()也就是说UploadClient的所有子类中只能有一个添加了@DefaultUploadClient()
系统才会找到默认的client
接下来分析一下其中的方法
其中:
publicApiResultreadyUpload(MultipartFilefile){
returnnull;
}
readyUpload:上传文件前会调用该,主要用于校验用,该接口要求返回一个ApiResult类,我们只需要返回如
下:
@Override
publicApiResultreadyUpload(MultipartFilefile){
if(Checker.BeNotNull(file)){
returnApiResult.result();
}
returnApiResult.result(20026);
}
result里的参数为空或者returnApiResult.result(0);时就说明校验正确可以上传前面我们提到了code为0时
我们保证接口调用正常,在这也是一样的当result(20026)的code为其他值时说明校验结果不正常不会继续上
传。
接下来我们看一下
@Override
publicApiResultuploadFile(MultipartFilefile){
returnnull;
}
这个方法当readyUpload返回结果正常时才会继续执行uploadFile方法
否则的话会调用uploadError
如何切换第三方对象存储
-33-本文档使用看云构建
@Override
publicApiResultuploadFile(MultipartFilemultipartFile){
try{
StorePathstorePath=storageClient.uploadFile(Constants.FDFS_GROUP,multi
partFile.getInputStream(),
multipartFile.getSize(),FileUtil.getSuffix(multipartFile.getOriginalFi
lename()));
if(Checker.BeNotNull(storePath)&&Checker.BeNotBlank(storePath.getPath(
))){
Map<String,String>uploadRes=newHashMap<>();
uploadRes.put("filePath",storePath.getFullPath());
uploadRes.put("fileFullPath",ThinkCMSConfig.getSiteFdfsDomain()+Fi
le.separator+storePath.getFullPath());
uploadRes.put("group",storePath.getGroup());
uploadRes.put("path",storePath.getPath());
uploadRes.put("fileName",multipartFile.getOriginalFilename());
returnApiResult.result(uploadRes);
}
returnApiResult.result(20004);
}catch(Exceptione){
returnApiResult.result(20004);
}
}
看代码文件上传成功后我们在map中设置了map的一些参数,这些参数是为了下一步存储数据库用
Map<String,String>uploadRes=newHashMap<>();
uploadRes.put("filePath",storePath.getFullPath());
uploadRes.put("fileFullPath"ThinkCMSConfig.getSiteFdfsDomain()+File
.separator+storePath.getFullPath());
uploadRes.put("group",storePath.getGroup());
uploadRes.put("path",storePath.getPath());
uploadRes.put("fileName",multipartFile.getOriginalFilename());
returnApiResult.result(uploadRes);
同时也要保证ApiResult.result(uploadRes);后的code是0后才会调用下面的方法否则的话会调用
uploadError
@Override
publicApiResultuploadSuccess(ApiResultapiResult,MultipartFilefile){
returnnull;
}
文件上传成功后你可以保存数据库了,ThinkItCMS提供了父类的saveFileToDb的方法可以直接调用,如果不
如何切换第三方对象存储
-34-本文档使用看云构建
符合你的数据格式,你也可以自己从写
@Override
publicApiResultuploadSuccess(ApiResultresult,MultipartFilefile){
returnsaveFileToDb(result,file);
}
ThinkItCMS提供了父类的不仅提供了saveFileToDb的方法也提供了以下的方法
方便调用,根据你自己的情况来
publicApiResultdeleteFileByUid(Stringuid){
resourceService.deleteByFiled("file_uid",uid);
returnApiResult.result();
}
publicSysResourceDtogetFileByUid(Stringuuid){
returnresourceService.getByField("file_uid",uuid);
}
还有一个deleteFile该类主要用于删除文件以及db,其中前台会穿一个fileUid的参数在map里面
然后根据fileUid获取具体的文件信息,可以根据这些信息删除第三方平台的文件后删除db数据
@Override
publicApiResultdeleteFile(Map<String,String>params){
Stringuid=params.get("fileUid");
SysResourceDtoresourceDto=getFileByUid(uid);
StringfilePath=resourceDto.getFilePath();
StringgroupName=resourceDto.getGroupName();
if(Checker.BeNotBlank(filePath)){
storageClient.deleteFile(groupName,filePath);
deleteFileByUid(uid);
returnApiResult.result(filePath);
}else{
returnApiResult.result(20018);
}
}
接下来还设有一个keepUploadFile这个方法该方法主要配合断点续传使用,
该功能因为各种平台差异较大,暂时省略介绍,感兴趣的小伙伴可以参考FastdfsClient类自己实现吧。
如何切换第三方对象存储
-35-本文档使用看云构建
ThinkItCMS如何实现断点续传
断点续传的实现分析在发布文章时会插入视频等功能考虑到视频较大的情况下cms扩展支持了断点续传的功能,
本cms使用的vue-upload作为前端的上传视频的组件具体文档可以参考githup
讲解一下后端如何实现断点续传,此处断点续传的原理是通过前端的空间将文件分割成若干份,取决于你设置
的片段文件大小,我设置的是1M,加入一个文件是11M那么该上传将分为11次上传该控件在上传时会把片段
序号以及分段总数,和文件的Md5传到后台,这样后台可以通过片段来记录已经上传的片段数,下次再上传的
时候直接告诉前台已经上传了哪些片段后,控件会自动接着上一次上传,具体看代码分析如下:
@GetMapping("keepUploadFile")
publicApiResultcheckFileIsExist(Chunkchunk){
returnuploadService.checkFileIsExist(chunk);
}
@PostMapping("keepUploadFile")
publicApiResultkeepUploadFile(Chunkchunk){
returnuploadService.keepUploadFile(chunk);
}
/**
*@ClassName:Chunk
*@Author:LG
*@Date:2019/2/1515:18
*@Version:1.0
**/
@Data
@Accessors(chain=true)
publicclassChunk{
privateLongid;
/**
*当前文件块,从1开始*/
privateIntegerchunkNumber;
/**
*分块大小*/
privateLongchunkSize;
ThinkItCMS如何实现断点续传
-36-本文档使用看云构建
/**
*当前分块大小*/
privateLongcurrentChunkSize;
/**
*总大小*/
privateLongtotalSize;
/**
*文件标识*/
privateStringidentifier;
/**
*文件名*/
privateStringfilename;
/**
*相对路径*/
privateStringrelativePath;
/**
*总块数*/
privateIntegertotalChunks;
/**
*文件类型*/
privateStringtype;
privateMultipartFilefile;
}
看代码该控件会在上传时第一次会请求get方法,这个方法主要是我们用来获取当前文件是否被上传过,以及是
否上传中断,如果该文件以前被上传到服务器上时,我们定义一个标识,skip:true标识文件以前上传了并且上
传完成,并返回文件路径和基本信息等,否则的话我们标识skip:false并返回已经上传的片段,如果没有上传
过片段的话(根据md5到数据库查询是否存在已经上传完成的文件,服务端需要判断最后一次上传成功的话需要
将文件信息保存到数据库)
则返回一个uploadChunk:如果不为空的话则返回已经上传的片段序号,否则的话返回空集合
即可配合前端空间完成断点续传。具体原理就是这样,实现细节参考代码UploadController类
params.put("uploadChunk",Checker.BeNotEmpty(uploadChunk)?uploadChunk:Lists.new
ArrayList());
ThinkItCMS如何实现断点续传
-37-本文档使用看云构建
publicMap<String,Object>checkFileIsExistByMd5(Stringmd5){
SysResourceDtoresource=checkerHasFileByMd5(md5);
Map<String,Object>params=newHashMap<>(16);
if(Checker.BeNotNull(resource)){
params.put("filePath",resource.getFilePath());
params.put("fileFullPath",resource.getFileFullPath());
params.put("fileName",resource.getFileName());
params.put("fileUid",resource.getFileUid());
params.put("group",resource.getGroupName());
params.put("path",resource.getFilePath());
params.put("skip",true);
}else{
params.put("skip",false);
StringfileKey=Constants.fastDfsKeepUpload+md5;
List<Integer>uploadChunk=(List<Integer>)baseRedisService.get(fi
leKey);
params.put("uploadChunk",Checker.BeNotEmpty(uploadChunk)?uploadChunk
:Lists.newArrayList());
}
returnparams;
}
}
ThinkItCMS如何实现断点续传
-38-本文档使用看云构建
ThinkItCMS讲解文章的生成过程#ThinkItCMS三大组件分析
ThinkItCMS在生成静态页时候封装了三大组件用于生成静态页,可以理解为基类,所有的生成静态页调用的方法都会执行三个组件的调用,接下来分析一下静态页模板在生成的时候执行流程,以及三大组件在此过程当中的执行调用。
ThinkItCMS讲解文章的生成过程
-39-本文档使用看云构建
ThinkItCMS三大组件分析组件1DirectiveComponent:
该组件是用于集成自定义模板,方法,变量的一个重要组件,该组件会在项目启动时将自定义的模板指令以及自
定义的方法,变量注入到freemark内部,作为一个函数,在页面生成的时候,根据设置的key去查找对应的函
数,如果是方法或者标签指令则会在生成静态页的时候调用该函数或者指令来进行获取数据。这是静态页生成的
数据源中心一般不需要修改。只需要集成扩展自定义标签、变量等,配合spring标签实现freemark标签源的
自动注入。
组件2NotifyComponent:
该组件是用于通知静态页要生成时调用的方法,本人封装的模板生成时调用的方法均封装在该组件内实现文件生
成,希望后续开发者保持规范,该组件采用观察者模式实现同步通知以及异步通知(定时任务通知来实现模板的
生成,具体方法参考代码注释,写的很详细)
组件3TemplateComponent:
该组件用于实际的代码生成调用的工具类,分为三个方法,其实可以进一步统一在这里还是分开看比较清晰
/**
*创建内容静态化页面*
*@return
*/
publicApiResultcreateContentStaticFile(StringtempPath,StringstaticFilePath,
Map<String,Object>param){
ApiResultapiResult=ApiResult.result();
try{
FreeMarkerUtils.generateFileByFile(tempPath,staticFilePath,this.configu
ration,param);
apiResult.put("url",param.get("url"));
}catch(Exceptione){
FileUtil.deleteFile(staticFilePath);
log.error(e.getMessage());
log.error("创建内容静态化页面生成失败!");
thrownewCustomException(ApiResult.result(20006));
}
apiResult.put(Constants.staticFilePath,"内容静态化文件:"+staticFilePath);
returnapiResult;
}
/**
*创建内容列表静态化页面*
*@return
*/
ThinkItCMS三大组件分析
-40-本文档使用看云构建
publicApiResultcreateCategoryStaticFile(StringtempPath,StringstaticFilePath,
Map<String,Object>param){
ApiResultapiResult=ApiResult.result();
try{
FreeMarkerUtils.generateFileByFile(tempPath,staticFilePath,this.configu
ration,param);
}catch(Exceptione){
FileUtil.deleteFile(staticFilePath);
log.error(e.getMessage());
log.error("创建内容列表静态化页面失败!");
apiResult=ApiResult.result(20006);
thrownewCustomException(apiResult);
}
apiResult.put(Constants.staticFilePath,"栏目分类静态化文件:"+staticFilePath);
returnapiResult;
}
/**
*创建首页静态化页面*
*@return
*/
publicApiResultcreateIndexStaticFile(StringtempPath,StringstaticFilePath,M
ap<String,Object>param){
ApiResultapiResult=ApiResult.result();
try{
FreeMarkerUtils.generateFileByFile(tempPath,staticFilePath,this.configu
ration,param);
}catch(Exceptione){
FileUtil.deleteFile(staticFilePath);
log.error(e.getMessage());
log.error("创建首页静态化页面生成失败!");
apiResult=ApiResult.result(20006);
thrownewCustomException(apiResult);
}
apiResult.put(Constants.staticFilePath,"首页静态化文件:"+staticFilePath);
returnapiResult;
}
实际的生成静态页时在此处调用的,实际的操作过程如果进一步深究的话还是调用的freemark生成的具体可参
考源代码。
ThinkItCMS三大组件分析
-41-本文档使用看云构建
从一个请求分析文章的创建过程
详解文章的创建过程本文详解从一个请求到文章的创建过程分析
step1:
@Logs(module=LogModule.CONTENT,operaEnum=LogOperation.SAVE,operation="新增内容")
@PostMapping(value="saveContent")
publicvoidsaveContent(@Validated@RequestBodyContentDtov){
service.saveContent(v);
}
这里调用的saveContent方法,看一下saveContent方法如下:
step2:
@Override
@Transactional(isolation=Isolation.DEFAULT)
@CacheClear(keys={"getNextOrPreviousContentByIdAndCateg"})
publicvoidsaveContent(ContentDtov){
CmsModelDtomodelDto=checkerContent(v);
v.setTemplatePath(modelDto.getTemplatePath()).setId(generateId());
if(Checker.BeNotEmpty(v.getTags())){
List<String>tagIds=cmsTagsService.saveTags(v.getTags());
if(Checker.BeNotEmpty(tagIds)){
v.setTagIds(StringUtils.join(tagIds.toArray(),","));
}
}
if(modelDto.getHasFiles()&&Checker.BeNotEmpty(v.getAttachFiles())){
contentFileService.saveContentFiles(v.getAttachFiles(),v.getId());
}
Map<String,Object>extendParam=v.getExtendParam();
if(Checker.BeNotEmpty(extendParam)||Checker.BeNotBlank(v.getText())){
ContentAttributeDtoattributeDto=newContentAttributeDto();
attributeDto.setContentId(v.getId()).setText(v.getText()).setData(JSON.
toJSONString(extendParam));
contentAttributeService.insert(attributeDto);
}
if(Checker.BeNotEmpty(v.getCmsContentRelateds())){
v.setHasRelated(true);
relatedService.saveRelated(v.getId(),v.getCmsContentRelateds());
}
super.insert(v);
从一个请求分析文章的创建过程
-42-本文档使用看云构建
notifyComponent.notifyCreate(contentObserver,v,null,false,"");
}
checkerContent:
验证一下文章模型是否存在,并返回文章模型的方法
privateCmsModelDtocheckerContent(ContentDtov){
CmsCategoryModelDtocmsCategoryModelDto=cmsCategoryModelService.getByField
("category_id",v.getCategoryId());
if(Checker.BeNull(cmsCategoryModelDto)){
thrownewCustomException(ApiResult.result(20003));
}
CmsModelDtomodelDto=cmsModelService.getByPk(cmsCategoryModelDto.getModel
Id());
if(Checker.BeNotNull(modelDto)){
v.setModelId(modelDto.getId()).setHasFiles(modelDto.getHasFiles()).setH
asImages(modelDto.getHasImages())
;
}
returnmodelDto;
}
step3:
if(Checker.BeNotEmpty(v.getTags())){
List<String>tagIds=cmsTagsService.saveTags(v.getTags());
if(Checker.BeNotEmpty(tagIds)){
v.setTagIds(StringUtils.join(tagIds.toArray(),","));
}
}
if(modelDto.getHasFiles()&&Checker.BeNotEmpty(v.getAttachFiles())){
contentFileService.saveContentFiles(v.getAttachFiles(),v.getId());
}
判断文章是否添加了标签并保存到数据库。
判断文章是否添加了附件并保存到数据库。
最后插入文章到数据库
然后:通知文章创建,此处采用的notifyComponent用来通知
notifyComponent.notifyCreate(contentObserver,v,null,false,"");
查看具体代码:
从一个请求分析文章的创建过程
-43-本文档使用看云构建
/**
*通知文章创建*@paramobserver
*@paramcontentDto
*@paramcallBack
*@paramuserId
*/
publicvoidnotifyCreate(Observerobserver,ContentDtocontentDto,CmsCategoryD
tocategoryDto,booleancallBack,StringuserId){
ContentCreateObserverDatacontentObserverData=newContentCreateObserverData(
ObserverAction.CONTENT_CREATE);
contentObserverData.setContentDto(contentDto).setCategoryDto(categoryDto).b
uildParams();
if(Checker.BeNotBlank(userId))contentObserverData.setCreateId(userId);
notify(categorySubject,observer,contentObserverData,callBack);
}
有以下几个个参数:
/**
*通知文章创建*@paramobserver通知对象*@paramcontentDto内容对象*@paramcallBack是否通知前台(true:websocket会通知前台用户实时的生成细节)*@paramuserId当前登录用户*/
其中ContentCreateObserverData用来构建通知数据后用于生成静态页时发送给通知对象
categorySubject:被观察者
然后调用了统一的notify方法查看
step4:
protectedvoidnotify(Subjectsubject,Observerobserver,ObserverDataobserver
Data,booleanonceCallBack){
synchronized(subject){
subject.registerObserver(observer);
subject.setObserverData(observerData);
try{
subject.callBack(observerData);
if(onceCallBack){
subject.notifyObserver(this);
}else{
subject.notifyObserver();
}
从一个请求分析文章的创建过程
-44-本文档使用看云构建
notifyAllSuccess(observer,observerData);
}catch(Exceptione){
notifyError(e,observer,observerData);
log.error(e.getMessage());
log.error("文章静态化失败,等待发布时再次尝试");
}finally{
subject.removeObserver(observer);
}
}
}
我们看到被观察者将观察者注册进去,然后设置了观察数据,后执行了一个callBack:此处的callBack是全局通
知用来在控制台显示当前系统的操作细节如不需要可以注释掉
接下来就是notifyObserver其中有2个构造方法其中一个设置了this的方法只有当需要通知前段生成细节时调
用,另一个就是不需要通知时的调用然后就是notifyAllSuccess该操作实通知完成时的调用操作。
如果异常时则会通知异常最后移除观察者
当执行subject.notifyObserver()的时候实际上会执行观察者的update方法
@Override
publicvoidnotifyObserver(){
if(Checker.BeNotEmpty(observers)){
observers.forEach(observer->{
observer.update(observerData);
});
}
}
即:ContentObserver的update方法
//组装数据创建静态页if(Checker.BeNotNull(data)){
if(ObserverAction.valueOf(data.getObserverAction().getCode()).equals(Obser
verAction.CONTENT_CREATE)){
if(datainstanceofContentCreateObserverData){
ContentCreateObserverDataobsData=(ContentCreateObserverData)data
;
StringdateStr=(obsData.getHasStatic()&&Checker.BeNotBlank(obsD
ata.getRulesData()))?obsData.getRulesData():data.getDateStr();
ApiResultapiResult=genStaticContentFile(obsData,dateStr);
if(!apiResult.ckSuccess()){
thrownewCustomException(apiResult);
}
updateDb(data,apiResult,dateStr);
if(Checker.BeNotNull(notifyRes))
notifyRes.notifyOnceSuccess(apiResult);
从一个请求分析文章的创建过程
-45-本文档使用看云构建
}
}
}
这个时候回执行静态页生成会执行
ApiResultapiResult=templateComponent.createContentStaticFile(tempPath,static
FilePath,params);
其中用到了templateComponent组价的创建静态页方法
以上就是一个文章的大体生成流程,具体细节请参考代码。
从一个请求分析文章的创建过程
-46-本文档使用看云构建
ThinkItCMS设计模式的应用案例
在ThinkItCMS设计模式案例分析在ThinkItCMS设计中应用到不少的设计模式,接下来就将在开发过程中的设计模式案例代码分析一下也作为一个学习的过程与大家共勉
在三大组件中其中DirectiveComponent组件在实行依赖注入的时候
@Autowired
publicvoidinit(Configurationconfiguration,List<AbstractTemplateDirective
>templateDirectives,List<BaseMethod>baseMethodHandlers)throwsTemplateModel
Exception{
Map<String,Object>freemarkerVariables=newHashMap<>();
if(Checker.BeNotEmpty(templateDirectives)){
for(AbstractTemplateDirectivedirective:templateDirectives){
freemarkerVariables.put(directive.getDirectiveName().getValue(),
directive);
}
}
if(Checker.BeNotEmpty(baseMethodHandlers)){
for(BaseMethodbaseMethod:baseMethodHandlers){
freemarkerVariables.put(baseMethod.getMethodName().getValue(),b
aseMethod);
}
}
configuration.setAllSharedVariables(newSimpleHash(freemarkerVariables,
configuration.getObjectWrapper()));
configuration.setSharedVariable(Constants.DOMAIN,thinkCmsConfig.getSit
eDomain());
configuration.setSharedVariable(Constants.SERVER,thinkCmsConfig.getSer
verApi());
templateComponent.setConfiguration(configuration);
}
}
其中ListtemplateDirectives,
ListbaseMethodHandlers
是一个抽象类实际注入的是他的被spring扫描管理的子类,这样我们就可以实现动态的注入,这虽然是spring
帮我们完成的但是确实是一个很好的设计,这样我们才能动态的新增指令或者。
在Java设计模式当中其中有很多设计原则,其中里氏代换是面向对象设计的基本之一,以上就是在开发中的实
际应用
ThinkItCMS设计模式的应用案例
-47-本文档使用看云构建
通知静态页生成过程中的观察者模式在项目中实际的应用,具体参考《从一个请求分析文章的创建过程》这一篇文章
再看指令获取参数后的beanToMap或者listToMap等方法,采用的迭代器模式
BeanPropertyBoxpropertyBox=newBeanPropertyBox(obj);
ThinkIteratoriterator=propertyBox.iterator(PropertyIterator.class);
Map<String,Object>parms=newLinkedHashMap<>(16);
while(iterator.hasNext()){
Map<String,Object>map=(Map)iterator.next();
if(!map.isEmpty()){
parms.putAll(map);
}
}
通过这样设计有个好处就是后期如果需要逻辑判断或者修改代码是至少在这段代码中是不需要修改的只需要修
改PropertyIterator的实际hasNext或者next的方法而不需要修改这段代码。
在本cms系统有不少的设计模式的应用具体需要大家去理解和发现,再次不在一一赘述。
ThinkItCMS设计模式的应用案例
-48-本文档使用看云构建
ThinkItCMS标签指令详解
CMS_TAGS_DIRECTIVE("_d_tags","tags","当前内容下的标签"),
<@_d_tagsrowNum=10>
<#iftags??&&(tags?size>0)>
<#listtagsastag>
${tag.name!}(${tag.useCount!})
</#list>
</#if>
</@_d_tags>
获取标签云字段统计分标签个数;
rowNum:非必填获取几个标签显示
CMS_CONTENT_TAGS_DIRECTIVE("_d_content_tags","tags","当前内容下的标签"),
使用举例:
<@_d_content_tagscontentId=contentIdhasTag=hasTag>
<#iftags??&&(tags?size>0)>
<#listtagsastag>
<ahref="/search/searchTags.html?tagId=${tag.id}&tagName=${tag.name}">$
{tag.name}</a>
</#list>
</#if>
</@_d_content_tags>
contentId:必填
hasTag:非必填(优化用)
其中:contentId=contentIdhasTag=hasTag是固定属性,只有在调用生成文章方法时存在,
contentId:当前文章的idhasTag:是否存在标签,该属性在创建文章生成的时候会自动判断文章是否存在标
签,这样可以减少数据库查询次数提升效率。
CMS_UPTODATE_DIRECTIVE("_d_uptodates","contents","获取最新内容"),
ThinkItCMS标签指令详解
-49-本文档使用看云构建
使用说明:
rowNum:非必填(默认6条)获取多少条数据,不宜过大
categoryId:非必填用于获取当前分类下的最新文章
<@_d_uptodatesrowNum=10categoryId=categoryId>
<#ifcontents??&&(contents?size>0)>
<#listcontentsascontent>
${content.title}
</#list>
</#if>
</@_d_uptodates>
CMS_SITE_DIRECTIVE("_d_site","site","获取网站配置"),
<@_d_site>${(shareCode)!}</@_d_site>
获取网站配置的分享代码:
其中参数为标记了@DirectMark的字段都可以获取
/**
*站点名称*/
@DirectMark
privateStringsiteName;
/**
*关键字*/
@DirectMark
privateStringsiteKeyWords;
/**
*站点描述*/
@DirectMark
privateStringsiteDesc;
/**
*logo
ThinkItCMS标签指令详解
-50-本文档使用看云构建
*/
@DirectMark
privateStringsiteLogo;
/**
*第三方统计代码*/
@DirectMark
privateStringstatisticalCode;
/**
*第三方分享代码*/
@DirectMark
privateStringshareCode;
/**
*第三方评论代码*/
@DirectMark
privateStringcommentCode;
/**
*域名*/
@DirectMark
privateStringdomain;
CMS_RECOMM_DIRECTIVE("_d_recomms","recomms","获取推荐内容"),
rowNum:非必填获取的条数
categoryId:非必填获取当前分类的推荐
<@_d_recommsrowNum=3categoryId=categoryId>
本栏推荐
<#ifrecomms??&&(recomms?size>0)>
${recomms[0].title!'无标题'}
<#listrecommsasrecomm>
ThinkItCMS标签指令详解
-51-本文档使用看云构建
${recomm.title!'无标题'}
</#list>
</#if></@_d_recomms>
CMS_NOTICE_DIRECTIVE("_d_notices","notices","获取公告内容"),
<@_d_notices>
<#ifnotices??&&(notices?size>0)>
<#listnoticesasnotice>
${notice.title!'无标题'}
</#list></#if></@_d_notices>rowNum:非必填获取的条数categoryId:非必填获取当前分类的公告
CMS_HOT_DIRECTIVE("_d_hots","hots","获取热门内容"),
同上
CMS_FRAGMENT_DATA_DIRECTIVE("_d_fragment_data","fragmentData","获取页面片段数据指令");
通过code获取页面片段的数据
<@_d_fragment_datacode='banner'>
<#listfragmentDataasfragment>
${fragment.url}
</#list></@_d_fragment_data>
CMS_CONTENT_RELATED_DIRECTIVE("_d_related","relateds","获内容推荐"),
<@_d_relatedcontentId=contentIdrowNum=10hasRelated=hasRelated>
ThinkItCMS标签指令详解
-52-本文档使用看云构建
<#listrelatedsasrelated>
${related.title!'暂无标题'}
</#list></@_d_related>contentId:必填文章id
rowNum:非必填获取几个推荐
hasRelated:非必填(主要用于减少数据库请求)
CMS_CONTENT_NEXT_DIRECTIVE("_d_content_next","next","获取下一篇文章"),
CMS_CONTENT_PREVIOUS_DIRECTIVE("_d_content_pre","previous","获取下一篇文章"),
<@_d_content_precontentId=contentIdcategoryId=categoryId>
<#ifprevious??>
${previous.title!'暂无标题'}
<#else>
无
</#if>
</@_d_content_pre>
下一篇:
<@_d_content_nextcontentId=contentIdcategoryId=categoryId>
<#ifnext??>
${next.title!'暂无标题'}
<#else>
无
</#if>
</@_d_content_next>
contentId:必填当前文章id:categoryId:必填当前栏目分类id
CMS_CONTENT_DIRECTIVE("_d_content","content","根据文章id获取详情"),
<@_d_contentcontentId=contentId>
${categoryName!}${domain}${categoryUrl!}
ThinkItCMS标签指令详解
-53-本文档使用看云构建
</@_d_content>
这些变量(具体变量参考contentDto上加了@DirectMark的字段)都可以获取自己用来定义显示效果
CMS_ATTACH_DIRECTIVE("_d_attach","attachs","当前内容下的附件"),
CMS_CLICKS_TOP_DIRECTIVE("_d_clicks_top","tops","热门点击"),
获取点击量最高的几条记录
CMS_CATEGORY_LIST_DIRECTIVE("_d_content_list","contents","根据栏目获取栏目下的文章指令,用于生成栏目列表页"),
参考代码:
<@_d_content_listpageNo=pageNopageSize=pageSizepageCount=pageCountcateg
oryId=categoryId>
<ul>
<#listcontentsascontent>
<li>
<h3class="blogtitle"><ahref="${domain}${content.url!'#'}"target="_
blank">${content.title!}</a></h3>
<spanclass="blogpicimgscale"><i><ahref="#">${content.copied?string(
'【转载】','【原创】')}</a></i><ahref="${domain}${content.url!'#'}"title="">
<imgsrc="${content.cover!'${domain}/static/images/b01.jpg'}"alt=""><
/a></span>
<pclass="blogtext">${content.description!'暂无描述'}</p>
<pclass="bloginfo"><iclass="avatar"><imgsrc="${domain}/static/imag
es/avatar.jpg"></i><span>${content.author!'本站编辑'}</span>
<span>${(content.publishDate?string('YY-MM-dd'))!'${.now?string("YY-M
M-dd")}'}</span>
<span>【<ahref="${domain}${categoryUrl!}">${categoryName!}</a>】</sp
an></p>
<ahref="${domain}${content.url!'#'}"class="viewmore">阅读更多</a>
</li>
</#list>
</ul>
<!--pagelist-->
<#ifpageCountgt1>
<#include_m_fragment_import('page_list')/>
</#if>
<!--
<divclass="pagelist">
<atitle="Totalrecord"> <b>67</b></a> <b>1</b>&n
ThinkItCMS标签指令详解
-54-本文档使用看云构建
bsp;<ahref="/download/index_2.html">
2</a> <ahref="/download/index_3.html">3</a> <ahref="/downlo
ad/index_2.html">下一页</a> <ahref="/download/index_3.html">尾页</a>
</div>
-->
<!--pagelistend-->
</@_d_content_list>
pageNo=pageNopageSize=pageSizepageCount=pageCountcategoryId=categoryId
以上参数都是固定写法后台会传变量到前台获取后在传往后台
ThinkItCMS标签指令详解
-55-本文档使用看云构建