Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

欢迎使用MCms。一直在改变,从未停止过!

MCms内容插件提供最基本的菜单、权限、角色、栏目、内容、静态化、等常用的功能,能够帮助企业或个人进行快速建站。

Tip

首次MCms安装部署好或启动好后,先登录后台,生成静态页面,这样才能正常访问前端地址,否则前端演示页面会提示404错误。修改模板或对文章内容进行修改后,必须进行更新操作

生态

  1. MStore提供了许多免费模版与常用插件 模板分享
  2. MS平台提供了在线模板设计器,帮助设计人员、前端开发者快速进行模版的设计;
  3. MS平台提供了代码生成器,帮助企业、开发者能快速进行二次开发:代码生成器在线视频教程代码生成器使用文档
  4. 后续还会提供在线APP混合打包,帮助企业实施移动端业务;
  5. MS平台提供了PM项目管理,帮助团队成员、远程协日常开发工作;PM方便管理项目进度

Tip

后端开发者推荐源码部署体验,前端开发者下载一键版本体验,也可以查看在线演示版本

低代码平台,开源排名第一的java cms,免费开源的java cms, 免费开源Javacms,MCMS,开源Java CMS,Java网站建设工具,开源CMS,内容管理系统,CMS软件,网站建设工具,网站模板,响应式模板,自适应模板,多功能模板,网站插件,功能插件,扩展插件,定制插件,Java CMS定制

低代码平台,开源排名第一的java cms,免费开源的java cms, 免费开源Javacms,MCMS,开源Java CMS,Java网站建设工具,开源CMS,内容管理系统,CMS软件,网站建设工具,网站模板,响应式模板,自适应模板,多功能模板,网站插件,功能插件,扩展插件,定制插件,Java CMS定制

低代码平台,开源排名第一的java cms,免费开源的java cms, 免费开源Javacms,MCMS,开源Java CMS,Java网站建设工具,开源CMS,内容管理系统,CMS软件,网站建设工具,网站模板,响应式模板,自适应模板,多功能模板,网站插件,功能插件,扩展插件,定制插件,Java CMS定制

低代码平台,开源排名第一的java cms,免费开源的java cms, 免费开源Javacms,MCMS,开源Java CMS,Java网站建设工具,开源CMS,内容管理系统,CMS软件,网站建设工具,网站模板,响应式模板,自适应模板,多功能模板,网站插件,功能插件,扩展插件,定制插件,Java CMS定制

邀请您加入技术交流QQ群

加入QQ群 加入QQ群 加入QQ群 加入QQ群 加入QQ群

依赖

当前版本

<!-- ms-mcms 内容模块插件 -->
<dependency>
    <groupId>net.mingsoft</groupId>
    <artifactId>ms-mcms</artifactId>
    <version>当前版本</version>
</dependency>

接口文档

项目访问路径 5.4.3版本以下 http://localhost:8080/swagger-ui.html

5.4.3版本及以上 http://localhost:8080/swagger-ui/index.html

6.0.0版本及以上 http://localhost:8080/swagger-ui.html

Tip

如果swagger接口文档不能正常访问,请检查 application.yml 中的swagger-enable: true 是否设置为 启用 true

文档视频

  1. mcms使用手册

  2. 系统部署手册

  3. 插件手册

  4. 代码生成器使用文档

  5. 代码生成器在线视频教程

  6. B站视频教程

版本更新说明

每天都在改变、从未停止过….

https://gitee.com/mingSoft/MCMS
https://github.com/ming-soft/MCMS

Fork:如果想参与一起贡献代码,请fork一下;

Watch:如果想第一时间接收到代码的更新,请Watch一下;

Star:如果觉得我们做的不错,请给我们一颗星,这是对团队的最好鼓励;

ISSUES:bug请统一提交到https://gitee.com/mingSoft/MCMS/issues

版本6.1.1

  • 【升级】重构模型SQL执行逻辑并增强安全性
  • 【升级】Jackson配置调整,Long类型序列化逻辑
  • 【升级】自定定义扩展模型渲染功能增强
  • 【修复】修复删除父菜单未删除子菜单
  • 【修复】栏目路径优化
  • 【优化】请求日志输出提示优化
  • 【优化】列表创建人展示优化
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本6.1.0

  • 【升级】后端核心依赖升级,如:SpringBoot、mybatis-plus、shiro等
  • 【升级】前端框架升级,如:element-ui、vue、axios等
  • 【升级】日志功能升级记录信息更详细
  • 【优化】业务权限标识优化,避免水平、垂直越权
  • 【优化】文件上传约束限制优化
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本6.0.3

  • 【升级】第三方依赖升级
  • 【修复】修复自定义业务问题
  • 【优化】优化内容、自定义业务代码
  • 【优化】优化筛选组件使用
  • 【优化】优化上传文件检验规则
  • 【优化】优化xss检验,使系统更加安全
  • 【优化】优化编辑器前台允许上传类型展示
  • 【优化】优化自定义模块的按钮展示
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本6.0.2

  • 【安全】安全重要更新,建议所有版本务必升级到此版本
  • 【优化】编辑器复制网络图片下载本地功能优化
  • 【优化】相对路径显示图片的优化
  • 【优化】栏目管理功能优化
  • 【优化】搜索表单重置优化
  • 【优化】内容查询时间优化
  • 【优化】压缩包解压优化
  • 【优化】编辑器上传文件大小优化
  • 【优化】自定义模型表单优化
  • 【优化】字典管理缓存优化
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本6.0.1

  • 【新增】上传路径支持指定绝对路径
  • 【修复】修复单篇使用模型问题
  • 【优化】context-path 配置优化
  • 【优化】日志管理记录优化
  • 【优化】异常处理优化
  • 【优化】业务表单边界优化
  • 【优化】基础结构代码优化
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本6.0.0

  • 【升级】升级为SpringBoot 3.4.2
  • 【升级】代码整体结构优化升级
  • 【升级】框架升级带来的安全方面升级
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.5.0

  • 【升级】使用springdoc替换springfox,方便升级springboot3版本
  • 【优化】标签参数优化,支持多字段排序、规范数据类型
  • 【优化】百度编辑器体验优化
  • 【优化】文章自定义模型描述优化
  • 【优化】优化资源异常处理结构
  • 【优化】代码规范,对底层大量代码进行替换过期方法与优化代码结构
  • 【修复】文件上传漏洞修复
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.4.5

  • 【优化】优化单篇类型无文章静态化提示
  • 【优化】优化自定义业务保存更新错误提示
  • 【优化】优化单篇类型无文章静态化提示
  • 【优化】优化自定义业务保存更新错误提示
  • 【修复】修复栏目和文章id精度丢失问题
  • 【修复】修复自定义模型验证码校验值未正常回显
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.4.5

  • 【升级】依赖包的升级spring、Mybatis-plus等
  • 【新增】增加图文组件(使用方法)
  • 【优化】栏目文章数据库结构优化
  • 【优化】自定义模型业务优化
  • 【优化】优化静态化业务
  • 【优化】优化底层SQL通用查询
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.4.4

  • 【升级】第三方依赖库升级到稳定版本
  • 【新增】增加下拉字典组件(使用方法)
  • 【优化】优化项目路径使用 ms.base ms.contextpath
  • 【优化】优化文章发布时间类型,标签使用方便
  • 【优化】优化模板注释,可以使用html注释掉ms标签,优化开发体验
  • 【修复】修复自定义配置权限
  • 【修复】修复自定义不可重复校验
  • 【修复】修复MSProperties读取yml失败
  • 【修复】修复栏目标签无法获取栏目小图
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.4.3 (祝大家2025新年快乐!)

  • 【新增】配合站群插件新增主域名与附属域名字段,优化URL绑定业务
  • 【新增】静态化业务支持删除页面功能
  • 【新增】增加全局自定义标签,更灵活的配置具体参数,可在默认模板查看(使用方法)
  • 【新增】文章缩略图图片支持一次选择多个图片上传
  • 【新增】默认模板详情页面新增评论和关注功能
  • 【新增】新增原生SQL操作方法,后续版本发布会陆续替换现有在dao的xml中的$参数赋值方式
  • 【优化】优化文章发布外链接正则匹配规则
  • 【优化】优化全局异常,避免异常信息来的信息泄露问题
  • 【优化】优化第三方前端库
  • 【优化】优化新增单篇栏目编辑保存后不刷新
  • 【优化】自定义字典页面显示优化,支持sort排序
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.4.2

  • 【新增】栏目标签新增typeids属性
  • 【新增】文章列表标签orderby支持多字段排序
  • 【新增】文章扩展模型获取接口支持
  • 【优化】自定义业务数据优化,支持唯一性检测
  • 【优化】表架构关系优化
  • 【优化】附件上传安全性优化
  • 【升级】swagger升级到3
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.4.1

  • 【新增】更改文章不显示时删除静态页面
  • 【新增】文章列表页是否显示字段
  • 【新增】限制自定义配置是否允许游客获取
  • 【优化】链接栏目在文章列表可点击问题
  • 【优化】子栏目不能成为顶级栏目问题
  • 【优化】删除文章后不删除静态页面问题
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.4.0

  • 【升级】核心库vue2升级到vue3(3.4.25)
  • 【升级】element-ui升级为两element-plus(2.7.1)
  • 【升级】依赖包的升级mybatis、log4j、druid等
  • 【升级】自定义插件底层渲染优化,方便使用新版本的MCode代码生成器
  • 【优化】接口参数优化
  • 【优化】废弃一些过期的组件
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.3.6

  • 【升级】升级springboot为最新版本2.7.18以及其他第三方库(应该也是springboot2的最后一个版本,后续springboot升级直接会升级到3)
  • 【新增】系统信息查看功能
  • 【优化】优化vue2的页面规范,方便迁移vue3脚手架(开发版以上已经支持脚手架的版本选择)
  • 【优化】自定义配置的字段类型
  • 【优化】修改标签功能
  • 【优化】优化底层序列化安全问题
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.3.5

  • 【新增】自定义业务数据ID增加雪花ID支持
  • 【新增】自定义模型增加代码生成器结构
  • 【新增】XSS支持地址参数的配置方式
  • 【优化】搜索功能优化
  • 【优化】默认模板优化
  • 【优化】标签功能优化
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.3.4

  • 【优化】页面编码规范,方便脚手架版本迁移
  • 【优化】优化静态化业务
  • 【优化】标签功能优化
  • 【优化】模板管理功能优化
  • 【优化】API接口参数优化,方便第三方引用调用
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.3.3

  • 【新增】文章标签支持多栏目设置
  • 【升级】第三方依赖包,优化第三方包带来的安全问题
  • 【优化】栏目管理
  • 【优化】异常日志记录优化
  • 【优化】SQL查询优化
  • 【优化】优化百度编辑器安全问题
  • 【优化】静态化业务优化
  • 【优化】优化模板管理安全
  • 【修复】具体参考开源中国与GitHub的ISSUES https://gitee.com/mingSoft/MCMS/issues

版本5.3.2

  • 【新增】新增栏目搜索控制
  • 【新增】新增文章Tag
  • 【新增】单篇文章删除
  • 【新增】栏目类型强制转换接口
  • 【升级】升级了默认皮肤
  • 【优化】管理员权限安全
  • 【优化】栏目路径设置安全
  • 【优化】模型渲染方式
  • 【优化】文件上传方式
  • 【优化】IP获取获取方式
  • 【修复】具体参考开源中国与GitHub的ISSUES

版本5.3.1

  • 【新增】MStore模板支持在线演示的效果,提示用户使用体验
  • 【新增】分享插件,方便开发者分享模板,同时MStore具备演示效果的模板通过此插件导入可以自动生成演示数据,确保开发者导入之后立即可以使用
  • 【新增】新增栏目显示控制
  • 【新增】新增栏目大图、小图控制
  • 【新增】新增栏目、文章副标题
  • 【新增】新增单篇内容获取标签
  • 【优化】新增文章缩略图支持多图
  • 【优化】菜单支持一级显示
  • 【优化】优化站点目录设置
  • 【升级】第三方依赖包更新
  • 【修复】具体参考开源中国与GitHub的ISSUES

版本5.3.0

  • 【升级】springboot 升级到2.7.7,废弃fastjson
  • 【优化】规范权限标识
  • 【优化】自定义业务管理
  • 【优化】页面跳转规范
  • 【修复】具体参考开源中国与GitHub的ISSUES

版本5.2.11

  • 【新增】xss 增加字段排除
  • 【新增】栏目增加预览功能
  • 【优化】栏目拼音路径、标签上一篇、下一篇等
  • 【优化】Store部分插件业务优化
  • 【修复】具体参考开源中国与GitHub的ISSUES

版本5.2.10

  • 【升级】第三方依赖包
  • 【新增】文章新增外连接、新增标签 field.outlink,自定模型复制、自定义配置复制、自定义业务复制
  • 【新增】docker 镜像
  • 【优化】权限控制优化
  • 【修复】具体参考开源中国与GitHub的ISSUES

版本5.2.9

  • 【升级】springboot、mybatis、mybatis-plus、shiro 、log4j、fastjson、hutool
  • 【优化】模版管理功能
  • 【优化】权限标识优化
  • 【优化】表结构字段优化增加部分雪花ID
  • 【优化】SQL安全方面优化
  • 【修复】具体参考开源中国与GitHub的ISSUES

版本5.2.8

  • 【新增】xss注入配置
  • 【升级】element ui 版本升级到2.15.8
  • 【优化】自定义模块优化
  • 【优化】查看权限优化
  • 【优化】默认模版优化
  • 【优化】后端代码注解优化
  • 【修复】具体参考开源中国ISSUES

版本5.2.7

  • 【优化】表结构优化
  • 【优化】自定义模块优化
  • 【优化】标签解析优化
  • 【修复】安全修复
  • 【修复】具体参考开源中国ISSUES

版本5.2.6

  • 【优化】表结构优化
  • 【优化】项目配置优化
  • 【修复】SQL注入
  • 【修复】具体参考开源中国ISSUES

版本5.2.5

  • 【升级】spring boot升级到 2.3.13 稳定版本、mybatis 升级 3.5.7,等其它依赖升级
  • 【优化】项目配置优化,shiro安全配置,企业版本支持后台可视化配置
  • 【修复】修复第三方jar安全漏洞,fastjons\log4j\shiro
  • 【修复】自定义业务,可以适配不同的数据库,具体参考平台代码生成器
  • 【修复】插件修复,微信插件、发送插件、支付插件
  • 【修复】具体参考开源中国ISSUES
  • 【新增】代码生成器新增组件(验证码、MCE编辑器、图片裁切)
  • 【新增】MStore新增MCE插件

版本5.2.4

  • 【优化】标签解析结构优化
  • 【优化】搜索功能优化
  • 【修复】修复MStore插件
  • 【修复】具体参考开源中国ISSUES

版本5.2.3

  • 【新增】自定义配置
  • 【优化】标签解析结构优化
  • 【优化】标签脚本优化
  • 【修复】修复MStore功能
  • 【修复】具体参考开源中国ISSUES

版本5.2.2

  • 【框架】框架依赖升级
  • 【新增】全局异常处理
  • 【优化】搜索功能优化
  • 【优化】标签脚本优化
  • 【修复】修复MStore功能
  • 【修复】具体参考开源中国ISSUES

版本5.2.1

  • 【框架】sprintboot版本更新到2.2.13.RELEASE
  • 【新增】全局异常处理
  • 【优化】搜索功能优化
  • 【优化】标签脚本优化
  • 【修复】修复MStore功能
  • 【修复】具体参考开源中国ISSUES 5.2.0升级5.2.1步骤( 1、同步代码; 2、导入5.2.0-up-5.2.1.sql)

版本5.2

  • 【框架】增加MybatisPlus支持
  • 【新增】静态化文件夹配置(html)
  • 【新增】静态化站点文件夹配置(web),html/1->自定义/自定义
  • 【优化】模版管理修改
  • 【优化】标签修改,更灵活的支持if等逻辑判断
  • 【优化】解析速度再次提升
  • 【优化】栏目管理操作功能优化
  • 【优化】栏目生成链接优化,采用拼音格式
  • 【修复】bug修复,具体参考开源中国ISSUES
  • 5.1升级5.2.0步骤(1、同步代码;2、导入5.1-to-5.2.0-mysql.sql;3、Store更新内容插件)

版本5.1

  • 【优化】文章内容解析速度提升
  • 【优化】解析标签全面使用freemarker模板引擎全面升级,支持freemarker指令
  • 【优化】文章内容解析速度提升
  • 【优化】自定义模板上传路径
  • 【优化】栏目生成链接优化,采用拼音格式
  • 【修复】bug修复,具体参考开源中国ISSUES
  • 5.0升级5.1步骤

1、先拉取最新代码
2、执行升级5.0.0-to-5.1-mysql.sql
3、在MStore中的内容插件点击安装升级

版本5.0

  • 【优化】数据库结构重构,独立文章分类、文章表
  • 【优化】后台使用饿了么UI升级
  • 【优化】对顶层的依赖进行了更新升级
  • 【新增】一键运行版本增加linux支持
  • 【新增】docker部署与描述
  • 【修复】bug修复,具体参考开源中国ISSUES

版本4.7.2

  • 【修复】部分标签解析bug

版本4.7.1

  • 【新增】栏目属性、栏目外表连接标签;
  • 【新增】模版模版动态解析标签演示
  • 【修复】部分标签解析bug
  • 【修复】sprinboot jar包、war包运行部署问题
  • 【修复】百度编辑器bug
  • 【修复】修复mstore的模版、插件
  • 【修复】阿里资源库同步

版本4.7.0

  • 【新增】spring boot版本升级
  • 【新增】动态解析页面
  • 【新增】前端采用了vue的UI框架element ui
  • 【新增】标签全面升级、栏目标签、文章标签支持嵌套,支持基本的if逻辑判断
  • 【修复】修复BUG > 100+
  • 【注意】由于此次版本升级较大,导致mstore部分模板标签存在差异,具体阅读一下标签章节

版本4.6.5

  • 【新增】sqlserver 版本发布
  • 【优化】后台登录界面自定义
  • 【修复】修复BUG

版本4.6.4

  • 【新增】oracle版本发布
  • 【新增】默认皮肤升级,默认皮肤将支持关注、评论等模块
  • 【修复】修复BUG;

版本4.6.3

  • 【新增】栏目标签增加self属性,可以获取自身栏目信息
  • 【新增】文章关键字标签、flag标签
  • 【修复】>40个BUG的修复(权限、栏目、自定义、文章标签等);
  • 【优化】base、basic、mdiy的稳定版本依赖,可以解决大多数情况下无法更新问题;
  • 【优化】mstore升级器升级,更新了8款插件(支付插件、消息发送插件、关注插件…)

过去

过去我们做的不够好,感谢各位小伙伴还一如既往的关注铭软开源的产品。谢谢!!!

后台快速使用

后台都采用?号与名文引导的方式进行提示,让操作使用更简单,可以直接跳过使用手册直接体验具体功能。

Tip

通常后台管理使用流程,应用设置>新增栏目>内容发布>静态化

后台功能介绍

主页面

左侧导航和主页面常用功能显示,在功能大全中点击星星后刷新页面,即可在左侧导航和常用功能部分显示菜单 收藏

系统设置

应用设置

在应用设置里可以修改网站标题,站点风格(静态页面生成的模板)等,具体可参考下方小字提示说明。 Alt text

模板管理

通过模板管理,在系统后台就可以对所有模板进行管理、修改;如果是源码启动,可以本地操作模板

Tip

如果是导入从mstore下载的带演示系统的皮肤,建议使用分享插件 导入,这样可以确保导入后的效果与演示站一致

Tip

模板上传zip包后,应到应用设置选定站点风格。否则会出现栏目管理编辑下拉后无新上传模板。

模板文件中有数据文件,执行sql,再静态化就能看到模板显示效果

没有数据文件,需要手动在栏目管理绑定模板

执行sql报错?栏目sql中的category_datetime字段需删除,文章sql的content_url更新成content_out_link(具体情况请按照控制台提示信息操作)

导入模板静态化报错?

系统日志

内容管理

栏目管理

只有叶子栏目可以发布文章(任何拥有子栏目的栏目都不是叶子栏目)

叶子栏目说明:

eg: 公司产品下有平台产品、开源产品等栏目;而平台产品、开源产品下没有栏目,所以平台产品、开源产品称为叶子栏目,即只能在平台产品、开源产品下新增发布文章。

栏目列表

新增

  • 栏目需绑定模板,该栏目才能通过静态化功能生成对应页面。
  • 列表模板:列表类型的栏目可以绑定列表模板,栏目下的文章会以此模板展示文章列表;如开源的新闻动态、公司产品栏目。
  • 内容模板:列表类型和单篇类型的栏目可以绑定内容模板,栏目下的文章会以绑定的内容模板来展示文章内容;如开源的联系我们、其他列表栏目的文章。
  • 自定义模型,栏目可以通过绑定自定义模型来给文章扩展字段,使用参考
  • 栏目属性是栏目的标记,来源于字典值,主要作用是在模板使用channel标签时能够通过flag参数来获取想要的某一类栏目数据,如导航条。

Important

首页特别说明,首页不是一个栏目,属于独立的页面类型;系统提供专门的静态化功能用于生成首页,选择主页模板生成主页即可。 首页模板必须使用 index.htm 作为文件名。 img.png

文章管理

文章列表

新增

文章必须有所属栏目,当所属栏目的类型为列表时,可以在该栏目发布多篇文章,类型为内容时只能发布一篇文章,类型为外链接则不可发布文章。

Tip

列表栏目,在栏目管理中 列表模板绑定 栏目模板;内容模板绑定文章模板 内容栏目,只需要绑定内容模板 链接栏目,无需绑定模板

静态化

  1. 生成主页:根据选中模板来生成首页,一般首页模板约定为index.htm。
  2. 生成文章:根据文章所属栏目绑定的内容模板,生成指定时间之后创建的文章内容页(单篇栏目的内容通过生成栏目生成)
  3. 生成栏目:根据当前栏目绑定的列表模板或单篇模板,生成栏目类型为列表或者单篇的数据

Important

如果文章更新,必须重新静态化文章和文章相对应的栏目才能看到效果。

静态化核心:修改的数据,在什么页面会有体现,那么就需要静态化对应的位置。

比如,修改了栏目顺序,由于首页、列表页、内容详情页,都会看到栏目数据,所以,首页、列表页、内容详情页都需要重新静态化。

如下图 ,调整导航栏中平台产品的顺序(导航栏需使用oderby) Alt text

列表栏目修改绑定的模板静态化演示

Alt text

文章静态化注意文章更新时间,生成文章只会静态化 文章更新时间在指定时间之后的文章

静态化文章时需要同时静态化该文章所属栏目,否则栏目列表页不会展示该文章列表

Tip

生成主页功能一般只有index.htm或调试模板使用;其余模板先通过栏目绑定模板,再通过生成文章或生成栏目使用。 调试模板需要先熟悉标签的使用。

权限管理

菜单管理

方便对系统菜单进行管理,复制、导入。

使用代码生成器快速增加菜单

  1. 使用代码生成器拖拽组件,完成后点击下载代码(项目包名需要注意放入启动类)代码生成器的使用?
  2. 将下载代码中的src目录直接覆盖,放入项目中;执行src同级目录的sql文件;
  3. 重新编译项目,启动后,在平台“代码“界面编辑菜单,按下图操作获取菜单json;在菜单管理中导入菜单json; Alt text 复制后即可导入菜单 Alt text

角色管理

不同的管理员可以设置不同的角色,不同角色按需设置权限

管理员管理

通过后台对管理员管理,有管理员新增权限的才可以新增管理员,只有管理员才能进入后台。管理员不可以注册,只能分配,保证系统安全性。

模版快速制作

模板制作

Tip

系统提供的默认皮肤是很好的参考的案例模板,演示了几乎所有的标签使用,请仔细阅读默认皮肤!!!可以参考博文 记一次免费开源的Java cms系统MCms模板使用过程

标签

ms标签基于freemark,所以两者语法是一样

Tip

在使用标签之前,必须了解栏目类型:列表、内容 模版的概念,
列表:通常指带有分页的文章模版,例如:新闻列表、产品列表,栏目类型应该选这列表
内容:具体的文章内容模版,例如:新闻详情、产品详情,如果栏目下面只有一篇内容,例如:关于我们、联系我们,这种情况栏目类型应该选内容
如果当栏目只是一个显示,栏目下不会产生内容,推荐栏目类型选择链接

  • 标签注释

5.4.4及以上版本可以使用html注释掉ms标签

  • 标签写法说明

Warning

标签不能用在纯js\css文件中,标签不会被解析,只有在模板中使用才会解析。 标签上的参数设置,不能加空格 例如:错误写法 size = 10 ,等号两边必须不能存在空格 size=10 标签参数值如果是字符串必须使用引号,如:flag='h'

  • 在css/js中使用标签

Tip

如果需要在css/js中使用标签,如js,

可通过<#include “js路径” /> 引入js逻辑,从而让freemarker能解析js中的标签

  • css/js文件修改问题
  1. 如果是修改模板内部的css/js文件,需要重新静态化,样式才能生效。
  2. 如果是修改引入的外部css/js文件,按住shift+刷新,样式即可生效
  • 内容类型说明

Note

如果栏目为内容类型时,如果没有编辑栏目的内容时,静态化不会生成该栏目页面且静态化会异常

  • 大数据量文章处理建议

Note

如果文章数据比较多,如十几万的文章数据,建议带分页文章列表数据采用接口方式获取,这样就可以减少栏目列表页面的生成

开发环境

下载MCms源码 下载地址

下载最新的JTM一键运行版 下载地址

静态文件目录

在没有嵌入MCms标签之前为静态页面,静态页面结构如下图。

Tip

模版文件推荐.htm后缀,具体的模版文件夹与文件明白根据实际所需情况来创建,首页模板必须使用 index.htm 作为文件名。

设计人员与前端人员可以通过在线设计器制作模版,快速实现html\css的代码生成;

静态文件命名推荐英文或拼音形式,单词之间用横杆隔开“-”,不推荐英文+拼音混合的命名方式;

注释与代码缩进;

模版文件(嵌入MCms标签)

嵌入了MCms之后才算是模版文件,模版常见规范如下:

公共模版:模版文件中出现频率较高的,可以抽成公共模版文件,例如常见的头部、底部;

尽量不要写死标签属性值,例如顶部导航,可以通过标签动态获取;

可以备用一个空的模板文件,只用于那些“只需要数据,不需要生成页面的栏目”,相比与随意绑定一个模板会大大提高静态化的效率。

Tip

也可以快速通过模版设计器辅助生成MCms标签
提取出公用的模版文件

模板开发一般流程

1、设计稿->切图

2、切图->html静态文件

3、后台创建好门户栏目结构

4、html静态文件->嵌入MCms标签

5、本地确认模版效果没问题->上传到服务器对应模版位置

Tip

门户项目实施第一先确定栏目,栏目定义好了整体的页面都会根据栏目结构做设计。

上传模版

有两种方式:

后台上传:

将制作好的模版使用压缩工具ZIP格式,存储方式压缩,将压缩包上传到后台就可以使用。

Alt text

本地上传:

本地使用源码部署后

2.1 无站群方式(默认):直接将模板文件存放在src/main/webapp/template/1目录下

Alt text

2.2 有站群方式:参考

Tip

本地开发模版建议开启自动编译,再直接在模版文件夹template/中直接修改模版,可以提供开发效率 Alt text 如果需要部署线上可以直接到对应的模版文件夹修改 Alt text 模板修改后必须重新静态化才能看到最新的效果参考

常见业务场景实现

幻灯

顶部大图,不与具体的文章关联的图片,通过创建一个独立的栏目来维护这些图片

文章幻灯、推荐文章、热门文章、头条文章

使用文章属性实现,通过 ms:artlist 标签增加flag属性实现获取

前台获取当前登录人信息

发送请求 具体可参考接口 ms-basic模块下 basic->action->web->LoginAction
Alt text

老模版升级

Tip

也可以参考网络博文 记一次免费开源的Java cms系统MCms模板下载与使用且升级一套旧的皮肤

MStore里面的有些模版存在一些标签的差异,如果遇到生成失败,可以参考按下面的步骤进行标签修改

  • 修改一、将所有的[]标签格式替换为${}
[field.title/] 替换为${field.title}
  • 修改二、将{ms:field.*/}或${field:*}替换为${field.*}
{ms:field.content/}替换为${field.content}
  • 修改三、将图片[field.litpic/]修改为{@ms:file field.litpic/}
<img src="{ms:global.host/}{@ms:file field.litpic/}">
  • 修改四、将时间${field.date?date?}或者[[field.date?/]]修改为${field.date?}
${field.date?date?string("yyyy-mm-dd")}或者[[field.date?string("yyyy-mm-dd")/]] 
替换为 
${field.date?string("yyyy-MM-dd")}
  • 修改五、字符串的替换

[field.content.substring(0,255)] 替换为 {@ms:len field.content 255/}

${field.content[0..140]} 替换为 {@ms:len field.content 140/}
  • 修改六、将channel标签和arclist里的typeid替换为自己的栏目id

Tip

老模板没有逻辑标签,如果需要可自行添加。有些老模板使用include标签引入了搜索模板,但搜索模板并没有创建,静态化可能会报错,这时可以删除引入搜索模板的include标签或创建搜索模板

  • 修改七、静态资源路径修改
static.ming-soft.net/base/ms.base.min.js
static.ming-soft.net/util/ms.util.min.js
static.ming-soft.net/people/ms.people.min.js
/static/plugins/ms/1.0.0/ms.util.js
/static/plugins/ms/1.0.0/ms.http.js
/static/plugins/ms/1.0.0/ms.js
修改成
static/plugins/ms/2.0/ms.umd.js

一些static的资源可以替换对应的最新路径。例如:

/static/plugins/element-ui/2.12.0/index.js
修改成
static/plugins/element-ui/2.15.8/index.js

/static/plugins/element-ui/2.12.0/index.css
修改成
/static/plugins/element-ui/2.15.8/index.css

Tip

1.有些模板资源路径错误报404错误,重新正确引入正确路径,但是有些静态文件版本对不上(如element-ui),所以修改成现有的静态资源版本。

2.静态文件是有加载顺序,vue.js必须在ms.umd.js前面

  • 修改八、search模板修改

在head和search中的form表单,action属性为 {ms:global.host/}mcms/search.do 表单中的查询关键字的name属性值,需按下面要求填写

  • 查询关键字设置 (模糊查询)
    • 文章标题 name必须为content_title 下面同理
    • 文章作者 content_author
    • 文章详情 content_details,更多字段参考下方文档
  • 根据栏目查询
    • 文章栏目 categoryIds

快速仿站

需要使用仿站小工具,下载地址:仿站小工具V11.1.zip

Tip

采集网页的工具有很多,开发者自行选择自己熟练的工具来使用,只要到能快速将目标网站的静态页面采集下来就可以

网站采集

第一步:查看网站的每个页面,进行采集,采集后检查静态页面效果是否与原网站一样,各种浏览器兼容性是否没问题。
第二步:首先替换首页的标签,先将网站头部底部和其他公共部分抽出来用新的单独文件保存,并用标签对应在页面上引用。再对首页的其他模块进行一块一块标签替换,不要一次性全部替换。
第三步:替换列表与内容显示页及其他页面的标签

Tip

仿的网站中包含一些线上的JS或CSS等其他文件,需要将其下载到模板里面引用;
模板中注意不要出现原网站的信息、链接;
确保模板的页面效果与原网站效果保持一致;

仿站工具

打开工具后点开“系统设置”进行如下设置:

  1. 新建网站,输入网址、文件名称,点“添加”将把本条信息存放到下面列表,把需要采集的页面地址与文件名对应填入后添加(一次性将需要的页面全部采集),点“转下一步”

Tip

系统设置中 设置好目录结构的命名,名称统一小写;

Tip

模版文件名称后缀推荐htm,不要使用html,这样的好处是可以与静态化之后的文件区分开

  1. 设置采集文件路径及保存位置,开始下载

  1. 下载完成后,打开目录可以看到指定目录下的采集到的站点文件。

二次开发

页面修改

修改登录页面

src/main/webapp/WEB-INF/manager/目录下创建login.ftl文件,就可以达到完全重写登陆页面的目的,如果只是修改登陆左侧的图片,可以直接审查元素查看对应的图片路径,再创建对应一致的路径图片

修改主界面


src/main/webapp/WEB-INF/manager/目录下创建main.ftl文件即可。如果是一件版本 对应文件WEB-INF/manager/main.ftl

参考页面

业务开发

可以通过 代码生成器 进行快速开发,具体参考 代码生成器手册

业务接口

接口部分截图

Note

前端-XXX 表示游客都可以调用的接口 ,前端-用户-xxx 表示需要会员登陆后才能调用的接口,后端-xxx 表示需要管理员登陆才能调用的接口.

接口调试

基于swagger很方便进行接口调试

接口地址 http://localhost:8080/swagger-ui.html

Tip

生产系统必须关闭 swagger 配置具体参考 application.yml,需要登陆的接口调试,可以先通过登陆系统之后再通过swagger进行调试。

标签

通用全局 {ms:global.*/}

站点基本信息标签 功能性标签

适用模版

首页模版、列表模版、内容模版、自定义页面模版

Tip

一般html模版页都可以使用通用全局标签

格式

{ms:global.*}

站点基本信息标签

标签描述
{ms:global.name/}网站标题
{ms:global.logo/}返回logo的图片地址<img src=“{@ms:file global.logo/}”/>
{ms:global.keyword/}网站关键字 范例:<meta name=“keywords” content=“{ms:global.keyword/}”/>
{ms:global.descrip/}网站描述 范例:<meta name=“description” content=“{ms:global.descrip/}”/>
{ms:global.copyright/}网站版权信息
{ms:global.html/}返回项目生成的静态文件所在目录,由html和站点生成目录组成,格式如: /html/web/
{ms:global.style/}返回当前站点的模板路径(eg:template/1/default/),主要用于引入css,js等资源文件读取,**范例: <script **src=“/{ms:global.style/}js/index.js”><img src=“/{ms:global.style/}images/pic.jpg”/> 使用时必须以“/“开头
{ms:global.contextpath/}获取项目名(yml中server.servlet.context-path对应的值),用于css,js等文件引入等,**范例: <script **src=“{ms:global.contextpath/}/{ms:global.style/}js/index.js”>
{ms:global.template/}获取对应模版的名称(企业版本及政务版本标签,针对一个网址多套皮肤)
{ms:global.url/}(不推荐在栏目、文章链接中使用,建议使用{ms:global.html/}替代; 返回域名+静态文件目录,格式:域名/html/站点目录/
{ms:global.host/}(不推荐在引入静态资源时使用) 返回项目对应域名地址

Tip

由于部署项目时,使用了{ms:global.url/}或{ms:global.host/}标签导致部署在其他服务器上时页面带有固定域名,导致访问失败;

Tip

使用相对路径获取图片时,不需要在标签前加 / ,否则会导致这种相对路径方式为//upload/**.jpg,从而获取不到图片。 同样,项目中文章链接采用相对路径去掉{ms:global.url/}时,需要使用{ms:global.html/}替换。

功能性标签

标签描述
<#include “url”>用于引入公用的HTML等文件,例如公用的头部导航,<#include “head.htm”> ,注意这里的url参数是带双引号。注意:不支持相对路径,如果不需要include标签,必须删除,不能<!–#include “url”–>
{@ms:file 图片或文件/}通过后台上传控件发布的图片或者文件都需要通过ms:file获取文件路径
{@ms:len 文本 截取长度/}根据长度截取文本内容,如果文本内容长度超出截取长度会采用省略号替代,例如:{@ms:len field.title 10/} 对应MCms内容管理…
{@ms:tags/}获取文章绑定的文章标签。首页获取全部文章标签,列表页获取当前栏目下所有文章标签
{@ms:memorialday/}国家公祭日标签,会自动将页面变成灰色(政务版本有效),通常应用在首页模版头部或者尾部(在公共页面(head-file)中使用,则所有引用的页面也会生效),需先在公祭日配置开启,日期格式按“MM-dd“以逗号分隔,如05-12,09-18,12-13,12-01
{@ms:accessibility “绑定dom”/}无障碍阅读,推荐放在模版的最底部,会在 绑定dom 元素上增加开启关闭的事件(政务版本有效),使用格式如下:<button id=“wuzhangai”/>
{@ms:accessibility “wuzhangai”/}

Tip

注意include标签事项(双引号、不支持相对路径,不能适应../方式、不需要必须删除include标签)

ms:file 也可以使用 ${图片或文件?eval\[0\].url} 获取,获取多个路径需要循环获取,代码如下:

<#list 图片或文件?eval as file> ${file.url} </#list>

使用{@ms:len 文本 截取长度/} 所截取文本,中英文都只占一个字符,且文字截取从零开始计算,即文字长度是截取长度数字+1

自定义全局标签

全局标签中一般文本写法

用于获取模型中的文本字段内容

标签格式:{ms:global.模型名称(foot).字段名称(address)/} Alt text

全局标签中文件路径获取写法

用于获取模型中文件类型字段的路径,适用于图片、附件等。

标签:{@ms:file global.模型名称(foot).字段名称(ico)/} Alt text

模型更新

在铭软官网的代码生成器托组件的时候推荐添加注释信息: img.png Alt text

字段名称规范

数据库字段名转为标签中的字段名称

eg: 数据库字段名:DATE_A

  1. 先将数据库字段名转为小写 DATE_A -> date_a
  2. 如果有下划线,转为驼峰写法 date_a -> dateA Alt text

范例

<title>{ms:global.name/}</title>
<meta name="description" content="{ms:global.descrip/}">
<meta name="keywords" content="{ms:global.keyword/}">
<link rel="shortcut icon" href="/{ms:global.style/}/fav.ico" type="x-icon">
<link rel="stylesheet" href="/{ms:global.style/}/css/base.css">
<script src="/{ms:global.style/}/js/jquery.js"></script>
<#include "nav.htm">
<h2><a href="/">站点首页</a></h2>
<img src="{@ms:file global.logo/}"/>
<!-- @ms:tags使用范例 点击标签后搜索展示带有该标签的所有文章列表 需要有search.htm模板(参考开源模板) -->
...
{@ms:tags/}
<#list tags?split(",") as content_tag>
  <a href="/mcms/search?content_tag=${content_tag}">${content_tag} </a> 
</#list>
...

Tip

不能用在.css或.js文件里面,注意{ms:*/}标签没有$符号,且必须/结束

栏目列表 {ms:channel}

适用模版

首页模版、列表模版、内容模版、自定义页面模版

Tip

主页面模版使用:如果不设置typeid会取出所有**顶级(第一级)**栏目;

列表模版使用:typid参数值为当前访问的栏目ID

内容模版使用: typeid为当前文章的所属栏目id

格式

{ms:channel 参数1=值1 参数2=值2}
${field.*}
{/ms:channel}

Tip

栏目的显示的排序可以通过排序字段设置

嵌套格式

{ms:channel type="nav" }
    <!--第一级-->
    +${field.typetitle} <br/>
    {ms:channel typeid=field.id}
        <!--第二级-->
        +++${field.typetitle}<br/>
        {ms:channel typeid=field.id}
        <!--第三级-->
            ++++++${field.typetitle}<br/>
        {/ms:channel}
    {/ms:channel}
{/ms:channel}

参数

参数名称类型必须示例值默认值描述
type字符串son
top
level
self
path
parent
nav
sonnav:该标签用于首页导航取第一级栏目
son:取下级栏目;
top:获取栏目的父级的平级栏目列表,typeid参数不为空才有效;
parent:上一级栏目,只获取一个;
level:取同级栏目,顶级栏目无效;
self: 取当前指定typeid本身;
path:取当前栏目的路径,通常用于详情页,显示隶属栏目位置,例如:新闻中心 > 行业新闻;
typeid字符串‘1’所有父级栏目typeid有值时,取所指栏目的子级
typeids字符串1,2,3typeids参数值,多个以逗号隔开,当typeids有值时,type参数无效,只取指定栏目id数据(该参数只在5.4.2及以上版本支持)
size整型>0所有栏目返回既定条件下的栏目个数,不使用则默认返回全部(5.3.1及以上版本支持)
flag字符串在自定义字典中定义好对应的值,具体属性值请在栏目属性进行查看,使用方式参考文章列表的flag使用
noflag字符串参考文章列表的noflag
orderby字符串date 更新时间
sort 排序
id栏目排序,sort排序需要在自定义顺序设置才能看到效果
order字符串升序desc:按照倒序排列,asc:按照正序排列
tableName字符串MDIY_MODEL_XXXtypeid指定对应栏目,type为self才有效,使用代码生成器导入的模型表名
appId字符串‘1’当前站点id配合 typeid 参数使用,指定目标站点ID以获取跨站点的栏目数据(需安装站群插件)

Tip

order参数必须要与orderby一起使用才有效 属性类型为字符串,使用需要使用引号 例如 type=“son” order=“desc”

输出字段

Tip

在栏目绑定的列表模板,如开源的news-list模板;${field.栏目相关字段}即可输出当前栏目对应数据;不需要被{ms:channel}标签包裹

字段名称描述
${field.index}序号,根据显示条数显示的序号1 2 …..10
${field.type}栏目类型,1:列表 2:单篇 3:链接与栏目链接一起使用
${field.typetitle}栏目名称,与栏目链接一起使用
${field.typeshorttitle}栏目副标题
${field.typepath}栏目拼音路径
${field.typelink}栏目链接:{ms:global.html/}${field.typelink};注意栏目属性为外连接标签显示为外连接的内容与${field.typeurl}一致
${field.typekeyword}栏目关键字
${field.typedescrip}栏目描述
${field.id}栏目id,单篇文章情况为文章id,需使用${field.typeid}获取栏目id
{@ms:file field.typelitpic/}栏目banner图:{@ms:file field.typelitpic/}
{@ms:file field.typeico/}栏目小图:{@ms:file field.typeico/}
${field.typeurl}栏目自定义链接,需要配合if条件来决定跳转自定义链接
${field.flag}栏目属性
${field.childsize}子分类数量
${field.parentid}父级编号
${field.parentids}父级编号集合
${field.typeleaf}子节点标识,返回 1 表示该栏目下没有任何子栏目,返回 0 则表示是有子栏目

Tip

当栏目为单篇时,需要选择静态化生成栏目,页面才会生成。且单篇栏目下,需要发布文章,才可用标签获取

范例

下面列举一些常用的使用场景

顶级栏目导航


{ms:channel type='nav'}
<a href="{ms:global.html/}${field.typelink}">${field.typetitle}</a>
{/ms:channel}

栏目链接为外链接

<!--field.type==3 为外链接类型-->
{ms:channel type='nav'}
    <a href="<#if field.type==3>${field.typeurl}<#else>{ms:global.html/}${field.typelink}</#if>">
        ${field.typetitle}
    </a>
{/ms:channel}

当前位置

主要用在列表模版、内容模版上,可以实现例如:当前位置:首页 > 新闻中心 > 行业新闻 的效果

当前位置: <a href="{ms:global.html/}/index.html">首页 </a>
{ms:channel type="path"}
   <a href="">> ${field.typetitle}</a>
{/ms:channel}

channel中使用include

主要用于父栏目和子栏目绑定不是相同模板的情况,在父栏目页面include子栏目的模板,方便维护和简化模板

<!-- 父栏目列表模板 -->
<#assign curTypeId=field.id>
{ms:channel typeid="son1的id" type="self"}
    <#include "son1.htm" />
{/ms:channel}
...

<!-- 子栏目son1的列表模板 -->
include获取传参:${curTypeId}
{ms:arclist size=10 ispaging=true }
    ${field.title}==${field.typetitle}==${field.typeid}  <br>
{/ms:arclist}
<#if page.total?number gt 0>
    <ul>
    <li><a href="{ms:global.html/}{ms:page.index/}">首页</a></li>
    <li><a href="{ms:global.html/}{ms:page.pre/}">上一页</a></li>
    <li><a href="{ms:global.html/}{ms:page.next/}">下一页</a></li>
    <li><a href="{ms:global.html/}{ms:page.last/}">末页</a></li>
    <li>当前页/总页数<span>{ms:page.cur/}/{ms:page.total/}</span></li>
    <li>共有:<span>{ms:page.rcount/}</span>篇文章</li>
    </ul>
</#if>

Tip

注意: 此种用法,父栏目不支持分页;分页子栏目在父栏目只显示第一页的数据,在自身栏目列表页分页正常;

可在父栏目定义变量(如curTypeId)传递给include的子栏目页面,定义的变量值在父栏目页面的子栏目模板中可以获取到;在子栏目自身的列表页面无法获取;如访问parent.html,能输出curTypeId的值;访问son.html,子栏目输出的curTypeId为空

当前栏目的子栏目

{ms:channel type="son" }
<a href="{ms:global.html/}${field.typelink}">${field.typetitle}</a>
{/ms:channel}

默认显示第一个子栏目的页面

可以将父栏目设置为链接,在自定义链接中填写具体的子栏目对应静态文件链接 样例代码片段,具体可以参考默认皮肤。

<a href="<#if field.type == 3>{ms:global.html/}${field.typeurl}<#else>{ms:global.html/}${field.typelink}</#if>">${field.typetitle}</a>

栏目选中效果


<a href="/" title="首页" class=" <#assign typeid=field.typeid><#if typeid==''>选中样式</#if>">
    <span data-title="首页">首页</span>
</a>
<!-- 定义typeid变量 field为当前栏目对象-->
<#assign typeid=field.typeid>
<#assign ids=field.parentids>
{ms:channel type="nav" }
<!-- if标签中直接使用typeid-->
<#if field.typeid == typeid || (ids?has_content && ids?split(",")?seq_contains(field.typeid.toString()))>
    <a href="{ms:global.html/}${field.typelink}" class="选中样式">${field.typetitle}</a>
<#else>
    <a href="{ms:global.html/}${field.typelink}">${field.typetitle}</a>
</#if>
{/ms:channel}

每个栏目不同banner的效果

栏目上传不同的缩略图就可以实现不同顶部banner图的效果

Tip

如果存在子栏目需要显示顶级栏目的图片,可以通过 if 逻辑来实现

注意#assign定义变量 filed.typeid 值为当前预览页面的栏目id(全局栏目id),而 ms:channel 循环里面的 filed.typeid 只是当前循环的栏目id(局部栏目id)

父栏目+当前子栏目+选择效果

下面是 default\news-list.htm 默认模版里面的代码片段


<!--通过type='parent'获取当前页面父栏目-->
{ms:channel type='parent'}
    <span class="title"> ${field.typetitle}</span>
{/ms:channel}

<!-- 定义typeid变量 field为当前栏目对象-->
<#assign typetitle=field.typetitle>
<!-- 判断当前是否已经是子节点-->
<#if field.typeleaf == 1>
{ms:channel type='level'}
    <!-- 选中效果 -->
    <a href="{ms:global.html/}${field.typelink}" class="<#if typetitle==field.typetitle>sub-title-sel<#else>sub-title</#if>">
        ${field.typetitle}
    </a>
{/ms:channel}
<#else>
{ms:channel type='son'}
    <!-- 选中效果 -->
    <a href="{ms:global.html/}${field.typelink}" class="<#if typetitle==field.typetitle>sub-title-sel<#else>sub-title</#if>">
        ${field.typetitle}
    </a>
{/ms:channel}
</#if>

Tip

field.typeleaf 子节点标识,表示该栏目下没有子栏目。就获取平级栏目,相反就获取子栏目

自定义栏目跳转链接


{ms:channel  }
    <!-- if标签中直接使用typeid-->
    <#if field.typeurl?has_content>
    <a href="{ms:global.html/}${field.typeurl}">${field.typetitle}</a>
    <#else>
    <a href="{ms:global.html/}${field.typelink}">${field.typetitle}</a>
    </#if>
{/ms:channel}


获取指定栏目的多级父栏目中的某个父栏目

<#assign typeid>
<!--指定某个栏目-->
{ms:channel typeid=栏目id type='self'}
    <#list field.parentids.split(",") as item>
<!--取parentids中的第二个parentid,index的值从0开始-->
        <#if item?index = 1>
            ${item}
        </#if>
    </#list>
{/ms:channel}
</#assign>
${typeid}

获取任意层级父栏目信息

系统field.parentids 存储格式示例如:"100,101,102":
- [0] 100(一级栏目/顶级)
- [1] 101(二级栏目)
- [2] 102(三级栏目/直接父级),以此类推

通过调整数组索引获取不同层级:即<#assign topid=ids?split(",")[0或1或2]>

获取顶级栏目信息
<!-- 定义变量topid存储顶级栏目id -->
<#assign typeid=field.typeid>
<#assign ids=field.parentids>
<#if ids==''>
<#assign topid=typeid>
<#else>
<#assign topid=ids?split(",")[0]>
</#if>

<!-- 使用topid获取栏目信息 -->
{ms:channel type='self' typeid=topid}
    ${field.typetitle}
{/ms:channel}


<!-- 获取二级栏目信息 -->
将上述 <#assign topid=ids?split(",")[1]>  修改数组中的数字改为1,其余不变,其他层级以此类推。

orderby 示例

{ms:channel type='nav' flag='n' size='8' orderby='sort' order='desc'}
    ${field.typetitle}
{/ms:channel}

指定多个typeids样例

<!-- 如果使用了typeids,typeid和type参数将失效,且只获取指定栏目的自身数据 -->
{ms:channel typeids='栏目id1,栏目id2,栏目id3'}
    ${field.typetitle}
{/ms:channel}

文章列表 {ms:arclist}

读取文章列表数据

适用模版

首页模版、列表模版、内容模版、自定义页面模版

Tip

主页面模版使用:如果不设置 typeid 会取出所有的栏目下的数据,推荐增加 typeid 值;
列表模版使用: 可以不设置 typeid 参数,默认为当前访问的栏目 ID,分页列表必须使用ispaging属性,注意:每个列表模版只能有一个使用了ispaging属性的arclist标签,且 ispaging 获取的分页数据是当前栏目的数据; 内容模版使用: 可以不设置 typeid,默认为当前文章的所属栏目 id;

格式

{ms:arclist [参数1=值1 参数2=值2]} ${field.*} {/ms:arclist}

Tip

可以嵌套在 channel 里面来实现栏目动态获取文章列表

嵌套格式

多级栏目文章嵌套格式

{ms:channel type="nav"}
    <!--顶级栏目-->
    +${field.typetitle} 
    {ms:channel}
        <!--二级栏目-->
        ++${field.typetitle} 
        <!--二级栏目下的文章-->
        {ms:arclist typeid=field.id}
            ${field.title}
        {/ms:arclist} 
    {/ms:channel} 
{/ms:channel}

Tip

在内容模版页、列表模版页使用栏目嵌套 ms:arclist标签时, 必须增加typeid=field.id参数,在主页、自定义页面 可以不使用

参数

名称类型必须示列值默认值描述
typeid字符串‘1’栏目 ID,在列表模板和内容模板会默认获取当前所属栏目 ID,可以不用设置typeid参数,在内容页不设置可以实现类似相关文章的效果
typeids字符串1,2,3栏目 IDs,以逗号分隔,返回多个栏目下的文章,如果设置了typeids参数,typeid参数将不会生效;(5.4.2 及以上版本)
size整型>020返回文档列表总数,默认为 20 条,也可以配合分页使用;特殊用法参考 2.11
topflag字符串c,f,h,p,指定某类属性的文章固定优先获取,即文章置顶效果(5.3.3 及以上版本)
flag字符串c,f,h,p,指定显示某个属性的文章数据,幻灯 f,图片 p,推荐 c,头条 h,跳转 j;请按照示例值顺序设置,例如:flag=c,f;注意具体属性值请在文章属性查看
noflag字符串c,f,h,p,noflag=c,显示文章属性除 c 之外的所有文章;请按照示例值顺序设置,例如:noflag=c,f;c,p
orderby字符串datecontent_datetime根据文章发布时间排序:date
根据文章更新时间排序:updatedate
根据文章自定义顺序排序:sort
根据文章点击数排序:hit(如果点击量增加了,需要重新生成排序才会变化),不填则显示默认顺序,支持多个参数值,以英文逗号分隔,例如:orderby=‘sort,date’(多个参数值,仅5.4.2及以上版本支持)
order字符串ascdescdesc:按照倒序(默认降序)排列
asc:按照正序(升序)排列
当orderby多个参数值时,可以自定义多个排序参数,以英文逗号分隔,例如:order=“asc,desc”,需和orderby参数对应(仅5.5.0以上版本支持);
ispaging布尔型truefalse当文章列表出现分页标签时必须添加本属性,且只能使用一次。注意不要双引号,可以不设置 typeid 参数默认获取当前栏目数据
tableName字符串mdiy_model_mobiletypeid必须指定对应栏目才有效,使用代码生成器导入的模型表名
appId字符串‘1’当前站点id配合 typeid 参数使用,指定目标站点ID以获取跨站点栏目下的文章数据(需安装站群插件)

Tip

属性类型为字符串使用需要使用引号 flag=“c,h” order=“desc”;
flag 可以通过后台自定义字典进行维护;
ispaging 只能列表模版下使用,使用 ispaging 之后 flag、noflag 不能使用,会导致分页不同步;
tablename 可以自定义模型决定,自定义模型创建流程:先在 代码生成器 拖出模型并下载模型代码 -> 打开系统后台自定义管理,进行自定义模型导入 -> 打开栏目管理,进行栏目绑定自定义模型 -> 打开文章管理,进行发布内容;
取自定义模型字段数据时,字段名是数据库字段名,严格区分大小写;参数必须在一行,否则会导致参数解析失败;

输出字段

字段名称描述
${field.index}序号,根据显示条数显示的序号 1 2 …..10
${field.id}文章编号
${field.title}文章标题
${field.author}文章作者
${field.source}文章来源
${field.outlink}文章外链接,必须以 http/http 开头,使用格式如:<a href=“${field.outlink}”/>
${field.typetitle}文章所属分类的名称
${field.typeshorttitle}文章所属分类的副标题
${field.typeid}文章所属分类的编号
${field.typelitpic}文章所属分类的 banner 图
${field.typeico}文章所属分类的小图
${field.typekeyword}文章所属分类的关键字
${field.topid}文章所属分类的顶级分类
${field.parentid}文章所属分类的父栏目编号
${field.parentids}文章所属分类的所有父栏目编号
${field.type}文章所属分类的栏目类型,如列表栏目输出为 1,链接栏目输出为 3
${field.typelink}文章分类链接,点击连接连接到当前分类的列表:{ms:global.html/}${field.typelink}
{@ms:file field.litpic/}文章缩略图,上传文章的缩略图,调用缩略图地址:{@ms:file field.litpic/},支持上传多张图片
${field.link}文章内容链接,点击显示文章具体的内容地址,一般配合文章标题使用:{ms:global.html/}${field.link}
${field.date?string(“”)}根据用户指定的格式输出时间,${field.date?string(“yyyy-MM-dd”)}
${field.descrip}文章摘要
${field.hit}文章点击量
${field.flag}文章 flag 属性
${field.keyword}文章关键字
${field.tags}文章标签,多个以“,“隔开;推荐文章标签字典的 label 和值一致
${field.css}文章标题样式,常规用法为用来控制某篇文章标题的样式特殊展示;企业版本及政务版本支持。
${field.*}其他自定义标签,使用代码生成器拖拽的字段名

Tip

不支持直接获取field.content,要在列表获取文章内容请参考列表页获取内容

field.title 长度的控制通过{@ms:len field.title 长度 /};

field.litpic缩略图必须要使用 {@ms:file field.litpic /} 来获取缩略图路径,多张缩略图的模板用法参考范例 2.4;

field.date 日期格式通过 ?string("yyyy-MM-dd hh:mm:ss") 格式化

自定义模型字段 通过 ${field.扩展模型字段名} 获取 html 属性中使用field.title后,““建议替换为title="${field.title?replace('\"','“')}"title="${field.title?replace('\"','')}";

orderby 不能使用在上下篇的文章列表,会导致上下篇和列表显示不一致;

列表页通过标签获取的文章点击数默认是不支持动态的,需要在列表页动态获取文章点击数,推荐列表页通过接口获取文章列表数据,接口查看 swagger 前端-内容模块接口-查询文章列表接口(企业版或政务版查看前端-文章接口)

范例

基础使用格式

获取 typeid='62' 下面size=5 五篇 flag="c" 推荐的文章

{ms:arclist flag="c" size=5 typeid='62'}
<img src="{@ms:file field.litpic/}" />
<a href="{ms:global.html/}${field.link}" target="_self">${field.title}</a>
{/ms:arclist}

在列表页,获取全部文章中文章属性为推荐的前 4 篇文章 默认按时间排序

...
<div class="tit">猜您喜欢</div>
  {ms:arclist size=4 flag='c' typeid='0'}
    <a href='{ms:global.html/}${field.link}' target="_self">${field.title}</a>
  {/ms:arclist}
</div>
...

图文组件使用示例

基于图片进行描述,达到类似下方的效果

Alt text

使用代码生成器的图文组件

Alt text

导入模型

Alt text


xxx 为使用代码生成器托的图文组件的字段名称;如上图,使用时将xxx改为PICDETAIL_YRLOP 

模型表名 通过自定义模型导入后可以看到,如上图为 MDIY_MODEL_TUWEN

<!-- 一张图文标签写法 -->
{ms:arclist typeid="1666653707301101568" tableName="模型表名" size=4}

    //  图片路径  
    {@ms:file field.xxx /} <br>

    // 图片描述
    {@ms:file_desc field.xxx /} <br>
    
{/ms:arclist}

<!-- 多张图文标签写法 -->
{ms:arclist typeid="1666653707301101568" tableName="模型表名" size=4}

     获取所有图文
    <#if field.xxx && field.xxx!=''>
        <#list field.xxx?eval as img>
            ${img.url} //图片路径
            ${img.desc} //图片描述
        </#list>
        获取当前文章缩略图数量,必须在判断里使用
        ${field.xxx?eval?size}
    </#if>


    获取指定位置的图文
    <#if field.xxx && field.xxx!=''>
        ${field.xxx?eval[2].url} //图片路径
        ${field.xxx?eval[2].desc} //图片描述
    </#if>

{/ms:arclist}

列表第一条凸显显示

{ms:arclist size=10  typeid='62'}
    <!--第一条图片方式显示-->
    <#if field.index == 1>
    <img src="{@ms:file field.litpic/}"/>
    <#else>
    <a href='{ms:global.html/}${field.link}' target="_self">${field.title}</a>
    </#if>
{/ms:arclist}

topflag 使用范例

Tip

topflag 支持自定义“置顶”属性,任意设置topflag的值,即可将对应属性的文章进行置顶,并且不影响分页;若没有对应属性的文章,则在全部文章中按orderby顺序查询

类似效果如微博热搜

{ms:arclist size=10 typeid='62' topflag='h' orderby='date'}
    <img src="{@ms:file field.litpic/}" />
    <a href="{ms:global.html/}${field.link}">${field.title}</a>
{/ms:arclist}

属性为 h 的文章会固定展示在列表顶部,多篇该属性文章之间会按照时间排序,其余文章在’置顶文章’之后按时间排序

{ms:arclist size=10 ispaging=true typeid='62' topflag='h,c' orderby='date'}
    <img src="{@ms:file field.litpic/}" />
    <a href="{ms:global.html/}${field.link}">${field.title}</a>
{/ms:arclist}

多置顶属性设置,优先置顶属性为 h 的文章(同属性按时间排序),再排属性为 c 的文章,其余文章在’置顶文章’之后按时间排序

带自定模型基础使用格式

{ms:arclist typeid='70' tableName="自定义模型表名"}
    ${field.自定义模型表字段名称}
{/ms:arclist}

获取当前文章的文章标签

{ms:arclist typeid='70'}
    <#list field.tags?split(",") as cur_content_tag>
        <a href="/mcms/search?content_tag=${cur_content_tag}">${cur_content_tag} </a>
    </#list>
{/ms:arclist}

多张缩略图获取方式

{ms:arclist typeid='70'}

    获取第一张缩略图
    <img src="{@ms:file field.litpic/}"/>
    <a href='{ms:global.html/}${field.link}' target="_self">${field.title}</a>

    获取所有缩略图
    <#if field.litpic && field.litpic!=''>
        <#list field.litpic?eval as img>
        ${img.url}
        </#list>
        获取当前文章缩略图数量,必须在判断里使用
        ${field.litpic?eval?size}
    </#if>
    获取除指定位置外的所有缩略图
    <#if field.litpic && field.litpic!=''>
        <#list field.litpic?eval as img>
        <#if img?index!=0> 获取除第一张缩略图外的所有缩略图
          ${img.url}
        </#if>
        </#list>
    </#if>

    获取指定位置的缩略图
    <#if field.litpic && field.litpic!=''>
        ${field.litpic?eval[2].url}
    </#if>

    判断指定的缩略图是否存在,不存在就不生成这个img标签
    <#if field.litpic && field.litpic!='' && field.litpic?eval[5]?has_content>
        <img  title="" alt="" src="${field.litpic?eval[5].url}" />
    </#if>


{/ms:arclist}

Tip

多图情况下{@ms:file field.litpic/}默认只会取出第一张图片,取指定位置的缩略图需要指定准确的数组下标 ieval[i]; 在范例中缩略图的判空是必要的,否则模板解析会失败,类似于 java 的 NullPointerException 注意5.4.

显示默认图片

<!--如果没有上传缩略图就显示一张默认图片-->
{ms:arclist}
    <#if field.litpic>
     <img src="{@ms:file field.litpic/}"/>
    <#else>
     <img src="默认图片"/>
    </#if>
{/ms:arclist}

Tip

部分老版本的旧数据使用 field.litpic!=“[]” 去做缩略图判空。 新版本无旧数据则无需使用此方法。

首页大幻灯

背景图幻灯可以通过创建一个 幻灯栏目 ,再创建对应的文章,文章只上传缩略图,如果还有其他文章信息,可以灵活使用其他字段填充,通过对应标签获取

<!-- 通过栏目管理幻灯图片 -->
{ms:arclist typeid="1666653706550321162"}
<li>
    <a href=javascript:;">
        <img src="{@ms:file field.litpic/}" width="100%" alt="MCMS"/>
    </a>
</li>
{/ms:arclist}
<!-- 通过文章属性管理幻灯图片 -->
{ms:arclist flag='f'}
<li>
    <a href=javascript:;">
        <img src="{@ms:file field.litpic/}" width="100%" alt="MCMS"/>
    </a>
</li>
{/ms:arclist}

Tip

注意:如果通过文章属性管理幻灯,需要在文章管理中设置属性为幻灯,否则无法获取到幻灯图片。如果还需要控制幻灯数量,需要设置size属性。 这里仅展示如何取出图片,具体实现幻灯根据业务需求自行实现。

列表页获取文章详情

<!-- 如果在首页获取需要在arclist标签中指定typeid -->
{ms:arclist} {ms:data dataid=field.id} ${field.title} 
    ${field.content} 
{/ms:data} {/ms:arclist}

列表页获取文章纯文本详情(5.3.5 及以上版本)

{ms:arclist} 
    {ms:data dataid=field.id}
    <div>全部内容纯文本: ${MUtil.html2text(field.content)}</div>
        <div>
          <#assign _fieldContent = MUtil.html2text(field.content)> 截取部分内容纯文本:
          {@ms:len _fieldContent 50 /}
    </div>
    {/ms:data} 
{/ms:arclist}

size 属性特殊用法

注意:此用法不能与 ispaging 同时使用

<!-- size="起始数,获取总数",如size="1,2",就是一共获取两条数据,排除第一条,取得后面所有的数据 -->
{ms:arclist size="1,2" typeid=72}
<li class="anim anim-1">
    <div class="product_b">
        <#if field.outlink!="">
        <a href="${field.outlink}" title="${field.title}" target="_blank">
        <#else>
        <a href="{ms:global.html/}${field.link}" title="${field.title}">
        </#if>
        <div class="imgs"><img src="{@ms:file field.litpic/}" alt="${field.title}">
            <p>&nbsp;</p>
        </div>
        <h2 class="f24">${field.title}</h2>
        <p>{@ms:len field.descrip 50 /}</p>
    </a></div>
</li>
{/ms:arclist}

orderby多字段使用样例(5.4.2及以上版本)

<!--orderby指定多个排序字段,只支持一种排序,优先级按orderby的顺序来-->
<!--如下 优先按sort倒序排,sort相同按date倒序排-->
{ms:arclist orderby='sort,date' order='desc'}
    ${field.title}
{/ms:arclist}

跨站点获取指定栏目下的文章数据(需安装站群插件)

使用场景: 在站群环境下,当需要在一个站点中展示其他站点的文章内容时,可以通过配置 appId 和 typeid 参数实现跨站点数据获取。如:

<!--如下 appId为目标站点id,typeid为目标栏目id-->
{ms:arclist typeid='1993150201510846465'  appId='1993149810245197826'}
    ${field.title}
{/ms:arclist}

分页 {ms:page.*/}

配合 ms:arclist 标签完成分页功能

适用模版

列表模版

Tip

使用分页标签时,只能在列表模版中单独使用 ms:arclist 不能被其他标签嵌套, 且必须使用参数 ispaging=true,一个列表页仅支持使用一次,通过使用参数 size=6可以指定一页展示文章数为6 ,自定义页面`不能使用分页标签

注意:在主页模板选择分页效果模板,进行生成主页操作是不能看到分页效果的;分页效果必须生成栏目,且栏目的列表模板绑定的是带有分页效果的模板,才能看到分页效果

格式

{ms:page.*}

标签

字段名称描述
{ms:page.index/}首页链接
{ms:page.pre/}上一页
{ms:page.next/}下一页
{ms:page.last/}末页
{ms:page.cur/}当前页码
{ms:page.total/}总页数
{ms:page.rcount/}文章总数

Tip

下一页上一页的顺序是根据文章在数据库的id主键顺序来定的,不是由自定义顺序来定的

范例

基本用法

<ul>
<li><a href="{ms:global.html/}{ms:page.index/}">首页</a></li>
<li><a href="{ms:global.html/}{ms:page.pre/}">上一页</a></li>
<li><a href="{ms:global.html/}{ms:page.next/}">下一页</a></li>
<li><a href="{ms:global.html/}{ms:page.last/}">末页</a></li>
<li>当前页/总页数<span>{ms:page.cur/}/{ms:page.total/}</span></li>
<li>共有:<span>{ms:page.rcount/}</span>篇文章</li>
</ul>

Tip

文章没有上一篇或下一篇时点击404可以使用逻辑标签判断

<#if pre.title?has_content>
  <a href="{ms:global.html/}{ms:page.pre/}">上一页</a>
<#else>
  没有上一页了
</#if>

基于element-ui用法

下面是default\news-list.htm默认模版里面的代码片段,分页显示效果 点击查看

<!--引入库-->
<script type="text/javascript" src="/static/plugins/vue/2.6.9/vue.min.js"></script>
<script src="/static/plugins/element-ui/2.15.7/index.min.js"></script>
<link rel="stylesheet" href="/static/plugins/element-ui/2.15.7/theme-chalk/index.min.css">

<div id="app">
    ...
    <el-pagination
        background
    @current-change="handleCurrentChange"
    :page-size="pageSize"
    :current-page.sync="pageCur"
     layout="prev, pager, next, jumper"
    :total="contentCount">
    </el-pagination>
    ...
</div>

...
<script>
var app = new Vue({
    el: '#app',
    data: {
     //当前页数
     pageCur: ${(page.cur)!1},
     //每页文章条数
     pageSize: ${(page.size)!20},
     //页数总数
     pageTotal: ${(page.total)!0},
     //内容总数
     contentCount: ${(page.rcount)!0}
    },
    methods: {   
     handleCurrentChange:function(val) {
        // 没有使用短链接的方式分页
         if (val == 1) { //首页
             location.href = "{ms:global.html/}${field.categoryPath}/index.html";
         } else { //其他页面 list-页码.html
             location.href = "{ms:global.html/}${field.categoryPath}/list-" + val + ".html";
        }

        //使用了短链接的分页方式,在政务版开启短链或使用短链插件时用此方法分页
        // if (val == 1) { //首页
        //     location.href = "{ms:global.html/}${field.categoryPinyin}.html";
        // } else { //其他页面 list-页码.html
        //     location.href = "{ms:global.html/}${field.categoryPinyin}-" + val + ".html";
        // }
     }
    }
})
</script>
...

Tip

也可以通过jquery的插件来实现分页效果,标签${field.categoryPath}的含义是获取栏目拼音

基于jQuery分页用法

https://www.jq22.com/jquery-info15113

...
$("#pagination3").pagination({
   currentPage: ${(page.cur)!1},// 当前页数
   totalPage: ${(page.total)!0},// 总页数
   isShow: true,// 是否显示首尾页
   count: ${(page.size)!20},// 显示个数
   homePageText: "首页",// 首页文本
   endPageText: "尾页",// 尾页文本
   prevPageText: "上一页",// 上一页文本
   nextPageText: "下一页",// 下一页文本
   callback: function(current) {
       // 回调,current(当前页数)
    if(current==1) { //首页
       location.href = "{ms:global.html/}${field.categoryPath}/index.html";
    } else { //其他页面 list-页码.html
       location.href = "{ms:global.html/}${field.categoryPath}/list-" + current + ".html";
    }

   }
});
...

页面中显示多个分页 或 页面中文章列表来源多个栏目并且需要分页

只能通过异步请求文章接口实现,不能使用标签

内容 ${field.*}

获取内容的基本信息

适用模版

内容模版

Tip

只能单独使用在具体内容模版

格式

${field.*}

标签

Tip

在栏目绑定的内容模板,如开源的news-detail模板;${field.文章相关字段}即可输出当前文章对应数据;不需要被{ms:arclist}或{ms:data}标签包裹

字段名称描述
${field.title}内容标题
${field.shorttitle}内容副标题
${field.id}文章id
${field.hit}文章点击数,不约束ip限制,在内容页使用点击会自动累加,注意:必须在内容页模版中使用此标签,文章预览量才会增加,否则文章预览量为不会生效,默认0,标签会生成对应的前端接口请求代码,每次刷新页面都会调用接口。
${field.date?string(“”)}根据用户指定的格式输出时间默认${field.date?string(“yyyy-MM-dd”)}
${field.author}文章发布作者
${field.keyword}文章关键字
${field.source}文章发布来源
${field.content}文章内容,截取1-30个内容长度{@ms:len field.content 30/},注意:由于内容是编辑器编辑过后的html内容,所以截取时候会存在html截取不完整,导致页面html出问题,如果只需要显示简短的内容描述,推荐使用描述或关键标签。注意!content标签不能使用在列表页和主页,只能在具体的内容页使用,
${pre.link}上一篇文章链接。根据排序字段显示顺序(如果上级没有栏目那么默认取当前栏目 一般与上一篇文章标题一起用)范例:上一篇: <a href=“{ms:global.html/}${pre.link}”>${pre.title}</a>上一篇作者:${pre.author}只需要将内容标签(field)替换为上一篇标签(pre)就可以取到对应的内容
${next.link}显示下一篇文章链接。范例:下一篇: <a href=“{ms:global.html/}${next.link}”>${next.title}</a>下一篇作者:${next.author}只需要将内容标签(field)替换为下一篇标签(next)就可以取到对应的内容
${field.link}当前文章内容链接:{ms:global.html/}${field.link}
${field.descrip}文章摘要
${field.litpic}文章缩略图 范例:<img src=“{@ms:file field.litpic/}”/>
${field.tags}文章标签,多个以“,“隔开
${field.typeid}当前所属栏目id,可在文章页取当前栏目id,也能在栏目列表页取当前栏目id
${field.type*}可用标签参考 **ms:channel ** 标签的 ${field.type*}
${field.*}自定义模型标签,当前文章属于绑定了模型的栏目,就可以使用该标签,使用参考文章列表
${field.typepath}获取当前栏目路径

Important

注意:单篇文章内容没有上下篇,列表文章内容的上下篇范围是当前文章所属栏目下的所有文章,不跨栏目

Tip

${filed.*}自定义模型字段使用与 ms:arclist 里面一致;
自定义模型字段不能与现有字段名称相同,例如:基本字段里面已经有了${filed.title},自定义模型字段就不能再有 title 的字段

范例

基本用法

<h3>${field.title}</h3>
<p>时间:${field.date?string("yyyy-MM-dd")}作者:${field.author}</p>
<p>文章内容:${field.content}</p>
<p>上一篇:<a href="{ms:global.html/}${pre.link}">${pre.title}</a></p>
<p>下一篇:<a href="{ms:global.html/}${next.link}">${next.title}</a></p>

Tip

上一篇、下一篇的排序显示是根据文章的实际发布时间排序,列表的排序规则不能决定上一篇、下一篇的显示顺序。因为如果多个列表使用不同的排序规则,而内容页面是静态化之后的文件,所以没办法确定使用那种排序显示。

将富文本转为纯文本

<div>
  富文本文章内容: ${field.content}
</div>
<div>
  纯文本文章内容: ${MUtil.html2text(field.content)}
</div>

专题页面

专题页面可以理解为在其他栏目中挑选一类的文章,聚合显示在内容页面上,例如可以新增内容属性奥运会,再通过文章列表标签的flag属性进行获取,再通过自定义页面的功能绑定个性化的专题模版,通过文章内容来源字段绑定自定义页面路径。这样就实现个性化的专题页面。

<!-- 在文章内容页,获取所有文章中某类(内容属性)文章列表,指定好flag后,需要指定typeid=0才能在所有文章中查询,不指定就是当前栏目的-->
{ms:arclist flag="c" typeid=0}
  ${field.title} 
{/ms:arclist}

单篇内容 {ms:data}

获取指定的某一篇文章

适用模版

首页模版、列表模版、内容模版、自定义页面模版

格式

{ms:data dataid=文章编号}
    ${field.*}
{/ms:data}

参数

名称类型必须示列值默认值描述
dataid整型>0文章的编号
tableName字符串mdiy_model_mobile自定义表名,使用代码生成器导入的模型表名

Tip

如果要获取自定义模型的内容必须设置tableName

输出字段

字段名称描述
${field.id}文章id,对应文章在数据库里的自增长编号
${field.title}文章标题
${field.shorttitle}文章副标题
${field.author}文章作者
${field.source}文章来源
${field.content}文章内容,截取1-30个内容长度${field.content[0..30]},注意:由于内容是编辑器编辑过后的html内容,所以截取时候会存在html截取不完整,导致页面html出问题,如果只需要显示简短的内容描述,推荐使用描述或关键标签。注意!content标签不能使用在列表页和主页,只能在具体的详情页使用,
${field.typetitle}文章所属分类的名称
${field.typeshorttitle}文章所属分类的副标题
${field.typeid}文章所属分类的编号
${field.type*}可用标签参考 **ms:channel ** 标签的 ${field.type*} (部分用法在data标签里被禁止)
${field.typelink}文章分类链接,点击连接连接到当前分类的列表:{ms:global.html/}${field.typelink}
{@ms:file field.litpic/}文章缩略图,上传文章的缩略图,调用缩略图地址:{@ms:file field.litpic/}
${field.link}文章内容链接,点击显示文章具体的内容地址,一般配合文章标题使用:{ms:global.html/}${field.link}
${field.date?string(“”)}根据用户指定的格式输出时间,${field.date?string(“yyyy-MM-dd”)}
${field.descrip}文章摘要
${field.flag}文章flag属性
${field.keyword}文章关键字
tableNamemdiy_model_mobile typeid必须指定对应栏目才有效,使用代码生成器导入的模型表名
${field.*}自定义标签,使用代码生成器拖拽的字段名

Tip

tablename可以自定义模型决定,自定义模型创建流程:

先在 代码生成器 拖出模型并下载模型代码 -> 打开系统后台自定义管理,进行自定义模型导入 -> 打开栏目管理,进行栏目绑定自定义模型 -> 打开文章管理,进行发布内容;

取自定义模型字段数据时,字段名是数据库字段名,严格区分大小写;

范例

基础使用格式

{ms:data dataid="88888888888" tableName="mdiy_model_xxx" }
    <img src="{@ms:file field.litpic/}"/>
    <a href='{ms:global.html/}${field.link}' target="_self">${field.title}</a>
{/ms:data}

逻辑标签

因为 ms 标签本身是基于 freemarker 扩展,理论上 freemarker 的逻辑标签模版里面都可以使用,官方文档

判断

文档参考

<#if condition>
...
<#elseif condition2>
...
<#elseif condition3>
...
<#else>
...
</#if>

范例

ms:arclist 中使用 if 判断逻辑

{ms:arclist size=10 ispaging=true}
<a href="{ms:global.url/}${field.link}">
<!-- field 对象是arclist标签循环出的文章对象,field中的属性值参考文章列表中的属性 -->
<#if field.title == "国内java开源 cms系统">
    <span style="color: red">${field.title}</span>
<#elseif field.title == "国内java开源bbs系统">
    <span style="color: green">${field.title}</span>
<#else>
    <span >${field.title}</span>
</#if>
</a>
{/ms:arclist}

Tip

不能直接使用 > < >= <= 等运算字符,需要使用lt小于、lte小于等于、gt大于、gte大于等于

注意数值比较时,注意类型问题,避免出现字符串类型和数值比较导致问题的情况, <#if stringNumber?number gt 10> 通过?number转成数值类型

循环标签

文档参考

<#list list>
    list数组长度超过为0个
    <#items as item>
        ${item.*}
    </#items>
<#else>
    list数组长度为0
</#list>

范例

ms:arclist 中使用 list 循环读取 自定义模型多图片字段

{ms:arclist typeid=70 tableName="mdiy_model_w_w"}
<#if field.img1??>
    <#list field.img1?eval as img>
    ${img.path}
    </#list>
</#if>
{/ms:arclist}

其他

如果还有其他逻辑部分代码的问题,请在提交 ISSUE

搜索

搜索模版名称不指定默认为 search.htm 且只能在对应模版的根目录下,不能在模板目录的子目录下,对应的固定地址是/mcms/search.do

Tip

搜索模板是动态生成,不需要静态化即可看到效果

默认模版参考:

Tip

search.htm必须在当前模版的第一级目录,不能将search.htm 放到 images css 或者其他的子目录

搜索参数

参数必填
tmpl可选模版文件名,默认search.htm
content_title必填搜索标题,也可以搜索文章内容的其他字段例如:content_keyword、content_author、content_source、content_type、content_description、content_tag、content_details 还有content_datetime的范围
categoryIds可选可搜索的栏目范围,多个栏目id直接逗号隔开且必须逗号结尾,
例如:单个栏目搜索,1,
多个栏目搜索 1,2,3,4,
如需要搜索自定义模型 的表数据,只能设置一个栏目编号
content_type可选文章属性,多个以逗号分开,例如:c,f
style必填(政企)企业版本、政府版本为必填,对应具体的模版文件夹名称,可以根据不同的皮肤效果制作不同的搜索页面结果页

Tip

categoryIds 可以指定某一个父级栏目来搜索。如需要搜索自定义模型 的表数据,只能设置一个栏目编号。

范例

普通搜索

搜索表单

...
<form action="/mcms/search.do" method="post">
<input type="text" name="style" value="{ms:global.template/}">
<input type="text" name="content_title" placeholder="请输入关键字">
<input type="text" name="content_type" placeholder="文章属性,多个以,分隔">
<input type="text" name="categoryIds" placeholder="请输入栏目id,多个以,分隔">
<!-- 对文章发布时间进行范围搜索 -->
<input type="datetime-local" name="content_datetime_start">
<input type="datetime-local" name="content_datetime_end">
<!-- 可在此处指定不同搜索模板 -->
<input type="hidden" name="tmpl" value="search.htm" />
<input type="submit" value="搜索">
</form>
...

Tip

如果需要多个搜索页面,可以拷贝search.htm,并在搜索表单指定tmpl属性值

搜索结果页 search.htm

<!-- 在搜索模板,通过${search.搜索表单的name值},获取表单传递的值 -->
<div class="ms-content-main-div-prompt">您搜索的关键字
<span>${search.content_title}</span>
<span>${search.content_datetime_start?replace('T',' ')}
</div>
<!-- 搜索结果列表 -->
{ms:arclist size=10 ispaging=true orderby="hit" order="desc"}
<a href="{ms:global.html/}${field.link}">
${field.title}
</a>
{/ms:arclist}

<div class="ms-content-main-page">
<a href="{ms:page.index/}">首页</a>
<a href="{ms:page.pre/}">上一页</a>
<a href="{ms:page.next/}">下一页</a>
<a href="{ms:page.last/}">末页</a>
</div>

Tip

这里的分页不需要拼接 {ms:global.html/} ; 通过 ${search.content_title} 可以动态获取搜索的内容 ;

采用 element-ui 的分页 效果

<!--网络请求框架-->
<script src="/static/plugins/axios/0.18.0/axios.min.js"></script>
<script src="/static/plugins/qs/6.6.0/qs.min.js"></script>
<script src="/static/plugins/ms/1.0.0/ms.js"></script>
<script src="/static/plugins/ms/1.0.0/ms.http.js"></script>
<script src="/static/plugins/ms/1.0.0/ms.util.js"></script>

<!--引入库-->
<script type="text/javascript" src="/static/plugins/vue/2.6.9/vue.min.js"></script>
<script src="/static/plugins/element-ui/2.12.0/index.js"></script>
<link rel="stylesheet" href="/static/plugins/element-ui/2.12.0/index.css">

<div id="app">


<!-- 搜索结果列表 -->
<#assign isEmpty=true>

{ms:arclist size=10 ispaging=true}
<!--标记搜索记录不为空-->
<#assign isEmpty=false>
<a href="{ms:global.html/}${field.link}">
${field.title}
</a>
{/ms:arclist}
<!-- 搜索结果为空时候展示 -->
<#if isEmpty>
 没有找到 "${search.content_title}" 相关记录
</#if>



<el-pagination
background
@current-change="handleCurrentChange"
:page-size="pageSize"
:current-page.sync="pageCur"
layout="prev, pager, next, jumper"
:total="contentCount">
</el-pagination>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
//当前页数
pageCur: ${(page.cur)!1},
//每页文章条数
pageSize: ${(page.size)!20},
//页数总数
pageTotal: ${(page.total)!0},
//内容总数
contentCount: ${(page.rcount)!0},
keyword: "{ms:search.content_title/}",
categoryIds: "{ms:search.categoryIds/}"
},
methods: {
handleCurrentChange:function(val) {
var form = document.createElement("form");
form.setAttribute("method", "post");
var input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'size');
input.setAttribute('value', this.pageSize);
form.append(input);
input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'pageNo');
input.setAttribute('value', val);
form.append(input);
input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'content_title');
input.setAttribute('value', this.keyword);
form.append(input);
input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'categoryIds');
input.setAttribute('value', this.categoryIds);
form.append(input);
input = document.createElement('input');
                input.setAttribute('type', 'hidden');
                input.setAttribute('name', 'style');
                input.setAttribute('value', '{ms:global.template/}');
                form.append(input);

form.setAttribute("action","/mcms/search.do");
document.body.appendChild(form);
form.submit();
form.remove();
},
}
})
</script>

Tip

这里采用的是动态创建表单的方式搜索,具体参考默认模版的 search.htm 模版

栏目绑定自定义模型搜索

Tip

对自定义模型的字段进行搜索,必须传递栏目id且只能传递叶子栏目(没有子栏目)id

搜索表单

...
<form action="/mcms/search.do" method="post">
<!-- 企业版政务版必须参数字段 -->
<input type="text" name="style" value="{ms:global.template/}">

<input type="text" name="categoryIds" placeholder="请输入绑定文章模型的栏目id,只能指定一个">

<!-- 这里的name是自定义模型字段的field,在自定义模型->表单预览->字段信息中获取field,支持模糊搜索-->
<input type="text" name="TEST_USE" placeholder="自定义模型字段的值,栏目绑定模型后在文章表单页可进行设置">

<!-- 可在此处指定不同搜索模板 -->
<input type="hidden" name="tmpl" value="search.htm" />
<input type="submit" value="搜索">
</form>
...

搜索结果页

<!-- 在搜索模板,通过${search.搜索表单的name值},获取表单传递的值 -->
<div class="ms-content-main-div-prompt">您搜索的关键字
    <span>${search.TEST_USE}</span>
</div>
<!-- 搜索结果列表 -->
{ms:arclist size=10 ispaging=true}
    <a href="{ms:global.html/}${field.link}">
        <!-- 通过field.自定义模型字段的field,获取当前模型字段的值 -->
        ${field.title}--${field.TEST_USE}
    </a>
{/ms:arclist}

<div class="ms-content-main-page">
    <a href="{ms:page.index/}">首页</a>
    <a href="{ms:page.pre/}">上一页</a>
    <a href="{ms:page.next/}">下一页</a>
    <a href="{ms:page.last/}">末页</a>
</div>

部署

源码部署

检出源代码

git clone https://gitee.com/mingSoft/MCMS.git

导入到 Eclipse

菜单 File -> Import,然后选择 Maven -> Existing Maven Projects,点击 Next> 按钮,选择mcms文件夹,然后点击 Finish 按钮,即可成功导入。

导入到IDEA

点击 Import Project,选择pom.xml文件,点击 Next 按钮,选择Import Maven projects automatically复选框,然后一直点击 Next 按钮,直到点击 Finish 按钮,即可成功导入。

Tip

Eclipse(IDEA)会自动加载 Maven 依赖包,初次加载会比较慢(根据自身网络情况而定,目前使用国外Maven仓库地址),若工程上有小叉号,请打开 Problems 窗口,查看具体错误内容,直到无错误为止

导入数据库

mysql指令导入,创建数据库CREATE DATABASE IF NOT EXISTS mcms DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

导入数据库文件#source mcms/doc/mcms-版本号.sql即可完成数据库初始化工作 ,也可以借助工具快速导入。

修改/src/main/resources/application-dev.yml数据库设置参数。

启动项目

Tip

设置working directory(工作区间) 开源版本忽略此操作,只有单项目多模块下(如:开发版、企业版、政务版)启动时候必须设置 Working directory 目录(MSApplication.java所在的目录)

  • 在 Eclipse 里找到net.mingsoft.MSApplication.java文件并打开,然后在空白处右键,点击 Debug As -> Java Application 即可启动服务。

  • 在 IDEA,找到右上角 Application 运行配置,点击 Debug Application 图标。

一键运行版

下载最新的一键运行版: 下载地址

目录说明

下载后解压的文档结构如下:

linux:linux运行环境

win:windows运行环境

mcms:铭软MCms内容系统

start.bat:window启动脚本

start.sh:linux启动脚本

Tip

注意:一键运行版本的存放路径不能存在中文路径,例如:d:/铭软/,这样会导致启动失败

window

【注意】点击start.bat运行window版本(建议右键“以管理员身份运行”)页面会快速刷出服务启动提示,启动完成会自动关闭窗口。

运行成功!

linux

【注意】启动start.sh运行linux版本,页面会快速刷出服务启动提示。

运行成功!

Tip

需要使用自己本地的数据库,需要配置文件配置本地数据库,再使用java -jar命令启动即可。

访问方式

系统后台路径:http://localhost:8181/ms/login.do

前台演示模版路径:http://localhost:8181

登录账号:msopen 密码:msopen

【注意】localhost为本地地址,根据实际情况修改

生产部署

参考部署文档

Tip

如果部署后静态化后的页面地址不对,可以通过应用设置点击保存一下,再重新静态化一下所有页面。

常见问题

MStore问题

添加MStore

首先在pom中添加依赖,最新开源pom

版本参照开源
<!--store入口依赖(源码不开发),如果不需要MStore可以直接去掉依赖-->
<dependency>
    <groupId>net.mingsoft</groupId>
    <artifactId>store-client</artifactId>
    <version>从开源获取最新版本</version>
</dependency>

开发版本在basic模块 WEB-INF/manager/include/head-file.ftl中

高级版本在主模块 WEB-INF/manager/include/head-file.ftl中

<!-- 此部分是铭飞平台MStroe的客户端(MStore不在铭飞开源产品范围),如果不需要使用MStore,可以删除掉 -->
<script src="${base}/static/plugins/ms/3.0/ms-store.umd.js"></script>
<link rel="stylesheet"  type="text/css" href="${base}/static/plugins/ms/3.0/ms-store.css" />

app.use(MsStore);

Alt text

安装插件后看不到

以会员插件为例,安装后会员菜单在功能大全下,可以通过点击收藏,再刷新即可在主页面和左侧导航看到会员中心菜单

Alt text

插件冲突处理

自动静态化和基础审批插件

CmsParserUtil文件冲突,需手动合并CmsParserUtil类;以自动静态化的CmsParserUtil为主,将基础审批插件CmsParserUtil的todo部分(TODO: 2023/7/6 集成插件 默认就开启审核),都合并到自动静态化的CmsParserUtil中

一键发布和短链插件

  1. GeneraterAction合并说明

    以一键发布插件中的GeneraterAction为主,viewIndex方法使用短链插件GeneraterAction中的

  2. CmsParserUtil合并说明

    以短链插件的CmsParserUtil为主,增加一键发布插件CmsParserUtil中的generateArticle和generateColumn方法

短链插件和站群插件

不能直接复制src文件,有文件冲突

  1. IndexAction冲突,将短链插件net.mingsoft.basic.action.web包下的IndexAction todo的代码复制到站群的indexAction中 Alt text

短链插件与基础审批

  1. ContentEntity冲突 将短链插件的ContentEntity的getUrl代码拷贝至实体中 Alt text

哪里下载模板?MStore怎么进入?

插件安装后缺少配置或菜单打开404

  1. 缺少配置:一般出现在重复安装导致有脏数据;将之前重复安装产生的数据清除,再安装;
  2. 打开菜单404:首先按照MStore说明步骤来操作;一般pom是没有添加相应依赖,或未编译到,添加依赖、编译,重启即可;

搜索页面模版

search.htm为默认搜索模版,直接通过表单提交参数到mcms/search.do地址。 使用参考

新系统使用MStore模板生成报错

检查列表页是否存在内容标签

{ms:field.typetitle/} 
替换成
{ms:channel type='self'}
    <a href="${global.url}${field.typelink}">${field.typetitle}</a>
{/ms:channel}
{ms:include filename=head-file.htm/}
替换成
<#include "head-file.htm">
{ms:arclist size=3}
    [field.date fmt=yyyy/]
{/ms:arclist}
替换成
{ms:arclist size=3}
    ${field.date?string("yyyy")}
{/ms:arclist}

具体更多的情况可以参考 旧模板快速升级 的章节

MStore无法进入,点击图标没有反应

首先确保代码是最新的,使用master分支

Mstore安装插件点击安装没有反应

检查浏览器是否开启阻止弹出式窗口,关闭即可。 如火狐浏览器 Alt text

系统使用https链接后,Mstore无法进入,the content must be served over HTTPS

Tip

报错信息 was loaded over HTTPS, but requested an insecure frame ‘http://store.mingsoft.net/?client=http%3A%2F%2Fen.niright.com%2F/#/?client=en.niright.com//ms’. This request has been blocked; the content must be served over HTTPS.

Mstore默认是http的链接,在线上不建议仍使用Mstore下载插件,插件建议在本地安装调试好后,再更新到线上,因为安装插件一般都需要重新编译,需要重启线上系统;

模板可以直接在官网下载。

Chrome 浏览mstore无法登陆进入

Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击和用户追踪。

Chrome升级到80版本后,默认限制了cross-site携带cookie,导致cookie失效,报错如下

A cookie associated with a cross-site resource at http://XXX.XXX.XXX.XXXX/ was set without the `SameSite` attribute. 
It has been blocked, as Chrome now only delivers cookies with cross-site requests if they are set with `SameSite=None` and `Secure`. 

Chrome访问地址 chrome://flags/

搜索“SameSite“,修改配置项如图,注意:设置好必须重启浏览器

mce编辑器导入

源码下载后

Alt text Alt text Alt text 导入后重新编译程序 即可看到新的编辑器了 Alt text

Tip

富文本插件

5.4.0及以上版本使用vue3 ,直接复制开发版本的文章表单页 src/main/webapp/WEB-INF/manager/cms/content/form.ftl

5.3.6 版本使用 vue2 ,使用 form-vue2.ftl 重命名覆盖 src/main/webapp/WEB-INF/manager/cms/content/form.ftl

标签问题

现有字段不满足需求,给文章表等加字段,推荐使用自定义模型

不建议直接在数据库表里面加字段,建议使用自定义模型。拖拽生成,一键绑定,即可使用。

1.在代码生成器制作模型

2.在自定义模型处导入模型json

3.在栏目管理处绑定导入的模型

4.在文章管理处使用导入的模型

5.在模板中使用带自定义模型的标签,需先了解模板基本使用



6.根据示例代码中 计数开关 字段控制输出内容,开、关效果分别如下

扩展标签输出字段

Tip

如果单纯是增加一个显示的属性,推荐使用自定义模型,简单使用,无需后台开发

场景举例:扩展的字段有需要处理的业务逻辑,这里以ContentEntity新增一个contentExtend属性为例

  1. 新增属性产生的基本修改,实体、xml等

  2. 标签修改

如果该属性需要在列表和详情展示,那么对应需要修改arclist标签和field标签(data标签按需)

这里以arclist标签为例
...
SELECT
  cms_content.id AS "id",
  content_title AS "title",
  content_short_title AS "shorttitle",
  content_author AS "author",
  content_source AS "source",
  content_out_link AS "outlink",
  content_tags AS "tags",
  content_extend AS "extend"  # 新增扩展字段属性
...
  1. 列表模板写法
{ms:arclist}
${field.title}---${field.extend}
{/ms:arclist}
  1. 栏目CategoryEntity扩展,比文章需要多一步添加实体属性
channel标签
...
cms_category.category_img as "typelitpic" ,
cms_category.category_ico as "typeico" ,
cms_category.category_extend as "typeextend", # 新增扩展字段属性
...
// CategoryEntity
...
/**
 * 获取栏目新增扩展字段属性 (标签使用) 这里的get后的属性名是上方扩展字段属性as的别名
 */
public String getTypeextend() {
    return this.categoryExtend;
}

Tip

对于二次开发的业务数据展示,不推荐新增标签获取,开发成本较高,推荐走接口获取

自定义全局标签初始配置(开源5.4.3及以上支持)

  1. 在菜单管理导入自定义全局参数菜单json Alt text
[{"createBy":"","id":"1821","updateBy":"57","updateDate":"2024-11-26 09:25:22","modelTitle":"自定义全局参数","modelDatetime":"2024-11-26 09:24:30","modelId":84,"modelUrl":"mdiy/tag/globalTag/config.do?modelId=1860892324126367745&isEditor=true","modelCode":"","isChild":"","modelIcon":"","modelSort":0,"modelIsMenu":1,"chick":0,"depth":1,"modelParentIds":"84","modelChildList":[{"id":"1822","modelTitle":"新增","modelDatetime":"2024-11-26 09:24:30","modelId":1821,"modelUrl":"mdiy:tag:save","modelSort":0,"modelIsMenu":0,"chick":0,"depth":2,"modelParentIds":"84,1821"},{"id":"1823","modelTitle":"编辑","modelDatetime":"2024-11-26 09:24:30","modelId":1821,"modelUrl":"mdiy:tag:update","modelSort":0,"modelIsMenu":0,"chick":0,"depth":2,"modelParentIds":"84,1821"},{"id":"1824","modelTitle":"查看","modelDatetime":"2024-11-26 09:24:30","modelId":1821,"modelUrl":"mdiy:tag:view","modelSort":0,"modelIsMenu":0,"chick":0,"depth":2,"modelParentIds":"84,1821"},{"id":"1825","modelTitle":"配置","modelDatetime":"2024-11-26 09:24:30","modelId":1821,"modelUrl":"mdiy:tag:config","modelSort":0,"modelIsMenu":0,"chick":0,"depth":2,"modelParentIds":"84,1821"}]}]
  1. 在数据库执行sql
INSERT INTO `mdiy_config` (`id`, `MODEL_ID`, `config_name`, `config_data`, `CONFIG_TYPE`, `update_date`, `update_by`, `create_date`, `create_by`, `del`, `NOT_DEL`, `APP_ID`) VALUES (1, '1860892324126367745', 'foot', '{\"timeEnd\":\"18:00:00\",\"timeBegin\":\"09:00:00\",\"ico\":\"[{\\\"url\\\":\\\"/upload/1/global/1872457333638361088.png\\\",\\\"name\\\":\\\"1869634627528175616.png\\\",\\\"uid\\\":1735263591501,\\\"status\\\":\\\"success\\\"}]\",\"qrCode\":\"[{\\\"url\\\":\\\"/upload/1/global/1872457360049893376.jpg\\\",\\\"name\\\":\\\"1869634658977067008.jpg\\\",\\\"uid\\\":1735263597758,\\\"status\\\":\\\"success\\\"}]\",\"modelId\":\"1860892324126367745\",\"tel\":\"19999999999\"}', 'tag', NULL, NULL, NULL, NULL, 0, 0, 'global');


INSERT INTO `mdiy_model` (`ID`, `model_json`, `model_table_name`, `model_name`, `model_id_type`, `model_type`, `model_field`, `model_custom_type`, `update_date`, `update_by`, `create_date`, `create_by`, `del`, `NOT_DEL`, `APP_ID`) VALUES (1860892324126367745, '{\"searchJson\":\"[\\n    \\n {},\\n]\\n\",\"isWebCode\":false,\"form\":\"[{\\\"type\\\":\\\"input\\\",\\\"name\\\":\\\"电话号码\\\",\\\"icon\\\":\\\"icon-danhangwenben\\\",\\\"pmofAllowUpdate\\\":1,\\\"pmofAllowGenerater\\\":1,\\\"pmofAllowRepet\\\":1,\\\"pmofAllowValidator\\\":1,\\\"pmofAllowEmpty\\\":0,\\\"pmofLength\\\":255,\\\"pmofType\\\":\\\"VARCHAR\\\",\\\"pmofJavaType\\\":\\\"String\\\",\\\"pmofSort\\\":0,\\\"likeSearch\\\":true,\\\"help\\\":\\\"标签:{ms:global.foot.tel/}\\\",\\\"options\\\":{\\\"isShow\\\":true,\\\"isFk\\\":false,\\\"readonly\\\":false,\\\"width\\\":\\\"100%\\\",\\\"repeat\\\":false,\\\"defaultValue\\\":\\\"\\\",\\\"clearable\\\":true,\\\"isLength\\\":true,\\\"lengthConfig\\\":{\\\"min\\\":0,\\\"max\\\":255},\\\"required\\\":false,\\\"dataType\\\":\\\"string\\\",\\\"isPattern\\\":false,\\\"isDataType\\\":false,\\\"pattern\\\":\\\"\\\",\\\"placeholder\\\":\\\"请输入电话号码\\\",\\\"disabled\\\":false,\\\"table\\\":{\\\"search\\\":false,\\\"show\\\":true,\\\"align\\\":\\\"left\\\"}},\\\"key\\\":\\\"INPUT_OFDLN\\\",\\\"model\\\":\\\"TEL\\\",\\\"rules\\\":\\\"[{\\\\\\\"min\\\\\\\":0,\\\\\\\"max\\\\\\\":255,\\\\\\\"message\\\\\\\":\\\\\\\"电话号码长度必须为0-255\\\\\\\"}]\\\"},{\\\"type\\\":\\\"time\\\",\\\"name\\\":\\\"底部服务时间\\\",\\\"icon\\\":\\\"icon-shijian-xianxing\\\",\\\"pmofAllowUpdate\\\":1,\\\"pmofAllowGenerater\\\":1,\\\"pmofAllowRepet\\\":1,\\\"pmofAllowValidator\\\":1,\\\"pmofAllowEmpty\\\":0,\\\"pmofType\\\":\\\"TIME\\\",\\\"pmofJavaType\\\":\\\"Date\\\",\\\"pmofSort\\\":0,\\\"pmofLength\\\":0,\\\"likeSearch\\\":false,\\\"help\\\":\\\"标签:{ms:global.foot.timeBegin/} -\\\\n{ms:global.foot.timeEnd/} \\\\n \\\",\\\"options\\\":{\\\"isShow\\\":true,\\\"defaultValue\\\":null,\\\"readonly\\\":false,\\\"disabled\\\":false,\\\"editable\\\":true,\\\"clearable\\\":true,\\\"placeholder\\\":\\\"请选择底部服务时间\\\",\\\"startPlaceholder\\\":\\\"开始时间\\\",\\\"endPlaceholder\\\":\\\"结束时间\\\",\\\"isRange\\\":true,\\\"arrowControl\\\":true,\\\"format\\\":\\\"HH:mm:ss\\\",\\\"required\\\":false,\\\"width\\\":\\\"100%\\\",\\\"table\\\":{\\\"search\\\":false,\\\"show\\\":true,\\\"width\\\":120,\\\"align\\\":\\\"center\\\"},\\\"label\\\":\\\"开始时间\\\",\\\"attachField\\\":[{\\\"type\\\":\\\"time\\\",\\\"name\\\":\\\"结束时间\\\",\\\"model\\\":\\\"TIME_END\\\",\\\"key\\\":\\\"1733127110000_98951\\\",\\\"pmofAllowUpdate\\\":1,\\\"pmofAllowGenerater\\\":1,\\\"pmofAllowRepet\\\":1,\\\"pmofAllowValidator\\\":1,\\\"pmofAllowEmpty\\\":1,\\\"pmofType\\\":\\\"TIME\\\",\\\"pmofJavaType\\\":\\\"Date\\\",\\\"pmofSort\\\":0,\\\"options\\\":{\\\"isShow\\\":true,\\\"defaultValue\\\":[],\\\"readonly\\\":false,\\\"disabled\\\":false,\\\"editable\\\":true,\\\"clearable\\\":true,\\\"placeholder\\\":\\\"请选择时间选择器\\\",\\\"startPlaceholder\\\":\\\"开始时间\\\",\\\"endPlaceholder\\\":\\\"结束时间\\\",\\\"isRange\\\":true,\\\"arrowControl\\\":true,\\\"format\\\":\\\"HH:mm:ss\\\",\\\"required\\\":false,\\\"width\\\":\\\"100%\\\",\\\"table\\\":{\\\"search\\\":false,\\\"show\\\":false,\\\"width\\\":120,\\\"align\\\":\\\"center\\\"}}}]},\\\"key\\\":\\\"TIME_NYRBY\\\",\\\"model\\\":\\\"TIME_BEGIN\\\",\\\"rules\\\":\\\"[]\\\"},{\\\"type\\\":\\\"imgupload\\\",\\\"name\\\":\\\"网站ico\\\",\\\"icon\\\":\\\"icon-tupian2\\\",\\\"pmofAllowUpdate\\\":1,\\\"pmofAllowGenerater\\\":1,\\\"pmofAllowRepet\\\":1,\\\"pmofAllowValidator\\\":1,\\\"pmofAllowEmpty\\\":0,\\\"pmofLength\\\":1000,\\\"pmofType\\\":\\\"VARCHAR\\\",\\\"pmofJavaType\\\":\\\"String\\\",\\\"pmofSort\\\":0,\\\"likeSearch\\\":false,\\\"help\\\":\\\"标签:{@ms:file global.foot.ico/}\\\",\\\"options\\\":{\\\"isShow\\\":true,\\\"defaultValue\\\":[],\\\"required\\\":false,\\\"width\\\":\\\"\\\",\\\"tokenFunc\\\":\\\"funcGetToken\\\",\\\"token\\\":\\\"\\\",\\\"domain\\\":\\\"http://pfp81ptt6.bkt.clouddn.com/\\\",\\\"disabled\\\":false,\\\"limit\\\":1,\\\"multiple\\\":false,\\\"isQiniu\\\":false,\\\"action\\\":\\\"ms.manager+\'/file/upload.do\'\\\",\\\"table\\\":{\\\"show\\\":true,\\\"align\\\":\\\"left\\\"}},\\\"key\\\":\\\"IMGUPLOAD_KKLFY\\\",\\\"model\\\":\\\"ICO\\\",\\\"rules\\\":\\\"[]\\\"},{\\\"type\\\":\\\"imgupload\\\",\\\"name\\\":\\\"底部微信二维码\\\",\\\"icon\\\":\\\"icon-tupian2\\\",\\\"pmofAllowUpdate\\\":1,\\\"pmofAllowGenerater\\\":1,\\\"pmofAllowRepet\\\":1,\\\"pmofAllowValidator\\\":1,\\\"pmofAllowEmpty\\\":0,\\\"pmofLength\\\":1000,\\\"pmofType\\\":\\\"VARCHAR\\\",\\\"pmofJavaType\\\":\\\"String\\\",\\\"pmofSort\\\":0,\\\"likeSearch\\\":false,\\\"help\\\":\\\"标签:{ms:global.contextpath/}{@ms:file global.foot.qrCode/}\\\",\\\"options\\\":{\\\"isShow\\\":true,\\\"defaultValue\\\":[],\\\"required\\\":false,\\\"width\\\":\\\"\\\",\\\"tokenFunc\\\":\\\"funcGetToken\\\",\\\"token\\\":\\\"\\\",\\\"domain\\\":\\\"http://pfp81ptt6.bkt.clouddn.com/\\\",\\\"disabled\\\":false,\\\"limit\\\":1,\\\"multiple\\\":false,\\\"isQiniu\\\":false,\\\"action\\\":\\\"ms.manager+\'/file/upload.do\'\\\",\\\"table\\\":{\\\"show\\\":true,\\\"align\\\":\\\"left\\\"}},\\\"key\\\":\\\"IMGUPLOAD_MAOHJ\\\",\\\"model\\\":\\\"QR_CODE\\\",\\\"rules\\\":\\\"[]\\\"}]\",\"isWebSubmit\":false,\"html\":\"\\n<template id=\\\"custom-model\\\">\\n    <el-form ref=\\\"form\\\" :model=\\\"form\\\" :rules=\\\"rules\\\" label-width=\\\"120px\\\" label-position=\\\"right\\\" size=\\\"default\\\" :disabled=\\\"disabled\\\" v-loading=\\\"loading\\\">\\n            <!--电话号码-->\\n\\n\\t        <el-form-item  label=\\\"电话号码\\\" prop=\\\"tel\\\">\\n\\t            <el-input\\n                        v-model=\\\"form.tel\\\"\\n                         :disabled=\\\"false\\\"\\n                          :readonly=\\\"false\\\"\\n                          :style=\\\"{width:  \'100%\'}\\\"\\n                          :clearable=\\\"true\\\"\\n                        placeholder=\\\"请输入电话号码\\\">\\n                </el-input>\\n                <div class=\\\"ms-form-tip\\\">\\n标签:{ms:global.foot.tel/}                </div>\\n\\t        </el-form-item>   \\n        <!--底部服务时间-->\\n    \\n        <el-form-item  label=\\\"底部服务时间\\\" prop=\\\"timeBegin\\\">\\n             <el-time-picker\\n                  \\n                    v-model=\\\"form.timeBegin\\\"\\n                   \\n                    :is-range=\\\"true\\\"\\n               \\n                    :readonly=\\\"false\\\"\\n                    :disabled=\\\"false\\\"\\n\\t\\t\\t\\t\\t\\t\\t\\t\\t\\t:editable=\\\"true\\\" :clearable=\\\"true\\\"\\n                             value-format=\\\"HH:mm:ss\\\"\\n                    \\n                    :arrow-control=\\\"true\\\" :style=\\\"{width:\'100%\'}\\\">\\n            </el-time-picker>\\n                <div class=\\\"ms-form-tip\\\">\\n标签:{ms:global.foot.timeBegin/} -\\n{ms:global.foot.timeEnd/}                </div>\\n        </el-form-item>\\n   \\n        <!--网站ico-->\\n    \\n        <el-form-item  label=\\\"网站ico\\\" prop=\\\"ico\\\">\\n             <el-upload\\n                    :file-list=\\\"form.ico\\\"\\n                    :action=\\\"ms.manager+\'/file/upload.do\'\\\"\\n                    :limit=\\\"1\\\"\\n                    multiple\\n                    :disabled=\\\"false\\\"\\n                    :data=\\\"{uploadPath:\'/global/\',\'isRename\':true,\'appId\':true}\\\"\\n                    :on-remove=\\\"icoHandleRemove\\\"\\n                    :on-exceed=\\\"icoHandleExceed\\\"\\n                    :on-preview=\\\"icoHandlePreview\\\"\\n                    :on-success=\\\"icoSuccess\\\"\\n                    :on-error=\\\"icoError\\\"\\n                    accept=\\\"image/*\\\"\\n                    list-type=\\\"picture-card\\\">\\n                <i class=\\\"el-icon-plus\\\"></i>\\n                <template #tip>\\n                  <div class=\\\"el-upload__tip\\\">最多上传1张图片</div>\\n               </template>\\n            </el-upload>\\n                <div class=\\\"ms-form-tip\\\">\\n标签:{@ms:file global.foot.ico/}                </div>\\n        </el-form-item>\\n   \\n        <!--底部微信二维码-->\\n    \\n        <el-form-item  label=\\\"底部微信二维码\\\" prop=\\\"qrCode\\\">\\n             <el-upload\\n                    :file-list=\\\"form.qrCode\\\"\\n                    :action=\\\"ms.manager+\'/file/upload.do\'\\\"\\n                    :limit=\\\"1\\\"\\n                    multiple\\n                    :disabled=\\\"false\\\"\\n                    :data=\\\"{uploadPath:\'/global/\',\'isRename\':true,\'appId\':true}\\\"\\n                    :on-remove=\\\"qrCodeHandleRemove\\\"\\n                    :on-exceed=\\\"qrCodeHandleExceed\\\"\\n                    :on-preview=\\\"qrCodeHandlePreview\\\"\\n                    :on-success=\\\"qrCodeSuccess\\\"\\n                    :on-error=\\\"qrCodeError\\\"\\n                    accept=\\\"image/*\\\"\\n                    list-type=\\\"picture-card\\\">\\n                <i class=\\\"el-icon-plus\\\"></i>\\n                <template #tip>\\n                  <div class=\\\"el-upload__tip\\\">最多上传1张图片</div>\\n               </template>\\n            </el-upload>\\n                <div class=\\\"ms-form-tip\\\">\\n标签:{ms:global.contextpath/}{@ms:file global.foot.qrCode/}                </div>\\n        </el-form-item>\\n   \\n    </el-form>\\n</template>\\n\",\"id\":1,\"script\":\"var custom_model = Vue.component(\\\"custom-model\\\",{\\n    el: \'#custom-model\',\\n    data:function() {\\n        return {\\n\\t\\t\\tloading:false,\\n            disabled:false,\\n            modelId:0,\\n            modelName: \\\"foot\\\",\\n            //表单数据\\n            form: {\\n                linkId:0,\\n                    // 电话号码\\n                    tel:\'\',\\n                    //底部服务时间\\n          timeBegin:null,\\n                    // 网站ico\\n                    ico: [],\\n                    // 底部微信二维码\\n                    qrCode: [],\\n            },\\n\\n            rules:{\\n                        // 电话号码\\n                        tel: [{\\\"min\\\":0,\\\"max\\\":255,\\\"message\\\":\\\"电话号码长度必须为0-255\\\"}],\\n            },\\n        }\\n    },\\n    watch:{\\n    },\\n    components:{\\n    },\\n    computed:{\\n    },\\n    methods: {\\n      \\tlink:function(e,field,binds){\\n      \\t\\tlet that = this;\\n            binds.forEach(function(item){\\n  \\t\\t\\t\\tms.http.post(ms.manager+\'/project/form/link.do\', {id:that.modelId,field:item.field,value:e}).then(function (res) {\\n                    if(res.result && res.data) {\\n                        that.form[ms.util.camelCaseString(item.field)]=res.data[0][item.target];\\n                    }else{\\n                        that.$notify({\\n                            title: \'失败\',\\n                            message: res.msg,\\n                            type: \'warning\'\\n                        });\\n                    }\\n                })\\n\\n            });\\n      \\t},\\n           //底部服务时间时间格式化\\n           timeBeginFormat:function(row, column, cellValue, index){\\n\\n                return row.TIME_BEGIN+\\\" - \\\"+row.TIME_END;\\n            },\\n        // ico删除\\n        icoHandleRemove: function (file, files) {\\n        \\tvar index = -1;\\n        \\tindex = this.form.ico.findIndex(function (text) {\\n        \\t\\treturn text.uid == file.uid;\\n        \\t});\\n        \\tif (index != -1) {\\n        \\t\\tthis.form.ico.splice(index, 1);\\n        \\t}\\n        },\\n        //ico上传超过限制\\n        icoHandleExceed: function (files, fileList) {\\n        \\tthis.$notify({\\n        \\t\\ttitle: \'失败\',\\n        \\t\\tmessage: \'当前最多上传1个文件\',\\n        \\t\\ttype: \'warning\'\\n        \\t});\\n        },\\n        //ico预览\\n        icoHandlePreview: function (file){\\n\\t\\t\\t\\twindow.open(file.url);\\n        },\\n        //ico上传成功\\n        icoSuccess: function (response, file, fileList) {\\n        \\tif (response.result) {\\n        \\t\\tthis.form.ico.push({\\n        \\t\\t\\turl: response.data,\\n        \\t\\t\\tname: file.name,\\n        \\t\\t\\tuid: file.uid\\n        \\t\\t});\\n        \\t} else {\\n        \\t\\tthis.$notify({\\n        \\t\\t\\ttitle: \'失败\',\\n        \\t\\t\\tmessage: response.msg,\\n        \\t\\t\\ttype: \'warning\'\\n        \\t\\t});\\n        \\t}\\n        },\\n        //ico上传失败\\n        icoError: function (response, file, fileList) {\\n        \\tresponse = response.toString().replace(\\\"Error: \\\",\\\"\\\")\\n        \\tresponse = JSON.parse(response);\\n        \\tthis.$notify({\\n        \\t\\ttitle: \'失败\',\\n        \\t\\tmessage: response.msg,\\n        \\t\\ttype: \'warning\'\\n        \\t});\\n        },        // qrCode删除\\n        qrCodeHandleRemove: function (file, files) {\\n        \\tvar index = -1;\\n        \\tindex = this.form.qrCode.findIndex(function (text) {\\n        \\t\\treturn text.uid == file.uid;\\n        \\t});\\n        \\tif (index != -1) {\\n        \\t\\tthis.form.qrCode.splice(index, 1);\\n        \\t}\\n        },\\n        //qrCode上传超过限制\\n        qrCodeHandleExceed: function (files, fileList) {\\n        \\tthis.$notify({\\n        \\t\\ttitle: \'失败\',\\n        \\t\\tmessage: \'当前最多上传1个文件\',\\n        \\t\\ttype: \'warning\'\\n        \\t});\\n        },\\n        //qrCode预览\\n        qrCodeHandlePreview: function (file){\\n\\t\\t\\t\\twindow.open(file.url);\\n        },\\n        //qrCode上传成功\\n        qrCodeSuccess: function (response, file, fileList) {\\n        \\tif (response.result) {\\n        \\t\\tthis.form.qrCode.push({\\n        \\t\\t\\turl: response.data,\\n        \\t\\t\\tname: file.name,\\n        \\t\\t\\tuid: file.uid\\n        \\t\\t});\\n        \\t} else {\\n        \\t\\tthis.$notify({\\n        \\t\\t\\ttitle: \'失败\',\\n        \\t\\t\\tmessage: response.msg,\\n        \\t\\t\\ttype: \'warning\'\\n        \\t\\t});\\n        \\t}\\n        },\\n        //qrCode上传失败\\n        qrCodeError: function (response, file, fileList) {\\n        \\tresponse = response.toString().replace(\\\"Error: \\\",\\\"\\\")\\n        \\tresponse = JSON.parse(response);\\n        \\tthis.$notify({\\n        \\t\\ttitle: \'失败\',\\n        \\t\\tmessage: response.msg,\\n        \\t\\ttype: \'warning\'\\n        \\t});\\n        },        validate:function(){\\n            var b = false\\n            this.$refs.form.validate(function(valid){\\n                b = valid;\\n            });\\n            return b;\\n        },\\n        save:function(callback) {\\n            var that = this;\\n            var url = this.formURL.save.url;\\n            if (that.form.id > 0) {\\n                url = this.formURL.update.url;\\n            }\\n            this.$refs.form.validate(function(valid) {\\n                if (valid) {\\n                    var form = JSON.parse(JSON.stringify(that.form));\\n                         //底部服务时间\\n                        if(form.timeBegin){\\n                           form.timeEnd = form.timeBegin[1];\\n                           form.timeBegin = form.timeBegin[0];                            \\n                        }\\n                        form.ico = JSON.stringify(form.ico);\\n                        form.qrCode = JSON.stringify(form.qrCode);\\n                    form.modelId = that.modelId;\\n                    ms.http.post(url, form).then(function (res) {\\n                        if(callback) {\\n                            callback(res);\\n                        }\\n                    });\\n                } else{\\n                    callback({\\n                    result:false,msg:\'请检查表单输入项\'\\n                    });\\n                }\\n            })\\n        },\\n        //获取当前foot\\n        get:function(id) {\\n            var that = this;\\n            that.loading = true;\\n            ms.http.get(this.formURL.get.url, Object.assign({\\\"modelId\\\":that.modelId},this.formURL.get.params)).then(function (res) {\\n                if(res.result&&res.data){\\n                                          \\n                        //底部服务时间 \\n                     if(res.data.timeBegin && res.data.timeEnd) {\\n                        res.data.timeBegin = [ms.util.date.fmt(res.data.timeBegin,\'HH:mm:ss\'),ms.util.date.fmt(res.data.timeEnd,\'HH:mm:ss\')];\\n                       }\\n                      if (res.data.ico) {\\n                          res.data.ico = JSON.parse(res.data.ico);\\n                          res.data.ico.forEach(function (value) {\\n                             if(value.url.substr(0,7).toLowerCase() == \\\"http://\\\" || value.url.substr(0,8).toLowerCase() == \\\"https://\\\"){\\n\\t\\t\\t\\t\\t\\t\\t } else {\\n\\t\\t\\t\\t\\t\\t\\t\\tvalue.url = ms.base + value.url;\\n\\t\\t\\t\\t\\t\\t\\t }\\n                              \\n                          });\\n                      } else {\\n                          res.data.ico = [];\\n                      }\\n                      if (res.data.qrCode) {\\n                          res.data.qrCode = JSON.parse(res.data.qrCode);\\n                          res.data.qrCode.forEach(function (value) {\\n                             if(value.url.substr(0,7).toLowerCase() == \\\"http://\\\" || value.url.substr(0,8).toLowerCase() == \\\"https://\\\"){\\n\\t\\t\\t\\t\\t\\t\\t } else {\\n\\t\\t\\t\\t\\t\\t\\t\\tvalue.url = ms.base + value.url;\\n\\t\\t\\t\\t\\t\\t\\t }\\n                              \\n                          });\\n                      } else {\\n                          res.data.qrCode = [];\\n                      }\\n                    that.form = res.data;\\n                    that.loading = false;\\n                } else {\\n                    that.loading = false;\\n                }\\n            }).catch(function (err) {\\n                console.log(err);\\n                that.loading = false;\\n            });\\n        },\\n\\n    },\\n    created:function() {\\n        var that = this;\\n        //渲染create\\n        that.get(this.form.linkId);\\n    }\\n});\",\"sql\":\"\\n-- FOOT\\nCREATE TABLE  `{model}FOOT` (\\n    `id` BIGINT(19) UNSIGNED NOT NULL AUTO_INCREMENT,\\n    `TEL` VARCHAR(255) DEFAULT NULL COMMENT \'电话号码\',\\n    `TIME_BEGIN` TIME DEFAULT NULL COMMENT \'底部服务时间\',\\n    `TIME_END` TIME DEFAULT NULL COMMENT \'结束时间\',\\n    `ICO` VARCHAR(1000) DEFAULT NULL COMMENT \'网站ico\',\\n    `QR_CODE` VARCHAR(1000) DEFAULT NULL COMMENT \'底部微信二维码\',\\n    `LINK_ID` BIGINT(20) DEFAULT NULL,\\n    `CREATE_DATE` DATETIME DEFAULT NULL COMMENT \'创建时间\',\\n    `CREATE_BY` VARCHAR(50) DEFAULT NULL COMMENT \'创建人\',\\n    `UPDATE_DATE` DATETIME DEFAULT NULL COMMENT \'修改时间\',\\n    `UPDATE_BY` VARCHAR(50) DEFAULT NULL COMMENT \'修改人\',\\n    `DEL` INT(1) DEFAULT 0 COMMENT \'删除标记\',\\n    PRIMARY KEY (`ID`) USING BTREE\\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT=\'foot\';\\n\",\"tableName\":\"FOOT\"}', '', 'foot', 1, '', '[\n    {\n    \"model\":\"tel\",\n    \"key\":\"TEL\",\n    \"field\":\"TEL\",\n    \"javaType\":\"String\",\n    \"jdbcType\":\"VARCHAR\",\n    \"name\":\"电话号码\",\n    \"type\":\"input\",\n    \"length\":\"255\",\n    \"isShow\":true,\n     \"repeat\":false,\n     \"isSearch\":false\n    }\n    ,{\n    \"model\":\"timeBegin\",\n    \"key\":\"TIME_BEGIN\",\n    \"field\":\"TIME_BEGIN\",\n    \"javaType\":\"Date\",\n    \"jdbcType\":\"TIME\",\n    \"name\":\"底部服务时间\",\n    \"type\":\"time\",\n    \"fmt\":\"HH:mm:ss\",\n    \"endModel\":\"time_end\",\n    \"length\":\"0\",\n    \"isShow\":true,\n     \"repeat\":false,\n     \"isSearch\":false\n    }\n                ,    {\n    \"model\":\"timeEnd\",\n    \"key\":\"TIME_END\",\n    \"field\":\"TIME_END\",\n    \"javaType\":\"Date\",\n    \"jdbcType\":\"TIME\",\n    \"name\":\"结束时间\",\n    \"type\":\"time\",\n    \"fmt\":\"HH:mm:ss\",\n    \"length\":\"0\",\n    \"isShow\":false,\n     \"repeat\":false,\n     \"isSearch\":false\n    }\n\n    ,{\n    \"model\":\"ico\",\n    \"key\":\"ICO\",\n    \"field\":\"ICO\",\n    \"javaType\":\"String\",\n    \"jdbcType\":\"VARCHAR\",\n    \"name\":\"网站ico\",\n    \"type\":\"imgupload\",\n    \"length\":\"1000\",\n    \"isShow\":true,\n     \"repeat\":false,\n     \"isSearch\":false\n    }\n    ,{\n    \"model\":\"qrCode\",\n    \"key\":\"QR_CODE\",\n    \"field\":\"QR_CODE\",\n    \"javaType\":\"String\",\n    \"jdbcType\":\"VARCHAR\",\n    \"name\":\"底部微信二维码\",\n    \"type\":\"imgupload\",\n    \"length\":\"1000\",\n    \"isShow\":true,\n     \"repeat\":false,\n     \"isSearch\":false\n    }\n]\n\n', 'tag', '2024-12-27 09:39:01', NULL, '2024-11-25 11:44:38', NULL, 0, 0, 'global');


INSERT INTO `mdiy_tag` (`id`, `tag_name`, `tag_type`, `tag_sql`, `tag_class`, `tag_description`, `MODEL_ID`, `UPDATE_BY`, `UPDATE_DATE`, `CREATE_BY`, `CREATE_DATE`, `APP_ID`, `DEL`, `NOT_DEL`) VALUES (19, 'global', 'global', 'select\n  APP_NAME as \"name\",\n  app_logo as \"logo\",\n  app_keyword as \"keyword\",\n  app_description as \"descrip\",\n  app_copyright as \"copyright\",\n  \'${contextPath}\' as \"contextpath\",\n  <#--动态解析 -->\n  <#if isDo?? && isDo>\n    \'${url}\' as \"url\",\n    \'${url}\' as \"host\",\n    \'/\' as \"html\",\n  <#--使用地址栏的域名 -->\n  <#else>\n    <#if shortSwitch?? && shortSwitch>\n      CONCAT(\'${url}\',\'${appDir}<#if appDir?has_content>/</#if>\') as \"url\",\n      \'<#if appDir?has_content>/</#if>${appDir}/\' as \"html\",\n    <#else>\n      CONCAT(CONCAT(\'${url}\',\'${html}/\'),\'<#if appDir?has_content>${appDir}/</#if>\') as \"url\",\n      CONCAT(CONCAT(\'/${html}\',\'<#if appDir?has_content>/${appDir}</#if>\'),\'/\') as \"html\",\n    </#if>\n    \'${url}\' as \"host\",\n  </#if>\n    CONCAT(CONCAT(CONCAT(CONCAT(\'template/\',id),\'/\'),app_style),\'/\') as \"style\"\nfrom app\n  <#--根据站点编号查询-->\n  <#if appId?? >\n    where id = \'${appId}\'\n  </#if>', 'globalTagService', '全局自定义标签样例', '1860892324126367745', NULL, '2024-11-25 09:36:40', '57', '2024-11-25 09:36:40', 'global', 0, 0);

自定义标签获取时间数据中时间多了个T(5.4.3及其以下版本处理)

以update_date(别名updateDate)为例,

通过string(“yyyy-MM-dd HH:mm:ss”) 自定义时间格式

最终格式: ${field.updateDate?replace(“T”,“ “)?datetime?string(“yyyy-MM-dd HH:mm:ss”)}

设置分页文章序号

<!-- 效果  xxx(文章标题)--10、xxx--11、xxx--12...   -->

<!-- 定义计算 上一页的index值  pageSize按需填写 -->
<!-- eg:当前为第二页 , pageSize=10 则index=(2-1)*10-->
<#assign index = (page.cur?number - 1) * pageSize/>
    
{ms:arclist size=pageSize ispaging=true}
<!-- 定义计算 当前的index值 -->
<!--计算后 result = 10,11,12... -->
<#assign result = index + field.index>
${field.title}--${result}
{/ms:arclist}

标签解析报错 The following has evaluated to null or missing

提示信息一般表现为缺少了某个标签,或标签的某个属性丢失;

1、如果提示为field.date问题,优先查看 静态化报错

2、其他问题,建议先将开源中国最新的sql(mdiy_tag表)执行一遍;仍有问题,第一时间反馈,我们会立即解决

生成报错**?**

1、标签没有正确的闭合{ms:page} 最后一个/符合不能少 正确:{ms:page /}

2、字符串类型参数没有加双引号,{ms:channel type=nav } 正确:{ms:channel type=“nav” },注意:数值不需要

3、include标签使用了注释 <!– <#include “url”> –> 正确:<#– <#include “url”> –>

报错分析参考

标签能嵌套使用吗?

目前只有部分标签支持嵌套,具体可以参考下方标签说明。

  1. 多级channel嵌套
  2. channel 与 arclist嵌套
  3. channel中使用include
  4. arclist与data嵌套

动态获取栏目或文章的typeid

当你在某个列表页时,使用ms:channel、ms:arclist两个标签时,如果不写typeid,会自动获取当前页面所属栏目的typeid值

文章点击量不自动增加

文章点击量是需要使用了的点击量标签之后前后端数据才会增加,同一台电脑多次访问只会增加一次点击量,注意后台的点击数量在去点击文章,看点击量是否增加。

用图片标签获取出来的图片总是裂开

如果是开源版,图片标签前面需要加上域名标签({ms:global.host/})才会正常显示图片。

标签有类似if判断的功能吗?

mcms4.7.0之后的版本可以支持简单的if判断,5.1之后支持freemarker if指令

<!-- freemarker if简单使用 -->
<!-- eg: 文章如果没有上传缩略图就显示一张默认图片 -->
{ms:arclist}
<#if field.litpic>
 <img src="{@ms:file field.litpic/}"/>
<#else>
 <img src="默认图片"/>
</#if>
{/ms:arclist}

使用参考

标签属性使用无效

5.1之后属性为字符串类型的需要加引号使用例如:type=“son”

页面静态化没有更新

1、按shift刷新浏览器 清除浏览器缓存
2、如果清楚缓存还不行,直接编辑一下文章,再静态化刷新页面

css文件、js文件能用标签吗?

不能,因为标签需要mcms系统解析之后才会渲染效果,而系统不会渲染引入css与js文件

在模板中使用了js的模板表达式用法如${xx * xx},导致报错

Alt text Alt text

原因:这种模板表达式用法与freemarker的标签解析冲突了,导致报错。

解决方式:在模板中使用noparse标签包裹模板表达式,如 <#noparse> ${xx * xx} </#noparse> Alt text

配置问题

代码生成器图片、附件组件提示undefined/upload/…

规范项目名路径变量,参考下图路径head-file文件,在src/main/webapp/WEB-INF/manager/include/下创建head-file文件

将会在5.4.4版本更新处理

Alt text

并添加ms.contextpath变量定义

...
    ms.base = "${base}";
    // 添加ms.contextpath
    ms.contextpath = "${base}"; 
    ms.login = "${managerPath}/login.do";
    ms.manager = "${managerPath}";
    ms.web = ms.base;
...

yml配置问题

自定义ms.manager.path和ms.manager.login-path问题

  1. 修改ms.manager.path 不能修改为cms、mcms等和web层接口重名冲突的名称
  2. 修改login-path 需要有一个登录地址和配置的login-path对应上 Alt text

部署多个相同的后台服务,同一浏览器同时登录多个服务会掉线问题

Alt text

多个服务的cookie-name需做区分,不相同就行

swagger文档

  • 开启swagger:修改yml中,springfox.documentation.auto-startup和springfox.documentation.enabled为true

  • 关闭swagger和api-doc:修改yml中springfox.documentation.auto-startup为false Alt text

Tip

注意:不同版本间的配置项可能存在差异,建议参考对应版本的 application.yml 配置的注释说明

上传的文件可能存在攻击处理

eg:上传html存在js攻击

Alt text

解决方式:通过yml配置中upload.denied添加html,禁止html上传

Alt text

启动报java.nio.charset.MalformedInputException: Input length = 2

产生原因:中文字符导致

快速解决方法,将yml中的中文注释全部删除

规范:禁止使用文本编辑器来修改配置文件,避免导致编码问题;推荐在ide中修改好

需要让标签生成域名解析为https

代理应用时标签解析域名依旧为http时,首先需要确认yml配置中 ms.scheme为https,然后应用设置保存、刷新缓存,再重新静态化。 Alt text

添加项目名后预览页面404,页面路径缺少项目名

出现这种情况可能是用户部署在Tomcat上加了项目名,没有在yml文件里配置,需要在yml里也进行配置, 并确保和Tomcat上项目名一致。 Alt text

Tip

由于添加了项目名称,在模板中所有涉及资源的引入路径(如js、css文件、图片等)也应该加上项目名称,即加上标签:{ms:global.contextpath/} Alt text

复制富文本图文,更换上下文后导致图片不显示问题

上下文:在这里指项目名称,默认为 /

第一步:通过编辑器的查看源代码功能,确定当前内容中图片路径是(上下文)/upload格式,否则说明图片没有上传成功。

第二步:由于图片链接是作为文本数据存在数据库中的,由于更换了上下文,导致原来的存储的图片路径在新的上下文中链接失效。可以写个脚本把数据库中新、旧上下文进行replace

超过1分钟,显示数据库链接超时

可以延长数据库最大等待时间,修改yml中spring.datasource.druid.maxWait的配置 Alt text

百度编辑器上传图片一直在加载

将yml中ms.upload.enable-web设置为true Alt text

模板文件太大 或 通过tinymce上传的文件太大 造成上传失败

问题:上传文件过大导致异常,修改application.yml文件,调整最大上传文件大小 Alt text

Alt text

pom.xml配置问题

页面静态资源丢失、部分页面访问404

在pom.xml中排除了静态资源和WEB-INF页面,未编译导致的问题

注意:excludes配置推荐在打包时开启,本地开发需要注释

Alt text

站群插件未生效

站群的依赖需要放到pom文件中依赖的第一位,因为站群插件重写了basic, 不放在第一位可能会导致站群不生效的情况。 Alt text

自定义配置问题

请求地址:http://localhost:8080/xxx/cms/content/list.do 当前操作存在sql注入风险,bad sql word:xxxxxx

因为修改了yml中的ms.manager.path,需要同步修改 安全设置 》 XSS过滤器配置中把ms替换成修改后的ms.manager.path

Alt text

Tip

注意,修改xss后需要重启服务才生效

sql注入问题,xss配置说明

MCms因为后台后台一些业务必须要使用到 ${} 来动态构建SQL语句,导致一些检测误认为系统存在 SQL 注入问题,实际上系统对请求的参数都已经做SQL 注入检测,只需要开启就可以防止注入问题。

  1. 开源版本开启配置参考

先更新到最新版本,删除排除路径中有关/ms/**,/**此类路径,也可以单独设置排除路径

开源xss配置

  1. 其他版本开启配置参考

启用xss过滤器,并删除排除路径中有关/ms/**/**此类路径,也可以单独设置排除路径

政务版xss配置

排除路径:添加排除路径后,则不会对该请求的参数做xss校验

排除字段:请求中的某个参数会触发xss,将触发xss的请求参数添加到排除字段中,则不会对该请求参数做xss校验

Tip

由于自定义会有动态修改表的操作和关键字,如果上线后仍需要使用自定义相关功能,建议加上/ms/mdiy/**的配置;其他功能添加配置和自定义一样

业务数据配置问题

定时调度任务执行失败

通常是由于任务没有正确配置参数导致的,例如生成首页,

调用目标:generaterServiceJob.index(‘index.htm’,‘index.html’,‘outsite’,‘http://localhost/’)

这里第一个参数’index.htm’指的是对应模板文件; 第二个参数’index.html’指的是主页文件名; 第三个参数’outsite’指的是自定义字典中设置模板类型数据值; 最后一个参数’http://localhost/’指的是调用本机。 需注意第三个参数填写正确,选择正确的模板类型。

更换域名后页面仍然显示ip地址

页面标签 {ms:global.url/}、{ms:global.host/}是从 APP 表中取地址字段进行渲染。 第一步:确认当前用域名登录后台,然后只需要在应用设置里保存一下,不需要做任何操作就可以更新地址字段。 第二步:确认 APP 表中地址字段app_url修改成域名,然后重新静态化。 第三步:确保静态化操作没问题,刷新页面就可以了(没效果强刷下页面)。

Tip

简单调试办法,首页模板只留{ms:global.url/}、{ms:global.host/},查看输出结果是否正确。 注意:若出现文章点击数失效的问题,在缓存管理里刷新缓存重新静态化即可。

idea配置问题

启动系统后台部分页面报404错误原因分析

情况1. 代码可能没有编译,重新clean,compile

判断是否编译小技巧:debug模式启动项目,打断点,只有圆圈没有勾,没有编译 Alt text

情况2. 地址正确,对应的页面资源缺少,按需补充视图文件 Alt text

情况3. pom依赖下排除了src/main/webapp目录文件,注释就行 Alt text

情况4. 如果你代码是最新的,那应该是你数据库导入错了,直接导入最新版本的数据就可以了

idea使用Tomcat开发,上传模板重启后模板消失

因为是上传到Tomcat内置的环境,重启Tomcat默认清理所有文件导致文件消失,解决方案:

  1. 开发过程中模板直接放在项目中,不要通过上传的方式放进去
  2. 上传的模板可以在重启之前,从Tomcat里面复制出来到项目中

启动时报缺少某个类,创建bean对象失败

可能是没有编译到,先重新clean、compile试一下,还是不行的话那就清除下idea缓存再maven强制更新一下

idea Tomcat 配置

启动es依赖报找不到org.elasticsearch.xcontent.ToXcontent0bject的类文件错误

解决方法

  1. 删除在该仓库下的org.elasticsearch文件夹
  2. maven配置中央仓库拉取依赖(不能使用阿里云镜像,尽量使用国外的)
  3. 重新拉取依赖,在仓库中找到es相关依赖并且版本对应
  4. idea中刷新一下maven

启动报错error creating bean with name “customMultipartResolver”.

  • 情况1 第一次启动报错

    解决办法

    1. 删除在该仓库下的net.mingsoft文件夹
    2. maven配置中央仓库,重新拉取依赖(不能使用阿里云镜像!)
  • 情况2 jar启动报错

    1. 首先看本地ide启动是否正常,本地不正常请参考情况1
    2. 把config文件中的配置与ide中resource文件夹下的同步

修改config配置未生效

config配置以mcms下的配置为标准,它会覆盖其他配置导致出现修改配置没有生效的问题。

mysql8.0驱动报错

需要使用jdk8 以上版本

包有错误且中文是乱码

项目右键属性窗口,配置项目编码格式为UTF-8

功能问题

静态化生成与链接问题

部署线上后,访问仍是本地地址

场景描述:本地使用localhost开发,部署到线上静态化后,发现地址仍然是localhost,而不是线上的域名

Alt text

  • 方案1:系统设置 -> 应用设置 保存再刷新缓存;

Alt text

简单检查是否生效,操作完后,查看get请求响应中appUrl是否为当前访问的地址;appUrl和当前访问地址一致后,重新静态化即可;

  • 方案2:按照方案1操作后,appUrl仍然不是当前访问的地址,并且使用了代理,比如nginx

一般为代理时,没有对请求头进行代理,导致外部获取的地址是服务器中访问mcms服务的地址,参考下方nginx配置

Alt text

  • 方案3:修改模板,移除带有域名地址的标签
{ms:global.host/} => /
{ms:global.url/}  => {ms:global.html/}

文章列表点击文章直接下载文件

自定义模型使用参考

思路:通过自定义模型处理

  1. 增加自定义文章模型,模型为附件

  2. 后台导入自定义模型,并给栏目绑定

  3. 文章通过模型上传附件 Alt text

  4. 文章列表获取文章模型字段,简单判断,如果模型字段有值,则链接为模型字段;否则为文章详情链接

列表模板写法参考:
如果模型中存在附件,则链接为附件地址;否则为文章详情链接
{ms:arclist  tableName="模型表名" size=4}
    <#if field.FILEUPLOAD_BMRPQ>
        //模型中存在附件,则跳转附件地址
        <a href="{@ms:file field.FILEUPLOAD_BMRPQ /}">${field.title}</a> 
    <#else>
        //否则跳转文章详情
        <a href="{ms:global.html/}${field.link}">${field.title}</a> 
    </#if>
{/ms:arclist}


内容模板写法参考:
如果存在附件,则在详情页通过iframe展示附件内容
<#if field.FILEUPLOAD_BMRPQ>
    <iframe
            src="{@ms:file field.FILEUPLOAD_BMRPQ /}" 
            width="100%"
            height="600px"></iframe>
</#if>

静态化报错,后台控制台提示ORDER BY CONTENT_HIT NULL Limit NULL

通过dbeaver数据库工具导入初始数据sql,执行静态化操作报错

Alt text

必须要取消勾选sql参数和变量配置,如果有启用需求,导入后再开启

Alt text

静态化文章点击数不显示问题

基本都是因为服务响应头的安全设置导致

可通过查看hit请求的响应头中是否有X-content-Type-Options,还有查看浏览器控制台是否有Strict MIME TYPE Checking错误确定问题

Refused to execute script from ‘XXXX’ because its MIME type (‘text/html’) is not executable
and strict MIME type checking is enabled.

X-Content-Type-Options: nosniff:严格按照服务器提供的 Content-Type 来处理资源

Alt text

需要将hit请求的返回类型做规范

Alt text

静态化后访问页面报404

  1. 选择的模板页面错误,比如你需要静态化A.htm,而你实际选择的是B.htm,导致A.htm没有静态化,那么就不会有的A.html页面,访问就会404

  2. 可能是由于后台代码没有编译到,需要重新去编译成功,再启动(如果使用tomcat,编译之后同步到tomcat,建议先去tomcat里面看下一是否同步成功)

  3. 可能是pom.xml中,启用了WEB-INF/,导致访问页面404,注释即可 Alt text

  4. 模板选择正确,但模板页面有语法错误,导致页面没有生成。

    • 4.1 页面有报错,先看后台的报错信息,方便定位问题,比如freemarker的语法错误: 找到当前静态化出问题的模板 Alt text 在后台查看报错 Alt text Alt text

    • 4.2 页面没有报错,静态化模板正确,查看页面404,查看后台是否有报错信息,分析原因

      如:检查对应栏目是否绑定模板;文章更新时间是否大于指定时间等。

静态化未达预期效果排查方式

  • 优先本地启动项目,线上环境复杂,如果已经发布还容易破坏生产环境;确保数据是一致的,代码没有差异。

  • 本地启动后如果并未复现问题,可能是线上环境问题,例如服务器没有文件写入权限导致静态化文件未生成或者文件访问权限不足等。

  • 本地问题与线上一致,就需要排查是数据问题还是模板问题,排查技巧为:先清空html静态文件夹,逐个栏目生成来定位是哪个栏目出现的问题。

  • 定位到某个栏目后,可以先逐渐删除模板内容,减少日志生成,通过日志中的sql执行和log信息结合来判断问题原因。

  • 其中数据问题一般是由非法直接导入数据产生,这类问题占比较少,通常是因为缺少必要关键字段如文章更新时间等导致的静态化失败。

  • 模板问题种类繁多,占比量很高,常见原因有:模板代码有语法问题;模板丢失;错误绑定了一个不存在的模板等等。

Tip

比较棘手的问题一般都是由于环境等第三方问题导致,需要开发者结合日志冷静分析。

静态化后链接跳转异常问题

  • 遵守规范,确保链接以单“/“开头

  • 当链接以双斜杠(//)开头时,浏览器会根据当前页面的协议(http或https)自动补充协议部分,而不会在前面拼接当前IP地址。这种链接称为“协议相对URL”,它会根据页面的协议自动选择相应的协议进行请求。

  • 当前页面的URL结尾缺少“/“,而链接中的href值也不是以”/“开头的相对路径,浏览器可能会错误地将链接解释为相对于当前路径的路径,而不是相对于根路径的路径。这可能导致链接中第一个层级被错误地添加到当前路径中。

静态化后控制台有异常的处理

  1. 企业版、政务版,优先在系统设置-》缓存管理处刷新缓存,再重新静态化看效果

  2. 模板标签有语法错误,根据异常堆栈信息,定位模板文件,逐个标签删除来定位错误标签

静态化生成爆空指针(NullPointerException)问题

静态化生成爆空指针问题

  1. 一般都是栏目没有绑定模板问题,非叶子节点的栏目,需要绑定列表模板,如果是叶子节点的栏目,需要绑定列表模板和文章内容模板
  2. 模板标签有语法错误,根据异常堆栈信息,定位模板文件,逐个标签删除来定位错误标签

静态化后查看页面报request method not support错误

原因:页面静态资源的请求与后台接口请求冲突

Alt text Alt text

静态页面访问路径层级太深问题

如路径/html/web/news/xingyedongtai/1666653707494039552.html

可以通过MStore短链接插件或nginx代理解决 Alt text

静态化报错 ${field.date?string(“yyyy-MM-dd”)} 标签语法错误

静态化时出现报错,日志或页面显示报错信息为 ${field.date?string(“yyyy-MM-dd”)} 标签语法错误,如下图

1.表content_datetime字段需为timestamp类型,且该字段值不能为空,出现上图报错排查数据库数据是否合规

2.企业版、政务版用户优先排查缓存管理中是否正确缓存了文章 需要在系统设置––>缓存管理 里进行刷新缓存,然后重新静态化即可。

Tip

文章缓存是缓存在 如ehcache或Redis(政务版支持)中,并非缓存至本地,各个管理员账号缓存数据共通。

模板样式(css,js)失效

1、参考global.style标签的使用,确保按照文档描述使用;

2、更新标签 或 在浏览器控制台查看静态资源引用地址,补全缺失的目录或“/“

3、如果配置了项目名,需要加上global.contextPath标签,具体参考global标签文档

静态页面文件路径说明

文章长链 html/web(网站目录,站群情况才会有)/default(模板文件夹名称,需绑定两个模板才有)/各层级栏目拼音/文章id.html

栏目长链 html/web(网站目录,站群情况才会有)/default(模板文件夹名称,需绑定两个模板才有)/各层级栏目拼音/index.html(分页情况list-2.html 为第二页)

文章短链 web(网站目录,站群情况才会有)/default(模板文件夹名称,需绑定两个模板才有)/文章id.html

栏目短链 web(网站目录,站群情况才会有)/default(模板文件夹名称,需绑定两个模板才有)/栏目拼音.html(分页情况 栏目拼音-2.html 为第二页) 具体静态页面路径可以参考: Alt text

html: 在yml中进行配置;短链情况下,此目录仅做静态文件管理,不在静态页面路径中体现

网站生成目录: 系统设置 > 应用设置中 网站生成目录的值,可自定义配置;短链且非站群情况下,不在静态页面路径中体现

模板名称: 如果绑定了两个模板,则会再加一层模板文件夹名称的路径;短链且单模板情况下,不在静态页面路径中体现

其他: 后台栏目文章数据,可自定义修改

点击生成栏目或生成文章,一直处于更新或生成中

情况1. 标签使用错误所以才会出现这种情况,仔细检查一下写的标签

情况2. 栏目绑定了一些不存在的模板,存在脏数据,解决:生成栏目或文章时一个一个点,排查问题栏目,再重新绑定模板

生成主页时,选择主页模板出现“暂无文件”

去系统设置—>应用设置里面重新保存一下,再去刷新一下生成主页界面即可。

首页(前台)界面样式混乱或无法打开

登录后台找到静态化菜单进入,进行所有的生成操作就能修复。

前台网站主页访问说明

  1. 通过域名访问网站时,默认访问default.html,没有default.html才访问index.html
  2. 预览主页默认是访问index.html

静态化后页面中的英文字母显示为大写形式

请仔细检查模板中是否使用了相关的样式,如text-transform: uppercase;如果不需要,移除即可 img_4.png

文件上传相关

上传文件类型及修改文件后缀可上传文件问题

上传是通用功能,根据系统后台配置限制可上传的文件类型

关于修改文件后缀可上传问题,部分用户反馈将html后缀改成jpg后,可上传问题

这里提供两种方案

  1. 需要限制该处只允许某种类型,需要自行新增扩展上传方法;文件类型基本校验系统都已提供,如果上传篡改文件需要获取文件真实类型,可以通过工具类或自行实现读取文件的方式(https://www.cnblogs.com/ryelqy/p/10104171.html 第三点)

  2. 需要上传文件确保系统安全,可以通过系统提供的xss对上传文件内容过滤

上传大文件说明

大文件上传推荐第三方专业存储平台,如七牛云存储,如果上传文件到服务器可以考虑直接ftp上传;

小文件可以通过编辑器上传;大文件推荐通过ftp上传到服务器中(推荐按照规定路径放置,注意文件名称不要含有中文和特殊字符)获取文件的路径,然后在上传视频的弹窗中,将文件路径填到表单中;

不推荐通过系统上传大文件,毕竟不是流媒体系统,不支持断点续传等功能,不能确保数据稳定传输;

并且在网站中展示大文件,比如视频、高码率图片等,会占用大量带宽,导致其他用户访问网站时速度变慢。

上传文件OOM(内存溢出)问题

设置JVM启动参数,具体按需设置

  • idea设置jvm内存参数 idea设置JVM参数

  • jar启动设置jvm内存参数

    java -Xmx1024m -Xms1024m -jar ms-mcms.jar
    

权限功能

角色分配权限后看不到菜单

分配权限后,将该角色对应的管理员账号进行登出再登录

生成主页按钮显示不出来

是由于你当前没有这个权限,需要去重新分配权限。(直接导入最新的数据库,默认账号就有权限)

内容管理功能

富文本内容详情与静态化显示效果有差异

一般是因为内容模板中的样式与富文本的样式有冲突

可以修改内容模板,如下

// 内容模板 只输出文章富文本内容 查看效果
${field.content}

再结合模板 排查样式影响

系统默认的文章数据发布时间为什么是未来时间?

因为这些只是演示数据,这么做是为了解决生产文章是可以直接默认当前是时间直接生成即可。

为什么主页、栏目、文章都生成了无报错,访问文章还是404,文章访问不了或没有生成成功

生成文章的时候需要注意生成的时间需要往前推。需要在生成文章时,选择生成文章时间,起始时间要选择靠前一点,比如文章时在2015年08月1日创建的,那么生成的时候时间需要选择在2015年08月1日前。

栏目页面有部分乱码,字符集不匹配如下截图

需要更改tomcat/conf/server.xml文件的Connector节点(端口配置的Connector节点)增加URIEncoding=“UTF-8”,

<Connector port=“8080” protocol=“HTTP/1.1” connectionTimeout=“20000” redirectPort=“8443” URIEncoding=“UTF-8”/>

启用移动端用手机访问是空白页面

先确认你的模板中是否有手机端,如果有手机端开启了,访问移动端的用手机访问就是访问移动端的模板,如果没有模板手机端访问的则是空白的,这个时候建议把启用手机端的按钮去掉勾选(自定义字典中修改)

生成器菜单说明

系统更新把生成器功能集合成静态化了,在内容管理菜单下面

在后台文章上传图片成功之后会显示,但是过一段时间就会裂开

因为图片是上传到Tomcat中,每当重置的时候图片就会消失,建议你上传完图片进行备份一下,或者上传完图片把upload文件夹覆盖项目中的这个文件夹

忘记密码

开源、开发版本

开源(开发)采用md5加密,通过在线md5加密,对密码进行加密(注意md5为32为小写),修改数据库

其他版本忘记密码

后台开发

新增后台页面一直有loading状态

Alt text

按下面示例添加页面,默认挂载vue,可根据需求自行处理

<html xmlns="http://www.w3.org/1999/html">
<head>
    <title>title</title>
    <#include "../../include/head-file.ftl">
    
</head>
<div id="app" class="ms-index" v-cloak>
    这里是页面
</div>
</html>
<script>

    var app = new _Vue({
        el: '#app',
        data:function(){
            return{

            }
        }
    });
</script>

Tip

推荐通过代码生成器来辅助开发,获取完整基本功能页面,再去做调整

通过swagger调试后台接口

需要先登录后台,再访问swagger来调试后台接口

二次开发的业务,配置免登录放行

  • 方法一(推荐)

    可以新增一个action,在RequestMapping里面去除${ms.manager.path}前缀,并且需要去除接口的权限校验。推荐将免登录的action放在web目录下,例图

    需要校验 Alt text

    不需要校验 Alt text

  • 方法二(不推荐)

    在shiroConfig中新增过滤路径,并且需要去除原方法的权限校验标识;涉及框架的改动,如果不熟悉框架,不推荐使用。 Alt text Alt text

文章访问记录功能说明,hit

目前只有在详情页面模板使用了hit标签,才会记录文章浏览,目前记录了访问ip,访问文章id等,均记录在cms_history_log表中,如下图。

在文章内容模板使用${field.hit} Alt text Alt text

如果开发者有如其他记录需求如 Alt text 可参考以下接口 Alt text

后端接口测试使用说明

  1. 使用登录验证

    接口需要添加 ms 前缀, 并加上对应的cookie
    eg: http://localhost:8080/ms/cms/content/list.do
    

    Alt text 登录验证问题:

    如果没添加对应的cookie 返回的就是首页的页面

  2. 跳过登录验证

    接口去除 ms 前缀 即可
    eg: http://localhost:8080/cms/content/list.do 
    

前端向后端发送请求精度丢失问题

在请求传递参数时,在参数加上单引号,如id:‘id’,这样就不会丢失精度。

使用 ms.xxx(ms工具类)显示undefined问题

需要在相对应的页面引入静态资源

<!--如以下资源,根据提示引入相对应的资源-->
<script src="/static/plugins/ms/3.0/ms.umd.js"></script>
<script src="/static/mdiy/index.js"></script>

请求含中文路径导致400 Bad Request

原因:shiro中引入了一个全局的InvalidRequestFilter ,其中blockNonAscii默认为true,路径含中文会被过滤掉。

出于安全问题考虑,mcms系统默认设置为blockNonAscii默认为true,防止用户通过非ASCII字符构造SQL注入、脚本注入或其他类型的注入攻击。开发者如果请求路径需要设置中文,造成的安全风险自行解决。

Alt text 处理参考1

处理参考2

不符合xss规则

在yml,ms.xss.exclude-url中添加异常信息提示的接口不符合xss规则的请求,或ms.xss.exclude-field中添加异常信息提示的参数名

插件功能问题

tinymce编辑器上传超时

可以延长上传文件的超时时间,即在下载的tinymce源码中,找到file_picker_callback方法将timeout的值调大即可

tinymce编辑器插入地图提示APP被禁用

  1. 访问百度地图控制台,注册获取ak

  2. 在static/plugins/tinymce/5.10.2/plugins/bdmap/bd.html和map.html中,使用获取到的ak

...
<!-- 更换成您的ak -->
<script charset="utf-8" src="https://api.map.baidu.com/api?v=3.0&ak=您的ak"></script>
...

使用百度编辑器显示最大输入2000字符数

这个是百度编辑器默认提示的,不影响实际使用,如需要修改,可以在content/form.ftl中修改

百度编辑器上传大小怎么设置?

Tip

如果想要控制文件上传大小等操作可以修改该配置文件以及在页面中的初始化参数。

现在官方已经不再维护百度编辑器,推荐使用store的新版编辑器。

tinymce编辑器复制富文本word

目前支持office word的图文复制,wps不支持

从office word 复制使用常见问题

office复制图片丢失问题说明

https://www.jianshu.com/u/e535e3f6462d

wps word 不支持图文复制,可以手动选中word中的图片复制到编辑器中

百度编辑器静态化后表格不显示

如图,编辑器内表格显示,但静态化后的页面不展示 Alt text Alt text 处理:在编辑器中设置表格边线可见,重新静态化 Alt text

编辑器字体相关说明

编辑器中部分字体涉及商用授权问题,需要购买相关授权才能使用。系统目前仅预置了几种传统的免费字体。

开发者可按需自行添加字体,新增字体配置步骤

  1. 在WEB-INF/manager/cms/content/form.ftl的editorConfig 中找到 fontfamily 配置,在 fontfamily 中添加需要的字体定义 img_3.png

浏览器相关说明

No static resource xxx.js.map

source map文件不会影响代码正常运行,只有在浏览器调试时才会去加载source map文件;

如果需要调试第三方js,可以自行下载资源对应版本的map文件,路径目录一致;

如果想要手动禁用source map,可以找到对应资源把最后的 //# sourceMappingURL移除

Alt text

浏览器缓存导致看不到最新页面

  1. 查看页面效果时按住shift刷新页面
  2. 开发时,可选择设置浏览器,禁用浏览器缓存
  3. 在模板中设置,通过html的meta属性设置不要缓存;参考博文

Tip

说明:缓存是浏览器的功能特性,不是系统Bug

浏览器小图标ico添加

  1. 使用标准命名自动加载

    在webapp/static目录下创建favicon.ico文件即可,注意自动引用名称必须为favicon.ico img_2.png

  2. 手动指定图标路径

    <!-- 如果需要使用自定义名称的ico文件,在模板中添加以下引入代码,推荐在公共模板中引入 -->
    <link rel="shortcut icon" href="图标路径">
    

控制台403问题

资源文件请求出现403,这一般是服务器拒绝访问该资源,需检查服务器之类

企业版、政务版

企业版、政务版没有store入口

在后续的企业版和政务版中我们取消了store功能,相关代码如下所示。

store主要面向开源版本和开发版本,企业版及政务版的数据结构和开源版有差异,会使得其数据结构被store中某些插件下载时执行的DDL语句破坏。

因此建议使用开源版本进入store下载模板然后再导入到企业版和政务版中。

模板(有演示站的)导入企业版或政务版的步骤

Tip

从开源版本升级到企业版、政务版,可以通过分享插件迁移文章、栏目、模板等相关数据

  1. 数据备份,导入会删除(当前站)所有的文章、栏目、自定义相关数据;可以手动备份数据和模板相关资源,或者通过分享插件备份
  2. 通过开源系统后台安装分享插件,在菜单管理中找到Store分享,然后复制菜单json,然后将这个菜单json在政务版导入;怎么进MStore?
  3. 开源MCMS仓库的pom找到store-client依赖并添加到企业版或政务版的pom中
  4. 没有站群忽略这步,有站群并且是站群配置是开启的,需要在mdiy_model表中添加APP_ID字段,导入完成后移除APP_ID字段即可,这里以mysql为例
ALTER TABLE `mdiy_model` ADD COLUMN `APP_ID` bigint(19) NULL AFTER `NOT_DEL`
  1. 企业版本需要增加这一步,政务版本忽略;需要执行sql,这里以mysql为例
-- 导入前执行
ALTER TABLE `cms_content` ADD COLUMN `CONTENT_STYLE` varchar(255) NULL AFTER `DEL`;

-- 导入完后 执行下面的sql移除
ALTER TABLE `cms_content` DROP COLUMN `CONTENT_STYLE`;
  1. 启动企业版或政务版,导入分享菜单,将在开源导出的zip文件导入(注意备份文章、栏目表数据),除mysql其他版本数据库导入说明
  2. 删除Store分享菜单,移除store-client依赖

Tip

导入时,默认导入模版类型字典的第一个值,要确保导入正确,可以让模版类型字典只保留一个值,这样能确保导入到对应字典的站点风格中

企业版政务版 除mysql其他版本数据库 通过分享插件导入皮肤说明

  1. 解压皮肤包后进入data/目录,确定是否包含模型数据,模型数据以model_开头

  2. 不含模型数据如图: Alt text 则可直接通过分享插件导入下载的皮肤压缩包。

  3. 包含模型数据如图: Alt text 将模板文件夹(包含.htm的文件夹)通过工具(如idea)导入,全局搜索模板文件找到modelName、tableName、modelId的相关代码,注释或删除; Alt text 后续如果需要使用这个模型,可使用代码生成器生成模型代码。

    将模型相关代码注释后,再删除模型数据文件(model_开头的文件),再手动压缩文件,还原压缩包,注意压缩的层级要和导出的层级一致。最后通过分享插件导入新压缩包。

代码更新

首先第一次下载源代码之后,请务必通过git仓库管理好下载的源代码,平台不定期会更新订单中的代码,如遇到技术专员提示升级代码,可以通过当初平台绑定的订单信息下载最新的源代码,直接覆盖本地的代码,再通过仓库比对的方式进行查看更新的部分。

插件依赖

平台选配的插件通过沟通群技术单独提供,包括插件的sql文件、菜单json、源代码。
组织机构为例:

将插件文件夹直接复制到工程目录里面

修改/pom.xml与/ms-mcms/pom.xml 增加插件依赖

Tip

如果还存在页面404问题,请重新编译项目重启,推荐使用默认maven配置以及mvn命令行重新编译,避免ide环境问题导致编译失败

初始化数据

  1. doc/ddl.sql 表机构
  2. doc/init.sql 初始化数据,插件依赖的初始化数据
  3. doc/data.sql 演示数据,如果不需要可以不执行
  4. doc/organization.json 菜单json,复制json直接在菜单导入

Tip

doc下面文件具体每个插件数量不一样,具体以提供的为准

版本比对

推荐用户下载代码后直接发布到自己的git仓库,后续下载代码覆盖本地与git仓库进行代码比对。数据库的更新比对方式,可以通过创建新的数据并导入最新SQL脚本,通过Navicat进行结构比对。

更新父pom.xml

  1. modules 增加 <module>ms-morganization</module>
  2. dependencies 增加依赖
<dependency>
    <groupId>net.mingsoft</groupId>
    <artifactId>ms-morganization</artifactId>
    <version>${ms.version}</version>
</dependency>

更新ms-mcms\pom.xml

dependencies 增加依赖

<dependency>
    <groupId>net.mingsoft</groupId>
    <artifactId>ms-morganization</artifactId>
    <version>${ms.version}</version>
</dependency>

后台页面404、模板管理读取不到模板文件、模板上传到错误路径等(工作区间)

排除方法:设置working directory(工作区间)

Tip

单项目多模块下启动时候必须设置 Working directory 目录(MSApplication.java所在模块的目录,这里是D:\work\gov\ms-mcms),然后重启项目。


检查ms-mcms/pom.xml配置,注释掉排除配置

屏蔽堆栈打印信息

屏蔽上面的堆栈信息 设置后 页面不再输出堆栈信息 是空白页

设置异常信息

避免异常信息中暴露库表结构

设置sql注入异常提示信息

sql注入异常提示信息

required a bean of type ‘org.springframework.cache.CacheManager’

Alt text

Alt text

ehcache.xml没有打进jar包内,导致启动失败

数据传输加密

不允许前端明文传递数据,对传递数据做加密处理

通过获取公私钥配置,对传输数据进行加密

Alt text

  • 前端加密参考片段

// login.ftl页面
...
ms.http.post(ms.manager + "/login.do", {
    managerName:that.rsaEncrypt(that.form.managerName),
    managerPassword:that.rsaEncrypt(that.form.managerPassword),
...

// 加密方法示例
rsaEncrypt:function(pwd){
    var that = this;
    var encrypt = new JSEncrypt();
    // publicKey 从后台获取的公钥
    encrypt.setPublicKey(that.publicKey);
    var encryptPwd = encrypt.encrypt(pwd)
    return encryptPwd
},

  • 后端解密参考片段

后端接收参数解密后不符合预期时,一般都是有前置的业务对参数进行了处理,尤其是aop,可以在前置业务中(aop)中就对参数进行解密处理


LoginAction

// privateKey 查询获取私钥
//开始解密
manager.setManagerName(RSAUtils.rsaDecode(manager.getManagerName(),privateKey));
manager.setManagerPassword(RSAUtils.rsaDecode(manager.getManagerPassword(),privateKey));

修改密码、忘记密码

通过控制台打印的信息,获取当前输入密码的加密结果(验证码要正确输入)

获取到加密结果后,修改数据库

生成文章内容为空

一般是缓存数据被清空导致,先点击清空缓存,再点击刷新缓存

刷新缓存后,再重新静态化生成文章

Tip

系统非正常重启都会导致缓存丢失,例如:linux下通过kill 结束进程,通常推荐重启系统之后都需要重新缓存一下

缓存管理数据

清空缓存或刷新缓存时,已缓存文章总数和实际文章总数没有对应的问题 缓存数据没对应

一般出现这种情况都是产生了脏数据,如 某些文章所属栏目为不存在的栏目或是父栏目下有文章

-- 查询文章中所属栏目不存在的文章数据,可使用sql排查,下面的以mysql为例
SELECT
	cms_content.* 
FROM
	cms_content
	LEFT JOIN cms_category ON cms_content.CATEGORY_ID = cms_category.id 
WHERE
	cms_category.id IS NULL;


-- 查询父栏目下的文章

SELECT
	* 
FROM
	cms_content
	LEFT JOIN cms_category ON cms_content.CATEGORY_ID = cms_category.id 
WHERE
	cms_category.LEAF = 0

Alt text

清空缓存清不到0的情况,一般是在有缓存时手动操作数据库删除了文章导致的,常见于站群情况

可以删除对应的缓存文件,或者增加一个清空全部缓存的方法

contentCache.deleteAll();

快速去掉铭软相关标识与引导信息

点击 系统设置下的 后台UI配置,可以快速设置登陆界面、后台logo、后台的信息提示

Tip

如需要隐藏表单上的引导信息,可以通过关闭 隐藏信息提示达到效果

政务版 发布到 功能

在多皮肤的基础上增加了发布到,可以实现例如 内网数据只在内网显示、外网只在外网显示。

需要栏目绑定好列表内容模板

新增文章必须选则发布到内网或外网。具体根据文章所属栏目绑定的模板决定,比如在某个栏目绑定了外网模板,那么栏目下的文章就需要选择发布到外网,如果不设置发布到,则文章不会生成。

如果某个栏目同时绑定了内外网模板,那么栏目下的文章内外网按需设置,如果只设置内网,那么就只有内网会生成文章。

Tip

比如幻灯栏目这种没有页面展示的栏目,可以创建一个空模板文件(如 empty.htm )并将其绑定到这些栏目上,以便正常新增删除文章。

Tip

如果修改了字典模版类型的数据,需要应用设置更新保持,栏目模版,文章的发布到进行更新同步

需要将静态文件同步到另外一台服务器使用(后台访问的地址与前台用户访问页面地址不一致)

由于{ms:global.url/}{ms:global.host/}都是会获取后台系统的实际地址,导致部署的时候页面中存在http://xxx这种绝对地址。

如果静态化后的页面需要分发到其他服务器或访问地址不一致, 处理:

  1. 统一去掉模版中的{ms:global.url/}{ms:global.host/}标签,改用html/{ms:global.template/}方式。
  2. 同时需要将模版文件夹templatestatic目录也同步到服务器

政务版 企业版 停用默认账号

先登陆后台通过默认msopen账号创建一个账号,再在安全设置->账号安全禁用默认账号角色

越权说明

系统很多接口功能是通用的接口,会被很多模块业务反复调用,所以通用的接口一般不给权限标识去控制权限

例如:字典读取、栏目的读取,模版接口等,都会被很多模块会调用到, 如果加上权限标识,就要为每一个业务调用权限标识,这样会接口代码的冗余。

如果需要业务接口增加权限区分,可以通过再控制层*Action对应的方法上增加权限标识 @RequiresPermissions("标识"),如下截图

img.png

菜单管理中需要增加权限标识 img_1.png

数据量大静态化慢

如果因为数据量大导致静态化慢,可以把栏目列表模版修改为异步请求大方式获取,这样就可以减少栏目列表页面的生成。

<!--列表-->
<ul>
    <li v-for="content in contents">
        {{content.title}}
        <!--格式化时间-->
        {{ms.util.date.fmt(content.date,'yyyy-MM-dd')}}

        <!-- 获取指定位置图片 -->
        <img
            :src="getLitpic(item.litpic)"
            alt=""
        />
        <!-- 获取多张图片 -->
        <!-- 如果 litpic 为空或解析后数组为空,显示默认图片 -->
        <template v-if="!item.litpic || JSON.parse(item.litpic || '[]').length === 0">
            <img
                src="/static/images/404.png"
                alt="默认图片"
            />
        </template>
        <!-- 否则正常循环图片 -->
        <template v-else>
            <img
                v-for="pic in JSON.parse(item.litpic)"
                :src="pic.url"
                :alt="pic.name"
                :key="pic.uid"
            />
        </template>

        <!-- 文本截取 -->
        {{truncateText(item.descrip,50)}}...
    </li>
</ul>
<!--分页-->
<el-pagination background @current-change="handleCurrentChange" :page-size="pageSize" :current-page.sync="pageCur" layout="prev, pager, next, jumper" :total="pageTotal"></el-pagination>

<script>
var app = new Vue({
    el: '#app',
    data: {
        //当前页数
        pageCur: 1,
        //每页文章条数
        pageSize: 20,
        //页数总数
        pageTotal: 0,
        contents: [],
    },
    methods: {

        handleCurrentChange: function(val) {
            this.list(val)
        },
        list: function(pageNo) {
            var that = this
            ms.http.post('/gov/cms/content/list.do ', {
                typeid: '${field.typeid}',
                pageSize: that.pageSize,
                pageNo: that.pageCur,
                orderby: 'date',
                order: 'desc',
                style: 'insite',
            }).then(function(data) {
                if (data.result) {
                    that.contents = data.data.rows
                    that.pageTotal = data.data.total
                }

            })
        },
        /**
         * 获取指定位置的图片url,默认获取第一个
         * @param images JSON格式图片 eg:[{"url":"1.jpg","name":"1.png","uid":1},{"url":"2.jpg","name":"2.jpg","uid":2}]
         * @param position 下标
         */
        getLitpic: function(images,position) {
            var defaultImgUrl = '/static/images/404.png';
            if (!position){
                position = 0;
            }
            if (!images){
                return defaultImgUrl;
            }
            try {
                var imageArr = JSON.parse(images);
                return imageArr[position]?.url || defaultImgUrl;
            } catch (e) {
                return defaultImgUrl;
            }
        },
        /**
         * 
         * @param text 被截取的文本
         * @param length 截取长度
         */
        truncateText: function(text, length) {
            if (!text) return '';

            if (text.length <= length) {
                return text;
            }

            return text.substring(0, length);
        },
    },
    mounted: function() {
        this.list()
    },
})
</script>

自动静态化

启用开关,在涉及到文章变动如新增修改删除分发等情况下,会自动静态化首页、相关栏目页、文章详情页。

Tip

生产环境推荐开启此功能,可以不用手动静态化页面。自动静态化地址必须和当前浏览器地址一致,否则静态化不成功,如何更改URL地址

自动静态化配置如何设置

{
  "外网": [
    {
      "file": "index.htm",
      "path": "index.html"
    }
  ]
}

如上静态化首页配置,其中外网对应是模板类型字典中的名称,file对应是静态化文件名称,path对应是静态化生成路径。如上配置就是静态化index.htm模板到index.html

Tip

谨慎修改,请务必确保配置内容符合标准 JSON 格式,否则在执行静态化过程中可能会出现错误。您可以使用 JSON格式验证工具 来检查配置是否正确。

多皮肤

不同的皮肤在html文件夹下会生成对以模版名称命名的文件夹,例如:html/a模版 、html/b模版

因防火墙、安全狗、WAF之类的过滤导致文章保存失败

如果本地开发环境保存没有问题,上线部署后产生了发布文章的数据不完整(例如大于多少文字后无法保存),建议检查环境配置,如果没有防火墙、安全狗、WAF之类的配置,再检查一下容器是否限制了传送数据的长度,下面以东方通为例截图:

从外部导入管理员数据

@Autowired
private IManagerBiz managerBiz;

@RequestMapping(value = "/init",method = {RequestMethod.GET,RequestMethod.POST})
@ResponseBody
public ResultData init(HttpServletResponse response, HttpServletRequest request) {


        // 账号
        String managerName = "msshow";
        // 密码原文
        String originPwd = "Msopen@2012";
        // 昵称
        String managerNickname = "演示导入demo";
        // 加密后的密码
        String password = SecureUtils.password(originPwd, managerName);
        // 身份标识 第二个参数由管理员标识(managerAdmin)+角色ids(roleIds)组成,普通管理员的标识为null,角色ids按需给
        String identifier = SecureUtils.password(managerName, "69,70");


        ManagerEntity manager = new ManagerEntity();
        manager.setManagerName(managerName);
        manager.setManagerPassword(password);
        manager.setManagerNickName(managerNickname);
        manager.setManagerIdentifier( identifier);
        manager.setRoleIds("69");
        manager.setCreateDate(new Date());
        manager.setUpdateDate(new Date());
        managerBiz.save( manager);

        return ResultData.build().success();


}

从外部导入数据到内容表时,注意需要初始化的字段,否则会导致无法静态化(旧数据迁移)

Tip

由于版本迭代会有文章表结构差异,数据的默认值、结构以系统新增的数据为准。

  1. content_type 不能为NULL ,如果为空应该为空串

  2. content_display 不能为NULL, 应设置为默认0

  3. del 默认0

  4. category_id 不能为NULL,这里记录着文章所属栏目的id

  5. content_style 不能为NULL,这里关系到静态化生成目录,如果为空不会生成静态文件;建议新增 设置“发布到“后查看值

  6. progress_status 应该默认为 终审通过

  7. has_list_html、has_detail_html 未被静态化标识;建议查看数据库该字段默认值

  8. 注意content_img字段的格式,格式不对会导致回显失败!建议新增 查看默认值格式

  9. 导入数据后需要重启服务并在静态化重新刷一下缓存或在缓存管理删除缓存并重新缓存

Tip

外来文章迁移进系统时应该同步到缓存及es中,在保存成功后回调缓存文章方法及es同步文章方法。

栏目数据导入/迁移说明

栏目数据由于包含父子关系,迁移特别注意CATEGORY_PARENT_IDS与CATEGORY_PATH数据组成。如果栏目数据不多,建议手动创建栏目。

栏目数据迁移时注意,以下列表重要字段说明

  1. CATEGORY_ID 记录当前栏目的父栏目id。如果是顶级栏目,则为NULL。

  2. LEAF 记录是否是叶子节点,1为叶子节点,0为非叶子节点。当前栏目下无子栏目即称为叶子节点。

  3. TOP_ID 记录顶级栏目的id。从当前栏目出发,如果没有父栏目称为顶级栏目;否则从父栏目继续往上找。如果当前栏目为顶级栏目,则值为0;否则值为顶级栏目的id。

  4. CATEGORY_PARENT_IDS 记录所属栏目编号集合,记录顺序从顶级栏目开始,逐级往下直至记录当前栏目的父栏目,多个ID使用逗号分隔。如:1458331339823124482,1904029579523604482

  5. CATEGORY_DISPLAY 记录栏目是否在静态化时显示,enable为显示,disable为禁用。

  6. CATEGORY_IMG 记录当前栏目banner图,注意图片保存格式为

[{"url":"/upload/1/cms/category/1646209415768.png","name":"weixin.png","uid":1646209415606}]
  1. CATEGORY_PINYIN 记录当前栏目的拼音,决定静态文件访问路径,一定要有值。如:新闻,则拼音为xinwen。栏目拼音可以自定义,但不能与其他栏目重复。

  2. CATEGORY_PATH 记录当前栏目的访问层级路径,值为各层级的栏目拼音,。如新闻为顶级栏目,则值为xinwen;如果新闻有子栏目国内,那栏目国内的路径为xinwen/guonei

  3. CATEGORY_TYPE 记录栏目的类型。1为列表栏目;2为单篇栏目;3为链接栏目。

  4. CATEGORY_URLS(企业版及以上使用) 记录内容的多模版参数,格式如

[{"name":"外网","template":"out","file":"news-detail.htm"},{"name":"内网","template":"","file":""}]

参数说明:name:内、外网值由自定义字典中的模板类型决定;template:使用的模板文件夹名称;file:具体的模板文件名称。如果不太熟悉系统,建议手动绑定模板

  1. CATEGORY_LIST_URLS(企业版及以上使用) 记录列表的多模版参数,内容多模板同理。

  2. CATEGORY_LIST_URL(开源使用) 记录栏目绑定的列表模板 格式为:news-list.htm

  3. CATEGORY_URL(开源使用) 记录栏目绑定的内容模板 格式为:news-detail.htm

静态化提示后台正在静态化问题

1.开发环境检查模板可以适当减少文章数据量,手动生成时不必多次点击,后台会一直执行程序。

2.线上环境请开启自动静态化,具体操作参考本页面 ‘自动静态化’ 问题。

Tip

静态化是多线程操作,多次点击会消耗大量线程,点击生成一次就可以,后台会持续生成静态文件直至结束

动态访问读取模板有误

1.通过/mcms/index.do?style=模板文件夹名称(out|out1) 来指定动态访问的模板 Alt text

2.动态静态化作用主要是让开发者快速调整模板,不需要去手动静态化查看修改模板后的效果,在生产环境建议不要开启!

3.动态访问会默认读取自定义字典里模板类型的第一个,排序不对会导致错误选择模板类型。皮肤风格会选择第一个模板类型对应的在应用设置里绑定的皮肤风格,确认都无误后再在静态化配置里开启动态静态化解析,通过ip+/mcms/index.do的方式访问。

政务版过期时间说明

gov的session过期时间以后台配置(安全设置 > 账号安全 > 会话超时)的标准,yml的配置会被后台配置覆盖

政务版重置密码错误次数

成功登录或者重启服务可以重置密码错误次数; 密码错误次数用尽导致账号被锁定,可以重启服务解锁账号,修改配置表中timeout无效; 修改密码请参考 政务版重置密码错误次数

密码错误次数过多,账号被锁定,也可登录其他管理员账号在 安全设置-> 在线管理员处解锁 Alt text

静态化进度条一直100%

一般是有栏目没有绑定模板导致后台一直请求,推荐先给栏目绑定好模板或选择已绑定模板的栏目进行静态化

修改加密算法后登录失败

修改加密算法后会使管理员密码失效,更改后需第一时间按以下步骤进行操作:
1.在管理员管理界面修改一名其他超级管理员的密码,

2.重启系统,

3.用已修改的超级管理员登录,恢复其他管理员密码数据。

Tip

尽量在拿到代码时确定使用的密码加密方式,避免修改加密算法,同时修改安全设置的权限避免给到普通管理员。

如何实现免密登录

在一些二次开发的场景中,会有不需要本系统登录业务的情况,对接上其他系统的登录业务并能完整使用本系统的功能; 在其他系统登录成功之后,调用本系统的接口(自行新增):

        ManagerEntity manager = managerBiz.getByManagerName(username);
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken upt = new UsernamePasswordToken(manager.getManagerName(), manager.getManagerPassword(),rememberMe);
        subject.login(upt);

还需要将密码匹配器ManagerLoginMD5CredentialsMatcherManagerLoginSM4CredentialsMatcher两个类的doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)方法的一段业务代码注释;

ManagerLoginSM4CredentialsMatcher

        boolean matches = false;
        //国密
        SimpleAuthenticationInfo _info = (SimpleAuthenticationInfo) info;
        UsernamePasswordToken _token = (UsernamePasswordToken) token;
        //判断用户账号和密码是否正确
        SymmetricCrypto sm4 = SmUtil.sm4(SecureUtils.getSalt(_manager.getManagerName()).getBytes());
        String tokenCredentials = sm4.encryptHex(String.valueOf(_token.getPassword()));
        Object accountCredentials = getCredentials(info);
        LOGGER.debug("国密:{}", tokenCredentials);
        // 将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false
        matches = equals(tokenCredentials, accountCredentials);

ManagerLoginMD5CredentialsMatcher

        boolean matches = super.doCredentialsMatch(token, info);

从截出片段开始往下所有代码都注释掉,这样就避免了在其他系统登录校验后进入本系统还校验密码失败的情况.

上述办法比较高效,但缺点是会影响本系统的账号密码登录功能,可以自定义一个密码匹配器继承HashedCredentialsMatcher,实现方法并根据不同的业务场景来区分是密码登录还是其他系统的免密登录;

这里可以自定义登录令牌继承UsernamePasswordToken,利用自定义的令牌来区分是否是免密登录;最后在ShiroConfig中换上自定义的密码匹配器即可.

如何切换缓存类型为Redis

1.在yml配置中type改成redis

2.放开redis配置注释

3.配置自己正确的redis链接配置


4.若需要使用redis共享session,要在ShiroConfig中将部分注释的代码放开(有多处代码需改动,需注释上一行代码启用红框代码),示例如下

ShiroConfig:securityManager,defaultWebSessionManager,singleSessionControlFilter

切换缓存类型为redis

文件上传失败问题

模板上传失败或者其他文件上传失败,通常都是有文件上传配置中未声明的文件类型如js、txt等;通过提示把文件类型添加到文件上传配置的文件上传类型限制中

zip压缩时,需要选择 ‘存储‘ 方式进行压缩,否则由于压缩算法不同也可能导致上传失败。参考 上传模板

上传模板出现 invalid CEN header

上传模板时出现invalid CEN header (bad entry name)错误,请检查模板文件中是否包含中文名称的文件。如果存在此类文件,请将其删除或重命名,也可在模板上传完成后再单独上传该文件。

更新自定义配置模型的Json数据

需要更新自定义模型,有两种方式。可以直接在原项目的基础上进行修改。保存之后重新进行导入即可。如果没有原项目,可以拖一个和原先模型命名一致的表单。且标题和字段名和原模型一致。

通过绑定内外网模板,不能限制内外网访问

设置内外网模板后,并非直接绑定内外网。通过模板配置,可以生成两套不同的样式和皮肤。在不同的目录下,以内外网模板命名静态化生成两套页面。栏目绑定对应模板,通过选择文章发布到,可以区分文章生成的位置。如果需要内外网配置,需要服务器允许且配置。例如允许外网IP访问其中某一个文件夹。

Tip

内外网不能使用同一个模板,]如需使用同一个模板,也需要区分模板名称。如果未使用内网模板,可以不进行设置。

静态化页面只显示当前模板绑定的风格

只显示外网静态化操作

  1. 在自定义字典中,找到模板类别,将不需要展示风格的字典设置为不启用,保存并刷新缓存
  2. 应用设置进行保存并刷新缓存

附属栏目只显示于当前子栏目

发布文章可勾选附属栏目,文章会同时显示于附属栏目之中,并参与分页。但是附属栏目只支持选中的栏目,其父栏目之中不显示此文章。

文章发布、审核、生成静态页面的流程说明

文章发布

  • 新增角色

    创建一个用于发布文章的角色,如 信息录入员,配置信息录入员角色的权限;权限分配的原则是 该角色负责什么菜单下的功能,就分配对应菜单下的功能权限 img_5.png

  • 分配角色栏目权限

    通过超管账号,给角色分配栏目权限,限制角色只能对某些栏目下的文章进行操作 img_6.png

  • 创建管理员

    创建管理员,绑定信息录入员角色 img_7.png

  • 登录账号,发布文章

    栏目权限:这里可以看到,信息录入员的栏目权限是符合预期,只能操作军事栏目下的文章数据的;

    操作权限:必须要角色权限和角色在指定栏目下的权限同时拥有才有操作按钮

    如下图的新增,角色和角色在军事栏目下都有文章的新增权限,才会显示新增按钮;像删除只有栏目管理权限中分配了,但是实际角色没有删除权限,所以删除按钮是不会显示的;后台接口也做了同时需要满足的权限校验 img_8.png

  • 提交审核

    如果栏目设置了审核,需要把新增的文章提交审核,文章发布部分的任务就完成了 img_9.png img_10.png

  • 撤回场景

    在提交审核后,发现有错误,需要撤回(需要有权限),文章审核状态变为草稿后,再进行文章修改;提交审核后,无法对文章进行修改撤回操作会在审批日志中留痕 img_13.png

审核

需要开启审核开关,并且给具体栏目配置完整审批,在未配置审核的栏目下发布的新文章直接为终审通过状态,不会进入审批流程。可以根据栏目区分不同的审核流程,审核节点“人”只支持角色,一个节点可以有多个角色,多个角色都可以看到待办任务,只需要一个角色审核通过或拒绝

  • 审核方案节点配置

    可以按需调整节点数,但是最后一个节点必须是终审通过 img_11.png

  • 节点审核人配置

    配置时,每个审批节点至少勾选一名角色,为确保审批流程能正常走到终审节点,漏勾选的数据会被认为是不合法的。

    一个角色有连续节点审核权限,审核时会跳级;比如 信息录入员在初审节点审核时,审核通过就直接到三审; img_12.png

  • 文章审核操作

    为了方便设置了信息录入员角色为审核节点角色,这边给角色补充审核权限 img_14.png

    登录信息录入员账号,查看 内容管理 > 待审文章 img_15.png

    点击操作列的审核,可以看到录入的表单数据 img_16.png

    点击预览,可以根据栏目绑定的模板查看文章数据在模板的展示效果 img_17.png

    点击审核按钮,对文章进行审核 img_18.png

    其他节点审核按上述审核操作重复即可

门户页面展示

在开启静态化配置>自动静态化后,文章状态为终审通过后,会自动静态化,产生html文件;有审核的文章 需要通过审核后,会自动静态化;没有设置审核的文章 新增后 就会自动静态化;

img_23.png

img_19.png

Tip

栏目不支持自动静态化,新增或更新栏目不会自动静态化栏目

首页对应更新的html文件,具体html展示效果根据首页模板决定

img_20.png

列表页对应更新的html文件

img_21.png

对应生成的详情页面html文件

img_22.png

定时发布

新增文章时设置发布时间配合定时调度能够实现文章定时发布功能

Tip

注意:发布时间按天计算,只能预发布明天及以后的文章 定时调度配置每天凌晨执行静态化首页、栏目、文章

XSS过滤配置

在稳定后上线前,建议把XSS过滤配置中的拦截路径调整好,删除有关/ms/**/**此类路径。

Tip

由于自定义会有动态修改表的操作和关键字,如果上线后仍需要使用自定义相关功能,建议加上/ms/mdiy/**的配置

文件上传路径不符合预期

多次上传同一张图片时,后续的上传将直接引用第一次上传时系统产生的路径;更换图片不会影响其他地方对该图片的引用;

eg: 栏目上传一张图片,成功后保存路径为 {“url”: “/upload/1/cms/category/1686219436865.jpg”};

再在文章上传这张图片,成功保存后路径仍为{“url”: “/upload/1/cms/category/1686219436865.jpg”}

图片是否相同取决于图片的内容(二进制数据)是否一致,不取决与图片名称。

进入缓存管理异常

  1. 如果缓存管理使用的是redis,检查redis连接是否正常
  2. 如果是开启集团站群的情况,首先站群配置中关闭站群,然后缓存管理清空缓存;再开启站群,缓存管理中刷新缓存;建议开发阶段确定是否要使用站群避免此情况!

数据库版本

默认按照readme配置

如 dm数据库 默认mysql database-id指定mysql

应用设置没有模板设置

字典管理处 编辑、保存一下模板类型字典

给角色分配权限后,仍提示没有权限

  1. 审批日志, 需要审核配置 进度日志的查看权限
  2. 统计管理 管理员工作量统计 额外需要管理员的查看权限

登录显示密码最大使用天数

  1. 更换浏览器或者使用当前浏览器无痕登录
  2. 点击右上角提示信息X,重新设置密码后,再次登录

Tip

如想延迟密码最大使用天数,请在后台安全设置设置合理时间

如何合并栏目

栏目同层级情况

  1. 点击内容管理->批量移动复制菜单
  2. 选择您要合并的栏目,将数据复制到目标栏目中,再将被合并的栏目删除。

合并到父栏目情况

  1. 创建一个新的栏目,将子栏目数据复制到新的栏目中。
  2. 删除子栏目,再次将新建的栏目数据复制到父栏目中,删除新建的栏目。

启动异常

清理插件添加依赖后启动失败,报Error creating bean with name ‘cleanTableAction’,‘cleanTableBizlmpl’

解决参考: 清理插件启动失败

启动报java.nio.charset.MalformedInputException: Input length = 2

解决参考: 字符规范

启动es依赖报找不到org.elasticsearch.xcontent.ToXcontent0bject的类文件错误

解决参考: es启动

启动报错error creating bean with name “customMultipartResolver”.

解决参考: 开发启动

启动后异常org.quartz.SchedulerConfigException、org.quartz.JobPersistenceException

解决参考: 定时启动

运行时异常

es同步时出现报错 cluster_block_exception index has read-only-allow-delete block

解决参考: es同步时出现报错存储空间不足

es同步报No qualifying bean of type ‘net.mingsoft.gov.service.IESContentService’ avaliable

解决参考: es同步时出现报no bean

聚合搜索报错Text fields are not optimised for operations that require per-document field data like aggregations and sorting…

解决参考: es聚合搜索

重要声明

1、开源版本永久免费发布源代码,开发者、企业可以终身免费使用,每个月团队会收集开源系统的问题并在每两个月进行一次更新;

2、企业版本以上更新频率上更快,功能也比开源版本的要多,同时会根据不同版本提供额外插件,不同版本也会提供不同的人工在线服务(服务时间工作日 9:00-17:30)

Tip

开发版本与开源版本功能,默认集成了会员功能插件,满足一些不愿意折腾maven源码的用户使用,但开发版会在产品群里标记,提出的问题客服会优先解答

插件化

每个版本都可以在平台上直接购买下载,在购买界面可以根据需要选择不同的插件

Tip

如果选择了额外的插件,需要联系商务安排组织讨论组,由技术提供对应的插件源代码。如果要具体看插件的效果,可以联系商务提供演示账号信息

增加插件

平台下载的是基本框架代码,数据库脚本后通过群方式提供,额外选配的插件会在VIP讨论组提供,提供后按以下步骤操作

  1. 复制插件代码文件夹到工程目录

  2. 在主工程父 pom.xml 增加依赖

修改主工程pom.xml,增加新模块,名称通常就是模块的文件夹名称,例如:ms-mcms

注意这里不需要设置版本,具体依赖由技术人员描述为准

  1. 在主项目 pom.xml 增加依赖

  1. 导入菜单json

复制菜单json,打开 权限管理 ,点击菜单管理点击导入

  1. 执行sql 表结构.sql 初始化.sql
  2. 重启mcms

开发版

单项目方式提供源码(ms-base、ms-basic、ms-mdiy、ms-mpeople),赠送价值50元的会员皮肤

功能演示

集成了 发送插件、会员插件、城市数据,默认皮肤增加了基本的会员功能。采用了自定义配置、自定义页面、自定义模型等功能(自定义模块是完全抵代码的开发方式)。

开发版优势

1、单项目结构方式,提供源代码,更方便阅读源代码;

2、相对开源版本代码bug会少一些;

3、默认集成实用的插件,可以实现业务系统的开发;

4、问题优先解答权;

服务支持

1、产品群问题优先解答

2、BUG问题优先解决

推荐用户

企业

1、快速帮助企业搭建门户网站;

2、快速帮助企业实现系统的二次开发;

3、可以帮助企业是实现批量建站业务,降低企业开发成本,提高企业收益;

开发者

1、可以阅读代码相互学习,共同参与开源开发;

2、可以接私活;

2022 企业版本

整个政务版本由9个模块插件组成,平台还提供了其他模块插件可以选择。

后台界面截图

部分功能展示

多角色

管理员支持多角色

多皮肤

多皮肤可以实现例如:PC端、移动端显示效果不一致,或者中、英多语言版本

[!tip]如果修改了字典模版类型的数据,需要应用设置更新保持,栏目模版,文章的发布到进行更新同步

数据权限

根据角色设置栏目权限

内容批量移动复制

可以批量移动或复制文章到目标栏目下

基本审批

基本的审批功能,可以通过分配菜单权限来控制审批的用户

自动静态化

会自动静态化当天发布以及当天更新的文章

系统设置

比开源版本新增了缓存管理、后台开发配置、后台UI配置

缓存管理

基于ecache轻量级缓存

后台开发配置、后台UI配置

可以修改后台登陆界面UI

安全设置

企业版本比开源版本安全性更好,密码加盐的处理、XSS接口安排的配置、在线管理员不能重复登陆等

XSS过滤配置

在线管理员管理

日志管理

独立出日志管理,方便日志查看

系统日志、异常日志

日志区分可以更好的进行排错

监控日志

在线实时监控日志,省去进入系统手动查看日志,维护系统更方便

服务支持

1、在线7天集中的人工远程技术支持(实时回复);

2、独立的QQ讨论组,保留1年,除了7天实时回复,1年内有任何问题可以在群里交流;

3、提供快速开发手册一份、数据库表文档一份;

2022 政务版本

整个政务版本由14个模块插件组成,平台还提供了其他模块插件可以选择。

后台界面截图

Tip

支持等保(只负责MCms自身的等保技术支持,数据库、服务器、网络等保不提供支持,需要联系对应供应商)

部分功能展示

政务版本具备企业版本的基本功能,同时针对政府部门的特性增加更多的特色功能

内容发布到

在多皮肤的基础上增加了发布到,可以实现例如 内网数据只在内网显示、外网只在外网显示。

图片灵活裁切

文章缩略图可以任意裁切

内容回收站

内容审批


基于进度插件实现的多级审批配置

三员权限

默认集成三员管理权限控制

审计管理

安全设置

替换词


配置纠错词

纠错词需要结合百度AI配置使用

账号安全

公私钥配置

IP黑白名单

系统设置

在企业版本基础之上增加了更多的设置

一键排版设置


方便快速编辑内容

无障碍配置

公祭日配置

可以根据配置的公祭日日期页面会灰度展示

全文搜索

百度AI配置

服务支持

  1. 在线30天集中的人工远程技术支持(实时回复);
  2. 独立的微信讨论组,保留1年,除了30天实时回复,1年内有任何问题可以在群里交流;
  3. 提供快速开发手册一份、数据库表文档一份;
  4. 支持等保(只负责MCms自身的等保技术支持,数据库、服务器、网络等保不提供支持,需要联系对应供应商);

开源中国

http://git.oschina.net/需要注册帐号!

参考文献:

协同工作

关注

项目的右上角三个按钮,分别是 watch、star、fork,下面分别描述具体的功能:

fork

选择fork,相当于自己在开源中国有了一份原项目的拷贝,当然这个拷贝只是针对当时的项目文件,如果后续原项目文件发生改变,必须要进行同步才能获取到最新的代码。

如果想帮助我们完善这个项目或者单纯的想在原来项目基础上己维护一个属于自己项目,那么建议fork项目。

watch

关注这个项目的所有动态,项目以后只要发生变动,如被别人提交了pullrequest、被别人发起了issue等等情况,你都会在自己的个人通知中心,收到一条通知消息,如果你设置了个人邮箱,那么你的邮箱也可能收到相应的邮件。

star

解释为`关注`或者`点赞`,点击star,也是对开源项目的一种鼓励!

同步最新代码

点击同步按钮可以同步到最新的代码

贡献代码

先将编辑过的代码提交到自己在平台的仓库,点击“+新建 Pull Request”按钮,

注意源分支与目标分支选择,目标分支一般为铭飞项目仓库的主分支。输入说明后点击创建按钮,然后就静静等待铭飞团队 merge 邮件通知了。

GitHub

http://www.github.com 需要注册帐号

关注

和开源中国一样。项目的右上角三个按钮,分别是 watch、star、fork,具体的功能请参照开源中国的关注;

提交和同步代码

github上的代码提交和同步与开源中国有所不同。在github中,代码的提交与同步都是依赖pull request来操作的。比如现在需要提交代码的话,需要新建一个请求。将贡献的代码提交上github;

提交代码

提交代码时,将开源代码作为目标分支,将自己的代码作为源分支。然后发起申请合并。

同步代码

同步代码时,将自己的代码作为目标分支,将开源代码作为源分支。然后发起申请合并。之后,再到自己的fork代码哪里合并。就可以得到最新代码。