Browse Source

first commit

master
wy 2 years ago
commit
cdfb3f0132
  1. 2
      .gitignore
  2. 0
      README.md
  3. 25
      doc/___极速打包部署.txt
  4. 439
      doc/__jfinal-club-changelog.txt
  5. 57
      doc/fatjar-打包部署方法/fatjar-打包部署方法.txt
  6. 278
      doc/fatjar-打包部署方法/pom.xml
  7. 1027
      doc/jfinal-club-init.sql
  8. 50
      doc/jfinal-club启动必读.txt
  9. 75
      doc/undertow-config-demo.txt
  10. 58
      doc/后台管理必读.txt
  11. 24
      doc/版权声明.txt
  12. 69
      jfinal.bat
  13. 82
      jfinal.sh
  14. 87
      package.xml
  15. 276
      pom.xml
  16. 189
      src/main/java/com/bt/_admin/account/AccountAdminController.java
  17. 165
      src/main/java/com/bt/_admin/account/AccountAdminService.java
  18. 67
      src/main/java/com/bt/_admin/account/AccountUpdateValidator.java
  19. 68
      src/main/java/com/bt/_admin/auth/AdminAuthInterceptor.java
  20. 87
      src/main/java/com/bt/_admin/auth/AdminAuthKit.java
  21. 62
      src/main/java/com/bt/_admin/auth/AdminAuthService.java
  22. 34
      src/main/java/com/bt/_admin/auth/admin_auth.sql
  23. 39
      src/main/java/com/bt/_admin/common/PjaxInterceptor.java
  24. 103
      src/main/java/com/bt/_admin/document/DocumentAdminController.java
  25. 121
      src/main/java/com/bt/_admin/document/DocumentAdminService.java
  26. 168
      src/main/java/com/bt/_admin/feedback/FeedbackAdminController.java
  27. 121
      src/main/java/com/bt/_admin/feedback/FeedbackAdminService.java
  28. 42
      src/main/java/com/bt/_admin/index/IndexAdminController.java
  29. 62
      src/main/java/com/bt/_admin/index/IndexAdminService.java
  30. 66
      src/main/java/com/bt/_admin/permission/PermissionAdminController.java
  31. 177
      src/main/java/com/bt/_admin/permission/PermissionAdminService.java
  32. 72
      src/main/java/com/bt/_admin/permission/PermissionDirective.java
  33. 32
      src/main/java/com/bt/_admin/permission/Remark.java
  34. 10
      src/main/java/com/bt/_admin/permission/新添加的表.txt
  35. 119
      src/main/java/com/bt/_admin/project/ProjectAdminController.java
  36. 115
      src/main/java/com/bt/_admin/project/ProjectAdminService.java
  37. 112
      src/main/java/com/bt/_admin/role/RoleAdminController.java
  38. 172
      src/main/java/com/bt/_admin/role/RoleAdminService.java
  39. 39
      src/main/java/com/bt/_admin/role/RoleAdminValidator.java
  40. 72
      src/main/java/com/bt/_admin/role/RoleDirective.java
  41. 165
      src/main/java/com/bt/_admin/share/ShareAdminController.java
  42. 133
      src/main/java/com/bt/_admin/share/ShareAdminService.java
  43. 58
      src/main/java/com/bt/_admin/后台管理必读.txt
  44. 243
      src/main/java/com/bt/common/JFinalClubConfig.java
  45. 26
      src/main/java/com/bt/common/_all_sqls.sql
  46. 157
      src/main/java/com/bt/common/account/AccountService.java
  47. 115
      src/main/java/com/bt/common/authcode/AuthCodeService.java
  48. 96
      src/main/java/com/bt/common/controller/BaseController.java
  49. 95
      src/main/java/com/bt/common/directive/MyDateDirective.java
  50. 80
      src/main/java/com/bt/common/handler/UrlSeoHandler.java
  51. 33
      src/main/java/com/bt/common/interceptor/AuthCacheClearInterceptor.java
  52. 72
      src/main/java/com/bt/common/interceptor/BaseSeoInterceptor.java
  53. 43
      src/main/java/com/bt/common/interceptor/FrontAuthInterceptor.java
  54. 76
      src/main/java/com/bt/common/interceptor/LoginSessionInterceptor.java
  55. 69
      src/main/java/com/bt/common/kit/DruidKit.java
  56. 135
      src/main/java/com/bt/common/kit/EmailKit.java
  57. 265
      src/main/java/com/bt/common/kit/ImageKit.java
  58. 86
      src/main/java/com/bt/common/kit/IpKit.java
  59. 56
      src/main/java/com/bt/common/kit/SensitiveWordsKit.java
  60. 41
      src/main/java/com/bt/common/kit/SqlKit.java
  61. 25
      src/main/java/com/bt/common/loginlog/LoginLog.java
  62. 32
      src/main/java/com/bt/common/loginlog/LoginLogService.java
  63. 55
      src/main/java/com/bt/common/model/Account.java
  64. 88
      src/main/java/com/bt/common/model/AuthCode.java
  65. 36
      src/main/java/com/bt/common/model/Document.java
  66. 25
      src/main/java/com/bt/common/model/Download.java
  67. 25
      src/main/java/com/bt/common/model/DownloadLog.java
  68. 78
      src/main/java/com/bt/common/model/Favorite.java
  69. 37
      src/main/java/com/bt/common/model/Feedback.java
  70. 40
      src/main/java/com/bt/common/model/FeedbackReply.java
  71. 38
      src/main/java/com/bt/common/model/Message.java
  72. 65
      src/main/java/com/bt/common/model/NewsFeed.java
  73. 11
      src/main/java/com/bt/common/model/Permission.java
  74. 37
      src/main/java/com/bt/common/model/Project.java
  75. 30
      src/main/java/com/bt/common/model/ReferMe.java
  76. 31
      src/main/java/com/bt/common/model/Remind.java
  77. 11
      src/main/java/com/bt/common/model/Role.java
  78. 44
      src/main/java/com/bt/common/model/Session.java
  79. 37
      src/main/java/com/bt/common/model/Share.java
  80. 40
      src/main/java/com/bt/common/model/ShareReply.java
  81. 25
      src/main/java/com/bt/common/model/TaskList.java
  82. 98
      src/main/java/com/bt/common/model/_Generator.java
  83. 41
      src/main/java/com/bt/common/model/_MappingKit.java
  84. 98
      src/main/java/com/bt/common/model/base/BaseAccount.java
  85. 44
      src/main/java/com/bt/common/model/base/BaseAuthCode.java
  86. 80
      src/main/java/com/bt/common/model/base/BaseDocument.java
  87. 96
      src/main/java/com/bt/common/model/base/BaseDownload.java
  88. 52
      src/main/java/com/bt/common/model/base/BaseDownloadLog.java
  89. 70
      src/main/java/com/bt/common/model/base/BaseFavorite.java
  90. 92
      src/main/java/com/bt/common/model/base/BaseFeedback.java
  91. 60
      src/main/java/com/bt/common/model/base/BaseFeedbackReply.java
  92. 112
      src/main/java/com/bt/common/model/base/BaseMessage.java
  93. 98
      src/main/java/com/bt/common/model/base/BaseNewsFeed.java
  94. 44
      src/main/java/com/bt/common/model/base/BasePermission.java
  95. 92
      src/main/java/com/bt/common/model/base/BaseProject.java
  96. 70
      src/main/java/com/bt/common/model/base/BaseReferMe.java
  97. 68
      src/main/java/com/bt/common/model/base/BaseRemind.java
  98. 36
      src/main/java/com/bt/common/model/base/BaseRole.java
  99. 36
      src/main/java/com/bt/common/model/base/BaseSession.java
  100. 92
      src/main/java/com/bt/common/model/base/BaseShare.java

2
.gitignore

@ -0,0 +1,2 @@
/log/
/target/

0
README.md

25
doc/___极速打包部署.txt

@ -0,0 +1,25 @@
1:命令行进入项目根目录,然后运行 mvn clean package 即可打包
2:打包完后,进入 jfinal-club/target/jfinal-club-release/jfinal-club
目录,windows 下双击 start.bat 启动项目, linux 下运行 start.sh 脚本启动项目,
运行 stop.sh 关闭项目
注意 jfinal-club/target 目录下面还会有一个 jfinal-club-release.zip 文件
该文件等价于对 /target/jfinal-club-release/jfinal-club 目录进行的压缩
方便上传到服务器上解压即部署,可通过删除 package.xml 中的 <format>zip</format>
项避免打出该 zip 包,具体用法在 package.xml 中有说明
3:start.sh 脚本中提供了详细的说明,根据说明可选择不同的运行模式
说明:打包部署只需在 pom.xml 中配置 maven-assembly-plugin 这一个插件,该插件用到了
package.xml 中的配置,整个过程已被简化到极致
start.sh、stop.sh、restart.sh、start.bat 这四个运行脚本在用于别的项目时,只需要修改一下
MAIN_CLASS 变量指向实际的启动入口类即可,例如在 jfinal-club 中的配置如下:
MAIN_CLASS=com.jfinal.club.common.JFinalClubConfig
俱乐部 2018-12-16 的直播视频中详细演示了 jfinal club 的打包与部署过程,还演示了 fatjar 的打包,
以及 HTTPS/SSL 的使用方法,有需要的同学可以在俱乐部 QQ 群通知里面找到下载链接

439
doc/__jfinal-club-changelog.txt

@ -0,0 +1,439 @@
jfinal club 4.9.08 changes:
1:jfinal 升级到 4.9.08
2:jfinal undertow 升级到 2.5
------------------------------------------
jfinal club 4.9.06 changes:
1:jfinal 升级到 4.9.06
2:jfinal undertow 升级到 2.4
3:commons-email 升级到 1.5
4:JFinalClubConfig 中开启路由扫描功能
5:删除 FrontRoutes、AdminRoutes,路由注册已被路由扫描功能所取代
6:EmailKit 改为走 ssl 通道 465 端口,阿里云、腾迅云、华为云都屏蔽了 25 端口
------------------------------------------
jfinal club 4.9.02 changes:
1:jfinal 升级到 4.9.02
2:jfinal undertow 升级到 2.2
3:fastjson 升级到 1.2.73
4:JsoupFilter 加强对于 CSRF 攻击的防备
5:删掉 TimeKit,该类已被添加到 jfinal 4.9.02 中
6:LoginService 删掉 loginAccount.put("sessionId", sessionId)
7:AccountAdminService 限定对超级管理员的操作
------------------------------------------
jfinal club 4.9.01 changes:
1:jfinal 升级到最新版本 4.9.01
2:fastjson 升到 1.2.72
3:sql 模板文件与 java 源代码放在相同目录,需在 pom.xml 中添加 <resources> 配置
4:由于 sql 模板将被打入 jar 包,所以 _all_sqls.sql 中的 #include 改为绝对路径
5:TimeKit.parseDate(...) 方法改为 parse(...),因为这个更常使用且对 dataPattern 兼容更好,
原方法名改为 parseLocalDateTime(...)
6:删除 MyRedirectRender、MyRedirect301Render、MyRenderFactory,jfinal 4.9.01 已不再需要这个扩展
7:删除 JFinalClubConfig 中的 me.setRenderFactory(new MyRenderFactory()) 配置,原因同上
------------------------------------------
jfinal club 4.9 相对 4.8 的变化:
1:jfinal 升级到最新版本 4.9
2:jfinal undertow 升级到最新版本 2.1
3:文件上传组件 cos 升级到最新版本 2020.4
4:优化 css 样式
5:新增 TimeKit 工具类
6:新增 MyRenderFactory、MyRedirectRender、MyRedirect301Render 用于解决
nginx 代理 https 时重定向到了 http 的问题,需要在 nginx 中添加如下配置:
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
7:fastjson 升到 1.2.70
------------------------------------------
jfinal club 4.8 相对 4.2 的变化:
1:jfinal 升级到最新版本 4.8
2:jfinal undertow 升级到最新版本 2.0
3:文件上传组件 cos 升级到最新版本 2019.8
4:log4j 升级到 1.2.17
5:首页添加轮播广告组件
6:package.xml 中 fileSet 标签添加 lineEnding 子标签,解决不同平台下脚本复制换行的问题
7:分页向前、向后按钮改为大于小于字符
8:MyFeedbackService、MyProjectSevice、MyShareService 的 update() 方法添加
remove("createAt"),避免 mass assignment 修改 createAt 值
9:fatjar 打包的 pom.xml 中的 shade 插件添加 filter,过滤掉 *.SF、*.DSA、*.RAS
------------------------------------------
jfinal club 4.2 相对 4.1 的变化:
1:jfinal 升级到 4.2
2:根据用户对 jfinal.com UI 的反馈,改进 html、css、js 细节,提升用户体验
3:首页、分享、反馈、动态列表中的 h1 改为 div, 否则在浏览器渲染的时候,如果电脑当时很慢
会首先显示很大的 h1 字体,然后才变成小字体
------------------------------------------
jfinal club 4.1 相对 4.0 的变化:
1:jfinal 升级到 4.1
2:大量调整 html、css、js 提升用户体验,例如:文档页面左侧菜单及脚本被大幅调整
3:所有 findByCache、findFirst、paginateByCache 由 getSqlPara 模式
改为 template(...) 模板模式
4:JsoupFilter 过滤 a 标签 href 中的 javascript 脚本,加强 XSS 防范
5:JsoupFilter 开启 html 5 的 video 标签,方便在文章中插入视频
6:Project、Share、Feedback 在 configEngine 中添加 me.addSharedObject(...),
便于在页面使用 field 表达式代替 static field 表达式,例如:
Project.REPORT_BLOCK_NUM
可改成下面的形式,进一步节省代码量
com.jfinal.club.common.model.Project::REPORT_BLOCK_NUM
---> 注意:Model 的 field 表达式读取字段的功能,最低 jfinal 4.0 版本才开始支持
7:后台 html 页面中 Project、Share、Feedback 的 static field 表达式改为 field 表达式
------------------------------------------
jfinal club 4.0 相对 2.0 的变化:
1:升级到 jfinal 4.0,去除 cglib/asm 依赖
2:前台 UI 全部替换,改进文档频道用户体验
3:控制层与业务层的依赖全部改成 @Inject
4:Validator 中改用 setRet(..) 与 render(getRet()),使得 js 中的代码统一,减少大量代码
5:优化部分 sql
6:AdminAuthService、PorjectService、ShareService、FeedbackService 中使用 jfinal 4.0
新提供的 template(...) 方法,节省大量代码,例如:
Db.template("admin.auth.isSuperAdmin", accountId).queryInt();
------------------------------------------
jfinal club 2.0 相对 1.9 的变化:
1:jfinal 升级到 3.8
2:jfinal undertow 升级到 1.6
3:升级前端 UI (TODO)
4:configConstant(...) 添加配置 me.setInjectSuperClass(true),支持超类依赖注入
5:生成器添加配置 gen.setGenerateRemarks(true) 为 base model 生成备注
6:all_sqls.sql 改名为 _all_sqls.sql,修改 JFinalClubConfig 相应配置
7:start.sh、stop.sh、restart.sh 合并为 jfinal.sh,添加 stop.bat
8:JFinalClubConfig 中的
PropKit.use(dev.txt).appendIfExists(pro.txt)
改为
PropKit.useFirstFound(pro.txt, dev.txt);
也即由生产环境配置追加模式改为生产环境配置整体代替模式
------------------------------------------
jfinal club 1.9 相对 1.8 的变化:
0:jfinal 升级到 3.6,避免客户端中途断开连接时抛出异常,日志文件会很干净。还包括很多其它改进与优化
1:jfinal undertow 升级到 1.5,解决 session 热加载问题
2:DocumentAdminService 、DocumentAdminController 的 findById 改成 findByIds
3:MessageController.friend() 添加 null 值判断并 renderError(404)
4:log4j.properties 日志文件存放添加 log 子目录,保持项目根路径干净清爽
5:JFinalClubConfig 中的 afterJFinalStart() 改名为 onStart()
6:stop.sh 三处 beforeJFinalStop() 改为 onStop()。添加 kill -9 有关注释
7:AccountService.getUsefulById(...) 添加 null 值判断
8:fastjson 升级到 1.2.55,支持 Page 反序列化(需要 jfinal-3.6 支持)
9:_Generator 中添加 setGenerateRemarks(false) 配置
10:添加配置:undertow.resourcePath=src/main/webapp, classpath:webapp
11:fatjar 打包配置文档修改主两处错误
------------------------------------------
jfinal club 1.8 相对 1.7 的变化:
0:jfinal-undertow 升级到 1.3
1:pom.xml 中添加 maven-jar-plugin 插件,避免 src/main/resources 下的配置文件打入 jar 包
好让 config 目录下配置文件生效,否则 config 中与 jar 包内的同名配置文件将不会生效
2:package.xml 中过滤掉对 web.xml、WEB-INF 的复制,jfinal-undertow 不需要 web.xml
3:更新 undertow-config-demo.txt,添加 jfinal-undertow-1.3 新增功能配置
4:添加 restart.sh 重启脚本
5:改进 ImageKit,解决 png 转 jpg 背景颜色改变的问题,感谢 @步步(讯) 的贡献
6:jfinal-club-1.8.sql 中的 role、permission、account_role、role_permission
表的 charset 由 utf8mb4 改成 utf8,解决老版本 mysql 无法导入问题
------------------------------------------
jfinal club 1.7 相对 1.6 的变化:
0:添加 "doc/___极速打包部署.txt" 说明文件
1:pom.xml 中删除 jetty-server 依赖
2:pom.xml 中添加 jfinal-undertow 依赖,从 jetty 切换到 undertow
3:pom.xml 中添加 maven-assembly-plugin 打包插件,根目录下面添加 package.xml 打包配置
4:根目录下面添加 start.sh、stop.sh、start.bat 启动脚本
5:JFinalClubConfig 中 JFinal.start(...) 改为 UndertowServer.start(...)
6:配置 文件 jfinal_club_config_dev.txt 的下划线改为减号
7:添加 undertow.txt 配置文件
8:修改 jfinal-club启动必读.txt,去除与 jetty 有关的一切内容
总述:jfinal-club 1.7 项目自身未做任何改动,所有变化围绕从 jetty-server 切换到
jfinal-undertow
jfinal-undertow 不仅启动极为迅速,热加载响应十分快捷,更重要是开发、打包、部署、运行
完全不用对项目或者配置有任何改动,从而实现从开发到部署的极致快捷
------------------------------------------
jfinal club 1.6 相对 1.5 版本的主要变化:
1:jfinal 升级到 3.5,pom.xml 中的 JDK 编译级别升为 1.8
2:jetty-server 升级到 jetty-server-2018.11,该版本的 JDK 最低要求为 1.8
3:JFinalClubConfig 中配置 me.setInjectDependency(true) 启用最新的 Aop
模块功能,免去业务层 AOP 必须手动 enhance 的步骤,免去业务层维护单例的样板式
代码,例如:
public static AccountService me = new AccountService();
注意:为了节省改动时间,现只先修改了后台管理模块的 AOP 使用方式。
此外,对于工具类的业务使用原有的 public static Xxx me 模式
比 @Inject 更加方便,同理,对于全局多处依赖的业务也如此,
例如:AccountService
4:添加文档管理模块,方便同学们使用 jfinal club 二次开发属于自己的带文档的社区
5:添加 @Remark 注解用于一键同步时向 permission 表的 remark 字段添加内容(该字段有内容时跳过)
详细用法请见 DocumentAdminController.java
6:账户管理添加 "查看后台账户/管理员" 功能,便于查看后台都有哪些账户被分配了角色,在对账户误操作分配了角色时,
也便于取消角色分配
7:模板文件 "/_view/admin/common/_menu.html" 之中添加了 #permission 指令 "细粒度"
控制菜单的示例,以及 #role 指令 "细粒度" 控制菜单的示例。缘于很多同学在问如何 "细粒度"
控制菜单权限的问题,jfinal club 细粒度控制菜单不需要添加后台代码,只需要事先在需要控制
的菜单那里用 #permission 与 #role 指令事先埋好点即可,权限控制的行为与后端完全一致,
前后端权限使用同一套逻辑,避免发生前后权限逻辑的死角、遗漏、不一致性
8:pom.xml 中添加 jetty-maven-plugin 插件,方便习惯于使用该插件的人启动并开发项目,启动项目只需
在控制台输入: mvn jetty:run
这种启动开发项目的方式也支持热加载,使用该启动方式可以去掉对 jetty-server 项目的依赖。
相对于 jfinal 官方提供的启动方式控制台输出信息较多较杂,启动速度慢,绝大多数情况建议
使用 jfinal 官方的启动方式
9:pom.xml 中添加 slf4j-nop 依赖避免在开发阶段启动项目时输出的 INFO 干扰信息,控制台从此特别干净清爽
10:reply.html 页面的 title、content 的输出使用 #escape() 指令,避免出现页面格式混乱
11:左侧首页菜单图标改为 fa-home,美观且符合度更高
12:BaseController 中添加 _clear_()
13:BaseController 中的 @Before(NotAction.class) 改用 @NotAction
14:启动方法统一使用 JFinal.start("src/main/webapp", 80, "/", 5),jfinal 3.5 已支持 IDEA
15:删掉 MyClassPathSourceFactory、MyClassPathSource,jfinal 3.5 已改进过 ClassPathSource
16:删除 com.jfinal.club.common.aop 包,改为使用最新版本 jfinal 3.5 的 aop
17:jdbcUrl 配置中添加 useSSL=false,避免使用高版本 mysql 时,控制台输出干扰开发的无用 INFO 信息
18:删除 JFinalClubConfig 中用于 IDEA 的启动代码以及相关所有注释说明,jfinal 3.5 已支持 IDEA 以及
所有 eclipse 版本
19:pom.xml 中的 maven-compiler-plugin 插件参数添加 -parameters 配置,便于二次开发中使用 action 带参
eclipse 相关编译配置见文档:https://jfinal.com/doc/3-3
---------------------------------------------------
jfinal club 1.5 相对 1.4 版本的主要变化:
1:项目管理、分享管理、反馈管理添加创建功能
2:分享管理、反馈管理模块添加回复管理功能
3:账户管理添加更换头像功能
4:权限管理的权限列表识别不存在的 action,并给出操作提示
5:授权管理添加 AdminAuthKit,支持 #if(hasPermission(...) ... #else ... #end 以及
#if(hasRole(...) ... #else ... #end
6:改进 ImageKit,移除对于 com.sun 包下的图片处理 API 依赖,更好支持 jdk 7/8
7:将 Switchery 组件换成 magic input 组件
a:在 __admin_layout.html 中引入 magic input 的 css 文件,删除 Switchery 的 css 与 js 引用
b:升级 jfinal-admin.js 中相关的 init 方法
c:将 checkbox 组件的 class="js-switch" 样式改为 class="mgc-switch mgc-tiny"
并将 initSwitchery() 方法名改为 initMagicInput(),并去掉第二个参数
8:修改页面提交以后返回到当前记录所在的页,老版本总是返回到第一页
9:RoleDirective.hasRole(...) PermissionDirective.hasPermission(...) 重构至 AdminAuthService
10:RoleDirective、PermissionDirective 中获取 loginAccount 的方式改为从 scope 中获取
11:重构 PermissionDirective、RoleDirective,代码更简洁
12:删掉 admin_role.sql 与 admin_permission.sql ,其中的 sql 转至 admin_auth.sql
13:删掉 all_sqls.sql 中对 admin_role.sql 与 admin_permission.sql 的 #include 包含
14:角色管理的 add、edit、_form 三个页面合并为 add_edit.html
15:UploadController 中的 renderJson(ret) 改为 render(new JsonRender(ret).forIE())
防止 ueditor 图片上传功能在 IE 下出现文件下载现象
16:$.pjax.defaults.timeout = 1500; 改为 5000
17:后台管理菜单颜色的 #ffffffb3 改为 hsla(0,0%,100%,.7),支持老版本 IE
18:其它打磨
升级建议:由于 jfinal club 1.5 打磨的地方比较多、比较细、也很重要,升级建议以 jfinal club 1.5 为基础,
将已经基于 jfinal club 1.4 做的项目中的自己写的代码重新整合到 jfinal club 1.5 中去。
二次开发产生的 js、css 代码要写到新建的文件中去,然后在 layout 中引用,这样有利于将来升级
此外,permission 表中的内容有所增加,升级完以后需要进入权限管理进行"一键同步",最后按个人需求
为角色分配相应的权限即可
---------------------------------------------------
jfinal club 1.4 相对 1.3 版本的变化:
1:升级到 jfinal 3.4
2:后台管理功能全部重写,添加内容管理、账户管理、角色管理、权限管理等功能
3:后台管理界面全部重写,手写骨架,右下角内容区域使用 bootstrap 3,方便进行二次开发
二次开发基本只需要照猫画虎,添加右下角内容区域代码
4:添加 #role #permission 指令,用于在界面控制权限
5:其它一些细节变化将在直播中讲解,会有更深刻的了解
注意:后台管理功能看似简单,其实里头很多细节,采用极简设计,尽可能消除学习成本和二次开发成本
后台管理源代码与功能将会在直播中详细讲解
---------------------------------------------------
jfinal club 1.3 相对 1.2 版本的变化:
1:升级到 jfinal 3.3
2:JFinalClubConfig 中加载配置文件由 try catch 改为使用 PropKit.appendIfExists(...)
3:ShareController、FeedbackController、MessageController、NewsFeedController
中的 renderToString(...) 的模板去掉路径
4:sql 管理功能使用 ClassPathSourceFactory 来做,从类路径里头加载
5:mysql 驱动升级到 5.1.44 版本
注意:jf club 1.2 已经处理过 Ret 类的升级,从 1.2 升级到 1.3 只需要关注上述五条
doc 目录下面的三个 png 图片文件,仅用于指导更老的版本升级到 1.2 或 1.3
---------------------------------------------------
jfinal club 1.2 相对 1.1 版本的变化:
1:jfinal 升级到 3.2 版本
2:文件上传组件 cos 升级到 2017.5 版本
3:UploadController 捕获 ExceededSizeException 异常,更好处理上传文件长度超出范围
4:"/upload" 路由改为 "/common/upload",修改 ueditor 相关配置指向新路由。此改进主要为了 nginx 配置方便
避免与 "webapp/upload" 的配置产生影响,减少 nginx 配置量
5:查找替换 html 文件中的 22 处 ret.isOk 为 ret.state == "ok"
6:查找替换 html 文件中的 4 处 ret.isFail 为 ret.state == "fail"
7:查找替换 js 文件中的 11 处 ret.isOk 为 ret.state == "ok"
建议大家升级自己项目 Ret 的方式为:
1:利用查找替换功能将 html 与 js 中的 ret.isOk 替换为 ret.state == "ok"
2:利用查找替换功能将 html 与 js 中的 ret.isFail 替换为 ret.state == "fail"
如果项目未涉及到 Ret 生成的 json 数据则不需要处理,java 代码中的
ret.isOk() ret.isFail() 行为并无变化,不需要处理
如果觉得上述升级方式比较麻烦,可以在项目启动时调用一次 Ret.setToOldWorkMode()
可以继续沿用老版本的工作模式,这种升级方式比较适合不需要怎么改动的老项目

57
doc/fatjar-打包部署方法/fatjar-打包部署方法.txt

@ -0,0 +1,57 @@
1、 public void configEngine(Engine me) 方法中添加如下两行代码
me.setBaseTemplatePath("webapp");
me.setToClassPathSourceFactory();
注意:上面两行配置代码要放在最前面,因为 addSharedFunction(...)
这类配置对上面配置有依赖关系
当你的 webapp 资源目录本来就放在 src/main/resources 之下时,需要 "去掉" pom.xml 中的如下配置:
<resource>
<directory>src/main/webapp</directory>
<targetPath>webapp</targetPath>
</resource>
上述配置将 src/main/webapp 目录整体复制到 target/classes 目录之下,好让其打到 jar 包之中去
所以,如果项目确定当成 fatjar 来开发的话,建议将 src/main/webapp 整体挪到
src/main/resources 之下。这样就从开发到打包都不用做上述这些配置与额外动作,避免了出错的可能
有部分同学习惯于使用 static 代替 webapp,将上述涉及 webapp 的地方改成 static 即可
2、在 undertow.txt 配置文件中添加如下配置:
undertow.resourcePath=src/main/webapp, classpath:webapp
3、将本文件夹下的 pom.xml 替换掉项目中原有的 pom.xml 文件
这两个文件的差别在于前者打 fatjar 包用到的是 maven-shade-plugin 这个插件
而后者用到的是 maven-assembly-plugin 插件
4、 打包运行
mvn clean package
java -jar jfinal-club.jar
5、 隐藏功能
在打好的 jar 包的目录中添加 config 目录并添加配置文件可以被项目加载,注意,这里的
配置文件名与 jar 包中的配置文件名不能相同,可以参考 JFinalClubConfig 中的用法:
PropKit.useFirstFound("jfinal-club-config-pro.txt", "jfinal-club-config-dev.txt");
也就是说在开发的时候使用 dev 配置,在生产环境手动创建一个 pro 配置,由于该配置文件
在 jar 包中不存在,所以会被加载
在打好的 jar 包目录中添加将项目中的 webapp 复制过来,便于对外部 css、js、html 等资源
进行修改,更重要的是支持有文件上传功能的 web 项目
以上两个隐藏功能,需要在项目启动之前添加好目录与文件。简单来说这两个目录不存在时
使用 jar 包中的资源,否则就使用它。注意 config 目录中与 jar 包中的同名配置不会被加载
6、退出程序
如果启动时没带与字符 ‘&’ 结尾,则 Ctrl + C 即可退出否则使用下面的方法:
找到程序 pid: ps aux | grep java
杀掉进程: kill pid
注意使用 kill -9 pid 杀进程时,JFinalConfig.onStop() 不会被回调
可以参考 "非 fatjar" 项目的运行、停止脚本来写一个用于 fatjar 的脚本
小结:以上配置并不是仅仅适用于打包部署,开发阶段也使用这些配置,不用改来改去

278
doc/fatjar-打包部署方法/pom.xml

@ -0,0 +1,278 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jfinal</groupId>
<artifactId>jfinal-club</artifactId>
<version>4.9.08</version>
<packaging>jar</packaging>
<name>jfinal-club</name>
<url>https://jfinal.com/club</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<!-- 使用阿里 maven 库 -->
<repositories>
<repository>
<id>ali-maven</id>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
<dependencies>
<!-- jfinal -->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal</artifactId>
<version>4.9.08</version>
</dependency>
<!-- jfinal-undertow 开发、部署一体化 web 服务器 -->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal-undertow</artifactId>
<version>2.5</version>
</dependency>
<!-- cos 文件上传 -->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>cos</artifactId>
<version>2020.4</version>
</dependency>
<!-- fastjson json 转换 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<!-- 开发 WebSockets 时开启下面的依赖 -->
<!--
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
<version>2.0.32.Final</version>
</dependency>
-->
<!-- junit 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- 避免控制台输出如下提示信息:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
项目中实际上用不到这个 jar 包
注意:eclipse 下可以将 scope 设置为 provided
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.29</version>
<!-- 打包前改成 provided,此处使用 compile 仅为支持 IDEA -->
<scope>compile</scope>
</dependency>
<!-- druid 数据源连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
<!-- log4j 日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
<!-- ehcache 缓存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<!-- 邮件发送 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
<!-- jsoup 过滤恶意数据提交 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.9.2</version>
</dependency>
<!-- joda-time 日期、时间变换 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.3</version>
</dependency>
<!-- cron4j 任务调度 -->
<dependency>
<groupId>it.sauronsoftware.cron4j</groupId>
<artifactId>cron4j</artifactId>
<version>2.2.5</version>
</dependency>
<!-- zxing 二维码生成 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
<build>
<finalName>jfinal-club</finalName>
<!--
添加 includes 配置后,excludes 默认为所有文件 **/*.*,反之亦然
该规则在 maven-jar-plugin 等插件中同样适用
-->
<resources>
<!-- 添加该配置是为了将 .sql 文件打入 jar 包 -->
<resource>
<directory>src/main/java</directory>
<includes>
<!-- **/* 前缀用法,可以匹配所有路径 -->
<include>**/*.sql</include>
<include>**/*.class</include>
</includes>
</resource>
<!--
没有添加 resources 配置时,src/main/resources 目录是默认配置
一旦添加 resources 配置指向 src/main/java 目录时,原先的默认配置被取代,
所以需要添加如下配置将默认配置再添加进来,否则无法使用 src/main/resources
下的资源文件
-->
<resource>
<directory>src/main/resources</directory>
</resource>
<!--
将 webapp 也指定为资源文件,并且将之输出到 webapp 目录下
该配置可以取代 maven-resources-plugin 的功能
targetPath: http://maven.apache.org/pom.html#Resources
注意:如果 web 资源本身就放在 src/main/resources/webapp 之下,
需要删除下面的配置
-->
<resource>
<directory>src/main/webapp</directory>
<targetPath>webapp</targetPath>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<!-- java8 保留参数名编译参数 -->
<compilerArgument>-parameters</compilerArgument>
<compilerArguments><verbose /></compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>jfinal-club</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.bt.common.JFinalClubConfig</mainClass>
</transformer>
<!--
下面的配置支持排除指定文件打包到 jar 之中,可以用于排除需要修改的配置文件以便于在外部的 config 目录下的
同名配置文件生效,建议使用 Prop.appendIfExists(xxx_pro.txt) 在外部放一个非同名配置来覆盖开发环境的配置
则可以不用使用下面的配置,文档参考:
http://maven.apache.org/plugins/maven-shade-plugin/examples/resource-transformers.html#DontIncludeResourceTransformer
-->
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resources>
<!-- <resource>jfinal-club-config-dev.txt</resource> -->
<!-- <resource>.PDF</resource> -->
<!-- <resource>READ.md</resource> -->
</resources>
</transformer>
</transformers>
<!--
解决 fatjar 的 "java.lang.SecurityException: Invalid signature file digest
for Manifest main attributes" 问题
-->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

1027
doc/jfinal-club-init.sql

File diff suppressed because it is too large

50
doc/jfinal-club启动必读.txt

@ -0,0 +1,50 @@
超级管理员登录账号:test@test.com 密码:111111
另外的几个用于演示账户、角色、权限管理的账号如下:
test2@test.com 密码:111111
test3@test.com 密码:111111
test4@test.com 密码:111111
test5@test.com 密码:111111
test6@test.com 密码:111111
test7@test.com 密码:111111
test8@test.com 密码:111111
注意:超级管理员角色在 role 表中 id 值固定为 1,请勿在数据库中修改该值
所有账号都可以在后台管理界面中修改登录名
修改密码功能忘了添加,暂时可以在前台的个人中心去操作:
http://localhost/my/setting/password
------------------------------------------------------------------------
0:启动之前需要先创建名为 jfinal_club 的数据库,字符集使用 utf-8
导入 jfinal-club-init.sql 中的建表与初始化数据
1:修改 src/main/resources 目录下的 jfinal-club-config-dev.txt 配置文件
2:该项目为标准的 maven web app 项目,以往相关经验可直接使用
3:JFinalClubConfig.java 文件中已经创建了一个 main 方法,
直接右键该文件,点击 debug 或 run 即可运行
4:按照以上方式运行过一次以后,eclipse 或 IDEA 会自动生成一条debug、run configuration
配置项,对该配置项可进行进一步设置,例如可设置 VM argument: -XX:PermSize=64M -XX:MaxPermSize=256M
5:由于 jfinal 是通过监听被编译出来的 class 文件是否被修改来支持的热载,而 IDEA 默认是不会
自动编译被修改的 java 源文件的,所以无法自动化支持热加载
所以在 IDEA 下开发,需要手动按一下 Ctrl + F9 快捷键来编译被修改过的 Java 源代码,
从而触发热加载(mac 操作系统按 Cmd + F9),当然你还可以在网上找一下配置 IDEA 自动
编译的方法来自动化实现热加载
此外,在 IDEA 下还可以按 Alt + 5 激活调试窗口,再按一下 Ctrl + F5 的方式重启项目,
此方法用来代替热加载功能也比较方便(mac 操作系统下是: Cmd + 5 再 Cmd + R)
x:源码中只给出了很粗浅的注释说明,加入俱乐部可全面掌握源码内涵:https://jfinal.com/club
加入俱乐部还可以获取往期大量直播视频,可以深度理解并掌握 jfinal 以及 jfinal club 的设计与用法

75
doc/undertow-config-demo.txt

@ -0,0 +1,75 @@
# 配置样例文件 undertow-config-demo.txt 使用说明:
#
# 1:系统默认在 class path 根目录下先加载 undertow.txt 再加载 undertow-pro.txt
# 进行配置,当上述两个配置文件不存在时不抛异常并使用默认值配置
#
# 2:所有配置可以省略,省略时使用默认配置
#
# 3:开发阶段 undertow.devMode 配置为 true 才支持热加载
#
# 4:该文件列出了绝大多数可配置项,更多不常用配置可以查看 UndertowConfig 源码中的配置常量定义
#
# 5:当配置项不足以满足需求时,可以通过继承 UndertowServer 并覆盖 configMore()
# 方法来添加额外的配置项
# true 值支持热加载,生产环境建议配置成 false
undertow.devMode=false
# 避免项目中的 .class 打成 jar 包以后,同时在使用 devMode 时报的异常
# 只要 underto.devMode 设置为 false,或者不打包就不会有异常
# 添加此配置以后则无需关心上面这类事情,多个前缀用逗号分隔开
undertow.hotSwapClassPrefix=com.jfinal.club.
undertow.port=80
undertow.host=localhost
undertow.contextPath=/
# js、css 等等 web 资源存放的目录
undertow.resourcePath=webapp, src/main/webapp, classpath:static
# io 线程数与 worker 线程数
# undertow.ioThreads=
# undertow.workerThreads=
# gzip 压缩开关
undertow.gzip.enable=false
# 配置压缩级别,默认值 -1。 可配置 1 到 9。 1 拥有最快压缩速度,9 拥有最高压缩率
undertow.gzip.level=-1
# 触发压缩的最小内容长度
undertow.gzip.minLength=1024
# session 过期时间,注意单位是秒
# undertow.session.timeout=1800
# 热加载保持 session 值,避免依赖于 session 的登录型项目反复登录,默认值为 true。仅用于 devMode,生产环境无影响
# undertow.session.hotSwap=true
# 下面两行命令生成密钥库
# keytool -genkeypair -validity 3650 -alias club -keyalg RSA -keystore club.jks
# keytool -importkeystore -srckeystore club.jks -destkeystore club.pfx -deststoretype PKCS12
# 生成过程中提示输入 "名字与姓氏" 时输入 localhost。生产环境从阿里云下载 tomcat 类型的密钥库
# 是否开启 ssl
undertow.ssl.enable=true
# ssl 监听端口号,部署环境设置为 443
undertow.ssl.port=443
# 密钥库类型,建议使用 PKCS12
undertow.ssl.keyStoreType=PKCS12
# 密钥库文件
undertow.ssl.keyStore=club.pfx
# 密钥库密码
undertow.ssl.keyStorePassword=111111
# ssl 开启时,是否开启 http2。检测该配置是否生效在 chrome 地址栏中输入: chrome://net-internals/#http2
# undertow.http2.enable=true
# ssl 开启时,http 请求是否重定向到 https
# undertow.http.toHttps=false
# ssl 开启时,http 请求跳转到 https 使用的状态码,默认值 302
# undertow.http.toHttpsStatusCode=302
# ssl 开启时,是否关闭 http
# undertow.http.disable=false

58
doc/后台管理必读.txt

@ -0,0 +1,58 @@
0:jfinal club 1.4 新添加了如下四张表:
role(id, name)
permission(id, actionKey, controller, remark)
account_role(accountId, roleId)
role_permission(roleId, permissionId)
其中 role、permission 与老版本的 account 构成权限管理功能的三张主表,
而 account_role、role_permission 建立这三张主表间的关联
1:权限管理模块对左侧菜单进行管理,可以参考 "/_view/_admin/common/_menu.html"
中的 #role("权限管理员", "CEO", "CTO") 用法,只需要先用这种式方式预先
安排好哪些角色可以访问哪些菜单,然后就可以通过为 account 配置 role 的方式
来配置菜单权限了,这种模式比通过再创建 menu 表要简单方便
此外,也可以通过 #permission(...) 更细粒度的控制每一个菜单,一般用 #role 指令即可
2:权限管理模块对界面操作按钮之类的组件细粒度的控制可以参考 "/_view/_admin/project/index.html"
中的 #permission("/admin/project/delete") 用法,只需要先用这种方式预先
安排好哪些权限用于控制访问哪些按钮或组件,然后就可以通过为 account 配置权限的
方式来细粒度控制按纽、组件了
3:share、feedback 的回复管理下一版本添加:贴子 title + table 结构
4:头部导航已经做过搜索的界面,一直对美观不满意,留到下版本做进去,
搜索栏样式参考:https://fontawesome.com/v4.7.0/icons/
5:项目整体结构采用先划分模块,然后在模块内部再分层的方式,便于向大型系统进化,
市面上很多先划分层次,然后再划分模块的方式远没有此方式适应大型系统的开发,
此划分方式,还有利于在未来将模块独立拆分成小型服务,再以微服务的方式做分布式
而先分层再分模块的方式则无法方便支持
6:后台管理源码的包名以及视图文件的目录名以下划线打头 "_",是为了让其始终排列在
固定的位置,便于开发过程中快速定位,提升开发效率。否则后台管理模块的位置
会不断变化,例如,如果存在 about 包名,则 admin 会排在其之后
7:视图文件基础路径 "_view" 以下划线打头 "_",是为了将其排在 webapp 的最前面,便于快速
定位,提升开发效率,否则该目录会被夹杂在 assets、WEB-INF、upload 等目录之间
由于 jfinal 有 baseViewPath 配置,所以此安排不会给开发带来麻烦
8:后台管理并没有专用的登录界面,一切针对于后台管理登录界面的黑客攻击根本找不到这个界面
一切对于该界面的寻找必将返回 404 页面,没有专用的管理账户表,管理员账号存在于普通
账号之中。无招才是更好的招。
总之:jfinal-club 值得讨论和学习的细节极多,上述仅为冰山一角,大家一定要加入俱乐部
收获所有福利 https://jfinal.com/club

24
doc/版权声明.txt

@ -0,0 +1,24 @@
请勿将俱乐部专享资源复制给任何人,保护知识产权即是保护我们所在的行业,
进而保护我们自己的利益,即便是公司的同事,也请尊重 JFinal 作者的
努力与付出,不要复制给任何人
JFinal 俱乐部是七年以来首次寻求外部资源的尝试,以便于有资源创建更加
高品质的产品与服务,为大家带来更大的价值,所以请大家多多支持,不要将
首次的尝试扼杀在了摇篮之中
如果你尚未加入俱乐部,请立即删除该项目,或者现在加入俱乐部:
https://jfinal.com/club
俱乐部将提供 jfinal-club 项目源码、直播视频、专用 QQ 群,以及作者
在俱乐部定期的分享与答疑,价值远比仅仅拥有 jfinal club
项目源代码要大得多,俱乐部福利资源是不断增加的,以下是俱乐部
新福利计划:
https://jfinal.com/club/1-2
JFinal 项目的可持续性发展需要你的支持!!!

69
jfinal.bat

@ -0,0 +1,69 @@
@echo off
rem -------------------------------------------------------------------------
rem
rem 使用说明:
rem
rem 1: 该脚本用于别的项目时只需要修改 MAIN_CLASS 即可运行
rem
rem 2: JAVA_OPTS 可通过 -D 传入 undertow.port 与 undertow.host 这类参数覆盖
rem 配置文件中的相同值此外还有 undertow.resourcePath, undertow.ioThreads
rem undertow.workerThreads 共五个参数可通过 -D 进行传入
rem
rem 3: JAVA_OPTS 可传入标准的 java 命令行参数,例如 -Xms256m -Xmx1024m 这类常用参数
rem
rem
rem -------------------------------------------------------------------------
setlocal & pushd
rem 启动入口类,该脚本文件用于别的项目时要改这里
set MAIN_CLASS=com.jfinal.club.common.JFinalClubConfig
rem Java 命令行参数,根据需要开启下面的配置,改成自己需要的,注意等号前后不能有空格
rem set "JAVA_OPTS=-Xms256m -Xmx1024m -Dundertow.port=80 -Dundertow.host=0.0.0.0"
rem set "JAVA_OPTS=-Dundertow.port=80 -Dundertow.host=0.0.0.0"
if "%1"=="start" goto normal
if "%1"=="stop" goto normal
if "%1"=="restart" goto normal
goto error
:error
echo Usage: jfinal.bat start | stop | restart
goto :eof
:normal
if "%1"=="start" goto start
if "%1"=="stop" goto stop
if "%1"=="restart" goto restart
goto :eof
:start
set APP_BASE_PATH=%~dp0
set CP=%APP_BASE_PATH%config;%APP_BASE_PATH%lib\*
echo starting jfinal undertow
java -Xverify:none %JAVA_OPTS% -cp %CP% %MAIN_CLASS%
goto :eof
:stop
set "PATH=%JAVA_HOME%\bin;%PATH%"
echo stopping jfinal undertow
for /f "tokens=1" %%i in ('jps -l ^| find "%MAIN_CLASS%"') do ( taskkill /F /PID %%i )
goto :eof
:restart
call :stop
call :start
goto :eof
endlocal & popd
pause

82
jfinal.sh

@ -0,0 +1,82 @@
#!/bin/bash
# ----------------------------------------------------------------------
# name: jfinal.sh
# version: 1.0
# author: yangfuhai
# email: fuhai999@gmail.com
#
# 使用说明:
# 1: 该脚本使用前需要首先修改 MAIN_CLASS 值,使其指向实际的启动类
#
# 2:使用命令行 ./jfinal.sh start | stop | restart 可启动/关闭/重启项目
#
# 3: JAVA_OPTS 可通过 -D 传入 undertow.port 与 undertow.host 这类参数覆盖
# 配置文件中的相同值此外还有 undertow.resourcePath、undertow.ioThreads、
# undertow.workerThreads 共五个参数可通过 -D 进行传入,该功能尽可能减少了
# 修改 undertow 配置文件的必要性
#
# 4: JAVA_OPTS 可传入标准的 java 命令行参数,例如 -Xms256m -Xmx1024m 这类常用参数
#
# 5: 函数 start() 给出了 4 种启动项目的命令行,根据注释中的提示自行选择合适的方式
#
# ----------------------------------------------------------------------
# 启动入口类,该脚本文件用于别的项目时要改这里
MAIN_CLASS=com.jfinal.club.common.JFinalClubConfig
if [[ "$MAIN_CLASS" == "com.yourpackage.YourMainClass" ]]; then
echo "请先修改 MAIN_CLASS 的值为你自己项目启动Class,然后再执行此脚本。"
exit 0
fi
COMMAND="$1"
if [[ "$COMMAND" != "start" ]] && [[ "$COMMAND" != "stop" ]] && [[ "$COMMAND" != "restart" ]]; then
echo "Usage: $0 start | stop | restart"
exit 0
fi
# Java 命令行参数,根据需要开启下面的配置,改成自己需要的,注意等号前后不能有空格
# JAVA_OPTS="-Xms256m -Xmx1024m -Dundertow.port=80 -Dundertow.host=0.0.0.0"
# JAVA_OPTS="-Dundertow.port=80 -Dundertow.host=0.0.0.0"
# 生成 class path 值
APP_BASE_PATH=$(cd `dirname $0`; pwd)
CP=${APP_BASE_PATH}/config:${APP_BASE_PATH}/lib/*
function start()
{
# 运行为后台进程,并在控制台输出信息
java -Xverify:none ${JAVA_OPTS} -cp ${CP} ${MAIN_CLASS} &
# 运行为后台进程,并且不在控制台输出信息
# nohup java -Xverify:none ${JAVA_OPTS} -cp ${CP} ${MAIN_CLASS} >/dev/null 2>&1 &
# 运行为后台进程,并且将信息输出到 output.log 文件
# nohup java -Xverify:none ${JAVA_OPTS} -cp ${CP} ${MAIN_CLASS} > output.log &
# 运行为非后台进程,多用于开发阶段,快捷键 ctrl + c 可停止服务
# java -Xverify:none ${JAVA_OPTS} -cp ${CP} ${MAIN_CLASS}
}
function stop()
{
# 支持集群部署
kill `pgrep -f ${APP_BASE_PATH}` 2>/dev/null
# kill 命令不使用 -9 参数时,会回调 onStop() 方法,确定不需要此回调建议使用 -9 参数
# kill `pgrep -f ${MAIN_CLASS}` 2>/dev/null
# 以下代码与上述代码等价
# kill $(pgrep -f ${MAIN_CLASS}) 2>/dev/null
}
if [[ "$COMMAND" == "start" ]]; then
start
elif [[ "$COMMAND" == "stop" ]]; then
stop
else
stop
start
fi

87
package.xml

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
<!--
assembly 打包配置更多配置可参考官方文档:
http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html
-->
<id>release</id>
<!--
设置打包格式,可同时设置多种格式,常用格式有:dir、zip、tar、tar.gz
dir 格式便于在本地测试打包结果
zip 格式便于 windows 系统下解压运行
tar、tar.gz 格式便于 linux 系统下解压运行
-->
<formats>
<format>dir</format>
<format>zip</format>
<!-- <format>tar.gz</format> -->
</formats>
<!-- 打 zip 设置为 true 时,会在 zip 包中生成一个根目录,打 dir 时设置为 false 少层目录 -->
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<!-- src/main/resources 全部 copy 到 config 目录下 -->
<fileSet>
<directory>${basedir}/src/main/resources</directory>
<outputDirectory>config</outputDirectory>
</fileSet>
<!-- src/main/webapp 全部 copy 到 webapp 目录下 -->
<fileSet>
<directory>${basedir}/src/main/webapp</directory>
<outputDirectory>webapp</outputDirectory>
<excludes>
<!-- **/* 前缀用法,可以匹配所有路径,例如:**/*.txt -->
<exclude>WEB-INF</exclude>
<exclude>WEB-INF/web.xml</exclude>
</excludes>
</fileSet>
<!-- 项目根下面的脚本文件 copy 到根目录下 -->
<fileSet>
<directory>${basedir}</directory>
<outputDirectory></outputDirectory>
<!-- 脚本文件在 linux 下的权限设为 755,无需 chmod 可直接运行 -->
<fileMode>755</fileMode>
<lineEnding>unix</lineEnding>
<includes>
<include>*.sh</include>
</includes>
</fileSet>
<fileSet>
<directory>${basedir}</directory>
<outputDirectory></outputDirectory>
<fileMode>755</fileMode>
<lineEnding>windows</lineEnding>
<includes>
<include>*.bat</include>
</includes>
</fileSet>
<!-- 项目 lib 目录下的本地 jar 包全部 copy 到 lib 目录下 -->
<!-- fileSet>
<directory>${basedir}/lib</directory>
<outputDirectory>lib</outputDirectory>
</fileSet -->
</fileSets>
<!-- 依赖的 jar 包 copy 到 lib 目录下 -->
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>

276
pom.xml

@ -0,0 +1,276 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jfinal</groupId>
<artifactId>bt-panel</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
<name>bt-panel</name>
<url>https://jfinal.com/club</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
</properties>
<!-- 使用阿里 maven 库 -->
<repositories>
<repository>
<id>ali-maven</id>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
<dependencies>
<!-- jfinal-undertow 开发、部署一体化 web 服务器 -->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal-undertow</artifactId>
<version>3.5</version>
</dependency>
<!-- jfinal -->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>jfinal</artifactId>
<version>5.1.2</version>
</dependency>
<!-- cos 文件上传 -->
<dependency>
<groupId>com.jfinal</groupId>
<artifactId>cos</artifactId>
<version>2020.4</version>
</dependency>
<!-- fastjson json 转换 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.48</version>
</dependency>
<!-- 开发 WebSockets 时开启下面的依赖 -->
<!--
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
<version>2.0.32.Final</version>
</dependency>
-->
<!-- junit 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- 避免控制台输出如下提示信息:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
项目中实际上用不到这个 jar 包
注意:eclipse 下可以将 scope 设置为 provided
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.29</version>
<!-- 打包前改成 provided,此处使用 compile 仅为支持 IDEA -->
<scope>compile</scope>
</dependency>
<!-- druid 数据源连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.22</version>
</dependency>
<!-- log4j 日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- ehcache 缓存 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<!-- 邮件发送 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
<!-- jsoup 过滤恶意数据提交 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.9.2</version>
</dependency>
<!-- joda-time 日期、时间变换 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.12.7</version>
</dependency>
<!-- cron4j 任务调度 -->
<dependency>
<groupId>it.sauronsoftware.cron4j</groupId>
<artifactId>cron4j</artifactId>
<version>2.2.5</version>
</dependency>
<!-- zxing 二维码生成 -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.39.37.ALL</version>
</dependency>
</dependencies>
<build>
<!--
添加 includes 配置后,excludes 默认为所有文件 **/*.*,反之亦然
该规则在 maven-jar-plugin 等插件中同样适用
-->
<resources>
<!-- 添加该配置是为了将 .sql 文件打入 jar 包 -->
<resource>
<directory>src/main/java</directory>
<includes>
<!-- **/* 前缀用法,可以匹配所有路径 -->
<include>**/*.sql</include>
<include>**/*.jf</include>
</includes>
</resource>
<!--
没有添加 resources 配置时,src/main/resources 目录是默认配置
一旦添加 resources 配置指向 src/main/java 目录时,原先的默认配置被取代,
所以需要添加如下配置将默认配置再添加进来,否则无法使用 src/main/resources
下的资源文件
-->
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<!-- java8 保留参数名编译参数 -->
<compilerArgument>-parameters</compilerArgument>
<compilerArguments><verbose /></compilerArguments>
</configuration>
</plugin>
<!--
jar 包中的配置文件优先级高于 config 目录下的 "同名文件"
因此,打包时需要排除掉 jar 包中来自 src/main/resources 目录的
配置文件,否则部署时 config 目录中的同名配置文件不会生效
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<excludes>
<!--
*.* 用法,可以匹配 jar 包根目录下所有文件
*.xxx 用法,可以匹配 jar 包根目录下特定扩展名文件,例如:*.xml
**/* 前缀用法,可以匹配所有路径,例如:**/*.txt
-->
<exclude>*.*</exclude>
</excludes>
</configuration>
</plugin>
<!--
使用 mvn clean package 打包
更多配置可参考官方文档:http://maven.apache.org/plugins/maven-assembly-plugin/single-mojo.html
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<!-- 打包生成的文件名 -->
<finalName>${project.artifactId}</finalName>
<!-- jar 等压缩文件在被打包进入 zip、tar.gz 时是否压缩,设置为 false 可加快打包速度 -->
<recompressZippedFiles>false</recompressZippedFiles>
<!-- 打包生成的文件是否要追加 package.xml 中定义的 id 值 -->
<appendAssemblyId>true</appendAssemblyId>
<!-- 指向打包描述文件 package.xml -->
<descriptors>
<descriptor>package.xml</descriptor>
</descriptors>
<!-- 打包结果输出的基础目录 -->
<outputDirectory>${project.build.directory}/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

189
src/main/java/com/bt/_admin/account/AccountAdminController.java

@ -0,0 +1,189 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.account;
import com.jfinal.aop.Before;
import com.jfinal.aop.Inject;
import com.bt._admin.role.RoleAdminService;
import com.bt.common.account.AccountService;
import com.bt.common.model.Role;
import com.bt.my.setting.MySettingService;
import com.jfinal.core.Path;
import com.jfinal.plugin.activerecord.Page;
import com.bt.common.controller.BaseController;
import com.jfinal.kit.Ret;
import com.bt.common.model.Account;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.upload.UploadFile;
import java.util.List;
/**
* 账户管理控制器
*/
@Path(value = "/admin/account", viewPath = "/account")
public class AccountAdminController extends BaseController {
@Inject
AccountAdminService srv;
@Inject
MySettingService mySettingSrv;
@Inject
RoleAdminService roleAdminSrv;
@Inject
AccountService accountSrv;
public void index() {
Page<Account> accountPage = srv.paginate(getParaToInt("p", 1));
setAttr("accountPage", accountPage);
render("index.html");
}
public void edit() {
keepPara("p"); // 保持住分页的页号,便于在 ajax 提交后跳转到当前数据所在的页
Account account = srv.findById(getParaToInt("id"));
setAttr("account", account);
render("edit.html");
}
/**
* 提交修改
*/
@Before(AccountUpdateValidator.class)
public void update() {
Account account = getBean(Account.class);
Ret ret = srv.update(account);
renderJson(ret);
}
/**
* 账户锁定
*/
public void lock() {
Ret ret = srv.lock(getLoginAccountId(), getParaToInt("id"));
renderJson(ret);
}
/**
* 账户解锁
*/
public void unlock() {
Ret ret = srv.unlock(getParaToInt("id"));
renderJson(ret);
}
/**
* 分配角色
*/
public void assignRoles() {
Account account = srv.findById(getParaToInt("id"));
List<Role> roleList = roleAdminSrv.getAllRoles();
srv.markAssignedRoles(account, roleList);
setAttr("account", account);
setAttr("roleList", roleList);
render("assign_roles.html");
}
/**
* 添加角色
*/
public void addRole() {
Ret ret = srv.addRole(getParaToInt("accountId"), getParaToInt("roleId"));
renderJson(ret);
}
/**
* 删除角色
*/
public void deleteRole() {
Ret ret = srv.deleteRole(getParaToInt("accountId"), getParaToInt("roleId"));
renderJson(ret);
}
/**
* 显示 "后台账户/管理员" 列表 account_role 表中存在的账户(被分配过角色的账户)
* 被定义为 "后台账户/管理员"
*
* 该功能便于查看后台都有哪些账户被分配了角色在对账户误操作分配了角色时也便于取消角色分配
*/
public void showAdminList() {
List<Record> adminList = srv.getAdminList();
setAttr("adminList", adminList);
render("admin_list.html");
}
public void avatar() {
keepPara("p"); // 保持住分页的页号,便于在 ajax 提交后跳转到当前数据所在的页
Account account = srv.findById(getParaToInt("accountId"));
setAttr("account", account);
render("avatar.html");
}
/**
* 上传用户图片为裁切头像做准备
*/
public void uploadAvatar() {
UploadFile uf = null;
try {
uf = getFile("avatar", mySettingSrv.getAvatarTempDir(), mySettingSrv.getAvatarMaxSize());
if (uf == null) {
renderJson(Ret.fail("msg", "请先选择上传文件"));
return;
}
} catch (Exception e) {
if (e instanceof com.jfinal.upload.ExceededSizeException) {
renderJson(Ret.fail("msg", "文件大小超出范围"));
} else {
if (uf != null) {
// 只有出现异常时才能删除,不能在 finally 中删,因为后面需要用到上传文件
uf.getFile().delete();
}
renderJson(Ret.fail("msg", e.getMessage()));
}
return ;
}
// 注意这里可以更换任意用户的头像,所以并非 getLoginAccountId()
int accountId = getParaToInt("accountId");
Ret ret = mySettingSrv.uploadAvatar(accountId, uf);
if (ret.isOk()) { // 上传成功则将文件 url 径暂存起来,供下个环节进行裁切
setSessionAttr("avatarUrl", ret.get("avatarUrl"));
}
renderJson(ret);
}
/**
* 保存 jcrop 裁切区域为用户头像
*/
public void saveAvatar() {
// 注意这里可以更换任意用户的头像,所以并非 getLoginAccountId()
int accountId = getParaToInt("accountId");
Account account = accountSrv.getById(accountId);
String avatarUrl = getSessionAttr("avatarUrl");
int x = getParaToInt("x");
int y = getParaToInt("y");
int width = getParaToInt("width");
int height = getParaToInt("height");
Ret ret = mySettingSrv.saveAvatar(account, avatarUrl, x, y, width, height);
renderJson(ret);
}
}

165
src/main/java/com/bt/_admin/account/AccountAdminService.java

@ -0,0 +1,165 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.account;
import com.jfinal.aop.Inject;
import com.bt.common.model.Account;
import com.bt.common.model.Role;
import com.bt.common.model.Session;
import com.bt.login.LoginService;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Page;
import com.jfinal.plugin.activerecord.Record;
import java.util.List;
/**
* 账户管理
*/
public class AccountAdminService {
@Inject
LoginService loginSrv;
private Account dao = new Account().dao();
public Page<Account> paginate(int pageNum) {
return dao.paginate(pageNum, 10, "select *", "from account order by id desc");
}
public Account findById(int accountId) {
return dao.findById(accountId);
}
/**
* 注意要验证 nickName userName 是否存在
*/
public Ret update(Account account) {
String nickName = account.getNickName().toLowerCase().trim();
String sql = "select id from account where lower(nickName) = ? and id != ? limit 1";
Integer id = Db.queryInt(sql, nickName, account.getId());
if (id != null) {
return Ret.fail("msg", "昵称已经存在,请输入别的昵称");
}
String userName = account.getUserName().toLowerCase().trim();
sql = "select id from account where lower(userName) = ? and id != ? limit 1";
id = Db.queryInt(sql, userName, account.getId());
if (id != null) {
return Ret.fail("msg", "邮箱已经存在,请输入别的昵称");
}
// 暂时只允许修改 nickName 与 userName
account.keep("id", "nickName", "userName");
account.update();
return Ret.ok("msg", "账户更新成功");
}
/**
* 锁定账号
*/
public Ret lock(int loginAccountId, int lockedAccountId) {
if (lockedAccountId == 1) {
return Ret.fail("msg", "不能锁定超级管理员的账号");
}
if (loginAccountId == lockedAccountId) {
return Ret.fail("msg", "不能锁定自己的账号");
}
int n = Db.update("update account set status = ? where id=?", Account.STATUS_LOCK_ID, lockedAccountId);
// 锁定后,强制退出登录,避免继续搞破坏
List<Session> sessionList = Session.dao.find("select * from session where accountId = ?", lockedAccountId);
if (sessionList != null) {
for (Session session : sessionList) { // 处理多客户端同时登录后的多 session 记录
loginSrv.logout(session.getId()); // 清除登录 cache,强制退出
}
}
if (n > 0) {
return Ret.ok("msg", "锁定成功");
} else {
return Ret.fail("msg", "锁定失败");
}
}
/**
* 解锁账号
*/
public Ret unlock(int accountId) {
// 如果账户未激活,则不能被解锁
int n = Db.update("update account set status = ? where status != ? and id = ?", Account.STATUS_OK , Account.STATUS_REG , accountId);
Db.update("delete from session where accountId = ?", accountId);
if (n > 0) {
return Ret.ok("msg", "解锁成功");
} else {
return Ret.fail("msg", "解锁失败,可能是账户未激活,请查看账户详情");
}
}
/**
* 添加角色
*/
public Ret addRole(int accountId, int roleId) {
Record accountRole = new Record().set("accountId", accountId).set("roleId", roleId);
Db.save("account_role", accountRole);
return Ret.ok("msg", "添加角色成功");
}
/**
* 删除角色
*/
public Ret deleteRole(int accountId, int roleId) {
if (accountId == 1 && roleId == 1) {
return Ret.fail("msg", "超级管理员角色账号不能被删除");
}
Db.delete("delete from account_role where accountId=? and roleId=?", accountId, roleId);
return Ret.ok("msg", "删除角色成功");
}
/**
* 标记出 account 拥有的角色
* 未来用 role left join account_role 来优化
*/
public void markAssignedRoles(Account account, List<Role> roleList) {
String sql = "select accountId from account_role where accountId=? and roleId=? limit 1";
for (Role role : roleList) {
Integer accountId = Db.queryInt(sql, account.getId(), role.getId());
if (accountId != null) {
// 设置 assigned 用于界面输出 checked
role.put("assigned", true);
}
}
}
/**
* 获取 "后台账户/管理员" 列表 account_role 表中存在的账户(被分配过角色的账户)
* 被定义为 "后台账户/管理员"
*
* 该功能便于查看后台都有哪些账户被分配了角色在对账户误操作分配了角色时也便于取消角色分配
*/
public List<Record> getAdminList() {
String sql = "select a.nickName, a.userName, ar.*, r.name from account a, account_role ar, role r " +
"where a.id = ar.accountId and ar.roleId = r.id " +
"order by roleId asc";
return Db.find(sql);
}
}

67
src/main/java/com/bt/_admin/account/AccountUpdateValidator.java

@ -0,0 +1,67 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.account;
import com.bt.common.kit.SensitiveWordsKit;
import com.bt.reg.RegValidator;
import com.jfinal.core.Controller;
import com.jfinal.kit.Ret;
import com.jfinal.validate.Validator;
/**
* AccountUpdateValidator 验证账号修改功能表单
*/
public class AccountUpdateValidator extends Validator {
protected void validate(Controller c) {
setShortCircuit(true);
/**
* 验证 nickName
*/
if (SensitiveWordsKit.checkSensitiveWord(c.getPara("account.nickName")) != null) {
addError("msg", "昵称不能包含敏感词");
}
validateRequired("account.nickName", "msg", "昵称不能为空");
validateString("account.nickName", 1, 19, "msg", "昵称不能超过19个字");
String nickName = c.getPara("account.nickName").trim();
if (nickName.contains("@") || nickName.contains("@")) { // 全角半角都要判断
addError("msg", "昵称不能包含 \"@\" 字符");
}
if (nickName.contains(" ") || nickName.contains(" ")) {
addError("msg", "昵称不能包含空格");
}
Ret ret = RegValidator.validateNickName(nickName);
if (ret.isFail()) {
addError("msg", ret.getStr("msg"));
}
/**
* 验证 userName
*/
validateRequired("account.userName", "msg", "邮箱不能为空");
validateEmail("account.userName", "msg", "邮箱格式不正确");
}
protected void handleError(Controller c) {
c.setAttr("state", "fail");
c.renderJson();
}
}

68
src/main/java/com/bt/_admin/auth/AdminAuthInterceptor.java

@ -0,0 +1,68 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.auth;
import com.jfinal.aop.Inject;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.bt.common.model.Account;
import com.bt.login.LoginService;
import com.jfinal.kit.Ret;
/**
* 后台管理员授权拦截器
*/
public class AdminAuthInterceptor implements Interceptor {
@Inject
AdminAuthService srv;
/**
* 用于 sharedObjectsharedMethod 扩展中使用
*/
private static final ThreadLocal<Account> threadLocal = new ThreadLocal<Account>();
public static Account getThreadLocalAccount() {
return threadLocal.get();
}
public void intercept(Invocation inv) {
Account loginAccount = inv.getController().getAttr(LoginService.loginAccountCacheName);
if (loginAccount != null && loginAccount.isStatusOk()) {
// 传递给 sharedObject、sharedMethod 扩展使用
threadLocal.set(loginAccount);
// 如果是超级管理员或者拥有对当前 action 的访问权限则放行
if ( srv.isSuperAdmin(loginAccount.getId()) ||
srv.hasPermission(loginAccount.getId(), inv.getActionKey())) {
inv.invoke();
return ;
}
}
// renderError(404) 避免暴露后台管理 url,增加安全性
if (loginAccount == null || inv.getActionKey().equals("/admin")) {
inv.getController().renderError(404);
}
// renderJson 提示没有操作权限,提升用户体验
else {
inv.getController().renderJson(Ret.fail("msg", "没有操作权限"));
}
}
}

87
src/main/java/com/bt/_admin/auth/AdminAuthKit.java

@ -0,0 +1,87 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.auth;
import com.jfinal.aop.Aop;
import com.bt.common.model.Account;
/**
* 权限管理的 shared method 扩展
*
* 作为 #role#permission 指令的补充支持 #else
*
*
* 使用示例
* #if (hasRole("权限管理员", "CEO", "CTO"))
* ...
* #else
* ...
* #end
*
* #if (hasPermission("/admin/project/edit"))
* ...
* #else
* ...
* #end
*/
public class AdminAuthKit {
/**
* 注意这里与控制器和拦截器不同不能使用 @Inject 注入
* 但可以使用 Aop.get(...) 实现同样的功能代码稍多点而已
*
* 不能使用 @Inject 注入的原因是 AdminAuthKit 工具对象
* 的创建并不是由 jfinal 接管的 controllerinterceptor
* 的创建是由 jfinal 接管的在接管后会自动进行注入动作
*
* 所以这里需要手动 Aop.get(...)
*/
static AdminAuthService adminAuthSrv = Aop.get(AdminAuthService.class);
/**
* 当前账号是否拥有某些角色
*/
public boolean hasRole(String... roleNameArray) {
Account account = AdminAuthInterceptor.getThreadLocalAccount();
if (account != null && account.isStatusOk()) {
if ( adminAuthSrv.isSuperAdmin(account.getId()) ||
adminAuthSrv.hasRole(account.getId(), roleNameArray)) {
return true;
}
}
return false;
}
/**
* 是否拥有具体某个权限
*/
public boolean hasPermission(String actionKey) {
Account account = AdminAuthInterceptor.getThreadLocalAccount();
if (account != null && account.isStatusOk()) {
if ( adminAuthSrv.isSuperAdmin(account.getId()) ||
adminAuthSrv.hasPermission(account.getId(), actionKey)) {
return true;
}
}
return false;
}
}

62
src/main/java/com/bt/_admin/auth/AdminAuthService.java

@ -0,0 +1,62 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.auth;
import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Db;
/**
* 后台管理员授权业务
*/
public class AdminAuthService {
/**
* 是否为超级管理员role.id 值为 1 的为超级管理员
*/
public boolean isSuperAdmin(int accountId) {
Integer ret = Db.template("admin.auth.isSuperAdmin", accountId).queryInt();
return ret != null;
}
/**
* 当前账号是否拥有某些角色
*/
public boolean hasRole(int accountId, String[] roleNameArray) {
if (roleNameArray == null || roleNameArray.length == 0) {
return false;
}
Kv data = Kv.by("accountId", accountId).set("roleNameArray", roleNameArray);
Integer ret = Db.template("admin.auth.hasRole", data).queryInt();
return ret != null;
}
/**
* 是否拥有具体某个权限
*/
public boolean hasPermission(int accountId, String actionKey) {
if (StrKit.isBlank(actionKey)) {
return false;
}
Integer ret = Db.template("admin.auth.hasPermission", actionKey, accountId).queryInt();
return ret != null;
}
}

34
src/main/java/com/bt/_admin/auth/admin_auth.sql

@ -0,0 +1,34 @@
### 验证是否为超级管理员, 超级管理员的 roleId 值为固定为 1
#sql("isSuperAdmin")
select accountId from account_role
where accountId = #para(0) and roleId = 1
limit 1
#end
### 验证是否拥有某个 role
#sql("hasRole")
select ar.accountId from account_role ar
inner join role r on ar.roleId = r.id
where ar.accountId = #para(accountId)
and (
#for (x : roleNameArray)
#(for.first ? "" : "or") r.name = #para(x.trim())
#end
)
limit 1
#end
### 验证是否拥有某个 permission
#sql("hasPermission")
select ar.accountId from (
select rp.roleId from role_permission rp
inner join permission p on rp.permissionId = p.id
where p.actionKey = #para(0)
)
as t inner join account_role ar on t.roleId = ar.roleId
where ar.accountId = #para(1)
limit 1
#end

39
src/main/java/com/bt/_admin/common/PjaxInterceptor.java

@ -0,0 +1,39 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.common;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
/**
* 设置 pjax 标志
*/
public class PjaxInterceptor implements Interceptor {
@Override
public void intercept(Invocation inv) {
try {
inv.invoke();
} finally {
Controller c = inv.getController();
boolean isPjax = "true".equalsIgnoreCase(c.getHeader("X-PJAX"));
c.setAttr("isPjax", isPjax);
}
}
}

103
src/main/java/com/bt/_admin/document/DocumentAdminController.java

@ -0,0 +1,103 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.document;
import com.bt._admin.permission.Remark;
import com.jfinal.aop.Inject;
import com.bt.common.controller.BaseController;
import com.jfinal.kit.Ret;
import com.bt.common.model.Document;
import com.jfinal.core.Path;
import java.util.List;
/**
* 文档管理控制器
* 暂不支持主菜单 doc 的显示主菜单 doc 现在仅用于我自己来 todolist 和大纲
*/
@Path(value = "/admin/doc", viewPath = "/document")
public class DocumentAdminController extends BaseController {
@Inject
DocumentAdminService srv;
@Remark("文档管理首页")
public void index() {
List<Document> docList = srv.getDocList();
setAttr("docList", docList);
render("index.html");
}
@Remark("创建文档")
public void add() {
List<Document> docList = srv.getDocList();
setAttr("docList", docList);
render("add_edit.html");
}
@Remark("创建文档提交")
public void save() {
Document doc = getBean(Document.class, "doc");
Ret ret = srv.save(doc);
renderJson(ret);
}
@Remark("修改文档")
public void edit() {
Document doc = srv.getByIds(getParaToInt("mainMenu"), getParaToInt("subMenu"));
if (doc == null) {
renderError(404);
}
setAttr("doc", doc);
render("add_edit.html");
}
@Remark("修改文档提交")
public void update() {
Document doc = getBean(Document.class, "doc");
Ret ret = srv.update(getParaToInt("oldMainMenu"), getParaToInt("oldSubMenu"), doc);
renderJson(ret);
}
@Remark("删除文档")
public void delete() {
Ret ret = srv.delete(getParaToInt("mainMenu"), getParaToInt("subMenu"));
renderJson(ret);
}
/**
* 发布
*/
@Remark("发布文档")
public void publish() {
Ret ret = srv.publish(getParaToInt("mainMenu"), getParaToInt("subMenu"));
renderJson(ret);
}
/**
* 取消发布成为草稿
*/
@Remark("取消发布文档")
public void unpublish() {
Ret ret = srv.unpublish(getParaToInt("mainMenu"), getParaToInt("subMenu"));
renderJson(ret);
}
}

121
src/main/java/com/bt/_admin/document/DocumentAdminService.java

@ -0,0 +1,121 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.document;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.kit.Ret;
import com.jfinal.aop.Inject;
import com.bt.common.model.Document;
import com.bt.document.DocumentService;
import java.time.LocalDateTime;
import java.util.List;
/**
* document 管理业务
*/
public class DocumentAdminService {
@Inject
DocumentService documentSrv;
private Document dao = new Document().dao();
// 加载一级文档,即便是 publish 为 0 的也加载
public List<Document> getDocList() {
List<Document> docList = dao.find("select * from document where subMenu = 0 order by mainMenu asc");
for (Document pDoc : docList) {
loadSubDocList(pDoc);
}
return docList;
}
// 加载二级文档,文档最多分两级目录,三级甚至更多级目录直接在 content 中体现
private void loadSubDocList(Document pDoc) {
int mainMenu = pDoc.getMainMenu();
String sql = "select * from document where mainMenu = ? and subMenu > 0 order by subMenu asc";
List<Document> subDocList = dao.find(sql, mainMenu);
pDoc.put("subDocList", subDocList);
}
public Document getByIds(int mainMenu, int subMenu) {
return dao.findByIds(mainMenu, subMenu);
}
public Ret save(Document doc) {
if (isExists(doc)) {
return Ret.fail("msg", "mainMenu 与 subMenu 组合已经存在");
}
doc.setCreateAt(LocalDateTime.now());
doc.setUpdateAt(LocalDateTime.now());
doc.save();
documentSrv.clearCache(); // 清缓存
return Ret.ok();
}
public Ret update(int oldMainMenu, int oldSubMenu, Document doc) {
// 当 mainMenu 或 subMenu 值也被修改的时候,判断一下新值是否已经存在
if (oldMainMenu != doc.getMainMenu() || oldSubMenu != doc.getSubMenu()) {
if (isExists(doc)) {
return Ret.fail("msg", "mainMenu 或 subMenu 已经存在,不能使用");
}
}
if (oldMainMenu != doc.getMainMenu() || oldSubMenu != doc.getSubMenu()) {
Db.update("update document set mainMenu=?, subMenu=? where mainMenu=? and subMenu=?",
doc.getMainMenu(), doc.getSubMenu(), oldMainMenu, oldSubMenu);
}
doc.setUpdateAt(LocalDateTime.now());
doc.update();
documentSrv.clearCache(); // 清缓存
return Ret.ok();
}
public Ret delete(int mainMenu, int subMenu) {
Db.update("delete from document where mainMenu=? and subMenu=? limit 1", mainMenu, subMenu);
documentSrv.clearCache(); // 清缓存
return Ret.ok("msg", "document 删除成功");
}
private boolean isExists(Document doc) {
String sql = "select mainMenu from document where mainMenu=? and subMenu=? limit 1";
return Db.queryInt(sql , doc.getMainMenu(), doc.getSubMenu()) != null;
}
final String publishSql = "update document set publish = ? where mainMenu=? and subMenu=?";
/**
* 发布
*/
public Ret publish(int mainMenu, int subMenu) {
Db.update(publishSql, Document.PUBLISH_YES, mainMenu, subMenu);
documentSrv.clearCache(); // 清缓存
return Ret.ok("msg", "发布成功");
}
/**
* 取消发布变草稿
*/
public Ret unpublish(int mainMenu, int subMenu) {
Db.update(publishSql, Document.PUBLISH_NO, mainMenu, subMenu);
documentSrv.clearCache(); // 清缓存
return Ret.ok("msg", "取消发布成功");
}
}

168
src/main/java/com/bt/_admin/feedback/FeedbackAdminController.java

@ -0,0 +1,168 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.feedback;
import com.jfinal.aop.Before;
import com.jfinal.aop.Inject;
import com.bt.common.account.AccountService;
import com.bt.my.feedback.MyFeedbackValidator;
import com.bt.project.ProjectService;
import com.jfinal.core.Path;
import com.jfinal.plugin.activerecord.Page;
import com.bt.common.controller.BaseController;
import com.jfinal.kit.Ret;
import com.bt.common.model.Feedback;
import com.bt.common.model.FeedbackReply;
import com.bt.feedback.FeedbackService;
import com.bt.index.IndexService;
import java.util.List;
/**
* 反馈管理控制器
*
* 注意sql 语句与业务逻辑要写在业务层在此仅由于时间仓促偷懒的做法
* 后续版本会改掉这样的用法请小伙伴们不要效仿
*/
@Path(value = "/admin/feedback", viewPath = "/feedback")
public class FeedbackAdminController extends BaseController {
@Inject
FeedbackAdminService srv;
@Inject
FeedbackService feedbackSrv;
@Inject
ProjectService projectSrv;
@Inject
AccountService accountSrv;
@Inject
IndexService indexSrv;
public void index() {
Page<Feedback> feedbackPage = srv.paginate(getParaToInt("p", 1));
setAttr("feedbackPage", feedbackPage);
render("index.html");
}
/**
* 创建
*/
public void add() {
setAttr("projectList", projectSrv.getAllProject("id, name")); // 关联项目下拉列表
render("add_edit.html");
}
/**
* 提交创建
*/
@Before(MyFeedbackValidator.class)
public void save() {
Feedback feedback = getBean(Feedback.class);
Ret ret = srv.save(getLoginAccountId(), feedback);
renderJson(ret);
}
/**
* 修改
*/
public void edit() {
keepPara("p"); // 保持住分页的页号,便于在 ajax 提交后跳转到当前数据所在的页
setAttr("projectList", projectSrv.getAllProject("id, name")); // 关联项目下拉列表
setAttr("feedback", srv.edit(getParaToInt("id")));
render("add_edit.html");
}
/**
* 提交修改
*/
@Before(MyFeedbackValidator.class)
public void update() {
Feedback feedback = getBean(Feedback.class);
Ret ret = srv.update(feedback);
renderJson(ret);
}
/**
* 锁定目前先做成使用举报量 report 值锁定
*/
public void lock() {
Ret ret = srv.lock(getParaToInt("id"));
feedbackSrv.clearHotFeedbackCache(); // 清缓存
indexSrv.clearCache();
renderJson(ret);
}
/**
* 解除锁定
*/
public void unlock() {
Ret ret = srv.unlock(getParaToInt("id"));
feedbackSrv.clearHotFeedbackCache(); // 清缓存
indexSrv.clearCache();
renderJson(ret);
}
/**
* 删除 feedback
*/
public void delete() {
Ret ret = srv.delete(getParaToInt("id"));
renderJson(ret);
}
/**
* 获取 feedback reply 列表
*/
public void getReplyList() {
int feedbackId = getParaToInt("feedbackId");
Feedback feedback = srv.getById(feedbackId);
List<FeedbackReply> feedbackReplyList = srv.getReplyList(feedbackId);
accountSrv.join("accountId", feedbackReplyList, "nickName");
setAttr("feedback", feedback);
setAttr("feedbackReplyList", feedbackReplyList);
setAttr("feedbackId", feedbackId);
render("reply.html");
}
/**
* 获取 feedback reply
*/
public void getReply() {
int replyId = getParaToInt("replyId");
Ret ret = srv.getReply(replyId);
renderJson(ret);
}
/**
* 删除 feedback reply
*/
public void deleteReply() {
int replyId = getParaToInt("replyId");
Ret ret = srv.deleteReply(replyId);
renderJson(ret);
}
}

121
src/main/java/com/bt/_admin/feedback/FeedbackAdminService.java

@ -0,0 +1,121 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.feedback;
import com.jfinal.aop.Inject;
import com.bt.common.model.Feedback;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Db;
import com.bt.common.model.FeedbackReply;
import com.bt.my.feedback.MyFeedbackService;
import com.jfinal.plugin.activerecord.Page;
import java.time.LocalDateTime;
import java.util.List;
/**
* feedback 管理业务
*/
public class FeedbackAdminService {
@Inject
MyFeedbackService myFeedbackSrv;
private Feedback dao = new Feedback().dao();
private FeedbackReply feedbackReplyDao = new FeedbackReply().dao();
/**
* feedback 分页
*/
public Page<Feedback> paginate(int pageNum) {
return dao.paginate(pageNum, 10, "select *", "from feedback order by id desc");
}
/**
* 创建反馈
*/
public Ret save(int accountId, Feedback feedback) {
feedback.setAccountId(accountId);
feedback.setTitle(feedback.getTitle().trim());
feedback.setCreateAt(LocalDateTime.now());
feedback.save();
return Ret.ok("msg", "创建成功");
}
public Feedback edit(int id) {
return dao.findById(id);
}
public Ret update(Feedback feedback) {
feedback.update();
return Ret.ok("msg", "修改成功");
}
public Ret lock(int id) {
Db.update("update feedback set report = report + ? where id=?", Feedback.REPORT_BLOCK_NUM, id);
return Ret.ok("msg", "锁定成功");
}
public Ret unlock(int id) {
Db.update("update feedback set report = 0 where id=?", id);
return Ret.ok("msg", "解除锁定成功");
}
/**
* 删除 feedback
*/
public Ret delete(int feedbackId) {
Integer accountId = Db.queryInt("select accountId from feedback where id=? limit 1", feedbackId);
if (accountId != null) {
myFeedbackSrv.delete(accountId, feedbackId);
return Ret.ok("msg", "feedback 删除成功");
} else {
return Ret.fail("msg", "feedback 删除失败");
}
}
public Feedback getById(int feedbackId) {
return dao.findById(feedbackId);
}
/**
* 获取 reply list
*/
public List<FeedbackReply> getReplyList(int feedbackId) {
String sql = "select id, accountId, createAt, substring(content, 1, 30) as content from feedback_reply where feedbackId=? order by id desc";
return feedbackReplyDao.find(sql, feedbackId);
}
public Ret getReply(int replyId) {
FeedbackReply reply = feedbackReplyDao.findById(replyId);
return Ret.ok("reply", reply);
}
/**
* 删除 feedback reply
*/
public Ret deleteReply(int feedbackReplyId) {
Integer accountId = Db.queryInt("select accountId from feedback_reply where id=? limit 1", feedbackReplyId);
if (accountId != null) {
myFeedbackSrv.deleteFeedbackReplyById(accountId, feedbackReplyId);
return Ret.ok("msg", "feedback reply 删除成功");
} else {
return Ret.fail("msg", "feedback reply 删除失败");
}
}
}

42
src/main/java/com/bt/_admin/index/IndexAdminController.java

@ -0,0 +1,42 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.index;
import com.jfinal.aop.Inject;
import com.bt.common.controller.BaseController;
import com.jfinal.core.Path;
/**
* 后台管理首页
*/
@Path(value = "/admin", viewPath = "/index")
public class IndexAdminController extends BaseController {
@Inject
IndexAdminService srv;
public void index() {
setAttr("accountProfile", srv.getAccountProfile());
setAttr("projectProfile", srv.getProjectProfile());
setAttr("shareProfile", srv.getShareProfile());
setAttr("feedbackProfile", srv.getFeedbackProfile());
setAttr("permissionProfile", srv.getPermissionProfile());
render("index.html");
}
}

62
src/main/java/com/bt/_admin/index/IndexAdminService.java

@ -0,0 +1,62 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.index;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Db;
/**
* 首页业务
*/
public class IndexAdminService {
public Ret getAccountProfile() {
Ret ret = Ret.by("title", "账户总数");
Integer total = Db.queryInt("select count(*) from account");
ret.set("total", total);
return ret;
}
public Ret getProjectProfile() {
Ret ret = Ret.by("title", "项目总数");
Integer total = Db.queryInt("select count(*) from project");
ret.set("total", total);
return ret;
}
public Ret getShareProfile() {
Ret ret = Ret.by("title", "分享总数");
Integer total = Db.queryInt("select count(*) from share");
ret.set("total", total);
return ret;
}
public Ret getFeedbackProfile() {
Ret ret = Ret.by("title", "反馈总数");
Integer total = Db.queryInt("select count(*) from feedback");
ret.set("total", total);
return ret;
}
public Ret getPermissionProfile() {
Ret ret = Ret.by("title", "权限总数");
Integer total = Db.queryInt("select count(*) from permission");
ret.set("total", total);
return ret;
}
}

66
src/main/java/com/bt/_admin/permission/PermissionAdminController.java

@ -0,0 +1,66 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.permission;
import com.jfinal.aop.Inject;
import com.bt.common.controller.BaseController;
import com.bt.common.model.Permission;
import com.jfinal.core.Path;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Page;
/**
* 权限管理
*/
@Path(value = "/admin/permission", viewPath = "/permission")
public class PermissionAdminController extends BaseController {
@Inject
PermissionAdminService srv;
public void index() {
Page<Permission> permissionPage = srv.paginate(getParaToInt("p", 1));
srv.replaceControllerPrefix(permissionPage, "com.jfinal.club._admin.", "...");
boolean hasRemovedPermission = srv.markRemovedActionKey(permissionPage);
setAttr("permissionPage", permissionPage);
setAttr("hasRemovedPermission", hasRemovedPermission);
render("index.html");
}
public void sync() {
Ret ret = srv.sync();
renderJson(ret);
}
public void edit() {
keepPara("p"); // 保持住分页的页号,便于在 ajax 提交后跳转到当前数据所在的页
Permission permission = srv.findById(getParaToInt("id"));
setAttr("permission", permission);
render("edit.html");
}
public void update() {
Ret ret = srv.update(getBean(Permission.class));
renderJson(ret);
}
public void delete() {
Ret ret = srv.delete(getParaToInt("id"));
renderJson(ret);
}
}

177
src/main/java/com/bt/_admin/permission/PermissionAdminService.java

@ -0,0 +1,177 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.permission;
import com.bt.common.model.Permission;
import com.jfinal.core.Action;
import com.jfinal.core.JFinal;
import com.jfinal.kit.Ret;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Page;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 权限管理业务
*/
public class PermissionAdminService {
private Permission dao = new Permission().dao();
// 一键同步功能需要排除掉的 actionKey
private Set<String> excludedActionKey = buildExcludedActionKey();
/**
* 一键同步功能需要排除的 actionKey在本方法中添加
*/
private Set<String> buildExcludedActionKey() {
Set<String> ret = new HashSet<String>();
// ret.add("/admin/captcha");
return ret;
}
public Page<Permission> paginate(int pageNum) {
return dao.paginate(pageNum, 10, "select *", "from permission order by actionKey asc");
}
/**
* 在已被移除的 permission put 进去一个 removed 值为 true 的标记便于在界面显示不同的样式
* @return 存在被删除的 actionKey 时返回 true否则返回 false
*/
public boolean markRemovedActionKey(Page<Permission> permissionPage) {
boolean ret = false;
for (Permission p : permissionPage.getList()) {
String actionKey = p.getActionKey();
String[] urlPara = new String[1];
Action action = JFinal.me().getAction(actionKey, urlPara);
if (action == null || ! actionKey.equals(action.getActionKey())) {
p.put("removed", true);
ret = true;
}
}
return ret;
}
/**
* 替换控制器前缀界面显示时更加美观
*
* 例子
* replaceControllerPrefix(permissionPage, "com.jfinal.club._admin.", "...");
* 以上例子将 "com.jfinal.club._admin." 这一长串前缀替换成 "..."显示更美观
*/
public void replaceControllerPrefix(Page<Permission> permissionPage, String replaceTarget, String replacement) {
for (Permission p : permissionPage.getList()) {
String c = p.getController().replace(replaceTarget, replacement);
p.setController(c);
}
}
/**
* 同步 permission
* 获取后台管理所有 actionKey 以及 controller将数据自动写入 permission
* 随着开发过程的前行可以动态进行同步添加新的 permission 数据
*/
public Ret sync() {
int counter = 0;
List<String> allActionKeys = JFinal.me().getAllActionKeys();
for (String actionKey : allActionKeys) {
// 只处理后台管理 action,其它跳过
if ( !actionKey.startsWith("/admin") ) {
continue ;
}
// 跳过需要排除的 actionKey
if (excludedActionKey.contains(actionKey)) {
continue ;
}
String[] urlPara = new String[1];
Action action = JFinal.me().getAction(actionKey, urlPara);
// 没有拦截器的 action 任何人都能访问,需要排除掉,例如:"/admin/login" "/admin/logout"
if (action == null || action.getInterceptors().length == 0) {
continue ;
}
String controller = action.getControllerClass().getName();
String sql = "select * from permission where actionKey=? and controller = ? limit 1";
Permission permission = dao.findFirst(sql, actionKey, controller);
if (permission == null) {
permission = new Permission();
permission.setActionKey(actionKey);
permission.setController(controller);
setRemarkValue(permission, action);
permission.save();
counter++;
} else {
// 如果 remark 字段是空值,才去尝试使用 @Remark 注解中的值
if (StrKit.isBlank(permission.getRemark())) {
setRemarkValue(permission, action);
if (permission.update()) {
counter++;
}
}
}
}
if (counter == 0) {
return Ret.ok("msg", "权限已经是最新状态,无需更新");
} else {
return Ret.ok("msg", "权限更新成功,共更新权限数 : " + counter);
}
}
private void setRemarkValue(Permission permission, Action action) {
Remark remark = action.getMethod().getAnnotation(Remark.class);
if (remark != null && StrKit.notBlank(remark.value())) {
permission.setRemark(remark.value());
}
}
public Permission findById(int id) {
return dao.findById(id);
}
public List<Permission> getAllPermissions() {
return dao.find("select * from permission order by controller asc");
}
public Ret update(Permission permission) {
permission.keep("id", "remark"); // 暂时只允许更新 remark
permission.update();
return Ret.ok("msg", "更新成功");
}
/**
* 如果某个 action 已经删掉或者改了名称可以使用该方法删除
*/
public Ret delete(final int permissionId) {
Db.tx(() -> {
Db.delete("delete from role_permission where permissionId = ?", permissionId);
dao.deleteById(permissionId);
return true;
});
return Ret.ok("msg", "权限删除成功");
}
}

72
src/main/java/com/bt/_admin/permission/PermissionDirective.java

@ -0,0 +1,72 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.permission;
import com.jfinal.aop.Aop;
import com.bt._admin.auth.AdminAuthService;
import com.bt.common.model.Account;
import com.bt.login.LoginService;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
* 界面上的权限控制功能
* 用来控制界面上的菜单按钮等等元素的显示
*
* 使用示例见模板文件 /_view/_admin/project/index.html 或者 /_view/_admin/permission/index.html
* #permission("/admin/project/edit")
* <a href="/admin/project/edit?id=#(x.id)">
* <i class="fa fa-pencil" title="修改"></i>
* </a>
* #end
*
* 别名 #perm() #end
*/
public class PermissionDirective extends Directive {
static AdminAuthService adminAuthSrv = Aop.get(AdminAuthService.class);
public void exec(Env env, Scope scope, Writer writer) {
Account account = (Account)scope.getRootData().get(LoginService.loginAccountCacheName);
if (account != null && account.isStatusOk()) {
// 如果是超级管理员,或者拥有指定的权限则放行
if ( adminAuthSrv.isSuperAdmin(account.getId()) ||
adminAuthSrv.hasPermission(account.getId(), getPermission(scope))) {
stat.exec(env, scope, writer);
}
}
}
/**
* #permission 指令参数中获取 permission
*/
private String getPermission(Scope scope) {
Object value = exprList.eval(scope);
if (value instanceof String) {
return (String)value;
} else {
throw new IllegalArgumentException("权限参数只能为 String 类型");
}
}
public boolean hasEnd() {
return true;
}
}

32
src/main/java/com/bt/_admin/permission/Remark.java

@ -0,0 +1,32 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.permission;
import java.lang.annotation.*;
/**
* 权限一键同步功能时自动向 permission 表的 remark 字段中
* 添加 @Remark 注解的内容注意只在 remark 字段为空时才添加
* 否则会覆盖掉用户自己添加的内容
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Remark {
String value();
}

10
src/main/java/com/bt/_admin/permission/新添加的表.txt

@ -0,0 +1,10 @@
role(id, name)
permission(id, actionKey, controller, remark)
account_role(accountId, roleId)
role_permission(roleId, permissionId)

119
src/main/java/com/bt/_admin/project/ProjectAdminController.java

@ -0,0 +1,119 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.project;
import com.jfinal.aop.Before;
import com.jfinal.aop.Inject;
import com.bt.my.project.MyProjectValidator;
import com.jfinal.plugin.activerecord.Page;
import com.bt.common.controller.BaseController;
import com.jfinal.kit.Ret;
import com.bt.common.model.Project;
import com.bt.index.IndexService;
import com.bt.project.ProjectService;
import com.jfinal.core.Path;
/**
* 项目管理控制器
*/
@Path(value = "/admin/project", viewPath = "/project")
public class ProjectAdminController extends BaseController {
@Inject
ProjectAdminService srv;
@Inject
ProjectService projectSrv;
@Inject
IndexService indexSrv;
public void index() {
Page<Project> projectPage = srv.paginate(getParaToInt("p", 1));
setAttr("projectPage", projectPage);
render("index.html");
}
/**
* 创建
*/
public void add() {
render("add_edit.html");
}
/**
* 提交创建
*/
@Before(MyProjectValidator.class)
public void save() {
Project project = getBean(Project.class);
Ret ret = srv.save(getLoginAccountId(), project);
renderJson(ret);
}
/**
* 修改
*/
public void edit() {
keepPara("p"); // 保持住分页的页号,便于在 ajax 提交后跳转到当前数据所在的页
setAttr("project", srv.edit(getParaToInt("id")));
render("add_edit.html");
}
/**
* 提交修改
*/
@Before(MyProjectValidator.class)
public void update() {
Project project = getBean(Project.class);
Ret ret = srv.update(project);
renderJson(ret);
}
/**
* 锁定
*/
public void lock() {
Ret ret = srv.lock(getParaToInt("id"));
projectSrv.clearHotProjectCache();
indexSrv.clearCache();
renderJson(ret);
}
/**
* 解除锁定
*/
public void unlock() {
Ret ret = srv.unlock(getParaToInt("id"));
projectSrv.clearHotProjectCache();
indexSrv.clearCache();
renderJson(ret);
}
/**
* 删除 project
*/
public void delete() {
Ret ret = srv.delete(getParaToInt("id"));
renderJson(ret);
}
}

115
src/main/java/com/bt/_admin/project/ProjectAdminService.java

@ -0,0 +1,115 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.project;
import com.jfinal.aop.Inject;
import com.bt.common.model.Project;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Db;
import com.bt.my.project.MyProjectService;
import com.jfinal.plugin.activerecord.Page;
import java.time.LocalDateTime;
/**
* project 管理业务
*/
public class ProjectAdminService {
@Inject
MyProjectService myProjectSrv;
private Project dao = new Project().dao();
/**
* 项目分页
*/
public Page<Project> paginate(int pageNum) {
return dao.paginate(pageNum, 10, "select *", "from project order by id desc");
}
/**
* 判断项目名称是否存在
* @param projectId 当前 project 对象的 id 如果 project 对象还未创建提供一个小于 0 的值即可
* @param name 项目名
*/
public boolean exists(int projectId, String name) {
name = name.toLowerCase().trim();
String sql = "select id from project where lower(name) = ? and id != ? limit 1";
Integer id = Db.queryInt(sql, name, projectId);
return id != null;
}
/**
* 创建项目
*/
public Ret save(int accountId, Project project) {
if (exists(-1, project.getName())) {
return Ret.fail("msg", "项目名称已经存在,请输入别的名称");
}
project.setAccountId(accountId);
project.setName(project.getName().trim());
project.setCreateAt(LocalDateTime.now());
project.save();
return Ret.ok("msg", "创建成功");
}
public Project edit(int id) {
return dao.findById(id);
}
public Ret update(Project project) {
if (exists(project.getId(), project.getName())) {
return Ret.fail("msg", "项目名称已经存在,请输入别的名称");
}
project.setName(project.getName().trim());
project.update();
return Ret.ok("msg", "修改成功");
}
/**
* 锁定
*/
public Ret lock(int id) {
Db.update("update project set report = report + ? where id=?", Project.REPORT_BLOCK_NUM, id);
return Ret.ok("msg", "锁定成功");
}
/**
* 解除锁定
*/
public Ret unlock(int id) {
Db.update("update project set report = 0 where id=?", id);
return Ret.ok("msg", "解除锁定成功");
}
/**
* 删除 project
*/
public Ret delete(int projectId) {
Integer accountId = Db.queryInt("select accountId from project where id=? limit 1", projectId);
if (accountId != null) {
myProjectSrv.delete(accountId, projectId);
return Ret.ok("msg", "project 删除成功");
} else {
return Ret.fail("msg", "project 删除失败");
}
}
}

112
src/main/java/com/bt/_admin/role/RoleAdminController.java

@ -0,0 +1,112 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.role;
import com.bt._admin.permission.PermissionAdminService;
import com.jfinal.aop.Before;
import com.jfinal.aop.Inject;
import com.bt.common.model.Permission;
import com.bt.common.model.Role;
import com.jfinal.core.Path;
import com.jfinal.plugin.activerecord.Page;
import com.bt.common.controller.BaseController;
import com.jfinal.kit.Ret;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 角色管理控制器
*/
@Path(value = "/admin/role", viewPath = "/role")
public class RoleAdminController extends BaseController {
@Inject
RoleAdminService srv;
@Inject
PermissionAdminService permissionAdminSrv;
public void index() {
Page<Role> rolePage = srv.paginate(getParaToInt("p", 1));
setAttr("rolePage", rolePage);
render("index.html");
}
public void add() {
render("add_edit.html");
}
@Before(RoleAdminValidator.class)
public void save() {
Role role = getBean(Role.class);
Ret ret = srv.save(role);
renderJson(ret);
}
public void edit() {
keepPara("p"); // 保持住分页的页号,便于在 ajax 提交后跳转到当前数据所在的页
Role role = srv.findById(getParaToInt("id"));
setAttr("role", role);
render("add_edit.html");
}
/**
* 提交修改
*/
@Before(RoleAdminValidator.class)
public void update() {
Role role = getBean(Role.class);
Ret ret = srv.update(role);
renderJson(ret);
}
public void delete() {
Ret ret = srv.delete(getParaToInt("id"));
renderJson(ret);
}
/**
* 分配权限
*/
public void assignPermissions() {
Role role = srv.findById(getParaToInt("id"));
List<Permission> permissionList = permissionAdminSrv.getAllPermissions();
srv.markAssignedPermissions(role, permissionList);
LinkedHashMap<String, List<Permission>> permissionMap = srv.groupByController(permissionList);
setAttr("role", role);
setAttr("permissionMap", permissionMap);
render("assign_permissions.html");
}
/**
* 添加权限
*/
public void addPermission() {
Ret ret = srv.addPermission(getParaToInt("roleId"), getParaToInt("permissionId"));
renderJson(ret);
}
/**
* 删除权限
*/
public void deletePermission() {
Ret ret = srv.deletePermission(getParaToInt("roleId"), getParaToInt("permissionId"));
renderJson(ret);
}
}

172
src/main/java/com/bt/_admin/role/RoleAdminService.java

@ -0,0 +1,172 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.role;
import com.bt.common.model.Permission;
import com.bt.common.model.Role;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Page;
import com.jfinal.plugin.activerecord.Record;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
/**
* 角色管理
*/
public class RoleAdminService {
private Role dao = new Role().dao();
public Page<Role> paginate(int pageNum) {
return dao.paginate(pageNum, 10, "select *", "from role order by id asc");
}
public Role findById(int roleId) {
return dao.findById(roleId);
}
public List<Role> getAllRoles() {
return dao.find("select * from role order by id asc");
}
/**
* 判断角色名是否存在
* @param roleId 当前 role 对象的 id 如果 role 对象还未创建提供一个小于 0 的值即可
* @param name 角色名
*/
public boolean exists(int roleId, String name) {
name = name.toLowerCase().trim();
String sql = "select id from role where lower(name) = ? and id != ? limit 1";
Integer id = Db.queryInt(sql, name, roleId);
return id != null;
}
/**
* 创建角色
*/
public Ret save(Role role) {
if (exists(-1, role.getName())) {
return Ret.fail("msg", "角色名称已经存在,请输入别的名称");
}
role.setName(role.getName().trim());
role.setCreateAt(LocalDateTime.now());
role.save();
return Ret.ok("msg", "创建成功");
}
/**
* 更新角色
*/
public Ret update(Role role) {
if (exists(role.getId(), role.getName())) {
return Ret.fail("msg", "角色名称已经存在,请输入别的名称");
}
role.setName(role.getName().trim());
role.update();
return Ret.ok("msg", "角色更新成功");
}
public Ret delete(final int roleId) {
if (roleId == 1) {
return Ret.fail("msg", "id 值为 1 的超级管理员不能被删除");
}
Db.tx(() -> {
Db.delete("delete from account_role where roleId=?", roleId);
Db.delete("delete from role_permission where roleId=?", roleId);
dao.deleteById(roleId);
return true;
});
return Ret.ok("msg", "角色删除成功");
}
/**
* 添加权限
*/
public Ret addPermission(int roleId, int permissionId) {
if (roleId == 1) {
return Ret.fail("msg", "超级管理员天然拥有所有权限,无需分配");
}
Record rolePermission = new Record().set("roleId", roleId).set("permissionId", permissionId);
Db.save("role_permission", rolePermission);
return Ret.ok("msg", "添加权限成功");
}
/**
* 删除权限
*/
public Ret deletePermission(int roleId, int permissionId) {
if (roleId == 1) {
return Ret.fail("msg", "超级管理员天然拥有所有权限,不能删除权限");
}
Db.delete("delete from role_permission where roleId=? and permissionId=?", roleId, permissionId);
return Ret.ok("msg", "删除权限成功");
}
/**
* 标记出 role 拥有的权限用于在界面输出 checkbox checked 属性
* 未来用 permission left join role_permission 来优化
*/
public void markAssignedPermissions(Role role, List<Permission> permissionList) {
// id 为 1 的超级管理员默认拥有所有权限
if (role.getId() == 1) {
for (Permission permission : permissionList) {
permission.put("assigned", true);
}
return ;
}
String sql = "select roleId from role_permission where roleId=? and permissionId=? limit 1";
for (Permission permission : permissionList) {
Integer roleId = Db.queryInt(sql, role.getId(), permission.getId());
if (roleId != null) {
// 设置 assigned 用于界面输出 checked
permission.put("assigned", true);
}
}
}
/**
* 根据 controller permission 进行分组
*/
public LinkedHashMap<String, List<Permission>> groupByController(List<Permission> permissionList) {
LinkedHashMap<String, List<Permission>> ret = new LinkedHashMap<String, List<Permission>>();
for (Permission permission : permissionList) {
String controller = permission.getController();
List<Permission> list = ret.get(controller);
if (list == null) {
list = new ArrayList<Permission>();
ret.put(controller, list);
}
list.add(permission);
}
return ret;
}
}

39
src/main/java/com/bt/_admin/role/RoleAdminValidator.java

@ -0,0 +1,39 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.role;
import com.jfinal.core.Controller;
import com.jfinal.validate.Validator;
/**
* AccountUpdateValidator 验证角色修改功能表单
*/
public class RoleAdminValidator extends Validator {
protected void validate(Controller c) {
setShortCircuit(true);
validateRequiredString("role.name", "msg", "角色名称不能为空");
}
protected void handleError(Controller c) {
c.setAttr("state", "fail");
c.renderJson();
}
}

72
src/main/java/com/bt/_admin/role/RoleDirective.java

@ -0,0 +1,72 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.role;
import com.jfinal.aop.Aop;
import com.bt._admin.auth.AdminAuthService;
import com.bt.common.model.Account;
import com.bt.login.LoginService;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.Scope;
/**
* 界面上的权限控制功能
* 用来控制界面上的菜单按钮等等元素的显示
*
* 使用示例见模板文件 /_view/_admin/common/_menu.html 或者 /_view/_admin/permission/index.html
* #role("权限管理员", "CEO", "CTO")
* ...
* #end
*/
public class RoleDirective extends Directive {
static AdminAuthService adminAuthSrv = Aop.get(AdminAuthService.class);
public void exec(Env env, Scope scope, Writer writer) {
Account account = (Account)scope.getRootData().get(LoginService.loginAccountCacheName);
if (account != null && account.isStatusOk()) {
// 如果是超级管理员,或者拥有指定的角色则放行
if ( adminAuthSrv.isSuperAdmin(account.getId()) ||
adminAuthSrv.hasRole(account.getId(), getRoleNameArray(scope))) {
stat.exec(env, scope, writer);
}
}
}
/**
* #role 指令参数中获取角色名称数组
*/
private String[] getRoleNameArray(Scope scope) {
Object[] values = exprList.evalExprList(scope);
String[] ret = new String[values.length];
for (int i=0; i<values.length; i++) {
if (values[i] instanceof String) {
ret[i] = (String)values[i];
} else {
throw new IllegalArgumentException("角色名只能为 String 类型");
}
}
return ret;
}
public boolean hasEnd() {
return true;
}
}

165
src/main/java/com/bt/_admin/share/ShareAdminController.java

@ -0,0 +1,165 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.share;
import com.jfinal.aop.Before;
import com.jfinal.aop.Inject;
import com.bt.common.account.AccountService;
import com.bt.my.share.MyShareValidator;
import com.bt.project.ProjectService;
import com.jfinal.plugin.activerecord.Page;
import com.bt.common.controller.BaseController;
import com.jfinal.kit.Ret;
import com.bt.common.model.Share;
import com.bt.common.model.ShareReply;
import com.bt.index.IndexService;
import com.bt.share.ShareService;
import com.jfinal.core.Path;
import java.util.List;
/**
* 分享管理控制器
*/
@Path(value = "/admin/share", viewPath = "/share")
public class ShareAdminController extends BaseController {
@Inject
ShareAdminService srv;
@Inject
ShareService shareSrv;
@Inject
ProjectService projectSrv;
@Inject
AccountService accountSrv;
@Inject
IndexService indexSrv;
public void index() {
Page<Share> sharePage = srv.paginate(getParaToInt("p", 1));
setAttr("sharePage", sharePage);
render("index.html");
}
/**
* 创建
*/
public void add() {
setAttr("projectList", projectSrv.getAllProject("id, name")); // 关联项目下拉列表
render("add_edit.html");
}
/**
* 提交创建
*/
@Before(MyShareValidator.class)
public void save() {
Share share = getBean(Share.class);
Ret ret = srv.save(getLoginAccountId(), share);
renderJson(ret);
}
/**
* 修改
*/
public void edit() {
keepPara("p"); // 保持住分页的页号,便于在 ajax 提交后跳转到当前数据所在的页
setAttr("projectList", projectSrv.getAllProject("id, name")); // 关联项目下拉列表
setAttr("share", srv.edit(getParaToInt("id")));
render("add_edit.html");
}
/**
* 提交修改
*/
@Before(MyShareValidator.class)
public void update() {
Share share = getBean(Share.class);
Ret ret = srv.update(share);
renderJson(ret);
}
/**
* 锁定
*/
public void lock() {
Ret ret = srv.lock(getParaToInt("id"));
shareSrv.clearHotShareCache();
indexSrv.clearCache();
renderJson(ret);
}
/**
* 解除锁定
*/
public void unlock() {
Ret ret = srv.unlock(getParaToInt("id"));
shareSrv.clearHotShareCache();
indexSrv.clearCache();
renderJson(ret);
}
/**
* 删除 share
*/
public void delete() {
Ret ret = srv.delete(getParaToInt("id"));
renderJson(ret);
}
/**
* 获取 share reply 列表
*/
public void getReplyList() {
int shareId = getParaToInt("shareId");
Share share = srv.getById(shareId);
List<ShareReply> shareReplyList = srv.getReplyList(shareId);
accountSrv.join("accountId", shareReplyList, "nickName");
setAttr("share", share);
setAttr("shareReplyList", shareReplyList);
setAttr("shareId", shareId);
render("reply.html");
}
/**
* 获取 share reply
*/
public void getReply() {
int replyId = getParaToInt("replyId");
Ret ret = srv.getReply(replyId);
renderJson(ret);
}
/**
* 删除 share reply
*/
public void deleteReply() {
int replyId = getParaToInt("replyId");
Ret ret = srv.deleteReply(replyId);
renderJson(ret);
}
}

133
src/main/java/com/bt/_admin/share/ShareAdminService.java

@ -0,0 +1,133 @@
/**
* 请勿将俱乐部专享资源复制给任何人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目源码直播视频专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多俱乐部福利资源是不断增加的以下是俱乐部
* 新福利计划
* https://jfinal.com/club/1-2
*
* JFinal 俱乐部是七年以来首次寻求外部资源的尝试以便于创建更加高品质的产品与服务为你带来
* 更多更大的价值
*
* JFinal 项目的可持续性发展需要你的支持
*/
package com.bt._admin.share;
import com.jfinal.aop.Inject;
import com.bt.common.model.Share;
import com.jfinal.kit.Ret;
import com.jfinal.plugin.activerecord.Db;
import com.bt.common.model.ShareReply;
import com.bt.my.share.MyShareService;
import com.jfinal.plugin.activerecord.Page;
import java.time.LocalDateTime;
import java.util.List;
/**
* share 管理业务
*/
public class ShareAdminService {
@Inject
MyShareService myShareSrv;
private Share dao = new Share().dao();
private ShareReply shareReplyDao = new ShareReply().dao();
/**
* share 分页
*/
public Page<Share> paginate(int pageNum) {
return dao.paginate(pageNum, 10, "select *", "from share order by id desc");
}
/**
* 创建分享
*/
public Ret save(int accountId, Share project) {
project.setAccountId(accountId);
project.setTitle(project.getTitle().trim());
project.setCreateAt(LocalDateTime.now());
project.save();
return Ret.ok("msg", "创建成功");
}
public Share edit(int id) {
return dao.findById(id);
}
public Ret update(Share share) {
share.update();
return Ret.ok("msg", "修改成功");
}
/**
* 锁定
*/
public Ret lock(int id) {
Db.update("update share set report = report + ? where id=?", Share.REPORT_BLOCK_NUM, id);
return Ret.ok("msg", "锁定成功");
}
/**
* 解除锁定
*/
public Ret unlock(int id) {
Db.update("update share set report = 0 where id=?", id);
return Ret.ok("msg", "解除锁定成功");
}
/**
* 删除 share
*/
public Ret delete(int shareId) {
Integer accountId = Db.queryInt("select accountId from share where id=? limit 1", shareId);
if (accountId != null) {
myShareSrv.delete(accountId, shareId);
return Ret.ok("msg", "share 删除成功");
} else {
return Ret.fail("msg", "share 删除失败");
}
}
public Share getById(int shareId) {
return dao.findById(shareId);
}
/**
* 获取 reply list
*/
public List<ShareReply> getReplyList(int shareId) {
String sql = "select id, accountId, createAt, substring(content, 1, 30) as content from share_reply where shareId=? order by id desc";
return shareReplyDao.find(sql, shareId);
}
public Ret getReply(int replyId) {
ShareReply reply = shareReplyDao.findById(replyId);
return Ret.ok("reply", reply);
}
/**
* 删除 share reply
*/
public Ret deleteReply(int shareReplyId) {
Integer accountId = Db.queryInt("select accountId from share_reply where id=? limit 1", shareReplyId);
if (accountId != null) {
myShareSrv.deleteShareReplyById(accountId, shareReplyId);
return Ret.ok("msg", "share reply 删除成功");
} else {
return Ret.fail("msg", "share reply 删除失败");
}
}
}

58
src/main/java/com/bt/_admin/后台管理必读.txt

@ -0,0 +1,58 @@
0:jfinal club 1.4 新添加了如下四张表:
role(id, name)
permission(id, actionKey, controller, remark)
account_role(accountId, roleId)
role_permission(roleId, permissionId)
其中 role、permission 与老版本的 account 构成权限管理功能的三张主表,
而 account_role、role_permission 建立这三张主表间的关联
1:权限管理模块对左侧菜单进行管理,可以参考 "/_view/_admin/common/_menu.html"
中的 #role("权限管理员", "CEO", "CTO") 用法,只需要先用这种式方式预先
安排好哪些角色可以访问哪些菜单,然后就可以通过为 account 配置 role 的方式
来配置菜单权限了,这种模式比通过再创建 menu 表要简单方便
此外,也可以通过 #permission(...) 更细粒度的控制每一个菜单,一般用 #role 指令即可
2:权限管理模块对界面操作按钮之类的组件细粒度的控制可以参考 "/_view/_admin/project/index.html"
中的 #permission("/admin/project/delete") 用法,只需要先用这种方式预先
安排好哪些权限用于控制访问哪些按钮或组件,然后就可以通过为 account 配置权限的
方式来细粒度控制按纽、组件了
3:share、feedback 的回复管理下一版本添加:贴子 title + table 结构
4:头部导航已经做过搜索的界面,一直对美观不满意,留到下版本做进去,
搜索栏样式参考:https://fontawesome.com/v4.7.0/icons/
5:项目整体结构采用先划分模块,然后在模块内部再分层的方式,便于向大型系统进化,
市面上很多先划分层次,然后再划分模块的方式远没有此方式适应大型系统的开发,
此划分方式,还有利于在未来将模块独立拆分成小型服务,再以微服务的方式做分布式
而先分层再分模块的方式则无法方便支持
6:后台管理源码的包名以及视图文件的目录名以下划线打头 "_",是为了让其始终排列在
固定的位置,便于开发过程中快速定位,提升开发效率。否则后台管理模块的位置
会不断变化,例如,如果存在 about 包名,则 admin 会排在其之后
7:视图文件基础路径 "_view" 以下划线打头 "_",是为了将其排在 webapp 的最前面,便于快速
定位,提升开发效率,否则该目录会被夹杂在 assets、WEB-INF、upload 等目录之间
由于 jfinal 有 baseViewPath 配置,所以此安排不会给开发带来麻烦
8:后台管理并没有专用的登录界面,一切针对于后台管理登录界面的黑客攻击根本找不到这个界面
一切对于该界面的寻找必将返回 404 页面,没有专用的管理账户表,管理员账号存在于普通
账号之中。无招才是更好的招。
总之:jfinal-club 值得讨论和学习的细节极多,上述仅为冰山一角,大家一定要加入俱乐部
收获所有福利 https://jfinal.com/club

243
src/main/java/com/bt/common/JFinalClubConfig.java

@ -0,0 +1,243 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common;
import java.sql.Connection;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.wall.WallFilter;
import com.bt._admin.auth.AdminAuthInterceptor;
import com.bt._admin.auth.AdminAuthKit;
import com.bt._admin.common.PjaxInterceptor;
import com.bt._admin.permission.PermissionDirective;
import com.bt._admin.role.RoleDirective;
import com.bt.common.kit.DruidKit;
import com.bt.common.model.Document;
import com.bt.common.model.Feedback;
import com.bt.common.model.Project;
import com.bt.common.model.Share;
import com.bt.common.model._MappingKit;
import com.jfinal.config.*;
import com.jfinal.json.MixedJsonFactory;
import com.jfinal.kit.Prop;
import com.jfinal.kit.PropKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.cron4j.Cron4jPlugin;
import com.jfinal.plugin.druid.DruidPlugin;
import com.jfinal.plugin.ehcache.EhCachePlugin;
import com.jfinal.render.JsonRender;
import com.jfinal.server.undertow.UndertowServer;
import com.jfinal.template.Engine;
import com.bt.common.handler.UrlSeoHandler;
import com.bt.common.model.*;
import com.bt.common.interceptor.LoginSessionInterceptor;
import com.bt.login.LoginService;
import com.bt.my.friend.FriendInterceptor;
/**
* JFinalClubConfig
*/
public class JFinalClubConfig extends JFinalConfig {
// 使用 jfinal-undertow 时此处仅保留声明,不能有加载代码
private static Prop p;
private WallFilter wallFilter;
/**
* 启动入口运行此 main 方法可以启动项目 main 方法
* 可以放置在任意的 Class 类定义中不一定要放于此
*/
public static void main(String[] args) {
UndertowServer.start(JFinalClubConfig.class);
}
/**
* PropKit.useFirstFound(...) 使用参数中从左到右最先被找到的配置文件
* 从左到右依次去找配置找到则立即加载并立即返回后续配置将被忽略
*/
static void loadConfig() {
if (p == null) {
p = PropKit.useFirstFound("jfinal-club-config-pro.txt", "jfinal-club-config-dev.txt");
}
}
public void configConstant(Constants me) {
loadConfig();
me.setDevMode(p.getBoolean("devMode", false));
me.setJsonFactory(MixedJsonFactory.me());
// 支持 Controller、Interceptor、Validator 之中使用 @Inject 注入业务层,并且自动实现 AOP
me.setInjectDependency(true);
// 是否对超类中的属性进行注入
me.setInjectSuperClass(true);
}
/**
* 路由拆分到 FrontRoutes AdminRoutes 之中配置的好处
* 1可分别配置不同的 baseViewPath Interceptor
* 2避免多人协同开发时频繁修改此文件带来的版本冲突
* 3避免本文件中内容过多拆分后可读性增强
* 4便于分模块管理路由
*/
public void configRoute(Routes me) {
// 改用扫描路由,手动注册路由不再使用,FrontRoutes、AdminRoutes 已被删除
/**
* 扫描后台路由
*/
me.add(new Routes() {
public void config() {
// 添加后台管理拦截器,将拦截在此方法中注册的所有 Controller
this.addInterceptor(new AdminAuthInterceptor());
this.addInterceptor(new PjaxInterceptor());
this.setBaseViewPath("/_view/_admin");
// 如果被扫描的包在 jar 文件之中,需要添加如下配置:
// undertow.hotSwapClassPrefix = com.bt._admin.
this.scan("com.bt._admin.");
}
});
/**
* 扫描前台路由
*
* 注意
* 1scan(...) 方法要添加过滤过滤掉后台路由否则后台路由会被扫描到
* 造成 baseViewPath 以及 routes 级别的拦截器配置错误
*
* 2: 由于 scan(...) 内部避免了重复扫描同一个类所以需要将扫描前台路由代码
* 放在扫描后台路由之前才能验证没有过滤造成的后果
*/
me.add(new Routes() {
public void config() {
this.setBaseViewPath("/_view");
// 如果被扫描的包在 jar 文件之中,需要添加如下配置:
// undertow.hotSwapClassPrefix = com.bt.
this.scan("com.bt.", className -> {
// className 为当前正扫描的类名,返回 true 时表示过滤掉当前类不扫描
return className.startsWith("com.bt._admin.");
});
}
});
}
/**
* 配置模板引擎通常情况只需配置共享的模板函数
*/
public void configEngine(Engine me) {
me.setDevMode(p.getBoolean("engineDevMode", false));
// 开启压缩功能,常用配置参数有: ' ' 与 '\n'
// me.setCompressorOn('\n');
// 配置用于主菜单的 URL,导航到 https://jfinal.com
me.addSharedObject("SSL", p.get("SSL"));
/**
* 用于在页面中使用 field 表达式代替 static field 表达式节省代码量
*
* 例如
* Feedback.REPORT_BLOCK_NUM
* 可代替
* com.jfinal.club.common.model.Feedback::REPORT_BLOCK_NUM
*/
me.addSharedObject("Feedback", new Feedback());
me.addSharedObject("Project", new Project());
me.addSharedObject("Share", new Share());
me.addSharedObject("Document", new Document());
// 添加角色、权限指令
me.addDirective("role", RoleDirective.class);
me.addDirective("permission", PermissionDirective.class);
me.addDirective("perm", PermissionDirective.class); // 配置一个别名指令
// 添加角色、权限 shared method
me.addSharedMethod(AdminAuthKit.class);
me.addSharedFunction("/_view/common/__layout.html");
me.addSharedFunction("/_view/common/__front_layout.html");
me.addSharedFunction("/_view/common/__b5layout.html");
me.addSharedFunction("/_view/common/_paginate.html");
me.addSharedFunction("/_view/_admin/common/__admin_layout.html");
me.addSharedFunction("/_view/_admin/common/_admin_paginate.html");
}
/**
* 抽取成独立的方法便于 _Generator 中重用该方法减少代码冗余
*/
public static DruidPlugin getDruidPlugin() {
loadConfig();
return new DruidPlugin(p.get("jdbcUrl"), p.get("user"), p.get("password"));
}
public void configPlugin(Plugins me) {
DruidPlugin druidPlugin = getDruidPlugin();
wallFilter = new WallFilter(); // 加强数据库安全
wallFilter.setDbType("mysql");
druidPlugin.addFilter(wallFilter);
druidPlugin.addFilter(new StatFilter()); // 添加 StatFilter 才会有统计数据
me.add(druidPlugin);
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
arp.setTransactionLevel(Connection.TRANSACTION_READ_COMMITTED);
_MappingKit.mapping(arp);
// 强制指定复合主键的次序,避免不同的开发环境生成在 _MappingKit 中的复合主键次序不相同
arp.setPrimaryKey("document", "mainMenu,subMenu");
me.add(arp);
arp.setShowSql(p.getBoolean("devMode", false));
arp.getEngine().setToClassPathSourceFactory();
arp.addSqlTemplate("/com/bt/common/_all_sqls.sql");
me.add(new EhCachePlugin());
me.add(new Cron4jPlugin(p));
}
public void configInterceptor(Interceptors me) {
me.add(new LoginSessionInterceptor());
}
public void configHandler(Handlers me) {
me.add(DruidKit.getDruidStatViewHandler()); // druid 统计页面功能
me.add(new UrlSeoHandler()); // index、detail 两类 action 的 url seo
}
/**
* 本方法会在 jfinal 启动过程完成之后被回调详见 jfinal 手册
*/
public void onStart() {
// 调用不带参的 renderJson() 时,排除对 loginAccount、remind 的 json 转换
JsonRender.addExcludedAttrs(
LoginService.loginAccountCacheName,
LoginSessionInterceptor.remindKey,
FriendInterceptor.followNum, FriendInterceptor.fansNum, FriendInterceptor.friendRelation
);
// 让 druid 允许在 sql 中使用 union
// https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE-wallfilter
wallFilter.getConfig().setSelectUnionCheck(false);
}
}

26
src/main/java/com/bt/common/_all_sqls.sql

@ -0,0 +1,26 @@
sql
1 JFinalClubConfig sql
2 namespace sql
3 define
CRUD
#namespace("index")
#include("/com/bt/index/index.sql")
#end
#namespace("project")
#include("/com/bt/project/project.sql")
#end
#namespace("share")
#include("/com/bt/share/share.sql")
#end
#namespace("feedback")
#include("/com/bt/feedback/feedback.sql")
#end
#namespace("admin.auth")
#include("/com/bt/_admin/auth/admin_auth.sql")
#end

157
src/main/java/com/bt/common/account/AccountService.java

@ -0,0 +1,157 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.account;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.ehcache.CacheKit;
import com.bt.common.model.Account;
import java.util.List;
/**
* 账户业务
*/
@SuppressWarnings("rawtypes")
public class AccountService {
private Account dao = new Account().dao();
private final String allAccountsCacheName = "allAccounts";
public void updateAccountAvatar(int accountId, String relativePathFileName) {
Db.update("update account set avatar=? where id=? limit 1", relativePathFileName, accountId);
clearCache(accountId);
}
/**
* 通过nickName获取Account对象仅返回指定的字段多字段用逗号分隔
*/
public Account getByNickName(String nickName, String columns) {
if (StrKit.isBlank(nickName)) {
return null;
}
return dao.findFirst("select " + columns +" from account where nickName=? and status=? limit 1", nickName, Account.STATUS_OK);
}
/**
* 通过 id 获取Account对象只能获取到未被 block account
*/
public Account getUsefulById(int accountId) {
// return dao.findFirst("select " + columns +" from account where id=? and status=? limit 1", accountId, Account.STATUS_OK);
Account account = getById(accountId);
return account != null && account.isStatusOk() ? account : null;
}
/**
* 优先从缓存中获取 account 对象可获取到被 block account
*/
public Account getById(int accountId) {
// 优先从缓存中取,未命中缓存则从数据库取
Account account = CacheKit.get(allAccountsCacheName, accountId);
if (account == null) {
// 考虑到可能需要 join 状态不合法的用户,先放开 status 的判断
// account = dao.findFirst("select * from account where id=? and status=? limit 1", accountId, Account.STATUS_OK);
account = dao.findFirst("select * from account where id=? limit 1", accountId);
if (account != null) {
account.removeSensitiveInfo();
CacheKit.put(allAccountsCacheName, accountId, account);
}
}
return account;
}
public void joinNickNameAndAvatar(List<? extends Model> modelList) {
join("accountId", modelList, "nickName", "avatar");
}
public void joinNickNameAndAvatar(Model model) {
join("accountId", model, "nickName", "avatar");
}
public void join(String joinOnField, List<? extends Model> modelList, String... joinAttrs) {
if (modelList != null) {
for (Model m : modelList) {
join(joinOnField, m, joinAttrs);
}
}
}
/**
* ProjectShareFeedbackNewsFeed 等模块需要关联查询获取 Account 对象的 nickNameavatar 时使用此方法
* 避免使用关联查询优化性能在使用中更关键的地方在于缓存的清除
* @param joinOnField join 使用的字段名account 这端使用 id
* @param model 需要 join model
* @param joinAttrs 需要 join model 中的的属性名称
*/
public void join(String joinOnField, Model model, String... joinAttrs) {
Integer accountId = model.getInt(joinOnField);
if (accountId == null) {
throw new RuntimeException("Model 中的 \"" + joinOnField + "\" 属性值不能为 null");
}
Account account = getById(accountId);
// join 真正开始的地方,前面是准备工作
if (account != null) {
for (String attr : joinAttrs) {
model.put(attr, account.get(attr));
}
} else {
throw new RuntimeException("未找到 account 或者 account 状态不合法,account 的 id 值为:" + accountId + " 可能是数据库数据不一致");
}
}
/**
* 更新 likeCount 字段
* TODO 未来做成延迟更新模式
*/
private void updateLikeCount(int accountId, boolean isAdd) {
String sql = isAdd ?
"update account set likeCount=likeCount+1 where id=? limit 1" :
"update account set likeCount=likeCount-1 where id=? and likeCount > 0 limit 1";
int n = Db.update(sql, accountId);
if (n > 0) {
// 直接更新缓存中的 likeCount 值
Account account = CacheKit.get(allAccountsCacheName, accountId);
if (account != null) {
account.setLikeCount(account.getLikeCount() + (isAdd ? 1 : -1));
}
}
}
/**
* likeCount 增加 1
*/
public void addLikeCount(int accountId) {
updateLikeCount(accountId, true);
}
/**
* likeCount 减去 1
*/
public void minusLikeCount(int accountId) {
updateLikeCount(accountId, false);
}
/**
* 根据 accountId 值移除缓存
*/
public void clearCache(int accountId) {
CacheKit.remove(allAccountsCacheName, accountId);
}
}

115
src/main/java/com/bt/common/authcode/AuthCodeService.java

@ -0,0 +1,115 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.authcode;
import com.bt.common.model.AuthCode;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.kit.Ret;
/**
* 授权码业务
* 用于一切需要授权的业务例如
* 1邮件激活
* 2密码找回
* 3未来一切需要授权码的场景
*/
public class AuthCodeService {
private AuthCode dao = new AuthCode().dao();
/**
* 创建注册激活授权码一个小时后过期3600
*/
public String createRegActivateAuthCode(int accountId) {
return createAuthCode(accountId, AuthCode.TYPE_REG_ACTIVATE, 3600);
}
/**
* 创建密码找回授权码一个小时后过期3600
*/
public String createRetrievePasswordAuthCode(int accountId) {
return createAuthCode(accountId, AuthCode.TYPE_RETRIEVE_PASSWORD, 3600);
}
/**
* 获取授权码授权码只能使用一次在被获取后会被立即删除
*/
public AuthCode getAuthCode(String authCodeId) {
if (StrKit.notBlank(authCodeId)) {
AuthCode authCode = dao.findById(authCodeId.trim()); // authCode 是唯一的
if (authCode != null) {
authCode.delete(); // 只要找到 authCode,则立即删除
return authCode;
}
}
return null;
}
/**
* 创建授权码并自动保存到数据库
* @param accountId 用户账号id
* @param authType 授权类型
* @param expireTime 授权码过期时长过期时长是指授权码自创建时间起直到过期的时间长度单位为秒
*/
private String createAuthCode(int accountId, int authType, int expireTime) {
long et = expireTime; // 使用 long et 为了避免 int 数值溢出,造成保存到数据库中的数值错误
long expireAt = System.currentTimeMillis() + (et * 1000);
AuthCode ac = new AuthCode();
ac.setId(StrKit.getRandomUUID());
ac.setAccountId(accountId);
ac.setType(authType);
ac.setExpireAt(expireAt);
if (ac.save()) {
return ac.getId();
} else {
throw new RuntimeException("保存 auth_code 记录失败,请联系管理员");
}
}
/**
* 看一眼授权码未过期时则不删除
*/
public Ret peekAuthCode(String id) {
AuthCode authCode = dao.findById(id);
if (authCode != null) {
if (authCode.notExpired()) {
return Ret.ok("authCode", authCode);
} else {
authCode.delete();
return Ret.fail("msg", "授权码已过期");
}
} else {
return Ret.fail("msg", "授权码不存在");
}
}
/**
* 主动清除未使用过的过期授权码
* 不用经常调用因为授权码在第一次使用时会自动删除过期的未删除的授权码仅是未使用过的
*/
public int clearExpiredAuthCode() {
return Db.update("delete from auth_code where expireAt < ?", System.currentTimeMillis());
}
}

96
src/main/java/com/bt/common/controller/BaseController.java

@ -0,0 +1,96 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.controller;
import com.jfinal.core.Controller;
import com.bt.common.model.Account;
import com.bt.login.LoginService;
import com.jfinal.core.NotAction;
/**
* 基础控制器方便获取登录信息
*
* 警告由于 BaseController 中声明了 Account loginAccount 属性
* 所以不能使用 FastControllerFactory 这类回收 Controller 的用法
* jfinal 3.5 发布以后可以通过继承 _clear_() 方法来清除属性中的值
* 才能使用 FastControllerFactory
* 用户自己的 Controller 也是同样的道理
*
* 注意
* 需要 LoginSessionInterceptor 配合该拦截器使用
* setAttr("loginAccount", ...) 事先注入了登录账户
* 否则即便已经登录该控制器也会认为没有登录
*
*/
public class BaseController extends Controller {
/**
* 警告由于这个属性的存在不能直接使用 FastControllerFactory除非使用 jfinal 3.5
* 并覆盖父类中的 _clear_() 方法清除本类中与父类中的属性值详情见本类中的
* protected void _clear_() 方法
*
* 原因是 FastControllerFactory 是回收使用 controller 对象的所以要在 _clear()_
* 中清除上次使用时的属性值
*/
private Account loginAccount = null;
protected void _clear_() {
this.loginAccount = null;
super._clear_();
}
@NotAction
public Account getLoginAccount() {
if (loginAccount == null) {
loginAccount = getAttr(LoginService.loginAccountCacheName);
if (loginAccount != null && ! loginAccount.isStatusOk()) {
throw new IllegalStateException("当前用户状态不允许登录,status = " + loginAccount.getStatus());
}
}
return loginAccount;
}
@NotAction
public boolean isLogin() {
return getLoginAccount() != null;
}
@NotAction
public boolean notLogin() {
return !isLogin();
}
/**
* 获取登录账户id
* 确保在 FrontAuthInterceptor 之下使用或者 isLogin() true 时使用
* 也即确定已经是在登录后才可调用
*/
@NotAction
public int getLoginAccountId() {
return getLoginAccount().getId();
}
@NotAction
public boolean isPjaxRequest() {
return "true".equalsIgnoreCase(getHeader("X-PJAX"));
}
@NotAction
public boolean isAjaxRequest() {
return "XMLHttpRequest".equalsIgnoreCase(getHeader("X-Requested-With"));
}
}

95
src/main/java/com/bt/common/directive/MyDateDirective.java

@ -0,0 +1,95 @@
package com.bt.common.directive;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import com.jfinal.template.Directive;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.Expr;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;
/**
* #date 日期格式化输出指令
*
* 三种用法<br>
* 1#date(createAt) 用默认 datePattern 配置输出 createAt 变量中的日期值<br>
* 2#date(createAt, "yyyy-MM-dd HH:mm:ss") 用第二个参数指定的 datePattern输出 createAt 变量中的日期值<br>
* 3#date() 用默认 datePattern 配置输出 当前 日期值
*
* 注意<br>
* 1#date 指令中的参数可以是变量例如#date(d, p) 中的 d p 可以全都是变量<br>
* 2默认 datePattern 可通过 Engine.setDatePattern(...) 进行配置
*
* @author Max_Qiu
*/
public class MyDateDirective extends Directive {
private Expr valueExpr;
private Expr datePatternExpr;
private int paraNum;
@Override
public void setExprList(ExprList exprList) {
this.paraNum = exprList.length();
if (paraNum > 2) {
throw new ParseException("#date 指令最多两个参数", location);
}
if (paraNum == 0) {
this.valueExpr = null;
this.datePatternExpr = null;
} else if (paraNum == 1) {
this.valueExpr = exprList.getExpr(0);
this.datePatternExpr = null;
} else if (paraNum == 2) {
this.valueExpr = exprList.getExpr(0);
this.datePatternExpr = exprList.getExpr(1);
}
}
@Override
public void exec(Env env, Scope scope, Writer writer) {
if (paraNum == 1) {
outputWithDatePattern(scope, env.getEngineConfig().getDatePattern(), writer);
} else if (paraNum == 2) {
Object datePattern = this.datePatternExpr.eval(scope);
if (!(datePattern instanceof String)) {
throw new TemplateException("#date 指令的第二个参数类型必须是 String", location);
}
outputWithDatePattern(scope, (String)datePattern, writer);
} else {
write(writer, LocalDateTime.now(), env.getEngineConfig().getDatePattern());
}
}
private void outputWithDatePattern(Scope scope, String datePattern, Writer writer) {
Object value = valueExpr.eval(scope);
if (value instanceof TemporalAccessor) {
write(writer, (TemporalAccessor)value, datePattern);
} else if (value instanceof Date) {
write(writer, (Date)value, datePattern);
} else if (value != null) {
throw new TemplateException("#date 指令的第一个参数必须是实现TemporalAccessor接口的时间类型或者是Date类型", location);
}
}
private void write(Writer writer, TemporalAccessor temporal, String datePattern) {
String s;
try {
s = DateTimeFormatter.ofPattern(datePattern).format(temporal);
} catch (Exception e) {
throw new TemplateException("格式化出错,请检查第二个参数:" + datePattern, location, e);
}
try {
writer.write(s, 0, s.length());
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
private void write(Writer writer, Date date, String datePattern) {
try {
writer.write(date, datePattern);
} catch (IOException e) {
throw new TemplateException(e.getMessage(), location, e);
}
}
}

80
src/main/java/com/bt/common/handler/UrlSeoHandler.java

@ -0,0 +1,80 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.handler;
import com.jfinal.core.Action;
import com.jfinal.core.JFinal;
import com.jfinal.handler.Handler;
import com.jfinal.kit.HandlerKit;
import com.jfinal.kit.StrKit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 只影响 index() detail()这两个 action url 变得更短有利于 SEO规则是
* 1actionMethod "index" 判断是否有 urlPara有的话直接将 target 转成 controllerKey + "detail" + urlPara
* 2actionMethod "detail" 直接阻止请求不允许这样的 url 出现
* 3其它情况直接放行
*
* 注意两点
* 1由于对带有 urlPara index() 请求都被转成了 detail() 请求所以这类应用场景需要将 index() 方法改名
* 并利用 @ActionKey(controllerKey) 指定为自己想要的 url具体可参考 "/my" 模块中的一些 Controller 用法
*
* 2由于 detail() 方法已被阻止系统中所有 url 都不能添加 "detail" actionMethod 部分直接去掉这部分即可
*
*/
public class UrlSeoHandler extends Handler {
private String detailMethodName;
private String detailMethodWithSlash;
public UrlSeoHandler() {
detailMethodName = "detail";
detailMethodWithSlash = "/" + detailMethodName + "/";
}
public UrlSeoHandler(String detailMethodName) {
if (StrKit.isBlank(detailMethodName)) {
throw new IllegalArgumentException("detailMethodName can not be blank.");
}
this.detailMethodName = detailMethodName;
this.detailMethodWithSlash = "/" + detailMethodName + "/";
}
public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
// 静态请求直接跳出
if (target.indexOf('.') != -1) {
return ;
}
String[] urlPara = {null};
Action action = JFinal.me().getAction(target, urlPara);
if (action != null) {
String methodName = action.getMethodName();
if ("index".equals(methodName)) {
if (StrKit.notBlank(urlPara[0])) {
target = action.getControllerKey() + detailMethodWithSlash + urlPara[0];
}
}
// 不允许 "detail" 出现在 url 中,映射到 detail() 方法的 url 仅使用 "/project/123" 这种格式
else if (detailMethodName.equals(methodName)) {
HandlerKit.renderError404(request, response, isHandled);
return;
}
}
next.handle(target, request, response, isHandled);
}
}

33
src/main/java/com/bt/common/interceptor/AuthCacheClearInterceptor.java

@ -0,0 +1,33 @@
package com.bt.common.interceptor;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.bt.common.model.Account;
import com.bt.login.LoginService;
import com.jfinal.plugin.activerecord.Db;
/**
* role 表中存在的 accountId 拥有清除前端 cache 的权限
* 超级管理员符合上述条件
*/
public class AuthCacheClearInterceptor implements Interceptor {
public static boolean isAdmin(Account loginAccount) {
if (loginAccount == null || !loginAccount.isStatusOk()) {
return false;
}
String admin = "select accountId from account_role where accountId = ? limit 1";
Integer accountId = Db.queryInt(admin, loginAccount.getId());
return accountId != null;
}
public void intercept(Invocation inv) {
Account loginAccount = inv.getController().getAttr(LoginService.loginAccountCacheName);
if (isAdmin(loginAccount)) {
inv.invoke();
} else {
inv.getController().renderError(404);
}
}
}

72
src/main/java/com/bt/common/interceptor/BaseSeoInterceptor.java

@ -0,0 +1,72 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.interceptor;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
/**
* SEO 搜索引擎优化基础拦截器
*/
public abstract class BaseSeoInterceptor implements Interceptor {
public static final String SEO_TITLE = "seoTitle";
public static final String SEO_KEYWORDS = "seoKeywords";
public static final String SEO_DESCR = "seoDescr";
protected void setSeoTitle(Controller c, String seoTitle) {
c.setAttr(SEO_TITLE, seoTitle);
}
protected void setSeoKeywords(Controller c, String seoKeywords) {
c.setAttr(SEO_KEYWORDS, seoKeywords);
}
protected void setSeoDescr(Controller c, String seoDescr) {
c.setAttr(SEO_DESCR, seoDescr);
}
public final void intercept(Invocation inv) {
inv.invoke();
Controller c = inv.getController();
String method = inv.getMethodName();
if (method.equals("index")) {
if (c.getPara() == null) {
indexSeo(c);
} else {
detailSeo(c);
}
} else if (method.equals("detail")) {
detailSeo(c);
} else {
othersSeo(c, method);
}
}
// 对 index() action 进行 seo
public abstract void indexSeo(Controller c) ;
// 对 detail() action 进行 seo
public abstract void detailSeo(Controller c) ;
// 对其它方法进行 seo,只需在该方法的实现类中判断一下 method 参数名
// 并调用前面的 seoTitle、setSeoKeywords、setSeoDescr 三个工具方法即可完成 seo
public abstract void othersSeo(Controller c, String method);
}

43
src/main/java/com/bt/common/interceptor/FrontAuthInterceptor.java

@ -0,0 +1,43 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.interceptor;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.kit.StrKit;
import com.bt.login.LoginService;
/**
* 需要登录才能授权的操作例如文件下载
*
* 未登录将被重定向到登录界面登录成功后又会再返回到原来想跳去的 url
* 注意在登录表单中有 returnUrl 变量的传递才能跳到原来想跳去的 url
* 详见登录页面表单传参
*/
public class FrontAuthInterceptor implements Interceptor {
public void intercept(Invocation inv) {
if (inv.getController().getAttr(LoginService.loginAccountCacheName) != null) {
inv.invoke();
} else {
String queryString = inv.getController().getRequest().getQueryString();
if (StrKit.isBlank(queryString)) {
inv.getController().redirect("/login?returnUrl=" + inv.getActionKey());
} else {
inv.getController().redirect("/login?returnUrl=" + inv.getActionKey() + "?" + queryString);
}
}
}
}

76
src/main/java/com/bt/common/interceptor/LoginSessionInterceptor.java

@ -0,0 +1,76 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.interceptor;
import com.bt.common.kit.IpKit;
import com.bt.common.model.Remind;
import com.jfinal.aop.Inject;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.Invocation;
import com.jfinal.core.Controller;
import com.bt.common.model.Account;
import com.bt.login.LoginService;
import com.bt.my.newsfeed.RemindService;
/**
* cookie 中获取 sessionId如果获取到则根据该值使用 LoginService
* 得到登录的 Account 对象 ---> loginAccount供后续的流程使用
*
* 注意将此拦截器设置为全局拦截器所有 action 都需要
*/
public class LoginSessionInterceptor implements Interceptor {
@Inject
LoginService loginSrv;
@Inject
RemindService remindSrv;
public static final String remindKey = "_remind";
public void intercept(Invocation inv) {
Account loginAccount = null;
Controller c = inv.getController();
String sessionId = c.getCookie(LoginService.sessionIdName);
if (sessionId != null) {
loginAccount = loginSrv.getLoginAccountWithSessionId(sessionId);
if (loginAccount == null) {
String loginIp = IpKit.getRealIp(c.getRequest());
loginAccount = loginSrv.loginWithSessionId(sessionId, loginIp);
}
if (loginAccount != null) {
// 用户登录账号
c.setAttr(LoginService.loginAccountCacheName, loginAccount);
} else {
c.removeCookie(LoginService.sessionIdName); // cookie 登录未成功,证明该 cookie 已经没有用处,删之
}
}
inv.invoke();
if (loginAccount != null) {
// remind 对象用于生成提醒 tips
Remind remind = remindSrv.getRemind(loginAccount.getId());
if (remind != null) {
if (remind.getReferMe() > 0 || remind.getMessage() > 0 || remind.getFans() > 0) {
c.setAttr(remindKey, remind);
}
}
}
}
}

69
src/main/java/com/bt/common/kit/DruidKit.java

@ -0,0 +1,69 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.kit;
import com.jfinal.aop.Aop;
import com.bt.common.interceptor.AuthCacheClearInterceptor;
import com.jfinal.plugin.druid.DruidStatViewHandler;
import com.jfinal.plugin.druid.IDruidStatViewAuth;
import com.bt.common.model.Account;
import com.bt.login.LoginService;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
/**
* 创建 DruidStatViewHandler 的工具类
*
* 可通过 "/assets/druid" 访问到 druid 提供的 sql 监控与统计功能
* 方便找到慢 sql进而对慢 sql 进行优化
* 注意这里的访问路径是下面代码中指定的可以设置为任意路径
*
* 注意 druid 监控模块中使用的静态资源文件如 .html .css 被打包在了 druid 的jar 包之中
* 如果你的项目在前端有 nginx 代理过了这些静态资源需要将这些资源解压出来并放到
* 正确的目录下面
*
* 具体到该配置中的 url "/assets/druid"那么相关静态资源需要解压到该目录之下
*/
public class DruidKit {
static LoginService loginSrv = Aop.get(LoginService.class);
public static DruidStatViewHandler getDruidStatViewHandler() {
return new DruidStatViewHandler("/assets/druid", new IDruidStatViewAuth() {
public boolean isPermitted(HttpServletRequest request) {
String sessionId = getCookie(request, LoginService.sessionIdName);
if (sessionId != null) {
Account loginAccount = loginSrv.getLoginAccountWithSessionId(sessionId);
return AuthCacheClearInterceptor.isAdmin(loginAccount);
}
return false;
}
});
}
public static String getCookie(HttpServletRequest request, String name) {
Cookie cookie = getCookieObject(request, name);
return cookie != null ? cookie.getValue() : null;
}
private static Cookie getCookieObject(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null)
for (Cookie cookie : cookies)
if (cookie.getName().equals(name))
return cookie;
return null;
}
}

135
src/main/java/com/bt/common/kit/EmailKit.java

@ -0,0 +1,135 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.kit;
import com.jfinal.log.Log;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail;
import com.jfinal.kit.StrKit;
/**
* 邮件发送工具类
*
* 文档
* 核心代码在 com.sun.mail.smtp.SMTPTransport.java通过调试可以了解很多细节
* EmailConstants 中有很多可配置常量
*/
public class EmailKit {
private static final Log log = Log.getLog(EmailKit.class);
private static boolean debug = false;
private static boolean initSsl = false;
public static void setDebug(boolean debug) {
EmailKit.debug = debug;
}
private static void initSsl(String emailServer) {
if (!initSsl) {
/**
* 配置邮件发送客户端信任的发送服务器配置为 "*" 时信任所有服务器
* 必须配置否则将抛出如下异常
* javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
* at com.sun.mail.smtp.SMTPTransport.openServer(SMTPTransport.java:2120)
* at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:712)
*/
// System.setProperty("mail.smtp.ssl.trust", emailServer);
System.setProperty("mail.smtp.ssl.trust", "*");
/**
* 本变量不配置也能支持 SSL因为本变量与 setSSLOnConnect(true) 都能触发 Email.java 中的如下代码被调用
* properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
* 其中第一个参数值为 "mail.smtp.socketFactory.class"
* 从而在 com.sun.mail.util.SocketFetcher.getSocket(String host, int port, Properties props, String prefix, boolean useSSL)
* 方法中在 if (isSSL) 两个分支中都通过 "mail.smtp.socketFactory.class" 获取到了
* "javax.net.ssl.SSLSocketFactory"从而后面的 ssl 逻辑变成一样的了
*
*
* 本配置在同时配置 SimpleEmail.setDebug(true) 控制台第 9 行输出的 isSSL 值为 true
* DEBUG SMTP: trying to connect to host "old.jfinal.com", port 465, isSSL true
*
* 建议使用该配置
*/
System.setProperty("mail.smtp.ssl.enable", "true");
initSsl = true;
}
}
public static String sendEmail(String fromEmail, String toEmail, String title, String content) {
return sendEmail(null, fromEmail, null, toEmail, title, content);
}
/**
* 阿里云从 2019 9 月底开始禁止了 25 号端口腾迅华为云亦如此
* 需要使用 465 端口当密码不为空时认为是在使用 465 SSL 通道
*/
public static String sendEmail(String emailServer, String fromEmail, String password, String toEmail, String title, String content) {
// emailServer 为空时,默认使用本地 postfix 发送
emailServer = StrKit.notBlank(emailServer) ? emailServer : "127.0.0.1";
initSsl(emailServer);
SimpleEmail email = new SimpleEmail();
email.setHostName(emailServer);
email.setDebug(debug); // 调试错误原因极其有用的配置
email.setSSLOnConnect(true); // 走 465 SSL 通道必须的配置,最关键配置
// email.setSslSmtpPort("465"); // 可选配置,默认值已经是 "465"
// email.setStartTLSRequired(true); // 强制使用 StartTLS,在测试 SSL 是否可用时开启一下
// email.setSSLCheckServerIdentity(true); // 验证服务端证书
email.setSocketConnectionTimeout(32 * 1000); // 连接超时
email.setSocketTimeout(32 * 1000); // socket IO 超时
// 如果密码为空,则不进行认证
if (StrKit.notBlank(password)) {
email.setAuthentication(fromEmail, password);
}
email.setCharset("utf-8");
try {
email.addTo(toEmail);
email.setFrom(fromEmail);
email.setSubject(title);
email.setMsg(content);
return email.send();
} catch (EmailException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
String ret = sendEmail(
"abc.com", // 邮件发送服务器地址
"no-reply@abc.com", // 发件邮箱
null, // 发件邮箱密码
"test@test.com", // 收件地址
"邮件标题", // 邮件标题
"content"); // 邮件内容
System.out.println("发送返回值: " + ret);
}
}

265
src/main/java/com/bt/common/kit/ImageKit.java

@ -0,0 +1,265 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.kit;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageOutputStream;
import com.jfinal.kit.StrKit;
/**
* ImageKit 图片高保真缩放与裁剪不依赖于任何第三方库
*/
public class ImageKit {
private final static String[] imgExts = new String[]{"jpg", "jpeg", "png", "bmp"};
public static String getExtName(String fileName) {
int index = fileName.lastIndexOf('.');
if (index != -1 && (index + 1) < fileName.length()) {
return fileName.substring(index + 1);
} else {
return null;
}
}
/**
* 通过文件扩展名判断是否为支持的图像文件支持则返回 true否则返回 false
*/
public static boolean isImageExtName(String fileName) {
if (StrKit.isBlank(fileName)) {
return false;
}
fileName = fileName.trim().toLowerCase();
String ext = getExtName(fileName);
if (ext != null) {
for (String s : imgExts) {
if (s.equals(ext)) {
return true;
}
}
}
return false;
}
public static final boolean notImageExtName(String fileName) {
return ! isImageExtName(fileName);
}
public static BufferedImage loadImageFile(String sourceImageFileName) {
if (notImageExtName(sourceImageFileName)) {
throw new IllegalArgumentException("只支持如下几种类型的图像文件:jpg、jpeg、png、bmp");
}
File sourceImageFile = new File(sourceImageFileName);
if (!sourceImageFile.exists() || !sourceImageFile.isFile()) {
throw new IllegalArgumentException("文件不存在");
}
try {
return ImageIO.read(sourceImageFile);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* maxWidth 为界对图像进行缩放高保真保存
* 1当图像 width > maxWidth 将宽度变为 maxWidth高度等比例进行缩放高保真保存
* 2:当图像 width <= maxWidth 宽高保持不变只进行高保真保存
*/
public static void zoom(int maxWidth, File srcFile, String saveFile) {
float quality = 0.8f;
try {
BufferedImage srcImage = ImageIO.read(srcFile);
int srcWidth = srcImage.getWidth();
int srcHeight = srcImage.getHeight();
// 当宽度在 maxWidth 范围之内,不改变图像宽高,只进行图像高保真保存
if (srcWidth <= maxWidth) {
/**
* 如果图像不进行缩放的话 resize 就没有必要了
* 经过测试是否有 resize 这一步生成的结果图像完全一样一个字节都不差
* 所以删掉 resize可以提升性能少一步操作
*/
// BufferedImage ret = resize(srcImage, srcWidth, srcHeight);
// saveWithQuality(ret, quality, saveFile);
saveWithQuality(srcImage, quality, saveFile);
}
// 当宽度超出 maxWidth 范围,将宽度变为 maxWidth,而高度按比例变化
else {
float scalingRatio = (float)maxWidth / (float)srcWidth; // 计算缩放比率
float maxHeight = ((float)srcHeight * scalingRatio); // 计算缩放后的高度
BufferedImage ret = resize(srcImage, maxWidth, (int)maxHeight);
saveWithQuality(ret, quality, saveFile);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 对图片进行剪裁只保存选中的区域
* @param sourceImageFile 原图
* @param left
* @param top
* @param width
* @param height
*/
public static BufferedImage crop(String sourceImageFile, int left, int top, int width, int height) {
if (notImageExtName(sourceImageFile)) {
throw new IllegalArgumentException("只支持如下几种类型的图像文件:jpg、jpeg、png、bmp");
}
try {
BufferedImage bi = ImageIO.read(new File(sourceImageFile));
width = Math.min(width, bi.getWidth());
height = Math.min(height, bi.getHeight());
if(width <= 0) width = bi.getWidth();
if(height <= 0) height = bi.getHeight();
left = Math.min(Math.max(0, left), bi.getWidth() - width);
top = Math.min(Math.max(0, top), bi.getHeight() - height);
BufferedImage subImage = bi.getSubimage(left, top, width, height);
return subImage; // return ImageIO.write(bi, "jpeg", fileDest);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void save(BufferedImage bi, String outputImageFile) {
FileOutputStream newImage = null;
try {
// ImageIO.write(bi, "jpg", new File(outputImageFile));
ImageIO.write(bi, getExtName(outputImageFile), new File(outputImageFile));
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (newImage != null) {
try {
newImage.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 高保真缩放
*/
public static BufferedImage resize(BufferedImage bi, int toWidth, int toHeight) {
Graphics g = null;
try {
// 从 BufferedImage 对象中获取一个经过缩放的 image
Image scaledImage = bi.getScaledInstance(toWidth, toHeight, Image.SCALE_SMOOTH);
// 创建 BufferedImage 对象,存放缩放过的 image
BufferedImage ret = new BufferedImage(toWidth, toHeight, BufferedImage.TYPE_INT_RGB);
g = ret.getGraphics();
g.drawImage(scaledImage, 0, 0, null);
// g.drawImage(scaledImage, 0, 0, Color.WHITE, null);
return ret;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (g != null) {
g.dispose();
}
}
}
/**
* jfinal.com 使用参数为宽200, 200, 质量0.8
* 生成大小为6.79 KB (6,957 字节)
*
* 如果使用参数为宽120, 120, 质量0.8
* 则生成的图片大小为3.45 KB (3,536 字节)
*
* 如果使用参数为宽300, 300, 质量0.5
* 则生成的图片大小为7.54 KB (7,725 字节)
*
*
* 建议使用 0.8 quality 并且稍大点的宽高
* 只选用两种质量0.8 0.9这两个差别不是很大
* 但是如果尺寸大些的话选用 0.8 0.9 要划算因为占用的空间差不多的时候尺寸大些的更清晰
*/
public static void saveWithQuality(BufferedImage im, float quality, String outputImageFile) {
ImageWriter writer = null;
FileOutputStream newImage = null;
try {
BufferedImage newBufferedImage = new BufferedImage(im.getWidth(), im.getHeight(), BufferedImage.TYPE_INT_RGB);
newBufferedImage.createGraphics().drawImage(im, 0, 0, Color.WHITE, null);
// 输出到文件流
newImage = new FileOutputStream(outputImageFile);
writer = ImageIO.getImageWritersBySuffix("jpg").next();
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(quality);
ImageOutputStream os = ImageIO.createImageOutputStream(newImage);
writer.setOutput(os);
writer.write((IIOMetadata) null, new IIOImage(newBufferedImage, null, null), param);
os.flush();
os.close();
}
catch (IOException e) {
throw new RuntimeException(e);
}
finally {
if (writer != null) {
try {writer.dispose();} catch (Throwable e) {}
}
if (newImage != null) {
try {newImage.close();} catch (IOException e) {throw new RuntimeException(e);}
}
}
}
/* 老版本
public static void saveWithQuality(BufferedImage im, float quality, String outputImageFile) {
FileOutputStream newImage = null;
try {
// 输出到文件流
newImage = new FileOutputStream(outputImageFile);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(newImage);
JPEGEncodeParam jep = JPEGCodec.getDefaultJPEGEncodeParam(im);
// 压缩质量, 0.75 就算是高质量
jep.setQuality(quality, true); // jep.setQuality(0.9f, true);
encoder.encode(im, jep);
// 近JPEG编码
// newImage.close();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (newImage != null) {
try {newImage.close();} catch (IOException e) {throw new RuntimeException(e);}
}
}
}*/
}

86
src/main/java/com/bt/common/kit/IpKit.java

@ -0,0 +1,86 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.kit;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
/**
* IpKit 获取 ip 地址的工具类
*/
public class IpKit {
public static String getRealIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
public static String getRealIpV2(HttpServletRequest request) {
String accessIP = request.getHeader("x-forwarded-for");
if (null == accessIP)
return request.getRemoteAddr();
return accessIP;
}
/**
* 获取本机 ip
* @return 本机IP
*/
public static String getLocalIp() throws SocketException {
String localip = null; // 本地IP,如果没有配置外网IP则返回
String netip = null; // 外网IP
Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
InetAddress ip = null;
boolean finded = false; // 是否找到外网IP
while (netInterfaces.hasMoreElements() && !finded) {
NetworkInterface ni = netInterfaces.nextElement();
Enumeration<InetAddress> address = ni.getInetAddresses();
while (address.hasMoreElements()) {
ip = address.nextElement();
if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 外网IP
netip = ip.getHostAddress();
finded = true;
break;
} else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 内网IP
localip = ip.getHostAddress();
}
}
}
if (netip != null && !"".equals(netip)) {
return netip;
} else {
return localip;
}
}
}

56
src/main/java/com/bt/common/kit/SensitiveWordsKit.java

@ -0,0 +1,56 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.kit;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import java.util.ArrayList;
import java.util.List;
/**
* 敏感词检测
*/
public class SensitiveWordsKit {
private static final List<String> sensitiveWords = build();
private static List<String> build() {
ArrayList<String> ret = new ArrayList<String>();
List<Record> list = Db.find("select * from sensitive_words where status = 1");
for (Record r : list) {
ret.add(r.getStr("word"));
}
return ret;
}
/**
* MyProjectValidator 中checkSensitiveWords(c.getPara("project.content"),....) 这行调用
* 在第一次调用时传入 null target String[1]对象而里面的内容为 null第二次调用 target 则为 null
*/
public static String checkSensitiveWord(String... target) {
if (target != null) {
for (String s : target) {
if (s != null) {
for (String sensitiveWord : sensitiveWords) {
if (s.indexOf(sensitiveWord) >= 0) {
return sensitiveWord;
}
}
}
}
}
return null;
}
}

41
src/main/java/com/bt/common/kit/SqlKit.java

@ -0,0 +1,41 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.kit;
import java.util.List;
/**
* SqlKit
*/
public class SqlKit {
/**
* id 列表 join 起来用逗号分隔并且用小括号括起来
*/
public static void joinIds(List<Integer> idList, StringBuilder ret) {
ret.append("(");
boolean isFirst = true;
for (Integer id : idList) {
if (isFirst) {
isFirst = false;
} else {
ret.append(", ");
}
ret.append(id.toString());
}
ret.append(")");
}
}

25
src/main/java/com/bt/common/loginlog/LoginLog.java

@ -0,0 +1,25 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.loginlog;
import com.jfinal.plugin.activerecord.Model;
/**
* 记录用户登录信息的表 login_log
*/
@SuppressWarnings("serial")
public class LoginLog extends Model<LoginLog> {
}

32
src/main/java/com/bt/common/loginlog/LoginLogService.java

@ -0,0 +1,32 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.loginlog;
/**
* 用户登录时做日志便于统计活跃用户
* 用缓存缓冲一下不要每次都写库
*
* 例如缓存设置为
* map {accountId, date, times}
*
* 用户的 session 过期时间设置为 30 分钟如果过期就会触发登录操作
* 另外注意登录操作可能是利用 cookie 值自动实现的但是这个自动实现也
* 算做是一次登录相当于只要 ehcache 中没有用户 session建立这个 session就算做是一次登录
*
* 集群部署时需要考虑 ehcache 同步问题
*/
public class LoginLogService {
}

55
src/main/java/com/bt/common/model/Account.java

@ -0,0 +1,55 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.safe.JsoupFilter;
import com.bt.common.model.base.BaseAccount;
/**
* Account
*/
public class Account extends BaseAccount<Account> {
private static final long serialVersionUID = 1L;
public static final String AVATAR_NO_AVATAR = "x.jpg"; // 刚注册时使用默认头像
public static final int STATUS_LOCK_ID = -1; // 锁定账号,无法做任何事情
public static final int STATUS_REG = 0; // 注册、未激活
public static final int STATUS_OK = 1; // 正常、已激活
public boolean isStatusOk() {
return getStatus() == STATUS_OK;
}
public boolean isStatusReg() {
return getStatus() == STATUS_REG;
}
public boolean isStatusLockId() {
return getStatus() == STATUS_LOCK_ID;
}
/**
* 过滤掉 nickName 中的 html 标记恶意脚本
*/
protected void filter(int filterBy) {
JsoupFilter.filterAccountNickName(this);
}
public Account removeSensitiveInfo() {
remove("password", "salt");
return this;
}
}

88
src/main/java/com/bt/common/model/AuthCode.java

@ -0,0 +1,88 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseAuthCode;
/**
* 授权码目前已用于
* 1账号激活
* 2密码找回
* 未来随着业务增加可添加新类型可能需要加 data 字段传递业务所需的额外数据
*/
@SuppressWarnings("serial")
public class AuthCode extends BaseAuthCode<AuthCode> {
public static final int TYPE_REG_ACTIVATE = 0; // 注册激活
public static final int TYPE_RETRIEVE_PASSWORD = 1; // 找回密码
/**
* 在保存前保障 type 正确随着 type 的增加需要修改此处的代码
*/
public boolean save() {
int type = getType();
if (type < TYPE_REG_ACTIVATE || type > TYPE_RETRIEVE_PASSWORD) {
throw new RuntimeException("授权码类型不正确: " + type);
}
return super.save();
}
public boolean update() {
throw new RuntimeException("授权码不支持更新操作");
}
/**
* 是否是有效的注册激活授权码
*/
public boolean isValidRegActivateAuthCode() {
return notExpired() && isTypeRegActivate();
}
/**
* 是否是有效的密码找回授权码
*/
public boolean isValidRetrievePasswordAuthCode() {
return notExpired() && isTypeRetrievePassword();
}
/**
* 是否是账号激活授权码
*/
public boolean isTypeRegActivate() {
return getType() == TYPE_REG_ACTIVATE;
}
/**
* 是否是密码找回授权码
*/
public boolean isTypeRetrievePassword() {
return getType() == TYPE_RETRIEVE_PASSWORD;
}
/**
* 是否已过期
*/
public boolean isExpired() {
return getExpireAt() < System.currentTimeMillis();
}
/**
* 是否未过期
*/
public boolean notExpired() {
return ! isExpired();
}
}

36
src/main/java/com/bt/common/model/Document.java

@ -0,0 +1,36 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseDocument;
/**
* Generated by JFinal.
*/
@SuppressWarnings("serial")
public class Document extends BaseDocument<Document> {
public static final int PUBLISH_NO = 0; // 不发布
public static final int PUBLISH_YES = 1; // 发布
public boolean isPublished() {
return getPublish() == PUBLISH_YES;
}
public boolean notPublish() {
return getPublish() == PUBLISH_NO;
}
}

25
src/main/java/com/bt/common/model/Download.java

@ -0,0 +1,25 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseDownload;
/**
* Generated by JFinal.
*/
@SuppressWarnings("serial")
public class Download extends BaseDownload<Download> {
}

25
src/main/java/com/bt/common/model/DownloadLog.java

@ -0,0 +1,25 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseDownloadLog;
/**
* Generated by JFinal.
*/
@SuppressWarnings("serial")
public class DownloadLog extends BaseDownloadLog<DownloadLog> {
}

78
src/main/java/com/bt/common/model/Favorite.java

@ -0,0 +1,78 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseFavorite;
import java.util.HashMap;
import java.util.Map;
/**
* Generated by JFinal.
*/
@SuppressWarnings("serial")
public class Favorite extends BaseFavorite<Favorite> {
// 收藏引用的资源类型
public static final int REF_TYPE_PROJECT = 1;
public static final int REF_TYPE_SHARE = 2;
public static final int REF_TYPE_FEEDBACK = 3;
private static Map<String, Integer> tableToTypeValue = new HashMap<String, Integer>(){{
put("project", REF_TYPE_PROJECT);
put("share", REF_TYPE_SHARE);
put("feedback", REF_TYPE_FEEDBACK);
}};
private static Map<Integer, String> typeValueToTable = new HashMap<Integer, String>(){{
put(REF_TYPE_PROJECT, "project");
put(REF_TYPE_SHARE, "share");
put(REF_TYPE_FEEDBACK, "feedback");
}};
public static String getRefTable(int refType) {
return typeValueToTable.get(refType);
}
public static int getRefType(String tableName) {
Integer refType = tableToTypeValue.get(tableName);
if (refType == null) {
throw new IllegalArgumentException("tableName 不正确");
}
return refType;
}
public static void checkRefTypeTable(String refTypeTable) {
if ( !tableToTypeValue.containsKey(refTypeTable) ) {
throw new IllegalArgumentException("refType 不正确");
}
}
/**
* 返回收藏资源的 url
*/
public String getRefUrl() {
int refType = getRefType();
int refId = getRefId();
if (refType == REF_TYPE_PROJECT) {
return "/project/" + refId;
} else if (refType == REF_TYPE_SHARE) {
return "/share/" + refId;
} else if (refType == REF_TYPE_FEEDBACK) {
return "/feedback/" + refId;
} else {
throw new RuntimeException("refType 不正确,无法生成 url,reType = " + refType);
}
}
}

37
src/main/java/com/bt/common/model/Feedback.java

@ -0,0 +1,37 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.safe.JsoupFilter;
import com.bt.common.model.base.BaseFeedback;
/**
* 反馈
*/
@SuppressWarnings("serial")
public class Feedback extends BaseFeedback<Feedback> {
/**
* 举报达到屏蔽的次数达到这个数直接屏蔽帖子
*/
public static final int REPORT_BLOCK_NUM = 3;
/**
* 过滤 title content 字段的 html 标记防止 XSS 攻击
*/
protected void filter(int filterBy) {
JsoupFilter.filterTitleAndContent(this);
}
}

40
src/main/java/com/bt/common/model/FeedbackReply.java

@ -0,0 +1,40 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.safe.JsoupFilter;
import com.bt.common.model.base.BaseFeedbackReply;
/**
* 反馈回复
*/
@SuppressWarnings("serial")
public class FeedbackReply extends BaseFeedbackReply<FeedbackReply> {
/**
* 举报达到屏蔽的次数达到这个数直接屏蔽帖子
*/
public static final int REPORT_BLOCK_NUM = 3;
/**
* 过滤 title content 字段的 html 标记防止 XSS 攻击
* 将回车换行转换成 <br> 标记便于 html 中显示换行
*/
protected void filter(int filterBy) {
String content = getContent().trim().replaceAll("\r\n", "<br>").replaceAll("\n", "<br>").replaceAll("\r", "<br>");
content = JsoupFilter.filterArticleContent(content);
setContent(content);
}
}

38
src/main/java/com/bt/common/model/Message.java

@ -0,0 +1,38 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseMessage;
import com.bt.common.safe.JsoupFilter;
/**
* Generated by JFinal.
*/
@SuppressWarnings("serial")
public class Message extends BaseMessage<Message> {
public static final int TYPE_NORMAL = 0; // 普通消息
public static final int TYPE_SYSTEM = 1; // 系统消息
/**
* 过滤 title content 字段的 html 标记防止 XSS 攻击
* 将回车换行转换成 <br> 标记便于 html 中显示换行
*/
protected void filter(int filterBy) {
String content = getContent().trim().replaceAll("\r\n", "<br>").replaceAll("\n", "<br>").replaceAll("\r", "<br>");
content = JsoupFilter.filterArticleContent(content);
setContent(content);
}
}

65
src/main/java/com/bt/common/model/NewsFeed.java

@ -0,0 +1,65 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseNewsFeed;
/**
* NewsFeed
*
* id
* accountId 发布该动态的用户
* refType 动态引用类型
* refId 动态引用所关联的 id refType 配合可唯一确定是某个表中的某条记录
* refParentType refId 对象的父对象例如 share_reply 的父对象是 share
* refParentId refId 对象的父对象的 id
* createAt
*/
@SuppressWarnings("serial")
public class NewsFeed extends BaseNewsFeed<NewsFeed> {
public static final int REF_TYPE_PROJECT = 1; // 项目动态
public static final int REF_TYPE_PROJECT_REPLY = 2; // 项目回复动态,暂时不用
public static final int REF_TYPE_SHARE = 3; // 分享动态
public static final int REF_TYPE_SHARE_REPLY = 4; // 分享回复动态
public static final int REF_TYPE_FEEDBACK = 5; // 反馈动态
public static final int REF_TYPE_FEEDBACK_REPLY = 6; // 反馈回复动态
public boolean isRefTypeProject() {
return getRefType() == REF_TYPE_PROJECT;
}
public boolean isRefTypeProjectReply() {
return getRefType() == REF_TYPE_PROJECT_REPLY;
}
public boolean isRefTypeShare() {
return getRefType() == REF_TYPE_SHARE;
}
public boolean isRefTypeShareReply() {
return getRefType() == REF_TYPE_SHARE_REPLY;
}
public boolean isRefTypeFeedback() {
return getRefType() == REF_TYPE_FEEDBACK;
}
public boolean isRefTypeFeedbackReply() {
return getRefType() == REF_TYPE_FEEDBACK_REPLY;
}
}

11
src/main/java/com/bt/common/model/Permission.java

@ -0,0 +1,11 @@
package com.bt.common.model;
import com.bt.common.model.base.BasePermission;
/**
* Generated by JFinal.
*/
@SuppressWarnings("serial")
public class Permission extends BasePermission<Permission> {
}

37
src/main/java/com/bt/common/model/Project.java

@ -0,0 +1,37 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.safe.JsoupFilter;
import com.bt.common.model.base.BaseProject;
/**
* 项目
*/
@SuppressWarnings("serial")
public class Project extends BaseProject<Project> {
/**
* 举报达到屏蔽的次数达到这个数直接屏蔽帖子
*/
public static final int REPORT_BLOCK_NUM = 3;
/**
* 过滤 title content 字段的 html 标记防止 XSS 攻击
*/
protected void filter(int filterBy) {
JsoupFilter.filterProject(this);
}
}

30
src/main/java/com/bt/common/model/ReferMe.java

@ -0,0 +1,30 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseReferMe;
/**
* refer_me 提到我 ReferMe 模型用于生成与我有关的记录
* 关联到 news_feed
*/
@SuppressWarnings("serial")
public class ReferMe extends BaseReferMe<ReferMe> {
public static final int TYPE_AT_ME = 1; // @我
public static final int TYPE_COMMENT_ME = 2; // 评论我
public static final int TYPE_SHARE_REF_MY_PROJECT = 3; // 分享引用到我的项目
public static final int TYPE_FEEDBACK_TO_MY_PROJECT = 4; // 反馈到我的项目
}

31
src/main/java/com/bt/common/model/Remind.java

@ -0,0 +1,31 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseRemind;
/**
* 提醒用于在右上角弹出层显示各类提醒
* accountId int(11) 主键
* referMe int(11) @提到我 的消息条数
* message int(11) 私信 消息条数暂时不用
*/
@SuppressWarnings("serial")
public class Remind extends BaseRemind<Remind> {
// 提醒类型
public static final int TYPE_REFER_ME = 0; // 非数据库字段,仅用于在业务层判断所操作的类型
public static final int TYPE_MESSAGE = 1; // 非数据库字段,仅用于在业务层判断所操作的类型
public static final int TYPE_FANS = 2; // 非数据库字段,仅用于在业务层判断所操作的类型
}

11
src/main/java/com/bt/common/model/Role.java

@ -0,0 +1,11 @@
package com.bt.common.model;
import com.bt.common.model.base.BaseRole;
/**
* Generated by JFinal.
*/
@SuppressWarnings("serial")
public class Role extends BaseRole<Role> {
}

44
src/main/java/com/bt/common/model/Session.java

@ -0,0 +1,44 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseSession;
/**
* session 存放在数据库中并引入 cache 中间层优点如下
* 1简单且高性能
* 2支持分布式与集群
* 3支持服务器断电和重启
* 4支持 tomcatjetty 等运行容器重启
*/
public class Session extends BaseSession<Session> {
private static final long serialVersionUID = 1L;
public static final Session dao = new Session().dao();
/**
* 登录会话是否已过期
*/
public boolean isExpired() {
return getExpireAt() < System.currentTimeMillis();
}
/**
* 登录会话是否未过期
*/
public boolean notExpired() {
return ! isExpired();
}
}

37
src/main/java/com/bt/common/model/Share.java

@ -0,0 +1,37 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.safe.JsoupFilter;
import com.bt.common.model.base.BaseShare;
/**
* Share
*/
@SuppressWarnings("serial")
public class Share extends BaseShare<Share> {
/**
* 举报达到屏蔽的次数达到这个数直接屏蔽帖子
*/
public static final int REPORT_BLOCK_NUM = 3;
/**
* 过滤 title content 字段的 html 标记防止 XSS 攻击
*/
protected void filter(int filterBy) {
JsoupFilter.filterTitleAndContent(this);
}
}

40
src/main/java/com/bt/common/model/ShareReply.java

@ -0,0 +1,40 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.safe.JsoupFilter;
import com.bt.common.model.base.BaseShareReply;
/**
* 分享回复
*/
@SuppressWarnings("serial")
public class ShareReply extends BaseShareReply<ShareReply> {
/**
* 举报达到屏蔽的次数达到这个数直接屏蔽帖子
*/
public static final int REPORT_BLOCK_NUM = 3;
/**
* 过滤 title content 字段的 html 标记防止 XSS 攻击
* 将回车换行转换成 <br> 标记便于 html 中显示换行
*/
protected void filter(int filterBy) {
String content = getContent().trim().replaceAll("\r\n", "<br>").replaceAll("\n", "<br>").replaceAll("\r", "<br>");
content = JsoupFilter.filterArticleContent(content);
setContent(content);
}
}

25
src/main/java/com/bt/common/model/TaskList.java

@ -0,0 +1,25 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.bt.common.model.base.BaseTaskList;
/**
* Generated by JFinal.
*/
@SuppressWarnings("serial")
public class TaskList extends BaseTaskList<TaskList> {
}

98
src/main/java/com/bt/common/model/_Generator.java

@ -0,0 +1,98 @@
/**
* 请勿将俱乐部专享资源复制给其他人保护知识产权即是保护我们所在的行业进而保护我们自己的利益
* 即便是公司的同事也请尊重 JFinal 作者的努力与付出不要复制给同事
*
* 如果你尚未加入俱乐部请立即删除该项目或者现在加入俱乐部http://jfinal.com/club
*
* 俱乐部将提供 jfinal-club 项目文档与设计资源专用 QQ 以及作者在俱乐部定期的分享与答疑
* 价值远比仅仅拥有 jfinal club 项目源代码要大得多
*
* JFinal 俱乐部是五年以来首次寻求外部资源的尝试以便于有资源创建更加
* 高品质的产品与服务为大家带来更大的价值所以请大家多多支持不要将
* 首次的尝试扼杀在了摇篮之中
*/
package com.bt.common.model;
import com.jfinal.kit.PathKit;
import com.jfinal.plugin.activerecord.dialect.MysqlDialect;
import com.jfinal.plugin.activerecord.generator.Generator;
import com.jfinal.plugin.druid.DruidPlugin;
import com.bt.common.JFinalClubConfig;
import javax.sql.DataSource;
/**
* ModelBaseModel_MappingKit 生成器
*/
public class _Generator {
/**
* 在此统一添加不参与生成的 table不参与生成的 table 主要有
* 1用于建立表之间关系的关联表 account_role
* 2部分功能使用 Db + Record 模式实现 project_page_view
*/
private static String[] excludedTable = {
"news_feed_reply", // 暂不实现该功能
"project_page_view", "share_page_view", "feedback_page_view",
"login_log",
"sensitive_words",
"upload_counter",
"task_run_log",
"message_tip",
"friend",
"project_like", "share_like", "feedback_like",
"share_reply_like", "feedback_reply_like",
"like_message_log",
"account_role", "role_permission"
};
/**
* 重用 JFinalClubConfig 中的数据源配置避免冗余配置
*/
public static DataSource getDataSource() {
DruidPlugin druidPlugin = JFinalClubConfig.getDruidPlugin();
druidPlugin.start();
return druidPlugin.getDataSource();
}
public static void main(String[] args) {
// base model 所使用的包名
String baseModelPackageName = "com.bt.common.model.base";
// base model 文件保存路径
String baseModelOutputDir = PathKit.getWebRootPath()
+ "/src/main/java/com/bt/common/model/base";
System.out.println("输出路径:"+ baseModelOutputDir);
// model 所使用的包名 (MappingKit 默认使用的包名)
String modelPackageName = "com.bt.common.model";
// model 文件保存路径 (MappingKit 与 DataDictionary 文件默认保存路径)
String modelOutputDir = baseModelOutputDir + "/..";
// 创建生成器
Generator gen = new Generator(getDataSource(), baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir);
// 设置数据库方言
gen.setDialect(new MysqlDialect());
// 设置是否生成字段备注
gen.setGenerateRemarks(true);
// 添加不需要生成的表名
for (String table : excludedTable) {
gen.addExcludedTable(table.trim());
}
// 设置是否在 Model 中生成 dao 对象
gen.setGenerateDaoInModel(false);
// 设置是否生成字典文件
gen.setGenerateDataDictionary(false);
// 设置需要被移除的表名前缀用于生成modelName。例如表名 "osc_user",移除前缀 "osc_"后生成的model名为 "User"而非 OscUser
// gernerator.setRemovedTableNamePrefixes("t_");
// 生成
gen.generate();
}
}

41
src/main/java/com/bt/common/model/_MappingKit.java

@ -0,0 +1,41 @@
package com.bt.common.model;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
/**
* Generated by JFinal, do not modify this file.
* <pre>
* Example:
* public void configPlugin(Plugins me) {
* ActiveRecordPlugin arp = new ActiveRecordPlugin(...);
* _MappingKit.mapping(arp);
* me.add(arp);
* }
* </pre>
*/
public class _MappingKit {
public static void mapping(ActiveRecordPlugin arp) {
arp.addMapping("account", "id", Account.class);
arp.addMapping("auth_code", "id", AuthCode.class);
// Composite Primary Key order: mainMenu,subMenu
arp.addMapping("document", "mainMenu,subMenu", Document.class);
arp.addMapping("download", "id", Download.class);
arp.addMapping("download_log", "id", DownloadLog.class);
arp.addMapping("favorite", "id", Favorite.class);
arp.addMapping("feedback", "id", Feedback.class);
arp.addMapping("feedback_reply", "id", FeedbackReply.class);
arp.addMapping("message", "id", Message.class);
arp.addMapping("news_feed", "id", NewsFeed.class);
arp.addMapping("permission", "id", Permission.class);
arp.addMapping("project", "id", Project.class);
arp.addMapping("refer_me", "id", ReferMe.class);
arp.addMapping("remind", "accountId", Remind.class);
arp.addMapping("role", "id", Role.class);
arp.addMapping("session", "id", Session.class);
arp.addMapping("share", "id", Share.class);
arp.addMapping("share_reply", "id", ShareReply.class);
arp.addMapping("task_list", "id", TaskList.class);
}
}

98
src/main/java/com/bt/common/model/base/BaseAccount.java

@ -0,0 +1,98 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseAccount<M extends BaseAccount<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setNickName(java.lang.String nickName) {
set("nickName", nickName);
}
public java.lang.String getNickName() {
return getStr("nickName");
}
public void setUserName(java.lang.String userName) {
set("userName", userName);
}
public java.lang.String getUserName() {
return getStr("userName");
}
public void setPassword(java.lang.String password) {
set("password", password);
}
public java.lang.String getPassword() {
return getStr("password");
}
public void setSalt(java.lang.String salt) {
set("salt", salt);
}
public java.lang.String getSalt() {
return getStr("salt");
}
public void setStatus(java.lang.Integer status) {
set("status", status);
}
public java.lang.Integer getStatus() {
return getInt("status");
}
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
public void setIp(java.lang.String ip) {
set("ip", ip);
}
public java.lang.String getIp() {
return getStr("ip");
}
public void setAvatar(java.lang.String avatar) {
set("avatar", avatar);
}
public java.lang.String getAvatar() {
return getStr("avatar");
}
/**
* 被赞次数
*/
public void setLikeCount(java.lang.Integer likeCount) {
set("likeCount", likeCount);
}
/**
* 被赞次数
*/
public java.lang.Integer getLikeCount() {
return getInt("likeCount");
}
}

44
src/main/java/com/bt/common/model/base/BaseAuthCode.java

@ -0,0 +1,44 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseAuthCode<M extends BaseAuthCode<M>> extends Model<M> implements IBean {
public void setId(java.lang.String id) {
set("id", id);
}
public java.lang.String getId() {
return getStr("id");
}
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
public void setExpireAt(java.lang.Long expireAt) {
set("expireAt", expireAt);
}
public java.lang.Long getExpireAt() {
return getLong("expireAt");
}
public void setType(java.lang.Integer type) {
set("type", type);
}
public java.lang.Integer getType() {
return getInt("type");
}
}

80
src/main/java/com/bt/common/model/base/BaseDocument.java

@ -0,0 +1,80 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseDocument<M extends BaseDocument<M>> extends Model<M> implements IBean {
/**
* 主菜单
*/
public void setMainMenu(java.lang.Integer mainMenu) {
set("mainMenu", mainMenu);
}
/**
* 主菜单
*/
public java.lang.Integer getMainMenu() {
return getInt("mainMenu");
}
/**
* 子菜单
*/
public void setSubMenu(java.lang.Integer subMenu) {
set("subMenu", subMenu);
}
/**
* 子菜单
*/
public java.lang.Integer getSubMenu() {
return getInt("subMenu");
}
public void setTitle(java.lang.String title) {
set("title", title);
}
public java.lang.String getTitle() {
return getStr("title");
}
public void setContent(java.lang.String content) {
set("content", content);
}
public java.lang.String getContent() {
return getStr("content");
}
public void setUpdateAt(java.time.LocalDateTime updateAt) {
set("updateAt", updateAt);
}
public java.time.LocalDateTime getUpdateAt() {
return getLocalDateTime("updateAt");
}
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
public void setPublish(java.lang.Integer publish) {
set("publish", publish);
}
public java.lang.Integer getPublish() {
return getInt("publish");
}
}

96
src/main/java/com/bt/common/model/base/BaseDownload.java

@ -0,0 +1,96 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseDownload<M extends BaseDownload<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setFileName(java.lang.String fileName) {
set("fileName", fileName);
}
public java.lang.String getFileName() {
return getStr("fileName");
}
/**
* 描述
*/
public void setDescr(java.lang.String descr) {
set("descr", descr);
}
/**
* 描述
*/
public java.lang.String getDescr() {
return getStr("descr");
}
/**
* 文件类型
*/
public void setFileType(java.lang.String fileType) {
set("fileType", fileType);
}
/**
* 文件类型
*/
public java.lang.String getFileType() {
return getStr("fileType");
}
public void setSize(java.lang.String size) {
set("size", size);
}
public java.lang.String getSize() {
return getStr("size");
}
public void setCreateDate(java.util.Date createDate) {
set("createDate", createDate);
}
public java.util.Date getCreateDate() {
return getDate("createDate");
}
public void setPath(java.lang.String path) {
set("path", path);
}
public java.lang.String getPath() {
return getStr("path");
}
public void setDownloadCount(java.lang.Integer downloadCount) {
set("downloadCount", downloadCount);
}
public java.lang.Integer getDownloadCount() {
return getInt("downloadCount");
}
public void setIsShow(java.lang.Integer isShow) {
set("isShow", isShow);
}
public java.lang.Integer getIsShow() {
return getInt("isShow");
}
}

52
src/main/java/com/bt/common/model/base/BaseDownloadLog.java

@ -0,0 +1,52 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseDownloadLog<M extends BaseDownloadLog<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
public void setIp(java.lang.String ip) {
set("ip", ip);
}
public java.lang.String getIp() {
return getStr("ip");
}
public void setFileName(java.lang.String fileName) {
set("fileName", fileName);
}
public java.lang.String getFileName() {
return getStr("fileName");
}
public void setDownloadDate(java.util.Date downloadDate) {
set("downloadDate", downloadDate);
}
public java.util.Date getDownloadDate() {
return getDate("downloadDate");
}
}

70
src/main/java/com/bt/common/model/base/BaseFavorite.java

@ -0,0 +1,70 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseFavorite<M extends BaseFavorite<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
/**
* 收藏类型1为项目2为分享3为反馈
*/
public void setRefType(java.lang.Integer refType) {
set("refType", refType);
}
/**
* 收藏类型1为项目2为分享3为反馈
*/
public java.lang.Integer getRefType() {
return getInt("refType");
}
/**
* 被收藏的资源 id
*/
public void setRefId(java.lang.Integer refId) {
set("refId", refId);
}
/**
* 被收藏的资源 id
*/
public java.lang.Integer getRefId() {
return getInt("refId");
}
/**
* 收藏时间
*/
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
/**
* 收藏时间
*/
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
}

92
src/main/java/com/bt/common/model/base/BaseFeedback.java

@ -0,0 +1,92 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseFeedback<M extends BaseFeedback<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
public void setProjectId(java.lang.Integer projectId) {
set("projectId", projectId);
}
public java.lang.Integer getProjectId() {
return getInt("projectId");
}
public void setTitle(java.lang.String title) {
set("title", title);
}
public java.lang.String getTitle() {
return getStr("title");
}
public void setContent(java.lang.String content) {
set("content", content);
}
public java.lang.String getContent() {
return getStr("content");
}
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
public void setClickCount(java.lang.Integer clickCount) {
set("clickCount", clickCount);
}
public java.lang.Integer getClickCount() {
return getInt("clickCount");
}
public void setReport(java.lang.Integer report) {
set("report", report);
}
public java.lang.Integer getReport() {
return getInt("report");
}
public void setLikeCount(java.lang.Integer likeCount) {
set("likeCount", likeCount);
}
public java.lang.Integer getLikeCount() {
return getInt("likeCount");
}
public void setFavoriteCount(java.lang.Integer favoriteCount) {
set("favoriteCount", favoriteCount);
}
public java.lang.Integer getFavoriteCount() {
return getInt("favoriteCount");
}
}

60
src/main/java/com/bt/common/model/base/BaseFeedbackReply.java

@ -0,0 +1,60 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseFeedbackReply<M extends BaseFeedbackReply<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setFeedbackId(java.lang.Integer feedbackId) {
set("feedbackId", feedbackId);
}
public java.lang.Integer getFeedbackId() {
return getInt("feedbackId");
}
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
public void setContent(java.lang.String content) {
set("content", content);
}
public java.lang.String getContent() {
return getStr("content");
}
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
public void setReport(java.lang.Integer report) {
set("report", report);
}
public java.lang.Integer getReport() {
return getInt("report");
}
}

112
src/main/java/com/bt/common/model/base/BaseMessage.java

@ -0,0 +1,112 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseMessage<M extends BaseMessage<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
/**
* 消息的主人
*/
public void setUser(java.lang.Integer user) {
set("user", user);
}
/**
* 消息的主人
*/
public java.lang.Integer getUser() {
return getInt("user");
}
/**
* 对方的ID
*/
public void setFriend(java.lang.Integer friend) {
set("friend", friend);
}
/**
* 对方的ID
*/
public java.lang.Integer getFriend() {
return getInt("friend");
}
/**
* 发送者
*/
public void setSender(java.lang.Integer sender) {
set("sender", sender);
}
/**
* 发送者
*/
public java.lang.Integer getSender() {
return getInt("sender");
}
/**
* 接收者
*/
public void setReceiver(java.lang.Integer receiver) {
set("receiver", receiver);
}
/**
* 接收者
*/
public java.lang.Integer getReceiver() {
return getInt("receiver");
}
/**
* 0普通消息1系统消息
*/
public void setType(java.lang.Integer type) {
set("type", type);
}
/**
* 0普通消息1系统消息
*/
public java.lang.Integer getType() {
return getInt("type");
}
public void setContent(java.lang.String content) {
set("content", content);
}
public java.lang.String getContent() {
return getStr("content");
}
/**
* 创建时间
*/
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
/**
* 创建时间
*/
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
}

98
src/main/java/com/bt/common/model/base/BaseNewsFeed.java

@ -0,0 +1,98 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseNewsFeed<M extends BaseNewsFeed<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
/**
* 动态创建者
*/
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
/**
* 动态创建者
*/
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
/**
* 动态引用类型
*/
public void setRefType(java.lang.Integer refType) {
set("refType", refType);
}
/**
* 动态引用类型
*/
public java.lang.Integer getRefType() {
return getInt("refType");
}
/**
* 动态引用所关联的 id
*/
public void setRefId(java.lang.Integer refId) {
set("refId", refId);
}
/**
* 动态引用所关联的 id
*/
public java.lang.Integer getRefId() {
return getInt("refId");
}
/**
* reply所属的贴子类型, 与type 字段填的值一样
*/
public void setRefParentType(java.lang.Integer refParentType) {
set("refParentType", refParentType);
}
/**
* reply所属的贴子类型, 与type 字段填的值一样
*/
public java.lang.Integer getRefParentType() {
return getInt("refParentType");
}
public void setRefParentId(java.lang.Integer refParentId) {
set("refParentId", refParentId);
}
public java.lang.Integer getRefParentId() {
return getInt("refParentId");
}
/**
* 动态创建时间
*/
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
/**
* 动态创建时间
*/
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
}

44
src/main/java/com/bt/common/model/base/BasePermission.java

@ -0,0 +1,44 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BasePermission<M extends BasePermission<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setActionKey(java.lang.String actionKey) {
set("actionKey", actionKey);
}
public java.lang.String getActionKey() {
return getStr("actionKey");
}
public void setController(java.lang.String controller) {
set("controller", controller);
}
public java.lang.String getController() {
return getStr("controller");
}
public void setRemark(java.lang.String remark) {
set("remark", remark);
}
public java.lang.String getRemark() {
return getStr("remark");
}
}

92
src/main/java/com/bt/common/model/base/BaseProject.java

@ -0,0 +1,92 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseProject<M extends BaseProject<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
public void setName(java.lang.String name) {
set("name", name);
}
public java.lang.String getName() {
return getStr("name");
}
public void setTitle(java.lang.String title) {
set("title", title);
}
public java.lang.String getTitle() {
return getStr("title");
}
public void setContent(java.lang.String content) {
set("content", content);
}
public java.lang.String getContent() {
return getStr("content");
}
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
public void setClickCount(java.lang.Integer clickCount) {
set("clickCount", clickCount);
}
public java.lang.Integer getClickCount() {
return getInt("clickCount");
}
public void setReport(java.lang.Integer report) {
set("report", report);
}
public java.lang.Integer getReport() {
return getInt("report");
}
public void setLikeCount(java.lang.Integer likeCount) {
set("likeCount", likeCount);
}
public java.lang.Integer getLikeCount() {
return getInt("likeCount");
}
public void setFavoriteCount(java.lang.Integer favoriteCount) {
set("favoriteCount", favoriteCount);
}
public java.lang.Integer getFavoriteCount() {
return getInt("favoriteCount");
}
}

70
src/main/java/com/bt/common/model/base/BaseReferMe.java

@ -0,0 +1,70 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseReferMe<M extends BaseReferMe<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
/**
* 接收者账号id
*/
public void setReferAccountId(java.lang.Integer referAccountId) {
set("referAccountId", referAccountId);
}
/**
* 接收者账号id
*/
public java.lang.Integer getReferAccountId() {
return getInt("referAccountId");
}
/**
* newsFeedId
*/
public void setNewsFeedId(java.lang.Integer newsFeedId) {
set("newsFeedId", newsFeedId);
}
/**
* newsFeedId
*/
public java.lang.Integer getNewsFeedId() {
return getInt("newsFeedId");
}
/**
* @我评论我等等的refer类型
*/
public void setType(java.lang.Integer type) {
set("type", type);
}
/**
* @我评论我等等的refer类型
*/
public java.lang.Integer getType() {
return getInt("type");
}
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
}

68
src/main/java/com/bt/common/model/base/BaseRemind.java

@ -0,0 +1,68 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseRemind<M extends BaseRemind<M>> extends Model<M> implements IBean {
/**
* 用户账号id必须手动指定不自增
*/
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
/**
* 用户账号id必须手动指定不自增
*/
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
/**
* 提到我的消息条数
*/
public void setReferMe(java.lang.Integer referMe) {
set("referMe", referMe);
}
/**
* 提到我的消息条数
*/
public java.lang.Integer getReferMe() {
return getInt("referMe");
}
/**
* 私信条数
*/
public void setMessage(java.lang.Integer message) {
set("message", message);
}
/**
* 私信条数
*/
public java.lang.Integer getMessage() {
return getInt("message");
}
/**
* 粉丝增加个数
*/
public void setFans(java.lang.Integer fans) {
set("fans", fans);
}
/**
* 粉丝增加个数
*/
public java.lang.Integer getFans() {
return getInt("fans");
}
}

36
src/main/java/com/bt/common/model/base/BaseRole.java

@ -0,0 +1,36 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseRole<M extends BaseRole<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setName(java.lang.String name) {
set("name", name);
}
public java.lang.String getName() {
return getStr("name");
}
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
}

36
src/main/java/com/bt/common/model/base/BaseSession.java

@ -0,0 +1,36 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseSession<M extends BaseSession<M>> extends Model<M> implements IBean {
public void setId(java.lang.String id) {
set("id", id);
}
public java.lang.String getId() {
return getStr("id");
}
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
public void setExpireAt(java.lang.Long expireAt) {
set("expireAt", expireAt);
}
public java.lang.Long getExpireAt() {
return getLong("expireAt");
}
}

92
src/main/java/com/bt/common/model/base/BaseShare.java

@ -0,0 +1,92 @@
package com.bt.common.model.base;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.IBean;
/**
* Generated by JFinal, do not modify this file.
*/
@SuppressWarnings("serial")
public abstract class BaseShare<M extends BaseShare<M>> extends Model<M> implements IBean {
public void setId(java.lang.Integer id) {
set("id", id);
}
public java.lang.Integer getId() {
return getInt("id");
}
public void setAccountId(java.lang.Integer accountId) {
set("accountId", accountId);
}
public java.lang.Integer getAccountId() {
return getInt("accountId");
}
public void setProjectId(java.lang.Integer projectId) {
set("projectId", projectId);
}
public java.lang.Integer getProjectId() {
return getInt("projectId");
}
public void setTitle(java.lang.String title) {
set("title", title);
}
public java.lang.String getTitle() {
return getStr("title");
}
public void setContent(java.lang.String content) {
set("content", content);
}
public java.lang.String getContent() {
return getStr("content");
}
public void setCreateAt(java.time.LocalDateTime createAt) {
set("createAt", createAt);
}
public java.time.LocalDateTime getCreateAt() {
return getLocalDateTime("createAt");
}
public void setClickCount(java.lang.Integer clickCount) {
set("clickCount", clickCount);
}
public java.lang.Integer getClickCount() {
return getInt("clickCount");
}
public void setReport(java.lang.Integer report) {
set("report", report);
}
public java.lang.Integer getReport() {
return getInt("report");
}
public void setLikeCount(java.lang.Integer likeCount) {
set("likeCount", likeCount);
}
public java.lang.Integer getLikeCount() {
return getInt("likeCount");
}
public void setFavoriteCount(java.lang.Integer favoriteCount) {
set("favoriteCount", favoriteCount);
}
public java.lang.Integer getFavoriteCount() {
return getInt("favoriteCount");
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save