在使用JPress之前,你应该对数据库、服务器等有基本的认识。本文档是建立在这些基础知识之上的。
另外可以通过以下几种方式获得帮助:
- JPress官方公众号:jpressio
- JPress交流QQ群:288397536
- 开源中国进行发帖提问。
Linux :
wget https://gitee.com/fuhai/jpress/raw/master/docker-compose.yml
docker-compose up -d
Mac OS :
curl -O https://gitee.com/fuhai/jpress/raw/master/docker-compose.yml
docker-compose up -d
执行完毕以上命令之后,访问 http://127.0.0.1:8080
即可。
JPress手动安装需要以下几个步骤:
- 下载JPress源码
- 通过maven编译JPress成war包(或可执行程序)
- 启动JPress
下载JPress源码通过以下几种方式:
1、git clone
git clone https://gitee.com/fuhai/jpress.git
2、进入 Gitee 的 JPress 发行页面进行下载
链接地址: https://gitee.com/fuhai/jpress/releases
JPress可以编译成war包和可执行程序,war需要在tomcat等web容器下运行。可执行程序内置undertow,不需要其他第三方web容器,运行脚本即可通过浏览器访问jpress应用。
下载好 JPress 源码后,通过 shell 进入源码目录,执行如下 maven 命令:
mvn package
稍等片刻,待命令执行完毕之后,即可在 starter-tomcat/target
目录下生成 starter-tomcat-2.0.war
的war包,在 starter/target/
目录下生成 starter-2.0 的文件夹,starter-2.0 文件夹的目录如下:
├── config
├── lib
├── webapp
├── jpress.sh
└── jpress.bat
若编译不通过注意事项:
- maven版本建议用3.0 以上,2.x没有测试过
- java版本1.8
- maven注意添加aliyun的源,修改
maven/conf/setting.xml
文件,找到 mirrors 节点 ,修改如下:
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
拷贝starter-tomcat/target
目录下的 starter-tomcat-1.0.war
war包,放到tomcat的webapp目录下,手动解压缩。
启动tomcat(运行 tomcat/bin/startup.sh
),浏览器输入 http://127.0.0.1:8080/starter-tomcat-2.0
即可访问。
若把 tomcat/webapp/starter-tomcat-2.0
里面的文件拷贝到 tomcat/webapp/ROOT
,访问http://127.0.0.1:8080
即可。
拷贝starter/target/
的 starter-2.0
目录,放到 Linux 上。
执行 ./jpress.sh start
脚本也可以启动jpress项目(window系统下执行 ./jpress.bat
)。
JPress模板主要是由html、css、js和JPress标签组成,JPress标签的主要作用是用于读取后台数据,逻辑控制。
与此同时、JPress模板文件的文件名也是固定的,目录结构如下:
模板文件 | 描述 | 备注 |
---|---|---|
index.html | 首页模板 | |
error.html | 错误页面模板 | 当系统发生错误的时候,会自动调用此页面进行渲染,也可以扩展为 error_404.html,当发生404错误的时候优先使用此文件,同理可以扩展 error_500.html ,当系统发生500错误的时候调用此文件渲染。 |
setting.html | 后台的模板设置页面 | 当次html不存在的时候,用户进入后台的模板设置,会显示此模板不支持设置功能 |
screenshot.png | 模板缩略图 | 用于在后台的模板列表里显示的图片 |
template.properties | 模板信息描述文件 | 文件格式在下方 |
page.html | 页面模块的模板 | page.html 可以扩展为 page_aaa.html 、page_bbbb.html ,当模板扩展出 page_xxx.html 的时候,用户在后台发布页面内容的时候,就可以选择使用哪个模板样式进行渲染。例如: page_xxx.html 其中 xxx 为样式的名称。 |
article.html | 文章详情模板 | 和page模块一样,article.html 可以扩展出 article_styel1.html、article_style2.html,这样,用户在后台发布文章的时候,可以选择文章样式。(备注:用户中心投稿不能选择样式) |
artlist.html | 文章列表模板 | 和page、article一样,可以通过样式 |
artsearch.html | 文章搜索模板 | 和page、article一样,可以通过样式 |
user_login.html | 用户登录页面 | JPress已经内置了登录页面,但是,当模板下有 user_login.html 的时候,就会自动使用模板下的这个页面来渲染 |
user_register.html | 用注册页面 | 用法通 user_login.html |
备注:所有的模板文件都可以扩展出专门用于渲染手机浏览器的模板。
例如:首页的渲染模板是 index.html
,如果当前目录下有 index_h5.html
,那么,当用户通过手机访问网站的时候,JPress 会自动使用 index_h5.html
去渲染。 page 和 article、artlist 同理。
template.properties 文件配置如下
id = cn.jeanstudio.bluelight
title = BlueLight
description = BlueLight是JeanStudio工作室为JPress设计的官网模板
anthor = jeanStudio
authorWebsite = http://www.jeanstudio.cn
version = 1.0
versionCode = 1
updateUrl =
screenshot = screenshot.png
- id :模板ID,全网唯一,建议用域名+名称的命名方式
- title :模板名称
- description :模板简介
- anthor :模板作者
- authorWebsite :模板作者的官网
- version :版本(不添加默认为1.0.0)
- versionCode :版本号(只能是数字,不填写默认为1)
- updateUrl :此模板升级的url地址
- screenshot :此模板的缩略图图片(不填写默认为:screenshot.png)
有了以上这些目录结构,实际上不用任何的标签就可以成为一套模板了,只是这个模板是静态模板,不能读取后台数据。
只有通过在静态的html上,添加 JPress 标签,才能可以读取后台数据。
目前,JPress内置的模板标签如下:
1、全局标签,全局标签用全用大写显示,全局标签在任意模板页面都可以使用。
标签名称 | 数据类型 | 标签描述 |
---|---|---|
#(WEB_NAME ??) | 字符串 | 网站名称 |
#(WEB_TITLE ??) | 字符串 | 网站标题 |
#(WEB_SUBTITLE ??) | 字符串 | 网站副标题 |
#(WEB_DOMAIN ??) | 字符串 | 网站域名 |
#(WEB_COPYRIGHT ??) | 字符串 | 网站版权信息 |
#(SEO_TITLE ??) | 字符串 | 网站SEO标题 |
#(SEO_KEYWORDS ??) | 字符串 | 网站SEO关键字 |
#(SEO_DESCRIPTION ??) | 字符串 | 网站SEO描述 |
MENUS | 数据列表( list ) | 菜单数据 |
标签描述,标签建议用 #( 名称 ??)
的方式来读取数据,而不是用 #(名称)
两个问号(??)的意思是如果 后台填写的名称为空格,那么就用 两个问号(??)之后的内容来显示。
例如:
#(WEB_NAME ??)
表示优先使用 WEB_NAME 来显示,但是当 WEB_NAME 为空的时候,显示空数据(因为两个问好(??)之后的内容为空)。
#(WEB_NAME ?? WEB_TITLE)
表示优先使用 WEB_NAME 来显示,但是当 WEB_NAME 为空的时候,用 WEB_TITLE(网站标题) 来显示。
#(SEO_TITLE ?? WEB_TITLE +'-'+ WEB_SUBTITLE)
表示优先使用 SEO_TITLE(SEO标题) 来显示,但是当 SEO_TITLE 为空的时候,用 WEB_TITLE - WEB_SUBTITLE
来显示。
有了以上知识后,我们的 index.html 首页模板文件可以如下:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>#(SEO_TITLE ?? (WEB_TITLE + '-' + WEB_SUBTITLE))</title>
<meta name="keywords" content="#(SEO_KEYWORDS ??)">
<meta name="description" content="#(SEO_DESCRIPTION ??)">
</head>
<body>
这是首页....
</body>
</html>
以上内容都只是针对 字符串
这种数据类型进行展示的,全局标签中还有一种数据类型叫 数据列表( list )
,那么,怎么来显示数据列表呢?
这个时候#( 名称 ??)
就不能正常显示了,需要用到一个新的标签:
#for
...
#end
#for ... #end
标签也叫循环标签,意思是把列表循环显示出来。
对于 MENUS
这种数据类型为 数据列表( list )
的数据,#for ... #end
标签使用如下。
#for(menu : MENUS)
<li> <a href="#(menu.url ??)">#(menu.text ??)</a> </li>
#end
这样,若我们在后台创建了5个菜单,那么html会输出 5个 <li> ... </li>
对于以上的菜单显示,还有一个问题就是如何显示二级菜单呢?
代码如下:
#for(menu : MENUS)
<li> <a href="#(menu.url ??)">#(menu.text ??)</a> </li>
#if(menu.hasChild())
<div class="二级菜单的class">
#for(childMenu : menu.getChilds())
<li> <a href="#(menu.url ??)">#(menu.text ??)</a> </li>
#end
</div>
#end
#end
以上代码显示了所有菜单的的二级菜单,但是,有些时候我们想在网站的某些位置,显示 当前菜单 下的子菜单,如何做呢?
代码如下:
#for(me: MENUS)
#if(me.isActive && me.hasChild())
<h3 class="menut-title">#(me.text ??)</h3>
<ul class="inner-menut">
#for(m : me.getChilds())
<li class="#(m.isActive ? 'active' : '')">
<a href="#(CPATH)#(m.url ??)">
#(m.text ??)
</a>
</li>
#end
</ul>
#end
#end
2、数据指令,数据指令一般情况下只能用于特有页面
文章相关指令:
指令名称 | 可用页面 | 描述 |
---|---|---|
#article() | 任意 | 用于读取特定的单篇文章 |
#articles() | 任意 | 用于读取文章列表,例如:热门文章文章、最新评论文章列表等等 |
#tagArticles() | 任意 | 读取某个tag下的文章列表 |
#categoryArticles() | 任意 | 读取某个分类下的文章列表 |
#articlePage() | 任意 | 用于对文章列表进行的内容和分页进行显示 |
#commentPage() | 文章详情:article.html | 用于对文章评论的内容和分页进行显示 |
#nextArticle() | 文章详情:article.html | 下一篇文章 |
#previousArticle() | 文章详情:article.html | 上一篇文章 |
#relevantArticles() | 文章详情:article.html | 相关文章列表,相同标签的的文章 |
#categories() | 任意 | 读取文章模块的所有分类 |
#tags() | 任意 | 用于读取文章标签 |
#articleCategories() | 任意 | 用于读取某一篇文章的所属分类,例如:文章的标签、文章的分类等 |
#articleSearchPage() | 文章搜索结果页 artsearch.html | 用于渲染搜索结果 |
产品相关指令:
指令名称 | 可用页面 | 描述 |
---|---|---|
#products() | 任意 | 获取产品的列表 |
#categoryProducts() | 任意 | 用根据分类获取产品内容 |
#nextProduct() | 产品详情:product.htm | 下一个产品 |
#previousProduct() | 产品详情:product.htm | 上一个产品 |
#productCategories() | 任意 | 获取某个产品对应的分类或者标签 |
#productCategoryList() | 任意 | 产品分类列表 |
#productCommentPage() | 产品详情:product.htm | 产品评论分页 |
#product() | 任意 | 根据产品id或者slug读取产品信息 |
#productPage() | 任意 | 产品分页 |
#productSearchPage() | 任意 | 产品搜索分页 |
#productTags() | 任意 | 产品标签 |
#relevantProducts() | 任意 | 某个产品的相关产品(相同tag的产品) |
页面相关指令:
指令名称 | 可用页面 | 描述 |
---|---|---|
#page() | 任意 | 用于读取某个页面 |
#pages() | 任意 | 用于读取页面列表 |
用户相关指令:
指令名称 | 可用页面 | 描述 |
---|---|---|
#users() | 暂不支持 | 用于读取页面列表 |
此指令是用来读取一篇文章,网站的任意页面进行展示。
#article()
<a href="#(article.url)">#(article.title)</a>
<div>#(article.content)</div>
#end
此指令是在任何页面,用来读取文章列表。例如:最新文章、热门文章等
#articles(flag="",hasThumbnail="",orderBy="",count=10)
#for(article : articles)
<a href="#(article.url)">#(article.title)</a>
#end
#end
#articles() 指令支持的参数有:
- flag:文章标识,这个是在编辑文章的时候自由填写。
- hasThumbnail:是否需要缩略图,值为 true 和 false。
- orderBy :根据什么进行排序,目前支持的值有:order_number(用户自定义排序)、comment_count(文章的评论数量)、comment_time(文章的评论时间)、view_count(文章的访问量)、created(文章的创建时间)、modified(文章的修改时间)
- count :要显示多少篇文章
- style :文章样式
此指令是在任何页面,用来读取文章列表。例如:最新文章、热门文章等
#tagArticles(tag="aaa",hasThumbnail="",orderBy="",count=10)
#for(article : articles)
<a href="#(article.url)">#(article.title)</a>
#end
#end
#tagArticles() 指令支持的参数有:
- tag:哪个tag。
- hasThumbnail:是否需要缩略图,值为 true 和 false。
- orderBy :根据什么进行排序,目前支持的值有:order_number(用户自定义排序)、comment_count(文章的评论数量)、comment_time(文章的评论时间)、view_count(文章的访问量)、created(文章的创建时间)、modified(文章的修改时间)
- count :要显示多少篇文章
此指令是在任何页面,用来读取文章列表。例如:最新文章、热门文章等
#categoryArticles(categoryFlag="",hasThumbnail="",orderBy="",count="")
#for(article : articles)
<a href="#(article.url)">#(article.title)</a>
#end
#end
#categoryArticles() 指令支持的参数有:
- categoryFlag:分类的标识。
- hasThumbnail:是否需要缩略图,值为 true 和 false。
- orderBy :根据什么进行排序,目前支持的值有:order_number(用户自定义排序)、comment_count(文章的评论数量)、comment_time(文章的评论时间)、view_count(文章的访问量)、created(文章的创建时间)、modified(文章的修改时间)
- count :要显示多少篇文章
指令 #articlePage() 只能用在文章列表页,也就是 artlist.html 模板文件及其扩展文件。
#articlePage()
#for(article : articlePage.list)
<a href="#(article.url ??)">
文章标题是:#(article.title ??)
</a>
<div>
文章内容是:#maxLength(article.text,100)
</div>
#end
#articlePaginate()
#for(page : pages)
<a class="page-link" href="#(page.url ??)">
#(page.text ??)
</a>
#end
#end
#end
说明 指令 #articlePage() 内部又包含了另一个指令 #articlePaginate(),#articlePaginate()是用于显示上一页和下一下。
指令 #articlePage() 的参数有:
- pageSize :可以用来指定当前页面有多少条数据,默认值是:10。也就是说
#articlePage()
等同于#articlePage(pageSize=10)
分页指令#articlePaginate()的参数有
- previousClass :上一页的样式,默认值:previous
- nextClass :下一页的样式,默认值:next
- activeClass :当前页面的样式,默认值:active
- disabledClass :禁用的样式(当下一页没有数据的时候,会使用此样式),默认值:disabled
- anchor :锚点链接
- onlyShowPreviousAndNext :是否只显示上一页和下一页(默认值为false,一般情况下在手机端才会把这个值设置true)
- previousText :上一页按钮的文本内容,默认值:上一页
- nextText :下一页按钮的文本内容,默认值:下一页
- firstGotoIndex : 是否让第一页进入首页,默认值:false
指令 #commentPage() 只能用在文章详情页,也就是 article.html 模板文件及其扩展文件。用于读取这篇文章的相关评论信息以及评论的分页功能。
#commentPage()
#for(comment : commentPage.list)
<div>评论内容是:#(comment.content ??)</div>
<div>评论作者是:#(comment.authro ??)</div>
#end
#commentPaginate()
#for(page : pages)
<a class="page-link" href="#(page.url ??)">
#(page.text ??)
</a>
#end
#end
#end
说明 和一样#articlePage(),#commentPage()指令 内部又包含了另一个指令 #commentPaginate(),#commentPaginate()是用于显示评论的上一页和下一下。
指令 #commentPage() 的参数有:
- pageSize :可以用来指定当前文章详情,每页的评论条数是多少,默认值是:10。也就是说
#commentPage()
等同于#commentPage(pageSize=10)
分页指令#commentPaginate()的参数有
- previousClass :上一页的样式,默认值:previous
- nextClass :下一页的样式,默认值:next
- activeClass :当前页面的样式,默认值:active
- disabledClass :禁用的样式(当下一页没有数据的时候,会使用此样式),默认值:disabled
- anchor :锚点链接
- onlyShowPreviousAndNext :是否只显示上一页和下一页(默认值为false,一般情况下在手机端才会把这个值设置true)
- previousText :上一页按钮的文本内容,默认值:上一页
- nextText :下一页按钮的文本内容,默认值:下一页
指令 #nextArticle() 只能用于文章详情页,用于显示下一篇文章的相关信息或者内容。
#nextArticle()
<a href="#(next.url)">标题是:#(next.title ??)</a>
#end
指令 #previousArticle() 只能用于文章详情页,用于显示上一篇文章的相关信息或者内容。
#previousArticle()
<a href="#(previous.url)">标题是:#(previous.title ??)</a>
#end
指令 #categories() 可以在任意页面使用,用来读取分类的内容。
#categories()
#for(category : categories)
<a href="#(category.url ??)">#(category.title ??)</a>
#end
#end
指令#categories()的参数有
- flag :读取哪些flag的分类列表。
- parentFlag :读取父级必须是该flag的分类列表。
- asTree :是否以树状的数据进行返回,默认是false,返回全部分类。
指令 #articleCategories() 是用于读取某一篇文章的分类、或tag标签。
如下代码是用于读取文章的分类:
#articleCategories(article.id,"category")
#for(category : categories)
<a href="#(category.url ??)">#(category.title ??)</a>
#end
#end
如下代码是用于读取谋一篇文章的标签(一般用在文章列表循环里):
#articleCategories(article.id,"tag")
#for(category : categories)
<a href="#(category.url ??)">#(category.title ??)</a>
#end
#end
指令#articleCategories()的参数有
- articleCategories的使用必须传入两个值,顺序不能相反。第一个是文章的id,第二个是指定要获取文章分类的类型。
#tags()
#for(tag : tags)
<li><a href="#(tag.url)">#(tag.title)</a></li>
#end
#end
指令#tags() 的参数有
- orderBy : 排序方式
- count : 数量
指令 #page() 可以用于任何页面,用于读取页面内容。
代码如下:
#page(slug=“about”)
这个页面的标题是:#(page.title ??)
这个页面的内容是:#(page.content ??)
#end
指令#page()的参数有
- slug :page的唯一标识,在后台编辑页面的时候填写,如下图:
指令 #pages() 可以用于任何页面,用于读取页面内容列表。
代码如下:
#pages()
#for(page : pages)
这个页面的标题是:#(page.title ??)
这个页面的内容是:#(page.content ??)
#end
#end
指令#pages()的参数有
- flag :页面标志,可以在后台编辑页面的时候填写。通过flag,可以把几个页面归属到同一个flag,做到页面的“归类”作用。
暂未完成
JPress 是一个内置了几乎任何互联网系统都必须模块:
- 用户管理
- 权限管理
- 文章功能
- 页面功能
- 微信公众号对接
- 微信小程序
- API接口
- 安全机制
- ...
目前、市面是几乎任何的互联网系统都应该具备以上几个功能,所以,通过JPress来做二次开发,是非常明智的选择。
另外,是基于JFinal 和 Jboot进行开发的,JFinal 连续在开源中国(oschina.net)获得了多年的 “最受欢迎的中国开源软件” 称号,Jboot 是基于JFinal的一个微服务框架,在1亿+用户量的商业产品得到了验证,稳定和安全。
通过 JPress 进行二次开发,也是非常容易的。
JPress 架构图
在开始 JPress 二次开发之前,有必要了解下JPress的目录结构:
目录 | 描述 | 备注 |
---|---|---|
codegen | 代码生成器 | 开发的时候用与生成maven模块代码,运行时用不到该模块 |
doc | 文档存放目录 | |
jpress-commons | 工具类和公用代码 | |
jpress-core | JPress的核心代码 | |
jpress-model | JPress非业务实体类 | |
jpress-service-api | JPress非业务 service 接口定义 | |
jpress-service-provider | JPress非业务 service 接口实现 | |
jpress-template | JPress的html模板 | |
jpress-web | JPress非业务的web处理代码 | 包含了 Controller、指令等 |
module-article | 文章模块代码 | |
module-page | 页面模块代码 | |
starter | undertow启动模块,开发的时候可以运行里面的 DevStarter.java 的main方法 | 编译的时候会 jpress 可执行程序 |
starter-tomcat | tomcat 启动模块 | 编译的时候回生成 war 包,用于放在tomcat部署 |
如果使用JPress来开发一个自己的程序呢 ?我们假设要使用 jpress 来开发一个小型的论坛。
主要有以下几个步骤:
- 1、需求分析和建库建表
- 2、通过 JPress 直接生成 maven 模块和相关基础代码
- 3、通过 实现 ModuleListener 配置模块基本信息
- 4、通过 注解 @AdminMenu 和 @UcenterMenu 配置后台和用户中心菜单
- 5、编码实现模块基本逻辑
- 6、修改maven配置并运行
1、需求分析和建库建表
我们假设论坛有三个表、分表时论坛版块、帖子和帖子回复,因为jpress已经有用户表了,所以不再需要用户表。
为了能够讲清楚如何使用jpress进行二次开发,我们故意把论坛的版块功能给简化了,论坛版块暂时不支持子版块功能。
表名分别为:club_category
、club_post
、club_post_comment
。
2、通过 JPress 直接生成 maven 模块和相关基础代码
我们在 jpress 项目的 starter 模块下,建立一个新的代码生成器,用于对社区模块的代码生成。
代码生成器如下:
public class PageModuleGenerator {
private static String dbUrl = "jdbc:mysql://127.0.0.1:3306/newjpress";
private static String dbUser = "root";
private static String dbPassword = "123456";
private static String moduleName = "club";
private static String dbTables = "club_category,club_post,club_post_comment";
private static String modelPackage = "io.jpress.module.club.model";
private static String servicePackage = "io.jpress.module.club.service";
public static void main(String[] args) {
ModuleGenerator moduleGenerator = new ModuleGenerator(moduleName, dbUrl, dbUser, dbPassword, dbTables, modelPackage, servicePackage);
moduleGenerator.gen();
}
}
执行完 main()
方法后,会在当前目录下生产一个叫 club 的新的maven模块。
3、通过 实现 ModuleListener 配置模块基本信息
我们自动生成的 module-club-web
模块里,建立一个 叫 ClubModuleListener
的类,实现ModuleListener
接口。
代码如下:
public class ClubModuleListener implements ModuleListener {
@Override
public String onRenderDashboardBox(Controller controller) {
//在这里配置后台首页的相关模块
//代码可以参考 ArticleModuleLisenter
return null;
}
@Override
public void onConfigAdminMenu(List<MenuGroup> adminMenus) {
//这里配置后台菜单
//代码参考 ArticleModuleLisenter
}
@Override
public void onConfigUcenterMenu(List<MenuGroup> ucenterMenus) {
//这里配置用户中心菜单
//代码参考 ArticleModuleLisenter
}
}
以上提到的ArticleModuleLisenter
代码在: https://gitee.com/fuhai/jpress/blob/master/module-article/module-article-web/src/main/java/io/jpress/module/article/ArticleModuleLisenter.java
4、通过 @AdminMenu 和 @UcenterMenu 配置后台和用户中心菜单
我们来为club这个模块添加一个新的后台菜单,在这个之前,我们先来建立一个共识:
- 1、菜单,肯定是一个可以访问的url地址。
- 2、既然是可以访问的url地址,那么这个url地址肯定会对应某个Controller的某个方法。
因此,某个菜单,其实就是Controller的某个方法。
对于这个论坛系统,我们希望后台菜单显示如下:
论坛管理
>>> 帖子列表
>>> 回帖管理
>>> 版块管理
那么,我们需要建立一个叫 _ClubController
的类(名字任意取,后台相关的Controller,建议用下划线(_)开头,这样可以和 jpress 统一。)
代码如下:
@RequestMapping("/admin/club")
public class _ClubController extends AdminControllerBase {
@AdminMenu(text = "帖子列表", groupId = "club")
public void index() {
render("club/post_list.html");
}
@AdminMenu(text = "回帖管理", groupId = "club")
public void index() {
render("club/post_comment_list.html");
}
@AdminMenu(text = "版块管理", groupId = "club")
public void index() {
render("club/category_list.html");
}
}
注意:
@AdminMenu
里的groupId
的值必须是ClubModuleListener
里配置的id。如果我们把groupId
修改为groupId = "page"
,那么此菜单将会被添加到后台的页页面管理
这个菜单下面。_ClubController
必须继承至AdminControllerBase
。@RequestMapping("/admin/club")
里的值必须是/admin/
开头。
5、编码实现模块基本逻辑
暂略
6、修改maven配置并运行
默认情况下,此时通过 mvn clean install
进行进行编译,会出现maven编译错误。原因是代码生成器虽然生成了 club 模块,但是 club 模块依赖 parent
, module-club
下的 pom 文件如下:
<parent>
<groupId>io.jpress</groupId>
<artifactId>parent</artifactId>
<version>3.0</version>
</parent>
maven 会去自动下载 io.jpress.parent 这个module,maven中央仓库上找不到这个 module 就会出现 maven编译错误。
因此,需要修改 jpress 根目录的 pom.xml ,添加把 module-club
添加到 pom.xml 里去,让 module-club
成为 jpress 的子模块。
如下代码:
<modules>
<!--.....略 .... -->
<module>module-article</module>
<module>module-page</module>
<!-- 添加这一行代码-->
<module>module-club</module>
<!--.....略 .... -->
</modules>
此时,如果直接运行starter模块下的main方法,也不到任何效果,原因是:
- 1、starter 这个模块并未依赖你生成的 club 模块
- 2、starter 里的resource并没有 club 模块的资源、比如html、css等。
所以,接下来我们需要修改 starter 的 pom.xml
文件,添加如下依赖。
<dependency>
<groupId>io.jpress</groupId>
<artifactId>module-club-web</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>io.jpress</groupId>
<artifactId>module-club-service-provider</artifactId>
<version>3.0</version>
</dependency>
同时,为了让 maven 编译的时候,把 club
中的资源拷贝到 starter 里阿里。我们需要修改starter模块下的 pom.xml 的maven-resources-plugin
插件配置为如下:
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes/webapp</outputDirectory>
<resources>
<resource>
<directory>${basedir}/../jpress-web/src/main/webapp</directory>
</resource>
<resource>
<directory>${basedir}/../jpress-template/src/main/webapp</directory>
</resource>
<resource>
<directory>${basedir}/../module-page/module-page-web/src/main/webapp</directory>
</resource>
<resource>
<directory>${basedir}/../module-article/module-article-web/src/main/webapp</directory>
</resource>
<!-- 添加如下代码-->
<resource>
<directory>${basedir}/../module-club/module-club-web/src/main/webapp</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
此时,进行 mvn clean install
完毕之后,就可以正常运行了。
注意:starter-tomcat
模块也需要做如此配置。
在 jpress 里,做微信开发非常简单,直接看代码:
public class HelloWechatAddon implements WechatAddon {
@Override
public boolean onMatchingMessage(InMsg inMsg, MsgController msgController) {
return false;
}
@Override
public boolean onRenderMessage(InMsg inMsg, MsgController msgController) {
return true;
}
}
说明:
-
1、在任意maven module下,编写任意名称的类,实现WechatAddon接口。JPress 会自动扫描到该类,并添加到 JPress 的管理体系里去。
-
2、复写方法
onMatchingMessage
和onRenderMessage
。- onMatchingMessage :用来匹配是否是本插件要处理的消息
- onRenderMessage :用来返回给微信客户端一个消息
-
3、添加
@WechatAddonConfig
注解的配置,用来给这个插件添加描述。
以下代码是完整的 hello world 例子,当用户在微信客客户端给公众号输入 hello
的时候,服务器给微信返回 world
字符串:
@WechatAddonConfig(
id = "ip.press.helloaddon", //这个插件的ID
title = "Hello World",//这个插件的标题,用于在后台显示
description = "这是一个 Hello World 微信插件,方便开发参考。用户输入 hello,返回 world", //这个插件的描述
author = "海哥" //这个插件的作者
)
public class HelloWechatAddon implements WechatAddon {
@Override
public boolean onMatchingMessage(InMsg inMsg, MsgController msgController) {
//当用户给公众号发送的不是文本消息的时候
//返回 false 不由本插件处理
if (!(inMsg instanceof InTextMsg)) {
return false;
}
InTextMsg inTextMsg = (InTextMsg) inMsg;
String content = inTextMsg.getContent();
//当用户输入的内容不是 hello 的时候
//返回false,不由本插件处理
return content != null && content.equalsIgnoreCase("hello");
}
@Override
public boolean onRenderMessage(InMsg inMsg, MsgController msgController) {
//创建一个新的文本消息
//通过 msgController 进行渲染返回给用户
OutTextMsg outTextMsg = new OutTextMsg(inMsg);
outTextMsg.setContent("world");
msgController.render(outTextMsg);
//返回 true,表示本插件已经成功处理该消息
//若返回false,表示本插件处理消息失败,将会交给系统或者其他插件去处理
return true;
}
}
- 访问路径:/api/option
- 参数信息
参数 | 描述 | 备注 |
---|---|---|
key | 获取哪些key | 可以传入多个key,多个key用英文逗号隔开 |
例如:
http://127.0.0.1:8080/api/option?key=key1,key2
返回数据:
{
state : "ok",
data : {
key1: "data1",
key2: "data2"
}
}
通过微信小程序创建用户需要以下几个步骤。
第一步:小程序端调用 wx.login() 之后获得 code 第二步:得到code之后,调用 jpress 的code2session接口
- 访问路径:/api/wechat/mp/code2session
- 参数信息
参数 | 描述 | 备注 |
---|---|---|
code | 通过 wx.login() 获得的code |
服务器相应内容如下:
{
state : "ok",
sessionId : "session_id_data"
}
第三步:小程序调用wx.getUserInfo() 得到加密的用户数据 第四步:得到加密数据后,调用 jpress 的解密接口,得到用户ID
- 访问路径:/api/wechat/mp/decryptUserInfo
- 参数信息
参数 | 描述 | 备注 |
---|---|---|
rawData | 通过 wx.getUserInfo() 获得的 | |
signature | 通过 wx.getUserInfo() 获得的 | |
encryptedData | 通过 wx.getUserInfo() 获得的 | |
iv | 通过 wx.getUserInfo() 获得的 | |
sessionId | 通过第二步调用code2session接口获得的 |
服务器相应内容如下:
{
state : "ok",
token : "token_data"
}
token 非常重要,是用户的唯一标识。其数据是通过jwt进行加密得到的,客户端也可以通过jwt解密后得到原始的 userId。
小程序端的api,涉及到任何和用户相关的操作,都必须传此token进行用户验证。
备注:/api/wechat/mp/decryptUserInfo
接口不仅仅是对用户数据进行解密,解密成功之后,会把数据存入数据库,得到的用户ID返回给客户端。
- 访问路径:/api/article
- 参数信息
参数 | 描述 | 备注 |
---|---|---|
id | 通过ID获取文章内容 | |
slug | 通过slug获取文章内容 |
例如:
http://127.0.0.1:8080/api/article?id=123
返回数据:
{
"state": "ok",
"article": {
"commentCount": 9,
"commentEnable": true,
"commentStatus": true,
"content": "文章内容\n",
"created": "2018-09-25 14:58:09",
"draft": false,
"editMode": "html",
"htmlView": "article.html",
"id": 20,
"modified": "2018-10-07 11:17:12",
"normal": true,
"orderNumber": 0,
"originalContent": "<p>文章原始内容(这部分可能是markdown)\n",
"slug": "slug",
"status": "normal",
"text": "文本没人....",
"thumbnail": "/attachment/20181001/1a0fc4fa8e6e4fa8b11de48a362b92d2.png",
"title": "文章标题",
"trash": false,
"url": "/article/slug.html",
"userId": 1,
"viewCount": 328
}
}
- 访问路径:/api/article/category
- 参数信息
参数 | 描述 | 备注 |
---|---|---|
id | 通过ID获取类别 | |
slug | 通过slug获取类别 | |
type | 类别的类型 | 支持的参数有:category , tag |
例如:
http://127.0.0.1:8080/api/article/category?id=100
返回数据:
{
"state": "ok",
"category": {
"count": 0,
"htmlView": "artlist.html",
"id": 10,
"layerNumber": 0,
"layerString": "",
"modified": "2018-10-04 14:30:58",
"parentId": 0,
"pid": 0,
"slug": "keji",
"tag": false,
"title": "科技",
"top": true,
"type": "category",
"url": "/article/category/keji.html"
}
}
微信公众号需要配置几个地方
一、微信后台 -> 公众号设置 -> 功能设置
务必要保证以下三个填写了正确的域名:
- 业务域名
- JS接口安全域名
- 网页授权域名
如下图所示:
二、微信后台 -> 开发 -> 基本设置 -> 服务器配置
务必要保证以下三个地方填写正确:
- URL:填写你的域名+'/wechat/msg' (这里非常重要)
- token:要保证和在你JPress后台填写的完全一致
- 消息加解密方式:这里选择明文模式
如下图所示:
三、微信后台 -> 开发 -> 基本设置 -> IP白名单
如下图所示,点击查看后,填写上 JPress 部署服务器的IP地址。