From 7d4b2abd4340dd2ce8ac66caea615b0043a39135 Mon Sep 17 00:00:00 2001 From: gjq Date: Fri, 3 Dec 2021 18:27:07 +0800 Subject: [PATCH] .... --- .gitignore | 29 + docs/database/cases.sql | 195 + docs/database/system.sql | 186 + docs/document/公司案例库网站构建逻辑.docx | Bin 0 -> 421366 bytes pom.xml | 289 + .../com/nbclass/CasesBootApplication.java | 28 + .../Interceptor/InterceptorConfig.java | 39 + .../nbclass/Interceptor/JWTInterceptor.java | 44 + .../com/nbclass/activity/constant/Const.java | 60 + .../controller/ContentController.java | 492 + .../controller/DataDictController.java | 244 + .../controller/OssUploadController.java | 92 + .../activity/controller/SysLogController.java | 73 + .../casesfirst/CaseTypeController.java | 82 + .../casesfirst/CommentController.java | 342 + .../controller/casesfirst/WxController.java | 218 + .../frontapi/FrontContentController.java | 88 + .../FrontContentLikeNumController.java | 66 + .../frontapi/FrontDataDictController.java | 79 + .../keditor/KindEditorController.java | 332 + .../KindEditorUploadToCDNController.java | 337 + .../KindEditorUploadToOSSController.java | 216 + .../activity/mapper/CaseTypeMapper.java | 49 + .../activity/mapper/CommentMapper.java | 212 + .../activity/mapper/ContentMapper.java | 221 + .../activity/mapper/DataDictItemMapper.java | 59 + .../activity/mapper/DataDictMapper.java | 75 + .../com/nbclass/activity/mapper/WXMapper.java | 67 + .../activity/mapper/WxDepartmentMapper.java | 27 + .../nbclass/activity/mapper/WxUserMapper.java | 53 + .../nbclass/activity/model/Application.java | 54 + .../com/nbclass/activity/model/Check.java | 46 + .../com/nbclass/activity/model/Comment.java | 81 + .../com/nbclass/activity/model/Content.java | 199 + .../nbclass/activity/model/ContentImages.java | 47 + .../nbclass/activity/model/ContentTags.java | 53 + .../com/nbclass/activity/model/DataDict.java | 52 + .../nbclass/activity/model/DataDictItem.java | 53 + .../activity/model/FavoritesFolder.java | 37 + .../com/nbclass/activity/model/GroupChat.java | 29 + .../nbclass/activity/model/GroupChatList.java | 16 + .../nbclass/activity/model/LabelLogger.java | 54 + .../nbclass/activity/model/MemberList.java | 26 + .../com/nbclass/activity/model/Praise.java | 38 + .../nbclass/activity/model/QueryCriteria.java | 95 + .../com/nbclass/activity/model/Report.java | 49 + .../com/nbclass/activity/model/Scoring.java | 36 + .../com/nbclass/activity/model/WeiXiUser.java | 95 + .../nbclass/activity/model/WxDepartment.java | 30 + .../com/nbclass/activity/model/WxUser.java | 46 + .../activity/model/enums/ContentType.java | 57 + .../activity/service/CaseTypeService.java | 52 + .../activity/service/CommentService.java | 208 + .../activity/service/ContentService.java | 193 + .../activity/service/DataDictService.java | 86 + .../service/ElasticSearchService.java | 60 + .../nbclass/activity/service/WxService.java | 47 + .../activity/service/WxUserService.java | 30 + .../service/impl/CaseTypeServiceImpl.java | 51 + .../service/impl/CommentServiceImpl.java | 178 + .../service/impl/ContentServiceImpl.java | 470 + .../service/impl/DataDictServiceImpl.java | 146 + .../impl/ElasticSearchServiceImpl.java | 349 + .../activity/service/impl/WxServiceImpl.java | 46 + .../service/impl/WxUserServiceImpl.java | 145 + .../nbclass/activity/task/ActivityRunner.java | 25 + .../task/MultiThreadScheduleTask.java | 56 + .../nbclass/aliyun/sdk/AliyunConstant.java | 40 + .../nbclass/aliyun/sdk/AliyunOSSUtils.java | 174 + .../nbclass/component/MyErrorAttributes.java | 27 + .../java/com/nbclass/config/CorsConfig.java | 31 + .../com/nbclass/config/DateConverter.java | 21 + .../com/nbclass/config/DefaultViewConfig.java | 23 + .../com/nbclass/enums/ResponseStatus.java | 33 + .../exception/CommonExceptionAdvice.java | 81 + .../nbclass/exception/DataBaseException.java | 26 + .../nbclass/exception/LogicalException.java | 26 + .../nbclass/exception/ParameterException.java | 26 + .../nbclass/exception/ServiceException.java | 26 + .../nbclass/holder/SpringContextHolder.java | 53 + .../java/com/nbclass/shiro/MyShiroRealm.java | 156 + .../java/com/nbclass/shiro/PermsService.java | 19 + .../java/com/nbclass/shiro/ShiroService.java | 125 + .../com/nbclass/shiro/config/LoginType.java | 22 + .../config/MyHashedCredentialsMatcher.java | 19 + .../shiro/config/MyUsernamePasswordToken.java | 45 + .../com/nbclass/shiro/config/ShiroConfig.java | 267 + .../filter/KickoutSessionControlFilter.java | 175 + .../shiro/filter/MobilePageOAuthFilter.java | 94 + .../shiro/filter/ResetPwdUrlFilter.java | 79 + .../system/controller/BaseController.java | 186 + .../system/controller/DatabaseController.java | 20 + .../system/controller/ErrorController.java | 23 + .../controller/OnlineUserController.java | 82 + .../controller/PermissionController.java | 124 + .../controller/QyWxLoginController.java | 138 + .../system/controller/RenderController.java | 41 + .../system/controller/RoleController.java | 184 + .../system/controller/SystemController.java | 200 + .../system/controller/UserController.java | 239 + .../system/mapper/PermissionMapper.java | 86 + .../com/nbclass/system/mapper/RoleMapper.java | 45 + .../system/mapper/RolePermissionMapper.java | 11 + .../nbclass/system/mapper/SysLogMapper.java | 23 + .../com/nbclass/system/mapper/UserMapper.java | 68 + .../nbclass/system/mapper/UserRoleMapper.java | 11 + .../com/nbclass/system/model/ExcelColumn.java | 35 + .../com/nbclass/system/model/Permission.java | 252 + .../java/com/nbclass/system/model/Role.java | 175 + .../nbclass/system/model/RolePermission.java | 82 + .../java/com/nbclass/system/model/SysLog.java | 67 + .../java/com/nbclass/system/model/User.java | 359 + .../com/nbclass/system/model/UserRole.java | 82 + .../system/model/enums/SysLogType.java | 48 + .../system/service/PermissionService.java | 84 + .../nbclass/system/service/RoleService.java | 91 + .../nbclass/system/service/SysLogService.java | 34 + .../nbclass/system/service/UserService.java | 101 + .../service/impl/PermissionServiceImpl.java | 80 + .../system/service/impl/RoleServiceImpl.java | 116 + .../service/impl/SysLogServiceImpl.java | 57 + .../system/service/impl/UserServiceImpl.java | 231 + src/main/java/com/nbclass/util/BCrypt.java | 777 ++ .../java/com/nbclass/util/CommonUtils.java | 849 ++ src/main/java/com/nbclass/util/CopyUtil.java | 143 + src/main/java/com/nbclass/util/CoreConst.java | 48 + src/main/java/com/nbclass/util/CoreUtils.java | 60 + .../java/com/nbclass/util/HttpClientUtil.java | 174 + src/main/java/com/nbclass/util/HttpUtil.java | 123 + src/main/java/com/nbclass/util/HttpUtils.java | 142 + src/main/java/com/nbclass/util/IpUtil.java | 46 + src/main/java/com/nbclass/util/JWTUtils.java | 149 + src/main/java/com/nbclass/util/MyMapper.java | 38 + src/main/java/com/nbclass/util/PageUtil.java | 19 + .../java/com/nbclass/util/PasswordHelper.java | 63 + .../java/com/nbclass/util/ResultUtil.java | 55 + .../java/com/nbclass/util/ShortenUtil.java | 127 + src/main/java/com/nbclass/util/UUIDUtil.java | 60 + src/main/java/com/nbclass/util/WebUtils.java | 80 + .../java/com/nbclass/vo/ChangePasswordVo.java | 36 + .../com/nbclass/vo/PermissionTreeListVo.java | 63 + .../java/com/nbclass/vo/UserOnlineVo.java | 87 + .../java/com/nbclass/vo/UserSessionVo.java | 27 + .../com/nbclass/vo/base/PageResultVo.java | 34 + .../java/com/nbclass/vo/base/ResponseVo.java | 43 + src/main/java/com/nbclass/vo/base/Result.java | 58 + src/main/resources/application-dev.yml | 42 + src/main/resources/application-prod.yml | 42 + src/main/resources/application.yml | 71 + .../resources/generator/generatorConfig.xml | 58 + src/main/resources/log4j2-spring.xml | 83 + .../mapper/activity/CaseTypeMapper.xml | 103 + .../mapper/activity/CommentMapper.xml | 604 + .../mapper/activity/ContentMapper.xml | 434 + .../mapper/activity/DataDictItemMapper.xml | 48 + .../mapper/activity/DataDictMapper.xml | 89 + .../resources/mapper/activity/WXMapper.xml | 101 + .../mapper/activity/WxDepartmentMapper.xml | 18 + .../mapper/activity/WxUserMapper.xml | 66 + .../mapper/system/PermissionMapper.xml | 90 + .../resources/mapper/system/RoleMapper.xml | 58 + .../mapper/system/RolePermissionMapper.xml | 18 + .../resources/mapper/system/SysLogMapper.xml | 43 + .../resources/mapper/system/UserMapper.xml | 85 + .../mapper/system/UserRoleMapper.xml | 18 + .../resources/static/css/cases/content.css | 13 + src/main/resources/static/css/common.css | 441 + src/main/resources/static/css/login.css | 409 + src/main/resources/static/favicon.ico | Bin 0 -> 16958 bytes src/main/resources/static/img/person.jpg | Bin 0 -> 20649 bytes src/main/resources/static/js/cases/content.js | 292 + src/main/resources/static/js/core.js | 402 + src/main/resources/static/js/init.js | 201 + .../static/libs/adminlte/css/AdminLTE.min.css | 7 + .../libs/adminlte/css/all-skins.min.bak.css | 1 + .../libs/adminlte/css/all-skins.min.css | 1 + .../static/libs/adminlte/img/boxed-bg.jpg | Bin 0 -> 123770 bytes .../static/libs/adminlte/img/boxed-bg.png | Bin 0 -> 43677 bytes .../libs/adminlte/img/default-50x50.gif | Bin 0 -> 184 bytes .../libs/adminlte/img/user3-128x128.jpg | Bin 0 -> 3511 bytes .../resources/static/libs/adminlte/js/app.js | 779 ++ .../static/libs/adminlte/js/settings-skins.js | 315 + .../css/bootstrap-datetimepicker.css | 418 + .../css/bootstrap-datetimepicker.min.css | 9 + .../js/bootstrap-datetimepicker.js | 1967 ++++ .../js/bootstrap-datetimepicker.min.js | 1 + .../js/locales/bootstrap-datetimepicker.es.js | 16 + .../locales/bootstrap-datetimepicker.zh-CN.js | 16 + .../locales/bootstrap-datetimepicker.zh-TW.js | 16 + .../css/bootstrap-table.min.css | 1 + .../js/bootstrap-table-zh-CN.min.js | 7 + .../bootstrap-table/js/bootstrap-table.min.js | 9 + .../static/libs/bootstrap/bootstrap-select.js | 3180 ++++++ .../libs/bootstrap/bootstrap-select.min.css | 6 + .../bootstrap/bootstrap-select.min.css.map | 1 + .../static/libs/bootstrap/bootstrap.min.css | 6 + .../libs/bootstrap/bootstrap.min.css.map | 1 + .../static/libs/bootstrap/bootstrap.min.js | 7 + .../libs/font-awesome/font-awesome.min.css | 4 + .../static/libs/fonts/FontAwesome.otf | Bin 0 -> 134808 bytes .../static/libs/fonts/fontawesome-webfont.eot | Bin 0 -> 165742 bytes .../static/libs/fonts/fontawesome-webfont.svg | 2671 +++++ .../static/libs/fonts/fontawesome-webfont.ttf | Bin 0 -> 165548 bytes .../libs/fonts/fontawesome-webfont.woff | Bin 0 -> 98024 bytes .../libs/fonts/fontawesome-webfont.woff2 | Bin 0 -> 77160 bytes .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes src/main/resources/static/libs/iCheck/all.css | 61 + .../static/libs/iCheck/flat/_all.css | 560 + .../static/libs/iCheck/flat/aero.css | 56 + .../static/libs/iCheck/flat/aero.png | Bin 0 -> 1520 bytes .../static/libs/iCheck/flat/aero@2x.png | Bin 0 -> 3218 bytes .../static/libs/iCheck/flat/blue.css | 56 + .../static/libs/iCheck/flat/blue.png | Bin 0 -> 1518 bytes .../static/libs/iCheck/flat/blue@2x.png | Bin 0 -> 3217 bytes .../static/libs/iCheck/flat/flat.css | 56 + .../static/libs/iCheck/flat/flat.png | Bin 0 -> 1515 bytes .../static/libs/iCheck/flat/flat@2x.png | Bin 0 -> 3217 bytes .../static/libs/iCheck/flat/green.css | 56 + .../static/libs/iCheck/flat/green.png | Bin 0 -> 1444 bytes .../static/libs/iCheck/flat/green@2x.png | Bin 0 -> 3117 bytes .../static/libs/iCheck/flat/grey.css | 56 + .../static/libs/iCheck/flat/grey.png | Bin 0 -> 1516 bytes .../static/libs/iCheck/flat/grey@2x.png | Bin 0 -> 3217 bytes .../static/libs/iCheck/flat/orange.css | 56 + .../static/libs/iCheck/flat/orange.png | Bin 0 -> 1518 bytes .../static/libs/iCheck/flat/orange@2x.png | Bin 0 -> 3275 bytes .../static/libs/iCheck/flat/pink.css | 56 + .../static/libs/iCheck/flat/pink.png | Bin 0 -> 1522 bytes .../static/libs/iCheck/flat/pink@2x.png | Bin 0 -> 3218 bytes .../static/libs/iCheck/flat/purple.css | 56 + .../static/libs/iCheck/flat/purple.png | Bin 0 -> 1519 bytes .../static/libs/iCheck/flat/purple@2x.png | Bin 0 -> 3218 bytes .../resources/static/libs/iCheck/flat/red.css | 56 + .../resources/static/libs/iCheck/flat/red.png | Bin 0 -> 1516 bytes .../static/libs/iCheck/flat/red@2x.png | Bin 0 -> 3276 bytes .../static/libs/iCheck/flat/yellow.css | 56 + .../static/libs/iCheck/flat/yellow.png | Bin 0 -> 1516 bytes .../static/libs/iCheck/flat/yellow@2x.png | Bin 0 -> 3216 bytes .../static/libs/iCheck/futurico/futurico.css | 56 + .../static/libs/iCheck/futurico/futurico.png | Bin 0 -> 1734 bytes .../libs/iCheck/futurico/futurico@2x.png | Bin 0 -> 3446 bytes .../resources/static/libs/iCheck/icheck.js | 478 + .../static/libs/iCheck/icheck.min.js | 10 + .../static/libs/iCheck/line/_all.css | 740 ++ .../static/libs/iCheck/line/aero.css | 74 + .../static/libs/iCheck/line/blue.css | 74 + .../static/libs/iCheck/line/green.css | 74 + .../static/libs/iCheck/line/grey.css | 74 + .../static/libs/iCheck/line/line.css | 74 + .../static/libs/iCheck/line/line.png | Bin 0 -> 588 bytes .../static/libs/iCheck/line/line@2x.png | Bin 0 -> 1073 bytes .../static/libs/iCheck/line/orange.css | 74 + .../static/libs/iCheck/line/pink.css | 74 + .../static/libs/iCheck/line/purple.css | 74 + .../resources/static/libs/iCheck/line/red.css | 74 + .../static/libs/iCheck/line/yellow.css | 74 + .../static/libs/iCheck/minimal/_all.css | 557 + .../static/libs/iCheck/minimal/aero.css | 62 + .../static/libs/iCheck/minimal/aero.png | Bin 0 -> 1151 bytes .../static/libs/iCheck/minimal/aero@2x.png | Bin 0 -> 1409 bytes .../static/libs/iCheck/minimal/blue.css | 62 + .../static/libs/iCheck/minimal/blue.png | Bin 0 -> 1132 bytes .../static/libs/iCheck/minimal/blue@2x.png | Bin 0 -> 1410 bytes .../static/libs/iCheck/minimal/green.css | 62 + .../static/libs/iCheck/minimal/green.png | Bin 0 -> 1143 bytes .../static/libs/iCheck/minimal/green@2x.png | Bin 0 -> 1408 bytes .../static/libs/iCheck/minimal/grey.css | 62 + .../static/libs/iCheck/minimal/grey.png | Bin 0 -> 1142 bytes .../static/libs/iCheck/minimal/grey@2x.png | Bin 0 -> 1407 bytes .../static/libs/iCheck/minimal/minimal.css | 62 + .../static/libs/iCheck/minimal/minimal.png | Bin 0 -> 1114 bytes .../static/libs/iCheck/minimal/minimal@2x.png | Bin 0 -> 1410 bytes .../static/libs/iCheck/minimal/orange.css | 62 + .../static/libs/iCheck/minimal/orange.png | Bin 0 -> 1139 bytes .../static/libs/iCheck/minimal/orange@2x.png | Bin 0 -> 1407 bytes .../static/libs/iCheck/minimal/pink.css | 62 + .../static/libs/iCheck/minimal/pink.png | Bin 0 -> 1150 bytes .../static/libs/iCheck/minimal/pink@2x.png | Bin 0 -> 1409 bytes .../static/libs/iCheck/minimal/purple.css | 62 + .../static/libs/iCheck/minimal/purple.png | Bin 0 -> 1132 bytes .../static/libs/iCheck/minimal/purple@2x.png | Bin 0 -> 1409 bytes .../static/libs/iCheck/minimal/red.css | 62 + .../static/libs/iCheck/minimal/red.png | Bin 0 -> 1130 bytes .../static/libs/iCheck/minimal/red@2x.png | Bin 0 -> 1410 bytes .../static/libs/iCheck/minimal/yellow.css | 62 + .../static/libs/iCheck/minimal/yellow.png | Bin 0 -> 1135 bytes .../static/libs/iCheck/minimal/yellow@2x.png | Bin 0 -> 1406 bytes .../static/libs/iCheck/polaris/polaris.css | 62 + .../static/libs/iCheck/polaris/polaris.png | Bin 0 -> 6401 bytes .../static/libs/iCheck/polaris/polaris@2x.png | Bin 0 -> 16760 bytes .../static/libs/iCheck/square/_all.css | 620 ++ .../static/libs/iCheck/square/aero.css | 62 + .../static/libs/iCheck/square/aero.png | Bin 0 -> 2167 bytes .../static/libs/iCheck/square/aero@2x.png | Bin 0 -> 4455 bytes .../static/libs/iCheck/square/blue.css | 62 + .../static/libs/iCheck/square/blue.png | Bin 0 -> 2185 bytes .../static/libs/iCheck/square/blue@2x.png | Bin 0 -> 4485 bytes .../static/libs/iCheck/square/green.css | 62 + .../static/libs/iCheck/square/green.png | Bin 0 -> 2193 bytes .../static/libs/iCheck/square/green@2x.png | Bin 0 -> 4498 bytes .../static/libs/iCheck/square/grey.css | 62 + .../static/libs/iCheck/square/grey.png | Bin 0 -> 2186 bytes .../static/libs/iCheck/square/grey@2x.png | Bin 0 -> 4483 bytes .../static/libs/iCheck/square/orange.css | 62 + .../static/libs/iCheck/square/orange.png | Bin 0 -> 2181 bytes .../static/libs/iCheck/square/orange@2x.png | Bin 0 -> 4474 bytes .../static/libs/iCheck/square/pink.css | 62 + .../static/libs/iCheck/square/pink.png | Bin 0 -> 2189 bytes .../static/libs/iCheck/square/pink@2x.png | Bin 0 -> 4479 bytes .../static/libs/iCheck/square/purple.css | 62 + .../static/libs/iCheck/square/purple.png | Bin 0 -> 2188 bytes .../static/libs/iCheck/square/purple@2x.png | Bin 0 -> 4501 bytes .../static/libs/iCheck/square/red.css | 62 + .../static/libs/iCheck/square/red.png | Bin 0 -> 2190 bytes .../static/libs/iCheck/square/red@2x.png | Bin 0 -> 4490 bytes .../static/libs/iCheck/square/square.css | 62 + .../static/libs/iCheck/square/square.png | Bin 0 -> 2175 bytes .../static/libs/iCheck/square/square@2x.png | Bin 0 -> 4478 bytes .../static/libs/iCheck/square/yellow.css | 62 + .../static/libs/iCheck/square/yellow.png | Bin 0 -> 2131 bytes .../static/libs/iCheck/square/yellow@2x.png | Bin 0 -> 4385 bytes .../libs/jquery.form/jquery.form.min.js | 23 + .../libs/jquery.form/jquery.form.min.js.map | 1 + .../static/libs/jquery/fastclick.min.js | 1 + .../static/libs/jquery/jquery.min.js | 4 + .../libs/jquery/jquery.slimscroll.min.js | 1 + .../static/libs/kindeditor/jsp/README.txt | 15 + .../static/libs/kindeditor/jsp/demo.jsp | 56 + .../libs/kindeditor/jsp/file_manager_json.jsp | 155 + .../libs/kindeditor/jsp/upload_json.jsp | 122 + .../kindeditor/kindeditor-all-custom-min.js | 1 + .../kindeditor/kindeditor-all-custom-src.js | 9885 +++++++++++++++++ .../libs/kindeditor/kindeditor-all-min.js | 7 + .../static/libs/kindeditor/kindeditor-all.js | 9849 ++++++++++++++++ .../static/libs/kindeditor/lang/ar.js | 242 + .../static/libs/kindeditor/lang/en.js | 241 + .../static/libs/kindeditor/lang/ko.js | 246 + .../static/libs/kindeditor/lang/ru.js | 242 + .../static/libs/kindeditor/lang/zh-CN.js | 239 + .../static/libs/kindeditor/lang/zh-TW.js | 243 + .../libs/kindeditor/plugins/anchor/anchor.js | 46 + .../plugins/autoheight/autoheight.js | 54 + .../kindeditor/plugins/baidumap/baidumap.js | 93 + .../kindeditor/plugins/baidumap/index.html | 83 + .../libs/kindeditor/plugins/baidumap/map.html | 43 + .../kindeditor/plugins/clearhtml/clearhtml.js | 29 + .../libs/kindeditor/plugins/code/code.js | 62 + .../libs/kindeditor/plugins/code/prettify.css | 13 + .../libs/kindeditor/plugins/code/prettify.js | 28 + .../kindeditor/plugins/emoticons/emoticons.js | 129 + .../kindeditor/plugins/emoticons/images/0.gif | Bin 0 -> 1810 bytes .../kindeditor/plugins/emoticons/images/1.gif | Bin 0 -> 1582 bytes .../plugins/emoticons/images/10.gif | Bin 0 -> 3716 bytes .../plugins/emoticons/images/100.gif | Bin 0 -> 1780 bytes .../plugins/emoticons/images/101.gif | Bin 0 -> 2443 bytes .../plugins/emoticons/images/102.gif | Bin 0 -> 1446 bytes .../plugins/emoticons/images/103.gif | Bin 0 -> 2166 bytes .../plugins/emoticons/images/104.gif | Bin 0 -> 2169 bytes .../plugins/emoticons/images/105.gif | Bin 0 -> 1277 bytes .../plugins/emoticons/images/106.gif | Bin 0 -> 1041 bytes .../plugins/emoticons/images/107.gif | Bin 0 -> 1058 bytes .../plugins/emoticons/images/108.gif | Bin 0 -> 1046 bytes .../plugins/emoticons/images/109.gif | Bin 0 -> 1081 bytes .../plugins/emoticons/images/11.gif | Bin 0 -> 8033 bytes .../plugins/emoticons/images/110.gif | Bin 0 -> 1082 bytes .../plugins/emoticons/images/111.gif | Bin 0 -> 1039 bytes .../plugins/emoticons/images/112.gif | Bin 0 -> 1111 bytes .../plugins/emoticons/images/113.gif | Bin 0 -> 1015 bytes .../plugins/emoticons/images/114.gif | Bin 0 -> 1003 bytes .../plugins/emoticons/images/115.gif | Bin 0 -> 1061 bytes .../plugins/emoticons/images/116.gif | Bin 0 -> 996 bytes .../plugins/emoticons/images/117.gif | Bin 0 -> 1041 bytes .../plugins/emoticons/images/118.gif | Bin 0 -> 1012 bytes .../plugins/emoticons/images/119.gif | Bin 0 -> 1101 bytes .../plugins/emoticons/images/12.gif | Bin 0 -> 2247 bytes .../plugins/emoticons/images/120.gif | Bin 0 -> 1008 bytes .../plugins/emoticons/images/121.gif | Bin 0 -> 1060 bytes .../plugins/emoticons/images/122.gif | Bin 0 -> 999 bytes .../plugins/emoticons/images/123.gif | Bin 0 -> 1055 bytes .../plugins/emoticons/images/124.gif | Bin 0 -> 1022 bytes .../plugins/emoticons/images/125.gif | Bin 0 -> 1013 bytes .../plugins/emoticons/images/126.gif | Bin 0 -> 1030 bytes .../plugins/emoticons/images/127.gif | Bin 0 -> 956 bytes .../plugins/emoticons/images/128.gif | Bin 0 -> 1022 bytes .../plugins/emoticons/images/129.gif | Bin 0 -> 972 bytes .../plugins/emoticons/images/13.gif | Bin 0 -> 1736 bytes .../plugins/emoticons/images/130.gif | Bin 0 -> 980 bytes .../plugins/emoticons/images/131.gif | Bin 0 -> 945 bytes .../plugins/emoticons/images/132.gif | Bin 0 -> 936 bytes .../plugins/emoticons/images/133.gif | Bin 0 -> 1012 bytes .../plugins/emoticons/images/134.gif | Bin 0 -> 968 bytes .../plugins/emoticons/images/14.gif | Bin 0 -> 4006 bytes .../plugins/emoticons/images/15.gif | Bin 0 -> 1562 bytes .../plugins/emoticons/images/16.gif | Bin 0 -> 1413 bytes .../plugins/emoticons/images/17.gif | Bin 0 -> 3366 bytes .../plugins/emoticons/images/18.gif | Bin 0 -> 8137 bytes .../plugins/emoticons/images/19.gif | Bin 0 -> 8135 bytes .../kindeditor/plugins/emoticons/images/2.gif | Bin 0 -> 1804 bytes .../plugins/emoticons/images/20.gif | Bin 0 -> 1808 bytes .../plugins/emoticons/images/21.gif | Bin 0 -> 1864 bytes .../plugins/emoticons/images/22.gif | Bin 0 -> 3010 bytes .../plugins/emoticons/images/23.gif | Bin 0 -> 1950 bytes .../plugins/emoticons/images/24.gif | Bin 0 -> 2257 bytes .../plugins/emoticons/images/25.gif | Bin 0 -> 2446 bytes .../plugins/emoticons/images/26.gif | Bin 0 -> 4014 bytes .../plugins/emoticons/images/27.gif | Bin 0 -> 2893 bytes .../plugins/emoticons/images/28.gif | Bin 0 -> 3262 bytes .../plugins/emoticons/images/29.gif | Bin 0 -> 5861 bytes .../kindeditor/plugins/emoticons/images/3.gif | Bin 0 -> 1852 bytes .../plugins/emoticons/images/30.gif | Bin 0 -> 1780 bytes .../plugins/emoticons/images/31.gif | Bin 0 -> 5174 bytes .../plugins/emoticons/images/32.gif | Bin 0 -> 7189 bytes .../plugins/emoticons/images/33.gif | Bin 0 -> 4317 bytes .../plugins/emoticons/images/34.gif | Bin 0 -> 2140 bytes .../plugins/emoticons/images/35.gif | Bin 0 -> 13392 bytes .../plugins/emoticons/images/36.gif | Bin 0 -> 1417 bytes .../plugins/emoticons/images/37.gif | Bin 0 -> 1195 bytes .../plugins/emoticons/images/38.gif | Bin 0 -> 1674 bytes .../plugins/emoticons/images/39.gif | Bin 0 -> 1798 bytes .../kindeditor/plugins/emoticons/images/4.gif | Bin 0 -> 1977 bytes .../plugins/emoticons/images/40.gif | Bin 0 -> 10092 bytes .../plugins/emoticons/images/41.gif | Bin 0 -> 3368 bytes .../plugins/emoticons/images/42.gif | Bin 0 -> 13367 bytes .../plugins/emoticons/images/43.gif | Bin 0 -> 4327 bytes .../plugins/emoticons/images/44.gif | Bin 0 -> 1571 bytes .../plugins/emoticons/images/45.gif | Bin 0 -> 4692 bytes .../plugins/emoticons/images/46.gif | Bin 0 -> 5162 bytes .../plugins/emoticons/images/47.gif | Bin 0 -> 3685 bytes .../plugins/emoticons/images/48.gif | Bin 0 -> 1755 bytes .../plugins/emoticons/images/49.gif | Bin 0 -> 6361 bytes .../kindeditor/plugins/emoticons/images/5.gif | Bin 0 -> 1866 bytes .../plugins/emoticons/images/50.gif | Bin 0 -> 3073 bytes .../plugins/emoticons/images/51.gif | Bin 0 -> 3731 bytes .../plugins/emoticons/images/52.gif | Bin 0 -> 1532 bytes .../plugins/emoticons/images/53.gif | Bin 0 -> 2056 bytes .../plugins/emoticons/images/54.gif | Bin 0 -> 2362 bytes .../plugins/emoticons/images/55.gif | Bin 0 -> 1582 bytes .../plugins/emoticons/images/56.gif | Bin 0 -> 1170 bytes .../plugins/emoticons/images/57.gif | Bin 0 -> 5072 bytes .../plugins/emoticons/images/58.gif | Bin 0 -> 2596 bytes .../plugins/emoticons/images/59.gif | Bin 0 -> 1533 bytes .../kindeditor/plugins/emoticons/images/6.gif | Bin 0 -> 3556 bytes .../plugins/emoticons/images/60.gif | Bin 0 -> 2667 bytes .../plugins/emoticons/images/61.gif | Bin 0 -> 1136 bytes .../plugins/emoticons/images/62.gif | Bin 0 -> 1269 bytes .../plugins/emoticons/images/63.gif | Bin 0 -> 971 bytes .../plugins/emoticons/images/64.gif | Bin 0 -> 988 bytes .../plugins/emoticons/images/65.gif | Bin 0 -> 5285 bytes .../plugins/emoticons/images/66.gif | Bin 0 -> 1159 bytes .../plugins/emoticons/images/67.gif | Bin 0 -> 2746 bytes .../plugins/emoticons/images/68.gif | Bin 0 -> 4148 bytes .../plugins/emoticons/images/69.gif | Bin 0 -> 1015 bytes .../kindeditor/plugins/emoticons/images/7.gif | Bin 0 -> 3929 bytes .../plugins/emoticons/images/70.gif | Bin 0 -> 1162 bytes .../plugins/emoticons/images/71.gif | Bin 0 -> 824 bytes .../plugins/emoticons/images/72.gif | Bin 0 -> 3679 bytes .../plugins/emoticons/images/73.gif | Bin 0 -> 2195 bytes .../plugins/emoticons/images/74.gif | Bin 0 -> 2454 bytes .../plugins/emoticons/images/75.gif | Bin 0 -> 1222 bytes .../plugins/emoticons/images/76.gif | Bin 0 -> 1211 bytes .../plugins/emoticons/images/77.gif | Bin 0 -> 1151 bytes .../plugins/emoticons/images/78.gif | Bin 0 -> 1565 bytes .../plugins/emoticons/images/79.gif | Bin 0 -> 1518 bytes .../kindeditor/plugins/emoticons/images/8.gif | Bin 0 -> 4679 bytes .../plugins/emoticons/images/80.gif | Bin 0 -> 1537 bytes .../plugins/emoticons/images/81.gif | Bin 0 -> 1591 bytes .../plugins/emoticons/images/82.gif | Bin 0 -> 1547 bytes .../plugins/emoticons/images/83.gif | Bin 0 -> 1591 bytes .../plugins/emoticons/images/84.gif | Bin 0 -> 3424 bytes .../plugins/emoticons/images/85.gif | Bin 0 -> 1581 bytes .../plugins/emoticons/images/86.gif | Bin 0 -> 1519 bytes .../plugins/emoticons/images/87.gif | Bin 0 -> 1558 bytes .../plugins/emoticons/images/88.gif | Bin 0 -> 2134 bytes .../plugins/emoticons/images/89.gif | Bin 0 -> 1219 bytes .../kindeditor/plugins/emoticons/images/9.gif | Bin 0 -> 3298 bytes .../plugins/emoticons/images/90.gif | Bin 0 -> 2743 bytes .../plugins/emoticons/images/91.gif | Bin 0 -> 654 bytes .../plugins/emoticons/images/92.gif | Bin 0 -> 1377 bytes .../plugins/emoticons/images/93.gif | Bin 0 -> 1119 bytes .../plugins/emoticons/images/94.gif | Bin 0 -> 3426 bytes .../plugins/emoticons/images/95.gif | Bin 0 -> 3011 bytes .../plugins/emoticons/images/96.gif | Bin 0 -> 1796 bytes .../plugins/emoticons/images/97.gif | Bin 0 -> 5300 bytes .../plugins/emoticons/images/98.gif | Bin 0 -> 1629 bytes .../plugins/emoticons/images/99.gif | Bin 0 -> 2261 bytes .../plugins/emoticons/images/static.gif | Bin 0 -> 35504 bytes .../plugins/filemanager/filemanager.js | 189 + .../plugins/filemanager/images/file-16.gif | Bin 0 -> 170 bytes .../plugins/filemanager/images/file-64.gif | Bin 0 -> 1149 bytes .../plugins/filemanager/images/folder-16.gif | Bin 0 -> 226 bytes .../plugins/filemanager/images/folder-64.gif | Bin 0 -> 1272 bytes .../plugins/filemanager/images/go-up.gif | Bin 0 -> 562 bytes .../plugins/fixtoolbar/fixtoolbar.js | 35 + .../libs/kindeditor/plugins/flash/flash.js | 161 + .../libs/kindeditor/plugins/image/image.js | 328 + .../plugins/image/images/align_left.gif | Bin 0 -> 639 bytes .../plugins/image/images/align_right.gif | Bin 0 -> 636 bytes .../plugins/image/images/align_top.gif | Bin 0 -> 625 bytes .../plugins/image/images/refresh.png | Bin 0 -> 800 bytes .../plugins/insertfile/insertfile.js | 138 + .../plugins/lineheight/lineheight.js | 38 + .../libs/kindeditor/plugins/link/link.js | 66 + .../libs/kindeditor/plugins/map/map.html | 57 + .../static/libs/kindeditor/plugins/map/map.js | 137 + .../libs/kindeditor/plugins/media/media.js | 170 + .../plugins/multiimage/images/image.png | Bin 0 -> 1862 bytes .../multiimage/images/select-files-en.png | Bin 0 -> 484 bytes .../multiimage/images/select-files-zh-CN.png | Bin 0 -> 481 bytes .../plugins/multiimage/images/swfupload.swf | Bin 0 -> 12787 bytes .../plugins/multiimage/multiimage.js | 1384 +++ .../kindeditor/plugins/pagebreak/pagebreak.js | 27 + .../plugins/plainpaste/plainpaste.js | 41 + .../kindeditor/plugins/preview/preview.js | 31 + .../plugins/quickformat/quickformat.js | 81 + .../libs/kindeditor/plugins/table/table.js | 712 ++ .../kindeditor/plugins/template/html/1.html | 14 + .../kindeditor/plugins/template/html/2.html | 42 + .../kindeditor/plugins/template/html/3.html | 36 + .../kindeditor/plugins/template/template.js | 58 + .../kindeditor/plugins/wordpaste/wordpaste.js | 51 + .../libs/kindeditor/themes/common/anchor.gif | Bin 0 -> 371 bytes .../libs/kindeditor/themes/common/blank.gif | Bin 0 -> 43 bytes .../libs/kindeditor/themes/common/flash.gif | Bin 0 -> 1089 bytes .../libs/kindeditor/themes/common/loading.gif | Bin 0 -> 2608 bytes .../libs/kindeditor/themes/common/media.gif | Bin 0 -> 1036 bytes .../libs/kindeditor/themes/common/rm.gif | Bin 0 -> 989 bytes .../kindeditor/themes/default/background.png | Bin 0 -> 1410 bytes .../kindeditor/themes/default/default.css | 1147 ++ .../kindeditor/themes/default/default.png | Bin 0 -> 8299 bytes .../libs/kindeditor/themes/qq/editor.gif | Bin 0 -> 1449 bytes .../static/libs/kindeditor/themes/qq/qq.css | 143 + .../libs/kindeditor/themes/simple/simple.css | 100 + src/main/resources/static/libs/layer/layer.js | 2 + .../static/libs/layer/mobile/layer.js | 2 + .../static/libs/layer/mobile/need/layer.css | 1 + .../libs/layer/skin/default/icon-ext.png | Bin 0 -> 5911 bytes .../static/libs/layer/skin/default/icon.png | Bin 0 -> 11493 bytes .../static/libs/layer/skin/default/layer.css | 1 + .../libs/layer/skin/default/loading-0.gif | Bin 0 -> 5793 bytes .../libs/layer/skin/default/loading-1.gif | Bin 0 -> 701 bytes .../libs/layer/skin/default/loading-2.gif | Bin 0 -> 1787 bytes src/main/resources/static/libs/pace/pace.css | 85 + src/main/resources/static/libs/pace/pace.js | 936 ++ .../static/libs/treegrid/jquery.treegrid.css | 17 + .../treegrid/jquery.treegrid.extension.js | 270 + .../libs/treegrid/jquery.treegrid.min.js | 2 + .../static/libs/treegrid/tree.table.js | 142 + .../resources/static/libs/upload-img/1.png | Bin 0 -> 7081 bytes .../static/libs/upload-img/index.html | 30 + .../static/libs/upload-img/jQuery.upload.js | 397 + .../libs/upload-img/jQuery.upload.min.js | 4 + .../static/libs/upload-img/upload.css | 1 + .../static/libs/upload/css/webuploader.css | 508 + .../static/libs/upload/js/jax.uploader.js | 305 + .../static/libs/upload/js/uploadCall.js | 144 + .../static/libs/upload/js/webuploader.js | 8106 ++++++++++++++ .../static/libs/validate/validate.css | 175 + .../static/libs/validate/validate.js | 530 + .../static/libs/ztree/css/ztree-bootstrap.css | 96 + .../static/libs/ztree/img/bootstrap.gif | Bin 0 -> 2940 bytes .../static/libs/ztree/img/bootstrap.png | Bin 0 -> 3522 bytes .../static/libs/ztree/img/line_conn.png | Bin 0 -> 933 bytes .../static/libs/ztree/img/loading.gif | Bin 0 -> 381 bytes .../static/libs/ztree/js/jquery.ztree.all.js | 3746 +++++++ .../libs/ztree/js/jquery.ztree.all.min.js | 165 + .../static/libs/ztree/js/jquery.ztree.core.js | 1925 ++++ .../libs/ztree/js/jquery.ztree.core.min.js | 74 + .../libs/ztree/js/jquery.ztree.excheck.js | 628 ++ .../libs/ztree/js/jquery.ztree.excheck.min.js | 34 + .../libs/ztree/js/jquery.ztree.exedit.js | 1192 ++ .../libs/ztree/js/jquery.ztree.exedit.min.js | 53 + .../libs/ztree/js/jquery.ztree.exhide.js | 367 + .../libs/ztree/js/jquery.ztree.exhide.min.js | 22 + .../static/libs/ztree/metro/bootstrap.gif | Bin 0 -> 4679 bytes .../static/libs/ztree/metro/bootstrap.png | Bin 0 -> 5283 bytes .../static/libs/ztree/metro/line_conn.png | Bin 0 -> 933 bytes .../static/libs/ztree/metro/loading.gif | Bin 0 -> 381 bytes .../templates/content-editor/edit.html | 336 + .../templates/content-editor/list.html | 201 + .../templates/content/application.html | 453 + .../resources/templates/content/edit.html | 394 + .../resources/templates/content/edit2.html | 523 + .../resources/templates/content/list.html | 203 + .../resources/templates/content/list2.html | 486 + .../resources/templates/data_dict/edit.html | 201 + .../resources/templates/data_dict/list.html | 155 + .../templates/database/monitoring.html | 21 + src/main/resources/templates/demo.html | 39 + src/main/resources/templates/error/403.html | 15 + src/main/resources/templates/error/404.html | 17 + src/main/resources/templates/error/4xx.html | 11 + src/main/resources/templates/error/500.html | 16 + src/main/resources/templates/error/5xx.html | 11 + .../resources/templates/fragments/footer.html | 11 + .../resources/templates/fragments/navbar.html | 182 + .../templates/fragments/sidebar.html | 7 + .../templates/home/fragments/footer.html | 71 + .../templates/home/fragments/top.html | 13 + src/main/resources/templates/index/index.html | 69 + .../resources/templates/index/workdest.html | 43 + .../resources/templates/onlineUsers/list.html | 131 + .../templates/permission/detail.html | 98 + .../resources/templates/permission/list.html | 395 + src/main/resources/templates/role/detail.html | 43 + src/main/resources/templates/role/list.html | 285 + src/main/resources/templates/syslog/list.html | 115 + .../resources/templates/system/kickout.html | 24 + .../resources/templates/system/login.html | 126 + .../resources/templates/system/register.html | 122 + src/main/resources/templates/ui/icons.html | 2431 ++++ src/main/resources/templates/user/list.html | 462 + .../resources/templates/user/userDetail.html | 65 + src/main/webapp/WEB-INF/loginPage.jsp | 24 + src/main/webapp/WEB-INF/web.xml | 8 + src/main/webapp/WEB-INF/weixinLogin.jsp | 31 + 619 files changed, 90270 insertions(+) create mode 100644 .gitignore create mode 100644 docs/database/cases.sql create mode 100644 docs/database/system.sql create mode 100644 docs/document/公司案例库网站构建逻辑.docx create mode 100644 pom.xml create mode 100644 src/main/java/com/nbclass/CasesBootApplication.java create mode 100644 src/main/java/com/nbclass/Interceptor/InterceptorConfig.java create mode 100644 src/main/java/com/nbclass/Interceptor/JWTInterceptor.java create mode 100644 src/main/java/com/nbclass/activity/constant/Const.java create mode 100644 src/main/java/com/nbclass/activity/controller/ContentController.java create mode 100644 src/main/java/com/nbclass/activity/controller/DataDictController.java create mode 100644 src/main/java/com/nbclass/activity/controller/OssUploadController.java create mode 100644 src/main/java/com/nbclass/activity/controller/SysLogController.java create mode 100644 src/main/java/com/nbclass/activity/controller/casesfirst/CaseTypeController.java create mode 100644 src/main/java/com/nbclass/activity/controller/casesfirst/CommentController.java create mode 100644 src/main/java/com/nbclass/activity/controller/casesfirst/WxController.java create mode 100644 src/main/java/com/nbclass/activity/controller/frontapi/FrontContentController.java create mode 100644 src/main/java/com/nbclass/activity/controller/frontapi/FrontContentLikeNumController.java create mode 100644 src/main/java/com/nbclass/activity/controller/frontapi/FrontDataDictController.java create mode 100644 src/main/java/com/nbclass/activity/controller/keditor/KindEditorController.java create mode 100644 src/main/java/com/nbclass/activity/controller/keditor/KindEditorUploadToCDNController.java create mode 100644 src/main/java/com/nbclass/activity/controller/keditor/KindEditorUploadToOSSController.java create mode 100644 src/main/java/com/nbclass/activity/mapper/CaseTypeMapper.java create mode 100644 src/main/java/com/nbclass/activity/mapper/CommentMapper.java create mode 100644 src/main/java/com/nbclass/activity/mapper/ContentMapper.java create mode 100644 src/main/java/com/nbclass/activity/mapper/DataDictItemMapper.java create mode 100644 src/main/java/com/nbclass/activity/mapper/DataDictMapper.java create mode 100644 src/main/java/com/nbclass/activity/mapper/WXMapper.java create mode 100644 src/main/java/com/nbclass/activity/mapper/WxDepartmentMapper.java create mode 100644 src/main/java/com/nbclass/activity/mapper/WxUserMapper.java create mode 100644 src/main/java/com/nbclass/activity/model/Application.java create mode 100644 src/main/java/com/nbclass/activity/model/Check.java create mode 100644 src/main/java/com/nbclass/activity/model/Comment.java create mode 100644 src/main/java/com/nbclass/activity/model/Content.java create mode 100644 src/main/java/com/nbclass/activity/model/ContentImages.java create mode 100644 src/main/java/com/nbclass/activity/model/ContentTags.java create mode 100644 src/main/java/com/nbclass/activity/model/DataDict.java create mode 100644 src/main/java/com/nbclass/activity/model/DataDictItem.java create mode 100644 src/main/java/com/nbclass/activity/model/FavoritesFolder.java create mode 100644 src/main/java/com/nbclass/activity/model/GroupChat.java create mode 100644 src/main/java/com/nbclass/activity/model/GroupChatList.java create mode 100644 src/main/java/com/nbclass/activity/model/LabelLogger.java create mode 100644 src/main/java/com/nbclass/activity/model/MemberList.java create mode 100644 src/main/java/com/nbclass/activity/model/Praise.java create mode 100644 src/main/java/com/nbclass/activity/model/QueryCriteria.java create mode 100644 src/main/java/com/nbclass/activity/model/Report.java create mode 100644 src/main/java/com/nbclass/activity/model/Scoring.java create mode 100644 src/main/java/com/nbclass/activity/model/WeiXiUser.java create mode 100644 src/main/java/com/nbclass/activity/model/WxDepartment.java create mode 100644 src/main/java/com/nbclass/activity/model/WxUser.java create mode 100644 src/main/java/com/nbclass/activity/model/enums/ContentType.java create mode 100644 src/main/java/com/nbclass/activity/service/CaseTypeService.java create mode 100644 src/main/java/com/nbclass/activity/service/CommentService.java create mode 100644 src/main/java/com/nbclass/activity/service/ContentService.java create mode 100644 src/main/java/com/nbclass/activity/service/DataDictService.java create mode 100644 src/main/java/com/nbclass/activity/service/ElasticSearchService.java create mode 100644 src/main/java/com/nbclass/activity/service/WxService.java create mode 100644 src/main/java/com/nbclass/activity/service/WxUserService.java create mode 100644 src/main/java/com/nbclass/activity/service/impl/CaseTypeServiceImpl.java create mode 100644 src/main/java/com/nbclass/activity/service/impl/CommentServiceImpl.java create mode 100644 src/main/java/com/nbclass/activity/service/impl/ContentServiceImpl.java create mode 100644 src/main/java/com/nbclass/activity/service/impl/DataDictServiceImpl.java create mode 100644 src/main/java/com/nbclass/activity/service/impl/ElasticSearchServiceImpl.java create mode 100644 src/main/java/com/nbclass/activity/service/impl/WxServiceImpl.java create mode 100644 src/main/java/com/nbclass/activity/service/impl/WxUserServiceImpl.java create mode 100644 src/main/java/com/nbclass/activity/task/ActivityRunner.java create mode 100644 src/main/java/com/nbclass/activity/task/MultiThreadScheduleTask.java create mode 100644 src/main/java/com/nbclass/aliyun/sdk/AliyunConstant.java create mode 100644 src/main/java/com/nbclass/aliyun/sdk/AliyunOSSUtils.java create mode 100644 src/main/java/com/nbclass/component/MyErrorAttributes.java create mode 100644 src/main/java/com/nbclass/config/CorsConfig.java create mode 100644 src/main/java/com/nbclass/config/DateConverter.java create mode 100644 src/main/java/com/nbclass/config/DefaultViewConfig.java create mode 100644 src/main/java/com/nbclass/enums/ResponseStatus.java create mode 100644 src/main/java/com/nbclass/exception/CommonExceptionAdvice.java create mode 100644 src/main/java/com/nbclass/exception/DataBaseException.java create mode 100644 src/main/java/com/nbclass/exception/LogicalException.java create mode 100644 src/main/java/com/nbclass/exception/ParameterException.java create mode 100644 src/main/java/com/nbclass/exception/ServiceException.java create mode 100644 src/main/java/com/nbclass/holder/SpringContextHolder.java create mode 100644 src/main/java/com/nbclass/shiro/MyShiroRealm.java create mode 100644 src/main/java/com/nbclass/shiro/PermsService.java create mode 100644 src/main/java/com/nbclass/shiro/ShiroService.java create mode 100644 src/main/java/com/nbclass/shiro/config/LoginType.java create mode 100644 src/main/java/com/nbclass/shiro/config/MyHashedCredentialsMatcher.java create mode 100644 src/main/java/com/nbclass/shiro/config/MyUsernamePasswordToken.java create mode 100644 src/main/java/com/nbclass/shiro/config/ShiroConfig.java create mode 100644 src/main/java/com/nbclass/shiro/filter/KickoutSessionControlFilter.java create mode 100644 src/main/java/com/nbclass/shiro/filter/MobilePageOAuthFilter.java create mode 100644 src/main/java/com/nbclass/shiro/filter/ResetPwdUrlFilter.java create mode 100644 src/main/java/com/nbclass/system/controller/BaseController.java create mode 100644 src/main/java/com/nbclass/system/controller/DatabaseController.java create mode 100644 src/main/java/com/nbclass/system/controller/ErrorController.java create mode 100644 src/main/java/com/nbclass/system/controller/OnlineUserController.java create mode 100644 src/main/java/com/nbclass/system/controller/PermissionController.java create mode 100644 src/main/java/com/nbclass/system/controller/QyWxLoginController.java create mode 100644 src/main/java/com/nbclass/system/controller/RenderController.java create mode 100644 src/main/java/com/nbclass/system/controller/RoleController.java create mode 100644 src/main/java/com/nbclass/system/controller/SystemController.java create mode 100644 src/main/java/com/nbclass/system/controller/UserController.java create mode 100644 src/main/java/com/nbclass/system/mapper/PermissionMapper.java create mode 100644 src/main/java/com/nbclass/system/mapper/RoleMapper.java create mode 100644 src/main/java/com/nbclass/system/mapper/RolePermissionMapper.java create mode 100644 src/main/java/com/nbclass/system/mapper/SysLogMapper.java create mode 100644 src/main/java/com/nbclass/system/mapper/UserMapper.java create mode 100644 src/main/java/com/nbclass/system/mapper/UserRoleMapper.java create mode 100644 src/main/java/com/nbclass/system/model/ExcelColumn.java create mode 100644 src/main/java/com/nbclass/system/model/Permission.java create mode 100644 src/main/java/com/nbclass/system/model/Role.java create mode 100644 src/main/java/com/nbclass/system/model/RolePermission.java create mode 100644 src/main/java/com/nbclass/system/model/SysLog.java create mode 100644 src/main/java/com/nbclass/system/model/User.java create mode 100644 src/main/java/com/nbclass/system/model/UserRole.java create mode 100644 src/main/java/com/nbclass/system/model/enums/SysLogType.java create mode 100644 src/main/java/com/nbclass/system/service/PermissionService.java create mode 100644 src/main/java/com/nbclass/system/service/RoleService.java create mode 100644 src/main/java/com/nbclass/system/service/SysLogService.java create mode 100644 src/main/java/com/nbclass/system/service/UserService.java create mode 100644 src/main/java/com/nbclass/system/service/impl/PermissionServiceImpl.java create mode 100644 src/main/java/com/nbclass/system/service/impl/RoleServiceImpl.java create mode 100644 src/main/java/com/nbclass/system/service/impl/SysLogServiceImpl.java create mode 100644 src/main/java/com/nbclass/system/service/impl/UserServiceImpl.java create mode 100644 src/main/java/com/nbclass/util/BCrypt.java create mode 100644 src/main/java/com/nbclass/util/CommonUtils.java create mode 100644 src/main/java/com/nbclass/util/CopyUtil.java create mode 100644 src/main/java/com/nbclass/util/CoreConst.java create mode 100644 src/main/java/com/nbclass/util/CoreUtils.java create mode 100644 src/main/java/com/nbclass/util/HttpClientUtil.java create mode 100644 src/main/java/com/nbclass/util/HttpUtil.java create mode 100644 src/main/java/com/nbclass/util/HttpUtils.java create mode 100644 src/main/java/com/nbclass/util/IpUtil.java create mode 100644 src/main/java/com/nbclass/util/JWTUtils.java create mode 100644 src/main/java/com/nbclass/util/MyMapper.java create mode 100644 src/main/java/com/nbclass/util/PageUtil.java create mode 100644 src/main/java/com/nbclass/util/PasswordHelper.java create mode 100644 src/main/java/com/nbclass/util/ResultUtil.java create mode 100644 src/main/java/com/nbclass/util/ShortenUtil.java create mode 100644 src/main/java/com/nbclass/util/UUIDUtil.java create mode 100644 src/main/java/com/nbclass/util/WebUtils.java create mode 100644 src/main/java/com/nbclass/vo/ChangePasswordVo.java create mode 100644 src/main/java/com/nbclass/vo/PermissionTreeListVo.java create mode 100644 src/main/java/com/nbclass/vo/UserOnlineVo.java create mode 100644 src/main/java/com/nbclass/vo/UserSessionVo.java create mode 100644 src/main/java/com/nbclass/vo/base/PageResultVo.java create mode 100644 src/main/java/com/nbclass/vo/base/ResponseVo.java create mode 100644 src/main/java/com/nbclass/vo/base/Result.java create mode 100644 src/main/resources/application-dev.yml create mode 100644 src/main/resources/application-prod.yml create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/generator/generatorConfig.xml create mode 100644 src/main/resources/log4j2-spring.xml create mode 100644 src/main/resources/mapper/activity/CaseTypeMapper.xml create mode 100644 src/main/resources/mapper/activity/CommentMapper.xml create mode 100644 src/main/resources/mapper/activity/ContentMapper.xml create mode 100644 src/main/resources/mapper/activity/DataDictItemMapper.xml create mode 100644 src/main/resources/mapper/activity/DataDictMapper.xml create mode 100644 src/main/resources/mapper/activity/WXMapper.xml create mode 100644 src/main/resources/mapper/activity/WxDepartmentMapper.xml create mode 100644 src/main/resources/mapper/activity/WxUserMapper.xml create mode 100644 src/main/resources/mapper/system/PermissionMapper.xml create mode 100644 src/main/resources/mapper/system/RoleMapper.xml create mode 100644 src/main/resources/mapper/system/RolePermissionMapper.xml create mode 100644 src/main/resources/mapper/system/SysLogMapper.xml create mode 100644 src/main/resources/mapper/system/UserMapper.xml create mode 100644 src/main/resources/mapper/system/UserRoleMapper.xml create mode 100644 src/main/resources/static/css/cases/content.css create mode 100644 src/main/resources/static/css/common.css create mode 100644 src/main/resources/static/css/login.css create mode 100644 src/main/resources/static/favicon.ico create mode 100644 src/main/resources/static/img/person.jpg create mode 100644 src/main/resources/static/js/cases/content.js create mode 100644 src/main/resources/static/js/core.js create mode 100644 src/main/resources/static/js/init.js create mode 100644 src/main/resources/static/libs/adminlte/css/AdminLTE.min.css create mode 100644 src/main/resources/static/libs/adminlte/css/all-skins.min.bak.css create mode 100644 src/main/resources/static/libs/adminlte/css/all-skins.min.css create mode 100644 src/main/resources/static/libs/adminlte/img/boxed-bg.jpg create mode 100644 src/main/resources/static/libs/adminlte/img/boxed-bg.png create mode 100644 src/main/resources/static/libs/adminlte/img/default-50x50.gif create mode 100644 src/main/resources/static/libs/adminlte/img/user3-128x128.jpg create mode 100644 src/main/resources/static/libs/adminlte/js/app.js create mode 100644 src/main/resources/static/libs/adminlte/js/settings-skins.js create mode 100644 src/main/resources/static/libs/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css create mode 100644 src/main/resources/static/libs/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css create mode 100644 src/main/resources/static/libs/bootstrap-datetimepicker/js/bootstrap-datetimepicker.js create mode 100644 src/main/resources/static/libs/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js create mode 100644 src/main/resources/static/libs/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.es.js create mode 100644 src/main/resources/static/libs/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js create mode 100644 src/main/resources/static/libs/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-TW.js create mode 100644 src/main/resources/static/libs/bootstrap-table/css/bootstrap-table.min.css create mode 100644 src/main/resources/static/libs/bootstrap-table/js/bootstrap-table-zh-CN.min.js create mode 100644 src/main/resources/static/libs/bootstrap-table/js/bootstrap-table.min.js create mode 100644 src/main/resources/static/libs/bootstrap/bootstrap-select.js create mode 100644 src/main/resources/static/libs/bootstrap/bootstrap-select.min.css create mode 100644 src/main/resources/static/libs/bootstrap/bootstrap-select.min.css.map create mode 100644 src/main/resources/static/libs/bootstrap/bootstrap.min.css create mode 100644 src/main/resources/static/libs/bootstrap/bootstrap.min.css.map create mode 100644 src/main/resources/static/libs/bootstrap/bootstrap.min.js create mode 100644 src/main/resources/static/libs/font-awesome/font-awesome.min.css create mode 100644 src/main/resources/static/libs/fonts/FontAwesome.otf create mode 100644 src/main/resources/static/libs/fonts/fontawesome-webfont.eot create mode 100644 src/main/resources/static/libs/fonts/fontawesome-webfont.svg create mode 100644 src/main/resources/static/libs/fonts/fontawesome-webfont.ttf create mode 100644 src/main/resources/static/libs/fonts/fontawesome-webfont.woff create mode 100644 src/main/resources/static/libs/fonts/fontawesome-webfont.woff2 create mode 100644 src/main/resources/static/libs/fonts/glyphicons-halflings-regular.eot create mode 100644 src/main/resources/static/libs/fonts/glyphicons-halflings-regular.svg create mode 100644 src/main/resources/static/libs/fonts/glyphicons-halflings-regular.ttf create mode 100644 src/main/resources/static/libs/fonts/glyphicons-halflings-regular.woff create mode 100644 src/main/resources/static/libs/fonts/glyphicons-halflings-regular.woff2 create mode 100644 src/main/resources/static/libs/iCheck/all.css create mode 100644 src/main/resources/static/libs/iCheck/flat/_all.css create mode 100644 src/main/resources/static/libs/iCheck/flat/aero.css create mode 100644 src/main/resources/static/libs/iCheck/flat/aero.png create mode 100644 src/main/resources/static/libs/iCheck/flat/aero@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/blue.css create mode 100644 src/main/resources/static/libs/iCheck/flat/blue.png create mode 100644 src/main/resources/static/libs/iCheck/flat/blue@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/flat.css create mode 100644 src/main/resources/static/libs/iCheck/flat/flat.png create mode 100644 src/main/resources/static/libs/iCheck/flat/flat@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/green.css create mode 100644 src/main/resources/static/libs/iCheck/flat/green.png create mode 100644 src/main/resources/static/libs/iCheck/flat/green@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/grey.css create mode 100644 src/main/resources/static/libs/iCheck/flat/grey.png create mode 100644 src/main/resources/static/libs/iCheck/flat/grey@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/orange.css create mode 100644 src/main/resources/static/libs/iCheck/flat/orange.png create mode 100644 src/main/resources/static/libs/iCheck/flat/orange@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/pink.css create mode 100644 src/main/resources/static/libs/iCheck/flat/pink.png create mode 100644 src/main/resources/static/libs/iCheck/flat/pink@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/purple.css create mode 100644 src/main/resources/static/libs/iCheck/flat/purple.png create mode 100644 src/main/resources/static/libs/iCheck/flat/purple@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/red.css create mode 100644 src/main/resources/static/libs/iCheck/flat/red.png create mode 100644 src/main/resources/static/libs/iCheck/flat/red@2x.png create mode 100644 src/main/resources/static/libs/iCheck/flat/yellow.css create mode 100644 src/main/resources/static/libs/iCheck/flat/yellow.png create mode 100644 src/main/resources/static/libs/iCheck/flat/yellow@2x.png create mode 100644 src/main/resources/static/libs/iCheck/futurico/futurico.css create mode 100644 src/main/resources/static/libs/iCheck/futurico/futurico.png create mode 100644 src/main/resources/static/libs/iCheck/futurico/futurico@2x.png create mode 100644 src/main/resources/static/libs/iCheck/icheck.js create mode 100644 src/main/resources/static/libs/iCheck/icheck.min.js create mode 100644 src/main/resources/static/libs/iCheck/line/_all.css create mode 100644 src/main/resources/static/libs/iCheck/line/aero.css create mode 100644 src/main/resources/static/libs/iCheck/line/blue.css create mode 100644 src/main/resources/static/libs/iCheck/line/green.css create mode 100644 src/main/resources/static/libs/iCheck/line/grey.css create mode 100644 src/main/resources/static/libs/iCheck/line/line.css create mode 100644 src/main/resources/static/libs/iCheck/line/line.png create mode 100644 src/main/resources/static/libs/iCheck/line/line@2x.png create mode 100644 src/main/resources/static/libs/iCheck/line/orange.css create mode 100644 src/main/resources/static/libs/iCheck/line/pink.css create mode 100644 src/main/resources/static/libs/iCheck/line/purple.css create mode 100644 src/main/resources/static/libs/iCheck/line/red.css create mode 100644 src/main/resources/static/libs/iCheck/line/yellow.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/_all.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/aero.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/aero.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/aero@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/blue.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/blue.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/blue@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/green.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/green.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/green@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/grey.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/grey.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/grey@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/minimal.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/minimal.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/minimal@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/orange.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/orange.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/orange@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/pink.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/pink.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/pink@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/purple.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/purple.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/purple@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/red.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/red.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/red@2x.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/yellow.css create mode 100644 src/main/resources/static/libs/iCheck/minimal/yellow.png create mode 100644 src/main/resources/static/libs/iCheck/minimal/yellow@2x.png create mode 100644 src/main/resources/static/libs/iCheck/polaris/polaris.css create mode 100644 src/main/resources/static/libs/iCheck/polaris/polaris.png create mode 100644 src/main/resources/static/libs/iCheck/polaris/polaris@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/_all.css create mode 100644 src/main/resources/static/libs/iCheck/square/aero.css create mode 100644 src/main/resources/static/libs/iCheck/square/aero.png create mode 100644 src/main/resources/static/libs/iCheck/square/aero@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/blue.css create mode 100644 src/main/resources/static/libs/iCheck/square/blue.png create mode 100644 src/main/resources/static/libs/iCheck/square/blue@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/green.css create mode 100644 src/main/resources/static/libs/iCheck/square/green.png create mode 100644 src/main/resources/static/libs/iCheck/square/green@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/grey.css create mode 100644 src/main/resources/static/libs/iCheck/square/grey.png create mode 100644 src/main/resources/static/libs/iCheck/square/grey@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/orange.css create mode 100644 src/main/resources/static/libs/iCheck/square/orange.png create mode 100644 src/main/resources/static/libs/iCheck/square/orange@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/pink.css create mode 100644 src/main/resources/static/libs/iCheck/square/pink.png create mode 100644 src/main/resources/static/libs/iCheck/square/pink@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/purple.css create mode 100644 src/main/resources/static/libs/iCheck/square/purple.png create mode 100644 src/main/resources/static/libs/iCheck/square/purple@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/red.css create mode 100644 src/main/resources/static/libs/iCheck/square/red.png create mode 100644 src/main/resources/static/libs/iCheck/square/red@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/square.css create mode 100644 src/main/resources/static/libs/iCheck/square/square.png create mode 100644 src/main/resources/static/libs/iCheck/square/square@2x.png create mode 100644 src/main/resources/static/libs/iCheck/square/yellow.css create mode 100644 src/main/resources/static/libs/iCheck/square/yellow.png create mode 100644 src/main/resources/static/libs/iCheck/square/yellow@2x.png create mode 100644 src/main/resources/static/libs/jquery.form/jquery.form.min.js create mode 100644 src/main/resources/static/libs/jquery.form/jquery.form.min.js.map create mode 100644 src/main/resources/static/libs/jquery/fastclick.min.js create mode 100644 src/main/resources/static/libs/jquery/jquery.min.js create mode 100644 src/main/resources/static/libs/jquery/jquery.slimscroll.min.js create mode 100644 src/main/resources/static/libs/kindeditor/jsp/README.txt create mode 100644 src/main/resources/static/libs/kindeditor/jsp/demo.jsp create mode 100644 src/main/resources/static/libs/kindeditor/jsp/file_manager_json.jsp create mode 100644 src/main/resources/static/libs/kindeditor/jsp/upload_json.jsp create mode 100644 src/main/resources/static/libs/kindeditor/kindeditor-all-custom-min.js create mode 100644 src/main/resources/static/libs/kindeditor/kindeditor-all-custom-src.js create mode 100644 src/main/resources/static/libs/kindeditor/kindeditor-all-min.js create mode 100644 src/main/resources/static/libs/kindeditor/kindeditor-all.js create mode 100644 src/main/resources/static/libs/kindeditor/lang/ar.js create mode 100644 src/main/resources/static/libs/kindeditor/lang/en.js create mode 100644 src/main/resources/static/libs/kindeditor/lang/ko.js create mode 100644 src/main/resources/static/libs/kindeditor/lang/ru.js create mode 100644 src/main/resources/static/libs/kindeditor/lang/zh-CN.js create mode 100644 src/main/resources/static/libs/kindeditor/lang/zh-TW.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/anchor/anchor.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/autoheight/autoheight.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/baidumap/baidumap.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/baidumap/index.html create mode 100644 src/main/resources/static/libs/kindeditor/plugins/baidumap/map.html create mode 100644 src/main/resources/static/libs/kindeditor/plugins/clearhtml/clearhtml.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/code/code.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/code/prettify.css create mode 100644 src/main/resources/static/libs/kindeditor/plugins/code/prettify.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/emoticons.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/0.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/1.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/10.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/100.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/101.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/102.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/103.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/104.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/105.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/106.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/107.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/108.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/109.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/11.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/110.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/111.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/112.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/113.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/114.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/115.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/116.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/117.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/118.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/119.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/12.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/120.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/121.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/122.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/123.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/124.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/125.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/126.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/127.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/128.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/129.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/13.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/130.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/131.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/132.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/133.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/134.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/14.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/15.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/16.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/17.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/18.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/19.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/2.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/20.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/21.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/22.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/23.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/24.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/25.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/26.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/27.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/28.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/29.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/3.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/30.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/31.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/32.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/33.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/34.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/35.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/36.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/37.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/38.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/39.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/4.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/40.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/41.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/42.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/43.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/44.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/45.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/46.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/47.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/48.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/49.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/5.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/50.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/51.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/52.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/53.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/54.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/55.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/56.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/57.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/58.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/59.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/6.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/60.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/61.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/62.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/63.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/64.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/65.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/66.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/67.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/68.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/69.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/7.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/70.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/71.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/72.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/73.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/74.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/75.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/76.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/77.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/78.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/79.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/8.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/80.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/81.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/82.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/83.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/84.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/85.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/86.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/87.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/88.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/89.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/9.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/90.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/91.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/92.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/93.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/94.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/95.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/96.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/97.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/98.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/99.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/emoticons/images/static.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/filemanager/filemanager.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/filemanager/images/file-16.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/filemanager/images/file-64.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/filemanager/images/folder-16.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/filemanager/images/folder-64.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/filemanager/images/go-up.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/fixtoolbar/fixtoolbar.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/flash/flash.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/image/image.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/image/images/align_left.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/image/images/align_right.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/image/images/align_top.gif create mode 100644 src/main/resources/static/libs/kindeditor/plugins/image/images/refresh.png create mode 100644 src/main/resources/static/libs/kindeditor/plugins/insertfile/insertfile.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/lineheight/lineheight.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/link/link.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/map/map.html create mode 100644 src/main/resources/static/libs/kindeditor/plugins/map/map.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/media/media.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/multiimage/images/image.png create mode 100644 src/main/resources/static/libs/kindeditor/plugins/multiimage/images/select-files-en.png create mode 100644 src/main/resources/static/libs/kindeditor/plugins/multiimage/images/select-files-zh-CN.png create mode 100644 src/main/resources/static/libs/kindeditor/plugins/multiimage/images/swfupload.swf create mode 100644 src/main/resources/static/libs/kindeditor/plugins/multiimage/multiimage.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/pagebreak/pagebreak.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/plainpaste/plainpaste.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/preview/preview.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/quickformat/quickformat.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/table/table.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/template/html/1.html create mode 100644 src/main/resources/static/libs/kindeditor/plugins/template/html/2.html create mode 100644 src/main/resources/static/libs/kindeditor/plugins/template/html/3.html create mode 100644 src/main/resources/static/libs/kindeditor/plugins/template/template.js create mode 100644 src/main/resources/static/libs/kindeditor/plugins/wordpaste/wordpaste.js create mode 100644 src/main/resources/static/libs/kindeditor/themes/common/anchor.gif create mode 100644 src/main/resources/static/libs/kindeditor/themes/common/blank.gif create mode 100644 src/main/resources/static/libs/kindeditor/themes/common/flash.gif create mode 100644 src/main/resources/static/libs/kindeditor/themes/common/loading.gif create mode 100644 src/main/resources/static/libs/kindeditor/themes/common/media.gif create mode 100644 src/main/resources/static/libs/kindeditor/themes/common/rm.gif create mode 100644 src/main/resources/static/libs/kindeditor/themes/default/background.png create mode 100644 src/main/resources/static/libs/kindeditor/themes/default/default.css create mode 100644 src/main/resources/static/libs/kindeditor/themes/default/default.png create mode 100644 src/main/resources/static/libs/kindeditor/themes/qq/editor.gif create mode 100644 src/main/resources/static/libs/kindeditor/themes/qq/qq.css create mode 100644 src/main/resources/static/libs/kindeditor/themes/simple/simple.css create mode 100644 src/main/resources/static/libs/layer/layer.js create mode 100644 src/main/resources/static/libs/layer/mobile/layer.js create mode 100644 src/main/resources/static/libs/layer/mobile/need/layer.css create mode 100644 src/main/resources/static/libs/layer/skin/default/icon-ext.png create mode 100644 src/main/resources/static/libs/layer/skin/default/icon.png create mode 100644 src/main/resources/static/libs/layer/skin/default/layer.css create mode 100644 src/main/resources/static/libs/layer/skin/default/loading-0.gif create mode 100644 src/main/resources/static/libs/layer/skin/default/loading-1.gif create mode 100644 src/main/resources/static/libs/layer/skin/default/loading-2.gif create mode 100644 src/main/resources/static/libs/pace/pace.css create mode 100644 src/main/resources/static/libs/pace/pace.js create mode 100644 src/main/resources/static/libs/treegrid/jquery.treegrid.css create mode 100644 src/main/resources/static/libs/treegrid/jquery.treegrid.extension.js create mode 100644 src/main/resources/static/libs/treegrid/jquery.treegrid.min.js create mode 100644 src/main/resources/static/libs/treegrid/tree.table.js create mode 100644 src/main/resources/static/libs/upload-img/1.png create mode 100644 src/main/resources/static/libs/upload-img/index.html create mode 100644 src/main/resources/static/libs/upload-img/jQuery.upload.js create mode 100644 src/main/resources/static/libs/upload-img/jQuery.upload.min.js create mode 100644 src/main/resources/static/libs/upload-img/upload.css create mode 100644 src/main/resources/static/libs/upload/css/webuploader.css create mode 100644 src/main/resources/static/libs/upload/js/jax.uploader.js create mode 100644 src/main/resources/static/libs/upload/js/uploadCall.js create mode 100644 src/main/resources/static/libs/upload/js/webuploader.js create mode 100644 src/main/resources/static/libs/validate/validate.css create mode 100644 src/main/resources/static/libs/validate/validate.js create mode 100644 src/main/resources/static/libs/ztree/css/ztree-bootstrap.css create mode 100644 src/main/resources/static/libs/ztree/img/bootstrap.gif create mode 100644 src/main/resources/static/libs/ztree/img/bootstrap.png create mode 100644 src/main/resources/static/libs/ztree/img/line_conn.png create mode 100644 src/main/resources/static/libs/ztree/img/loading.gif create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.all.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.all.min.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.core.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.core.min.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.excheck.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.excheck.min.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.exedit.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.exedit.min.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.exhide.js create mode 100644 src/main/resources/static/libs/ztree/js/jquery.ztree.exhide.min.js create mode 100644 src/main/resources/static/libs/ztree/metro/bootstrap.gif create mode 100644 src/main/resources/static/libs/ztree/metro/bootstrap.png create mode 100644 src/main/resources/static/libs/ztree/metro/line_conn.png create mode 100644 src/main/resources/static/libs/ztree/metro/loading.gif create mode 100644 src/main/resources/templates/content-editor/edit.html create mode 100644 src/main/resources/templates/content-editor/list.html create mode 100644 src/main/resources/templates/content/application.html create mode 100644 src/main/resources/templates/content/edit.html create mode 100644 src/main/resources/templates/content/edit2.html create mode 100644 src/main/resources/templates/content/list.html create mode 100644 src/main/resources/templates/content/list2.html create mode 100644 src/main/resources/templates/data_dict/edit.html create mode 100644 src/main/resources/templates/data_dict/list.html create mode 100644 src/main/resources/templates/database/monitoring.html create mode 100644 src/main/resources/templates/demo.html create mode 100644 src/main/resources/templates/error/403.html create mode 100644 src/main/resources/templates/error/404.html create mode 100644 src/main/resources/templates/error/4xx.html create mode 100644 src/main/resources/templates/error/500.html create mode 100644 src/main/resources/templates/error/5xx.html create mode 100644 src/main/resources/templates/fragments/footer.html create mode 100644 src/main/resources/templates/fragments/navbar.html create mode 100644 src/main/resources/templates/fragments/sidebar.html create mode 100644 src/main/resources/templates/home/fragments/footer.html create mode 100644 src/main/resources/templates/home/fragments/top.html create mode 100644 src/main/resources/templates/index/index.html create mode 100644 src/main/resources/templates/index/workdest.html create mode 100644 src/main/resources/templates/onlineUsers/list.html create mode 100644 src/main/resources/templates/permission/detail.html create mode 100644 src/main/resources/templates/permission/list.html create mode 100644 src/main/resources/templates/role/detail.html create mode 100644 src/main/resources/templates/role/list.html create mode 100644 src/main/resources/templates/syslog/list.html create mode 100644 src/main/resources/templates/system/kickout.html create mode 100644 src/main/resources/templates/system/login.html create mode 100644 src/main/resources/templates/system/register.html create mode 100644 src/main/resources/templates/ui/icons.html create mode 100644 src/main/resources/templates/user/list.html create mode 100644 src/main/resources/templates/user/userDetail.html create mode 100644 src/main/webapp/WEB-INF/loginPage.jsp create mode 100644 src/main/webapp/WEB-INF/web.xml create mode 100644 src/main/webapp/WEB-INF/weixinLogin.jsp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ffe5c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Compiled class file +*.class +target + +.classpath +.project +.settings + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar +*.iml +.idea + +# virtual machine crash logs +hs_err_pid* diff --git a/docs/database/cases.sql b/docs/database/cases.sql new file mode 100644 index 0000000..20709a4 --- /dev/null +++ b/docs/database/cases.sql @@ -0,0 +1,195 @@ + +USE `xgl_cases`; + + + +-- ---------------------------- +-- Table structure for data_dict +-- ---------------------------- +DROP TABLE IF EXISTS `data_dict`; +CREATE TABLE `data_dict` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID, 主键,自增', + `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `datakey` varchar(32) NOT NULL COMMENT '字典key值', + `name` varchar(64) NOT NULL COMMENT '字典名称', + `isvalid` tinyint(4) DEFAULT '1' COMMENT '是否有效(1:有效,0:无效)', + `description` varchar(255) DEFAULT NULL COMMENT '描述', + PRIMARY KEY (`id`), + UNIQUE INDEX `ukey_datakey`(`datakey`) +) ENGINE = InnoDB COMMENT='数据字典表'; + +/*Data for the table `data_dict` */ +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (1,'2020-05-27 15:54:33','2020-05-27 15:54:33','isValid','状态',1,'全局使用,不可删除'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (2,'2020-05-27 16:24:01','2020-05-27 16:24:01','logType','日志类型',1,'系统日志使用'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (3,'2020-05-27 16:25:09','2020-05-27 16:25:09','isTask','任务状态',1,'任务使用'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (4,'2020-05-27 16:29:08','2020-05-27 16:29:08','tag_ppqa','标签_品牌全案',1,'案例库-品牌全案目录下的标签库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (5,'2020-05-27 16:29:25','2020-05-27 16:29:49','tag_ggqa','标签_公关全案',1,'案例库-公关全案目录下的标签库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (6,'2020-05-27 16:30:16','2020-05-27 16:30:30','tag_design','标签_创意设计',1,'案例库-创意设计目录下的标签库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (7,'2020-05-27 16:30:43','2020-05-27 16:30:55','tag_video','标签_视频动画',1,'案例库-视频动画目录下的标签库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (8,'2020-05-27 16:31:05','2020-05-27 18:12:32','tag_h5','标签_技术开发',1,'案例库-技术开发目录下的标签库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (9,'2020-05-27 16:32:04','2020-05-27 16:32:14','tag_ldhd','标签_落地活动',1,'案例库-落地活动目录下的标签库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (10,'2020-05-27 16:32:24','2020-05-27 16:42:16','tag_others','标签_其他案例',1,'案例库-其他案例目录下的标签库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (11,'2020-05-27 16:39:02','2020-05-27 16:39:19','from_ppqa','来源_品牌全案',1,'案例库-品牌全案目录下的案例来源库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (12,'2020-05-27 16:39:36','2020-05-27 16:39:46','from_ggqa','来源_公关全案',1,'案例库-公关全案目录下的案例来源库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (13,'2020-05-27 16:40:01','2020-05-27 16:40:12','from_design','来源_创意设计',1,'案例库-创意设计目录下的案例来源库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (14,'2020-05-27 16:40:15','2020-05-27 16:40:34','from_video','来源_视频动画',1,'案例库-视频动画目录下的案例来源库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (15,'2020-05-27 16:40:47','2020-05-27 18:12:00','from_h5','来源_技术开发',1,'案例库-技术开发目录下的案例来源库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (16,'2020-05-27 16:41:19','2020-05-27 16:41:39','from_ldhd','来源_落地活动',1,'案例库-落地活动目录下的案例来源库'); +insert into `data_dict`(`id`,`createtime`,`updatetime`,`datakey`,`name`,`isvalid`,`description`) values (17,'2020-05-27 16:41:48','2020-05-27 16:42:18','from_others','来源_其他案例',1,'案例库-其他案例目录下的案例来源库'); + + + +-- ---------------------------- +-- Table structure for data_dict_item +-- ---------------------------- +DROP TABLE IF EXISTS `data_dict_item`; +CREATE TABLE `data_dict_item` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID, 主键,自增', + `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `dictid` bigint(20) NOT NULL COMMENT '字典id值(data_dict表id)', + `value` varchar(50) NOT NULL COMMENT '字典value值(组合主键)', + `name` varchar(50) NOT NULL COMMENT '字典名字', + `sort` int(10) NOT NULL DEFAULT '0' COMMENT '排序', + PRIMARY KEY (`id`), + UNIQUE INDEX `ukey`(`dictId`, `value`) +) ENGINE = InnoDB COMMENT='数据字典值集合表'; + + + + +-- ---------------------------- +-- Table structure for content +-- ---------------------------- +DROP TABLE IF EXISTS `content`; +CREATE TABLE `content` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID, 主键,自增', + `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `type` enum('ppqa','ggqa','design','video','h5','ldhd','others') NOT NULL COMMENT '案例类型[ppqa:品牌全案, ggqa:公关全案, design:创意设计, video:视频动画, h5:技术开发, ldhd:落地活动, others:其他案例]', + `isvalid` tinyint(4) DEFAULT '1' COMMENT '是否有效(1:有效,0:无效)', + `fromid` int(11) NULL DEFAULT NULL COMMENT '案例来源ID', + `title` varchar(100) NOT NULL COMMENT '标题', + `desct` varchar(1000) NULL DEFAULT NULL COMMENT '摘要,描述', + `viewno` int(11) NULL DEFAULT NULL COMMENT '查看数', + `praiseno` int(11) NULL DEFAULT NULL COMMENT '点赞数', + `listicon` varchar(200) NULL DEFAULT NULL COMMENT '列表icon图', + `qrcode` varchar(200) NULL DEFAULT NULL COMMENT '二维码', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序值, 值越大越考前', + `url` varchar(500) DEFAULT NULL COMMENT '访问地址', + `content` mediumtext COMMENT 'HTML内容,富文本内容存储在这', + `content_text` mediumtext COMMENT '纯Text内容,用于存到ES便于搜索', + `attachment` json DEFAULT NULL COMMENT '附件', + PRIMARY KEY (`ID`), + INDEX `idx_title`(`title`), + INDEX `idx_sort`(`sort`) +) ENGINE = InnoDB COMMENT = '案例库内容表'; + + + + +-- ---------------------------- +-- Table structure for content_images +-- ---------------------------- +DROP TABLE IF EXISTS `content_images`; +CREATE TABLE `content_images` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID, 主键,自增', + `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `cid` bigint(20) NOT NULL COMMENT '内容表ID', + `imgurl` varchar(500) NOT NULL COMMENT '图片地址', + `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序', + `ori_name` varchar(100) DEFAULT NULL COMMENT '原始名称', + PRIMARY KEY (`id`) +) ENGINE = InnoDB COMMENT = '内容图片集表'; + + + +-- ---------------------------- +-- Table structure for content_tags +-- ---------------------------- +DROP TABLE IF EXISTS `content_tags`; +CREATE TABLE `content_tags` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID, 主键,自增', + `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `cid` bigint(20) NOT NULL COMMENT '内容表ID', + `tid` bigint(20) NOT NULL COMMENT '标签id(与data_dict_item.id对应)', + PRIMARY KEY (`id`), + UNIQUE INDEX `ukey_value`(`cid`, `tid`) +) ENGINE = InnoDB COMMENT = '内容标签关联表'; + + + + + +-- ---------------------------- +-- Table structure for wx_department +-- 企业微信部门表 +-- ---------------------------- +DROP TABLE IF EXISTS `wx_department`; +CREATE TABLE `wx_department` ( + `id` bigint(20) NOT NULL COMMENT '主键, 部门id', + `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `name` varchar(100) NOT NULL COMMENT '部门名称', + `parentid` bigint(20) NOT NULL COMMENT '父亲部门id。根部门为1', + `order` int(11) DEFAULT NULL COMMENT '在父部门中的排序值', + PRIMARY KEY (`id`), + UNIQUE KEY `ukey_deptid` (`id`) +) DEFAULT CHARSET=utf8mb4 COMMENT='企业微信部门表'; + + + +-- ---------------------------- +-- Table structure for wx_user +-- 微信成员表 +-- ---------------------------- +DROP TABLE IF EXISTS `wx_user`; +CREATE TABLE `wx_user` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID, 主键,自增', + `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updatetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `userid` varchar(50) NOT NULL COMMENT '成员UserID,对应管理端的帐号,企业内必须唯一', + `name` varchar(50) NOT NULL COMMENT '成员名称', + `mobile` varchar(25) DEFAULT NULL COMMENT '手机号码', + `department` varchar(100) NOT NULL COMMENT '成员所属部门id列表', + `position` varchar(100) DEFAULT NULL COMMENT '职位信息', + `gender` char(1) DEFAULT '0' COMMENT '性别。0表示未定义,1表示男性,2表示女性', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `weixinid` varchar(50) DEFAULT NULL COMMENT '微信号', + `isleader` varchar(25) DEFAULT NULL COMMENT '上级字段,标识是否为上级', + `avatar` varchar(255) DEFAULT NULL COMMENT '头像url。注:如果要获取小图将url最后的”/0”改成”/100”即可', + `english_name` varchar(50) DEFAULT NULL COMMENT '英文名', + `status` smallint(6) NOT NULL DEFAULT '0' COMMENT '激活状态: 1=已激活,2=已禁用,4=未激活', + PRIMARY KEY (`id`), + UNIQUE KEY `ukey_userid` (`userid`) +) DEFAULT CHARSET=utf8mb4 COMMENT='微信成员表'; + + + +-- ---------------------------- +-- Function structure for getChildDeptIds +-- 递归查询子部门ID函数 +-- ---------------------------- +DROP FUNCTION IF EXISTS getChildDeptIds; +DELIMITER $$ +CREATE FUNCTION `getChildDeptIds`(rootdeptid INT) RETURNS VARCHAR(500) COMMENT '获取子部门ID列表,包含当前id' + BEGIN + DECLARE sChildList VARCHAR(500); + DECLARE sChildTemp VARCHAR(500); + SET sChildTemp =CAST(rootdeptid AS CHAR); + WHILE sChildTemp IS NOT NULL DO + IF (sChildList IS NOT NULL) THEN + SET sChildList = CONCAT(sChildList,',',sChildTemp); + ELSE + SET sChildList = CONCAT(sChildTemp); + END IF; + SELECT GROUP_CONCAT(deptid) INTO sChildTemp FROM wx_department WHERE FIND_IN_SET(parentid,sChildTemp)>0; + END WHILE; + RETURN sChildList; +END $$ +DELIMITER ; + + + + diff --git a/docs/database/system.sql b/docs/database/system.sql new file mode 100644 index 0000000..9ac9cdb --- /dev/null +++ b/docs/database/system.sql @@ -0,0 +1,186 @@ + +CREATE DATABASE IF NOT EXISTS `xgl_cases` DEFAULT CHARACTER SET utf8mb4; + +USE `xgl_cases`; + + + + +/*Table structure for table `permission` */ +DROP TABLE IF EXISTS `permission`; +CREATE TABLE `permission` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `permission_id` varchar(20) NOT NULL COMMENT '权限id', + `name` varchar(100) NOT NULL COMMENT '权限名称', + `description` varchar(255) DEFAULT NULL COMMENT '权限描述', + `url` varchar(255) DEFAULT NULL COMMENT '权限访问路径', + `perms` varchar(255) DEFAULT NULL COMMENT '权限标识', + `parent_id` int(11) DEFAULT NULL COMMENT '父级权限id', + `type` int(1) DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮', + `order_num` int(3) DEFAULT '0' COMMENT '排序', + `icon` varchar(50) DEFAULT NULL COMMENT '图标', + `status` int(1) NOT NULL COMMENT '状态:1有效;2删除', + `create_time` datetime DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +/*Data for the table `permission` */ +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (1,'1','工作台','工作台','/workdest','workdest',0,1,1,'fa fa-home',1,'2017-09-27 21:22:02','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (2,'2','权限管理','权限管理','',NULL,0,0,2,'fa fa-th-list',1,'2017-07-13 15:04:42','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (3,'201','用户管理','用户管理','/users','users',2,1,1,'fa fa-circle-o',1,'2017-07-13 15:05:47','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (4,'20101','列表查询','用户列表查询','/user/list','user:list',3,2,0,NULL,1,'2017-07-13 15:09:24','2017-10-09 05:38:29'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (5,'20102','新增','新增用户','/user/add','user:add',3,2,0,NULL,1,'2017-07-13 15:06:50','2018-02-28 17:58:46'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (6,'20103','编辑','编辑用户','/user/edit','user:edit',3,2,0,NULL,1,'2017-07-13 15:08:03','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (7,'20104','删除','删除用户','/user/delete','user:delete',3,2,0,NULL,1,'2017-07-13 15:08:42','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (8,'20105','批量删除','批量删除用户','/user/batch/delete','user:batchDelete',3,2,0,'',1,'2018-07-11 01:53:09','2018-07-11 01:53:09'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (9,'20106','分配角色','分配角色','/user/assign/role','user:assignRole',3,2,0,NULL,1,'2017-07-13 15:09:24','2017-10-09 05:38:29'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (10,'202','角色管理','角色管理','/roles','roles',2,1,2,'fa fa-circle-o',1,'2017-07-17 14:39:09','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (11,'20201','列表查询','角色列表查询','/role/list','role:list',10,2,0,NULL,1,'2017-10-10 15:31:36','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (12,'20202','新增','新增角色','/role/add','role:add',10,2,0,NULL,1,'2017-07-17 14:39:46','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (13,'20203','编辑','编辑角色','/role/edit','role:edit',10,2,0,NULL,1,'2017-07-17 14:40:15','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (14,'20204','删除','删除角色','/role/delete','role:delete',10,2,0,NULL,1,'2017-07-17 14:40:57','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (15,'20205','批量删除','批量删除角色','/role/batch/delete','role:batchDelete',10,2,0,'',1,'2018-07-10 22:20:43','2018-07-10 22:20:43'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (16,'20206','分配权限','分配权限','/role/assign/permission','role:assignPerms',10,2,0,NULL,1,'2017-09-26 07:33:05','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (17,'203','资源管理','资源管理','/permissions','permissions',2,1,3,'fa fa-circle-o',1,'2017-09-26 07:33:51','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (18,'20301','列表查询','资源列表','/permission/list','permission:list',17,2,0,NULL,1,'2018-07-12 16:25:28','2018-07-12 16:25:33'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (19,'20302','新增','新增资源','/permission/add','permission:add',17,2,0,NULL,1,'2017-09-26 08:06:58','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (20,'20303','编辑','编辑资源','/permission/edit','permission:edit',17,2,0,NULL,1,'2017-09-27 21:29:04','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (21,'20304','删除','删除资源','/permission/delete','permission:delete',17,2,0,NULL,1,'2017-09-27 21:29:50','2018-02-27 10:53:14'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (22,'3','运维管理','运维管理','',NULL,0,0,3,'fa fa-th-list',1,'2018-07-06 15:19:26','2018-07-06 15:19:26'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (23,'301','数据监控','数据监控','/database/monitoring','database',22,1,1,'fa fa-circle-o',1,'2018-07-06 15:19:55','2018-09-12 13:14:48'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (24,'4','系统工具','系统工具','',NULL,0,0,4,'fa fa-th-list',1,'2018-07-06 15:20:38','2018-07-06 15:20:38'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (25,'401','图标工具','图标工具','/icons','icons',24,1,1,'fa fa-circle-o',1,'2018-07-06 15:21:00','2018-07-06 15:21:00'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (28,'5','在线用户','在线用户','/online/users','onlineUsers',2,1,4,'fa fa-circle-o',1,'2018-07-18 21:00:38','2018-07-19 12:47:42'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (29,'501','在线用户查询','在线用户查询','/online/user/list','onlineUser:list',28,2,0,NULL,1,'2018-07-18 21:01:25','2018-07-19 12:48:04'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (30,'502','踢出用户','踢出用户','/online/user/kickout','onlineUser:kickout',28,2,0,NULL,1,'2018-07-18 21:41:54','2018-07-19 12:48:25'); +insert into `permission`(`id`,`permission_id`,`name`,`description`,`url`,`perms`,`parent_id`,`type`,`order_num`,`icon`,`status`,`create_time`,`update_time`) values (31,'503','批量踢出','批量踢出','/online/user/batch/kickout','onlineUser:batchKickout',28,2,0,'',1,'2018-07-19 12:49:30','2018-07-19 12:49:30'); + + + + +/*Table structure for table `role` */ +DROP TABLE IF EXISTS `role`; +CREATE TABLE `role` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `role_id` varchar(20) NOT NULL COMMENT '角色id', + `name` varchar(50) NOT NULL COMMENT '角色名称', + `description` varchar(255) DEFAULT NULL COMMENT '角色描述', + `status` int(1) NOT NULL COMMENT '状态:1有效;2删除', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +/*Data for the table `role` */ +insert into `role`(`id`,`role_id`,`name`,`description`,`status`,`create_time`,`update_time`) values (1,'1','超级管理员','超级管理员',1,'2017-06-28 20:30:05','2017-06-28 20:30:10'); +insert into `role`(`id`,`role_id`,`name`,`description`,`status`,`create_time`,`update_time`) values (2,'2','管理员','管理员',1,'2017-06-30 23:35:19','2017-10-11 09:32:33'); +insert into `role`(`id`,`role_id`,`name`,`description`,`status`,`create_time`,`update_time`) values (3,'3','普通用户','普通用户',1,'2017-06-30 23:35:44','2018-07-13 11:44:06'); + + + + +/*Table structure for table `role_permission` */ +DROP TABLE IF EXISTS `role_permission`; +CREATE TABLE `role_permission` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `role_id` varchar(20) NOT NULL COMMENT '角色id', + `permission_id` varchar(20) NOT NULL COMMENT '权限id', + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +/*Data for the table `role_permission` */ +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (1,'1','1'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (2,'1','2'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (3,'1','201'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (4,'1','20101'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (5,'1','20102'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (6,'1','20103'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (7,'1','20104'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (8,'1','20105'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (9,'1','20106'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (10,'1','202'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (11,'1','20201'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (12,'1','20202'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (13,'1','20203'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (14,'1','20204'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (15,'1','20205'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (16,'1','20206'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (17,'1','203'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (18,'1','20301'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (19,'1','20302'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (20,'1','20303'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (21,'1','20304'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (22,'1','5'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (23,'1','501'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (24,'1','502'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (25,'1','503'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (26,'1','3'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (27,'1','301'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (28,'1','4'); +insert into `role_permission`(`id`,`role_id`,`permission_id`) values (29,'1','401'); + + + + +/*Table structure for table `user` */ +DROP TABLE IF EXISTS `user`; +CREATE TABLE `user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` varchar(20) NOT NULL COMMENT '用户id', + `username` varchar(50) NOT NULL COMMENT '用户名', + `password` varchar(50) NOT NULL, + `salt` varchar(128) DEFAULT NULL COMMENT '加密盐值', + `email` varchar(50) DEFAULT NULL COMMENT '邮箱', + `phone` varchar(50) DEFAULT NULL COMMENT '联系方式', + `sex` int(255) DEFAULT NULL COMMENT '年龄:1男2女', + `age` int(3) DEFAULT NULL COMMENT '年龄', + `status` int(1) NOT NULL COMMENT '用户状态:1有效; 2删除', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', + PRIMARY KEY (`id`,`user_id`) +) ENGINE=InnoDB; + +/*Data for the table `user` */ +insert into `user`(`id`,`user_id`,`username`,`password`,`salt`,`status`,`create_time`,`update_time`) +values (1,'1','admin','64b4f7f481befa153d4be6273a5a9743','8ec2761c3ec31c4100884553abf1221b',1,'2019-12-01 12:00:00','2019-12-01 12:00:00'); + + + + +/*Table structure for table `user_role` */ +DROP TABLE IF EXISTS `user_role`; +CREATE TABLE `user_role` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` varchar(20) NOT NULL COMMENT '用户id', + `role_id` varchar(20) NOT NULL COMMENT '角色id', + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +/*Data for the table `user_role` */ +insert into `user_role`(`id`,`user_id`,`role_id`) values (1,'1','1'); + + + + + +-- ---------------------------- +-- Table structure for sys_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_log`; +CREATE TABLE `sys_log` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID, 主键,自增', + `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `type` int(10) NOT NULL COMMENT '日志类型(1:系统管理;2:登录日志;3:字典管理;4:案例管理)', + `operator` varchar(100) DEFAULT NULL COMMENT '操作者', + `ipaddr` varchar(150) DEFAULT NULL COMMENT 'IP地址', + `description` varchar(255) NOT NULL COMMENT '描述', + PRIMARY KEY (`id`) +) ENGINE = InnoDB COMMENT='系统日志表'; + + + + + + diff --git a/docs/document/公司案例库网站构建逻辑.docx b/docs/document/公司案例库网站构建逻辑.docx new file mode 100644 index 0000000000000000000000000000000000000000..84c8e390b3aac22c8cf8938c65d46b1e99a08961 GIT binary patch literal 421366 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

k zzC79Z7JB9nF>R4#%_2w>ux-K{0gfX2myj!hz-fOw(1d}NVp;wqm|;@i2|OCe1F4kM z3HIn2>Uv6UrI>;A=_4n@aPM8b^Tzx5hK8z>u(h$dkxB28%z4l9Cn940Q@1AI3xu!* zdzEK^fyThu<&kglrv)?ecBNN3sC&!^paFhkU?JV1&B8Pmg8PID4{I!%oEl>T3I$0| z;BHa5a8(rmGN{yCqRFQW#3kD3%fo$=q?o}Vn7o3o{use82A8c1+uPE|8>KO`S0$

"); + }else{ + JSONObject json = new JSONObject(); + json.put(CoreConst.STATUS, CoreConst.STATUS_ERROR); + json.put(CoreConst.MSG, error); + out.print(json.toString()); + } + out.flush(); + out.close(); + } catch (IOException e) { + logger.error("统一处理异常错误", e); + } + } + + @ExceptionHandler(AuthorizationException.class) + public String handleAuth(HttpServletRequest request) { + request.setAttribute("javax.servlet.error.status_code",com.nbclass.enums.ResponseStatus.FORBIDDEN.getCode()); + return "forward:/error"; + } + + @ExceptionHandler(ParameterException.class) + @ResponseBody + public Result ParameterException(RuntimeException runtimeException){ + return Result.error(runtimeException.getMessage()); + } + +} + diff --git a/src/main/java/com/nbclass/exception/DataBaseException.java b/src/main/java/com/nbclass/exception/DataBaseException.java new file mode 100644 index 0000000..c03dad9 --- /dev/null +++ b/src/main/java/com/nbclass/exception/DataBaseException.java @@ -0,0 +1,26 @@ +package com.nbclass.exception; + +/** + * 数据库异常类 + * @author Leon + * @datetime 2019年3月31日 下午11:17:35 + */ +public class DataBaseException extends RuntimeException{ + + private static final long serialVersionUID = -7912461660403185962L; + + public DataBaseException(){ + } + + public DataBaseException(String message, Throwable cause){ + super(message, cause); + } + + public DataBaseException(String message){ + super(message); + } + + public DataBaseException(Throwable cause){ + super(cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/exception/LogicalException.java b/src/main/java/com/nbclass/exception/LogicalException.java new file mode 100644 index 0000000..24df05b --- /dev/null +++ b/src/main/java/com/nbclass/exception/LogicalException.java @@ -0,0 +1,26 @@ +package com.nbclass.exception; + +/** + * 业务逻辑异常类 + * @author Leon + * @datetime 2019年3月31日 下午11:16:56 + */ +public class LogicalException extends RuntimeException{ + + private static final long serialVersionUID = 2494732724125452953L; + + public LogicalException(){ + } + + public LogicalException(String message, Throwable cause){ + super(message, cause); + } + + public LogicalException(String message){ + super(message); + } + + public LogicalException(Throwable cause){ + super(cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/exception/ParameterException.java b/src/main/java/com/nbclass/exception/ParameterException.java new file mode 100644 index 0000000..6050923 --- /dev/null +++ b/src/main/java/com/nbclass/exception/ParameterException.java @@ -0,0 +1,26 @@ +package com.nbclass.exception; + +/** + * 参数错误异常类 + * @author Leon + * @datetime 2019年3月31日 下午11:01:35 + */ +public class ParameterException extends RuntimeException{ + + private static final long serialVersionUID = 7936887727544416780L; + + public ParameterException(){ + } + + public ParameterException(String message, Throwable cause){ + super(message, cause); + } + + public ParameterException(String message){ + super(message); + } + + public ParameterException(Throwable cause){ + super(cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/exception/ServiceException.java b/src/main/java/com/nbclass/exception/ServiceException.java new file mode 100644 index 0000000..994d5af --- /dev/null +++ b/src/main/java/com/nbclass/exception/ServiceException.java @@ -0,0 +1,26 @@ +package com.nbclass.exception; + +/** + * 服务异常类 + * @author Leon + * @datetime 2019年3月31日 下午11:16:09 + */ +public class ServiceException extends RuntimeException{ + + private static final long serialVersionUID = 3794850430751334041L; + + public ServiceException(){ + } + + public ServiceException(String message, Throwable cause){ + super(message, cause); + } + + public ServiceException(String message){ + super(message); + } + + public ServiceException(Throwable cause){ + super(cause); + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/holder/SpringContextHolder.java b/src/main/java/com/nbclass/holder/SpringContextHolder.java new file mode 100644 index 0000000..4267b94 --- /dev/null +++ b/src/main/java/com/nbclass/holder/SpringContextHolder.java @@ -0,0 +1,53 @@ +package com.nbclass.holder; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class SpringContextHolder implements ApplicationContextAware { + + private static ApplicationContext appContext = null; + + /** + * 通过name获取 Bean. + * + * @param name + * @return + */ + public static Object getBean(String name) { + return appContext.getBean(name); + + } + + /** + * 通过class获取Bean. + * + * @param clazz + * @param + * @return + */ + public static T getBean(Class clazz) { + return appContext.getBean(clazz); + } + + /** + * 通过name,以及Clazz返回指定的Bean + * + * @param name + * @param clazz + * @param + * @return + */ + public static T getBean(String name, Class clazz) { + return appContext.getBean(name, clazz); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (appContext == null) { + appContext = applicationContext; + } + } +} diff --git a/src/main/java/com/nbclass/shiro/MyShiroRealm.java b/src/main/java/com/nbclass/shiro/MyShiroRealm.java new file mode 100644 index 0000000..840b419 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/MyShiroRealm.java @@ -0,0 +1,156 @@ +package com.nbclass.shiro; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.LockedAccountException; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UnknownAccountException; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.mgt.RealmSecurityManager; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.mgt.eis.SessionDAO; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; +import org.apache.shiro.subject.support.DefaultSubjectContext; +import org.apache.shiro.util.ByteSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.nbclass.system.model.User; +import com.nbclass.system.service.PermissionService; +import com.nbclass.system.service.RoleService; +import com.nbclass.system.service.UserService; +import com.nbclass.util.CoreConst; +import com.nbclass.util.IpUtil; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class MyShiroRealm extends AuthorizingRealm { + + @Autowired + private UserService userService; + @Autowired + private RoleService roleService; + @Autowired + private PermissionService permissionService; + + //@Autowired + //private RedisSessionDAO redisSessionDAO; + @Autowired + private SessionDAO sessionDAO; + + //授权 + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + if(principals == null){ + throw new AuthorizationException("principals should not be null"); + } + User user = (User) principals.getPrimaryPrincipal(); + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); + info.setRoles(roleService.findRoleByUserId(user.getUserId())); + info.setStringPermissions(permissionService.findPermsByUserId(user.getUserId())); + return info; + } + + //认证 + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + //获取用户的输入的账号. + String username = (String)token.getPrincipal(); + User user = userService.selectByUsername(username); + if(user==null) { + throw new UnknownAccountException(); + } + if (CoreConst.STATUS_INVALID.equals(user.getStatus())) { + // 帐号锁定 + throw new LockedAccountException(); + } + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + // 把ip放入user存入redis缓存里 + user.setLoginIpAddress(IpUtil.getIpAddr(request)); + SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( + user, + user.getPassword(), + ByteSource.Util.bytes(user.getCredentialsSalt()), + getName() + ); + return authenticationInfo; + } + + /**清除认证信息*/ + public void removeCachedAuthenticationInfo(List userIds) { + if(null == userIds || userIds.size() == 0) { + return ; + } + List list = getSpcListByUserIds(userIds); + RealmSecurityManager securityManager = + (RealmSecurityManager) SecurityUtils.getSecurityManager(); + MyShiroRealm realm = (MyShiroRealm)securityManager.getRealms().iterator().next(); + for (SimplePrincipalCollection simplePrincipalCollection : list) { + realm.clearCachedAuthenticationInfo(simplePrincipalCollection); + } + } + + /** + * 根据userId 清除当前session存在的用户的权限缓存 + * @param userIds 已经修改了权限的userId + */ + public void clearAuthorizationByUserId(List userIds){ + if(null == userIds || userIds.size() == 0) { + return ; + } + List list = getSpcListByUserIds(userIds); + RealmSecurityManager securityManager = + (RealmSecurityManager) SecurityUtils.getSecurityManager(); + MyShiroRealm realm = (MyShiroRealm)securityManager.getRealms().iterator().next(); + for (SimplePrincipalCollection simplePrincipalCollection : list) { + realm.clearCachedAuthorizationInfo(simplePrincipalCollection); + } + } + + /** + * 根据用户id获取所有spc + * @param userIds 已经修改了权限的userId + */ + private List getSpcListByUserIds(List userIds){ + //获取所有session + // Collection sessions = redisSessionDAO.getActiveSessions(); + Collection sessions = sessionDAO.getActiveSessions(); + //定义返回 + List list = new ArrayList(); + for (Session session:sessions){ + //获取session登录信息。 + Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); + if(null != obj && obj instanceof SimplePrincipalCollection){ + //强转 + SimplePrincipalCollection spc = (SimplePrincipalCollection)obj; + //判断用户,匹配用户ID。 + obj = spc.getPrimaryPrincipal(); + if(null != obj && obj instanceof User){ + User user = (User) obj; + System.out.println("user:"+user); + //比较用户ID,符合即加入集合 + if(null != user && userIds.contains(user.getUserId())){ + list.add(spc); + } + } + } + } + return list; + } +} diff --git a/src/main/java/com/nbclass/shiro/PermsService.java b/src/main/java/com/nbclass/shiro/PermsService.java new file mode 100644 index 0000000..0763174 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/PermsService.java @@ -0,0 +1,19 @@ +package com.nbclass.shiro; + +import org.apache.shiro.SecurityUtils; +import org.springframework.stereotype.Component; + +/** + * js调用 thymeleaf 实现按钮权限 + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Component("perms") +public class PermsService +{ + public boolean hasPerm(String permission) + { + return SecurityUtils.getSubject().isPermitted(permission); + } +} diff --git a/src/main/java/com/nbclass/shiro/ShiroService.java b/src/main/java/com/nbclass/shiro/ShiroService.java new file mode 100644 index 0000000..61422f0 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/ShiroService.java @@ -0,0 +1,125 @@ +package com.nbclass.shiro; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager; +import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; +import org.apache.shiro.web.servlet.AbstractShiroFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.nbclass.holder.SpringContextHolder; +import com.nbclass.system.model.Permission; +import com.nbclass.system.service.PermissionService; +import com.nbclass.util.CoreConst; + +/** + * + * @author Leon + * @datetime 2019年4月1日 下午2:47:53 + */ +@Service +public class ShiroService { + + @Autowired + private PermissionService permissionService; + + /** + * 初始化加载权限 + * @return + */ + public Map loadFilterChainDefinitions() { + // 权限控制map.从数据库获取 + Map filterChainDefinitionMap = new LinkedHashMap(); + + filterChainDefinitionMap.put("/register", "anon"); + filterChainDefinitionMap.put("/login", "anon"); + filterChainDefinitionMap.put("/error/**", "anon"); + filterChainDefinitionMap.put("/kickout", "anon"); + /*filterChainDefinitionMap.put("/logout", "logout");*/ + filterChainDefinitionMap.put("/verificationCode", "anon"); // 登录验证码 + filterChainDefinitionMap.put("/qywx/qrLogin", "anon"); // 企业微信登录页面 + + // 前端接口放行 + filterChainDefinitionMap.put("/front/api/**", "anon"); // 前端接口 + filterChainDefinitionMap.put("/", "anon"); // 网站首页 + filterChainDefinitionMap.put("/caseType/**", "anon"); // 数据字典 + filterChainDefinitionMap.put("/wx/**", "anon"); // wx接口 + filterChainDefinitionMap.put("/comment/**", "anon"); // wx接口 + filterChainDefinitionMap.put("/console/content/saveScore", "anon"); // + + filterChainDefinitionMap.put("/mobile/*.html", "wxWebOAuth"); // 移动端页面授权拦截 + + // 静态资源按目录排除 + filterChainDefinitionMap.put("/css/**","anon"); + filterChainDefinitionMap.put("/js/**","anon"); + filterChainDefinitionMap.put("/img/**","anon"); + filterChainDefinitionMap.put("/libs/**","anon"); + // 静态资源按后缀排除 + filterChainDefinitionMap.put("/**/*.ico", "anon"); + filterChainDefinitionMap.put("/**/*.htm", "anon"); + filterChainDefinitionMap.put("/**/*.html", "anon"); + filterChainDefinitionMap.put("/**/*.css", "anon"); + filterChainDefinitionMap.put("/**/*.js", "anon"); + filterChainDefinitionMap.put("/**/*.jpg", "anon"); + filterChainDefinitionMap.put("/**/*.png", "anon"); + filterChainDefinitionMap.put("/**/*.gif", "anon"); + filterChainDefinitionMap.put("/**/*.webp", "anon"); + filterChainDefinitionMap.put("/**/*.ttf", "anon"); + filterChainDefinitionMap.put("/**/*.woff", "anon"); + filterChainDefinitionMap.put("/**/*.woff2", "anon"); + filterChainDefinitionMap.put("/**/*.mp3", "anon"); + filterChainDefinitionMap.put("/**/*.mp4", "anon"); + filterChainDefinitionMap.put("/**/*.txt", "anon"); + + + List permissionList = permissionService.selectAll(CoreConst.STATUS_VALID); + for(Permission permission : permissionList){ + if (StringUtils.isNotBlank(permission.getUrl())&& StringUtils.isNotBlank(permission.getPerms())) { + String perm = "perms[" + permission.getPerms()+ "]"; + filterChainDefinitionMap.put(permission.getUrl(),perm+",kickout"); + } + } + + filterChainDefinitionMap.put("/**", "user,kickout"); + + return filterChainDefinitionMap; + } + + /** + * 添加/修改/删除 后 重新加载权限 + */ + public void updatePermission() { + ShiroFilterFactoryBean shiroFilterFactoryBean = SpringContextHolder.getBean(ShiroFilterFactoryBean.class); + synchronized (shiroFilterFactoryBean) { + AbstractShiroFilter shiroFilter = null; + try { + shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject(); + } catch (Exception e) { + throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!"); + } + + PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter + .getFilterChainResolver(); + DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver + .getFilterChainManager(); + + // 清空老的权限控制 + manager.getFilterChains().clear(); + + shiroFilterFactoryBean.getFilterChainDefinitionMap().clear(); + shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitions()); + // 重新构建生成 + Map chains = shiroFilterFactoryBean.getFilterChainDefinitionMap(); + for (Map.Entry entry : chains.entrySet()) { + String url = entry.getKey(); + String chainDefinition = entry.getValue().trim().replace(" ", ""); + manager.createChain(url, chainDefinition); + } + } + } +} diff --git a/src/main/java/com/nbclass/shiro/config/LoginType.java b/src/main/java/com/nbclass/shiro/config/LoginType.java new file mode 100644 index 0000000..e283d32 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/config/LoginType.java @@ -0,0 +1,22 @@ +package com.nbclass.shiro.config; + +/** + * 登录类型 + * @author Leon + * @datetime 2019年4月22日 下午8:48:14 + */ +public enum LoginType { + + PASSWORD("password"), // 密码登录 + NOPASSWD("nopassword"); // 免密登录 + + private String code;// 状态值 + + private LoginType(String code) { + this.code = code; + } + public String getCode () { + return code; + } + +} diff --git a/src/main/java/com/nbclass/shiro/config/MyHashedCredentialsMatcher.java b/src/main/java/com/nbclass/shiro/config/MyHashedCredentialsMatcher.java new file mode 100644 index 0000000..578e724 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/config/MyHashedCredentialsMatcher.java @@ -0,0 +1,19 @@ +package com.nbclass.shiro.config; + +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.credential.HashedCredentialsMatcher; + +public class MyHashedCredentialsMatcher extends HashedCredentialsMatcher { + + @Override + public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) { + MyUsernamePasswordToken tk = (MyUsernamePasswordToken) authcToken; + // 如果是免密登录直接返回true + if(tk.getType().equals(LoginType.NOPASSWD)){ + return true; + } + // 不是免密登录,调用父类的方法 + return super.doCredentialsMatch(tk, info); + } +} diff --git a/src/main/java/com/nbclass/shiro/config/MyUsernamePasswordToken.java b/src/main/java/com/nbclass/shiro/config/MyUsernamePasswordToken.java new file mode 100644 index 0000000..b295e7d --- /dev/null +++ b/src/main/java/com/nbclass/shiro/config/MyUsernamePasswordToken.java @@ -0,0 +1,45 @@ +package com.nbclass.shiro.config; + +import org.apache.shiro.authc.UsernamePasswordToken; + +/** + * + * @author Leon + * @datetime 2019年4月22日 下午8:59:23 + */ +public class MyUsernamePasswordToken extends UsernamePasswordToken { + + private static final long serialVersionUID = 1744966815127132051L; + + private LoginType type; + + public MyUsernamePasswordToken() { + super(); + } + + public MyUsernamePasswordToken(String username, String password, LoginType type, boolean rememberMe, String host) { + super(username, password, rememberMe, host); + this.type = type; + } + + /**免密登录*/ + public MyUsernamePasswordToken(String username) { + super(username, "", false, null); + this.type = LoginType.NOPASSWD; + } + + /**账号密码登录*/ + public MyUsernamePasswordToken(String username, String password) { + super(username, password, false, null); + this.type = LoginType.PASSWORD; + } + + public LoginType getType() { + return type; + } + + public void setType(LoginType type) { + this.type = type; + } + +} diff --git a/src/main/java/com/nbclass/shiro/config/ShiroConfig.java b/src/main/java/com/nbclass/shiro/config/ShiroConfig.java new file mode 100644 index 0000000..9091530 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/config/ShiroConfig.java @@ -0,0 +1,267 @@ +package com.nbclass.shiro.config; + +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.servlet.Filter; + +import org.apache.shiro.authc.credential.HashedCredentialsMatcher; +import org.apache.shiro.cache.MemoryConstrainedCacheManager; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO; +import org.apache.shiro.session.mgt.eis.SessionDAO; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.CookieRememberMeManager; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.apache.shiro.web.servlet.SimpleCookie; +import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.nbclass.shiro.MyShiroRealm; +import com.nbclass.shiro.ShiroService; +import com.nbclass.shiro.filter.KickoutSessionControlFilter; +import com.nbclass.shiro.filter.MobilePageOAuthFilter; +import com.nbclass.shiro.filter.ResetPwdUrlFilter; + +import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Configuration +public class ShiroConfig { + + @Autowired + private ShiroService shiroService; + + /* + @Value("${spring.redis.host}") + private String host; + + @Value("${spring.redis.port}") + private int port; + + @Value("${spring.redis.timeout}") + private int timeout; + + @Value("${spring.redis.password}") + private String password; + */ + + @Bean + public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + /** + * ShiroDialect,为了在thymeleaf里使用shiro的标签的bean + * @return + */ + @Bean + public ShiroDialect shiroDialect() { + return new ShiroDialect(); + } + /** + * ShiroFilterFactoryBean 处理拦截资源文件问题。 + * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在 + * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager + * + Filter Chain定义说明 + 1、一个URL可以配置多个Filter,使用逗号分隔 + 2、当设置多个过滤器时,全部验证通过,才视为通过 + 3、部分过滤器可指定参数,如perms,roles + * + */ + @Bean + public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){ + ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); + // 必须设置 SecurityManager + shiroFilterFactoryBean.setSecurityManager(securityManager); + // 登录url + shiroFilterFactoryBean.setLoginUrl("/login"); + // 登录成功后要跳转的链接 + shiroFilterFactoryBean.setSuccessUrl("/index"); + //未授权界面; + shiroFilterFactoryBean.setUnauthorizedUrl("/error/403"); + //自定义拦截器 + Map filtersMap = new LinkedHashMap(); + //限制同一帐号同时在线的个数。 + filtersMap.put("kickout", kickoutSessionControlFilter()); + // 找回密码链接拦截验证 + filtersMap.put("resetPwdUrl", new ResetPwdUrlFilter()); + // 移动端页面授权拦截器 + filtersMap.put("wxWebOAuth", new MobilePageOAuthFilter()); + + shiroFilterFactoryBean.setFilters(filtersMap); + //拦截器. + Map filterChainDefinitionMap = shiroService.loadFilterChainDefinitions(); + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + return shiroFilterFactoryBean; + } + + /** + * cookie对象; + * @return + */ + public SimpleCookie rememberMeCookie(){ + //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe + SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); + // + simpleCookie.setMaxAge(2592000); + return simpleCookie; + } + + /** + * cookie管理对象;记住我功能 + * @return + */ + public CookieRememberMeManager rememberMeManager(){ + CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); + cookieRememberMeManager.setCookie(rememberMeCookie()); + //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位) + cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag==")); + return cookieRememberMeManager; + } + + @Bean(name = "securityManager") + public SecurityManager securityManager(){ + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + //设置realm. + securityManager.setRealm(myShiroRealm()); + /*记住我*/ + securityManager.setRememberMeManager(rememberMeManager()); + // 自定义缓存实现 使用redis + // securityManager.setCacheManager(redisCacheManager()); + securityManager.setCacheManager(new MemoryConstrainedCacheManager()); //Shiro默认提供了CacheManager的简单内存实现MemoryConstrainedCacheManager,可用于生产环境; + // 自定义session管理 使用redis + securityManager.setSessionManager(sessionManager()); + return securityManager; + } + + @Bean + public MyShiroRealm myShiroRealm(){ + MyShiroRealm myShiroRealm = new MyShiroRealm(); + myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); + return myShiroRealm; + } + + /** + * 凭证匹配器 + * ) + * @return + */ + @Bean + public HashedCredentialsMatcher hashedCredentialsMatcher(){ + // HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); + MyHashedCredentialsMatcher hashedCredentialsMatcher = new MyHashedCredentialsMatcher(); // 使用自定义匹配器,方便使用免密登录 + hashedCredentialsMatcher.setHashAlgorithmName("md5"); + hashedCredentialsMatcher.setHashIterations(2); + return hashedCredentialsMatcher; + } + + + /** + * 开启shiro aop注解支持. + * 使用代理方式;所以需要开启代码支持; + * @param securityManager + * @return + */ + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ + AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); + return authorizationAttributeSourceAdvisor; + } + + /** + * 配置shiro redisManager + * 使用的是shiro-redis开源插件 + * @return + */ + /* + public RedisManager redisManager() { + RedisManager redisManager = new RedisManager(); + redisManager.setHost(host+":"+port); + // redisManager.setPort(port); + // 配置缓存过期时间 + // redisManager.setExpire(1800); + + redisManager.setTimeout(timeout); + redisManager.setPassword(password); + return redisManager; + } + */ + + /** + * cacheManager 缓存 redis实现 + * 使用的是shiro-redis开源插件 + * @return + */ + /* + @Bean + public RedisCacheManager redisCacheManager() { + RedisCacheManager redisCacheManager = new RedisCacheManager(); + redisCacheManager.setRedisManager(redisManager()); + return redisCacheManager; + } + */ + + /** + * RedisSessionDAO shiro sessionDao层的实现 通过redis + * 使用的是shiro-redis开源插件 + */ + /* + @Bean + public RedisSessionDAO redisSessionDAO() { + RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); + redisSessionDAO.setRedisManager(redisManager()); + return redisSessionDAO; + } + */ + @Bean + public SessionDAO sessionDAO() { + // return new MemorySessionDAO(); //使用默认的MemorySessionDAO + return new EnterpriseCacheSessionDAO(); + } + + /** + * shiro session的管理 + */ + @Bean + public DefaultWebSessionManager sessionManager() { + DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); + // sessionManager.setSessionDAO(redisSessionDAO()); + sessionManager.setSessionDAO(sessionDAO()); + return sessionManager; + } + /** + * 限制同一账号登录同时登录人数控制 + * @return + */ + public KickoutSessionControlFilter kickoutSessionControlFilter(){ + KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter(); + //使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的; + //这里我们还是用之前shiro使用的redisManager()实现的cacheManager()缓存管理 + //也可以重新另写一个,重新配置缓存时间之类的自定义缓存属性 + // kickoutSessionControlFilter.setCacheManager(redisCacheManager()); + kickoutSessionControlFilter.setCacheManager(new MemoryConstrainedCacheManager()); //Shiro默认提供了CacheManager的简单内存实现MemoryConstrainedCacheManager,可用于生产环境; + //用于根据会话ID,获取会话进行踢出操作的; + kickoutSessionControlFilter.setSessionManager(sessionManager()); + //是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序。 + kickoutSessionControlFilter.setKickoutAfter(false); + //同一个用户最大的会话数,默认5;比如5的意思是同一个用户允许最多同时五个人登录; + kickoutSessionControlFilter.setMaxSession(5); + //被踢出后重定向到的地址; + kickoutSessionControlFilter.setKickoutUrl("/kickout"); + return kickoutSessionControlFilter; + } + + +} diff --git a/src/main/java/com/nbclass/shiro/filter/KickoutSessionControlFilter.java b/src/main/java/com/nbclass/shiro/filter/KickoutSessionControlFilter.java new file mode 100644 index 0000000..bf1dbc8 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/filter/KickoutSessionControlFilter.java @@ -0,0 +1,175 @@ +package com.nbclass.shiro.filter; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serializable; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.apache.shiro.cache.Cache; +import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.session.Session; +import org.apache.shiro.session.mgt.DefaultSessionKey; +import org.apache.shiro.session.mgt.SessionManager; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.web.filter.AccessControlFilter; +import org.apache.shiro.web.util.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.nbclass.system.model.User; + +/** + * @author superzheng + * @date 2018-07-18 + */ +public class KickoutSessionControlFilter extends AccessControlFilter { + + protected static Logger logger = LoggerFactory.getLogger(KickoutSessionControlFilter.class); + + /** + * 踢出后到的地址 + */ + private String kickoutUrl; + /** + * 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户 + */ + private boolean kickoutAfter = false; + /** + * 同一个帐号最大会话数 默认5 + */ + private int maxSession = 5; + + private SessionManager sessionManager; + private Cache> cache; + + public void setKickoutUrl(String kickoutUrl) { + this.kickoutUrl = kickoutUrl; + } + + public void setKickoutAfter(boolean kickoutAfter) { + this.kickoutAfter = kickoutAfter; + } + + public void setMaxSession(int maxSession) { + this.maxSession = maxSession; + } + + public void setSessionManager(SessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + public void setCacheManager(CacheManager cacheManager) { + this.cache = cacheManager.getCache("shiro_redis_cache"); + } + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { + return false; + } + + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { + // HttpServletRequest req = (HttpServletRequest) request; + + Subject subject = getSubject(request, response); + if(!subject.isAuthenticated() && !subject.isRemembered()) { + //如果没有登录,直接进行之后的流程 + return true; + } + + + Session session = subject.getSession(); + User user = (User) subject.getPrincipal(); + String username = user.getUsername(); + Serializable sessionId = session.getId(); + + //读取缓存 没有就存入 + Deque deque = cache.get(username); + + //如果此用户没有session队列,也就是还没有登录过,缓存中没有 + //就new一个空队列,不然deque对象为空,会报空指针 + if(deque==null){ + deque = new LinkedList(); + } + + //如果队列里没有此sessionId,且用户没有被踢出;放入队列 + if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) { + //将sessionId存入队列 + deque.push(sessionId); + //将用户的sessionId队列缓存 + cache.put(username, deque); + } + + //如果队列里的sessionId数超出最大会话数,开始踢人 + while(deque.size() > maxSession) { + Serializable kickoutSessionId = null; + //如果踢出后者 + if(kickoutAfter) { + kickoutSessionId = deque.removeFirst(); + //踢出后再更新下缓存队列 + cache.put(username, deque); + } else { //否则踢出前者 + kickoutSessionId = deque.removeLast(); + //踢出后再更新下缓存队列 + cache.put(username, deque); + } + + + + try { + //获取被踢出的sessionId的session对象 + Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId)); + if(kickoutSession != null) { + //设置会话的kickout属性表示踢出了 + kickoutSession.setAttribute("kickout", true); + } + } catch (Exception e) {//ignore exception + } + } + + //如果被踢出了,直接退出,重定向到踢出后的地址 + if ((Boolean)session.getAttribute("kickout")!=null&&(Boolean)session.getAttribute("kickout") == true) { + //会话被踢出了 + try { + //退出登录 + subject.logout(); + } catch (Exception e) { //ignore + } + saveRequest(request); + + Map resultMap = new HashMap(2); + //判断是不是Ajax请求 + if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) { + resultMap.put("user_status", "300"); + resultMap.put("message", "您已经在其他地方登录,请重新登录!"); + //输出json串 + out(response, resultMap); + }else{ + //重定向 + WebUtils.issueRedirect(request, response, kickoutUrl); + } + return false; + } + return true; + } + private void out(ServletResponse hresponse, Map resultMap) + throws IOException { + try { + hresponse.setCharacterEncoding("UTF-8"); + PrintWriter out = hresponse.getWriter(); + out.println(JSON.toJSONString(resultMap)); + out.flush(); + out.close(); + } catch (Exception e) { + System.err.println("KickoutSessionFilter.class 输出JSON异常,可以忽略。"); + } + } +} diff --git a/src/main/java/com/nbclass/shiro/filter/MobilePageOAuthFilter.java b/src/main/java/com/nbclass/shiro/filter/MobilePageOAuthFilter.java new file mode 100644 index 0000000..0e8c3f3 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/filter/MobilePageOAuthFilter.java @@ -0,0 +1,94 @@ +package com.nbclass.shiro.filter; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.util.CommonUtils; +import com.nbclass.util.HttpUtils; +import com.nbclass.util.WebUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.web.filter.AccessControlFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URLEncoder; +import java.util.UUID; + +/** + * 移动端页面授权拦截器 + * @author Leon + * @datetime 2019年9月2日 上午10:55:53 + */ +public class MobilePageOAuthFilter extends AccessControlFilter { + + protected static Logger logger = LoggerFactory.getLogger(MobilePageOAuthFilter.class); + + private static final String WX_API_DOMAIN = "http://www.qiween.cn/wxapi"; + + private static final String OAUTH2_SCOPE = "snsapi_base"; + + @Override + protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + response.setHeader("Cache-Control", "no-store"); + + String userAgent = StringUtils.trimToEmpty(request.getHeader("User-Agent")).toLowerCase(); + if(!userAgent.contains("micromessenger")){ // pc端打开不拦截 + return true; + } + + String openId = WebUtils.getValueFromCookie(request, "cookie_key_s3_vote_uid"); + String s_openId = (String) request.getSession().getAttribute("sk_wx_openid"); + if(StringUtils.isNotBlank(openId) && StringUtils.isNotBlank(s_openId)){ + return true; + } + + String httpRequestFullURL = getHttpRequestFullURL(request); + String redirectUrl = WX_API_DOMAIN+"/api/oauth?redirect_uri=" + URLEncoder.encode(httpRequestFullURL, "UTF-8")+"&scope="+OAUTH2_SCOPE; + String code = request.getParameter("code"); + if(StringUtils.isNotBlank(code)){ + String url = WX_API_DOMAIN+"/api/oauthInfo?code="+code+"&scope="+OAUTH2_SCOPE; + try { + JSONObject json = HttpUtils.httpGet(url); + if(json == null || json.getIntValue("errcode") == 40029 || json.getIntValue("errcode") == 40163 + || (StringUtils.isNotBlank(json.getString("msg")) && (json.getString("msg").contains("40029") || json.getString("msg").contains("40163"))) ){ + logger.info("微信授权失败,将重新跳转授权......"); + redirectUrl = CommonUtils.removeParamValue(redirectUrl, "code"); + response.sendRedirect(redirectUrl); + return false; + } + logger.info("微信授权获取到用户信息: "+json); + openId = json.getString("openId"); + WebUtils.addCookie(response, "cookie_key_s3_vote_uid", openId, 3600 * 24 * 7); + request.getSession().setAttribute("sk_wx_openid", openId); + } catch (Exception e) { + logger.error("授权失败:", e); + openId = UUID.randomUUID().toString().replace("-", ""); + WebUtils.addCookie(response, "cookie_key_s3_vote_uid", openId, 3600 * 24 * 7); // 7天有效期 + } + }else{ + response.sendRedirect(redirectUrl); + return false; + } + return true; + } + + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { + return false; + } + + public static String getHttpRequestFullURL(HttpServletRequest request) { + StringBuffer requestURL = request.getRequestURL(); + String queryString = request.getQueryString(); + if (queryString == null) { + return requestURL.toString(); + } else { + return requestURL.append('?').append(queryString).toString(); + } + } + +} diff --git a/src/main/java/com/nbclass/shiro/filter/ResetPwdUrlFilter.java b/src/main/java/com/nbclass/shiro/filter/ResetPwdUrlFilter.java new file mode 100644 index 0000000..6146d15 --- /dev/null +++ b/src/main/java/com/nbclass/shiro/filter/ResetPwdUrlFilter.java @@ -0,0 +1,79 @@ +package com.nbclass.shiro.filter; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.web.filter.AccessControlFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * 找回密码链接验证 + * @author Leon + * @datetime 2019年5月2日 下午6:59:01 + */ +public class ResetPwdUrlFilter extends AccessControlFilter { + + protected static Logger logger = LoggerFactory.getLogger(ResetPwdUrlFilter.class); + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { + logger.info("ResetPwdUrlFilter.isAccessAllowed ......"); + String verifyCode = request.getParameter("verifyCode"); + if(StringUtils.isBlank(verifyCode)){ + logger.info("进入重置密码页面,参数[verifyCode]为null ......"); + outPrintError(response, "The requested URL was not found on this server."); + return false; + } + /* + CustomerMapper customerMapper = SpringContextHolder.getBean(CustomerMapper.class); + try{ + // 查询验证码是否有效,半小时重置密码才有效 + CustomerResetPwd customerResetPwd = customerMapper.selectResetPwd(verifyCode); + if(customerResetPwd == null || customerResetPwd.getStatus() == null || customerResetPwd.getStatus() != 1){ + logger.info("进入重置密码页面,验证码已失效[verifyCode="+verifyCode+"]."); + outPrintError(response, "The requested URL was not found on this server."); + return false; + } + }finally{ + customerMapper = null; + } + */ + return true; + } + + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { + return false; + } + + public void outPrintError(ServletResponse response, String result) { + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + PrintWriter out = null; + try { + String html = ""; + html += "Error 404 (Not Found)"; + html += ""; + html += ""; + html += ""; + html+="
"; + html += result; + html+="
"; + out = response.getWriter(); + out.print(html); + } catch (IOException e) { + logger.error("IO工作流出现异常!", e); + } finally { + if(out!=null){ + out.close(); + out = null; + } + } + } + + +} diff --git a/src/main/java/com/nbclass/system/controller/BaseController.java b/src/main/java/com/nbclass/system/controller/BaseController.java new file mode 100644 index 0000000..2832dc8 --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/BaseController.java @@ -0,0 +1,186 @@ +package com.nbclass.system.controller; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.shiro.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.system.model.User; +import com.nbclass.util.CoreConst; +import com.nbclass.util.WebUtils; + +/** + * + * @author Leon + * @datetime 2019年4月1日 下午3:22:58 + */ +public abstract class BaseController { + + protected Logger logger = LoggerFactory.getLogger(getClass()); + + private static ThreadLocal> outPutMsg = new ThreadLocal>(); + + /** + * 获取request + * @return HttpServletRequest + */ + protected HttpServletRequest getRequest() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + } + + /** + * 获取Response + * @return HttpServletResponse + */ + protected HttpServletResponse getResponse() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); + } + + /** + * 获取session + * @return HttpSession + */ + protected HttpSession getSession() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession(); + } + + protected ServletContext getServletContext() { + return ContextLoader.getCurrentWebApplicationContext().getServletContext(); + } + + public void outPrint() { + outPrint(null); + } + + public void outPrint(Object result) { + outPrint(result, true); + } + + /** + * 输出,同时清空outPutMsg + * @param response + * @param result + * @param respCode -- 是否返回响应状态吗,false:将直接输出result + */ + public void outPrint(Object result, boolean respCode) { + try { + HttpServletResponse response = getResponse(); + response.setCharacterEncoding("utf-8"); + response.setContentType("application/json"); + + PrintWriter out = response.getWriter(); + if(respCode){ + JSONObject obj = new JSONObject(); + if(result == null){ + // no msg + }else if(result instanceof String){ + obj.put(CoreConst.MSG, result); + }else{ + obj = (JSONObject) JSONObject.toJSON(result); + } + obj.put(CoreConst.STATUS, CoreConst.STATUS_SUCCESS); + out.print(obj.toString()); + }else{ + out.print(result.toString()); + } + getOutputMsg().clear(); + } catch (IOException e) { + logger.error("输出异常", e); + } + } + + /** + * 输出失败信息,同时清空outPutMsg + * @param response + * @param result void + */ + public void outPrintFail(Object result) { + try { + HttpServletResponse response = getResponse(); + response.setCharacterEncoding("utf-8"); + response.setContentType("application/json"); + + JSONObject obj = new JSONObject(); + if(result == null){ + // no msg + }else if(result instanceof String){ + obj.put(CoreConst.MSG, result); + }else{ + obj = (JSONObject) JSONObject.toJSON(result); + } + obj.put(CoreConst.STATUS, CoreConst.STATUS_ERROR); + PrintWriter out = response.getWriter(); + out.print(obj.toString()); + getOutputMsg().clear(); + } catch (IOException e) { + logger.error("输出异常", e); + } + } + + /** + * 线程绑定,其内容会在outPrint方法调用后清空 + * @return Map + */ + public Map getOutputMsg() { + Map output = outPutMsg.get(); + if(output==null){ + output = new HashMap(); + outPutMsg.set(output); + } + return output; + } + + public User getLoginUser(){ + User user = (User) SecurityUtils.getSubject().getPrincipal(); + return user; + } + + public int getLoginUserId(){ + int userid = -1; + User user = getLoginUser(); + if(user!=null && user.getId()!=null)userid=user.getId(); + return userid; + } + + /** + * 获取ip地址 + * @return + */ + public String getIpAddr() { + String ip = WebUtils.getReqIpAddr(getRequest()); + return ip; + } + + /** + * 是否是管理员角色 + * @return + */ + public boolean isAdminRole(){ + boolean isSuperAdmin = SecurityUtils.getSubject().hasRole("1"); //是否是超级管理员,admin的roleid为1 + boolean isBaseAdmin = SecurityUtils.getSubject().hasRole("1000000002073979"); //是否是普通管理员, 给客户的管理员 + boolean isAdmin = isSuperAdmin||isBaseAdmin; + return isAdmin; + } + + public static void main(String[] args) { + Object result = null; + result = ""; + JSONObject obj = (JSONObject) JSONObject.toJSON(result); + obj.put(CoreConst.STATUS, CoreConst.STATUS_SUCCESS); + System.out.println(obj); + } + +} diff --git a/src/main/java/com/nbclass/system/controller/DatabaseController.java b/src/main/java/com/nbclass/system/controller/DatabaseController.java new file mode 100644 index 0000000..e0faa89 --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/DatabaseController.java @@ -0,0 +1,20 @@ +package com.nbclass.system.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.ModelAndView; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Controller +@RequestMapping("/database") +public class DatabaseController{ + @GetMapping(value = "/monitoring") + public ModelAndView databaseMonitoring(){ + return new ModelAndView("database/monitoring"); + } +} diff --git a/src/main/java/com/nbclass/system/controller/ErrorController.java b/src/main/java/com/nbclass/system/controller/ErrorController.java new file mode 100644 index 0000000..078df4e --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/ErrorController.java @@ -0,0 +1,23 @@ +package com.nbclass.system.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Controller +@RequestMapping("/error") +public class ErrorController { + /*shiro无权限时进入*/ + @RequestMapping("/403") + public String noPermission(HttpServletRequest request, HttpServletResponse response){ + response.setStatus(HttpStatus.FORBIDDEN.value()); + return "error/403"; + } +} diff --git a/src/main/java/com/nbclass/system/controller/OnlineUserController.java b/src/main/java/com/nbclass/system/controller/OnlineUserController.java new file mode 100644 index 0000000..28c9933 --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/OnlineUserController.java @@ -0,0 +1,82 @@ +package com.nbclass.system.controller; + +import java.io.Serializable; +import java.util.List; + +import org.apache.shiro.SecurityUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.nbclass.system.service.UserService; +import com.nbclass.util.ResultUtil; +import com.nbclass.vo.UserOnlineVo; +import com.nbclass.vo.UserSessionVo; +import com.nbclass.vo.base.PageResultVo; +import com.nbclass.vo.base.ResponseVo; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ + +@Controller +@RequestMapping("/online/user") +public class OnlineUserController { + @Autowired + private UserService userService; + + // 在线用户列表 + @PostMapping("/list") + @ResponseBody + public PageResultVo onlineUsers(UserOnlineVo user, Integer limit, Integer offset){ + List userList = userService.selectOnlineUsers(user); + int endIndex = (offset+limit) > userList.size() ? userList.size() : (offset+limit); + return ResultUtil.table(userList.subList(offset,endIndex),(long)userList.size()); + } + + // 强制踢出用户 + @PostMapping("/kickout") + @ResponseBody + public ResponseVo kickout(String sessionId,String username) { + try { + if(SecurityUtils.getSubject().getSession().getId().equals(sessionId)){ + return ResultUtil.error("不能踢出自己"); + } + userService.kickout(sessionId,username); + return ResultUtil.success("踢出用户成功"); + } catch (Exception e) { + return ResultUtil.error("踢出用户失败"); + } + } + + // 批量强制踢出用户 + @PostMapping("/batch/kickout") + @ResponseBody + public ResponseVo kickout(@RequestBody List sessions) { + try { + //要踢出的用户中是否有自己 + boolean hasOwn=false; + Serializable sessionId = SecurityUtils.getSubject().getSession().getId(); + for (UserSessionVo sessionVo : sessions) { + if(sessionVo.getSessionId().equals(sessionId)){ + hasOwn=true; + }else{ + userService.kickout(sessionVo.getSessionId(),sessionVo.getUsername()); + } + + + } + if(hasOwn){ + return ResultUtil.success("不能踢出自己"); + } + return ResultUtil.success("踢出用户成功"); + } catch (Exception e) { + return ResultUtil.error("踢出用户失败"); + } + } +} diff --git a/src/main/java/com/nbclass/system/controller/PermissionController.java b/src/main/java/com/nbclass/system/controller/PermissionController.java new file mode 100644 index 0000000..a304d76 --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/PermissionController.java @@ -0,0 +1,124 @@ +package com.nbclass.system.controller; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.nbclass.shiro.ShiroService; +import com.nbclass.system.model.Permission; +import com.nbclass.system.service.PermissionService; +import com.nbclass.util.CoreConst; +import com.nbclass.util.ResultUtil; +import com.nbclass.vo.base.ResponseVo; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Controller +@RequestMapping("/permission") +public class PermissionController{ + private static final Logger logger = LoggerFactory.getLogger(PermissionController.class); + /**1:全部资源,2:菜单资源*/ + private static final String[] MENU_FLAG ={"1","2"}; + @Autowired + private PermissionService permissionService; + @Autowired + private ShiroService shiroService; + + + + /*权限列表数据*/ + @PostMapping("/list") + @ResponseBody + public List loadPermissions(String flag){ + List permissionListList = new ArrayList(); + if(StringUtils.isBlank(flag) || MENU_FLAG[0].equals(flag)){ + permissionListList = permissionService.selectAll(CoreConst.STATUS_VALID); + }else if(MENU_FLAG[1].equals(flag)){ + permissionListList = permissionService.selectAllMenuName(CoreConst.STATUS_VALID); + } + return permissionListList; + } + + /*添加权限*/ + @ResponseBody + @PostMapping("/add") + public ResponseVo addPermission(Permission permission){ + try { + int a = permissionService.insert(permission); + if (a > 0) { + shiroService.updatePermission(); + return ResultUtil.success("添加权限成功"); + } else { + return ResultUtil.error("添加权限失败"); + } + } catch (Exception e) { + logger.error(String.format("PermissionController.addPermission%s", e)); + throw e; + } + } + + /*删除权限*/ + @ResponseBody + @PostMapping("/delete") + public ResponseVo deletePermission(String permissionId){ + try { + int subPermsByPermissionIdCount = permissionService.selectSubPermsByPermissionId(permissionId); + if(subPermsByPermissionIdCount>0){ + return ResultUtil.error("改资源存在下级资源,无法删除!"); + } + int a = permissionService.updateStatus(permissionId,CoreConst.STATUS_INVALID); + if (a > 0) { + shiroService.updatePermission(); + return ResultUtil.success("删除权限成功"); + } else { + return ResultUtil.error("删除权限失败"); + } + } catch (Exception e) { + logger.error(String.format("PermissionController.deletePermission%s", e)); + throw e; + } + } + + /*权限详情*/ + @GetMapping("/edit") + public String detail(Model model, String permissionId) { + Permission permission = permissionService.findByPermissionId(permissionId); + if(null!=permission){ + if(permission.getParentId().equals(CoreConst.TOP_MENU_ID)){ + model.addAttribute("parentName", CoreConst.TOP_MENU_NAME); + }else{ + Permission parent = permissionService.findById(permission.getParentId()); + model.addAttribute("parentName", parent.getName()); + } + } + model.addAttribute("permission", permission); + return "permission/detail"; + } + + /*编辑权限*/ + @ResponseBody + @PostMapping("/edit") + public ResponseVo editPermission(@ModelAttribute("permission")Permission permission){ + int a = permissionService.updateByPermissionId(permission); + if (a > 0) { + shiroService.updatePermission(); + return ResultUtil.success("编辑权限成功"); + } else { + return ResultUtil.error("编辑权限失败"); + } + } + +} diff --git a/src/main/java/com/nbclass/system/controller/QyWxLoginController.java b/src/main/java/com/nbclass/system/controller/QyWxLoginController.java new file mode 100644 index 0000000..2632a1f --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/QyWxLoginController.java @@ -0,0 +1,138 @@ +package com.nbclass.system.controller; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URLEncoder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.LockedAccountException; +import org.apache.shiro.subject.Subject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.shiro.config.MyUsernamePasswordToken; +import com.nbclass.system.model.User; +import com.nbclass.system.service.UserService; +import com.nbclass.util.CommonUtils; +import com.nbclass.util.HttpUtils; + +import lombok.extern.slf4j.Slf4j; + +/** + * 企业微信扫码登录 + * @author Leon + * @datetime 2020年6月2日 下午4:18:04 + */ +@Slf4j +@Controller +@RequestMapping("/qywx") +public class QyWxLoginController { + + private static final int AgentId = 1000016; // 企业微信登录应用ID(信广龙广告) + + @Autowired + private UserService userService; + + /** + * PC端网页授权(企业微信授权登录) + * @param code + * @param request + * @param response + * @throws IOException + */ + @RequestMapping("/qrLogin") + public void qrLogin(String code, HttpServletRequest request, HttpServletResponse response) throws IOException{ + response.setHeader("Cache-Control", "no-store"); + String httpRequestFullURL = URLEncoder.encode(getBaseUrl(request)+"/qywx/qrLogin", "UTF-8"); + String redirectUrl = "http://show.szxgl.cn/qywx/api/sso/login?agentid="+AgentId+"&redirect_uri=" + httpRequestFullURL; + if(StringUtils.isBlank(code)){ + response.sendRedirect(redirectUrl); + return; + } + + String url = "http://show.szxgl.cn/qywx/api/sso/getUserInfo?agentid="+AgentId+"&code="+code; + try { + JSONObject json = HttpUtils.httpPost(url); + if(json == null || json.getIntValue("errcode") == 40029 || json.getIntValue("errcode") == 40163){ + log.info("企业微信授权失败,将重新跳转授权......"); + redirectUrl = CommonUtils.removeParamValue(redirectUrl, "code"); + response.sendRedirect(redirectUrl); + return; + } + log.info("企业微信扫码登录, 用户信息: "+json); + String userid = json.getString("userid"); + if(StringUtils.isBlank(userid)){ + outPrintError(response, "该账号未经授权,请联系管理员授权后再试!"); + return; + } + + // 企业微信登录成功,下一步进行系统shiro身份验证 + String username = userid; + MyUsernamePasswordToken token = new MyUsernamePasswordToken(username); // 免密登录,用于扫码登录手机验证码登录等场景 + try{ + Subject subject = SecurityUtils.getSubject(); + subject.login(token); + } catch (LockedAccountException e) { + token.clear(); + outPrintError(response, "用户已经被锁定不能登录,请联系管理员!"); + return; + } catch (AuthenticationException e) { + token.clear(); + outPrintError(response, "账号异常,请联系管理员处理!"); + return; + } + //更新最后登录时间 + userService.updateLastLoginTime((User) SecurityUtils.getSubject().getPrincipal()); + + response.sendRedirect(request.getContextPath()+"/admin"); + } catch (Exception e) { + log.error("企业微信登录授权失败", e); + outPrintError(response, "企业微信授权失败,请稍候再试!"); + } + } + + /** + * 输出错误信息到页面 + * @param response + * @param result + */ + public void outPrintError(HttpServletResponse response, String result) { + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/html; charset=UTF-8"); + PrintWriter out = null; + try { + String html = ""; + html += "鉴权错误"; + html += ""; + html += ""; + html += ""; + html+="
"; + html += result; + html+="
"; + out = response.getWriter(); + out.print(html); + } catch (IOException e) { + throw new RuntimeException("IO工作流出现异常!"); + } finally { + if(out!=null){ + out.close(); + out = null; + } + } + } + + private static String getBaseUrl(HttpServletRequest request){ + String result = null; + int port = request.getServerPort(); + result = request.getScheme()+"://"+request.getServerName()+(port==80||port==443 ? "" : ":"+port)+request.getContextPath()+"/"; + return result; + } + +} diff --git a/src/main/java/com/nbclass/system/controller/RenderController.java b/src/main/java/com/nbclass/system/controller/RenderController.java new file mode 100644 index 0000000..2a0f0fa --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/RenderController.java @@ -0,0 +1,41 @@ +package com.nbclass.system.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * @version V1.0 + * @date 2018年7月13日 + * @author superzheng + */ +@Controller +public class RenderController { + + /*工作台*/ + @GetMapping("/workdest") + public String workdest(){ + return "index/workdest"; + } + + /**用户列表入口*/ + @GetMapping("/users") + public String userList(){ + return "user/list"; + } + + /*角色列表入口*/ + @GetMapping("/roles") + public String roleList() { + return "role/list"; + } + + /*权限列表入口*/ + @GetMapping("/permissions") + public String permissionList(){ + return "permission/list"; + } + + /*在线用户入口*/ + @GetMapping("/online/users") + public String onlineUsers(){return "onlineUsers/list";} +} diff --git a/src/main/java/com/nbclass/system/controller/RoleController.java b/src/main/java/com/nbclass/system/controller/RoleController.java new file mode 100644 index 0000000..d574828 --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/RoleController.java @@ -0,0 +1,184 @@ +package com.nbclass.system.controller; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.shiro.MyShiroRealm; +import com.nbclass.system.model.Permission; +import com.nbclass.system.model.Role; +import com.nbclass.system.model.User; +import com.nbclass.system.service.PermissionService; +import com.nbclass.system.service.RoleService; +import com.nbclass.util.CoreConst; +import com.nbclass.util.PageUtil; +import com.nbclass.util.ResultUtil; +import com.nbclass.vo.PermissionTreeListVo; +import com.nbclass.vo.base.PageResultVo; +import com.nbclass.vo.base.ResponseVo; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Controller +@RequestMapping("/role") +public class RoleController { + private static final Logger logger = LoggerFactory.getLogger(RoleController.class); + @Autowired + private RoleService roleService; + @Autowired + private PermissionService permissionService; + @Autowired + private MyShiroRealm myShiroRealm; + + /*角色列表数据*/ + @PostMapping("/list") + @ResponseBody + public PageResultVo pageRoles(Role role, Integer limit, Integer offset) { + try { + PageHelper.startPage(PageUtil.getPageNo(limit, offset),limit); + List roleList = roleService.selectRoles(role); + PageInfo pages = new PageInfo<>(roleList); + return ResultUtil.table(roleList,pages.getTotal()); + } catch (Exception e) { + logger.error(String.format("RoleController.loadRoles%s", e)); + throw e; + } + + } + + /*新增角色*/ + @PostMapping("/add") + @ResponseBody + public ResponseVo addRole(Role role) { + try { + int a = roleService.insert(role); + if (a > 0) { + return ResultUtil.success("添加角色成功"); + } else { + return ResultUtil.error("添加角色失败"); + } + } catch (Exception e) { + logger.error(String.format("RoleController.addRole%s", e)); + throw e; + } + } + + /*删除角色*/ + @GetMapping("/delete") + @ResponseBody + public ResponseVo deleteRole(String roleId) { + if(roleService.findByRoleId(roleId).size()>0){ + return ResultUtil.error("删除失败,该角色下存在用户"); + } + List roleIdsList = Arrays.asList(roleId); + int a = roleService.updateStatusBatch(roleIdsList, CoreConst.STATUS_INVALID); + if (a > 0) { + return ResultUtil.success("删除角色成功"); + } else { + return ResultUtil.error("删除角色失败"); + } + } + + /*批量删除角色*/ + @GetMapping("/batch/delete") + @ResponseBody + public ResponseVo batchDeleteRole(String roleIdStr) { + String[] roleIds = roleIdStr.split(","); + List roleIdsList = Arrays.asList(roleIds); + if(roleService.findByRoleIds(roleIdsList).size()>0){ + return ResultUtil.error("删除失败,选择的角色下存在用户"); + } + int a = roleService.updateStatusBatch(roleIdsList, CoreConst.STATUS_INVALID); + if (a > 0) { + return ResultUtil.success("删除角色成功"); + } else { + return ResultUtil.error("删除角色失败"); + } + } + + /*编辑角色详情*/ + @GetMapping("/edit") + public String detail(Model model, Integer id) { + Role role = roleService.findById(id); + model.addAttribute("role", role); + return "role/detail"; + } + + /*编辑角色*/ + @PostMapping("/edit") + @ResponseBody + public ResponseVo editRole(@ModelAttribute("role") Role role) { + int a = roleService.updateByRoleId(role); + if (a > 0) { + return ResultUtil.success("编辑角色成功"); + } else { + return ResultUtil.error("编辑角色失败"); + } + } + + /*分配权限列表查询*/ + @PostMapping("/assign/permission/list") + @ResponseBody + public List assignRole(String roleId){ + List listVos = new ArrayList<>(); + List allPermissions = permissionService.selectAll(CoreConst.STATUS_VALID); + List hasPermissions = roleService.findPermissionsByRoleId(roleId); + for(Permission permission : allPermissions){ + PermissionTreeListVo vo = new PermissionTreeListVo(); + vo.setId(permission.getId()); + vo.setPermissionId(permission.getPermissionId()); + vo.setName(permission.getName()); + vo.setParentId(permission.getParentId()); + for(Permission hasPermission:hasPermissions){ + //有权限则选中 + if(hasPermission.getPermissionId().equals(permission.getPermissionId())){ + vo.setChecked(true); + break; + } + } + listVos.add(vo); + } + return listVos; + } + + + /*分配权限*/ + @PostMapping("/assign/permission") + @ResponseBody + public ResponseVo assignRole(String roleId, String permissionIdStr){ + List permissionIdsList = new ArrayList<>(); + if(StringUtils.isNotBlank(permissionIdStr)){ + String[] permissionIds = permissionIdStr.split(","); + permissionIdsList = Arrays.asList(permissionIds); + } + ResponseVo rv = roleService.addAssignPermission(roleId,permissionIdsList); + /*重新加载角色下所有用户权限*/ + List userList = roleService.findByRoleId(roleId); + if(userList.size()>0){ + List userIds = new ArrayList<>(); + for(User user : userList){ + userIds.add(user.getUserId()); + } + myShiroRealm.clearAuthorizationByUserId(userIds); + } + return rv; + } + +} diff --git a/src/main/java/com/nbclass/system/controller/SystemController.java b/src/main/java/com/nbclass/system/controller/SystemController.java new file mode 100644 index 0000000..74ec2a4 --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/SystemController.java @@ -0,0 +1,200 @@ +package com.nbclass.system.controller; + +import java.util.Date; +import java.util.List; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.LockedAccountException; +import org.apache.shiro.subject.Subject; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.nbclass.shiro.config.MyUsernamePasswordToken; +import com.nbclass.system.model.Permission; +import com.nbclass.system.model.SysLog; +import com.nbclass.system.model.User; +import com.nbclass.system.model.enums.SysLogType; +import com.nbclass.system.service.PermissionService; +import com.nbclass.system.service.SysLogService; +import com.nbclass.system.service.UserService; +import com.nbclass.util.CoreConst; +import com.nbclass.util.PasswordHelper; +import com.nbclass.util.ResultUtil; +import com.nbclass.util.UUIDUtil; +import com.nbclass.vo.base.ResponseVo; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Controller +public class SystemController extends BaseController{ + + @Resource + private UserService userService; + @Resource + private PermissionService permissionService; + @Resource + private SysLogService sysLogService; + + /*首页*/ + @RequestMapping(value={"/admin"}) + public String admin(){ + return "index/index"; + } + + /*注册*/ + @GetMapping(value = "/register") + public String register(){ + return "system/register"; + } + + /*提交注册*/ + @PostMapping("/register") + @ResponseBody + public ResponseVo register(HttpServletRequest request, User registerUser, String confirmPassword, String verification){ + if(true)return ResultUtil.error("注册失败,请稍后再试!"); + @SuppressWarnings("unused") + //判断验证码 + /* + * String rightCode = (String) + * request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY); if + * (StringUtils.isNotBlank(verification) && StringUtils.isNotBlank(rightCode) && + * verification.equals(rightCode)) { //验证码通过 } else { return + * ResultUtil.error("验证码错误!"); } + */ + String username = registerUser.getUsername(); + User user = userService.selectByUsername(username); + if (null != user) { + return ResultUtil.error("用户名已存在!"); + } + String password = registerUser.getPassword(); + //判断两次输入密码是否相等 + if (confirmPassword != null && password != null) { + if (!confirmPassword.equals(password)) { + return ResultUtil.error("两次密码不一致!"); + } + } + registerUser.setUserId(UUIDUtil.getUniqueIdByUUId()); + registerUser.setStatus(CoreConst.STATUS_VALID); + Date date = new Date(); + registerUser.setCreateTime(date); + registerUser.setUpdateTime(date); + registerUser.setLastLoginTime(date); + PasswordHelper.encryptPassword(registerUser); + //注册 + int registerResult = userService.register(registerUser); + if(registerResult > 0){ + return ResultUtil.success("注册成功!"); + }else { + return ResultUtil.error("注册失败,请稍后再试!"); + } + } + + /*登陆*/ + @GetMapping("/login") + public String login(){ + return "system/login"; + } + + /*提交登录*/ + @PostMapping("/login") + @ResponseBody + public ResponseVo login(HttpServletRequest request, String username, String password, + String verification, @RequestParam(value = "rememberMe", defaultValue = "0") Integer rememberMe){ + + /* + //判断验证码 + String rightCode = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY); + if (StringUtils.isNotBlank(verification) && StringUtils.isNotBlank(rightCode) && verification.equalsIgnoreCase(rightCode)) { + //验证码通过 + } else { + return ResultUtil.error("验证码错误!"); + } + */ + + MyUsernamePasswordToken token = new MyUsernamePasswordToken(username, password); // 账密登录 + // MyUsernamePasswordToken token = new MyUsernamePasswordToken(username); // 免密登录,用于扫码登录手机验证码登录等场景 + try{ + token.setRememberMe(1 == rememberMe); + Subject subject = SecurityUtils.getSubject(); + subject.login(token); + } catch (LockedAccountException e) { + token.clear(); + return ResultUtil.error("用户已经被锁定不能登录,请联系管理员!"); + } catch (AuthenticationException e) { + token.clear(); + return ResultUtil.error("用户名或者密码错误!"); + } + //更新最后登录时间 + userService.updateLastLoginTime((User) SecurityUtils.getSubject().getPrincipal()); + User user = getLoginUser(); + request.getSession().setAttribute("username",user.getUsername()); + sysLogService.add(new SysLog(SysLogType.login.key(), user.getUserId()+"("+user.getUsername()+")", getIpAddr(), "登录系统!")); + return ResultUtil.success("登录成功!"); + } + + /*踢出*/ + @GetMapping("/kickout") + public String kickout(){ + return "system/kickout"; + } + + /*登出*/ + @RequestMapping(value = "/logout") + @ResponseBody + public ResponseVo logout() { + Subject subject = SecurityUtils.getSubject(); + if(null!=subject){ + /* + String username = ((User) SecurityUtils.getSubject().getPrincipal()).getUsername(); + Serializable sessionId = SecurityUtils.getSubject().getSession().getId(); + Cache> cache = redisCacheManager.getCache(redisCacheManager.getKeyPrefix()+username); + Deque deques = cache.get(username); + for(Serializable deque : deques){ + if(sessionId.equals(deque)){ + deques.remove(deque); + break; + } + } + cache.put(username,deques); + */ + } + subject.logout(); + return ResultUtil.success("退出成功"); + } + + /*获取当前登录用户的菜单*/ + @PostMapping("/menu") + @ResponseBody + public List getMenus(){ + List permissionListList = permissionService.selectMenuByUserId(((User) SecurityUtils.getSubject().getPrincipal()).getUserId()); + return permissionListList; + } + + /*图标*/ + @GetMapping(value = "/icons") + public String getIcons(){ + return "ui/icons"; + } + + @GetMapping(value = "/test") + public String test(){ + return "ui/icons"; + } + + @GetMapping(value = "/test1") + public String test1(){ + return "ui/icons"; + } + +} diff --git a/src/main/java/com/nbclass/system/controller/UserController.java b/src/main/java/com/nbclass/system/controller/UserController.java new file mode 100644 index 0000000..605f4e3 --- /dev/null +++ b/src/main/java/com/nbclass/system/controller/UserController.java @@ -0,0 +1,239 @@ +package com.nbclass.system.controller; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Resource; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.alibaba.fastjson.JSON; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.shiro.MyShiroRealm; +import com.nbclass.system.model.Role; +import com.nbclass.system.model.SysLog; +import com.nbclass.system.model.User; +import com.nbclass.system.model.enums.SysLogType; +import com.nbclass.system.service.RoleService; +import com.nbclass.system.service.SysLogService; +import com.nbclass.system.service.UserService; +import com.nbclass.util.CopyUtil; +import com.nbclass.util.CoreConst; +import com.nbclass.util.PageUtil; +import com.nbclass.util.PasswordHelper; +import com.nbclass.util.ResultUtil; +import com.nbclass.util.UUIDUtil; +import com.nbclass.vo.ChangePasswordVo; +import com.nbclass.vo.base.PageResultVo; +import com.nbclass.vo.base.ResponseVo; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Controller +@RequestMapping("/user") +public class UserController extends BaseController{ + + private Logger logger = LoggerFactory.getLogger(getClass()); + + @Autowired + private MyShiroRealm myShiroRealm; + @Autowired + private UserService userService; + @Autowired + private RoleService roleService; + @Autowired + private MyShiroRealm shiroRealm; + @Resource + private SysLogService sysLogService; + + /**用户列表数据*/ + @PostMapping("/list") + @ResponseBody + public PageResultVo loadUsers(User user, Integer limit, Integer offset){ + PageHelper.startPage(PageUtil.getPageNo(limit, offset),limit); + List userList = userService.selectUsers(user); + PageInfo pages = new PageInfo<>(userList); + return ResultUtil.table(userList,pages.getTotal()); + } + + /**新增用户*/ + @PostMapping("/add") + @ResponseBody + public ResponseVo add(User userForm, String confirmPassword){ + String username = userForm.getUsername(); + User user = userService.selectByUsername(username); + if (null != user) { + return ResultUtil.error("用户名已存在"); + } + String password = userForm.getPassword(); + //判断两次输入密码是否相等 + if (confirmPassword != null && password != null) { + if (!confirmPassword.equals(password)) { + return ResultUtil.error("两次密码不一致"); + } + } + userForm.setUserId(UUIDUtil.getUniqueIdByUUId()); + userForm.setStatus(CoreConst.STATUS_VALID); + Date date = new Date(); + userForm.setCreateTime(date); + userForm.setUpdateTime(date); + userForm.setLastLoginTime(date); + PasswordHelper.encryptPassword(userForm); + int num = userService.register(userForm); + if(num > 0){ + return ResultUtil.success("添加用户成功"); + }else { + return ResultUtil.error("添加用户失败"); + } + } + + /**编辑用户详情*/ + @GetMapping("/edit") + public String userDetail(Model model, String userId){ + User user = userService.selectByUserId(userId); + model.addAttribute("user", user); + return "user/userDetail"; + } + + /**编辑用户*/ + @PostMapping("/edit") + @ResponseBody + public ResponseVo editUser(User userForm){ + int a = userService.updateByUserId(userForm); + if (a > 0) { + return ResultUtil.success("编辑用户成功!"); + } else { + return ResultUtil.error("编辑用户失败"); + } + } + + /**删除用户*/ + @GetMapping("/delete") + @ResponseBody + public ResponseVo deleteUser(String userId) { + List userIdsList = Arrays.asList(userId); + int a = userService.updateStatusBatch(userIdsList,CoreConst.STATUS_INVALID); + if (a > 0) { + return ResultUtil.success("删除用户成功"); + } else { + return ResultUtil.error("删除用户失败"); + } + } + + /**批量删除用户*/ + @GetMapping("/batch/delete") + @ResponseBody + public ResponseVo batchDeleteUser(String userIdStr) { + String[] userIds = userIdStr.split(","); + List userIdsList = Arrays.asList(userIds); + int a = userService.updateStatusBatch(userIdsList,CoreConst.STATUS_INVALID); + if (a > 0) { + return ResultUtil.success("删除用户成功"); + } else { + return ResultUtil.error("删除用户失败"); + } + } + + /**分配角色列表查询*/ + @PostMapping("/assign/role/list") + @ResponseBody + public Map assignRoleList(String userId){ + List roleList = roleService.selectRoles(new Role()); + Set hasRoles = roleService.findRoleByUserId(userId); + Map jsonMap = new HashMap<>(2); + jsonMap.put("rows", roleList); + jsonMap.put("hasRoles",hasRoles); + return jsonMap; + } + + /**保存分配角色*/ + @PostMapping("/assign/role") + @ResponseBody + public ResponseVo assignRole(String userId, String roleIdStr){ + String[] roleIds = roleIdStr.split(","); + List roleIdsList = Arrays.asList(roleIds); + ResponseVo responseVo = userService.addAssignRole(userId,roleIdsList); + List userIds = new ArrayList<>(); + userIds.add(userId); + myShiroRealm.clearAuthorizationByUserId(userIds); + return responseVo; + } + + /*修改密码*/ + @RequestMapping(value = "/changePassword",method = RequestMethod.POST) + @ResponseBody + public ResponseVo changePassword(ChangePasswordVo changePasswordVo) { + if(!changePasswordVo.getNewPassword().equals(changePasswordVo.getConfirmNewPassword())){ + return ResultUtil.error("两次密码输入不一致"); + } + User loginUser = userService.selectByUserId(((User) SecurityUtils.getSubject().getPrincipal()).getUserId()); + User newUser = CopyUtil.getCopy(loginUser,User.class); + String sysOldPassword = loginUser.getPassword(); + newUser.setPassword(changePasswordVo.getOldPassword()); + String entryOldPassword = PasswordHelper.getPassword(newUser); + if(sysOldPassword.equals(entryOldPassword)){ + newUser.setPassword(changePasswordVo.getNewPassword()); + PasswordHelper.encryptPassword(newUser); + newUser.setUpdateTime(new Date()); + userService.updateUserByPrimaryKey(newUser); + //*清除登录缓存*// + List userIds = new ArrayList<>(); + userIds.add(loginUser.getUserId()); + shiroRealm.removeCachedAuthenticationInfo(userIds); + /*SecurityUtils.getSubject().logout();*/ + }else{ + return ResultUtil.error("您输入的旧密码有误"); + } + logger.info("账号[username="+loginUser.getUsername()+"]修改密码, 提交的参数为: "+JSON.toJSONString(changePasswordVo)); + + User user = getLoginUser(); + sysLogService.add(new SysLog(SysLogType.xtgl.key(), user.getUserId()+"("+user.getUsername()+")", getIpAddr(), "修改密码")); + return ResultUtil.success("修改密码成功"); + } + + /*管理员重置密码*/ + @PostMapping("/resetPwd") + @ResponseBody + public ResponseVo resetPassword(String userid, String password) { + if(StringUtils.isBlank(userid)){ + return ResultUtil.error("用户不能为空"); + } + if(StringUtils.isBlank(password)){ + return ResultUtil.error("密码不能为空"); + } + User dbUser = userService.selectByUserId(userid); + User newUser = new User(); + newUser.setId(dbUser.getId()); + newUser.setUserId(dbUser.getUserId()); + newUser.setUsername(dbUser.getUsername()); + newUser.setStatus(dbUser.getStatus()); + newUser.setPassword(password); + PasswordHelper.encryptPassword(newUser); + newUser.setUpdateTime(new Date()); + userService.updateUserByPrimaryKey(newUser); + logger.info("管理员重置密码[username="+dbUser.getUsername()+"], 提交的参数为: "+password); + User user = getLoginUser(); + sysLogService.add(new SysLog(SysLogType.xtgl.key(), user.getUserId()+"("+user.getUsername()+")", getIpAddr(), "管理员重置密码,账号:"+userid+"("+dbUser.getUsername()+")")); + return ResultUtil.success("修改密码成功"); + } + +} diff --git a/src/main/java/com/nbclass/system/mapper/PermissionMapper.java b/src/main/java/com/nbclass/system/mapper/PermissionMapper.java new file mode 100644 index 0000000..b9b2089 --- /dev/null +++ b/src/main/java/com/nbclass/system/mapper/PermissionMapper.java @@ -0,0 +1,86 @@ +package com.nbclass.system.mapper; + +import com.nbclass.system.model.Permission; +import com.nbclass.util.MyMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Set; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface PermissionMapper extends MyMapper { + + /** + * 根据状态查询全部资源 + * @param status 状态 + * @return the list + */ + List selectAllPerms(Integer status); + + /** + * 根据状态查询全部菜单 + * @param status 状态 + * @return the list + */ + List selectAllMenuName(Integer status); + + /** + * 根据用户id查询权限集合 + * @param userId 状态 + * @return set + */ + Set findPermsByUserId(String userId); + + /** + * 根据角色id查询权限 + * @param id 角色id + * @return the list + */ + List findByRoleId(String id); + + /** + * 根据用户id查询权限 + * @param userId 用户id + * @return the list + */ + List selectByUserId(String userId); + + /** + * 根据用户id查询菜单 + * @param userId 用户id + * @return the list + */ + List selectMenuByUserId(String userId); + + /** + * 根据权限id修改状态 + * @param permissionId 权限id + * @param status 状态 + * @return int + */ + int updateStatusByPermissionId(@Param("permissionId") String permissionId, @Param("status") Integer status); + + /** + * 根据权限id查询权限 + * @param permissionId 权限id + * @return permission + */ + Permission selectByPermissionId(String permissionId); + + /** + * 根据权限bean修改权限 + * @param permission 权限 + * @return int + */ + int updateByPermissionId(Permission permission); + + /** + * 根据权限id查询有几个子资源 + * @param permissionId 权限id + * @return int + */ + int selectSubPermsByPermissionId(String permissionId); +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/mapper/RoleMapper.java b/src/main/java/com/nbclass/system/mapper/RoleMapper.java new file mode 100644 index 0000000..0cc5a46 --- /dev/null +++ b/src/main/java/com/nbclass/system/mapper/RoleMapper.java @@ -0,0 +1,45 @@ +package com.nbclass.system.mapper; + +import com.nbclass.system.model.Role; +import com.nbclass.util.MyMapper; + +import java.util.List; +import java.util.Map; +import java.util.Set; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface RoleMapper extends MyMapper { + /** + * 根据用户id查询角色集合 + * @param userId 用户id + * @return set + */ + Set findRoleByUserId(String userId); + + /** + * 根据role参数查询角色列表 + * @param role role + * @return list + */ + List selectRoles(Role role); + + /** + * 根据参数批量更新状态 + * @param params + * @return int + */ + int updateStatusBatch(Map params); + + /** + * 根据roleId更新角色信息 + * @param params + * @return int + */ + int updateByRoleId(Map params); + + + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/mapper/RolePermissionMapper.java b/src/main/java/com/nbclass/system/mapper/RolePermissionMapper.java new file mode 100644 index 0000000..d6fb837 --- /dev/null +++ b/src/main/java/com/nbclass/system/mapper/RolePermissionMapper.java @@ -0,0 +1,11 @@ +package com.nbclass.system.mapper; + +import com.nbclass.system.model.RolePermission; +import com.nbclass.util.MyMapper; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface RolePermissionMapper extends MyMapper { +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/mapper/SysLogMapper.java b/src/main/java/com/nbclass/system/mapper/SysLogMapper.java new file mode 100644 index 0000000..bb1e60b --- /dev/null +++ b/src/main/java/com/nbclass/system/mapper/SysLogMapper.java @@ -0,0 +1,23 @@ +package com.nbclass.system.mapper; + +import java.util.List; + +import com.nbclass.system.model.SysLog; +import com.nbclass.util.MyMapper; + +public interface SysLogMapper extends MyMapper { + + /** + * 根据id批量删除记录 + * @param list + */ + public void deleteByIds(List list); + + /** + * 根据条件查询列表 + * @param entity + * @return + */ + public List getList(SysLog entity); + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/mapper/UserMapper.java b/src/main/java/com/nbclass/system/mapper/UserMapper.java new file mode 100644 index 0000000..b196f54 --- /dev/null +++ b/src/main/java/com/nbclass/system/mapper/UserMapper.java @@ -0,0 +1,68 @@ +package com.nbclass.system.mapper; + +import com.nbclass.system.model.User; +import com.nbclass.util.MyMapper; + +import java.util.List; +import java.util.Map; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface UserMapper extends MyMapper { + /** + * 根据user参数查询用户列表 + * @param user + * @return list + */ + List selectUsers(User user); + + /** + * 根据用户名查询用户 + * @param username + * @return user + */ + User selectByUsername(String username); + + /** + * 根据用户ID查询用户 + * @param userId + * @return user + */ + User selectByUserId(String userId); + + /** + * 更新最后登录时间 + * @param user + */ + void updateLastLoginTime(User user); + + /** + * 根据用户id更新用户信息 + * @param user + * @return int + */ + int updateByUserId(User user); + + /** + * 根据参数批量修改用户状态 + * @param params + * @return int + */ + int updateStatusBatch(Map params); + + /** + * 根据角色id查询用户list + * @param roleId + * @return list + */ + List findByRoleId(String roleId); + + /** + * 根据角色id查询用户list + * @param roleIds + * @return list + */ + List findByRoleIds(List roleIds); +} diff --git a/src/main/java/com/nbclass/system/mapper/UserRoleMapper.java b/src/main/java/com/nbclass/system/mapper/UserRoleMapper.java new file mode 100644 index 0000000..7bc2e8a --- /dev/null +++ b/src/main/java/com/nbclass/system/mapper/UserRoleMapper.java @@ -0,0 +1,11 @@ +package com.nbclass.system.mapper; + +import com.nbclass.system.model.UserRole; +import com.nbclass.util.MyMapper; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface UserRoleMapper extends MyMapper { +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/model/ExcelColumn.java b/src/main/java/com/nbclass/system/model/ExcelColumn.java new file mode 100644 index 0000000..9387633 --- /dev/null +++ b/src/main/java/com/nbclass/system/model/ExcelColumn.java @@ -0,0 +1,35 @@ +package com.nbclass.system.model; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义实体类所需要的bean(Excel属性标题、位置等) + * @author Leon + * @datetime 2019年6月24日 下午3:07:48 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface ExcelColumn { + + /** + * Excel标题 + * + * @return + * @author Lynch + */ + String value() default ""; + + /** + * Excel从左往右排列位置 + * + * @return + * @author Lynch + */ + int col() default 0; + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/model/Permission.java b/src/main/java/com/nbclass/system/model/Permission.java new file mode 100644 index 0000000..f5ecd04 --- /dev/null +++ b/src/main/java/com/nbclass/system/model/Permission.java @@ -0,0 +1,252 @@ +package com.nbclass.system.model; + +import java.io.Serializable; +import java.util.Date; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class Permission implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 权限id + */ + private String permissionId; + + /** + * 权限名称 + */ + private String name; + + /** + * 权限描述 + */ + private String description; + + /** + * 权限访问路径 + */ + private String url; + + /** + * 权限标识 + */ + private String perms; + + /** + * 父级权限id + */ + private Integer parentId; + + /** + * 类型 0:目录 1:菜单 2:按钮 + */ + private Integer type; + + /** + * 排序 + */ + private Integer orderNum; + + /** + * 图标 + */ + private String icon; + /** + * 状态:1有效; 0无效 + */ + private Integer status; + + private Date createTime; + + private Date updateTime; + + /** + * @return id + */ + public Integer getId() { + return id; + } + + /** + * @param id + */ + public void setId(Integer id) { + this.id = id; + } + + /** + * 获取权限id + * + * @return permission_id - 权限id + */ + public String getPermissionId() { + return permissionId; + } + + /** + * 设置权限id + * + * @param permissionId 权限id + */ + public void setPermissionId(String permissionId) { + this.permissionId = permissionId == null ? null : permissionId.trim(); + } + + /** + * 获取权限名称 + * + * @return name - 权限名称 + */ + public String getName() { + return name; + } + + /** + * 设置权限名称 + * + * @param name 权限名称 + */ + public void setName(String name) { + this.name = name == null ? null : name.trim(); + } + + /** + * 获取权限描述 + * + * @return description - 权限描述 + */ + public String getDescription() { + return description; + } + + /** + * 设置权限描述 + * + * @param description 权限描述 + */ + public void setDescription(String description) { + this.description = description == null ? null : description.trim(); + } + + /** + * 获取权限访问路径 + * + * @return url - 权限访问路径 + */ + public String getUrl() { + return url; + } + + /** + * 设置权限访问路径 + * + * @param url 权限访问路径 + */ + public void setUrl(String url) { + this.url = url == null ? null : url.trim(); + } + + public String getPerms() { + return perms; + } + + public void setPerms(String perms) { + this.perms = perms; + } + + /** + * 获取父级权限id + * + * @return parent_id - 父级权限id + */ + public Integer getParentId() { + return parentId; + } + + /** + * 设置父级权限id + * + * @param parentId 父级权限id + */ + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Integer getOrderNum() { + return orderNum; + } + + public void setOrderNum(Integer orderNum) { + this.orderNum = orderNum; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + /** + * 获取状态:1有效;2删除 + * + * @return status - 状态:1有效;2删除 + */ + public Integer getStatus() { + return status; + } + + /** + * 设置状态:1有效;2删除 + * + * @param status 状态:1有效;2删除 + */ + public void setStatus(Integer status) { + this.status = status; + } + + /** + * @return create_time + */ + public Date getCreateTime() { + return createTime; + } + + /** + * @param createTime + */ + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/model/Role.java b/src/main/java/com/nbclass/system/model/Role.java new file mode 100644 index 0000000..fd209b8 --- /dev/null +++ b/src/main/java/com/nbclass/system/model/Role.java @@ -0,0 +1,175 @@ +package com.nbclass.system.model; + +import java.io.Serializable; +import java.util.Date; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class Role implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 角色id + */ + private String roleId; + + /** + * 角色名称 + */ + private String name; + + /** + * 角色描述 + */ + private String description; + + /** + * 状态:1有效; 0无效 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * @return id + */ + public Integer getId() { + return id; + } + + /** + * @param id + */ + public void setId(Integer id) { + this.id = id; + } + + /** + * 获取角色id + * + * @return role_id - 角色id + */ + public String getRoleId() { + return roleId; + } + + /** + * 设置角色id + * + * @param roleId 角色id + */ + public void setRoleId(String roleId) { + this.roleId = roleId == null ? null : roleId.trim(); + } + + /** + * 获取角色名称 + * + * @return name - 角色名称 + */ + public String getName() { + return name; + } + + /** + * 设置角色名称 + * + * @param name 角色名称 + */ + public void setName(String name) { + this.name = name == null ? null : name.trim(); + } + + /** + * 获取角色描述 + * + * @return description - 角色描述 + */ + public String getDescription() { + return description; + } + + /** + * 设置角色描述 + * + * @param description 角色描述 + */ + public void setDescription(String description) { + this.description = description == null ? null : description.trim(); + } + + /** + * 获取状态:1有效;2删除 + * + * @return status - 状态:1有效;2删除 + */ + public Integer getStatus() { + return status; + } + + /** + * 设置状态:1有效;2删除 + * + * @param status 状态:1有效;2删除 + */ + public void setStatus(Integer status) { + this.status = status; + } + + /** + * 获取创建时间 + * + * @return create_time - 创建时间 + */ + public Date getCreateTime() { + return createTime; + } + + /** + * 设置创建时间 + * + * @param createTime 创建时间 + */ + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + /** + * 获取更新时间 + * + * @return update_time - 更新时间 + */ + public Date getUpdateTime() { + return updateTime; + } + + /** + * 设置更新时间 + * + * @param updateTime 更新时间 + */ + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/model/RolePermission.java b/src/main/java/com/nbclass/system/model/RolePermission.java new file mode 100644 index 0000000..f8b8b7b --- /dev/null +++ b/src/main/java/com/nbclass/system/model/RolePermission.java @@ -0,0 +1,82 @@ +package com.nbclass.system.model; + +import java.io.Serializable; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class RolePermission implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 角色id + */ + private String roleId; + + /** + * 权限id + */ + private String permissionId; + + /** + * @return id + */ + public Integer getId() { + return id; + } + + /** + * @param id + */ + public void setId(Integer id) { + this.id = id; + } + + /** + * 获取角色id + * + * @return role_id - 角色id + */ + public String getRoleId() { + return roleId; + } + + /** + * 设置角色id + * + * @param roleId 角色id + */ + public void setRoleId(String roleId) { + this.roleId = roleId == null ? null : roleId.trim(); + } + + /** + * 获取权限id + * + * @return permission_id - 权限id + */ + public String getPermissionId() { + return permissionId; + } + + /** + * 设置权限id + * + * @param permissionId 权限id + */ + public void setPermissionId(String permissionId) { + this.permissionId = permissionId == null ? null : permissionId.trim(); + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/model/SysLog.java b/src/main/java/com/nbclass/system/model/SysLog.java new file mode 100644 index 0000000..1f62b93 --- /dev/null +++ b/src/main/java/com/nbclass/system/model/SysLog.java @@ -0,0 +1,67 @@ +package com.nbclass.system.model; + +import java.util.Date; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; + +import lombok.Data; + +/** + * + * @author Leon + * @datetime 2020年6月3日 下午5:15:38 + */ +@Data +@Table(name = "sys_log") +public class SysLog { + + public SysLog() { + + } + + public SysLog(int type, String operator, String ipaddr, String desc) { + this.type = type; + this.operator = operator; + this.ipaddr = ipaddr; + this.description = desc; + } + + /** + * ID, 主键,自增 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + private Date createtime; + + /** + * 日志类型(1:系统管理;2:登录日志;3:字典管理;4:案例管理) + */ + private Integer type; + @Transient + private String typeName; + + /** + * 操作者 + */ + private String operator; + + /** + * IP地址 + */ + private String ipaddr; + + /** + * 描述 + */ + private String description; + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/model/User.java b/src/main/java/com/nbclass/system/model/User.java new file mode 100644 index 0000000..3194ab8 --- /dev/null +++ b/src/main/java/com/nbclass/system/model/User.java @@ -0,0 +1,359 @@ +package com.nbclass.system.model; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Transient; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class User implements Serializable{ + private static final long serialVersionUID = -8736616045315083846L; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 用户id + */ + private String userId; + + /** + * 用户名 + */ + private String username; + + private String password; + + /** + * 加密盐值 + */ + private String salt; + + /** + * 邮箱 + */ + private String email; + + /** + * 联系方式 + */ + private String phone; + + /** + * 年龄:1男2女 + */ + private Integer sex; + + /** + * 年龄 + */ + private Integer age; + + /** + * 用户状态:1有效; 0无效 + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 最后登录时间 + */ + private Date lastLoginTime; + + /** + * 登录ip + */ + @Transient + private String loginIpAddress; + + /** + * 角色 + */ + @Transient + private List roles; + + /** + * 角色名称 + */ + @Transient + private String rolename; + + /** + * @return id + */ + public Integer getId() { + return id; + } + + /** + * @param id + */ + public void setId(Integer id) { + this.id = id; + } + + /** + * 获取用户id + * + * @return user_id - 用户id + */ + public String getUserId() { + return userId; + } + + /** + * 设置用户id + * + * @param userId 用户id + */ + public void setUserId(String userId) { + this.userId = userId == null ? null : userId.trim(); + } + + /** + * 获取用户名 + * + * @return username - 用户名 + */ + public String getUsername() { + return username; + } + + /** + * 设置用户名 + * + * @param username 用户名 + */ + public void setUsername(String username) { + this.username = username == null ? null : username.trim(); + } + + /** + * @return password + */ + public String getPassword() { + return password; + } + + /** + * @param password + */ + public void setPassword(String password) { + this.password = password == null ? null : password.trim(); + } + + /** + * + * 重写获取盐值方法,自定义realm使用 + * Gets credentials salt. + * + * @return the credentials salt + */ + public String getCredentialsSalt() { + return username + "com.github.cnjd" + salt; + } + + + /** + * 获取加密盐值 + * + * @return salt - 加密盐值 + */ + public String getSalt() { + return salt; + } + + /** + * 设置加密盐值 + * + * @param salt 加密盐值 + */ + public void setSalt(String salt) { + this.salt = salt == null ? null : salt.trim(); + } + + /** + * 获取邮箱 + * + * @return email - 邮箱 + */ + public String getEmail() { + return email; + } + + /** + * 设置邮箱 + * + * @param email 邮箱 + */ + public void setEmail(String email) { + this.email = email == null ? null : email.trim(); + } + + /** + * 获取联系方式 + * + * @return phone - 联系方式 + */ + public String getPhone() { + return phone; + } + + /** + * 设置联系方式 + * + * @param phone 联系方式 + */ + public void setPhone(String phone) { + this.phone = phone == null ? null : phone.trim(); + } + + /** + * 获取年龄:1男2女 + * + * @return sex - 年龄:1男2女 + */ + public Integer getSex() { + return sex; + } + + /** + * 设置年龄:1男2女 + * + * @param sex 年龄:1男2女 + */ + public void setSex(Integer sex) { + this.sex = sex; + } + + /** + * 获取年龄 + * + * @return age - 年龄 + */ + public Integer getAge() { + return age; + } + + /** + * 设置年龄 + * + * @param age 年龄 + */ + public void setAge(Integer age) { + this.age = age; + } + + /** + * 获取用户状态:1有效; 2删除 + * + * @return status - 用户状态:1有效; 2删除 + */ + public Integer getStatus() { + return status; + } + + /** + * 设置用户状态:1有效; 2删除 + * + * @param status 用户状态:1有效; 2删除 + */ + public void setStatus(Integer status) { + this.status = status; + } + + /** + * 获取创建时间 + * + * @return create_time - 创建时间 + */ + public Date getCreateTime() { + return createTime; + } + + /** + * 设置创建时间 + * + * @param createTime 创建时间 + */ + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + /** + * 获取更新时间 + * + * @return update_time - 更新时间 + */ + public Date getUpdateTime() { + return updateTime; + } + + /** + * 设置更新时间 + * + * @param updateTime 更新时间 + */ + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + /** + * 获取最后登录时间 + * + * @return last_login_time - 最后登录时间 + */ + public Date getLastLoginTime() { + return lastLoginTime; + } + + /** + * 设置最后登录时间 + * + * @param lastLoginTime 最后登录时间 + */ + public void setLastLoginTime(Date lastLoginTime) { + this.lastLoginTime = lastLoginTime; + } + + public String getLoginIpAddress() { + return loginIpAddress; + } + + public void setLoginIpAddress(String loginIpAddress) { + this.loginIpAddress = loginIpAddress; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public String getRolename() { + return rolename; + } + + public void setRolename(String rolename) { + this.rolename = rolename; + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/model/UserRole.java b/src/main/java/com/nbclass/system/model/UserRole.java new file mode 100644 index 0000000..3bc4074 --- /dev/null +++ b/src/main/java/com/nbclass/system/model/UserRole.java @@ -0,0 +1,82 @@ +package com.nbclass.system.model; + +import java.io.Serializable; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class UserRole implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 用户id + */ + private String userId; + + /** + * 角色id + */ + private String roleId; + + /** + * @return id + */ + public Integer getId() { + return id; + } + + /** + * @param id + */ + public void setId(Integer id) { + this.id = id; + } + + /** + * 获取用户id + * + * @return user_id - 用户id + */ + public String getUserId() { + return userId; + } + + /** + * 设置用户id + * + * @param userId 用户id + */ + public void setUserId(String userId) { + this.userId = userId == null ? null : userId.trim(); + } + + /** + * 获取角色id + * + * @return role_id - 角色id + */ + public String getRoleId() { + return roleId; + } + + /** + * 设置角色id + * + * @param roleId 角色id + */ + public void setRoleId(String roleId) { + this.roleId = roleId == null ? null : roleId.trim(); + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/system/model/enums/SysLogType.java b/src/main/java/com/nbclass/system/model/enums/SysLogType.java new file mode 100644 index 0000000..5f03dba --- /dev/null +++ b/src/main/java/com/nbclass/system/model/enums/SysLogType.java @@ -0,0 +1,48 @@ +package com.nbclass.system.model.enums; + +/** + * i系统日志类型枚举 + * @author Leon + * @datetime 2020年6月3日 下午5:34:36 + */ +public enum SysLogType { + + xtgl(1, "系统管理"), + + login(2, "登录日志"), + + dict(3, "字典管理"), + + cases(4, "案例管理") + ; + + // 枚举值 + private final int key; + + // 枚举描述 + private final String desc; + + + SysLogType(final int key, final String desc ) { + this.key = key; + this.desc = desc; + } + + public int key() { + return key; + } + + public String desc() { + return desc; + } + + public static String getNameByCode(int key){ + for(SysLogType entity : SysLogType.values()){ + if(key == entity.key){ + return entity.desc; + } + } + return null; + } + +} diff --git a/src/main/java/com/nbclass/system/service/PermissionService.java b/src/main/java/com/nbclass/system/service/PermissionService.java new file mode 100644 index 0000000..a45c3ff --- /dev/null +++ b/src/main/java/com/nbclass/system/service/PermissionService.java @@ -0,0 +1,84 @@ +package com.nbclass.system.service; + +import java.util.List; +import java.util.Set; + +import com.nbclass.system.model.Permission; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface PermissionService { + + /** + * 根据用户id查询权限集合 + * @param userId + * @return set + */ + Set findPermsByUserId(String userId); + + /** + * 查询全部权限 + * @param status + * @return list + */ + List selectAll(Integer status); + + /** + * 查询全部菜单 + * @param status + * @return list + */ + List selectAllMenuName(Integer status); + + /** + * 根据用户id查询权限集合 + * @param userId + * @return list + */ + List selectMenuByUserId(String userId); + + /** + * 插入权限 + * @param permission + * @return int + */ + int insert(Permission permission); + + /** + * 根据权限id更新状态 + * @param permissionId + * @param status + * @return int + */ + int updateStatus(String permissionId, Integer status); + + /** + * 根据权限id查询权限 + * @param permissionId + * @return permission + */ + Permission findByPermissionId(String permissionId); + + /** + * 根据id查询权限 + * @param id + * @return permission + */ + Permission findById(Integer id); + + /** + * 更新权限 + * @param permission + * @return int + */ + int updateByPermissionId(Permission permission); + + /** + * 查询子权限条数 + * @param permissionId + * @return int + */ + int selectSubPermsByPermissionId(String permissionId); +} diff --git a/src/main/java/com/nbclass/system/service/RoleService.java b/src/main/java/com/nbclass/system/service/RoleService.java new file mode 100644 index 0000000..8b3a015 --- /dev/null +++ b/src/main/java/com/nbclass/system/service/RoleService.java @@ -0,0 +1,91 @@ +package com.nbclass.system.service; + +import java.util.List; +import java.util.Set; + +import com.nbclass.system.model.Permission; +import com.nbclass.system.model.Role; +import com.nbclass.system.model.User; +import com.nbclass.vo.base.ResponseVo; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface RoleService { + + /** + * 根据用户id查询角色集合 + * @param userId + * @return set + */ + Set findRoleByUserId(String userId); + + /** + * 根据条件查询角色列表 + * @param role + * @return list + */ + List selectRoles(Role role); + + /** + * 插入角色 + * @param role + * @return int + */ + int insert(Role role); + + /** + * 批量更新状态 + * @param roleIds + * @param status + * @return int + */ + int updateStatusBatch(List roleIds, Integer status); + + /** + * 根据id查询角色 + * @param id + * @return role + */ + Role findById(Integer id); + + /** + * 根据角色id更新角色信息 + * @param role + * @return int + */ + int updateByRoleId(Role role); + + /** + * 根据角色id查询权限集合 + * @param roleId + * @return list + */ + List findPermissionsByRoleId(String roleId); + + /** + * 根据角色id保存分配权限 + * @param roleId + * @param permissionIdsList + * @return list + */ + ResponseVo addAssignPermission(String roleId, List permissionIdsList); + + /** + * 根据角色id下的所有用户 + * @param roleId + * @return list + */ + List findByRoleId(String roleId); + + /** + * 根据角色id下的所有用户 + * @param roleIds + * @return list + */ + List findByRoleIds(List roleIds); + + +} diff --git a/src/main/java/com/nbclass/system/service/SysLogService.java b/src/main/java/com/nbclass/system/service/SysLogService.java new file mode 100644 index 0000000..e91d9e5 --- /dev/null +++ b/src/main/java/com/nbclass/system/service/SysLogService.java @@ -0,0 +1,34 @@ +package com.nbclass.system.service; + +import java.util.List; + +import com.nbclass.system.model.SysLog; + +/** + * 系统日志Service + * @author Leon + * @datetime 2020年6月3日 下午5:17:36 + */ +public interface SysLogService { + + + /** + * 添加 + * @param entity + */ + public void add(SysLog entity); + + /** + * 删除 + * @param ids + */ + public void delete(List ids); + + /** + * 根据条件查询列表 + * @param entity + * @return + */ + public List getList(SysLog entity); + +} diff --git a/src/main/java/com/nbclass/system/service/UserService.java b/src/main/java/com/nbclass/system/service/UserService.java new file mode 100644 index 0000000..4518f9c --- /dev/null +++ b/src/main/java/com/nbclass/system/service/UserService.java @@ -0,0 +1,101 @@ +package com.nbclass.system.service; + +import java.io.Serializable; +import java.util.List; + +import com.nbclass.activity.model.Application; +import com.nbclass.system.model.User; +import com.nbclass.vo.UserOnlineVo; +import com.nbclass.vo.base.ResponseVo; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface UserService { + + /** + * 根据用户名查询用户 + * @param username + * @return user + */ + User selectByUsername(String username); + + /** + * 注册用户 + * @param user + * @return int + */ + int register(User user); + + /** + * 更新最后登录时间 + * @param user + */ + void updateLastLoginTime(User user); + + /** + * 根据条件查询用户列表 + * @param user + * @return list + */ + List selectUsers(User user); + + /** + * 根据用户id查询用户 + * @param userId + * @return user + */ + User selectByUserId(String userId); + + /** + * 根据用户id更新用户信息 + * @param user + * @return int + */ + int updateByUserId(User user); + + /** + * 根据用户id集合批量更新用户状态 + * @param userIds + * @param status + * @return int + */ + int updateStatusBatch(List userIds, Integer status); + + /** + * 根据用户id分配角色集合 + * @param userId + * @param roleIds + * @return int + */ + ResponseVo addAssignRole(String userId, List roleIds); + + /** + * 根据主键更新用户信息 + * @param user + * @return int + */ + int updateUserByPrimaryKey(User user); + + /** + * 查询在线用户 + * @param userOnlineVo + * @return list + */ + List selectOnlineUsers(UserOnlineVo userOnlineVo); + + /** + * 踢出用户 + * @param sessionId 会话id + * @param username 用户名 + */ + void kickout(Serializable sessionId, String username); + + /** + * 修改企业微信用户信息 + * @param user + */ + public void updateQyWxUser(User user); + +} diff --git a/src/main/java/com/nbclass/system/service/impl/PermissionServiceImpl.java b/src/main/java/com/nbclass/system/service/impl/PermissionServiceImpl.java new file mode 100644 index 0000000..b0fe941 --- /dev/null +++ b/src/main/java/com/nbclass/system/service/impl/PermissionServiceImpl.java @@ -0,0 +1,80 @@ +package com.nbclass.system.service.impl; + +import com.nbclass.system.mapper.PermissionMapper; +import com.nbclass.system.model.Permission; +import com.nbclass.system.service.PermissionService; +import com.nbclass.util.CoreConst; +import com.nbclass.util.UUIDUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; +import java.util.Set; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Service +public class PermissionServiceImpl implements PermissionService { + @Autowired + private PermissionMapper permissionMapper; + + @Override + public Set findPermsByUserId(String userId) { + return permissionMapper.findPermsByUserId(userId); + } + + @Override + public List selectAll(Integer status) { + return permissionMapper.selectAllPerms(status); + } + + @Override + public List selectAllMenuName(Integer status) { + return permissionMapper.selectAllMenuName(status); + } + + @Override + public List selectMenuByUserId(String userId) { + return permissionMapper.selectMenuByUserId(userId); + } + + @Override + public int insert(Permission permission) { + Date date = new Date(); + permission.setPermissionId(UUIDUtil.getUniqueIdByUUId()); + permission.setStatus(CoreConst.STATUS_VALID); + permission.setCreateTime(date); + permission.setUpdateTime(date); + return permissionMapper.insert(permission); + } + + @Override + public int updateStatus(String permissionId,Integer status) { + return permissionMapper.updateStatusByPermissionId(permissionId,status); + } + + @Override + public Permission findByPermissionId(String permissionId) { + return permissionMapper.selectByPermissionId(permissionId); + } + + @Override + public Permission findById(Integer id) { + Permission permission = new Permission(); + permission.setId(id); + return permissionMapper.selectByPrimaryKey(permission); + } + + @Override + public int updateByPermissionId(Permission permission) { + return permissionMapper.updateByPermissionId(permission); + } + + @Override + public int selectSubPermsByPermissionId(String permissionId) { + return permissionMapper.selectSubPermsByPermissionId(permissionId); + } +} diff --git a/src/main/java/com/nbclass/system/service/impl/RoleServiceImpl.java b/src/main/java/com/nbclass/system/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..31f3407 --- /dev/null +++ b/src/main/java/com/nbclass/system/service/impl/RoleServiceImpl.java @@ -0,0 +1,116 @@ +package com.nbclass.system.service.impl; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.nbclass.system.mapper.PermissionMapper; +import com.nbclass.system.mapper.RoleMapper; +import com.nbclass.system.mapper.RolePermissionMapper; +import com.nbclass.system.mapper.UserMapper; +import com.nbclass.system.model.Permission; +import com.nbclass.system.model.Role; +import com.nbclass.system.model.RolePermission; +import com.nbclass.system.model.User; +import com.nbclass.system.service.RoleService; +import com.nbclass.util.ResultUtil; +import com.nbclass.util.UUIDUtil; +import com.nbclass.vo.base.ResponseVo; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Service +public class RoleServiceImpl implements RoleService { + + @Autowired + private RoleMapper roleMapper; + @Autowired + private PermissionMapper permissionMapper; + @Autowired + private RolePermissionMapper rolePermissionMapper; + @Autowired + private UserMapper userMapper; + + @Override + public Set findRoleByUserId(String userId) { + return roleMapper.findRoleByUserId(userId); + } + + @Override + public List selectRoles(Role role) { + return roleMapper.selectRoles(role); + } + + @Override + public int insert(Role role) { + role.setRoleId(UUIDUtil.getUniqueIdByUUId()); + role.setStatus(1); + role.setCreateTime(new Date()); + return roleMapper.insert(role); + } + + @Override + public int updateStatusBatch(List roleIds, Integer status) { + Map params = new HashMap(2); + params.put("roleIds",roleIds); + params.put("status",status); + return roleMapper.updateStatusBatch(params); + } + + @Override + public Role findById(Integer id) { + Role role = new Role(); + role.setId(id); + return roleMapper.selectByPrimaryKey(role); + } + + @Override + public int updateByRoleId(Role role) { + Map params = new HashMap<>(3); + params.put("name",role.getName()); + params.put("description",role.getDescription()); + params.put("role_id",role.getRoleId()); + return roleMapper.updateByRoleId(params); + } + + @Override + public List findPermissionsByRoleId(String roleId) { + return permissionMapper.findByRoleId(roleId); + } + + @Override + public ResponseVo addAssignPermission(String roleId, List permissionIds) { + try{ + RolePermission rolePermission = new RolePermission(); + rolePermission.setRoleId(roleId); + rolePermissionMapper.delete(rolePermission); + for(String permissionId : permissionIds){ + rolePermission.setId(null); + rolePermission.setPermissionId(permissionId); + rolePermissionMapper.insert(rolePermission); + } + return ResultUtil.success("分配权限成功"); + }catch(Exception e){ + return ResultUtil.error("分配权限失败"); + } + } + + @Override + public List findByRoleId(String roleId) { + return userMapper.findByRoleId(roleId); + } + + @Override + public List findByRoleIds(List roleIds) { + return userMapper.findByRoleIds(roleIds); + } + +} diff --git a/src/main/java/com/nbclass/system/service/impl/SysLogServiceImpl.java b/src/main/java/com/nbclass/system/service/impl/SysLogServiceImpl.java new file mode 100644 index 0000000..14967f1 --- /dev/null +++ b/src/main/java/com/nbclass/system/service/impl/SysLogServiceImpl.java @@ -0,0 +1,57 @@ +package com.nbclass.system.service.impl; + +import java.util.List; + +import javax.annotation.Resource; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import com.nbclass.exception.ParameterException; +import com.nbclass.system.mapper.SysLogMapper; +import com.nbclass.system.model.SysLog; +import com.nbclass.system.model.enums.SysLogType; +import com.nbclass.system.service.SysLogService; + +/** + * + * @author Leon + * @datetime 2020年6月3日 下午5:18:54 + */ +@Service +public class SysLogServiceImpl implements SysLogService { + + @Resource + private SysLogMapper mapper; + + @Override + public void add(SysLog entity) { + /*if(entity.getType() == null || entity.getType() < 1) { + throw new ParameterException("type不能为空"); + }*/ + if(StringUtils.isBlank(entity.getDescription())) { + throw new ParameterException("描述不能为空"); + } + mapper.insertSelective(entity); + } + + @Override + public void delete(List ids) { + if(ids == null || ids.isEmpty()){ + throw new ParameterException("id不能为空"); + } + mapper.deleteByIds(ids); + } + + @Override + public List getList(SysLog entity) { + List list = mapper.getList(entity); + if(list!=null && !list.isEmpty()) { + for (SysLog sysLog : list) { + sysLog.setTypeName(SysLogType.getNameByCode(sysLog.getType())); + } + } + return list; + } + +} diff --git a/src/main/java/com/nbclass/system/service/impl/UserServiceImpl.java b/src/main/java/com/nbclass/system/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..e0399cf --- /dev/null +++ b/src/main/java/com/nbclass/system/service/impl/UserServiceImpl.java @@ -0,0 +1,231 @@ +package com.nbclass.system.service.impl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.session.Session; +import org.apache.shiro.subject.SimplePrincipalCollection; +import org.apache.shiro.subject.support.DefaultSubjectContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.nbclass.exception.ParameterException; +import com.nbclass.system.mapper.UserMapper; +import com.nbclass.system.mapper.UserRoleMapper; +import com.nbclass.system.model.User; +import com.nbclass.system.model.UserRole; +import com.nbclass.system.service.UserService; +import com.nbclass.util.ResultUtil; +import com.nbclass.vo.UserOnlineVo; +import com.nbclass.vo.base.ResponseVo; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Service +public class UserServiceImpl implements UserService { + + /* + @Autowired + private RedisSessionDAO redisSessionDAO; + + @Autowired + private SessionManager sessionManager; + + @Autowired + private RedisCacheManager redisCacheManager; + */ + + + @Autowired + private UserMapper userMapper; + @Autowired + private UserRoleMapper userRoleMapper; + + @Override + public User selectByUsername(String username) { + return userMapper.selectByUsername(username); + } + + @Override + public int register(User user) { + int a = userMapper.insert(user); + return a; + } + + @Override + public void updateLastLoginTime(User user) { + userMapper.updateLastLoginTime(user); + } + + @Override + public List selectUsers(User user) { + return userMapper.selectUsers(user); + } + + @Override + public User selectByUserId(String userId) { + return userMapper.selectByUserId(userId); + } + + @Override + public int updateByUserId(User user) { + return userMapper.updateByUserId(user); + } + + @Override + public int updateStatusBatch(List userIds,Integer status) { + Map params = new HashMap(2); + params.put("userIds",userIds); + params.put("status",status); + return userMapper.updateStatusBatch(params); + } + + @Override + public ResponseVo addAssignRole(String userId, List roleIds) { + try{ + UserRole userRole = new UserRole(); + userRole.setUserId(userId); + userRoleMapper.delete(userRole); + for(String roleId :roleIds){ + userRole.setId(null); + userRole.setRoleId(roleId); + userRoleMapper.insert(userRole); + } + return ResultUtil.success("分配角色成功"); + }catch(Exception e){ + return ResultUtil.error("分配角色失败"); + } + } + + @Override + public int updateUserByPrimaryKey(User user) { + return userMapper.updateByPrimaryKey(user); + } + + @Override + public List selectOnlineUsers(UserOnlineVo userVo) { + List onlineUserList = new ArrayList(); + /* + // 因为我们是用redis实现了shiro的session的Dao,而且是采用了shiro+redis这个插件 + // 所以从spring容器中获取redisSessionDAO + // 来获取session列表. + Collection sessions = redisSessionDAO.getActiveSessions(); + Iterator it = sessions.iterator(); + // 遍历session + while (it.hasNext()) { + // 这是shiro已经存入session的 + // 现在直接取就是了 + Session session = it.next(); + //标记为已提出的不加入在线列表 + if(session.getAttribute("kickout") != null) { + continue; + } + UserOnlineVo onlineUser = getSessionBo(session); + if(onlineUser!=null){ + 用户名搜索 + if(StringUtils.isNotBlank(userVo.getUsername())){ + if(onlineUser.getUsername().contains(userVo.getUsername()) ){ + onlineUserList.add(onlineUser); + } + }else{ + onlineUserList.add(onlineUser); + } + } + } + */ + return onlineUserList; + } + + @Override + public void kickout(Serializable sessionId, String username) { + /* + getSessionBysessionId(sessionId).setAttribute("kickout", true); + //读取缓存,找到并从队列中移除 + Cache> cache = redisCacheManager.getCache(redisCacheManager.getKeyPrefix()+username); + Deque deques = cache.get(username); + for(Serializable deque : deques){ + if(sessionId.equals(deque)){ + deques.remove(deque); + break; + } + } + cache.put(username,deques); + */ + } + + /* + private Session getSessionBysessionId(Serializable sessionId){ + Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(sessionId)); + return kickoutSession; + } + */ + + @SuppressWarnings("unused") + private UserOnlineVo getSessionBo(Session session){ + //获取session登录信息。 + Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); + if(null == obj){ + return null; + } + //确保是 SimplePrincipalCollection对象。 + if(obj instanceof SimplePrincipalCollection){ + SimplePrincipalCollection spc = (SimplePrincipalCollection)obj; + obj = spc.getPrimaryPrincipal(); + if(null != obj && obj instanceof User){ + User user = (User) obj; + //存储session + user 综合信息 + UserOnlineVo userBo = new UserOnlineVo(); + //最后一次和系统交互的时间 + userBo.setLastAccess(session.getLastAccessTime()); + //主机的ip地址 + userBo.setHost(user.getLoginIpAddress()); + //session ID + userBo.setSessionId(session.getId().toString()); + //最后登录时间 + userBo.setLastLoginTime(user.getLastLoginTime()); + //回话到期 ttl(ms) + userBo.setTimeout(session.getTimeout()); + //session创建时间 + userBo.setStartTime(session.getStartTimestamp()); + //是否踢出 + userBo.setSessionStatus(false); + /*用户名*/ + userBo.setUsername(user.getUsername()); + return userBo; + } + } + return null; + } + + @Override + public void updateQyWxUser(User user) { + if(user == null)return; + if(StringUtils.isBlank(user.getUsername())) { + throw new ParameterException("用户名不能为空"); + } + User dbUser = selectByUsername(user.getUsername()); + if(dbUser != null && StringUtils.isNotBlank(dbUser.getUserId())) { + if(StringUtils.isNotBlank(user.getEmail()))dbUser.setEmail(user.getEmail()); + if(StringUtils.isNotBlank(user.getPhone()))dbUser.setPhone(user.getPhone()); + if(user.getSex()!=null)dbUser.setSex(user.getSex()); + updateByUserId(dbUser); + }else { + user.setCreateTime(new Date()); + userMapper.insertSelective(user); + // 更新用户角色 + List roleIds = new ArrayList(); + roleIds.add("3"); // 固定角色id为3 + addAssignRole(user.getUserId(), roleIds); + } + } + + +} diff --git a/src/main/java/com/nbclass/util/BCrypt.java b/src/main/java/com/nbclass/util/BCrypt.java new file mode 100644 index 0000000..cb9d83c --- /dev/null +++ b/src/main/java/com/nbclass/util/BCrypt.java @@ -0,0 +1,777 @@ +// Copyright (c) 2006 Damien Miller +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +package com.nbclass.util; + +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; + +/** + * BCrypt implements OpenBSD-style Blowfish password hashing using + * the scheme described in "A Future-Adaptable Password Scheme" by + * Niels Provos and David Mazieres. + *

+ * This password hashing system tries to thwart off-line password + * cracking using a computationally-intensive hashing algorithm, + * based on Bruce Schneier's Blowfish cipher. The work factor of + * the algorithm is parameterised, so it can be increased as + * computers get faster. + *

+ * Usage is really simple. To hash a password for the first time, + * call the hashpw method with a random salt, like this: + *

+ * + * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
+ *
+ *

+ * To check whether a plaintext password matches one that has been + * hashed previously, use the checkpw method: + *

+ * + * if (BCrypt.checkpw(candidate_password, stored_hash))
+ *     System.out.println("It matches");
+ * else
+ *     System.out.println("It does not match");
+ *
+ *

+ * The gensalt() method takes an optional parameter (log_rounds) + * that determines the computational complexity of the hashing: + *

+ * + * String strong_salt = BCrypt.gensalt(10)
+ * String stronger_salt = BCrypt.gensalt(12)
+ *
+ *

+ * The amount of work increases exponentially (2**log_rounds), so + * each increment is twice as much work. The default log_rounds is + * 10, and the valid range is 4 to 30. + * + * @author Damien Miller + * @version 0.2 + */ +public class BCrypt { + // BCrypt parameters + private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; + private static final int BCRYPT_SALT_LEN = 16; + + // Blowfish parameters + private static final int BLOWFISH_NUM_ROUNDS = 16; + + // Initial contents of key schedule + private static final int P_orig[] = { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + }; + private static final int S_orig[] = { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + }; + + // bcrypt IV: "OrpheanBeholderScryDoubt". The C implementation calls + // this "ciphertext", but it is really plaintext or an IV. We keep + // the name to make code comparison easier. + static private final int bf_crypt_ciphertext[] = { + 0x4f727068, 0x65616e42, 0x65686f6c, + 0x64657253, 0x63727944, 0x6f756274 + }; + + // Table for Base64 encoding + static private final char base64_code[] = { + '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9' + }; + + // Table for Base64 decoding + static private final byte index_64[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, + -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + -1, -1, -1, -1, -1, -1, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, -1, -1, -1, -1, -1 + }; + + // Expanded Blowfish key + private int P[]; + private int S[]; + + /** + * Encode a byte array using bcrypt's slightly-modified base64 + * encoding scheme. Note that this is *not* compatible with + * the standard MIME-base64 encoding. + * + * @param d the byte array to encode + * @param len the number of bytes to encode + * @return base64-encoded string + * @exception IllegalArgumentException if the length is invalid + */ + private static String encode_base64(byte d[], int len) + throws IllegalArgumentException { + int off = 0; + StringBuffer rs = new StringBuffer(); + int c1, c2; + + if (len <= 0 || len > d.length) + throw new IllegalArgumentException ("Invalid len"); + + while (off < len) { + c1 = d[off++] & 0xff; + rs.append(base64_code[(c1 >> 2) & 0x3f]); + c1 = (c1 & 0x03) << 4; + if (off >= len) { + rs.append(base64_code[c1 & 0x3f]); + break; + } + c2 = d[off++] & 0xff; + c1 |= (c2 >> 4) & 0x0f; + rs.append(base64_code[c1 & 0x3f]); + c1 = (c2 & 0x0f) << 2; + if (off >= len) { + rs.append(base64_code[c1 & 0x3f]); + break; + } + c2 = d[off++] & 0xff; + c1 |= (c2 >> 6) & 0x03; + rs.append(base64_code[c1 & 0x3f]); + rs.append(base64_code[c2 & 0x3f]); + } + return rs.toString(); + } + + /** + * Look up the 3 bits base64-encoded by the specified character, + * range-checking againt conversion table + * @param x the base64-encoded value + * @return the decoded value of x + */ + private static byte char64(char x) { + if ((int)x < 0 || (int)x > index_64.length) + return -1; + return index_64[(int)x]; + } + + /** + * Decode a string encoded using bcrypt's base64 scheme to a + * byte array. Note that this is *not* compatible with + * the standard MIME-base64 encoding. + * @param s the string to decode + * @param maxolen the maximum number of bytes to decode + * @return an array containing the decoded bytes + * @throws IllegalArgumentException if maxolen is invalid + */ + private static byte[] decode_base64(String s, int maxolen) + throws IllegalArgumentException { + StringBuffer rs = new StringBuffer(); + int off = 0, slen = s.length(), olen = 0; + byte ret[]; + byte c1, c2, c3, c4, o; + + if (maxolen <= 0) + throw new IllegalArgumentException ("Invalid maxolen"); + + while (off < slen - 1 && olen < maxolen) { + c1 = char64(s.charAt(off++)); + c2 = char64(s.charAt(off++)); + if (c1 == -1 || c2 == -1) + break; + o = (byte)(c1 << 2); + o |= (c2 & 0x30) >> 4; + rs.append((char)o); + if (++olen >= maxolen || off >= slen) + break; + c3 = char64(s.charAt(off++)); + if (c3 == -1) + break; + o = (byte)((c2 & 0x0f) << 4); + o |= (c3 & 0x3c) >> 2; + rs.append((char)o); + if (++olen >= maxolen || off >= slen) + break; + c4 = char64(s.charAt(off++)); + o = (byte)((c3 & 0x03) << 6); + o |= c4; + rs.append((char)o); + ++olen; + } + + ret = new byte[olen]; + for (off = 0; off < olen; off++) + ret[off] = (byte)rs.charAt(off); + return ret; + } + + /** + * Blowfish encipher a single 64-bit block encoded as + * two 32-bit halves + * @param lr an array containing the two 32-bit half blocks + * @param off the position in the array of the blocks + */ + private final void encipher(int lr[], int off) { + int i, n, l = lr[off], r = lr[off + 1]; + + l ^= P[0]; + for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) { + // Feistel substitution on left word + n = S[(l >> 24) & 0xff]; + n += S[0x100 | ((l >> 16) & 0xff)]; + n ^= S[0x200 | ((l >> 8) & 0xff)]; + n += S[0x300 | (l & 0xff)]; + r ^= n ^ P[++i]; + + // Feistel substitution on right word + n = S[(r >> 24) & 0xff]; + n += S[0x100 | ((r >> 16) & 0xff)]; + n ^= S[0x200 | ((r >> 8) & 0xff)]; + n += S[0x300 | (r & 0xff)]; + l ^= n ^ P[++i]; + } + lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1]; + lr[off + 1] = l; + } + + /** + * Cycically extract a word of key material + * @param data the string to extract the data from + * @param offp a "pointer" (as a one-entry array) to the + * current offset into data + * @return the next word of material from data + */ + private static int streamtoword(byte data[], int offp[]) { + int i; + int word = 0; + int off = offp[0]; + + for (i = 0; i < 4; i++) { + word = (word << 8) | (data[off] & 0xff); + off = (off + 1) % data.length; + } + + offp[0] = off; + return word; + } + + /** + * Initialise the Blowfish key schedule + */ + private void init_key() { + P = (int[])P_orig.clone(); + S = (int[])S_orig.clone(); + } + + /** + * Key the Blowfish cipher + * @param key an array containing the key + */ + private void key(byte key[]) { + int i; + int koffp[] = { 0 }; + int lr[] = { 0, 0 }; + int plen = P.length, slen = S.length; + + for (i = 0; i < plen; i++) + P[i] = P[i] ^ streamtoword(key, koffp); + + for (i = 0; i < plen; i += 2) { + encipher(lr, 0); + P[i] = lr[0]; + P[i + 1] = lr[1]; + } + + for (i = 0; i < slen; i += 2) { + encipher(lr, 0); + S[i] = lr[0]; + S[i + 1] = lr[1]; + } + } + + /** + * Perform the "enhanced key schedule" step described by + * Provos and Mazieres in "A Future-Adaptable Password Scheme" + * http://www.openbsd.org/papers/bcrypt-paper.ps + * @param data salt information + * @param key password information + */ + private void ekskey(byte data[], byte key[]) { + int i; + int koffp[] = { 0 }, doffp[] = { 0 }; + int lr[] = { 0, 0 }; + int plen = P.length, slen = S.length; + + for (i = 0; i < plen; i++) + P[i] = P[i] ^ streamtoword(key, koffp); + + for (i = 0; i < plen; i += 2) { + lr[0] ^= streamtoword(data, doffp); + lr[1] ^= streamtoword(data, doffp); + encipher(lr, 0); + P[i] = lr[0]; + P[i + 1] = lr[1]; + } + + for (i = 0; i < slen; i += 2) { + lr[0] ^= streamtoword(data, doffp); + lr[1] ^= streamtoword(data, doffp); + encipher(lr, 0); + S[i] = lr[0]; + S[i + 1] = lr[1]; + } + } + + /** + * Perform the central password hashing step in the + * bcrypt scheme + * @param password the password to hash + * @param salt the binary salt to hash with the password + * @param log_rounds the binary logarithm of the number + * of rounds of hashing to apply + * @param cdata the plaintext to encrypt + * @return an array containing the binary hashed password + */ + public byte[] crypt_raw(byte password[], byte salt[], int log_rounds, + int cdata[]) { + int rounds, i, j; + int clen = cdata.length; + byte ret[]; + + if (log_rounds < 4 || log_rounds > 30) + throw new IllegalArgumentException ("Bad number of rounds"); + rounds = 1 << log_rounds; + if (salt.length != BCRYPT_SALT_LEN) + throw new IllegalArgumentException ("Bad salt length"); + + init_key(); + ekskey(salt, password); + for (i = 0; i != rounds; i++) { + key(password); + key(salt); + } + + for (i = 0; i < 64; i++) { + for (j = 0; j < (clen >> 1); j++) + encipher(cdata, j << 1); + } + + ret = new byte[clen * 4]; + for (i = 0, j = 0; i < clen; i++) { + ret[j++] = (byte)((cdata[i] >> 24) & 0xff); + ret[j++] = (byte)((cdata[i] >> 16) & 0xff); + ret[j++] = (byte)((cdata[i] >> 8) & 0xff); + ret[j++] = (byte)(cdata[i] & 0xff); + } + return ret; + } + + /** + * Hash a password using the OpenBSD bcrypt scheme + * @param password the password to hash + * @param salt the salt to hash with (perhaps generated + * using BCrypt.gensalt) + * @return the hashed password + */ + public static String hashpw(String password, String salt) { + BCrypt B; + String real_salt; + byte passwordb[], saltb[], hashed[]; + char minor = (char)0; + int rounds, off = 0; + StringBuffer rs = new StringBuffer(); + + if (salt.charAt(0) != '$' || salt.charAt(1) != '2') + throw new IllegalArgumentException ("Invalid salt version"); + if (salt.charAt(2) == '$') + off = 3; + else { + minor = salt.charAt(2); + if (minor != 'a' || salt.charAt(3) != '$') + throw new IllegalArgumentException ("Invalid salt revision"); + off = 4; + } + + // Extract number of rounds + if (salt.charAt(off + 2) > '$') + throw new IllegalArgumentException ("Missing salt rounds"); + rounds = Integer.parseInt(salt.substring(off, off + 2)); + + real_salt = salt.substring(off + 3, off + 25); + try { + passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8"); + } catch (UnsupportedEncodingException uee) { + throw new AssertionError("UTF-8 is not supported"); + } + + saltb = decode_base64(real_salt, BCRYPT_SALT_LEN); + + B = new BCrypt(); + hashed = B.crypt_raw(passwordb, saltb, rounds, + (int[])bf_crypt_ciphertext.clone()); + + rs.append("$2"); + if (minor >= 'a') + rs.append(minor); + rs.append("$"); + if (rounds < 10) + rs.append("0"); + if (rounds > 30) { + throw new IllegalArgumentException( + "rounds exceeds maximum (30)"); + } + rs.append(Integer.toString(rounds)); + rs.append("$"); + rs.append(encode_base64(saltb, saltb.length)); + rs.append(encode_base64(hashed, + bf_crypt_ciphertext.length * 4 - 1)); + return rs.toString(); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method + * @param log_rounds the log2 of the number of rounds of + * hashing to apply - the work factor therefore increases as + * 2**log_rounds. + * @param random an instance of SecureRandom to use + * @return an encoded salt value + */ + public static String gensalt(int log_rounds, SecureRandom random) { + StringBuffer rs = new StringBuffer(); + byte rnd[] = new byte[BCRYPT_SALT_LEN]; + + random.nextBytes(rnd); + + rs.append("$2a$"); + if (log_rounds < 10) + rs.append("0"); + if (log_rounds > 30) { + throw new IllegalArgumentException( + "log_rounds exceeds maximum (30)"); + } + rs.append(Integer.toString(log_rounds)); + rs.append("$"); + rs.append(encode_base64(rnd, rnd.length)); + return rs.toString(); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method + * @param log_rounds the log2 of the number of rounds of + * hashing to apply - the work factor therefore increases as + * 2**log_rounds. + * @return an encoded salt value + */ + public static String gensalt(int log_rounds) { + return gensalt(log_rounds, new SecureRandom()); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method, + * selecting a reasonable default for the number of hashing + * rounds to apply + * @return an encoded salt value + */ + public static String gensalt() { + return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS); + } + + /** + * Check that a plaintext password matches a previously hashed + * one + * @param plaintext the plaintext password to verify + * @param hashed the previously-hashed password + * @return true if the passwords match, false otherwise + */ + public static boolean checkpw(String plaintext, String hashed) { + byte hashed_bytes[]; + byte try_bytes[]; + try { + String try_pw = hashpw(plaintext, hashed); + hashed_bytes = hashed.getBytes("UTF-8"); + try_bytes = try_pw.getBytes("UTF-8"); + } catch (UnsupportedEncodingException uee) { + return false; + } + if (hashed_bytes.length != try_bytes.length) + return false; + byte ret = 0; + for (int i = 0; i < try_bytes.length; i++) + ret |= hashed_bytes[i] ^ try_bytes[i]; + return ret == 0; + } +} diff --git a/src/main/java/com/nbclass/util/CommonUtils.java b/src/main/java/com/nbclass/util/CommonUtils.java new file mode 100644 index 0000000..2de94e7 --- /dev/null +++ b/src/main/java/com/nbclass/util/CommonUtils.java @@ -0,0 +1,849 @@ +package com.nbclass.util; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.UUID; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CommonUtils { + + protected static Logger logger = LoggerFactory.getLogger(CommonUtils.class); + + + /** + * 转换Date类型为字符串类型 + * + * @param value + * @return + */ + public static String getSimpleDate(Date value) { + return getSimpleDate(value, "yyyy-MM-dd HH:mm:ss"); + } + + /** + * 转换Date类型为中文字符串类型 + * + * @param value + * @return + */ + public static String getChinieseDate(Date value) { + return getSimpleDate(value, "yyyy年MM月dd日HH时mm分"); + } + + /** + * 转换Date类型为字符串类型 + * + * @param value + * @return + */ + public static String getSimpleDate(Date value, String pattern) { + SimpleDateFormat formatter = new SimpleDateFormat(pattern); + return formatter.format(value); + } + + /** + * 判断字符串是否空或空字符串 + * + * @param str + * @return + */ + public static boolean isEmpty(String str) { + if (str == null || str.trim().length() == 0) { + return true; + } + return false; + } + + /** + * 判断字符串是否为非空 + * + * @param str + * @return + */ + public static boolean isNotEmpty(String str) { + if (str == null || str.trim().length() == 0) { + return false; + } + return true; + } + + /** + * 返回trim后的字符串,如空字符串,则直接返回空。 + * + * @param str + * @return + */ + public static String trimToEmpty(String str) { + if (str == null || str.trim().length() == 0) { + return null; + } + return str.trim(); + } + + /** + * 返回trim后的字符串,如果为null,则返回"" + * + * @param str + * @return + */ + public static String trimToEmptyForce(String str) { + if (str == null || str.trim().length() == 0) { + return ""; + } + return str.trim(); + } + + /** + * 判断字符是否为数值型 + * + * @param str + * @return + */ + public static boolean isNumeric(String str) { + try { + Integer.parseInt(str); + return true; + } catch (NumberFormatException e) { + return false; + } + + } + + /** + * 获取几月后的时间 + * + * @param d + * @param d + * @return + */ + public static Date nextMonth(Date d, int m) { + Calendar now = Calendar.getInstance(); + now.setTime(d); + now.set(Calendar.MONTH, now.get(Calendar.MONTH) + m); + return now.getTime(); + } + + /** + * 获取几天后的时间 + * + * @param d + * @param day + * @return + */ + public static Date nextDate(Date d, int day) { + Calendar now = Calendar.getInstance(); + now.setTime(d); + now.set(Calendar.DATE, now.get(Calendar.DATE) + day); + return now.getTime(); + } + + /** + * 获取几小时后的时间 + * + * @param d + * @param d + * @return + */ + public static Date nextHour(Date d, int hour) { + Calendar now = Calendar.getInstance(); + now.setTime(d); + now.set(Calendar.HOUR_OF_DAY, now.get(Calendar.HOUR_OF_DAY) + hour); + return now.getTime(); + } + + /** + * 获取几分钟后的时间 + * + * @param d + * @param minute + * @return + */ + public static Date nextMinute(Date d, int minute) { + Calendar now = Calendar.getInstance(); + now.setTime(d); + now.set(Calendar.MINUTE, now.get(Calendar.MINUTE) + minute); + return now.getTime(); + } + + /** + * 获取两个日期的间隔天数 + * + * @param startDay + * @param endDay + * @return + */ + public static int dayInterval(Date startDay, Date endDay) { + return (int) ((endDay.getTime() - startDay.getTime()) / (24 * 60 * 60 * 1000)); + } + + /** + * 转换String类型为Date类型 + * + * @param value + * @return + * @throws java.text.ParseException + */ + public static Date getSimpleDate(String value){ + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + return formatter.parse(value); + } catch (ParseException e) { + } + return null; + } + + /** + * 转换String类型为Date类型 + * + * @param value + * @return + * @throws java.text.ParseException + */ + public static Date getSimpleDateBy(String value, String pattern){ + SimpleDateFormat formatter = new SimpleDateFormat(pattern); + try { + return formatter.parse(value); + } catch (ParseException e) { + } + return null; + } + + /** + * 转换String类型为Date类型 + * + * @param value + * @return + * @throws java.text.ParseException + */ + public static Date getSimpleDate2(String value) throws ParseException { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + return formatter.parse(value); + } + + /** + * 把时分秒置为0 + * + * @param date + * @return + */ + public static Date getDateOnly(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + /** + * 首字母大写 + * + * @param name + * @return + */ + public static String getUpperName(String name) { + byte[] items = name.getBytes(); + items[0] = (byte) ((char) items[0] - 'a' + 'A'); + return new String(items); + } + + /** + * 判断字符串是否合法(不含有非法字符或中文) 若数组中其中一字符串含有非法字符,返回false,反之,返回true + * + * @param sarray + * @return + */ + public static boolean judgeIllegalChar(String[] sarray) { + boolean result = true; + if (sarray != null) { + Pattern pattern = Pattern.compile("^\\w+$"); + for (int i = 0; i < sarray.length; i++) { + if (!pattern.matcher(sarray[i]).matches()) { + result = false; + break; + } + } + } else { + result = false; + } + return result; + } + + /** + * 判断字符串是否为合法邮箱 + * + * @param email + * @return + */ + public static boolean judgeEmail(String email) { + boolean result = false; + if (email != null) { + Pattern pattern = Pattern + .compile("^([a-z0-9A-Z._-])+@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"); + if (pattern.matcher(email).matches()) { + result = true; + } + } + return result; + } + + /** + * 判断字符串是否合法(不含有非法字符,可含中文) 若数组中其中一字符串含有非法字符,返回false,反之,返回true + * + * @param sarray + * @return + */ + public static boolean judgeIllegalCharAndChinese(String[] sarray) { + boolean result = true; + if (sarray != null) { + Pattern pattern = Pattern.compile("^[\u4e00-\u9fa5a-zA-Z0-9_-]+$"); + for (int i = 0; i < sarray.length; i++) { + if (sarray[i] == null || !pattern.matcher(sarray[i]).matches()) { + result = false; + break; + } + } + } else { + result = false; + } + return result; + } + + /** + * 判断字符串是否合法(不含 ' 字符,可含中文) 若数组中其中一字符串含有非法字符,返回false,反之,返回true + * + * @param sarray + * @return + */ + public static boolean judgePartIllegalCharAndChinese(String[] sarray) { + boolean result = true; + if (sarray != null) { + Pattern pattern = Pattern.compile("[^\']+"); + for (int i = 0; i < sarray.length; i++) { + if (sarray[i] == null || !pattern.matcher(sarray[i]).matches()) { + result = false; + break; + } + } + } else { + result = false; + } + return result; + } + + public static String apiEncode(Integer uid, Integer wuid) { + String randString = UUID.randomUUID().toString(); + String startString = randString.substring(0, 4); + String endString = randString.substring(0, 10); + String midString = "C"; + wuid = wuid << 2; + uid = uid << 3; + return startString + uid + midString + wuid + endString; + } + + public static Integer[] apiDecode(String key) { + key = key.substring(0, key.length() - 10).substring(4); + String[] array = key.split("C"); + Integer uid = Integer.parseInt(array[0]); + uid = uid >> 3; + Integer wuid = Integer.parseInt(array[1]); + wuid = wuid >> 2; + return new Integer[] { uid, wuid }; + } + + /** + * 分享链接编码 + * + * @param uid + * @return + */ + public static String shareEncode(Integer uid) { + String randString = UUID.randomUUID().toString(); + String startString = randString.substring(0, 4); + String endString = randString.substring(0, 10); + String midString = "C"; + uid = uid << 3; + return startString + uid + midString + endString; + } + + /** + * 分享链接解码 + * + * @param key + * @return + */ + public static Integer shareDecode(String key) { + key = key.substring(0, key.length() - 10).substring(4); + String[] array = key.split("C"); + Integer uid = Integer.parseInt(array[0]); + uid = uid >> 3; + return uid; + } + + /** + * 截取左边max个字符,ascll码大于255算两个字符,字符过长以...结尾 + * + * @param s + * @param max + * @return + */ + public static String left(String s, int max) { + char[] cs = s.toCharArray(); + int count = 0; + int last = cs.length; + for (int i = 0, len = last; i < len; i++) { + if (cs[i] > 255) { + count += 2; + } else { + count++; + } + if (count > max) { + last = i + 1; + break; + } + } + if (count <= max) { + return s; + } + max -= 3; + for (int i = last - 1; i >= 0; i--) { + if (cs[i] > 255) { + count -= 2; + } else { + count--; + } + if (count <= max) { + return s.substring(0, i) + "..."; + } + } + return "..."; + } + + /** + * 从指定的时间截取年月日。将时分秒毫秒都设置为0 + * + * @param source + * 原始时间 + * @return 将时分秒毫秒都设置为0的日期 + */ + public static Date trimDate(Date source) { + Calendar cal = Calendar.getInstance(); + cal.setTime(source); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTime(); + } + + /** + * 获取字符串的长度,中文占一个字符,英文数字占半个字符 + * + * @param value + * 指定的字符串 + * @return 字符串的长度 + */ + public static double length(String value) { + double valueLength = 0; + String chinese = "[\u4e00-\u9fa5]"; + for (int i = 0; i < value.length(); i++) { + String temp = value.substring(i, i + 1); + if (temp.matches(chinese)) { + valueLength += 1; + } else { + valueLength += 0.5; + } + } + return Math.ceil(valueLength); + } + + /** + * 生成一个随机码 + * + * @return + */ + /* + public static String getRandomCode() { + SimpleDateFormat millSecondFormat = new SimpleDateFormat( + "yyyyMMddHHmmssSSS"); + String prefix = millSecondFormat.format(new Date()); + Random random = new Random(); + int number = random.nextInt(100); + return prefix + number; + } + */ + + /** + * 计算两个时间之间的秒数,注意不能超过50年 + * @param start 开始时间 + * @param end 结束时间 + * @return + */ + public static int calSecondsBetween(Date start, Date end) { + long times = (end.getTime() - start.getTime()) / 1000; + return (int)times; + } + + public static java.sql.Date UtilDateToSQLDate(Date utilDate) { + if (utilDate == null) return null; + return new java.sql.Date(utilDate.getTime()); + } + + public static Timestamp UtilDateToSQLTimestamp(Date utilDate){ + if (utilDate == null) return null; + return new Timestamp(utilDate.getTime()); + } + + public static final String REGEX_MOBILE_EXACT = "^((13[0-9])|(14[5,7,9])|(15[0-3,5-9])|(16[6])|(17[0-3,5-8])|(18[0-9])|(19[89]))\\d{8}$"; + + /** + * 验证手机号(精确) + * @param phone 待验证文本 + * @return {@code true}: 匹配
{@code false}: 不匹配 + */ + public static boolean isMobile(String phone) { + boolean result = StringUtils.isNotBlank(phone) && Pattern.matches(REGEX_MOBILE_EXACT, phone); + return result; + } + + /** + * 获取随机代码 + * @param length 需要的长度 + * @return + */ + public static String getRandomCode(int length) { + char[] chs = { 'a', 'b', 'c', '1', '2', '3', 'd', 'e', 'f', + 'A', 'B', 'C', 'D', 'E', 'F', + '4', '5', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + '6', '8', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '6', '7', '8', '9', + '2', '5', '9', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; + SecureRandom random = new SecureRandom(); + char[] value = new char[length]; + for (int i = 0; i < length; i++) { + value[i] = chs[random.nextInt(chs.length)]; + } + String code = new String(value); + return code; + } + + public static int getValidInt(String str){ + return getValidInt(str, -1); + } + + public static int getValidInt(String str, int defaultVal){ + int result = defaultVal; + try { + if(StringUtils.isNotBlank(str)){ + result = Integer.parseInt(str); + } + } catch (NumberFormatException e) { + e.printStackTrace(); + } + return result; + } + + /** + * 封装JDK自带的UUID, 通过Random数字生成, 中间无-分割. + */ + public static String getUUID() { + String uuid = UUID.randomUUID().toString().trim().replaceAll("-", ""); + //uuid = uuid + "|yupenxiqi"; + if(uuid.length() > 28)uuid = uuid.substring(0, 28); + return uuid; + } + + public static String getOpenId(String prefix) { + int length = 28; + if(StringUtils.isNotBlank(prefix)){ + length -= prefix.length(); + } + + char[] chs = { 'a', 'b', 'c', '1', '2', '3', 'd', 'e', 'f', + 'A', 'B', 'C', 'D', 'E', 'F', + '4', '5', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + '6', '8', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '6', '7', '8', '9', + '2', '5', '9', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; + SecureRandom random = new SecureRandom(); + char[] value = new char[length]; + for (int i = 0; i < length; i++) { + value[i] = chs[random.nextInt(chs.length)]; + } + String code = new String(value); + if(StringUtils.isNotBlank(prefix)){ + code = prefix+code; + } + return code; + } + + + /** + * 向URL中添加参数(有则修改,无则添加) + * @param url + * @param ParamName + * @param ParamValue + * @return + */ + public static String addParamValue(String url, String ParamName, String ParamValue) { + if(StringUtils.isNotBlank(url) && StringUtils.isNotBlank(ParamName) && StringUtils.isNotBlank(ParamValue)) { + if(url.contains(ParamName+"=")){ + url = replaceParamValue(url, ParamName, ParamValue); + }else{ + if(url.contains("?") || url.contains("\\?")){ + url += "&"+ParamName+"=" + ParamValue; + }else{ + url += "?"+ParamName+"=" + ParamValue; + } + } + } + return url; + } + + /** + * 替换URL参数的值 + * @param url + * @param ParamName + * @param ParamValue + * @return + */ + public static String replaceParamValue(String url, String ParamName, String ParamValue) { + if(StringUtils.isNotBlank(url) && StringUtils.isNotBlank(ParamName) && StringUtils.isNotBlank(ParamValue)) { + url = url.replaceAll("(\\?" + ParamName +"=[^&]*)", "?"+ParamName + "=" + ParamValue); + url = url.replaceAll("(\\&" + ParamName +"=[^&]*)", "&"+ParamName + "=" + ParamValue); + } + return url; + } + + /** + * 删除URL中的参数 + * @param url + * @param ParamName + * @return + */ + public static String removeParamValue(String url, String ParamName) { + if(StringUtils.isNotBlank(url) && StringUtils.isNotBlank(ParamName)) { + while(url.contains("?"+ParamName+"=") || url.contains("%3f"+ParamName+"%3d") || url.contains("%3F"+ParamName+"%3D") + || url.contains("&"+ParamName+"=") || url.contains("%26"+ParamName+"%3d") || url.contains("%26"+ParamName+"%3D")){ + url = url.replaceAll("(\\?" + ParamName +"=[^&]*)", ""); + url = url.replaceAll("(%3f" + ParamName +"%3d[^&]*)", ""); + url = url.replaceAll("(%3F" + ParamName +"%3D[^&]*)", ""); + + url = url.replaceAll("(\\&" + ParamName +"=[^&]*)", ""); + url = url.replaceAll("(%26" + ParamName +"%3d[^&]*)", ""); + url = url.replaceAll("(%26" + ParamName +"%3D[^&]*)", ""); + } + } + return url; + } + + /** + * 优惠码的生成 + * @return + */ + public static String getCode(int length) { + char[] chs = { 'a', 'b', 'c', '1', '2', '3', '4', '5', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '6', '7', '8', '9' }; + SecureRandom random = new SecureRandom(); + final char[] value = new char[length]; + for (int i = 0; i < length; i++) { + value[i] = chs[random.nextInt(chs.length)]; + } + final String code = new String(value); + return code; + } + + /** + * 生成随机邀请码 + * @return + */ + public static String getInvitecode() { + int length = 4; + char[] chs = { 'A', 'B', 'C', '1', '2', '3', '4', '5', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '6', '7', '8', '9' }; + SecureRandom random = new SecureRandom(); + final char[] value = new char[length]; + for (int i = 0; i < length; i++) { + value[i] = chs[random.nextInt(chs.length)]; + } + final String code = new String(value); + return code; + } + + public static int[] toIntArray(String[] array) { + int[] result = new int[array.length]; + int count = 0; + for (int i = 0; i < array.length; i++) { + if (array[i] != null && !array[i].trim().equals("") && !array[i].trim().equals("undefined")) + result[count++] = Integer.parseInt(array[i]); + } + return result; + } + + public static int[] toIntArray(ArrayList array){ + int[] result = new int[array.size()]; + int count = 0; + for (int i = 0; i < array.size(); i++) { + String item = array.get(i); + if (item != null && !item.trim().equals("")) + result[count++] = Integer.parseInt(item); + } + return result; + } + + public static int[] toIntArray(String str){ + if(str == null || str.equals("")) return null; + int[] result; + if(str.indexOf(",")!=-1){ + String[] strArr = str.split(","); + result =toIntArray(strArr); + }else{ + result = new int[]{Integer.parseInt(str)}; + } + return result; + } + + public static List toIntList(String str){ + List idList = null; + if(str == null || str.equals("")) return null; + int[] result = toIntArray(str); + if(result!=null && result.length>0){ + idList = new ArrayList(); + for (int id : result) { + idList.add(id); + } + } + return idList; + } + + + public static long[] toLongArray(String[] array) { + long[] result = new long[array.length]; + int count = 0; + for (int i = 0; i < array.length; i++) { + if (array[i] != null && !array[i].trim().equals("") && !array[i].trim().equals("undefined")) + result[count++] = Long.parseLong(array[i]); + } + return result; + } + + public static long[] toLongArray(String str){ + if(str == null || str.equals("")) return null; + long[] result; + if(str.indexOf(",")!=-1){ + String[] strArr = str.split(","); + result = toLongArray(strArr); + }else{ + result = new long[]{Long.parseLong(str)}; + } + return result; + } + + public static List toLongList(String str){ + List idList = null; + if(str == null || str.equals("")) return null; + long[] result = toLongArray(str); + if(result!=null && result.length>0){ + idList = new ArrayList(); + for (long id : result) { + idList.add(id); + } + } + return idList; + } + + /** + * 获取请求URL,到ContextPath止 + * @param request + * @return + */ + public static String getBaseUrl(HttpServletRequest request){ + String result = null; + int port = request.getServerPort(); + result = request.getScheme()+"://"+request.getServerName()+(port==80||port==443 ? "" : ":"+port)+request.getContextPath()+"/"; + return result; + } + + /** + * 获取服务器IP地址 + * @return + */ + public static String getHostIP(){ + String hostip = null; + try { + InetAddress address = InetAddress.getLocalHost(); + hostip = address.getHostAddress(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + return hostip; + } + + /** + * 获取当前服务器cdn域名 + * 国内: 39.108.10.19 - 172.18.219.193 + * 国外: 47.88.236.90 - 172.21.7.78 + * @return + */ + /** + * 是否是内网 + * @return + */ + public static boolean isNw(){ + boolean isnw = false; + String hostip = getHostIP(); + if(hostip.startsWith("172.") || hostip.startsWith("10.")){ + isnw = true; + } + return isnw; + } + + /** + * str数组转list + * @param str + * @return + */ + public static ArrayList str2List(String str){ + ArrayList list = new ArrayList<>(); + if(str!=null && str.trim().length()>0){ + if(str.indexOf(",")!=-1){ + String[] arr = str.split(","); + for (String string : arr) { + list.add(string); + } + }else{ + list.add(str); + } + } + return list; + } + + public static void main(String[] args) { + for (int i=0; i < 10; i++) { + System.out.println(getInvitecode()); + } + } +} diff --git a/src/main/java/com/nbclass/util/CopyUtil.java b/src/main/java/com/nbclass/util/CopyUtil.java new file mode 100644 index 0000000..cd87a58 --- /dev/null +++ b/src/main/java/com/nbclass/util/CopyUtil.java @@ -0,0 +1,143 @@ +package com.nbclass.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; + +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.List; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class CopyUtil { + private static Logger logger = LoggerFactory.getLogger(CopyUtil.class); + /** + * @param source 源 + * @param dest 目标 + */ + public static void copy(Object source, Object dest) { + try { + if (source == null || dest == null) { + return; + } + // 获取属性 + BeanInfo sourceBean = Introspector.getBeanInfo(source.getClass(), Object.class); + PropertyDescriptor[] sourceProperty = sourceBean.getPropertyDescriptors(); + BeanInfo destBean = Introspector.getBeanInfo(dest.getClass(), Object.class); + PropertyDescriptor[] destProperty = destBean.getPropertyDescriptors(); + for (int i = 0; i < sourceProperty.length; i++) { + for (int j = 0; j < destProperty.length; j++) { + if (sourceProperty[i].getName().equals(destProperty[j].getName())) { + try { + // 调用source的getter方法和dest的setter方法 + destProperty[j].getWriteMethod().invoke(dest, sourceProperty[i].getReadMethod().invoke(source)); + break; + } catch (Exception e) { + logger.info("属性赋值失败," + sourceProperty[i].getName() + e.getMessage()); + } + } + } + } + } catch (Exception e) { + logger.info("对象赋值失败," + source + "," + dest); + } + } + + /** + * @param source 源 + * @param dest 目标 + */ + public static void copyNotNull(Object source, Object dest) { + try { + if (source == null || dest == null) { + return; + } + // 获取属性 + BeanInfo sourceBean = Introspector.getBeanInfo(source.getClass(), Object.class); + PropertyDescriptor[] sourceProperty = sourceBean.getPropertyDescriptors(); + BeanInfo destBean = Introspector.getBeanInfo(dest.getClass(), Object.class); + PropertyDescriptor[] destProperty = destBean.getPropertyDescriptors(); + for (int i = 0; i < sourceProperty.length; i++) { + for (int j = 0; j < destProperty.length; j++) { + if (sourceProperty[i].getName().equals(destProperty[j].getName()) && CoreUtils.notNull(sourceProperty[i].getReadMethod().invoke(source))) { + try { + // 调用source的getter方法和dest的setter方法 + destProperty[j].getWriteMethod().invoke(dest, sourceProperty[i].getReadMethod().invoke(source)); + break; + } catch (Exception e) { + logger.info("属性赋值失败," + sourceProperty[i].getName() + e.getMessage()); + } + } + } + } + } catch (Exception e) { + logger.info("对象赋值失败," + source + "," + dest); + } + } + + /*** + * + * @param source + * 源 + * @param clazz + * 目标类 + * @return 目标类实例 + */ + public static T getCopy(Object source, Class clazz) { + T dest = null; + try { + dest = clazz.newInstance(); + copy(source, dest); + } catch (Exception e) { + e.printStackTrace(); + logger.info("对象复制错误:" + e.getMessage()); + } + return dest; + } + + /*** + * + * @param source + * 源 + * @param dest + * 目标类 + * @param ignoreProperties + * 过滤掉的属性 + */ + public static Object getCopy(Object source, Object dest, String... ignoreProperties) { + try { + BeanUtils.copyProperties(source, dest, ignoreProperties); + } catch (Exception e) { + e.printStackTrace(); + logger.info("对象复制错误:" + e.getMessage()); + } + return dest; + } + + @SuppressWarnings("rawtypes") + public static List getCopyList(List sources, Class clazz) { + List clazzs = new ArrayList<>(); + if (sources == null) { + return clazzs; + } + for (Object source : sources) { + try { + T dest = clazz.newInstance(); + copy(source, dest); + clazzs.add(dest); + } catch (InstantiationException e) { + e.printStackTrace(); + logger.info("对象复制错误:" + e.getMessage()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + logger.info("对象复制错误:" + e.getMessage()); + } + } + return clazzs; + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/util/CoreConst.java b/src/main/java/com/nbclass/util/CoreConst.java new file mode 100644 index 0000000..d9fcdb0 --- /dev/null +++ b/src/main/java/com/nbclass/util/CoreConst.java @@ -0,0 +1,48 @@ +package com.nbclass.util; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class CoreConst { + + /** + * 用户状态:1有效; 2删除 + */ + public static final Integer STATUS_VALID = 1; + public static final Integer STATUS_INVALID = 0; + + public static Integer TOP_MENU_ID = 0; + public static String TOP_MENU_NAME = "顶层菜单"; + + + /** 响应状态码 key **/ + public static final String STATUS = "ret"; + + /** 成功状态码 **/ + public static final int STATUS_SUCCESS = 0; + + /** 成功消息 **/ + public static final String STATUS_MSG = "操作成功"; + + /** 失败状态码 **/ + public static final int STATUS_ERROR = -1; + + /** 响应消息 key **/ + public static final String MSG = "msg"; + + /** 响应内容 key **/ + public static final String DATA = "data"; + + /** + * 用户个人权限在session中的key + */ + public static final String SESSION_USER = "user"; + + public static final String SESSION_USER_ID = "userId"; + + public static final String SESSION_USER_NAME = "userName"; + + public static final String SESSION_USER_ISADMIN = "userIsAdmin"; + +} diff --git a/src/main/java/com/nbclass/util/CoreUtils.java b/src/main/java/com/nbclass/util/CoreUtils.java new file mode 100644 index 0000000..c182d96 --- /dev/null +++ b/src/main/java/com/nbclass/util/CoreUtils.java @@ -0,0 +1,60 @@ +package com.nbclass.util; + +import org.apache.commons.beanutils.PropertyUtils; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class CoreUtils { + + public static T copy(Object orig, Class clazz) { + T dest = null; + try { + if (notNull(orig)) { + dest = clazz.newInstance(); + PropertyUtils.copyProperties(dest, orig); + } + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + e.printStackTrace(); + } + return dest; + } + + public static List copyList(List origs, Class clazz) { + List list = new ArrayList(); + if (notNull(origs)) { + for (Object orig : origs) { + list.add(copy(orig, clazz)); + } + } + return list; + } + + public static boolean notNullAndEmpty(String string) { + return null == string || "".equals(string) ? false : true; + } + + public static boolean notNullAndZero(List list) { + return null == list || list.size() == 0 ? false : true; + } + + public static boolean notNull(Object object) { + if (object instanceof List) { + return notNullAndZero((List) object); + } else if (object instanceof String) { + return notNullAndEmpty((String) object); + } else { + return null != object; + } + } + + public static boolean isEmpty(String string) { + return null == string || "".equals(string) ? true : false; + } + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/util/HttpClientUtil.java b/src/main/java/com/nbclass/util/HttpClientUtil.java new file mode 100644 index 0000000..acb24af --- /dev/null +++ b/src/main/java/com/nbclass/util/HttpClientUtil.java @@ -0,0 +1,174 @@ +package com.nbclass.util; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.exception.ServiceException; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Map.Entry; + +/** + * HttpClients 请求工具类 + * @author Leon + * @datetime 2018年8月13日 下午4:42:17 + */ +public class HttpClientUtil { + + private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class); + + private static final CloseableHttpClient httpClient; + + static { + RequestConfig globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).setConnectTimeout(10000).setSocketTimeout(10000).build(); + httpClient = HttpClientBuilder.create().setDefaultRequestConfig(globalConfig).build(); + } + + /** + * POST请求 + * @param url + * @param json + * @return + */ + public static JSONObject doPost(String url, JSONObject json){ + // CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig(globalConfig).build(); + HttpPost httpPost = new HttpPost(url); + JSONObject response = null; + CloseableHttpResponse res = null; + try { + // 设置请求的header + // httpPost.setHeader("Content-Type", "application/json;charset=utf-8"); + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); + httpPost.setHeader("Accept", "application/json;charset=utf-8"); + + // StringEntity s = new StringEntity(json.toString(), "UTF-8"); + StringEntity s = new StringEntity(formatPostData(json), "UTF-8"); + // s.setContentEncoding("UTF-8"); + // s.setContentType("application/json");//发送json数据需要设置contentType + httpPost.setEntity(s); + + res = httpClient.execute(httpPost); + int statusCode = res.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + logger.info("请求接口返回状态吗, getStatusCode="+statusCode+", getReasonPhrase="+res.getStatusLine().getReasonPhrase()); + httpPost.abort(); + throw new ServiceException("HttpClient,error status code :" + statusCode); + } + + HttpEntity entity = res.getEntity(); + if (entity != null) { + String result = EntityUtils.toString(entity, "UTF-8"); + response = JSONObject.parseObject(result); + } + } catch (Exception e) { + logger.error("HTTP-POST请求出错, URL="+url, e); + } finally { + httpPost.releaseConnection(); + if (res != null) { + try { + EntityUtils.consume(res.getEntity()); + } catch (IOException e){} + try { + res.close(); + } catch (IOException e){} + } + } + return response; + } + + /** + * GET请求 + * @param url + * @return + */ + public static JSONObject doGet(String url){ + HttpGet httpGet = new HttpGet(url); + CloseableHttpResponse res = null; + JSONObject response = null; + try { + // 设置请求的header + httpGet.setHeader("Content-Type", "application/json;charset=utf-8"); + httpGet.setHeader("Accept", "application/json;charset=utf-8"); + + res = httpClient.execute(httpGet); + int statusCode = res.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + logger.info("请求接口返回状态码, getStatusCode="+statusCode+", getReasonPhrase="+res.getStatusLine().getReasonPhrase()); + httpGet.abort(); + throw new ServiceException("HttpClient,error status code :" + statusCode); + } + + HttpEntity entity = res.getEntity(); + if (entity != null) { + String result = EntityUtils.toString(entity, "UTF-8"); + response = JSONObject.parseObject(result); + } + } catch (Exception e) { + logger.error("HTTP-GET请求出错, URL="+url, e); + } finally { + httpGet.releaseConnection(); + if (res != null) { + try { + EntityUtils.consume(res.getEntity()); + } catch (IOException e){} + try { + res.close(); + } catch (IOException e){} + } + } + return response; + } + + protected static String formatPostData(JSONObject json){ + String result = null; + try { + JSONObject jsonObject = json; + if(jsonObject != null){ + result = "?"; + for (Entry entry : jsonObject.entrySet()) { + if(StringUtils.isEmpty(result)){ + result = entry.getKey() + "=" + entry.getValue(); + }else{ + result += "&" + entry.getKey() + "=" + entry.getValue(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + protected static String formatPostData(String strdata){ + String result = strdata; + try { + JSONObject jsonObject = JSONObject.parseObject(strdata); + if(jsonObject != null){ + result = ""; + for (Entry entry : jsonObject.entrySet()) { + if(StringUtils.isEmpty(result)){ + result = entry.getKey() + "=" + entry.getValue(); + }else{ + result += "&" + entry.getKey() + "=" + entry.getValue(); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + +} diff --git a/src/main/java/com/nbclass/util/HttpUtil.java b/src/main/java/com/nbclass/util/HttpUtil.java new file mode 100644 index 0000000..b8488c2 --- /dev/null +++ b/src/main/java/com/nbclass/util/HttpUtil.java @@ -0,0 +1,123 @@ +package com.nbclass.util; + + +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; + +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class HttpUtil { + public static String doGet(String url) { // 无参数get请求 + return doGet(url, null); + } + + public static String doGet(String url, Map param) { // 带参数get请求 + CloseableHttpClient httpClient = HttpClients.createDefault(); // 创建一个默认可关闭的Httpclient 对象 + String resultMsg = ""; // 设置返回值 + CloseableHttpResponse response = null; // 定义HttpResponse 对象 + try { + URIBuilder builder = new URIBuilder(url); // 创建URI,可以设置host,设置参数等 + if (param != null) { + for (String key : param.keySet()) { + builder.addParameter(key, param.get(key)); + } + } + URI uri = builder.build(); + HttpGet httpGet = new HttpGet(uri); // 创建http GET请求 + response = httpClient.execute(httpGet); // 执行请求 + if (response.getStatusLine().getStatusCode() == 200) { // 判断返回状态为200则给返回值赋值 + resultMsg = EntityUtils.toString(response.getEntity(), "UTF-8"); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { // 不要忘记关闭 + try { + if (response != null) { + response.close(); + } + httpClient.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return resultMsg; + } + + public static String doPost(String url) { // 无参数post请求 + return doPost(url, null); + } + + public static String doPost(String url, Map param) {// 带参数post请求 + CloseableHttpClient httpClient = HttpClients.createDefault(); // 创建一个默认可关闭的Httpclient 对象 + CloseableHttpResponse response = null; + String resultMsg = ""; + try { + HttpPost httpPost = new HttpPost(url); // 创建Http Post请求 + if (param != null) { // 创建参数列表 + List paramList = new ArrayList(); + for (String key : param.keySet()) { + paramList.add(new BasicNameValuePair(key, param.get(key))); + } + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);// 模拟表单 + httpPost.setEntity(entity); + } + response = httpClient.execute(httpPost); // 执行http请求 + if (response.getStatusLine().getStatusCode() == 200) { + resultMsg = EntityUtils.toString(response.getEntity(), "utf-8"); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (response != null) { + response.close(); + } + httpClient.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return resultMsg; + } + + public static String doPostJson(String url, String json) { + CloseableHttpClient httpClient = HttpClients.createDefault(); + CloseableHttpResponse response = null; + String resultString = ""; + try { + HttpPost httpPost = new HttpPost(url); + StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON); + httpPost.setEntity(entity); + response = httpClient.execute(httpPost); + if (response.getStatusLine().getStatusCode() == 200) { + resultString = EntityUtils.toString(response.getEntity(), "utf-8"); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (response != null) { + response.close(); + } + httpClient.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return resultString; + } +} diff --git a/src/main/java/com/nbclass/util/HttpUtils.java b/src/main/java/com/nbclass/util/HttpUtils.java new file mode 100644 index 0000000..7947668 --- /dev/null +++ b/src/main/java/com/nbclass/util/HttpUtils.java @@ -0,0 +1,142 @@ +package com.nbclass.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map.Entry; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSONObject; + + +/** + * HTTP请求工具类 + * @author leon + * @datetime 2016年5月19日 下午6:05:53 + */ +public class HttpUtils { + + private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 默认http连接超时时间(单位:ms) + */ + public static final int HTTP_CONNECT_TIMEOUT = 5000; + + + public static JSONObject httpGet(String requestUrl){ + return httpGet(requestUrl, null); + } + + public static JSONObject httpGet(String requestUrl, String postData){ + return httpRequest(requestUrl, "GET", postData); + } + + public static JSONObject httpPost(String requestUrl){ + return httpPost(requestUrl, null); + } + + public static JSONObject httpPost(String requestUrl, String postData){ + return httpRequest(requestUrl, "POST", postData); + } + + private static JSONObject httpRequest(String requestUrl, String requestMethod, String postData) { + JSONObject jsonObject = null; + StringBuffer buffer = new StringBuffer(); + OutputStream outputStream = null; + InputStream inputStream = null; + InputStreamReader inputStreamReader = null; + BufferedReader bufferedReader = null; + HttpURLConnection httpUrlConn = null; + try{ + postData = formatPostData(postData); + // logger.info("httpRequestUrl: "+requestUrl+", postData: "+postData); + URL url = new URL(requestUrl); + httpUrlConn = (HttpURLConnection) url.openConnection(); + httpUrlConn.setDoOutput(true); + httpUrlConn.setDoInput(true); + httpUrlConn.setUseCaches(false); + //设置请求方式(GET/POST) + httpUrlConn.setRequestMethod(requestMethod); + if ("GET".equalsIgnoreCase(requestMethod)){ + httpUrlConn.connect(); + } + //当有数据需要提交时 + if (postData != null){ + outputStream = httpUrlConn.getOutputStream(); + outputStream.write(postData.getBytes("UTF-8")); + + } + //将返回的输入流转换成字符串 + inputStream = httpUrlConn.getInputStream(); + inputStreamReader = new InputStreamReader(inputStream, "UTF-8"); + bufferedReader = new BufferedReader(inputStreamReader); + for(String str = null; (str = bufferedReader.readLine()) != null;){ + buffer.append(str); + } + + jsonObject = JSONObject.parseObject(buffer.toString()); + }catch (Exception e) { + logger.error("https request error:{}", e); + }finally{ + try { + if(httpUrlConn!=null){ + httpUrlConn.disconnect(); + httpUrlConn=null; + } + } catch (Exception e) {} + try { + if(outputStream!=null){ + outputStream.close(); + outputStream=null; + } + } catch (IOException e) {} + try { + if(inputStreamReader!=null){ + inputStreamReader.close(); + inputStreamReader = null; + } + } catch (IOException e) {} + try { + if(inputStream!=null){ + inputStream.close(); + inputStream = null; + } + } catch (IOException e) {} + try { + if(bufferedReader!=null){ + bufferedReader.close(); + bufferedReader = null; + } + } catch (IOException e) {} + } + return jsonObject; + } + + private static String formatPostData(String strdata){ + String result = strdata; + try { + JSONObject jsonObject = JSONObject.parseObject(strdata); + if(jsonObject != null){ + result = ""; + for (Entry entry : jsonObject.entrySet()) { + if(StringUtils.isBlank(result)){ + result = entry.getKey() + "=" + entry.getValue(); + }else{ + result += "&" + entry.getKey() + "=" + entry.getValue(); + } + } + } + } catch (Exception e) { + } + return result; + } + +} diff --git a/src/main/java/com/nbclass/util/IpUtil.java b/src/main/java/com/nbclass/util/IpUtil.java new file mode 100644 index 0000000..2161a2e --- /dev/null +++ b/src/main/java/com/nbclass/util/IpUtil.java @@ -0,0 +1,46 @@ +package com.nbclass.util; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Leon + * @datetime 2019年4月1日 下午10:08:26 + */ +public class IpUtil { + + public static String getIpAddr(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(); + if("127.0.0.1".equals(ip)){ + //根据网卡取本机配置的IP + InetAddress inet=null; + try { + inet = InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } + ip= inet.getHostAddress(); + } + } + // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 + if(ip != null && ip.length() > 50){ + // if(ip.indexOf(",")>0){ + // ip = ip.substring(0,ip.indexOf(",")); + // } + ip = ip.substring(0,50); + } + return ip; + } + +} diff --git a/src/main/java/com/nbclass/util/JWTUtils.java b/src/main/java/com/nbclass/util/JWTUtils.java new file mode 100644 index 0000000..6208d19 --- /dev/null +++ b/src/main/java/com/nbclass/util/JWTUtils.java @@ -0,0 +1,149 @@ +package com.nbclass.util; + + +import com.alibaba.fastjson.JSON; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.activity.model.WeiXiUser; +import com.nbclass.exception.ParameterException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author admin + */ +public class JWTUtils { + + /** + * 加密密文 + */ + public static final String JWT_SECRET = "onyx"; + public static final int JWT_TTL = 60 * 60 * 1000; //millisecond + + /** + * 由字符串生成加密key + * + * @return + */ + public static SecretKey generalKey() { + String stringKey = JWT_SECRET; + // 本地的密码解码 + byte[] encodedKey = Base64.decodeBase64(stringKey); + // 根据给定的字节数组使用AES加密算法构造一个密钥 + SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); + return key; + } + + + /** + * 创建jwt + */ + public static String createJWT(String id, String issuer, String subject, long ttlMillis, Map claims) throws Exception { + + // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。 + SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; + + // 生成JWT的时间 + long nowMillis = System.currentTimeMillis(); + + // 生成签名的时候使用的秘钥secret,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。 + // 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。 + SecretKey key = generalKey(); + + // 下面就是在为payload添加各种标准声明和私有声明了 + JwtBuilder builder = Jwts.builder() // 这里其实就是new一个JwtBuilder,设置jwt的body + //.setClaims(claims) // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 + .setId(id) // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 + .setIssuedAt(new Date(nowMillis)) // iat: jwt的签发时间 + .setIssuer(issuer) // issuer:jwt签发人 + .setSubject(subject) // sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 + .signWith(signatureAlgorithm, key); // 设置签名使用的签名算法和签名使用的秘钥 + + // 设置过期时间 + if (ttlMillis >= 0) { + long expMillis = nowMillis + ttlMillis; + builder.setExpiration(new Date(expMillis)); + } + return builder.compact(); + } + + /** + * 解密jwt + */ + public static Claims parseJWT(String jwt) throws Exception { + SecretKey key = generalKey(); //签名秘钥,和生成的签名的秘钥一模一样 + Claims claims = Jwts.parser() //得到DefaultJwtParser + .setSigningKey(key) //设置签名的秘钥 + .parseClaimsJws(jwt).getBody(); //设置需要解析的jwt + return claims; + } + + /** + * 获取用户ID + * @param token + */ + public static String getUserId(String token){ + String userId = ""; + try { + Claims claims = JWTUtils.parseJWT(token); + String subject = claims.getSubject(); + JSONObject jsonObject = JSON.parseObject(subject); + userId = jsonObject.getString("id"); + } catch (Exception e) { + throw new ParameterException("token无效或token过期"); + } + return userId; + } + + + public static void main(String[] args) { + + WeiXiUser user = new WeiXiUser(); + user.setId(1L); + user.setOpenid("asdfsaf123"); + user.setNickname("张三"); + String subject = JSON.toJSONString(user); + + // 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的) + /*Map claims = new HashMap<>(); + claims.put("id", "110"); + claims.put("user_name", "zhaojun"); + claims.put("nick_name", "zhaojun22");*/ + + try { + String jwt = JWTUtils.createJWT(UUID.randomUUID().toString(), "Anson", subject, JWT_TTL,null); + System.out.println("JWT:" + jwt); + System.out.println(jwt.length()); + + System.out.println("解密"); + + Claims c = JWTUtils.parseJWT(jwt); + System.out.println(c.getId()); + System.out.println(c.getIssuedAt()); + System.out.println(c.getIssuedAt().getTime()); + Date expiration = c.getExpiration(); + System.out.println(expiration); + System.out.println(expiration.getTime()); + System.out.println(c.getSubject()); + System.out.println(c.getIssuer()); + System.out.println(c.get("id", String.class)); + System.out.println(c.get("user_name", String.class)); + System.out.println(c.get("nick_name", String.class)); + + } catch (Exception e) { + e.printStackTrace(); + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/util/MyMapper.java b/src/main/java/com/nbclass/util/MyMapper.java new file mode 100644 index 0000000..f0c7cd8 --- /dev/null +++ b/src/main/java/com/nbclass/util/MyMapper.java @@ -0,0 +1,38 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2014-2016 abel533@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.nbclass.util; + +import tk.mybatis.mapper.common.Mapper; +import tk.mybatis.mapper.common.MySqlMapper; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public interface MyMapper extends Mapper, MySqlMapper { + //TODO + //FIXME 特别注意,该接口不能被扫描到,否则会出错 +} diff --git a/src/main/java/com/nbclass/util/PageUtil.java b/src/main/java/com/nbclass/util/PageUtil.java new file mode 100644 index 0000000..5093d6b --- /dev/null +++ b/src/main/java/com/nbclass/util/PageUtil.java @@ -0,0 +1,19 @@ +package com.nbclass.util; + +/** + * + * @author Leon + * @datetime 2020年6月3日 下午7:07:17 + */ +public class PageUtil { + + /** + * i获取pgeNo,特别注意下,这个offset是起始位置,不是页码 + * @param limit 每页条数 + * @param offset 其实位置,比如limit为5,第一页offset值为0,第二页为5,第二页为10 + * @return + */ + public static Integer getPageNo(Integer limit,Integer offset){ + return offset==0 ? 1 : offset / limit + 1; + } +} diff --git a/src/main/java/com/nbclass/util/PasswordHelper.java b/src/main/java/com/nbclass/util/PasswordHelper.java new file mode 100644 index 0000000..8d46e29 --- /dev/null +++ b/src/main/java/com/nbclass/util/PasswordHelper.java @@ -0,0 +1,63 @@ +package com.nbclass.util; + +import org.apache.shiro.crypto.RandomNumberGenerator; +import org.apache.shiro.crypto.SecureRandomNumberGenerator; +import org.apache.shiro.crypto.hash.SimpleHash; +import org.apache.shiro.util.ByteSource; + +import com.nbclass.system.model.User; + +/** + * 密码加密 + * @author Leon + * @datetime 2019年4月1日 下午10:09:02 + */ +public class PasswordHelper { + + private static RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator(); + private static String algorithmName = "md5"; + private static int hashIterations = 2; + + public static void encryptPassword(User user) { + user.setSalt(randomNumberGenerator.nextBytes().toHex()); + String newPassword = new SimpleHash(algorithmName, user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), hashIterations).toHex(); + user.setPassword(newPassword); + } + + public static String getPassword(User user){ + String encryptPassword = new SimpleHash(algorithmName,user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()),hashIterations).toHex(); + return encryptPassword; + } + + /** + * 使用BCrypt对密码进行加密 + * @param password + * @return + */ + public static String encryptPwdByBCrypt(String password){ + return BCrypt.hashpw(password, BCrypt.gensalt()); + } + + /** + * BCrypt 密码验证 + * @param password 明文 + * @param ciphertext 密文 + * @return if equal ? true : false + */ + public static boolean checkPwdByBCrypt(String password, String ciphertext){ + return BCrypt.checkpw(password, ciphertext); + } + + public static void main(String[] args) { + User user = new User(); + user.setUsername("admin"); + user.setPassword("szxgl.com"); + encryptPassword(user); + System.out.println("salt="+user.getSalt()); + System.out.println("password="+user.getPassword()); + + String pwd1 = encryptPwdByBCrypt("123456"); + boolean result = checkPwdByBCrypt("123456", pwd1); + System.out.println("pwd1="+pwd1+", result="+result); + } +} diff --git a/src/main/java/com/nbclass/util/ResultUtil.java b/src/main/java/com/nbclass/util/ResultUtil.java new file mode 100644 index 0000000..2a9cd77 --- /dev/null +++ b/src/main/java/com/nbclass/util/ResultUtil.java @@ -0,0 +1,55 @@ +package com.nbclass.util; + +import java.util.List; + +import com.nbclass.vo.base.PageResultVo; +import com.nbclass.vo.base.ResponseVo; + +import static com.nbclass.util.CoreConst.STATUS_MSG; + +/** + * + * @author Leon + * @datetime 2019年4月1日 下午2:44:52 + */ +public class ResultUtil{ + + public static ResponseVo success(){ + return vo(CoreConst.STATUS_SUCCESS,null,null); + } + + public static ResponseVo success(String msg){ + return vo(CoreConst.STATUS_SUCCESS, msg,null); + } + + public static ResponseVo success(Object data){ + return vo(CoreConst.STATUS_SUCCESS, STATUS_MSG, data); + } + + public static ResponseVo success(String msg, Object data){ + return vo(CoreConst.STATUS_SUCCESS, msg, data); + } + + public static ResponseVo error(){ + return vo(CoreConst.STATUS_ERROR, null, null); + } + + public static ResponseVo error(String msg){ + return vo(CoreConst.STATUS_ERROR, msg, null); + } + + public static ResponseVo error(String msg, Object data){ + return vo(CoreConst.STATUS_ERROR, msg,data); + } + + public static PageResultVo table( List list, Long total){ + return new PageResultVo(list, total); + } + + public static ResponseVo vo(Integer status, String message, Object data) { + return new ResponseVo(status, message, data); + } + + + +} diff --git a/src/main/java/com/nbclass/util/ShortenUtil.java b/src/main/java/com/nbclass/util/ShortenUtil.java new file mode 100644 index 0000000..4669cd1 --- /dev/null +++ b/src/main/java/com/nbclass/util/ShortenUtil.java @@ -0,0 +1,127 @@ +package com.nbclass.util; + +import java.util.Stack; + +/** + * + * @author Leon + * @datetime 2019年4月11日 下午3:40:42 + */ +public class ShortenUtil { + + private static final char[] array = { + 'a', 'z', 'm', 'b', 'v', 'o', 'h', 'c', 'w', 'k', 'y', 'i', 'f', 's', 'l', 't', 'd', 'x', 'n', 'g', 'r', 'p', 'e', 'u', 'q', 'j', + '0', '8', '4', '9', '5', '2', '6', '3', '7', '1', + 'P', 'G', 'A', 'Z', 'R', 'I', 'F', 'C', 'T', 'W', 'O', 'J', 'U', 'Y', 'E', 'N', 'V', 'H', 'B', 'M', 'S', 'L', 'X', 'D', 'Q', 'K' + }; + + //private static final char[] array1 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); + + public static void main(String[] args) { + long id = 200; + String str = encode(id); + System.out.println("62System=" +str); + System.out.println("10System=" +decode(str)); + } + + /** + * _10_to_62 + * @param number + * @return + */ + public static String encode(long number){ + number = 500000+number; + Long rest=number; + Stack stack=new Stack(); + StringBuilder result=new StringBuilder(0); + while(rest!=0){ + stack.add(array[new Long((rest-(rest/62)*62)).intValue()]); + rest=rest/62; + } + for(;!stack.isEmpty();){ + result.append(stack.pop()); + } + return result.toString(); + } + + /** + * _10_to_62 + * 将10进制转化为62进制 + * @param number + * @param length 转化成的62进制长度,不足length长度的话高位补0,否则不改变什么 + * @return + */ + public static String encode(long number, int length){ + Long rest=number; + Stack stack=new Stack(); + StringBuilder result=new StringBuilder(0); + while(rest!=0){ + stack.add(array[new Long((rest-(rest/62)*62)).intValue()]); + rest=rest/62; + } + for(;!stack.isEmpty();){ + result.append(stack.pop()); + } + int result_length = result.length(); + StringBuilder temp0 = new StringBuilder(); + for(int i = 0; i < length - result_length; i++){ + temp0.append('0'); + } + return temp0.toString() + result.toString(); + } + + public static long decode(String sixty_str){ + int multiple=1; + long result=0; + Character c; + for(int i=0;i= 0; i-- ) { + int num = 0; + if ( ident[i] > 48 && ident[i] <= 57 ) { + num = ident[i] - 48; + } + else if ( ident[i] >= 65 && ident[i] <= 90 ) { + num = ident[i] - 65 + 10; + } + else if ( ident[i] >= 97 && ident[i] <= 122 ) { + num = ident[i] - 97 + 10 + 26; + } + keisu = (int) java.lang.Math.pow( base, cnt ); + decimal += num * keisu; + cnt++; + } + return String.format( "%08d", decimal ); + } + +} diff --git a/src/main/java/com/nbclass/util/UUIDUtil.java b/src/main/java/com/nbclass/util/UUIDUtil.java new file mode 100644 index 0000000..db975fb --- /dev/null +++ b/src/main/java/com/nbclass/util/UUIDUtil.java @@ -0,0 +1,60 @@ +package com.nbclass.util; + +import java.util.UUID; + +/** + * + * @author Leon + * @datetime 2019年4月1日 下午10:12:05 + */ +public class UUIDUtil { + + private static final int SHORT_LENGTH = 8; + + public static String uuid() { + String str = UUID.randomUUID().toString(); + String temp = str.replace("-",""); + return temp; + } + + public static String getUniqueIdByUUId() { + //最大支持1-9个集群机器部署 + int machineId = 1; + int hashCodeV = UUID.randomUUID().toString().hashCode(); + if(hashCodeV < 0) { + hashCodeV = - hashCodeV; + } + // 0 代表前面补充0 + // 4 代表长度为4 + // d 代表参数为正数型 + return machineId + String.format("%015d", hashCodeV); + } + public static void main(String[] args) { + System.out.println(getUniqueIdByUUId()); + System.out.println(uuid()); + } + + + + public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", + "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", + "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z" }; + + + public static String generateShortUuid() { + StringBuffer shortBuffer = new StringBuffer(); + String uuid = UUID.randomUUID().toString().replace("-", ""); + for (int i = 0; i < SHORT_LENGTH; i++) { + String str = uuid.substring(i * 4, i * 4 + 4); + int x = Integer.parseInt(str, 16); + shortBuffer.append(chars[x % 0x3E]); + } + return shortBuffer.toString(); + + } + + +} diff --git a/src/main/java/com/nbclass/util/WebUtils.java b/src/main/java/com/nbclass/util/WebUtils.java new file mode 100644 index 0000000..756c61c --- /dev/null +++ b/src/main/java/com/nbclass/util/WebUtils.java @@ -0,0 +1,80 @@ +package com.nbclass.util; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; + +public class WebUtils { + + /** + * cookie默认保存时长 (1天) + */ + private static final int COOKIE_DEFAULT_MAXAGE = 3600 * 24 * 1; + + + public static void addCookie(HttpServletResponse response, String name, String value){ + addCookie(response, name, value, COOKIE_DEFAULT_MAXAGE); + } + + public static void addCookie(HttpServletResponse response, String name, String value, int maxage){ + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + cookie.setMaxAge(maxage > 0 ? maxage : COOKIE_DEFAULT_MAXAGE); + response.addCookie(cookie); + } + + public static void removeCookie(HttpServletRequest request, HttpServletResponse response, String name){ + if(StringUtils.isBlank(name))return; + try { + name = name.trim(); + Cookie[] cookies = request.getCookies(); + if(cookies == null || cookies.length == 0)return; + for (Cookie cookie : cookies) { + if (cookie.getName().contains(name)) { + String cookieName = cookie.getName(); + Cookie newCookie = new Cookie(cookieName, null); + newCookie.setMaxAge(0); + newCookie.setPath("/"); // 根据你创建cookie的路径进行填写 + response.addCookie(newCookie); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static String getValueFromCookie(HttpServletRequest request, String cookieName) { + if(cookieName==null || cookieName.trim().equals(""))return null; + Cookie[] cookies = request.getCookies(); + if (cookies != null && cookies.length > 0) { + for (int i = 0; i < cookies.length; i++) { + Cookie item = cookies[i]; + String name = item.getName(); + if (cookieName.equals(name)) { + return item.getValue(); + } + } + } + return null; + } + + public static String getReqIpAddr(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(); + } + if(ip == null){ + ip = "未知IP"; + } + return ip; + } + +} diff --git a/src/main/java/com/nbclass/vo/ChangePasswordVo.java b/src/main/java/com/nbclass/vo/ChangePasswordVo.java new file mode 100644 index 0000000..2a4e936 --- /dev/null +++ b/src/main/java/com/nbclass/vo/ChangePasswordVo.java @@ -0,0 +1,36 @@ +package com.nbclass.vo; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class ChangePasswordVo { + String oldPassword; + String newPassword; + String confirmNewPassword; + + public String getOldPassword() { + return oldPassword; + } + + public void setOldPassword(String oldPassword) { + this.oldPassword = oldPassword; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } + + public String getConfirmNewPassword() { + return confirmNewPassword; + } + + public void setConfirmNewPassword(String confirmNewPassword) { + this.confirmNewPassword = confirmNewPassword; + } +} diff --git a/src/main/java/com/nbclass/vo/PermissionTreeListVo.java b/src/main/java/com/nbclass/vo/PermissionTreeListVo.java new file mode 100644 index 0000000..a0eac9f --- /dev/null +++ b/src/main/java/com/nbclass/vo/PermissionTreeListVo.java @@ -0,0 +1,63 @@ +package com.nbclass.vo; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class PermissionTreeListVo { + private Integer id; + private String permissionId; + private String name; + private Integer parentId; + private Boolean open=true; + private Boolean checked=false; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getPermissionId() { + return permissionId; + } + + public void setPermissionId(String permissionId) { + this.permissionId = permissionId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public Boolean getOpen() { + return open; + } + + public void setOpen(Boolean open) { + this.open = open; + } + + public Boolean getChecked() { + return checked; + } + + public void setChecked(Boolean checked) { + this.checked = checked; + } +} diff --git a/src/main/java/com/nbclass/vo/UserOnlineVo.java b/src/main/java/com/nbclass/vo/UserOnlineVo.java new file mode 100644 index 0000000..42dcddb --- /dev/null +++ b/src/main/java/com/nbclass/vo/UserOnlineVo.java @@ -0,0 +1,87 @@ +package com.nbclass.vo; + +import java.io.Serializable; +import java.util.Date; + +/** + * @version V1.0 + * @date 2018年7月20日 + * @author superzheng + */ +public class UserOnlineVo implements Serializable { + + private static final long serialVersionUID = 1L; + + private String sessionId; + private String username; + private String host; + private Date startTime; + private Date lastAccess; + private Date lastLoginTime; + private long timeout; + private boolean sessionStatus = Boolean.TRUE; + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getLastAccess() { + return lastAccess; + } + + public void setLastAccess(Date lastAccess) { + this.lastAccess = lastAccess; + } + + public Date getLastLoginTime() { + return lastLoginTime; + } + + public void setLastLoginTime(Date lastLoginTime) { + this.lastLoginTime = lastLoginTime; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public boolean isSessionStatus() { + return sessionStatus; + } + + public void setSessionStatus(boolean sessionStatus) { + this.sessionStatus = sessionStatus; + } +} diff --git a/src/main/java/com/nbclass/vo/UserSessionVo.java b/src/main/java/com/nbclass/vo/UserSessionVo.java new file mode 100644 index 0000000..663dffc --- /dev/null +++ b/src/main/java/com/nbclass/vo/UserSessionVo.java @@ -0,0 +1,27 @@ +package com.nbclass.vo; + +/** + * @version V1.0 + * @date 2018年7月20日 + * @author superzheng + */ +public class UserSessionVo { + private String sessionId; + private String username; + + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/src/main/java/com/nbclass/vo/base/PageResultVo.java b/src/main/java/com/nbclass/vo/base/PageResultVo.java new file mode 100644 index 0000000..cff7556 --- /dev/null +++ b/src/main/java/com/nbclass/vo/base/PageResultVo.java @@ -0,0 +1,34 @@ +package com.nbclass.vo.base; + +import java.util.List; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class PageResultVo { + private List rows; + private Long total; + + public List getRows() { + return rows; + } + + public void setRows(List rows) { + this.rows = rows; + } + + public Long getTotal() { + return total; + } + + public void setTotal(Long total) { + this.total = total; + } + + public PageResultVo(List rows ,Long total) { + this.total = total; + this.rows = rows; + } + +} diff --git a/src/main/java/com/nbclass/vo/base/ResponseVo.java b/src/main/java/com/nbclass/vo/base/ResponseVo.java new file mode 100644 index 0000000..2b5aea7 --- /dev/null +++ b/src/main/java/com/nbclass/vo/base/ResponseVo.java @@ -0,0 +1,43 @@ +package com.nbclass.vo.base; +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +public class ResponseVo { + private Integer ret; + private String msg; + private T data; + + public Integer getRet() { + return ret; + } + + public void setRet(Integer ret) { + this.ret = ret; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public ResponseVo(Integer ret, String msg, T data) { + this.ret = ret; + this.msg = msg; + this.data = data; + } + + +} diff --git a/src/main/java/com/nbclass/vo/base/Result.java b/src/main/java/com/nbclass/vo/base/Result.java new file mode 100644 index 0000000..4b7dcfb --- /dev/null +++ b/src/main/java/com/nbclass/vo/base/Result.java @@ -0,0 +1,58 @@ +package com.nbclass.vo.base; + +import com.nbclass.util.CoreConst; + +import lombok.Data; + +import java.util.Map; + +/** + * 返回数据对象 + * @author Leon + * @datetime 2020年1月10日 下午2:12:08 + */ +@Data +public class Result { + + private int ret; + private String msg; + private Object data; + private Map map; + + public Result() { + } + + public Result(int ret, String msg, Object data) { + this.ret = ret; + this.msg = msg; + this.data = data; + } + + public static Result success() { + return new Result(CoreConst.STATUS_SUCCESS, null, null); + } + + public static Result success(Object data) { + return new Result(CoreConst.STATUS_SUCCESS, null, data); + } + public static Result success(Map map) { + return new Result(CoreConst.STATUS_SUCCESS, null, map); + } + + public static Result success(String msg, Object data) { + return new Result(CoreConst.STATUS_SUCCESS, msg, data); + } + + public static Result error() { + return new Result(CoreConst.STATUS_ERROR, null, null); + } + + public static Result error(String msg) { + return new Result(CoreConst.STATUS_ERROR, msg, null); + } + + public static Result error(int code, String msg) { + return new Result(code, msg, null); + } + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..92ee609 --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,42 @@ +spring: + # 数据库配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://39.108.110.167:13376/xgl_cases?autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 + username: root + password: 'szxgl@2001B' + druid: + initial-size: 3 + min-idle: 1 + max-active: 500 + max-wait: 60000 # 获取连接等待超时的时间 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒 + test-while-idle: true # 申请连接的时候检测, 建议配置为true,不影响性能 + test-on-borrow: false # 申请连接时执行validationQuery检测连接是否有效 + test-on-return: false # 归还连接时执行validationQuery检测连接是否有效 + validation-query: SELECT 1 + log-abandoned: true # 关闭abanded连接时输出错误日志 + remove-abandoned: false # 是否开启连接泄漏监测,对性能会有一些影响,建议怀疑存在泄漏之后再打开 + remove-abandoned-timeout: 1800 # 1800秒,也就是30分钟 + filters: stat, wall + web-stat-filter: + exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*' + stat-view-servlet: + enabled: true + url-pattern: /druid/* + login-username: admin + login-password: szxgl.com + data: + elasticsearch: + repositories: + enabled: true # 开启 Elasticsearch仓库(默认值:true) + client: + reactive: + endpoints: 127.0.0.1:9200 + connection-timeout: 5000 + socket-timeout: 5000 + + + diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..9f2ee41 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,42 @@ +spring: + # 数据库配置 + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://rm-wz9vza84pe0hb338kbo.mysql.rds.aliyuncs.com:3306/xgl_cases?autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 + username: root + password: 'lyg8266@Qiween#com' + druid: + initial-size: 3 + min-idle: 1 + max-active: 500 + max-wait: 60000 # 获取连接等待超时的时间 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒 + test-while-idle: true # 申请连接的时候检测, 建议配置为true,不影响性能 + test-on-borrow: false # 申请连接时执行validationQuery检测连接是否有效 + test-on-return: false # 归还连接时执行validationQuery检测连接是否有效 + validation-query: SELECT 1 + log-abandoned: true # 关闭abanded连接时输出错误日志 + remove-abandoned: false # 是否开启连接泄漏监测,对性能会有一些影响,建议怀疑存在泄漏之后再打开 + remove-abandoned-timeout: 1800 # 1800秒,也就是30分钟 + filters: stat, wall + web-stat-filter: + exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*' + stat-view-servlet: + enabled: true + url-pattern: /druid/* + login-username: admin + login-password: szxgl.com + data: + elasticsearch: + repositories: + enabled: true # 开启 Elasticsearch仓库(默认值:true) + client: + reactive: + endpoints: 127.0.0.1:9200 + connection-timeout: 5000 + socket-timeout: 5000 + + + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..a956fc3 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,71 @@ +# tomcat配置 +server: + port: 8081 + servlet: + context-path: /cases + jsp: + init-parameters: + development: true # 开发者模式,不加这个修改jsp内容不会实时生效 + tomcat: + uri-encoding: utf-8 + remoteip: + remote-ip-header: x-forwarded-for + protocol-header: x-forwarded-proto + port-header: X-Forwarded-Port + +logging: + level: + root: INFO + com.nbclass: DEBUG + tk.mybatis: DEBUG + org.springframework: INFO + +spring: + profiles: + active: dev # 环境 dev|test|prod + main: + allow-bean-definition-overriding: true # 当遇到相同名字bean的时候,是否允许覆盖注册 + + thymeleaf: + # 关闭缓存,相当于调试模式,修改模板直接生效 + cache: false + mode: LEGACYHTML5 + #enabled: false + messages: + encoding: UTF-8 + basename: i18n/messages + use-code-as-default-message: true + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non-null # 响应JSON数据过滤NULL值 + servlet: + multipart: + enabled: true # 开启 multipart上传功能 + max-file-size: 100MB # 最大单文件大小 + max-request-size: 200MB # 最大请求大小 + file-size-threshold: 5KB # 文件写入磁盘的阈值 +# mvc: +# view: +# prefix: /WEB-INF/ +# suffix: .jsp +# static-path-pattern: /** +mybatis: + type-aliases-package: com.nbclass.*.model + mapper-locations: classpath:mapper/**/*.xml + configuration: + # 进行自动映射时,数据以下划线命名,如数据库返回的"order_address"命名字段是否映射为class的"orderAddress"字段。默认为false + map-underscore-to-camel-case: false + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl +mapper: + mappers: com.nbclass.util.MyMapper + not-empty: false + identity: MYSQL +pagehelper: + helperDialect: mysql + reasonable: false # 分页参数合理化,启用时,如果参数超出范围也会返回数据,禁用时返回空 + supportMethodsArguments: true + params: count=countSql + + + diff --git a/src/main/resources/generator/generatorConfig.xml b/src/main/resources/generator/generatorConfig.xml new file mode 100644 index 0000000..3ce2a1b --- /dev/null +++ b/src/main/resources/generator/generatorConfig.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
\ No newline at end of file diff --git a/src/main/resources/log4j2-spring.xml b/src/main/resources/log4j2-spring.xml new file mode 100644 index 0000000..5c32e90 --- /dev/null +++ b/src/main/resources/log4j2-spring.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/activity/CaseTypeMapper.xml b/src/main/resources/mapper/activity/CaseTypeMapper.xml new file mode 100644 index 0000000..09a5be8 --- /dev/null +++ b/src/main/resources/mapper/activity/CaseTypeMapper.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/activity/CommentMapper.xml b/src/main/resources/mapper/activity/CommentMapper.xml new file mode 100644 index 0000000..6972371 --- /dev/null +++ b/src/main/resources/mapper/activity/CommentMapper.xml @@ -0,0 +1,604 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into comment + ( + content_id, + user_id, + comment_content, + create_time + ) + values + ( + #{contentId}, + #{userId}, + #{commentContent}, + now() + ) + + + + + + insert into praise + ( + comment_id, + praise_id, + user_id, + create_time + ) + values + ( + #{commentId}, + #{praiseId}, + #{userId}, + now() + ) + + + + update comment set + + praiseno = praiseno + 1 + + where id = #{commentId} + + + + update content set + + praiseno = praiseno+1 + + where id = #{contentId} + + + + insert into comment + ( + content_id, + user_id, + reply_id, + comment_id, + comment_content, + create_time + ) + values + ( + #{contentId}, + #{userId}, + #{replyId}, + #{commentId}, + #{commentContent}, + now() + ) + + + + insert into report + ( + comment_id, + report_id, + report_content, + report_type, + create_time + ) + values + ( + #{commentId}, + #{reportId}, + #{reportContent}, + #{reportType}, + now() + ) + + + + + + insert into label_logger + ( + content_id, + user_id, + logger, + create_time + ) + values + ( + #{contentId}, + #{userId}, + #{logger}, + now() + ) + + + + insert into favorites_folder + ( + name, + user_id, + create_time + ) + values + ( + #{name}, + #{userId}, + now() + ) + + + + + + insert into folder_resources + ( + folder_id, + content_id, + create_time + ) + values + ( + #{folderId}, + #{contentId}, + now() + ) + + + + update favorites_folder set + + total_collections = total_collections+1 + + where id = #{folderId} + + + + + + + + delete from folder_resources where folder_id = #{folderId} and content_id = #{contentId} + + + + + + + + + + delete from praise where comment_id = #{commentId} and praise_id = #{userId} + + + + update comment set + + praiseno = praiseno - 1 + + where id = #{commentId} + + + + update comment set + + reply_number = reply_number + 1 + + where id = #{commentId} + + + + + + + + + + update content set + + collection_number = collection_number + 1 + + where id = #{contentId} + + + + update content set + + collection_number = collection_number - 1 + + where id = #{contentId} + + + + update content set + + label_feedback = label_feedback + 1 + + where id = #{contentId} + + + + + + insert into scoring_table + ( + content_id, + score, + user_id, + create_time + ) + values + ( + #{contentId}, + #{score}, + #{userId}, + now() + ) + + + diff --git a/src/main/resources/mapper/activity/ContentMapper.xml b/src/main/resources/mapper/activity/ContentMapper.xml new file mode 100644 index 0000000..ed2fc33 --- /dev/null +++ b/src/main/resources/mapper/activity/ContentMapper.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + delete from content where id in + + #{item} + + + + + + + + + + + + + + delete from content_tags WHERE cid = #{cid} + + + + INSERT IGNORE INTO content_tags (cid, tid) + values + + (#{item.cid}, #{item.tid} ) + + + + + delete from content_images WHERE cid = #{cid} + + + + INSERT IGNORE INTO content_images (cid, imgurl, sort, ori_name) + values + + (#{item.cid}, #{item.imgurl}, #{item.sort}, #{item.ori_name} ) + + + + + + UPDATE content + + + viewno = IF(viewno IS NULL, 1, viewno+1) + + + praiseno = IF(praiseno IS NULL, 1, praiseno+1) + + + WHERE id = #{id} + + + + + + insert ignore into content_tags + ( + createtime, + cid, + tid + ) + values + ( + now(), + #{cid}, + #{tid} + ) + + + + + + delete from content_tags where cid in + + #{id} + + + + + update content set + + comments = comments + 1 + + where id = #{contentId} + + + + insert into scoring_table + ( + content_id, + score, + user_id, + create_time + ) + values + ( + #{contentId}, + #{score}, + #{userId}, + now() + ) + + + + + + + + insert into application + ( + content_id, + user_id, + type, + create_time + ) + values + ( + #{contentId}, + #{userId}, + #{type}, + now() + ) + + + + insert into `check` + ( + application_id, + check_id, + create_time + ) + values + ( + #{applicationId}, + #{checkId}, + now() + ) + + + + + + delete from application where id = #{applicationId} + + + + delete from `check` where application_id = #{applicationId} + + + + update application set + + status = 1 + + where id = #{applicationId} + + + + update `check` set + + status = 1 + + where application_id = #{applicationId} + + + + + + + + update `check` set + + status = #{status} + + where application_id = #{applicationId} and check_id = #{checkId} + + + + + + update content set + + `release` = #{release} + + where id = #{contentId} + + + + update application set + + status = #{status} + + where id = #{id} + + + + + diff --git a/src/main/resources/mapper/activity/DataDictItemMapper.xml b/src/main/resources/mapper/activity/DataDictItemMapper.xml new file mode 100644 index 0000000..68da645 --- /dev/null +++ b/src/main/resources/mapper/activity/DataDictItemMapper.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/activity/DataDictMapper.xml b/src/main/resources/mapper/activity/DataDictMapper.xml new file mode 100644 index 0000000..bd0a250 --- /dev/null +++ b/src/main/resources/mapper/activity/DataDictMapper.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + delete from data_dict where id in + + #{item} + + + + + + + + + + + + + INSERT ignore INTO data_dict_item(dictid, `value`, `name`) VALUES (#{dictid}, #{value}, #{name} ) + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/activity/WXMapper.xml b/src/main/resources/mapper/activity/WXMapper.xml new file mode 100644 index 0000000..b436e7e --- /dev/null +++ b/src/main/resources/mapper/activity/WXMapper.xml @@ -0,0 +1,101 @@ + + + + + + + + insert into weixi_user + ( + openid, + nickname, + sex, + province, + city, + headimgurl, + unionid, + create_time, + update_time + ) + values + ( + #{openid}, + #{nickname}, + #{sex}, + #{province}, + #{city}, + #{headimgurl}, + #{unionid}, + now(), + now() + ) + + + + + + + + + + update weixi_user + + + birthday = #{birthday}, + + + occupation_id = #{occupationId}, + + + company = #{company}, + + + mailbox = #{mailbox}, + + + brief_introduction = #{briefIntroduction}, + + + update_time = #{updateTime} + + + where id = #{id} + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/activity/WxDepartmentMapper.xml b/src/main/resources/mapper/activity/WxDepartmentMapper.xml new file mode 100644 index 0000000..1a3a643 --- /dev/null +++ b/src/main/resources/mapper/activity/WxDepartmentMapper.xml @@ -0,0 +1,18 @@ + + + + + + insert into wx_department (id, `name`, parentid, `order`) + values + + (#{o.id}, #{o.name}, #{o.parentid}, #{o.order}) + + ON DUPLICATE KEY UPDATE `name` = VALUES(name), parentid = VALUES(parentid), `order` = VALUES(`order`) + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/activity/WxUserMapper.xml b/src/main/resources/mapper/activity/WxUserMapper.xml new file mode 100644 index 0000000..9a054a1 --- /dev/null +++ b/src/main/resources/mapper/activity/WxUserMapper.xml @@ -0,0 +1,66 @@ + + + + + + insert into wx_user (userid, name, mobile, department, position, gender, email, weixinid, + isleader, avatar, english_name, status) + values (#{userid}, #{name}, #{mobile}, #{department}, #{position}, #{gender}, #{email}, #{weixinid}, + #{isleader}, #{avatar}, #{english_name}, #{status}) + + + + update wx_user set name = #{name}, mobile = #{mobile}, department = #{department} + + , position = #{position} + + + , gender = #{gender} + + + , email = #{email} + + + , weixinid = #{weixinid} + + + , isleader = #{isleader} + + + , avatar = #{avatar} + + + , english_name = #{english_name} + + + , `status` = #{status} + + where userid = #{userid} + + + + + + + + + + delete from wx_user where userid not in + + #{userid} + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/system/PermissionMapper.xml b/src/main/resources/mapper/system/PermissionMapper.xml new file mode 100644 index 0000000..22ecbe2 --- /dev/null +++ b/src/main/resources/mapper/system/PermissionMapper.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + id, permission_id, name, description, url, perms, parent_id, type, order_num, icon, status, create_time, update_time + + + + + + + + + + + + + + + + UPDATE + permission + SET + status=#{status}, update_time=now() + where + permission_id = #{permissionId} + + + + + + UPDATE + permission + SET + name=#{name},description=#{description},url=#{url},perms=#{perms}, parent_id=#{parentId}, order_num=#{orderNum}, icon=#{icon}, update_time=now() + where + permission_id = #{permissionId} + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/system/RoleMapper.xml b/src/main/resources/mapper/system/RoleMapper.xml new file mode 100644 index 0000000..b82d81d --- /dev/null +++ b/src/main/resources/mapper/system/RoleMapper.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + id, role_id, name, description, status, create_time, update_time + + + + + + + + UPDATE + role + SET + status=#{status}, update_time=now() + where + role_id in + + #{item} + + + + + UPDATE + role + SET + name=#{name},description=#{description}, update_time=now() + where + role_id = #{role_id} + + + \ No newline at end of file diff --git a/src/main/resources/mapper/system/RolePermissionMapper.xml b/src/main/resources/mapper/system/RolePermissionMapper.xml new file mode 100644 index 0000000..4b3ad35 --- /dev/null +++ b/src/main/resources/mapper/system/RolePermissionMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + id, role_id, permission_id + + \ No newline at end of file diff --git a/src/main/resources/mapper/system/SysLogMapper.xml b/src/main/resources/mapper/system/SysLogMapper.xml new file mode 100644 index 0000000..eddd55e --- /dev/null +++ b/src/main/resources/mapper/system/SysLogMapper.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + delete from sys_log where id in + + #{item} + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/system/UserMapper.xml b/src/main/resources/mapper/system/UserMapper.xml new file mode 100644 index 0000000..29c9407 --- /dev/null +++ b/src/main/resources/mapper/system/UserMapper.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + user.id, user.user_id, user.username, user.password, user.salt, user.email, user.phone, user.sex, user.age, user.status, + user.create_time, user.update_time, user.last_login_time + + + + + + + + + + update user SET last_login_time = now() where id = #{id,jdbcType=BIGINT} + + + + update user SET email=#{email},phone=#{phone},sex=#{sex},age=#{age},update_time = now() where user_id = #{userId,jdbcType=BIGINT} + + + + UPDATE + user + SET + status=#{status}, update_time=now() + where + user_id in + + #{item} + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/system/UserRoleMapper.xml b/src/main/resources/mapper/system/UserRoleMapper.xml new file mode 100644 index 0000000..c8280dd --- /dev/null +++ b/src/main/resources/mapper/system/UserRoleMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + id, user_id, role_id + + \ No newline at end of file diff --git a/src/main/resources/static/css/cases/content.css b/src/main/resources/static/css/cases/content.css new file mode 100644 index 0000000..9cd4b79 --- /dev/null +++ b/src/main/resources/static/css/cases/content.css @@ -0,0 +1,13 @@ + +#fileInput{position:absolute;left:0;top:0;right:0;bottom:0;opacity:0;} + +.fileList_parent{margin:10px 0px;} +.fileList_parent th{background:#dadada;font-weight:bold;} +.fileList_parent th,.fileList_parent td{padding:5px;} +.fileList tr:nth-of-type(2n){background:#dadada;} +.fileList .td-filename{max-width: 90px;} + +.progressParent{width:200px;height:20px;border-radius:5px;background:#ccc;overflow:hidden;position:relative;} +.progress{width:0%;height:20px;background:#a5d24a;} +.progressNum{display:inline-block;width:100%;height:20px;text-align:center;line-height:20px;color:#fff;position:absolute;left:0;top:0;} + diff --git a/src/main/resources/static/css/common.css b/src/main/resources/static/css/common.css new file mode 100644 index 0000000..71a7c41 --- /dev/null +++ b/src/main/resources/static/css/common.css @@ -0,0 +1,441 @@ +.mt0{ + margin-top: 0px; +} +.mt10{ + margin-top: 10px; +} +.mt20{ + margin-top: 20px; +} +.mr0{ + margin-right: 0px; +} +.mr10{ + margin-right: 10px; +} +.mr20{ + margin-right: 20px; +} +.mb0{ + margin-bottom: 0px; +} +.mb10{ + margin-bottom: 10px; +} +.mb20{ + margin-bottom: 20px; +} +.ml0{ + margin-left: 0px; +} +.ml10{ + margin-left: 10px; +} +.ml20{ + margin-left: 20px; +} +.pb-15{ + padding-bottom: 15px; +} +.pr-5{ + padding-right: 5px; +} +.pl-15{ + padding-left: 15px; +} +.btn { + border-width: 0; + padding: 7px 14px; + font-size: 14px; + outline: none !important; + background-image: none !important; + filter: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + text-shadow: none; +} + +.red{ + color: red; +} + +.blue.btn { + color: white; + background-color: #4b8df8; +} + +.cursor-pointer{ + cursor: pointer; +} + +.display-none{ + display: none; +} +.display-block{ + display: block; +} +.navbar-custom-menu>.navbar-nav>li> .dropdown-left{ + right: auto; +} +/*.dropdown-menu>li>a { + padding: 3px 14px; +}*/ +.dropdown-menu{ + min-width: 100px; +} +.dropdown-menu .divider { + margin: 1px 0; +} +.navbar-nav>.user-menu>.dropdown-menu { + width: auto; +} +.form-group label{ + font-weight: 500; + color:#353535; +} +.control-sidebar{ + min-height: 100%; +} +.content-wrapper{ + padding-bottom: 15px; +} +.fixed .control-sidebar{ + position: fixed; + max-height: 100%; + overflow: auto; + padding-bottom: 50px; +} + +/*旋转*/ +.icon-spin { + display: inline-block; + -webkit-animation: spin 1s infinite linear; + animation: spin 1s infinite linear; +} +.icon-spin-music { + display: inline-block; + -webkit-animation: spin 5s infinite linear; + animation: spin 5s infinite linear; +} +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0) + } + + 100% { + -webkit-transform: rotate(359deg) + } +} + +@keyframes spin { + 0% { + -webkit-transform: rotate(0); + transform: rotate(0) + } + + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg) + } +} + +.bootstrap-table .table thead>tr>th { + background-color: #f5f5f5; +} +.fixed-table-pagination .pagination-detail, .fixed-table-pagination div.pagination { + margin-left: 10px; +} +/*layer弹出框自定义样式示例*/ +body .demo-class .layui-layer-title{background:#c00; color:#fff; border: none;} +body .demo-class .layui-layer-btn{border-top:1px solid #E9E7E7} +body .demo-class .layui-layer-btn a{background:#2e8ded;} +body .demo-class .layui-layer-btn .layui-layer-btn1{background:#f1f1f1;} + +.fontawesome-icon-list div i{ + color: #4969b3; +} + +.table thead>tr>th{ + background-color: #ecf0f5!important; +} + +.grid-table-toolbar{ + margin-top: 10px; + margin-bottom: 10px; +} + +/*.jax-table .fixed-table-toolbar .bs-bars, .fixed-table-toolbar .columns, .fixed-table-toolbar .search { + margin-top: 0px!important; +}*/ + +.nav-tabs-custom>.nav-tabs>li.active.danger{ + border-top-color: #dd4b39 +} +.nav>li>.jax-tab-a { + padding: 8px 15px; +} + +.jax-nav-tabs{ + background-color: #f9f9f9; +} +/*菜单*/ +.sidebar-menu li.li-close>a>.fa-angle-left, .sidebar-menu li.li-close>a>.pull-right-container>.fa-angle-left { + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + -o-transform: rotate(0deg); + transform: rotate(0deg); +} + +.sidebar-menu li a{ + -webkit-transition: all 0.2s ; + -ms-transition: all 0.2s ; + -o-transition: all 0.2s ; + transition: all 0.2s; +} +.sidebar-menu li.li-open>a>.fa-angle-left, .sidebar-menu li.li-open>a>.pull-right-container>.fa-angle-left { + -webkit-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + transform: rotate(-90deg); +} + +.sidebar-menu>li.li-open>a:hover { + color: #fff; + background: #1e282c; +} + +.skin-blue-light .sidebar-menu>li.li-open>a:hover { + color: #000; + background: #f4f4f5 +} +.skin-black-light .sidebar-menu>li.li-open>a:hover { + color: #000; + background: #f4f4f5 +} +.skin-purple-light .sidebar-menu>li.li-open>a:hover { + color: #000; + background: #f4f4f5 +} +.skin-green-light .sidebar-menu>li.li-open>a:hover { + color: #000; + background: #f4f4f5 +} +.skin-red-light .sidebar-menu>li.li-open>a:hover { + color: #000; + background: #f4f4f5 +} +.skin-yellow-light .sidebar-menu>li.li-open>a:hover { + color: #000; + background: #f4f4f5 +} +.upload-music-div,.upload-img-div{ + margin-bottom: 20px; +} +.upload-div1{ + padding-left: 8px; +} +.upload-div2{ + padding-left: 15px; +} +.jax-upload-btn{ + font-size: 20px; + padding: 3px 0; + color: #5d5d5d; +} +.upload-music-btn:hover,.upload-img-btn:hover{ + cursor: pointer; + color: #3d78e2; +} + +.jax-box{ + border-radius: 3px; + background: #ffffff; + border: 1px solid #e6e7e7; + border-bottom: none; + margin-bottom: 10px; + width: 100%; + padding: 10px; + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.jax-box .form-group{ + margin-bottom: 0px; +} +.jax-box .form-group label{ + padding-left: 5px; + padding-right: 5px; + padding-top: 7px; +} +.jax-box .control-label{ + text-align: left; +} + +.jax-box-table{ + padding-top: 0px; +} + +.table-btn{ + margin: 0 2px; + border-radius: 10px; + padding: 2px 5px; + font-size: 12px; +} +.table-btn.table-btn-info{ + color: #00c0ef; + border: 1px solid #00c0ef; +} +.table-btn.table-btn-info:hover{ + color: #00acd6; + border: 1px solid #00acd6; +} +.table-btn.table-btn-success{ + color: #00a65a; + border: 1px solid #00a65a; +} +.table-btn.table-btn-success:hover{ + color: #008d4c; + border: 1px solid #008d4c; +} +.table-btn.table-btn-warning{ + color: #ec971f; + border: 1px solid #ec971f; +} +.table-btn.table-btn-warning:hover{ + color: #dc8d1e; + border: 1px solid #dc8d1e; +} +.table-btn.table-btn-danger{ + color: #dd4b39; + border: 1px solid #dd4b39; +} +.table-btn.table-btn-danger:hover{ + color: #d73925; + border: 1px solid #d73925; +} + +.treegrid-tbody tr :first-child.table-btn-info{ + border-left: 1px solid #00c0ef!important; +} +.treegrid-tbody tr :first-child.table-btn-danger{ + border-left: 1px solid #dd4b39!important; +} + +@media (max-width: 767px){ + .dropdown-menu li.divider { + background-color: #eee!important; + } + .navbar-nav .open .dropdown-menu>li>a { + line-height: 16px; + } + .navbar-nav .open .dropdown-menu .dropdown-header, .navbar-nav .open .dropdown-menu>li>a { + padding: 5px 15px 5px 20px; + } + .upload-div1,.upload-div2{ + padding-left: 0px; + } +} +@media (max-width: 991px) { + .dropdown-li { + position: relative!important; + } + .dropdown-left a{ + color: #777!important; + } +} + + +.zb-checkbox{ + position: relative; +} +thead .zb-checkbox label{ + padding-top: 2px; +} +.zb-checkbox input[type="checkbox"] { + opacity: 0; +} +.zb-checkbox label:before { + content: ''; + width: 18px; + height: 18px; + display: inline-block; + border-radius: 2px; + border: 1px solid #ddd; + background: #fff; +} +thead .zb-checkbox input[type="checkbox"]:checked + label:after { + top:24%; +} +.zb-checkbox input[type="checkbox"]:checked + label:after { + display: inline-block; + font-family: 'Glyphicons Halflings'; + content: "\e013"; + top: 18%; + left: 5%; + position: absolute; + font-size: 10px; + line-height: 1; + width: 16px; + height: 16px; + color: #009ddc; +} +.zb-checkbox label { + cursor: pointer; + text-align: center; + position: absolute; + left: 10px; +} +.fixed-table-container tbody tr.selected td{ + border-bottom: 1px solid #e9e9e9; + background-color: #f2f8ff; +} +.fixed-table-container tbody tr.selected:hover td{ + background-color: #f5f5f5; +} + + + + +.zb-radio{ + position: relative; +} +thead .zb-radio label{ + padding-top: 2px; +} +.zb-radio input[type="radio"] { + opacity: 0; +} +.zb-radio label:before { + content: ''; + width: 18px; + height: 18px; + display: inline-block; + border-radius: 50px; + border: 1px solid #ddd; + background: #fff; +} +thead .zb-radio input[type="radio"]:checked + label:after { + top:24%; +} +.zb-radio input[type="radio"]:checked + label:after { + display: inline-block; + font-family: 'Glyphicons Halflings'; + content: "\e013"; + top: 18%; + left: 5%; + position: absolute; + font-size: 10px; + line-height: 1; + width: 16px; + height: 16px; + color: #009ddc; +} +.zb-radio label { + cursor: pointer; + text-align: center; + position: absolute; + left: 10px; +} +.jax-box-table .treegrid-selected{ + background-color: #f2f8ff!important; +} +.ke-content img{max-width:100%;} diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css new file mode 100644 index 0000000..a79c5d4 --- /dev/null +++ b/src/main/resources/static/css/login.css @@ -0,0 +1,409 @@ + +body{background-color: #1e282c;} + +ol, ul { + list-style: none; +} + +button { + outline: none; +} + +.btn:active, .btn:focus { + outline: 0 !important; +} + +.ml-15 { + margin-left: 15px; +} + +.ml-20 { + margin-left: 20px; +} + +.mb-20 { + margin-bottom: 20px; +} + +.pb-10 { + padding-bottom: 20px; +} + +.pb-20 { + padding-bottom: 20px; +} + +.pb-30 { + padding-bottom: 30px; +} +.bg-47bf82{ + background: #47bf82; +} +.bg-6e8bd6{ + background: #6e8bd6; +} +.bg-ad53c4{ + background: #ad53c4; +} +.bg-b98140{ + background: #b98140; +} +.center{ + text-align: center; +} + +/*-------top-start------*/ +.logo-a { + float: left; + padding: 5px 0; + height: 54px; +} + +.logo-a img { + height: 100%; +} + +.navbar-default .navbar-toggle { + border-color: #0086da; + background-color: #0086da; + margin-top: 10px; + margin-bottom: 10px; +} + +.navbar-default .navbar-toggle .icon-bar { + background-color: #fff; +} + +.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { + background-color: #0086da; +} + +.navbar-nav .active { + border-bottom: 4px solid #3977de; +} + +@media (max-width: 768px) { + .logo-a { + padding-left: 15px; + } + + .navbar-nav .active { + border-bottom: 4px solid #3977de; + border-left: 1px solid #f8f8f8; + border-right: 1px solid #f8f8f8; + } +} + +.navbar-nav .active a { + color: #3977de !important; +} + +.navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover { + color: #3977de; +} + +.navbar-default .navbar-nav > li > a:hover { + color: #3977de; +} + +.navbar-nav li a { + font-size: 15px; +} + +.navbar-default .navbar-nav > .color-red > a { + color: red; +} + +.navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover { + background-color: #f8f8f8; +} + +.dropdown-menu { + min-width: 110px; +} + +.navbar-nav > li > .dropdown-menu { + background-color: #fff; + margin-top: 4px; +} + +.navbar-nav > li > .dropdown-menu a { + color: #585858; +} + +.navbar-default .navbar-nav .open .dropdown-menu > li > a:focus, .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover { + color: #3977de; +} + +.navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:focus, .navbar-default .navbar-nav > .open > a:hover { + color: #3977de; + background-color: transparent; +} + +.dropdown-menu .divider { + margin: 3px 0; +} + +.flex-prev i { + color: rgba(0, 0, 0, 0.65); + font-size: 30px; +} + +.flex-next i { + color: rgba(0, 0, 0, 0.65); + font-size: 30px; +} + +/*-------footer-start------*/ +.footer-section { + padding-top: 30px; + background: #313131; +} + +.footer-section h5 { + color: #cccccc; + font-size: 14px; + text-transform: uppercase; + margin-bottom: 20px; + font-weight: bold; + position: relative; +} + +.footer-section h5:after { + content: ""; + display: block; + margin-top: 20px; + width: 25px; + height: 4px; + background: #ff6400; +} + +.footer-section p { + color: #999999; + font-size: 14px; +} + +.footer-section .footer-nav li { + line-height: 24px; +} + +.footer-section .footer-nav li.active a { + color: #ff6400; +} + +.footer-section .footer-nav li a { + font-size: 14px; + line-height: 2; + color: #999999; + text-decoration: none; +} + +.footer-section .footer-nav li a:hover { + color: #ff6400; +} + +.footer-section .contacts-list i { + min-width: 20px; + margin-right: 5px; +} + +.footer-section .contacts-list p { + margin-bottom: 0; +} + +.footer-section .contacts-list a { + color: #999; + text-decoration: none; +} + +.footer-section .form-control { + border-color: #333333; +} + +.footer-section .form-control:focus, .footer-section .form-control:active { + border-color: #ddd; +} + +.footer-section .form-control-feedback { + color: #999; + top: 16px; + font-size: 15px; +} + +.footer-section ul { + margin: 0; + padding: 0; +} + +.footer-section .form-control { + background: none; + box-shadow: none !important; + outline: none; + border: none; + border-bottom: 1px solid #999; + border-radius: 0; + font-size: 14px; + padding-left: 0; + height: 50px; + color: #999; +} + +.footer-content { + margin-bottom: 30px; +} + +.copyright-section { + padding: 30px 0; + text-align: center; +} +.copyright-section p { + margin-bottom: 0; + font-size: 10px; + text-transform: uppercase; + font-family: 'Montserrat', sans-serif; +} +.copyright-dark.copyright-section { + background: #313131; + border-top: 1px solid #545252; +} + +.copyright-dark.copyright-section p { + color: #999; +} + +.copyright-dark.copyright-section p span { + color: #ccc; +} +.copyright-light.copyright-section { + background: #fff; + border-top: 1px solid #c7c6c6; +} + +.copyright-light.copyright-section p { + color: #6c6b6b; +} + +.copyright-light.copyright-section p span { + color: #6c6b6b; +} + +/*swipper*/ +.swiper-button-next { + background-image: none; + font-size: 28px; + opacity: .6; + color: rgba(0, 0, 0, .65) +} + +.swiper-button-prev { + background-image: none; + font-size: 28px; + opacity: .6; + color: rgba(0, 0, 0, .65) +} + +.swiper-container .swiper-button-next:hover, .swiper-container .swiper-button-prev:hover { + opacity: 1 +} + +/*login内容开始*/ +input{ + -webkit-appearance: none; +} +.login-content { + position: relative; + /*margin-top: 55px;*/ +} + +.login-bg { + /*background: url(../img/login-bg.jpg) center top no-repeat;*/ + /*background-color: currentColor;*/ + width: 100%; + /*height: 750px;*/ + top: 0px; +} + +.login-box { + position: absolute; + z-index: 2; + /* + margin-top: 8%; + margin-right: 15%; + */ + margin-top: 10%; + margin-right: 42%; + + top: 0px; + right: 0px; + padding-bottom: 30px; + background: #fff; + border-radius: 3px; + border-color: #e2e2e2; +} + +.login-form { + width: 324px; + float: right; + padding: 0px 26px; +} + +.login-form .login-title { + margin-top: 20px; + margin-bottom: 15px; + font-size: 18px; + color: #4e4e4e; +} + +.vcode-icon{ + font-weight: 700; + font-size: 15px; +} + +@media (max-width: 768px) { + .login-box { + right: 50%; + margin-right: -162px; + } +} + +.login-form .form-control-feedback { + color: #666; + left: 0; + right: auto; +} + +.login-form .has-feedback .form-control { + padding-left: 35px; + padding-right: 10px; + height: 40px; +} +.login-form .form-control-feedback{ + width: 40px; + height: 40px; + line-height: 40px; +} + +vcode { + font-size: 0px; +} + +.vcode-content { + width: 60%; + display: inline-block; +} + +.vcode-input { + display: inline-block; + vertical-align: middle; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; +} + +.vcode-img { + width: 40%; + height: 40px; + position: absolute; +} +.position-a{ + right: 120px; +} \ No newline at end of file diff --git a/src/main/resources/static/favicon.ico b/src/main/resources/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f4e960f8c5c9d696fc8dc3d4349fbb497ad9d870 GIT binary patch literal 16958 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%0D639OS3Szq?eAS+xVQgdwOh>*@Wi9O6G7MPS+v zH~DHa{et~9R(0~cT;sDM!e@c}8w)H=QygaBxCvg3kwX}DhY+?4-#RSJ1u=9ahucCn z$ZVkQia^_dVFo6Vgs(AIbYtkd#}QUJczfl?kT2%@ue4YDZXa`_?DcCH$WTCjC<`ij z*I|)pD5iI!bAK1?G9?_nW6;t&Uvq;DlX;Dpw+85PqUyoTG1x}W7NUP|DsO9#cR~Gu zV7J(L&W{ma-N=LS_V5fAJ(LHKsWF~d$x#YgU6|`N#CVI%TeQ!SZivR7vD&z^{n&GK zbt6z^)st^K21VKkI``IMG)V;c{>yxv%^6J@qF6jC=(m0i>8@~W%-w52gmU<-Fkh|a zfvvaTp?EwCo>t+V4%^GHtpcC8e@)&koO5$_9$bzuI*oi6P89`cKaGy5Uk zlZ$~9x#YJ8FmR@q%doz*jqJD(1BXa=H$1r=k{2tuZ#qkUL+$tWz&GNE(PNJIn(fJC zUGj%BoEGxG#KTzH$x&fCz2Skkut@Jq=IX{_qj<#HB*YMw8ad`BtlSo|+>lE-d8g6m z`!jfr_1!Sk$e_wmk{t9wS1hJ;7IILUR0ZL&jKL#WaP+8RxH4O|a(+(cA8n@Z0?7CG z$;`1iZ2V?B#@arOFup{5v|#c=pCjmm6=baq{oFRLwC~29!R1iQpzlZt)aexK2SS*l z@7R13W*9|-^o}D4#jzY1=-VA#hLrr6W(@30hxTFvY(+O{Lbf4FG2i6dg5Ml=`k*|R zhh@^2tSpg_JH1JG2g07mwY;7feIyp#e)Ald!!bQ&e1!K=3m`E>UfCjbMPj^J2Mvb5%F24ShUbV}5?f?t!g^`~{MksXQTHxJ2YXt+~^w;SD%pk4^)o9^HDqR zH{=OE4Wq>pz3+B(D7pU|Eh8DY>7Cf;!SuHpW4b-+oQJ2MWP|VxOxby!VuAW1Ev}eG9xq^H@%k6?SK|y;?&ggcX=>rz?CJ6k(&|Ff|I}}>8 zJSV&-`;wymf53{`Zp1LeAJ`A+k!59>a(G<7Q=OkL;k`I&quA!wccg4CqY0by zRW1t#;k+3R3lqY-zUwz~`1~7~8~TpZ;wz5pXPpznZP6&ufz?;edXe!rwU|sby$I)p(4EV5d===qLX)oo ftoLBNM}d13xJQ9|6!`K`;IsZOz`g$e9~AgsSw+^{ literal 0 HcmV?d00001 diff --git a/src/main/resources/static/img/person.jpg b/src/main/resources/static/img/person.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0d2b0d87bb2d9789653546cdbbf18e21b3aac1a7 GIT binary patch literal 20649 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/static/js/cases/content.js b/src/main/resources/static/js/cases/content.js new file mode 100644 index 0000000..61ff818 --- /dev/null +++ b/src/main/resources/static/js/cases/content.js @@ -0,0 +1,292 @@ + +// 元素 +var oFileBox = $(".fileBox"); //选择文件父级盒子 +var oFileInput = $("#fileInput"); //选择文件按钮 + +var oFileList_parent = $(".fileList_parent"); //表格 +var oFileList = $(".fileList"); //表格tbody +var oFileBtn = $("#fileBtn"); //上传按钮 + +var flieList = []; //数据,为一个复合数组 +//var sizeObj = []; //存放每个文件大小的数组,用来比较去重 + +$(function(){ + + //点击选择文件按钮选文件 + oFileInput.on("change",function(){ + flieList = new Array(); // 重新选择前先删除上次选择的 + analysisList(this.files); + }) + + // 删除表格单行文件 + oFileList.on("click","a.btn_delfile",function(){ + if(!confirm("确认删除?"))return; + var oTr = $(this).parents("tr"); + var index = oTr.index(); + oTr.remove(); //删除这一行 + flieList.splice(index,1); // 删除数据 + // sizeObj.splice(index,1); // 删除文件大小数组中的项 + }); + + // 预览表格单行文件 + oFileList.on("click","a.btn_viewfile",function(){ + var imgurl = $(this).parent().find('.imgurl').val(); + if(imgurl==null || $.trim(imgurl)=='')return; + layer.photos({ + photos: {"id":'a1',"start":0,"data":[{"src":""+imgurl+""}]} + ,anim: 5 // 0-6的选择,指定弹出图片动画类型,默认随机 + }); + }); + + // 只允许输入数字 + oFileList.on("keyup",".sort",function(){ + $(this).val($(this).val().replace(/\D/g,'')); + }); + // 只允许粘贴数字 + oFileList.on("paste",".sort",function(){ + $(this).val($(this).val().replace(/\D/g,'')); + }); + + //上传 + oFileBtn.on("click",function(){ + // oFileBtn.off(); + var tr = oFileList.find(".uploaddata"); //获取所有tr列表 + var successNum = 0; //已上传成功的数目 + // oFileList.off(); //取消删除事件 + oFileBox.slideUp(); //隐藏输入框 + // oFileList.find("a.btn_delfile").text("等待上传"); + + for( var i=0;i 16){ + alert('最多只能上传16张图片'); + return false; + }else{ + var totallength = obj.length; + var uptr = oFileList.find(".uploaddata"); + if(uptr.length > 0)totallength+=uptr.length; + var dbtr = oFileList.find(".dbdata"); + if(dbtr.length > 0)totallength+=dbtr.length; + if(totallength > 16){ + alert('最多只能上传16张图片'); + return false; + } + } + } + + + // 先全部校验一遍 + for( var i=0;i 10*1024*1024){ + if(size > 2*1024*1024){ + alert('文件"'+ name +'"超过了2M,不能上传'); + return false; + } + //文件类型不为这几种,就不上传 + if(("png/jpg/jpeg/bmp/gif").indexOf(type) == -1){ + alert('文件"'+ name +'"类型不对,不能上传'); + return false; + } + } + + for( var i=0;i20)name=name.substr(0,10)+'...'+name.substr(name.length-5); + + var oTr = $(""); + var str = ''+ name +''; + str += ''; + str += ''; + str += '
'; + str += '

'; + str += '0%'; + str += '
'; + str += ''; + str += ''; + str += ''; + str += '删除'; + str += '  预览'; + str += ''; + + oTr.html(str); + oTr.appendTo(oFileList); + } +} + +function uploadFn(obj,i){ + var formData = new FormData(); + var arrNow = flieList[i]; //获取数据数组的当前项 + + // 从当前项中获取上传文件,放到 formData对象里面,formData参数以key name的方式 + var result = arrNow[0]; //数据 + formData.append("file" , result); + + var name = arrNow[1]; //文件名 + formData.append("filename" , name); + + var progress = obj.find(".progress"); //上传进度背景元素 + var progressNum = obj.find(".progressNum"); //上传进度元素文字 +// var obtn_delfile = obj.find("a.btn_delfile"); //按钮 + +// obtn_delfile.text("正在上传"); //改变操作按钮 +// obtn_delfile.off(); + + var request = $.ajax({ + type: "POST", + url: 'console/tool/oss/uploadFile?caseType='+caseType+'&category=image', + data: formData, //这里上传的数据使用了formData 对象 + processData : false, //必须false才会自动加上正确的Content-Type + contentType : false, + //这里我们先拿到jQuery产生的XMLHttpRequest对象,为其增加 progress 事件绑定,然后再返回交给ajax使用 + xhr: function(){ + var xhr = $.ajaxSettings.xhr(); + if(onprogress && xhr.upload){ + xhr.upload.addEventListener("progress", onprogress, false); + return xhr; + } + }, + //上传成功后回调 + success: function(result){ + result = result.data; + obj.removeClass('uploaddata').addClass('dbdata'); // 删除待上传标记,增加已上传成功标记 + obj.find(".imgbtn").show(); + obj.find(".imgurl").val(result.url); + obj.find(".filename").text(result.ori_name); + }, + + //上传失败后回调 + error: function(){ + obj.find(".imgbtn").show(); + /* + obtn_delfile.text("重传"); + obtn_delfile.on("click",function(){ + request.abort(); //终止本次 + uploadFn(obj,i); + }); + */ + } + + }); + + //侦查附件上传情况 ,这个方法大概0.05-0.1秒执行一次 + function onprogress(evt){ + var loaded = evt.loaded; //已经上传大小情况 + var tot = evt.total; //附件总大小 + var per = Math.floor(100*loaded/tot); //已经上传的百分比 + progressNum.html(per +"%"); + progress.css("width", per +"%"); + } +} + + +//字节大小转换,参数为b +function bytesToSize(bytes) { + var sizes = ['Bytes', 'KB', 'MB']; + if (bytes == 0) return 'n/a'; + var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); + return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; +}; + +//通过文件名,返回文件的后缀名 +function fileType(name){ + var nameArr = name.split("."); + return nameArr[nameArr.length-1].toLowerCase(); +} + +// 设置数据行到表格 +function setTableList(dataList){ + if(dataList == null || dataList == undefined || dataList.length == 0)return; + dataList=dataList.replace(new RegExp('"','g'),'"'); // java返回的引号变成了" +// dataList=dataList.replace(/\s*/g,''); // 去除字符串内所有的空格 +// console.log('dataList='+dataList); + dataList = JSON.parse(dataList); + oFileList.empty(); // 先清空元素内容 + for( var i=0; i20)fname=fname.substr(0,10)+'...'+fname.substr(fname.length-5); + var oTr = $(""); + var str = ''+ fname +''; + str += ''; + str += ''; + str += ''; + str += ''; + str += ''; + str += '  '; + str += ''; + + oTr.html(str); + oTr.appendTo(oFileList); + } + oFileList_parent.show(); // 表格显示 + oFileList.find(".imgbtn").show(); // 操作按钮显示 +} + +// 返回json格式数据给后端 +function getImgJsonList(){ + var jsonArray = new Array(); + var trs = oFileList.find(".dbdata"); //获取所有tr列表 + for( var i=0;i'):(tableOptions.clickToSelect==true?$(''):$(''))); + $check.attr("id", id).parent().addClass("zb-checkbox").append($label); + }); + $(tableOptions.id).find("input:radio").each(function (i) { + var $check = $(this); + if ($check.attr("id") && $check.next("label")) { + return; + } + var name = $check.attr("name"); + var id = name + "-" + i; + var $label = (i==0?$(''):(tableOptions.clickToSelect==true?$(''):$(''))); + $check.attr("id", id).parent().addClass("zb-radio").append($label); + }); + if ($.isFunction(options.onPostBody)) { + options.onPostBody(); + } + } + }); + } + function queryInitParams(params) { + var temp = { //这里的键的名字和控制器的变量名必须一致,这边改动,控制器也需要改成一样的 + limit: params.limit, //页面大小 + offset: params.offset //页码 + }; + return temp; + } + + function responseHandler(data) { + return data; + } + + function tableLoadSuccess(data) { + } + + /*刷新表格 :flag-是否跳转到当前页。默认首页*/ + core.refreshTable = function (id, flag) { + if (flag) { + $(id).bootstrapTable("refresh"); + } else { + $(id).bootstrapTable("refresh", {"pageNumber": 1}); + } + } + + /*根据data选中数据*/ + core.checkTableBy=function (id,data) { + $(id).bootstrapTable("checkBy", data) + } + + /*根据uniqueId获取所选列*/ + core.getRowByUniqueId = function (id, val) { + return $(id).bootstrapTable("getRowByUniqueId", val); + } + core.selectSingleData = function (id){ + var selectContent = $(id).bootstrapTable('getSelections'); + if(typeof(selectContent) == 'undefined' || selectContent == "") { + layer.msg("请先选择一条数据!"); + return false; + }else if(selectContent.length > 1){ + layer.msg("只能选择一条数据!"); + return false; + }else{ + var selectData = selectContent[0]; + return selectData; + } + } + + core.selectMutiData = function (id){ + var checkedRows= $(id).bootstrapTable('getSelections'); + if(checkedRows.length==0){ + layer.msg("请先选择一条数据!"); + return false; + }else{ + return checkedRows; + } + } + + + /*更新某一列的值 index-行索引,field-字段名,value-值*/ + core.updateCell = function (id, index, field, value) { + var updateCellOptions = { + index: index, + field: field, + value: value + } + return $(id).bootstrapTable("updateCell", updateCellOptions); + } + + /*禁用button*/ + core.mask = function (e) { + var i = "" + $(e).append(i); + $(e).attr('disabled', "true");//添加disabled属性 + } + /*启用button*/ + core.unmask = function (e) { + $(e).children('i').remove(); + $(e).removeAttr('disabled');//添加disabled属性 + } + + /*询问框*/ + core.confirm = function(content,d){ + layer.confirm(content, { + icon: 3, + title: "系统提示", + btn: ['确认', '取消'], + btnclass: ['btn btn-primary', 'btn btn-danger'], + }, function (index) { + layer.close(index); + d(true); + }); + } + + //date类型到字符串 + core.formatterDateTime = function (date) { + var datetime = date.getFullYear() + + "-"// "年" + + ((date.getMonth() + 1) >= 10 ? (date.getMonth() + 1) + : "0" + (date.getMonth() + 1)) + + "-"// "月" + + (date.getDate() < 10 ? "0" + date.getDate() : date + .getDate()) + + " " + + (date.getHours() < 10 ? "0" + date.getHours() : date + .getHours()) + + ":" + + (date.getMinutes() < 10 ? "0" + date.getMinutes() + : date.getMinutes()) + + ":" + + (date.getSeconds() < 10 ? "0" + date.getSeconds() + : date.getSeconds()); + return datetime; + } + + //long类型转时间字符串 + core.longMsTimeConvertToDateTime = function (time) { + var myDate = new Date(time); + return this.formatterDateTime(myDate); + } + + /*日期+*/ + core.addDate = function (date, days) { + if (days == undefined || days == '') { + days = 1; + } + var date = new Date(date); + date.setDate(date.getDate() + days); + var month = date.getMonth() + 1; + var day = date.getDate(); + return date.getFullYear() + '-' + getFormatDate(month) + '-' + getFormatDate(day); + } + function getFormatDate(arg) { + if (arg == undefined || arg == '') { + return ''; + } + var re = arg + ''; + if (re.length < 2) { + re = '0' + re; + } + return re; + } + + /*是否是数组*/ + core.isArray = function (s) { + return s instanceof Array; + } + + core.clearForm = function (id) { + + var objId = document.getElementById(id); + if (objId == undefined) { + return; + } + for (var i = 0; i < objId.elements.length; i++) { + if (objId.elements[i].type == "text") { + objId.elements[i].value = ""; + } + else if (objId.elements[i].type == "password") { + objId.elements[i].value = ""; + } + else if (objId.elements[i].type == "radio") { + objId.elements[i].checked = false; + } + else if (objId.elements[i].type == "checkbox") { + objId.elements[i].checked = false; + } + else if (objId.elements[i].type == "select-one") { + objId.elements[i].options[0].selected = true; + } + else if (objId.elements[i].type == "select-multiple") { + for (var j = 0; j < objId.elements[i].options.length; j++) { + objId.elements[i].options[j].selected = false; + } + } + else if (objId.elements[i].type == "textarea") { + objId.elements[i].value = ""; + } + } + } + + /*清除表单错误提示*/ + core.clearError = function (id) { + $(id).find(".warning,.valid,.promimg").remove(); + $(id).find(".error").removeClass("error"); + $(id).find(".prombtn").removeClass("prombtn"); + $(id).find(".prominput").removeClass("prominput"); + } + /*保留两位小数*/ + core.numberTwo = function (num) { + if (isNaN(num) || num == "") { + return num; + } else { + if (isNaN(parseFloat(num).toFixed(2))) { + return num; + } else { + return parseFloat(num).toFixed(2); + } + } + } + /*数字千分话并保留两位小数*/ + core.numToTwo = function (num) { + try { + num = this.numberTwo(num).replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); + } finally { + return num; + } + } + + + // 判断是否为json对象 + core.isJsonObject = function (obj) { + var isjson = typeof(obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && !obj.length; + return isjson; + } + + + return core; +})(Core, window); diff --git a/src/main/resources/static/js/init.js b/src/main/resources/static/js/init.js new file mode 100644 index 0000000..4b8724e --- /dev/null +++ b/src/main/resources/static/js/init.js @@ -0,0 +1,201 @@ +$(document).ajaxStart(function() { +// Pace.restart(); +}); +$(function () { + /*固定布局*/ + $("#fixCheckBox").click(function () { + var neg = $('.main-header').outerHeight() ; + var window_height = $(window).height(); + if($(this).is(':checked')) { + $("body").addClass("fixed"); + $(".content-wrapper, .right-side").css('min-height', window_height); + }else{ + $("body").removeClass("fixed"); + $(".content-wrapper, .right-side").css('min-height', window_height-neg); + $(".control-sidebar").removeAttr("style"); + } + }) + /*盒状布局*/ + $("#layoutBox").click(function(){ + if($(this).is(':checked')) { + $("body").addClass("layout-boxed"); + }else{ + $("body").removeClass("layout-boxed"); + } + }) +}); +$(function(){ + var clickHref="";//点击菜单判断使用 + //菜单列表html + var menus = ''; + $.ajax({ + type: 'POST', + url: "menu" , + data: {} , + success: function (data) { + GetData(0, data) + $("#menu").append(menus); + /*菜单点击事件*/ + $(".sidebar-menu a").click(function () { + var aHref=$(this).attr("href"); + if(clickHref==aHref){ + if(aHref.substring(1,aHref.length)!=""){ + Core.load("#content",aHref.substring(1,aHref.length)); + } + }else{ + clickHref = aHref;//赋值 + } + }); + /*初始化加载菜单样式*/ + loadMenuRefresh(); + } + }); + //根据菜单主键id生成菜单列表html + //id:菜单主键id + //arry:菜单数组信息 + function GetData(id, arry) { + var childArry = GetParentArry(id, arry); + if (childArry.length > 0) { + if(menus==""){ + menus += '

i9mb=1-qIs)r8%MiXiX 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 GZvP+r4|oay literal 0 HcmV?d00001 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e5f1d37 --- /dev/null +++ b/pom.xml @@ -0,0 +1,289 @@ + + + 4.0.0 + com.nbclass + cases + 1.0-SNAPSHOT + war + + cases + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.3.0.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + 2.1.5 + 1.2.13 + 2.1.2 + 1.1.22 + 1.2.70 + 1.4 + 2.7 + 3.2.14 + 1.5.3 + 4.5.2 + 3.9.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-aop + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot-starter.version} + + + org.springframework.boot + spring-boot-configuration-processor + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + com.alibaba + druid-spring-boot-starter + ${druid.springboot.version} + + + + + tk.mybatis + mapper-spring-boot-starter + ${mapper.starter.version} + + + + javax.persistence + persistence-api + + + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper-spring-boot-starter.version} + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + + org.apache.shiro + shiro-spring + ${shiro-spring.version} + + + mysql + mysql-connector-java + + + com.github.theborakompanioni + thymeleaf-extras-shiro + 2.0.0 + + + + org.apache.commons + commons-lang3 + + + + com.alibaba + fastjson + ${fastjson.version} + + + + ognl + ognl + ${ognl.version} + + + + + com.aliyun + aliyun-java-sdk-core + ${aliyun-java-sdk-core.version} + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyun-sdk-oss.version} + + + + + + org.springframework.boot + spring-boot-starter-data-elasticsearch + + + + + + + commons-fileupload + commons-fileupload + ${commons-fileupload.version} + + + commons-io + commons-io + ${commons-io.version} + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + net.coobird + thumbnailator + 0.4.11 + + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-devtools + true + true + + + + com.auth0 + java-jwt + 3.7.0 + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + javax.servlet + jstl + + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + + + cases + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.mybatis.generator + mybatis-generator-maven-plugin + 1.3.7 + + ${basedir}/src/main/resources/generator/generatorConfig.xml + true + true + + + + mysql + mysql-connector-java + ${mysql.version} + + + tk.mybatis + mapper-generator + 1.1.5 + + + + + + + + diff --git a/src/main/java/com/nbclass/CasesBootApplication.java b/src/main/java/com/nbclass/CasesBootApplication.java new file mode 100644 index 0000000..620b4e3 --- /dev/null +++ b/src/main/java/com/nbclass/CasesBootApplication.java @@ -0,0 +1,28 @@ +package com.nbclass; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +import tk.mybatis.spring.annotation.MapperScan; + +/** + * + * @author Leon + * @datetime 2019年3月31日 下午7:21:36 + */ +@SpringBootApplication +@MapperScan(basePackages = "com.nbclass.**.mapper") +public class CasesBootApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(CasesBootApplication.class, args); + } + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(CasesBootApplication.class); + } + +} diff --git a/src/main/java/com/nbclass/Interceptor/InterceptorConfig.java b/src/main/java/com/nbclass/Interceptor/InterceptorConfig.java new file mode 100644 index 0000000..9016721 --- /dev/null +++ b/src/main/java/com/nbclass/Interceptor/InterceptorConfig.java @@ -0,0 +1,39 @@ +package com.nbclass.Interceptor; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @title: InterceptorConfig + * @Author gjt + * @Date: 2020-12-21 + * @Description: + */ +@Configuration +public class InterceptorConfig implements WebMvcConfigurer{ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(jwtInterceptor()) + .addPathPatterns("/comment/**")// 拦截前端接口所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录 + //排除登录接口 + .excludePathPatterns("/wx/**"); + + //注册TestInterceptor拦截器 +// InterceptorRegistration registration = registry.addInterceptor(jwtInterceptor()); +// registration.addPathPatterns("/**"); //添加拦截路径 +// registration.excludePathPatterns( //添加不拦截路径 +// "/**/*.html", //html静态资源 +// "/**/*.js", //js静态资源 +// "/**/*.css", //css静态资源 +// "/**/*.woff", +// "/**/*.ttf", +// "/swagger-ui.html" +// ); + } + @Bean + public JWTInterceptor jwtInterceptor() { + return new JWTInterceptor(); + } +} diff --git a/src/main/java/com/nbclass/Interceptor/JWTInterceptor.java b/src/main/java/com/nbclass/Interceptor/JWTInterceptor.java new file mode 100644 index 0000000..57cb45f --- /dev/null +++ b/src/main/java/com/nbclass/Interceptor/JWTInterceptor.java @@ -0,0 +1,44 @@ +package com.nbclass.Interceptor; + +import com.alibaba.druid.util.StringUtils; +import com.auth0.jwt.exceptions.AlgorithmMismatchException; +import com.auth0.jwt.exceptions.SignatureVerificationException; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.nbclass.exception.ParameterException; +import com.nbclass.util.JWTUtils; +import io.jsonwebtoken.Claims; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Date; + +/** + * @author admin + */ +@Slf4j +public class JWTInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String token = request.getHeader("token"); + if(StringUtils.isEmpty(token)){ + throw new ParameterException("token不能为空"); + } + try { + Claims claims = JWTUtils.parseJWT(token); + //获取token的签发时间 + long issuedAt = claims.getIssuedAt().getTime(); + //获取token的过期时间 + long expiration = claims.getExpiration().getTime(); + if(expiration < issuedAt){ + throw new ParameterException("token已过期!"); + } + } catch (Exception e) { + log.error("token无效! 错误 ->", e); + throw new ParameterException("token无效!"); + } + return true; + } +} diff --git a/src/main/java/com/nbclass/activity/constant/Const.java b/src/main/java/com/nbclass/activity/constant/Const.java new file mode 100644 index 0000000..5a7dbbc --- /dev/null +++ b/src/main/java/com/nbclass/activity/constant/Const.java @@ -0,0 +1,60 @@ +package com.nbclass.activity.constant; + +import com.nbclass.util.CommonUtils; + +/** + * 项目常量 + * @author Leon + * @datetime 2019年4月11日 上午11:22:59 + */ +public class Const { + + /** + * 当前期数 + */ + public volatile static Integer period = 1; + + /** + * 测试环境appid + */ + public static final String test_appid = "2018050300000578"; + /** + * 生产环境appid + */ + public static final String product_appid = "2018081400000135"; + + /** + * 测试环境 + */ + public static final String test_client_id = "P_XGLBDHB"; + public static final String test_client_secret = "tX6Vwv72"; + public static final String test_api_host = "https://test-api.pingan.com.cn:20443"; + + /** + * 正式环境 + */ + public static final String product_client_id = "P_XGLKXSW"; + public static final String product_client_secret = " E1Fhw56u"; + public static final String product_api_host = "http://api.pingan.com.cn"; + + /** + * 活动结束时间 + */ + public static long hd_endtime = CommonUtils.getSimpleDate("2020-03-31 23:59:59").getTime(); + + /** + * 信广龙企业微信接口 + */ + public static final String QYWX_API_DOMAIN = "http://wx.szxgl.cn/qywx"; + + /** + * 支持的上传图片后缀 + */ + public static final String UPLOAD_IMAGE_SUFFIX = ".gif,.jpg,.jpeg,.png,.bmp"; + + /** + * 支持的上传视频后缀 + */ + public static final String UPLOAD_VIDEO_SUFFIX = ".mp4,.mov,.mkv,.rmvb,.rm,.avi,.flv"; + +} diff --git a/src/main/java/com/nbclass/activity/controller/ContentController.java b/src/main/java/com/nbclass/activity/controller/ContentController.java new file mode 100644 index 0000000..780c965 --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/ContentController.java @@ -0,0 +1,492 @@ +package com.nbclass.activity.controller; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.Resource; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import com.nbclass.activity.model.*; +import com.nbclass.activity.service.DataDictService; +import com.nbclass.exception.ParameterException; +import com.nbclass.system.service.UserService; +import com.nbclass.util.JWTUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.model.enums.ContentType; +import com.nbclass.activity.service.ContentService; +import com.nbclass.system.controller.BaseController; +import com.nbclass.system.model.SysLog; +import com.nbclass.system.model.User; +import com.nbclass.system.model.enums.SysLogType; +import com.nbclass.system.service.SysLogService; +import com.nbclass.util.CommonUtils; +import com.nbclass.util.PageUtil; +import com.nbclass.util.ResultUtil; +import com.nbclass.vo.base.PageResultVo; +import com.nbclass.vo.base.Result; + +/** + * 案例内容 + * @author Leon + * @datetime 2020年5月28日 下午4:43:17 + */ +@Controller +@RequestMapping("/console/content") +public class ContentController extends BaseController { + + @Resource + private ContentService service; + + @Resource + private SysLogService sysLogService; + + @Resource + private UserService userService; + + @Resource + private DataDictService dataDictService; + + /** + * 案例页面 + * @param model + * @return + */ + @GetMapping("/indexPage") + public String indexPage(Model model){ + return "content/list2"; + } + + /** + * 案例页面 + * @param model + * @return + */ + @GetMapping("/index-application") + public String application(Model model){ + return "content/application"; + } + + /** + * i技术开发案例页面 + * @param model + * @return + */ + @GetMapping("/index-h5") + public String h5Page(Model model){ + model.addAttribute("caseType", ContentType.h5.key()); + return "content/list"; + } + + /** + * i创意设计案例页面 + * @param model + * @return + */ + @GetMapping("/index-design") + public String designPage(Model model){ + model.addAttribute("caseType", ContentType.design.key()); + // return "content/list"; + return "content-editor/list"; + } + + /** + * i视频动画案例页面 + * @param model + * @return + */ + @GetMapping("/index-video") + public String videoPage(Model model){ + model.addAttribute("caseType", ContentType.video.key()); + return "content/list"; + } + + /** + * i品牌全案 - 富文本编辑器形式 + * @param model + * @return + */ + @GetMapping("/index-ppqa") + public String ppqaPage(Model model){ + model.addAttribute("caseType", ContentType.ppqa.key()); + return "content-editor/list"; + } + + /** + * i公关全案 - 富文本编辑器形式 + * @param model + * @return + */ + @GetMapping("/index-ggqa") + public String ggqaPage(Model model){ + model.addAttribute("caseType", ContentType.ggqa.key()); + return "content-editor/list"; + } + + /** + * i落地活动 - 富文本编辑器形式 + * @param model + * @return + */ + @GetMapping("/index-ldhd") + public String ldhdPage(Model model){ + model.addAttribute("caseType", ContentType.ldhd.key()); + return "content-editor/list"; + } + + /** + * i其他案例 - 富文本编辑器形式 + * @param model + * @return + */ + @GetMapping("/index-others") + public String othersPage(Model model){ + model.addAttribute("caseType", ContentType.others.key()); + return "content-editor/list"; + } + + /** + * i编辑页面 + * @param model + * @param id + * @return + */ + @GetMapping("/edit") + public String editPage(Model model, String caseType, Long id){ + Content entity = new Content(); + if(id!=null && id>0)entity=service.findById(id); + JSONObject obj = (JSONObject) JSON.toJSON(entity); + model.addAllAttributes(obj); + model.addAttribute("caseType", caseType); + return "content/edit"; + } + + /** + * i编辑页面 + * @param model + * @param id + * @return + */ + @GetMapping("/edit2") + public String editPage2(Model model, String caseType, Long id,Long applicationId,Integer count){ + Content entity = new Content(); + if(id!=null && id>0)entity=service.findById(id); + if(count!=null) entity.setCount(count); + if(applicationId!=null) entity.setApplicationId(applicationId); + JSONObject obj = (JSONObject) JSON.toJSON(entity); + model.addAllAttributes(obj); + model.addAttribute("caseType", caseType); + return "content/edit2"; + } + + /** + * i富文本编辑器的编辑页面 + * @param model + * @param caseType + * @param id + * @return + */ + @GetMapping("/edit-editor") + public String editEditorPage(Model model, String caseType, Long id){ + Content entity = new Content(); + if(id!=null && id>0)entity=service.findById(id); + JSONObject obj = (JSONObject) JSON.toJSON(entity); + model.addAllAttributes(obj); + model.addAttribute("caseType", caseType); + return "content-editor/edit"; + } + + /** + * i查询数据列表 + * @param entity + * @param limit + * @param offset + * @return + */ + @PostMapping("/list") + @ResponseBody + public PageResultVo getList(Content entity, Integer limit, Integer offset){ + PageHelper.startPage(PageUtil.getPageNo(limit, offset),limit); + List userList = service.getList(entity); + PageInfo pages = new PageInfo<>(userList); + return ResultUtil.table(userList,pages.getTotal()); + } + + /** + * i查询数据列表 + * @param entity + * @param limit + * @param offset + * @return + */ + @PostMapping("/list2") + @ResponseBody + public PageResultVo getList2(Content entity, Integer limit, Integer offset,HttpServletRequest request){ + User loginUser = getLoginUser(); + User user = userService.selectByUsername(loginUser.getUsername()); + if(!user.getUsername().contains("admin")){ + entity.setUserId(Long.parseLong(user.getId().toString())); + } + List list = Arrays.asList(entity.getTagLists()); + String[] strings = list.stream().filter(String -> !String.isEmpty()).toArray(String[]::new); + if(strings.length == 0){ + entity.setTagLists(null); + }else { + entity.setTagLists(strings); + entity.setCount(strings.length); + } + PageHelper.startPage(PageUtil.getPageNo(limit, offset),limit); + List userList = service.getList2(entity); + PageInfo pages = new PageInfo<>(userList); + return ResultUtil.table(userList,pages.getTotal()); + } + + + /** + * i添加或修改数据 + * @param entity + */ + @PostMapping("/merge") + @ResponseBody + public Result merge(Content entity){ + if(StringUtils.isNotBlank(entity.getImages())) { + // json数据去除多余的引号,否则存数据库报错 + String imgList = entity.getImages(); + imgList = imgList.replace("\"{", "{").replace("}\"", "}").replace("\\", ""); + entity.setImages(imgList); + } + String type = ""; + if(entity.getTagLists().length != 0){ + for (String tagList : entity.getTagLists()) { + if(tagList.equals("106") || tagList.equals("107") + || tagList.equals("108") || tagList.equals("109") + || tagList.equals("110") || tagList.equals("111") + || tagList.equals("112")){ + type = ContentType.getNameByCode(tagList); + } + } + } + String desc = null; + if(entity.getId()!=null && entity.getId()>0){ + service.update(entity); + if(entity.getApplicationId()!=null){ + //更新审核申请单状态 + service.updateApplicationStatus(entity.getApplicationId()); + } + desc="修改案例,案例类型:"+ContentType.getNameByCode(type)+",案例ID:"+entity.getId()+",案例标题:"+entity.getTitle(); + }else{ + User loginUser = getLoginUser(); + User user = userService.selectByUsername(loginUser.getUsername()); + entity.setUserId(Long.parseLong(user.getId().toString())); + service.add(entity); + //标签关联案例 + String[] tagList = entity.getTagLists(); + for (String s : tagList) { + ContentTags contentTags = new ContentTags(); + contentTags.setCid(entity.getId()); + contentTags.setTid(Long.parseLong(s)); + service.addContentTags(contentTags); + } + desc="添加案例,案例类型:"+ContentType.getNameByCode(type)+",案例ID:"+entity.getId()+",案例标题:"+entity.getTitle(); + } + User user = getLoginUser(); + sysLogService.add(new SysLog(SysLogType.cases.key(), user.getUserId()+"("+user.getUsername()+")", getIpAddr(), desc)); + + return Result.success(); + } + + /**删除用户*/ + @PostMapping("/delete") + @ResponseBody + public Result delete(String ids) { + List IdList = CommonUtils.toLongList(ids); + service.delete(IdList); + //取消案例关联标签的关系 + service.deleteContentTags(IdList); + User user = getLoginUser(); + sysLogService.add(new SysLog(SysLogType.cases.key(), user.getUserId()+"("+user.getUsername()+")", getIpAddr(), "删除案例,ids:"+ids)); + return Result.success(); + } + + /** + * 同步MySQL数据到ES + * @param ids 如果有值,同步指定id,如果没有值,同步全部 + * @return + */ + @PostMapping("/syncEsData") + @ResponseBody + public Result syncEsData(Content entity, @RequestParam(defaultValue="false")Boolean isReBuild) { + long total = service.syncEsData(entity, isReBuild); + User user = getLoginUser(); + String type = ""; + if(entity.getTagLists().length != 0){ + for (String tagList : entity.getTagLists()) { + if(tagList.equals("106") || tagList.equals("107") + || tagList.equals("108") || tagList.equals("109") + || tagList.equals("110") || tagList.equals("111") + || tagList.equals("112")){ + type = ContentType.getNameByCode(tagList); + } + } + } + sysLogService.add(new SysLog(SysLogType.cases.key(), user.getUserId()+"("+user.getUsername()+")", getIpAddr(), + "同步案例到ElasticSearch,案例类型:"+ContentType.getNameByCode(type)+",是否重建:"+isReBuild+",受影响的记录数:"+total)); + return Result.success(total); + } + + /** + * 查询案例标题、评论数和点赞数 + * @return + */ + @GetMapping("/getCaseTitle") + @ResponseBody + public Result getCaseTitle(Integer page,Integer pageSize){ + PageInfo list = service.getCaseTitle(page,pageSize); + return Result.success(list); + } + + /** + * 获取自定义标签列表 + * @param page + * @param pageSize + * @return + */ + @GetMapping("/getOtherLabels") + @ResponseBody + public Result getOtherLabels(Integer page,Integer pageSize){ + PageHelper.startPage(page,pageSize); + List list = service.getOtherLabels(); + return Result.success(new PageInfo<>(list)); + } + + /** + * 添加案例评分 + * @param request + * @return + */ + @PostMapping("/saveScore") + @ResponseBody + public Result saveScore(HttpServletRequest request,Long contentId,Double score){ + String token = request.getHeader("token"); + String Id = JWTUtils.getUserId(token); + Long userId = Long.parseLong(Id); + //查看用户是否已经打过分 + int result = service.getScore(userId,contentId); + if(result > 0 ) throw new ParameterException("请勿重复打分!"); + service.saveScore(userId,contentId,score); + return Result.success(); + } + + /** + * 根据案例id查询案例详情 + * @param contentId + * @return + */ + @GetMapping("/getEditContent") + @ResponseBody + public Result getEditContent(Long contentId){ + Content content = service.getEditContent(contentId); + return Result.success(content); + } + + /** + * 提交审核申请单 + * @param application + * @return + */ + @PostMapping("/saveApplication") + @ResponseBody + public Result saveApplication(Application application){ + //查询案例是否已提交过审核 + int result = service.getApplicationCount(application.getContentId()); + if(result > 0 ) throw new ParameterException("请勿重复提交审核!"); + User loginUser = getLoginUser(); + User user = userService.selectByUsername(loginUser.getUsername()); + application.setUserId(Long.parseLong(user.getId().toString())); + //根据userId获取微信用户信息 + WxUser wxUser = service.getQyWxUser(user.getUsername()); + //根据部门id获取用户领导 + List checks = service.getUserLeader(wxUser.getDepartment()); + application.setChecks(checks); + //添加审核申请单 + service.saveApplication(application); + return Result.success(); + } + + /** + * 撤回审核申请单 + * @return + */ + @PostMapping("/deleteApplication") + @ResponseBody + public Result deleteApplication(Long applicationId){ + //删除案例审核申请 + service.deleteApplication(applicationId); + return Result.success(); + } + + + /** + * 获取审核列表 + * @return + */ + @PostMapping("/getContentApplication") + @ResponseBody + public PageResultVo getContentApplication(Content entity,Integer limit, Integer offset){ + User loginUser = getLoginUser(); + User user = userService.selectByUsername(loginUser.getUsername()); + entity.setUserId(Long.parseLong(user.getId().toString())); + List list = Arrays.asList(entity.getTagLists()); + String[] strings = list.stream().filter(String -> !String.isEmpty()).toArray(String[]::new); + if(strings.length == 0){ + entity.setTagLists(null); + }else { + entity.setTagLists(strings); + entity.setCount(strings.length); + } + PageHelper.startPage(limit,offset); + List list2 = service.getContentApplication(entity); + PageInfo pageInfo = new PageInfo<>(list2); + return ResultUtil.table(list2,pageInfo.getTotal()); + } + + /** + * 审核案例 + * @return + */ + @PostMapping("/updateApplication") + @ResponseBody + public Result updateApplication(Check check){ + User loginUser = getLoginUser(); + User user = userService.selectByUsername(loginUser.getUsername()); + service.updateApplication(check,Long.parseLong(user.getId().toString())); + return Result.success(); + } + + /** + * 下线案例 + * @param contentId + * @return + */ + @PostMapping("/deleteRelease") + @ResponseBody + public Result deleteRelease(Long contentId){ + service.deleteRelease(contentId); + return Result.success(); + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/DataDictController.java b/src/main/java/com/nbclass/activity/controller/DataDictController.java new file mode 100644 index 0000000..ccb182c --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/DataDictController.java @@ -0,0 +1,244 @@ +package com.nbclass.activity.controller; + +import java.util.List; + +import javax.annotation.Resource; + +import com.nbclass.activity.mapper.DataDictMapper; +import com.nbclass.activity.model.ContentTags; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.model.DataDict; +import com.nbclass.activity.model.DataDictItem; +import com.nbclass.activity.service.DataDictService; +import com.nbclass.exception.ParameterException; +import com.nbclass.system.controller.BaseController; +import com.nbclass.system.model.SysLog; +import com.nbclass.system.model.User; +import com.nbclass.system.model.enums.SysLogType; +import com.nbclass.system.service.SysLogService; +import com.nbclass.util.CommonUtils; +import com.nbclass.util.PageUtil; +import com.nbclass.util.ResultUtil; +import com.nbclass.vo.base.PageResultVo; +import com.nbclass.vo.base.Result; + +/** + * 数据字典 + * @author Leon + * @datetime 2020年5月28日 下午3:27:43 + */ +@Controller +@RequestMapping("/console/datadict") +public class DataDictController extends BaseController { + + @Resource + private DataDictService service; + + @Resource + private SysLogService sysLogService; + + @Resource + private DataDictMapper dataDictMapper; + + /** + * 列表页面 + * @return + */ + @GetMapping("/index") + public String indexPage(){ + return "data_dict/list"; + } + + /** + * 编辑页面 + * @param model + * @param id + * @return + */ + @GetMapping("/edit") + public String editPage(Model model, Long id){ + DataDict entity = new DataDict(); + if(id!=null && id>0)entity=service.findById(id); + JSONObject obj = (JSONObject) JSON.toJSON(entity); + model.addAllAttributes(obj); + return "data_dict/edit"; + } + + /** + * 报名用户信息列表 + * @param entity + * @param limit + * @param offset + * @return + */ + @PostMapping("/list") + @ResponseBody + public PageResultVo getList(DataDict entity, Integer limit, Integer offset){ + PageHelper.startPage(PageUtil.getPageNo(limit, offset),limit); + List userList = service.getList(entity); + PageInfo pages = new PageInfo<>(userList); + return ResultUtil.table(userList,pages.getTotal()); + } + + /** + * 根据字典ID查询子项列表 + * @param entity + * @param limit + * @param offset + * @return + */ + @PostMapping("/getItemList") + @ResponseBody + public PageResultVo getItemList(DataDictItem entity, Integer limit, Integer offset){ + PageHelper.startPage(PageUtil.getPageNo(limit, offset),limit); + List userList = service.getItemList(entity); + PageInfo pages = new PageInfo<>(userList); + return ResultUtil.table(userList,pages.getTotal()); + } + + /** + * i添加或修改记录 + * @param entity + */ + @PostMapping("/merge") + @ResponseBody + public Result merge(DataDict entity){ + String desc = null; + if(entity.getId()!=null && entity.getId()>0){ + service.update(entity); + desc = "修改数据字典,内容为:" + JSON.toJSONString(entity); + }else{ + int result = dataDictMapper.getDataDicName(entity.getName()); + if(result > 0 ) throw new ParameterException("该字典已存在!"); + service.add(entity); + desc = "添加数据字典,内容为:" + JSON.toJSONString(entity); + } + User user = getLoginUser(); + sysLogService.add(new SysLog(SysLogType.dict.key(), user.getUserId()+"("+user.getUsername()+")", getIpAddr(), desc)); + return Result.success(); + } + + /**删除记录*/ + @PostMapping("/delete") + @ResponseBody + public Result delete(String ids) { + List IdList = CommonUtils.toLongList(ids); + service.delete(IdList); + User user = getLoginUser(); + sysLogService.add(new SysLog(SysLogType.dict.key(), user.getUserId()+"("+user.getUsername()+")", getIpAddr(), "删除数据字典,ids:"+ids)); + return Result.success(); + } + + /** + * 根据字典key获取案例标签列表 + * @param dictKey + * @return + */ + @RequestMapping("getTagSelect") + @ResponseBody + public Result getTagSelect(String dictKey){ + if(StringUtils.isBlank(dictKey)) { + throw new ParameterException("参数不能为空"); + } + try { + List obj = service.getItemsByKey(dictKey); + return Result.success(obj); + } catch (Exception e) { + logger.error("getTagSelect异常", e); + return Result.error("获取数据失败"); + } + } + + /** + * 标签下拉联想获取数据 + * @param tags + * @return + */ + @ResponseBody + @RequestMapping("/getTags") + public JSONArray getCategoryTrees(String dictKey, String tags){ +// if(StringUtils.isBlank(dictKey)) { +// throw new ParameterException("参数不能为空"); +// } + JSONArray result = new JSONArray(); + //List list = service.getItemsByKey(dictKey); + List list = service.getItemsByKey("other_labels"); + for (DataDictItem entity : list) { + JSONObject obj = new JSONObject(); + obj.put("id", entity.getId()); + obj.put("value", entity.getName()); + result.add(obj); + } + return result; + } + + + /** + * 根据字典key获取案例来源列表 + * @param dictKey + * @return + */ + @RequestMapping("getCaseFromSelect") + @ResponseBody + public Result getCaseFromSelect(String dictKey){ + if(StringUtils.isBlank(dictKey)) { + throw new ParameterException("参数不能为空"); + } + try { + List obj = service.getItemsByKey(dictKey); + return Result.success(obj); + } catch (Exception e) { + logger.error("getCaseFromSelect异常", e); + return Result.error("获取数据失败"); + } + } + + + /** + * 添加或修改字典子项 + * @param entity + * @return + */ + @PostMapping("/mergeDataDictItem") + @ResponseBody + public Result mergeDataDictItem(DataDictItem entity){ + service.mergeDataDictItem(entity); + return Result.success(); + } + + /** + * 删除字典子项 + * @param entity + * @return + */ + @PostMapping("/deleteDataDictItem") + @ResponseBody + public Result deleteDataDictItem(Long id){ + service.deleteDataDictItem(id); + return Result.success(); + } + + /** + * 获取字典值 + * @return + */ + @GetMapping("/getDropDownBox") + @ResponseBody + public Result getDropDownBox(){ + List list = service.getDropDownBox(); + return Result.success(list); + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/OssUploadController.java b/src/main/java/com/nbclass/activity/controller/OssUploadController.java new file mode 100644 index 0000000..39e881b --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/OssUploadController.java @@ -0,0 +1,92 @@ +package com.nbclass.activity.controller; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Date; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.activity.constant.Const; +import com.nbclass.aliyun.sdk.AliyunOSSUtils; +import com.nbclass.exception.ParameterException; +import com.nbclass.system.controller.BaseController; +import com.nbclass.util.CommonUtils; +import com.nbclass.vo.base.Result; + +import lombok.extern.slf4j.Slf4j; + +/** + * 阿里云OSS上传工具 + * @author Leon + * @datetime 2020年5月27日 下午6:51:55 + */ +@Slf4j +@RestController +@RequestMapping("/console/tool/oss") +public class OssUploadController extends BaseController{ + + /** + * i上传文件到阿里云OSS + * @param file + * @param category 文件类别【icon:案例列表icon图,attachment:案例附件,image:案例正文图集,video:视频】 + * @return + */ + @PostMapping("/uploadFile") + public Result fileUploadTest(@RequestParam(required=false, value="file")MultipartFile file, String caseType, String category){ + if(file==null || file.isEmpty()){ + throw new ParameterException("请选择文件"); + } + /*if(StringUtils.isBlank(caseType)) { + throw new ParameterException("案例类型不能为空"); + }else if(!Arrays. asList("ppqa,ggqa,design,video,h5,ldhd,others".split(",")).contains(caseType)) { + throw new ParameterException("案例类型参数有误"); + }*/ + if(StringUtils.isBlank(category)) { + throw new ParameterException("文件类别不能为空"); + } + category = category.toLowerCase().trim(); + if(!Arrays. asList("icon,attachment,image,video".split(",")).contains(category)) { + throw new ParameterException("文件类别参数有误"); + } + try { + String suffix = "", ori_name = file.getOriginalFilename(); + if(StringUtils.isNotBlank(ori_name)){ + suffix = ori_name.substring(ori_name.lastIndexOf("."), ori_name.length()); + suffix = suffix.toLowerCase(); + } + if(("icon".equals(category)||"image".equals(category)) + && !Arrays. asList(Const.UPLOAD_IMAGE_SUFFIX.split(",")).contains(suffix)) { + throw new ParameterException("请上传正确的图片"); + } + + if("video".equals(category) && !Arrays. asList(Const.UPLOAD_VIDEO_SUFFIX.split(",")).contains(suffix)) { + throw new ParameterException("请上传正确的视频"); + } + + String daydir = CommonUtils.getSimpleDate(new Date(), "yyyyMM"); + if("icon".equals(category)) { // icon图上传一年1个目录 + daydir = CommonUtils.getSimpleDate(new Date(), "yyyy"); + }else if("video".equals(category)) { // 视频也是按一年1个目录 + daydir = CommonUtils.getSimpleDate(new Date(), "yyyy"); + } + + String filename = CommonUtils.getSimpleDate(new Date(), "yyyyMMddHHmmss")+"_"+CommonUtils.getCode(5)+suffix; + // 组成的文件key eg. /casetype-h5/icons/xxx.jpg + String url = AliyunOSSUtils.uploadBytes("casetype-"+caseType+"/"+category+"s"+"/"+daydir, filename, file.getBytes()); + JSONObject fileObj = new JSONObject(); + fileObj.put("url", url); + fileObj.put("ori_name", ori_name); + return Result.success(fileObj); + } catch (IOException e) { + log.error("上传到OSS文件异常", e); + return Result.error(e.getMessage()!=null ? e.getMessage() :"上传文件失败"); + } + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/SysLogController.java b/src/main/java/com/nbclass/activity/controller/SysLogController.java new file mode 100644 index 0000000..c3df803 --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/SysLogController.java @@ -0,0 +1,73 @@ +package com.nbclass.activity.controller; + +import java.util.List; + +import javax.annotation.Resource; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.system.controller.BaseController; +import com.nbclass.system.model.SysLog; +import com.nbclass.system.service.SysLogService; +import com.nbclass.util.CommonUtils; +import com.nbclass.util.PageUtil; +import com.nbclass.util.ResultUtil; +import com.nbclass.vo.base.PageResultVo; + +/** + * i系统日志 + * @author Leon + * @datetime 2020年6月3日 下午5:28:24 + */ +@Controller +@RequestMapping("/console/syslog") +public class SysLogController extends BaseController { + + @Resource + private SysLogService service; + + /** + * i查询列表页面 + * @param model + * @return + */ + @GetMapping("/index") + public String indexPage(){ + return "syslog/list"; + } + + /** + * i查询数据列表 + * @param entity + * @param limit + * @param offset + * @return + */ + @PostMapping("/list") + @ResponseBody + public PageResultVo getList(SysLog entity, Integer limit, Integer offset){ + PageHelper.startPage(PageUtil.getPageNo(limit, offset),limit); + List userList = service.getList(entity); + PageInfo pages = new PageInfo<>(userList); + return ResultUtil.table(userList,pages.getTotal()); + } + + /** + * i删除数据 + * @param ids + */ + @PostMapping("/delete") + @ResponseBody + public void delete(String ids) { + List IdList = CommonUtils.toLongList(ids); + service.delete(IdList); + outPrint(); + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/casesfirst/CaseTypeController.java b/src/main/java/com/nbclass/activity/controller/casesfirst/CaseTypeController.java new file mode 100644 index 0000000..9eec68d --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/casesfirst/CaseTypeController.java @@ -0,0 +1,82 @@ +package com.nbclass.activity.controller.casesfirst; + +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.model.*; +import com.nbclass.activity.service.CaseTypeService; +import com.nbclass.vo.base.Result; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 案例web前台 + * @author gao + * @datetime 2021年10月12日 + */ +@RestController +@RequestMapping("/caseType") +public class CaseTypeController { + + @Autowired + private CaseTypeService caseTypeService; + + /** + * 数据字典(案例类型、案例用途、创意形式、节日事件)列表查询 + * @return + */ + @GetMapping("/getTypeCase") + public Result getTypeCase(Integer dictId){ + List list = caseTypeService.getTypeCase(dictId); + return Result.success(list); + } + + /** + * 字典值类型列表查询 + * @return + */ + @GetMapping("/getDictionariesType") + public Result getDictionariesType(){ + List list = caseTypeService.getDictionariesType(); + return Result.success(list); + } + + /** + * 案例库列表 + * @param page + * @param pageSize + * @return + */ + @GetMapping("/getListCase") + public Result getListCase(Integer page,Integer pageSize){ + PageInfo list = caseTypeService.getListCase(page,pageSize); + return Result.success(list); + } + + /** + * 案例搜索框 + * @param parameter 任意参数(案例标签,案例标题,案例编号) + * @param dictItemId 案例类型id + * @return + */ + @GetMapping("/getSearchCase") + public Result getSearchCase(String parameter,Integer dictItemId,Integer page,Integer pageSize){ + PageInfo list = caseTypeService.getSearchCase(parameter,dictItemId,page,pageSize); + return Result.success(list); + } + + /** + * 获取案例标签 + * @param contentId 案例id + * @return + */ + @GetMapping("/getContentTags") + public Result getContentTags(Integer contentId){ + ContentTags contentTags = caseTypeService.getContentTags(contentId); + return Result.success(contentTags); + } + + +} diff --git a/src/main/java/com/nbclass/activity/controller/casesfirst/CommentController.java b/src/main/java/com/nbclass/activity/controller/casesfirst/CommentController.java new file mode 100644 index 0000000..3e067eb --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/casesfirst/CommentController.java @@ -0,0 +1,342 @@ +package com.nbclass.activity.controller.casesfirst; + +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.model.*; +import com.nbclass.activity.service.CommentService; +import com.nbclass.activity.service.ContentService; +import com.nbclass.exception.ParameterException; +import com.nbclass.util.JWTUtils; +import com.nbclass.vo.base.Result; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 评论api + */ +@RestController +@RequestMapping("/comment") +public class CommentController { + + @Autowired + private CommentService commentService; + + @Autowired + private ContentService contentService; + + /** + * 案例详情和评论列表 + * @param contentId + * @return + */ + @GetMapping("/getComment") + public Result getComment(HttpServletRequest request,Long contentId){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + Content content = commentService.getComment(contentId,Long.parseLong(userId)); + return Result.success(content); + } + + /** + * 评论回复列表 + * @param request + * @param commentId 评论id + * @return + */ + @GetMapping("/getCommentReplyList") + public Result getCommentReplyList(HttpServletRequest request,Long commentId){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + Comment comment = commentService.getCommentReplyList(commentId,Long.parseLong(userId)); + return Result.success(comment); + } + + /** + * 添加用户发布信息 + * @param comment + * @return + */ + @PostMapping("/saveComment") + public Result saveComment(Comment comment,HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + comment.setUserId(Long.parseLong(userId)); + commentService.saveComment(comment); + //更新案例总评论数 + contentService.updateComments(comment.getContentId()); + return Result.success(); + } + + /** + * 添加评论点赞 + * @param commentId 评论Id + * @return + */ + @PostMapping("/updatePraiseNo") + public Result updatePraiseNo(Long commentId, HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + //根据评论id获取被点赞人Id + Comment comment = commentService.getCommentUserId(commentId); + System.out.println(comment); + Praise praise = new Praise(); + praise.setCommentId(commentId); + praise.setPraiseId(Long.parseLong(userId)); + praise.setUserId(comment.getUserId()); + commentService.savePraiseNo(praise); + //评论总数加1 + commentService.updatePraiseno(commentId); + return Result.success(); + } + + /** + * 取消评论点赞 + * @return + */ + @PostMapping("/deletePraise") + public Result deletePraise(Long commentId,HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + commentService.deletePraise(commentId,Long.parseLong(userId)); + //总点赞数减一 + commentService.updatePraiseNoONE(commentId); + return Result.success(); + } + + /** + * 添加案例点赞 + * @param contentId + * @return + */ + @PostMapping("/updateContentPraiseNo") + public Result updateContentPraiseNo(Long contentId){ + commentService.updateContentPraiseNo(contentId); + return Result.success(); + } + + + /** + * 用户回复 + * @param comment + * @param request + * @return + */ + @PostMapping("/saveUserReply") + public Result saveUserReply(Comment comment,HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + comment.setReplyId(Long.parseLong(userId)); + commentService.saveUserReply(comment); + //评论总回复数加一 + commentService.updateReplyNumberONE(comment.getCommentId()); + //更新案例总评论数 + contentService.updateComments(comment.getContentId()); + return Result.success(); + } + + /** + * 用户举报 + * @param report + * @return + */ + @PostMapping("/saveReport") + public Result saveReport(Report report,HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + report.setReportId(Long.parseLong(userId)); + commentService.saveReport(report); + return Result.success(); + } + + /** + * 举报列表获取 + * @param page + * @param pageSize + * @return + */ + @GetMapping("/getReport") + public Result getReport(Integer page,Integer pageSize,HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + PageInfo list = commentService.getReport(page,pageSize,userId); + return Result.success(list); + } + + + + /** + * 添加标签、索引反馈日志 + * @return + */ + @PostMapping("/saveLabelLogger") + public Result saveLabelLogger(LabelLogger labelLogger,HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + labelLogger.setUserId(Long.parseLong(userId)); + commentService.saveLabelLogger(labelLogger); + //案例索引标签反馈次数+1 + contentService.updateLabelFeedback(labelLogger.getContentId()); + return Result.success(); + } + + /** + * 标签、索引反馈日志列表 + * @param request + * @param page + * @param pageSize + * @return + */ + @GetMapping("/getLabelLogger") + public Result getLabelLogger(HttpServletRequest request,Integer page,Integer pageSize){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + PageInfo list = commentService.getLabelLogger(page,pageSize,Long.parseLong(userId)); + return Result.success(list); + } + + /** + * 新建文件夹 + * @param favoritesFolder + * @param request + * @return + */ + @PostMapping("/saveFavorites") + public Result saveFavorites(FavoritesFolder favoritesFolder, HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + //判断文件夹是否重名 + int result = commentService.getFavoritesName(favoritesFolder.getName()); + if(result > 0){ + throw new ParameterException("文件夹已存在!"); + } + favoritesFolder.setUserId(Long.parseLong(userId)); + commentService.saveFavorites(favoritesFolder); + return Result.success(); + } + + /** + * 案例详情的文件夹列表 + * @param request + * @return + */ + @GetMapping("/getUserFolder") + public Result getUserFolder(HttpServletRequest request,Integer page,Integer pageSize,Long commentId){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + PageInfo list = commentService.getUserFolder(Long.parseLong(userId),page,pageSize,commentId); + return Result.success(list); + } + + /** + * 收藏案例到文件夹中 + * @param contentId + * @return + */ + @PostMapping("/saveFolder") + public Result saveFolder(Long folderId,Long contentId){ + //判断该文件夹是否已被收藏 + int result = commentService.getFolderExistence(folderId,contentId); + if(result > 0 ){ + throw new ParameterException("该文件夹已被收藏!"); + } + //收藏案例到文件夹中 + commentService.saveFolder(folderId,contentId); + //添加收藏总数 + commentService.saveFolderTotal(folderId); + //更新案例的收藏总数+1 + commentService.updateCollectionNumber(contentId); + return Result.success(); + } + + /** + * 取消文件夹中的案例收藏 + * @param folderId + * @param contentId + * @return + */ + @PostMapping("/deleteFolderResources") + public Result deleteFolderResources(Long folderId,Long contentId){ + commentService.deleteFolderResources(folderId,contentId); + //更新案例的收藏总数-1 + commentService.updateCollectionNumberReduce(contentId); + return Result.success(); + } + + /** + * 获取用户文件夹列表 + * @param request + * @return + */ + @GetMapping("/getUserFolderList") + public Result getUserFolderList(HttpServletRequest request,Integer page,Integer pageSize){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + PageInfo list = commentService.getUserFolderList(Long.parseLong(userId),page,pageSize); + return Result.success(list); + } + + /** + * 我的收藏 + * @param request + * @param page + * @param pageSize + * @return + */ + @GetMapping("/getMyCollection") + public Result getMyCollection(HttpServletRequest request,Integer page,Integer pageSize,Long folderId){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + PageInfo list = commentService.getMyCollection(Long.parseLong(userId),page,pageSize,folderId); + return Result.success(list); + } + + /** + * 个人中心回复列表 + * @param page + * @param pageSize + * @return + */ + @GetMapping("/getReplyList") + public Result getReplyList(HttpServletRequest request,Integer page,Integer pageSize){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + PageInfo list = commentService.getReplyList(Long.parseLong(userId),page,pageSize); + return Result.success(list); + } + + /** + * 点赞列表 + * @param request + * @param page + * @param pageSize + * @return + */ + @GetMapping("/getFabulousList") + public Result getFabulousList(HttpServletRequest request,Integer page,Integer pageSize){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + PageInfo list = commentService.getFabulousList(Long.parseLong(userId),page,pageSize); + return Result.success(list); + } + + /** + * 案例打分 + * @param scoring + * @return + */ + @PostMapping("/saveScoring") + public Result saveScoring(HttpServletRequest request,Scoring scoring){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + scoring.setUserId(Long.parseLong(userId)); + commentService.saveScoring(scoring); + return Result.success(); + } + + +} diff --git a/src/main/java/com/nbclass/activity/controller/casesfirst/WxController.java b/src/main/java/com/nbclass/activity/controller/casesfirst/WxController.java new file mode 100644 index 0000000..d66f0dc --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/casesfirst/WxController.java @@ -0,0 +1,218 @@ +package com.nbclass.activity.controller.casesfirst; + + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.nbclass.activity.model.WeiXiUser; +import com.nbclass.activity.service.WxService; +import com.nbclass.exception.ParameterException; + +import com.nbclass.util.HttpUtils; +import com.nbclass.util.JWTUtils; +import com.nbclass.vo.base.Result; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + + +/** + * 微信接口 + * @author gao + * @datetime 2021年10月14日 + */ +@Controller +@RequestMapping("/wx") +public class WxController { + + /** + * wx的AppID + */ + private static final String appId = "wx013ea7738ce6991f"; + + /** + * wx的AppSecret + */ + private static final String AppSecret = "03255f509c6b53cb2b94c9bd21b8aaa8"; + + /** + * 企业微信corpid + */ + private static final String corpid = "wwc0b239a20872ff25"; + + /** + * 企业微信corpsecret(客户的secret) + */ + private static final String corpsecret = "VuJ-Sd2X-oUwFUCzzdQfDZDe0Ryb3IbvkczFu8KUP0o"; + + @Autowired + private WxService wxService; + + @RequestMapping("/loginPage") + public String loginPage(){ + return "loginPage"; + } + + @RequestMapping("/weixinLogin") + public String weixinLogin(){ + return "weixinLogin"; + } + + @RequestMapping("/demo") + public String demo(){ + return "demo"; + } + + /** + * 获取微信用户信息 + * @param request + * @return + */ + @GetMapping("/getWxUser") + @ResponseBody + public Result getWxUser(HttpServletRequest request){ + if(request.getParameter("code")==null){ + throw new ParameterException("code参数异常!"); + } + + //获取code + String code = request.getParameter("code"); + + //获取access_token + String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"; + url = String.format(url,appId,AppSecret,code); + JSONObject jsonObject = HttpUtils.httpGet(url); + + if(jsonObject.getString("access_token")==null){ + throw new ParameterException("access_token参数异常!"); + } + + if(jsonObject.getString("openid")==null){ + throw new ParameterException("openid参数异常!"); + } + + String accessToken = jsonObject.getString("access_token"); + String openid = jsonObject.getString("openid"); + //String wxUnionId = jsonObject.getString("unionid"); + + //数据库查询是否有该openid,如果有直接从数据库获取用户信息 + int result = wxService.getOpenId(openid); + if(result == 0){ + //获取用户信息 + String url2 = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s"; + url2 = String.format(url2,accessToken,openid); + JSONObject jsonUser = HttpUtils.httpGet(url2); + WeiXiUser weiXiUser = jsonUser.toJavaObject(WeiXiUser.class); + //添加用户信息到数据库 + wxService.saveWxUser(weiXiUser); + } + //数据库获取用户信息 + WeiXiUser weiXiUser = wxService.getWxUser(openid); + WeiXiUser user = new WeiXiUser(); + user.setId(weiXiUser.getId()); + user.setOpenid(weiXiUser.getOpenid()); + + String token = ""; + + //生成token + String subject = JSON.toJSONString(user); + try { + token = JWTUtils.createJWT(UUID.randomUUID().toString(), "admin", subject, 7200 * 1000, null); + } catch (Exception e) { + e.printStackTrace(); + } + Map map = new HashMap<>(); + map.put("token",token); + map.put("nickname",weiXiUser.getNickname()); + map.put("headimgurl",weiXiUser.getHeadimgurl()); + + + //获取企业微信的access_token + /* String qyWxUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s"; + qyWxUrl = String.format(qyWxUrl,corpid,corpsecret); + JSONObject qyWxJson = HttpUtils.httpGet(qyWxUrl); + if(qyWxJson.getString("access_token")==null){ + throw new ParameterException("企业微信的access_token参数异常!"); + } + String qyWxAccessToken = qyWxJson.getString("access_token"); + + //获取客户群列表 + String qyWxListUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/list?access_token=%s"; + qyWxListUrl = String.format(qyWxListUrl,qyWxAccessToken); + String limit = "{\"limit\": 1000}"; + String s1 = HttpUtil.doPostJson(qyWxListUrl, limit); + JSONObject jsonObject = JSON.parseObject(s1); + + if(jsonObject.getString("group_chat_list")==null){ + throw new ParameterException("企业微信的chatId参数异常!"); + } + + String s = jsonObject.toJSONString(); + System.out.println(s); + Map groupChats = JSONObject.parseObject(s,Map.class); + JSONArray jsonArray = JSONArray.parseArray(groupChats.get("group_chat_list").toString()); + //获取chatId + String chatId = jsonArray.getJSONObject(0).getString("chat_id"); + + //获取客户群详情 + String khUrl = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat/get?access_token=%s"; + khUrl = String.format(khUrl,qyWxAccessToken); + String chat_id = "{\"chat_id\": \""+chatId+"\"}"; + String s2 = HttpUtil.doPostJson(khUrl, chat_id); + System.out.println(s2); + String group_chat = JSON.parseObject(s2).get("group_chat").toString(); + GroupChat groupChat = JSONObject.parseObject(group_chat, GroupChat.class); + System.out.println(groupChat); + List memberLists = groupChat.getMember_list(); + List list = new ArrayList<>(); + for (MemberList memberList : memberLists) { + *//* if(wxUnionId.equals(memberList.getUnionid())){ + + }*//* + + System.out.println(memberList.getUnionid()); + }*/ + + + return Result.success(map); + } + + /** + * 查看用户基础信息 + * @return + */ + @GetMapping("/getUserBasicData") + @ResponseBody + public Result getUserBasicData(HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + WeiXiUser weiXiUser = wxService.getUserBasicData(Long.parseLong(userId)); + return Result.success(weiXiUser); + } + + /** + * 更新用户基础信息 + * @param weiXiUser + * @return + */ + @PostMapping("/updateBasicData") + @ResponseBody + public Result updateBasicData(WeiXiUser weiXiUser, HttpServletRequest request){ + String token = request.getHeader("token"); + String userId = JWTUtils.getUserId(token); + weiXiUser.setId(Long.parseLong(userId)); + weiXiUser.setUpdateTime(new Date()); + wxService.updateBasicData(weiXiUser); + return Result.success(); + } + + +} diff --git a/src/main/java/com/nbclass/activity/controller/frontapi/FrontContentController.java b/src/main/java/com/nbclass/activity/controller/frontapi/FrontContentController.java new file mode 100644 index 0000000..4585cd5 --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/frontapi/FrontContentController.java @@ -0,0 +1,88 @@ +package com.nbclass.activity.controller.frontapi; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.activity.model.Content; +import com.nbclass.activity.model.enums.ContentType; +import com.nbclass.activity.service.ElasticSearchService; +import com.nbclass.exception.ParameterException; +import com.nbclass.system.controller.BaseController; +import com.nbclass.vo.base.Result; + +/** + * i前端获取案例查询 + * @author Leon + * @datetime 2020年5月21日 下午5:32:25 + */ +@RestController +@RequestMapping("/front/api/content") +public class FrontContentController extends BaseController { + +// @Resource +// private ContentService service; + + @Resource + private ElasticSearchService esService; + + /** + * 查询案例列表 + * @param page + * @param o + * @param response + * @return + */ + @RequestMapping("/list") + public Result getList(String type, HttpServletResponse response, + @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, + String keyWord, String tagIds, String sourceIds){ + response.setHeader("Access-Control-Allow-Origin", "*"); + if(StringUtils.isNotBlank(type)){ + type = type.trim().toLowerCase(); + if(!ContentType.ppqa.key().equals(type) && !ContentType.ggqa.key().equals(type) && + !ContentType.design.key().equals(type) && !ContentType.video.key().equals(type) && + !ContentType.h5.key().equals(type) && !ContentType.ldhd.key().equals(type) + && !ContentType.others.key().equals(type)){ + throw new ParameterException("参数[type]不是有效的数据!"); + } + } + // List tagIdList = CommonUtils.toIntList(tagIds); + // List sourceIdList = CommonUtils.toIntList(sourceIds); + JSONObject resultObj = esService.getList(pageNum, pageSize, type, keyWord, tagIds, sourceIds); + return Result.success(resultObj); + } + + /** + * 根据id查找单条记录 + * @param id + * @param response + * @return + */ + @RequestMapping("/findById") + public Result findById(Long id, String type, HttpServletResponse response){ + response.setHeader("Access-Control-Allow-Origin", "*"); + if(StringUtils.isBlank(type)){ + throw new ParameterException("参数[type]不能为空!"); + } + type=type.trim().toLowerCase(); + if(!ContentType.ppqa.key().equals(type) && !ContentType.ggqa.key().equals(type) && + !ContentType.design.key().equals(type) && !ContentType.video.key().equals(type) && + !ContentType.h5.key().equals(type) && !ContentType.ldhd.key().equals(type) + && !ContentType.others.key().equals(type)){ + throw new ParameterException("参数[type]不是有效的数据!"); + } + if(id == null || id < 1) { + throw new ParameterException("参数[id]不能为空!"); + } + // ES里保存的ID是唯一的,所以不需要提交type字段查询 + Content content = esService.findById(id); + return Result.success(content); + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/frontapi/FrontContentLikeNumController.java b/src/main/java/com/nbclass/activity/controller/frontapi/FrontContentLikeNumController.java new file mode 100644 index 0000000..69d0725 --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/frontapi/FrontContentLikeNumController.java @@ -0,0 +1,66 @@ +package com.nbclass.activity.controller.frontapi; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nbclass.activity.service.ContentService; +import com.nbclass.exception.LogicalException; +import com.nbclass.exception.ParameterException; +import com.nbclass.exception.ServiceException; +import com.nbclass.system.controller.BaseController; +import com.nbclass.util.WebUtils; +import com.nbclass.vo.base.Result; + +/** + * i案例库阅读数点赞数功能 + * @author Leon + * @datetime 2020年5月22日 下午2:02:13 + */ +@RestController +public class FrontContentLikeNumController extends BaseController { + + @Resource + private ContentService service; + + /** + * 提交阅读数点赞数 + * @param type 提交类型[view:阅读数, like:点赞数] + * @param id 点赞记录id + * @param response + * @return + */ + @PostMapping("/front/api/content/addNum") + public Result updateContentNum(String type, Long id, HttpServletResponse response){ + response.setHeader("Access-Control-Allow-Origin", "*"); + try { + if(StringUtils.isBlank(type))throw new ParameterException("参数type不能为空"); + if(id == null || id < 1)throw new ParameterException("案例id不能为空"); + + String cookieNum = WebUtils.getValueFromCookie(getRequest(), "case_content_num_"+type+"_"+id); + if(StringUtils.isNotBlank(cookieNum)){ + if("view".equals(type)){ + return Result.success(); + }else{ + throw new ParameterException("你已经点过赞了"); + } + } + int num = service.updateContentNum(type, id);; + + WebUtils.addCookie(response, "case_content_num_"+type+"_"+id, String.valueOf(type+id), 3600 * 24 * 1); + return Result.success(num); + } catch (Exception e) { + String errmsg = "保存失败"; + if(e instanceof ParameterException || e instanceof ServiceException || e instanceof LogicalException){ + errmsg = e.getMessage(); + }else{ + logger.error("提交阅读数点赞数失败", e); + } + return Result.error(errmsg); + } + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/frontapi/FrontDataDictController.java b/src/main/java/com/nbclass/activity/controller/frontapi/FrontDataDictController.java new file mode 100644 index 0000000..22008e3 --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/frontapi/FrontDataDictController.java @@ -0,0 +1,79 @@ +package com.nbclass.activity.controller.frontapi; + +import java.util.List; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.nbclass.activity.model.DataDictItem; +import com.nbclass.activity.model.enums.ContentType; +import com.nbclass.activity.service.DataDictService; +import com.nbclass.exception.ParameterException; +import com.nbclass.system.controller.BaseController; +import com.nbclass.vo.base.Result; + +/** + * i前端获取案例标签或来源列表 + * @author Leon + * @datetime 2020年5月21日 下午5:32:25 + */ +@RestController +@RequestMapping("/front/api/dict") +public class FrontDataDictController extends BaseController { + + @Resource + public DataDictService service; + + /** + * 根据类型查询案例标签列表 + * @param type + * @param response + * @return + */ + @RequestMapping("/getTags") + public Result getTags(String type, HttpServletResponse response){ + response.setHeader("Access-Control-Allow-Origin", "*"); + if(StringUtils.isBlank(type)) { + throw new ParameterException("参数type不能为空"); + } + type=type.trim().toLowerCase(); + if(!ContentType.ppqa.key().equals(type) && !ContentType.ggqa.key().equals(type) && + !ContentType.design.key().equals(type) && !ContentType.video.key().equals(type) && + !ContentType.h5.key().equals(type) && !ContentType.ldhd.key().equals(type) + && !ContentType.others.key().equals(type)){ + throw new ParameterException("参数[type]不是有效的数据!"); + } + type = "tag_"+type; + List list = service.getItemsByKey(type); + return Result.success(list); + } + + /** + * 根据类型查询案例来源列表 + * @param type + * @param response + * @return + */ + @RequestMapping("/getSources") + public Result getSources(String type, HttpServletResponse response){ + response.setHeader("Access-Control-Allow-Origin", "*"); + if(StringUtils.isBlank(type)) { + throw new ParameterException("参数type不能为空"); + } + type=type.trim().toLowerCase(); + if(!ContentType.ppqa.key().equals(type) && !ContentType.ggqa.key().equals(type) && + !ContentType.design.key().equals(type) && !ContentType.video.key().equals(type) && + !ContentType.h5.key().equals(type) && !ContentType.ldhd.key().equals(type) + && !ContentType.others.key().equals(type)){ + throw new ParameterException("参数[type]不是有效的数据!"); + } + type = "from_"+type; + List list = service.getItemsByKey(type); + return Result.success(list); + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/keditor/KindEditorController.java b/src/main/java/com/nbclass/activity/controller/keditor/KindEditorController.java new file mode 100644 index 0000000..10d9eed --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/keditor/KindEditorController.java @@ -0,0 +1,332 @@ +package com.nbclass.activity.controller.keditor; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import net.coobird.thumbnailator.Thumbnails; + +/** + * kindeditor上传文件 - 保存的本地项目的相对目录 + * @author Leon + * @datetime 2019年4月9日 下午7:52:31 + */ +@RequestMapping("/console/kindeditor/local") +@RestController +public class KindEditorController { + + protected static Logger logger = LoggerFactory.getLogger(KindEditorController.class); + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private static final long upload_max_size = 5242880; // 上传文件最大值,5M + + @PostMapping("/api/file-upload") + public Map fileUpload(HttpServletRequest request, + HttpServletResponse response,@RequestParam("imgFile")MultipartFile file) + throws ServletException, IOException, FileUploadException { + response.setContentType("text/html; charset=UTF-8"); + ServletContext application = request.getSession().getServletContext(); + String savePath = application.getRealPath("/") + "keditor-attached/"; + + // 文件保存目录URL + String saveUrl = request.getContextPath() + "/keditor-attached/"; + + // 定义允许上传的文件扩展名 + HashMap extMap = new HashMap(); + extMap.put("image", "gif,jpg,jpeg,png,bmp"); + extMap.put("flash", "swf,flv"); + extMap.put("media", "swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb,mp4,mov"); // 上传音视频格式 + extMap.put("file", "doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2"); + + if (!ServletFileUpload.isMultipartContent(request)) { + return getError("请选择文件。"); + } + // 检查目录 + File uploadDir = new File(savePath); + if (!uploadDir.isDirectory()) { + // return getError("上传目录不存在。"); + uploadDir.mkdirs(); + } + // 检查目录写权限 + if (!uploadDir.canWrite()) { + return getError("上传目录没有写权限。"); + } + + String dirName = request.getParameter("dir"); + if (dirName == null) { + dirName = "image"; + } + if (!extMap.containsKey(dirName)) { + return getError("目录名不正确。"); + } + // 创建文件夹 + savePath += dirName + "/"; + saveUrl += dirName + "/"; + File saveDirFile = new File(savePath); + if (!saveDirFile.exists()) { + saveDirFile.mkdirs(); + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String ymd = sdf.format(new Date()); + savePath += ymd + "/"; + saveUrl += ymd + "/"; + File dirFile = new File(savePath); + if (!dirFile.exists()) { + dirFile.mkdirs(); + } + + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + upload.setHeaderEncoding("UTF-8"); + //List items = upload.parseRequest(request); + //Iterator itr = items.iterator(); + + // FileItem item = (FileItem) itr.next(); + String fileName = file.getOriginalFilename(); + + // 检查文件大小 + if (file.getSize() > upload_max_size) { + return getError("上传文件大小超过限制,请上传5M内的文件。"); + } + // 检查扩展名 + String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + if (!Arrays. asList(extMap.get(dirName).split(",")).contains(fileExt)) { + return getError("上传文件扩展名是不允许的扩展名。\n只允许" + extMap.get(dirName) + "格式。"); + } + + SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); + String newFileName = df.format(new Date()) + "_" + new Random().nextInt(1000) + "." + fileExt; + try { + logger.info("savePath="+savePath+", newFileName="+newFileName); + // File uploadedFile = new File(savePath, newFileName); + File saveFile = new File(savePath+newFileName); + file.transferTo(saveFile); + createThumbnail(savePath, newFileName); + } catch (Exception e) { + e.printStackTrace(); + return getError("上传文件失败。"); + } + Map msg = new HashMap(); + msg.put("error", 0); + msg.put("url", saveUrl + newFileName); + return msg; + } + + private Map getError(String message) { + Map msg = new HashMap(); + msg.put("error", 1); + msg.put("message", message); + return msg; + } + + /** + * @param savePath + * @param newFileName + */ + private void createThumbnail(String savePath, String newFileName) { + try { + Thumbnails.of(savePath+newFileName) + .size(200,200) + .toFile(savePath+"thump"+newFileName); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("unchecked") + @RequestMapping(value = "/api/file-manager") + public void fileManager(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + ServletContext application = request.getSession().getServletContext(); + ServletOutputStream out = response.getOutputStream(); + // 根目录路径,可以指定绝对路径,比如 /var/www/attached/ + String rootPath = application.getRealPath("/") + "keditor-attached/"; + // 根目录URL,可以指定绝对路径,比如 http://www.yoursite.com/attached/ + + String rootUrl = request.getContextPath() + "/keditor-attached/"; + + // 图片扩展名 + String[] fileTypes = new String[] { "gif", "jpg", "jpeg", "png", "bmp" }; + + String dirName = request.getParameter("dir"); + if (dirName != null) { + if (!Arrays. asList(new String[] { "image", "flash", "media", "file" }).contains(dirName)) { + out.println("Invalid Directory name."); + return; + } + rootPath += dirName + "/"; + rootUrl += dirName + "/"; + File saveDirFile = new File(rootPath); + if (!saveDirFile.exists()) { + saveDirFile.mkdirs(); + } + } + // 根据path参数,设置各路径和URL + String path = StringUtils.trimToEmpty(request.getParameter("path")); + String currentPath = rootPath + path; + String currentUrl = rootUrl + path; + String currentDirPath = path; + String moveupDirPath = ""; + if (StringUtils.isNotBlank(path)) { + String str = currentDirPath.substring(0, + currentDirPath.length() - 1); + moveupDirPath = str.lastIndexOf("/") >= 0 ? str.substring(0, + str.lastIndexOf("/") + 1) : ""; + } + + // 排序形式,name or size or type + String order = request.getParameter("order") != null ? request.getParameter("order").toLowerCase() : "name"; + + // 不允许使用..移动到上一级目录 + if (path.indexOf("..") >= 0) { + out.println("Access is not allowed."); + return; + } + // 最后一个字符不是/ + if (!"".equals(path) && !path.endsWith("/")) { + out.println("Parameter is not valid."); + return; + } + // 目录不存在或不是目录 + File currentPathFile = new File(currentPath); + if (!currentPathFile.isDirectory()) { + out.println("Directory does not exist."); + return; + } + // 遍历目录取的文件信息 + List> fileList = new ArrayList>(); + if (currentPathFile.listFiles() != null) { + for (File file : currentPathFile.listFiles()) { + Hashtable hash = new Hashtable(); + String fileName = file.getName(); + if (file.isDirectory()) { + hash.put("is_dir", true); + hash.put("has_file", (file.listFiles() != null)); + hash.put("filesize", 0L); + hash.put("is_photo", false); + hash.put("filetype", ""); + } else if (file.isFile()) { + String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + hash.put("is_dir", false); + hash.put("has_file", false); + hash.put("filesize", file.length()); + hash.put("is_photo", Arrays. asList(fileTypes).contains(fileExt)); + hash.put("filetype", fileExt); + } + hash.put("filename", fileName); + hash.put("datetime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(file.lastModified())); + fileList.add(hash); + } + } + + if ("size".equals(order)) { + Collections.sort(fileList, new SizeComparator()); + } else if ("type".equals(order)) { + Collections.sort(fileList, new TypeComparator()); + } else { + Collections.sort(fileList, new NameComparator()); + } + Map msg = new HashMap(); + msg.put("moveup_dir_path", moveupDirPath); + msg.put("current_dir_path", currentDirPath); + msg.put("current_url", currentUrl); + msg.put("total_count", fileList.size()); + msg.put("file_list", fileList); + response.setContentType("application/json; charset=UTF-8"); + String msgStr = objectMapper.writeValueAsString(msg); + out.println(msgStr); + } + + @SuppressWarnings("rawtypes") + class NameComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + return ((String) hashA.get("filename")) + .compareTo((String) hashB.get("filename")); + } + } + } + + @SuppressWarnings("rawtypes") + class SizeComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + if (((Long) hashA.get("filesize")) > ((Long) hashB + .get("filesize"))) { + return 1; + } else if (((Long) hashA.get("filesize")) < ((Long) hashB + .get("filesize"))) { + return -1; + } else { + return 0; + } + } + } + } + + @SuppressWarnings("rawtypes") + class TypeComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + return ((String) hashA.get("filetype")) + .compareTo((String) hashB.get("filetype")); + } + } + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/keditor/KindEditorUploadToCDNController.java b/src/main/java/com/nbclass/activity/controller/keditor/KindEditorUploadToCDNController.java new file mode 100644 index 0000000..90f3504 --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/keditor/KindEditorUploadToCDNController.java @@ -0,0 +1,337 @@ +package com.nbclass.activity.controller.keditor; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import net.coobird.thumbnailator.Thumbnails; + +/** + * kindeditor上传文件 - 保存到cdn + * @author Leon + * @datetime 2019年4月15日 下午4:50:49 + */ +@RequestMapping("/console/kindeditor/cdn") +@RestController +public class KindEditorUploadToCDNController { + + protected static Logger logger = LoggerFactory.getLogger(KindEditorUploadToCDNController.class); + + // CDN保存目录 + private static final String upload_dir = "/mnt/cdn/xgl-cases/keditor-attached/"; + + private static final String cdn_domain = "https://cdn.szxgl.cn/"; + + private static final long upload_max_size = 5242880; // 上传文件最大值,5M + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + @PostMapping("/api/file-upload") + public Map fileUpload(HttpServletRequest request, + HttpServletResponse response,@RequestParam("imgFile")MultipartFile file) + throws ServletException, IOException, FileUploadException { + response.setContentType("text/html; charset=UTF-8"); + // ServletContext application = request.getSession().getServletContext(); + String savePath = upload_dir; + + // 文件保存目录URL + // String saveUrl = request.getContextPath() + "/attached/"; + + // 定义允许上传的文件扩展名 + HashMap extMap = new HashMap(); + extMap.put("image", "gif,jpg,jpeg,png,bmp"); + extMap.put("flash", "swf,flv"); + extMap.put("media", "swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb,mp4,mov"); // 上传音视频格式 + extMap.put("file", "doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2"); + + if (!ServletFileUpload.isMultipartContent(request)) { + return getError("请选择文件。"); + } + // 检查目录 + File uploadDir = new File(savePath); + if (!uploadDir.isDirectory()) { + uploadDir.mkdirs(); + } + // 检查目录写权限 + if (!uploadDir.canWrite()) { + return getError("上传目录没有写权限。"); + } + + String dirName = request.getParameter("dir"); + if (dirName == null) { + dirName = "image"; + } + if (!extMap.containsKey(dirName)) { + return getError("目录名不正确。"); + } + // 创建文件夹 + savePath += dirName + "/"; + File saveDirFile = new File(savePath); + if (!saveDirFile.exists()) { + saveDirFile.mkdirs(); + } + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); + String ymd = sdf.format(new Date()); + savePath += ymd + "/"; + File dirFile = new File(savePath); + if (!dirFile.exists()) { + dirFile.mkdirs(); + } + + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + upload.setHeaderEncoding("UTF-8"); + //List items = upload.parseRequest(request); + //Iterator itr = items.iterator(); + + // FileItem item = (FileItem) itr.next(); + String fileName = file.getOriginalFilename(); + + // 检查文件大小 + if (file.getSize() > upload_max_size) { + return getError("上传文件大小超过限制,请上传5M内的文件。"); + } + // 检查扩展名 + String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + if (!Arrays. asList(extMap.get(dirName).split(",")).contains(fileExt)) { + return getError("上传文件扩展名是不允许的扩展名。\n只允许" + extMap.get(dirName) + "格式。"); + } + + SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); + String newFileName = df.format(new Date()) + "_" + new Random().nextInt(1000) + "." + fileExt; + try { + logger.info("savePath="+savePath+", newFileName="+newFileName); + // File uploadedFile = new File(savePath, newFileName); + File saveFile = new File(savePath+newFileName); + file.transferTo(saveFile); + createThumbnail(savePath, newFileName); + } catch (Exception e) { + e.printStackTrace(); + return getError("上传文件失败。"); + } + String url = savePath + newFileName; + String host = cdn_domain; + url = url.replace("/mnt/cdn/", host); + + Map msg = new HashMap(); + msg.put("error", 0); + msg.put("url", url); + return msg; + } + + private Map getError(String message) { + Map msg = new HashMap(); + msg.put("error", 1); + msg.put("message", message); + return msg; + } + + /** + * @param savePath + * @param newFileName + */ + private void createThumbnail(String savePath, String newFileName){ + try { + Thumbnails.of(savePath+newFileName) + .size(200,200) + .toFile(savePath+"thump"+newFileName); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("unchecked") + @RequestMapping(value = "/api/file-manager") + public void fileManager(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // ServletContext application = request.getSession().getServletContext(); + ServletOutputStream out = response.getOutputStream(); + // 根目录路径,可以指定绝对路径,比如 /var/www/attached/ + String rootPath = upload_dir; + // 根目录URL,可以指定绝对路径,比如 http://www.yoursite.com/attached/ + //String rootUrl = request.getContextPath() + "/attached/"; + + // 图片扩展名 + String[] fileTypes = new String[] { "gif", "jpg", "jpeg", "png", "bmp" }; + + String dirName = request.getParameter("dir"); + if (dirName != null) { + if (!Arrays. asList(new String[] { "image", "flash", "media", "file" }).contains(dirName)) { + out.println("Invalid Directory name."); + return; + } + rootPath += dirName + "/"; + File saveDirFile = new File(rootPath); + if (!saveDirFile.exists()) { + saveDirFile.mkdirs(); + } + } + // 根据path参数,设置各路径和URL + String path = StringUtils.trimToEmpty(request.getParameter("path")); + String currentPath = rootPath + path; + String currentDirPath = path; + String moveupDirPath = ""; + if (StringUtils.isNotBlank(path)) { + String str = currentDirPath.substring(0, currentDirPath.length() - 1); + moveupDirPath = str.lastIndexOf("/") >= 0 ? str.substring(0, str.lastIndexOf("/") + 1) : ""; + } + + // 排序形式,name or size or type + String order = request.getParameter("order") != null ? request.getParameter("order").toLowerCase() : "name"; + + // 不允许使用..移动到上一级目录 + if (path.indexOf("..") >= 0) { + out.println("Access is not allowed."); + return; + } + // 最后一个字符不是/ + if (!"".equals(path) && !path.endsWith("/")) { + out.println("Parameter is not valid."); + return; + } + // 目录不存在或不是目录 + File currentPathFile = new File(currentPath); + if (!currentPathFile.isDirectory()) { + out.println("Directory does not exist."); + return; + } + // 遍历目录取的文件信息 + List> fileList = new ArrayList>(); + if (currentPathFile.listFiles() != null) { + for (File file : currentPathFile.listFiles()) { + Hashtable hash = new Hashtable(); + String fileName = file.getName(); + if (file.isDirectory()) { + hash.put("is_dir", true); + hash.put("has_file", (file.listFiles() != null)); + hash.put("filesize", 0L); + hash.put("is_photo", false); + hash.put("filetype", ""); + } else if (file.isFile()) { + String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + hash.put("is_dir", false); + hash.put("has_file", false); + hash.put("filesize", file.length()); + hash.put("is_photo", Arrays. asList(fileTypes).contains(fileExt)); + hash.put("filetype", fileExt); + } + hash.put("filename", fileName); + hash.put("datetime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(file.lastModified())); + fileList.add(hash); + } + } + + if ("size".equals(order)) { + Collections.sort(fileList, new SizeComparator()); + } else if ("type".equals(order)) { + Collections.sort(fileList, new TypeComparator()); + } else { + Collections.sort(fileList, new NameComparator()); + } + + String currentUrl = currentPath; + String host = cdn_domain; + currentUrl = currentUrl.replace("/mnt/cdn/", host); + + Map msg = new HashMap(); + msg.put("moveup_dir_path", moveupDirPath); + msg.put("current_dir_path", currentDirPath); + msg.put("current_url", currentUrl); + msg.put("total_count", fileList.size()); + msg.put("file_list", fileList); + response.setContentType("application/json; charset=UTF-8"); + String msgStr = objectMapper.writeValueAsString(msg); + out.println(msgStr); + } + + @SuppressWarnings("rawtypes") + class NameComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + return ((String) hashA.get("filename")) + .compareTo((String) hashB.get("filename")); + } + } + } + + @SuppressWarnings("rawtypes") + class SizeComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + if (((Long) hashA.get("filesize")) > ((Long) hashB + .get("filesize"))) { + return 1; + } else if (((Long) hashA.get("filesize")) < ((Long) hashB + .get("filesize"))) { + return -1; + } else { + return 0; + } + } + } + } + + @SuppressWarnings("rawtypes") + class TypeComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + return ((String) hashA.get("filetype")) + .compareTo((String) hashB.get("filetype")); + } + } + } + +} diff --git a/src/main/java/com/nbclass/activity/controller/keditor/KindEditorUploadToOSSController.java b/src/main/java/com/nbclass/activity/controller/keditor/KindEditorUploadToOSSController.java new file mode 100644 index 0000000..0900c34 --- /dev/null +++ b/src/main/java/com/nbclass/activity/controller/keditor/KindEditorUploadToOSSController.java @@ -0,0 +1,216 @@ +package com.nbclass.activity.controller.keditor; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.Random; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.nbclass.aliyun.sdk.AliyunOSSUtils; +import com.nbclass.util.CommonUtils; + +import lombok.extern.slf4j.Slf4j; +import net.coobird.thumbnailator.Thumbnails; + +/** + * kindeditor上传文件 - 保存到阿里云OSS + * @author Leon + * @datetime 2020年6月8日 下午2:26:44 + */ +@Slf4j +@RequestMapping("/console/kindeditor/oss") +@RestController +public class KindEditorUploadToOSSController { + + // OSS保存目录 + private static final String upload_dir = "kindeditor-attached/"; + + private static final long upload_max_size = 5242880; // 上传文件最大值,5M + + @PostMapping("/api/file-upload") + public Map fileUpload(HttpServletRequest request, + HttpServletResponse response,@RequestParam("imgFile")MultipartFile file) + throws ServletException, IOException, FileUploadException { + response.setContentType("text/html; charset=UTF-8"); + String savePath = upload_dir; + + // 文件保存目录URL + // String saveUrl = request.getContextPath() + "/keditor-attached/"; + + // 定义允许上传的文件扩展名 + HashMap extMap = new HashMap(); + extMap.put("image", "gif,jpg,jpeg,png,bmp"); + extMap.put("flash", "swf,flv"); + extMap.put("media", "swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb,mp4,mov"); // 上传音视频格式 + extMap.put("file", "doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2"); + + if (!ServletFileUpload.isMultipartContent(request)) { + return getError("请选择文件。"); + } + // 检查目录 + File uploadDir = new File(savePath); + if (!uploadDir.isDirectory()){ + // return getError("上传目录不存在。"); + uploadDir.mkdirs(); + } + // 检查目录写权限 + if (!uploadDir.canWrite()){ + return getError("上传目录没有写权限。"); + } + + String dirName = request.getParameter("dir"); + if (StringUtils.isBlank(dirName)) { + dirName = "image"; + } + if (!extMap.containsKey(dirName)) { + return getError("目录名不正确。"); + } + // 文件夹加上类型目录 + savePath += dirName + "/"; + // 文件夹加上日期目录 + savePath += CommonUtils.getSimpleDate(new Date(), "yyyyMM"); // yyyyMMdd + + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + upload.setHeaderEncoding("UTF-8"); + //List items = upload.parseRequest(request); + //Iterator itr = items.iterator(); + + // FileItem item = (FileItem) itr.next(); + String fileName = file.getOriginalFilename(); + + // 检查文件大小 + if (file.getSize() > upload_max_size) { + return getError("上传文件大小超过限制,请上传5M内的文件。"); + } + // 检查扩展名 + String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); + if (!Arrays. asList(extMap.get(dirName).split(",")).contains(fileExt)) { + return getError("上传文件扩展名是不允许的扩展名。\n只允许" + extMap.get(dirName) + "格式。"); + } + + String url = null; + String newFileName = CommonUtils.getSimpleDate(new Date(), "yyyyMMddHHmmss") + "_" + new Random().nextInt(1000) + "." + fileExt; + try { + // File saveFile = new File(savePath+newFileName); + // file.transferTo(saveFile); + // createThumbnail(savePath, newFileName); + + url = AliyunOSSUtils.uploadBytes(savePath, newFileName, file.getBytes()); + log.info("kindeditor上传文件到oss, url="+url); + // 阿里云OSS缩略图,在URL后面加上:?x-oss-process=image/resize,m_fill,h_200,w_200 + } catch (Exception e) { + e.printStackTrace(); + return getError("上传文件失败。"); + } + + Map msg = new HashMap(); + msg.put("error", 0); + msg.put("url", url); + return msg; + } + + private Map getError(String message) { + Map msg = new HashMap(); + msg.put("error", 1); + msg.put("message", message); + return msg; + } + + /** + * @param savePath + * @param newFileName + */ + + @SuppressWarnings("unused") + private void createThumbnail(String savePath, String newFileName) { + try { + Thumbnails.of(savePath+newFileName) + .size(200,200) + .toFile(savePath+"thump"+newFileName); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("rawtypes") + class NameComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + return ((String) hashA.get("filename")) + .compareTo((String) hashB.get("filename")); + } + } + } + + @SuppressWarnings("rawtypes") + class SizeComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + if (((Long) hashA.get("filesize")) > ((Long) hashB + .get("filesize"))) { + return 1; + } else if (((Long) hashA.get("filesize")) < ((Long) hashB + .get("filesize"))) { + return -1; + } else { + return 0; + } + } + } + } + + @SuppressWarnings("rawtypes") + class TypeComparator implements Comparator { + public int compare(Object a, Object b) { + Hashtable hashA = (Hashtable) a; + Hashtable hashB = (Hashtable) b; + if (((Boolean) hashA.get("is_dir")) + && !((Boolean) hashB.get("is_dir"))) { + return -1; + } else if (!((Boolean) hashA.get("is_dir")) + && ((Boolean) hashB.get("is_dir"))) { + return 1; + } else { + return ((String) hashA.get("filetype")) + .compareTo((String) hashB.get("filetype")); + } + } + } + +} diff --git a/src/main/java/com/nbclass/activity/mapper/CaseTypeMapper.java b/src/main/java/com/nbclass/activity/mapper/CaseTypeMapper.java new file mode 100644 index 0000000..efd868f --- /dev/null +++ b/src/main/java/com/nbclass/activity/mapper/CaseTypeMapper.java @@ -0,0 +1,49 @@ +package com.nbclass.activity.mapper; + +import com.nbclass.activity.model.Content; +import com.nbclass.activity.model.ContentTags; +import com.nbclass.activity.model.DataDict; +import com.nbclass.activity.model.DataDictItem; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface CaseTypeMapper { + + + /** + * 查询(案例类型、案例用途、创意形式、节日事件) + * @return + */ + List getTypeCase(Integer dictId); + + /** + * 字典值类型列表查询 + * @return + */ + List getDictionariesType(); + + /** + * 案例搜索框 + * @param dictItemId 案例类型id + * @return + */ + List getSearchCase(@Param("parameter") String parameter, + @Param("dictItemId") Integer dictItemId); + + /** + * 案例库列表 + * @return + */ + List getListCase(); + + /** + * 获取案例标签 + * @param contentId 案例id + * @return + */ + ContentTags getContentTags(Integer contentId); + +} diff --git a/src/main/java/com/nbclass/activity/mapper/CommentMapper.java b/src/main/java/com/nbclass/activity/mapper/CommentMapper.java new file mode 100644 index 0000000..e3f833a --- /dev/null +++ b/src/main/java/com/nbclass/activity/mapper/CommentMapper.java @@ -0,0 +1,212 @@ +package com.nbclass.activity.mapper; + +import com.nbclass.activity.model.*; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface CommentMapper { + + /** + * 案例详情和评论列表 + * @param contentId + * @return + */ + Content getComment(@Param("contentId") Long contentId, + @Param("userId") Long userId); + + + /** + * 评论回复列表 + * @param commentId + * @param userId + * @return + */ + Comment getCommentReplyList(@Param("commentId") Long commentId, + @Param("userId") Long userId); + + /** + * 添加用户发布信息 + * @param comment + * @return + */ + void saveComment(Comment comment); + + /** + * 根据评论id获取被点赞人Id + * @param commentId + * @return + */ + Comment getCommentUserId(Long commentId); + + /** + * 添加评论点赞 + * @param praise + * @return + */ + void savePraiseNo(Praise praise); + + /** + * 添加案例点赞 + * @param contentId + * @return + */ + void updateContentPraiseNo(Long contentId); + + /** + * 评论总数加1 + * @param commentId + */ + void updatePraiseno(Long commentId); + + /** + * 用户回复 + * @param comment + */ + void saveUserReply(Comment comment); + + /** + * 用户举报 + * @param report + * @return + */ + void saveReport(Report report); + + /** + * 举报列表获取 + * @return + */ + List getReport(String userId); + + /** + * 添加标签、索引反馈日志 + * @return + */ + void saveLabelLogger(LabelLogger labelLogger); + + /** + * 新建文件夹 + * @param favoritesFolder + * @return + */ + void saveFavorites(FavoritesFolder favoritesFolder); + + /** + * 案例详情的文件夹列表 + * @return + */ + List getUserFolder(@Param("userId") Long userId, + @Param("commentId") Long commentId); + + /** + * 收藏案例到文件夹中 + * @param contentId + * @return + */ + void saveFolder(@Param("folderId") Long folderId, + @Param("contentId") Long contentId); + + /** + * 添加收藏总数 + * @param folderId + */ + void saveFolderTotal(Long folderId); + + /** + * 判断该文件夹是否已被收藏 + * @param folderId + * @param contentId + * @return + */ + int getFolderExistence(@Param("folderId") Long folderId, + @Param("contentId") Long contentId); + + /** + * 判断文件夹是否重名 + * @param name + * @return + */ + int getFavoritesName(String name); + + /** + * 取消文件夹中的案例收藏 + * @param folderId + * @param contentId + * @return + */ + void deleteFolderResources(@Param("folderId") Long folderId, + @Param("contentId") Long contentId); + + /** + * 我的收藏 + * @return + */ + List getMyCollection(@Param("userId") Long userId, + @Param("folderId") Long folderId); + + /** + * 获取用户文件夹列表 + * @return + */ + List getUserFolderList(Long userId); + + /** + * 取消评论点赞 + * @return + */ + void deletePraise(@Param("commentId") Long commentId, + @Param("userId") Long userId); + + /** + * 总点赞数减一 + * @param commentId + */ + void updatePraiseNoONE(Long commentId); + + /** + * 评论总回复数加一 + */ + void updateReplyNumberONE(Long commentId); + + /** + * 回复列表 + * @param userId + * @return + */ + List getReplyList(Long userId); + + /** + * 点赞列表 + * @param userId + * @return + */ + List getFabulousList(Long userId); + + /** + * 更新案例的收藏总数+1 + * @param contentId + */ + void updateCollectionNumber(Long contentId); + + /** + * 更新案例的收藏总数-1 + * @param contentId + */ + void updateCollectionNumberReduce(Long contentId); + + /** + * 标签、索引反馈日志列表 + * @param userId + * @return + */ + List getLabelLogger(Long userId); + + /** + * 案例打分 + * @param scoring + */ + void saveScoring(Scoring scoring); + +} diff --git a/src/main/java/com/nbclass/activity/mapper/ContentMapper.java b/src/main/java/com/nbclass/activity/mapper/ContentMapper.java new file mode 100644 index 0000000..67e110e --- /dev/null +++ b/src/main/java/com/nbclass/activity/mapper/ContentMapper.java @@ -0,0 +1,221 @@ +package com.nbclass.activity.mapper; + +import java.util.List; + +import com.nbclass.activity.model.*; +import org.apache.ibatis.annotations.Param; + +import com.nbclass.util.MyMapper; + +public interface ContentMapper extends MyMapper { + + /** + * 根据id查找记录 + * @param id + * @return + */ + public Content findById(long id); + + /** + * 根据条件查询列表 + * @param entity + * @return + */ + public List getList(Content entity); + + /** + * 根据条件查询列表 - 前端页面查询 + * @param entity + * @return + */ + public List getListByFront(Content entity); + + /** + * 根据id批量删除记录 + * @param list + */ + public void deleteByIds(List list); + + + /** + * 删除关联的标签 + * @param cid + */ + public void deleteTags(long cid); + + /** + * 批量添加标签 + * @param list + */ + public void insertContentTags(List list); + + /** + * 通过内容ID删除图片集 + * @param cid + */ + public void deleteImages(long cid); + + /** + * 批量添加图片 + * @param list + */ + public void insertContentImages(List list); + + /** + * 添加点赞数阅读数 + * @param type [view, like] + * @param id + */ + public void updateContentNum(@Param("type")String type, @Param("id")Long id); + + /** + * 查询案例标题和评论数和点赞数 + * @return + */ + List getCaseTitle(); + + /** + * 标签关联案例 + * @param contentTags + */ + void addContentTags(ContentTags contentTags); + + /** + * 获取自定义标签列表 + * @return + */ + List getOtherLabels(); + + /** + * 取消案例关联标签的关系 + * @param idList + */ + void deleteContentTags(List idList); + + /** + * 案例索引标签反馈次数+1 + * @param contentId + */ + void updateLabelFeedback(Long contentId); + + /** + * 根据条件查询列表 + * @param entity + * @return + */ + List getList2(Content entity); + + /** + * 更新案例总评论数 + * @param contentId + */ + void updateComments(Long contentId); + + /** + * 添加案例评分 + * @param userId + * @param contentId + * @param score + */ + void saveScore(@Param("userId") Long userId, + @Param("contentId") Long contentId, + @Param("score") Double score); + + /** + * 查看用户是否已经打过分 + * @param userId + * @param contentId + * @return + */ + int getScore(@Param("userId") Long userId, + @Param("contentId") Long contentId); + + /** + * 根据案例id查询案例详情 + * @param contentId + * @return + */ + Content getEditContent(Long contentId); + + /** + * 提交审核申请单 + * @param application + */ + void saveApplication(Application application); + + /** + * 添加审核人信息 + * @param check + */ + void saveCheck(Check check); + + /** + *查询案例是否已提交过审核 + * @param contentId + * @return + */ + int getApplicationCount(Long contentId); + + /** + * 删除案例审核申请 + * @param applicationId + */ + void deleteApplication(Long applicationId); + + /** + * 删除审核人信息 + * @param applicationId + */ + void deleteCheck(Long applicationId); + + /** + * 更新审核申请单状态 + * @param applicationId + */ + void updateApplicationStatus(Long applicationId); + + /** + * 更新审核人状态 + * @param applicationId + */ + void updateCheckStatus(Long applicationId); + + /** + * 获取审核列表 + * @param entity + * @return + */ + List getContentApplication(Content entity); + + /** + * 更新审核表状态 + * @param check + */ + void updateCheck(Check check); + + /** + * 更新申请单状态 + * @param application + */ + void updateApplication(Application application); + + /** + * 查询案例id + * @return + */ + Long getContentId(Long applicationId); + + /** + * 更新案例状态 + * @param contentId + */ + void updateContentRelease(Long contentId,Integer release); + + /** + * 根据案例id查询申请单信息 + * @param contentId + * @return + */ + Application getApplication(Long contentId); + +} diff --git a/src/main/java/com/nbclass/activity/mapper/DataDictItemMapper.java b/src/main/java/com/nbclass/activity/mapper/DataDictItemMapper.java new file mode 100644 index 0000000..612fda0 --- /dev/null +++ b/src/main/java/com/nbclass/activity/mapper/DataDictItemMapper.java @@ -0,0 +1,59 @@ +package com.nbclass.activity.mapper; + +import java.util.List; + +import com.nbclass.activity.model.ContentTags; +import org.apache.ibatis.annotations.Param; + +import com.nbclass.activity.model.DataDictItem; +import com.nbclass.util.MyMapper; + +/** + * + * @author Leon + * @datetime 2020年6月1日 下午9:15:33 + */ +public interface DataDictItemMapper extends MyMapper { + + /** + * 根据dictid查询字典item列表 + * @param entity + * @return + */ + public List getList(DataDictItem entity); + + /** + * 判断记录是否存在 + * @param entity + * @return + */ + public Long checkExist(DataDictItem entity); + + + /** + * 根据id检查字典是否在案例标签中有使用 + * @param id + * @return + */ + public Long checkContentTag(@Param("id")Long id); + + /** + * 根据id检查字典是否在案例来源中有使用 + * @param id + * @return + */ + public Long checkContentFrom(@Param("id")Long id); + + /** + * 获取字典值 + * @return + */ + List getDropDownBox(); + + /** + * 根据名字查询tasId + * @param name + * @return + */ + String[] getTagId(String name); +} diff --git a/src/main/java/com/nbclass/activity/mapper/DataDictMapper.java b/src/main/java/com/nbclass/activity/mapper/DataDictMapper.java new file mode 100644 index 0000000..4e9c905 --- /dev/null +++ b/src/main/java/com/nbclass/activity/mapper/DataDictMapper.java @@ -0,0 +1,75 @@ +package com.nbclass.activity.mapper; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +import com.nbclass.activity.model.DataDict; +import com.nbclass.activity.model.DataDictItem; +import com.nbclass.util.MyMapper; + +public interface DataDictMapper extends MyMapper { + + /** + * 根据条件查询列表 + * @param entity + * @return + */ + public List getList(DataDict entity); + + /** + * 删除前验证 + * @param list + * @return + */ + public Long delCheck(List list); + + /** + * 根据id批量删除记录 + * @param list + */ + public void deleteByIds(List list); + + /** + * 通过字典Key获取子集 + * @param datakey + * @return + */ + public List getItemsByKey(@Param(value="datakey")String datakey); + + /** + * 根据字典名获取列表 + * @param datakey + * @param tagNames + * @return + */ + public List getItemsByNames(@Param(value="datakey")String datakey, @Param("tagNames")List tagNames); + + /** + * 保存字典item + * @param entity + */ + public void insertDataDictItem(DataDictItem entity); + + /** + * 根据标签名称查询标签信息 + * @param postTagList + * @return + */ + List getDataDictTemName(@Param("tagNames")ArrayList postTagList); + + /** + * 获取自定义标签id + * @return + */ + Long getDataDictID(); + + /** + * 查询字典值是否重复 + * @param name + * @return + */ + int getDataDicName(String name); + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/activity/mapper/WXMapper.java b/src/main/java/com/nbclass/activity/mapper/WXMapper.java new file mode 100644 index 0000000..05805a5 --- /dev/null +++ b/src/main/java/com/nbclass/activity/mapper/WXMapper.java @@ -0,0 +1,67 @@ +package com.nbclass.activity.mapper; + +import com.nbclass.activity.model.Check; +import com.nbclass.activity.model.WeiXiUser; +import com.nbclass.activity.model.WxUser; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface WXMapper { + + /** + * //数据库查询是否有该openid,如果有直接从数据库获取用户信息 + * @param openid + * @return + */ + int getOpenId(String openid); + + + /** + * //添加用户信息到数据库 + * @param weiXiUser + */ + void saveWxUser(WeiXiUser weiXiUser); + + /** + * //数据库获取用户信息 + * @param openid + * @return + */ + WeiXiUser getWxUser(String openid); + + /** + * 根据用户id获取信息 + * @param userId + * @return + */ + WeiXiUser getUser(String userId); + + /** + * 更新用户基础信息 + * @param weiXiUser + * @return + */ + void updateBasicData(WeiXiUser weiXiUser); + + /** + * 查看用户基础信息 + * @return + */ + WeiXiUser getUserBasicData(Long userId); + + /** + * 根据userId获取微信用户信息 + * @param username + * @return + */ + WxUser getQyWxUser(String username); + + /** + * 根据部门id获取用户领导 + * @return + */ + List getUserLeader(String department); + +} diff --git a/src/main/java/com/nbclass/activity/mapper/WxDepartmentMapper.java b/src/main/java/com/nbclass/activity/mapper/WxDepartmentMapper.java new file mode 100644 index 0000000..10181a3 --- /dev/null +++ b/src/main/java/com/nbclass/activity/mapper/WxDepartmentMapper.java @@ -0,0 +1,27 @@ +package com.nbclass.activity.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +import com.nbclass.activity.model.WxDepartment; +import com.nbclass.util.MyMapper; + +/** + * + * @author Leon + * @datetime 2020年6月2日 下午4:42:57 + */ +public interface WxDepartmentMapper extends MyMapper { + + + public void insertBatch(List list); + + /** + * 通过父极id获取子部门id列表,返回结果包含当前父级id + * @param parentid + * @return 返回id以逗号分隔 + */ + public String getChildDeptIds(@Param("parentid")int parentid); + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/activity/mapper/WxUserMapper.java b/src/main/java/com/nbclass/activity/mapper/WxUserMapper.java new file mode 100644 index 0000000..749de11 --- /dev/null +++ b/src/main/java/com/nbclass/activity/mapper/WxUserMapper.java @@ -0,0 +1,53 @@ +package com.nbclass.activity.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +import com.nbclass.activity.model.WxUser; +import com.nbclass.util.MyMapper; + +/** + * + * @author Leon + * @datetime 2020年6月2日 下午4:42:57 + */ +public interface WxUserMapper extends MyMapper { + + // 这个没用,使用insertSelective方法。 + public void insertCustom(WxUser entity); + + /** + * 根据userid修改记录 + * @param entity + */ + public void updateByUserId(WxUser entity); + + /** + * 获取成员UserID列表 + * @return + */ + public List getUserIds(); + + /** + * 通过部门ID列表获取用户信息列表 + * @param deptids + * @return + */ + public List getUsersByDeptIds(@Param("deptids")String[] deptids); + + /** + * 通过用户ID列表获取用户信息列表 + * @param userids + * @return + */ + public List getUsersByUserIds(@Param("userids")String[] userids); + + + /** + * 删除此userids之外的用户 + * @param userIds + */ + public void delOtherUsers(@Param("userids")List userids); + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/activity/model/Application.java b/src/main/java/com/nbclass/activity/model/Application.java new file mode 100644 index 0000000..b092d65 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/Application.java @@ -0,0 +1,54 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@Data +public class Application implements Serializable { + + private static final long serialVersionUID = 22459352105621985L; + + /** + * id(主键自增) + */ + private Long id; + + /** + * 案例id + */ + private Long contentId; + + /** + * 申请人id + */ + private Long userId; + + /** + * 审核类型(1、案例审核) + */ + private Integer type; + + /** + * 审核状态(1、待审核 2、通过 3、不通过) + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 审核人 + */ + private List checks; + +} diff --git a/src/main/java/com/nbclass/activity/model/Check.java b/src/main/java/com/nbclass/activity/model/Check.java new file mode 100644 index 0000000..343fd09 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/Check.java @@ -0,0 +1,46 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@Data +public class Check implements Serializable { + + private static final long serialVersionUID = -6085309152205259190L; + + /** + * id(主键自增) + */ + private Long id; + + /** + * 申请单id + */ + private Long applicationId; + + /** + * 审核人id + */ + private Long checkId; + + /** + * 审核状态(1、待审核 2、通过 3、失败) + */ + private Integer status; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + + +} diff --git a/src/main/java/com/nbclass/activity/model/Comment.java b/src/main/java/com/nbclass/activity/model/Comment.java new file mode 100644 index 0000000..56e5f59 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/Comment.java @@ -0,0 +1,81 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@Data +public class Comment implements Serializable { + + private static final long serialVersionUID = 1702315030539408893L; + + /** + * 评论id(主键自增) + */ + private Long id; + + /** + * 案例id + */ + private Long contentId; + + /** + * 评论人id + */ + private Long userId; + + /** + * 回复人id(回复有值,没有回复,默认值是0) + */ + private Long replyId; + + /** + * 评论id(有值代表回复,0代表主评论) + */ + private Long commentId; + + /** + * 评论内容 + */ + private String commentContent; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 点赞总数 + */ + private Integer praiseNo; + + /** + * 总回复数 + */ + private Integer replyNumber; + + /** + * 评论用户名称 + */ + private String nickname; + + /** + * 评论用户头像 + */ + private String headimgurl; + + /** + * 点赞人id(有值代表已点赞,没有值代表没有点赞) + */ + private Long praiseId; + + //回复列表 + private List respondents; + + //最新2人的点赞人列表 + private List userList; + + +} diff --git a/src/main/java/com/nbclass/activity/model/Content.java b/src/main/java/com/nbclass/activity/model/Content.java new file mode 100644 index 0000000..4d34df8 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/Content.java @@ -0,0 +1,199 @@ +package com.nbclass.activity.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.persistence.*; + +import lombok.Data; + +@Data +@Table(name = "content") +public class Content implements Serializable { + + private static final long serialVersionUID = -6422276602170816740L; + + /** + * ID, 主键,自增 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + private Date createtime; + + /** + * 修改时间 + */ + private Date updatetime; + + /** + * 案例类型[ppqa:品牌全案, ggqa:公关全案, design:创意设计, video:视频动画, h5:技术开发, ldhd:落地活动, others:其他案例] + */ + private String type; + + + /** + * 是否有效(1:有效,0:无效) + */ + private Byte isvalid; + + /** + * 案例来源ID + */ + private Integer fromid; + // 案例来源名称 + @Transient + private String fromName; + @Transient + private String fromids; // 案例来源id,多个逗号分隔 + + /** + * 标题 + */ + private String title; + + /** + * 摘要,描述 + */ + private String desct; + + /** + * 查看数 + */ + private Integer viewno; + + /** + * 点赞数 + */ + private Integer praiseno; + + /** + * 收藏数 + */ + private Integer collectionNumber; + + /** + * 列表icon图 + */ + private String listicon; + + /** + * 二维码 + */ + private String qrcode; + + /** + * 排序值, 值越大越考前 + */ + private Integer sort; + + /** + * 访问地址 + */ + private String url; + + /** + * 内容,富文本内容存储在这 + */ + private String content; + + /** + * 纯Text内容,用于存到ES便于搜索 + */ + private String content_text; + + /** + * 附件 + */ + private String attachment; + + /** + * 创建人id + */ + private Long userId; + + /** + * 索引标签反馈次数 + */ + private Integer labelFeedback; + + /** + * 字典数据名称 + */ + private String name; + + /** + * 可见性 + */ + private Integer visibility; + + /** + *评论数 + */ + private Integer comments; + + /** + * 发布状态(1、未发布 2、已发布 4、已下线) + */ + private Integer release; + + /** + * 审核状态(1、待审核 2、已上线 3、不通过 4、已下线 5、未提交) + */ + private Integer status; + + //用户人名称 + private String userName; + + //用户人职位 + private String position; + + //申请单id + @Transient + private Long applicationId; + + //审核表案例状态 + private Long checkStatus; + + //申请单 + private Application application; + + //总评分 + private Double TotalScore; + + //评分总人数 + private Integer userTotal; + + + + // 标签 + @Transient + private String tags; + @Transient + private List tagList = new ArrayList(); + + //案例分类 + private String[] tagLists; + + //反馈次数 + private Integer quantity; + + // 图片集 + @Transient + private String images; + @Transient + private List imgList = new ArrayList(); + + //评论列表 + private List commentators; + + //查询标签个数 控制前端案例列表的查看操作 + private Integer count; + +} diff --git a/src/main/java/com/nbclass/activity/model/ContentImages.java b/src/main/java/com/nbclass/activity/model/ContentImages.java new file mode 100644 index 0000000..d86c038 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/ContentImages.java @@ -0,0 +1,47 @@ +package com.nbclass.activity.model; + +import java.util.Date; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.Data; + +@Data +@Table(name = "content_images") +public class ContentImages { + /** + * ID, 主键,自增 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + private Date createtime; + + /** + * 内容表ID + */ + private Long cid; + + /** + * 图片地址 + */ + private String imgurl; + + /** + * 排序 + */ + private Integer sort; + + /** + * 图片原始名称 + */ + private String ori_name; + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/activity/model/ContentTags.java b/src/main/java/com/nbclass/activity/model/ContentTags.java new file mode 100644 index 0000000..9bc9bd0 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/ContentTags.java @@ -0,0 +1,53 @@ +package com.nbclass.activity.model; + +import java.util.Date; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; + +import lombok.Data; + +@Data +@Table(name = "content_tags") +public class ContentTags { + /** + * ID, 主键,自增 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + private Date createtime; + + /** + * 内容表ID + */ + private Long cid; + + /** + * 标签id(与data_dict_item.id对应) + */ + private Long tid; + + /** + * 字典id + */ + private Long dictId; + + /** + * + */ + + + + // 标签名(查询字段) + @Transient + private String tagName; + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/activity/model/DataDict.java b/src/main/java/com/nbclass/activity/model/DataDict.java new file mode 100644 index 0000000..6fa7d15 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/DataDict.java @@ -0,0 +1,52 @@ +package com.nbclass.activity.model; + +import java.util.Date; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.Data; + +@Data +@Table(name = "data_dict") +public class DataDict { + /** + * ID, 主键,自增 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + private Date createtime; + + /** + * 修改时间 + */ + private Date updatetime; + + /** + * 字典key值 + */ + private String datakey; + + /** + * 字典名称 + */ + private String name; + + /** + * 是否有效(1:有效,0:无效) + */ + private Byte isvalid; + + /** + * 描述 + */ + private String description; + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/activity/model/DataDictItem.java b/src/main/java/com/nbclass/activity/model/DataDictItem.java new file mode 100644 index 0000000..6298376 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/DataDictItem.java @@ -0,0 +1,53 @@ +package com.nbclass.activity.model; + +import java.util.Date; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.Data; + +@Data +@Table(name = "data_dict_item") +public class DataDictItem { + /** + * ID, 主键,自增 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 创建时间 + */ + private Date createtime; + + /** + * 修改时间 + */ + private Date updatetime; + + /** + * 字典id值(data_dict表id) + */ + private Long dictid; + + /** + * 字典value值(组合主键) + */ + private String value; + + /** + * 字典名字 + */ + private String name; + + /** + * 排序 + */ + private Integer sort; + + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/activity/model/FavoritesFolder.java b/src/main/java/com/nbclass/activity/model/FavoritesFolder.java new file mode 100644 index 0000000..cffe62c --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/FavoritesFolder.java @@ -0,0 +1,37 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +@Data +public class FavoritesFolder implements Serializable { + + private static final long serialVersionUID = -220203874286975484L; + + private Long id; + + /** + * 文件夹名称 + */ + private String name; + + /** + *创建人id + */ + private Long userId; + + private Date createTime; + + /** + * 收藏总数 + */ + private Integer totalCollections; + + /** + * 案例Id + */ + private Long contentId; + +} diff --git a/src/main/java/com/nbclass/activity/model/GroupChat.java b/src/main/java/com/nbclass/activity/model/GroupChat.java new file mode 100644 index 0000000..464eb35 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/GroupChat.java @@ -0,0 +1,29 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +@Data +public class GroupChat implements Serializable { + + private static final long serialVersionUID = -4159209038197422844L; + + private String chat_id; + + private String name; + + private String owner; + + private Date create_time; + + private String notice; + + + + private List member_list; + + +} diff --git a/src/main/java/com/nbclass/activity/model/GroupChatList.java b/src/main/java/com/nbclass/activity/model/GroupChatList.java new file mode 100644 index 0000000..eaf0028 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/GroupChatList.java @@ -0,0 +1,16 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class GroupChatList implements Serializable { + + private static final long serialVersionUID = 279034174223992751L; + + private String chat_id; + + private Integer status; + +} diff --git a/src/main/java/com/nbclass/activity/model/LabelLogger.java b/src/main/java/com/nbclass/activity/model/LabelLogger.java new file mode 100644 index 0000000..cdaf3cf --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/LabelLogger.java @@ -0,0 +1,54 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +@Data +public class LabelLogger implements Serializable { + + private static final long serialVersionUID = 8533327511622545026L; + + /** + * id(主键自增) + */ + private Long id; + + /** + * 案例id + */ + private Long contentId; + + /** + * 反馈人 + */ + private Long userId; + + /** + * 日志 + */ + private String logger; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 状态(0、待解决 1、已解决) + */ + private int status; + + /** + * 反馈人名称 + */ + private String name; + + /** + * 案例名称 + */ + private String contentName; + +} + diff --git a/src/main/java/com/nbclass/activity/model/MemberList.java b/src/main/java/com/nbclass/activity/model/MemberList.java new file mode 100644 index 0000000..e7c243d --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/MemberList.java @@ -0,0 +1,26 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class MemberList implements Serializable { + + private static final long serialVersionUID = 2160707045527379917L; + + private String userid; + + private String type; + + private String unionid; + + private Long join_time; + + private Integer join_scene; + + private String group_nickname; + + private String name; + +} diff --git a/src/main/java/com/nbclass/activity/model/Praise.java b/src/main/java/com/nbclass/activity/model/Praise.java new file mode 100644 index 0000000..aba0a41 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/Praise.java @@ -0,0 +1,38 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class Praise implements Serializable { + + private static final long serialVersionUID = -1111401809162956593L; + + /** + * 点赞id(主键自增) + */ + private Long id; + + /** + * 评论id + */ + private Long commentId; + + /** + * 点赞人id + */ + private Long praiseId; + + /** + * 被点赞人id(评论人id) + */ + private Long userId; + + /** + * 点赞时间 + */ + private String createTime; + + +} diff --git a/src/main/java/com/nbclass/activity/model/QueryCriteria.java b/src/main/java/com/nbclass/activity/model/QueryCriteria.java new file mode 100644 index 0000000..1920d00 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/QueryCriteria.java @@ -0,0 +1,95 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.util.Date; + +@Data +public class QueryCriteria { + + /** + * 案例编号 + */ + private Long contentId; + + /** + * 案例标题 + */ + private String title; + + /** + * 案例状态 + */ + private Integer status; + + /** + * 反馈次数 + */ + private Integer labelFeedback; + + /** + * 标签 + */ + private String labelName; + + /** + * 品牌公司 + */ + private String brandCompany; + + /** + * 广告公司 + */ + private String advertisingAgency; + + /** + * 案例用途 + */ + private Integer purposeCase; + + /** + * 创意形式 + */ + private Integer formCreative; + + /** + * 案例类型 + */ + private Integer typeCase; + + /** + * 节日事件 + */ + private Integer eventsFestival; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 创建时间区间 + */ + private Date createTimeSection; + + /** + * 修改时间 + */ + private Date updateTime; + + /** + * 修改时间区间 + */ + private Date updateTimeSection; + + /** + * 上线时间 + */ + private Date onlineTime; + + /** + * 上线时间 + */ + private Date onlineTimeSection; + +} diff --git a/src/main/java/com/nbclass/activity/model/Report.java b/src/main/java/com/nbclass/activity/model/Report.java new file mode 100644 index 0000000..f14cfbc --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/Report.java @@ -0,0 +1,49 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +@Data +public class Report implements Serializable { + + private static final long serialVersionUID = 6136060231077523641L; + + /** + * id(主键自增) + */ + private Long id; + + /** + * 评论id + */ + private Long commentId; + + /** + * 举报人Id + */ + private Long reportId; + + + /** + * 举报内容 + */ + private String reportContent; + + /** + *举报类型(1、政治、色情等违法言论,2、人身攻击,3、故意引战倾向等不当言论,4、其他原因) + */ + private Integer reportType; + + /** + * 处理状态(0、未处理,1、已处理) + */ + private Integer status; + + /** + * 举报时间 + */ + private Date createTime; + +} diff --git a/src/main/java/com/nbclass/activity/model/Scoring.java b/src/main/java/com/nbclass/activity/model/Scoring.java new file mode 100644 index 0000000..caf494b --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/Scoring.java @@ -0,0 +1,36 @@ +package com.nbclass.activity.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +@Data +public class Scoring implements Serializable { + + private static final long serialVersionUID = -9026107018961218312L; + + private Long id; + + /** + * 案例id + */ + private Long contentId; + + /** + * 评分数 + */ + private Double score; + + /** + * 评分人 + */ + private Long userId; + + /** + * 创建时间 + */ + private Date createTime; + + +} diff --git a/src/main/java/com/nbclass/activity/model/WeiXiUser.java b/src/main/java/com/nbclass/activity/model/WeiXiUser.java new file mode 100644 index 0000000..d898ddc --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/WeiXiUser.java @@ -0,0 +1,95 @@ +package com.nbclass.activity.model; + +import com.alibaba.fastjson.JSON; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.util.Date; + +/** + * 微信用户成员 + */ +@Data +public class WeiXiUser implements Serializable { + + private static final long serialVersionUID = -4661130931634820819L; + + private Long id; + + /** + * 普通用户的标识,对当前开发者帐号唯一 + */ + private String openid; + + /** + * 普通用户昵称 + */ + private String nickname; + + /** + * 普通用户性别,1为男性,2为女性 + */ + private Integer sex; + + /** + * 普通用户个人资料填写的省份 + */ + private String province; + + /** + * 普通用户个人资料填写的城市 + */ + private String city; + + /** + * 国家,如中国为CN + */ + private String country; + + /** + * 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 + */ + private String headimgurl; + + /** + * 用户特权信息,json数组,如微信沃卡用户为(chinaunicom) + */ + private JSON privilege; + + /** + * 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。 + */ + private String unionid; + + /** + * 生日 + */ + private Date birthday; + + /** + * 职业id(dict_item的id) + */ + private Integer occupationId; + + /** + * 公司 + */ + private String company; + + /** + * 邮箱 + */ + private String mailbox; + + /** + * 简介 + */ + private String briefIntroduction; + + private Date createTime; + + private Date updateTime; + + +} diff --git a/src/main/java/com/nbclass/activity/model/WxDepartment.java b/src/main/java/com/nbclass/activity/model/WxDepartment.java new file mode 100644 index 0000000..390e2cc --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/WxDepartment.java @@ -0,0 +1,30 @@ +package com.nbclass.activity.model; + +import java.io.Serializable; + +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.Data; + +/** + * 企业微信部门表 + * @author leiyun + * @datetime 2017年12月21日 下午4:17:39 + */ +@Data +@Table(name = "wx_department") +public class WxDepartment implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + private Long id; + + private String name; + + private Long parentid; + + private Integer order; + +} diff --git a/src/main/java/com/nbclass/activity/model/WxUser.java b/src/main/java/com/nbclass/activity/model/WxUser.java new file mode 100644 index 0000000..a748173 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/WxUser.java @@ -0,0 +1,46 @@ +package com.nbclass.activity.model; + +import java.io.Serializable; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import lombok.Data; + +/** + * 微信成员 + * @author leiyun + * @datetime 2017年12月21日 上午10:58:59 + */ +@Data +@Table(name = "wx_user") +public class WxUser implements Serializable{ + + private static final long serialVersionUID = 1L; + + /** + * ID, 主键,自增 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String userid; + private String name; + private String mobile; + + private String department; + private String position; + private String gender; // 性别。0表示未定义,1表示男性,2表示女性 + + private String email; + private String weixinid; + private String isleader; + private String avatar; + private String english_name; + + private int status; + +} diff --git a/src/main/java/com/nbclass/activity/model/enums/ContentType.java b/src/main/java/com/nbclass/activity/model/enums/ContentType.java new file mode 100644 index 0000000..4b21731 --- /dev/null +++ b/src/main/java/com/nbclass/activity/model/enums/ContentType.java @@ -0,0 +1,57 @@ +package com.nbclass.activity.model.enums; + +import org.apache.commons.lang3.StringUtils; + +/** + * i案例类型枚举 + * @author Leon + * @datetime 2020年5月29日 下午1:56:11 + */ +public enum ContentType { + + ppqa("106", "品牌全案"), + + ggqa("107", "公关全案"), + + design("108", "创意设计"), + + video("109", "视频动画"), + + h5("110", "技术开发"), + + ldhd("111", "落地活动"), + + others("112", "其他案例") + ; + + // 枚举值 + private final String key; + + // 枚举描述 + private final String desc; + + + ContentType(final String key, final String desc ) { + this.key = key; + this.desc = desc; + } + + public String key() { + return key; + } + + public String desc() { + return desc; + } + + + public static String getNameByCode(String key){ + for(ContentType entity : ContentType.values()){ + if(key.equals(entity.key)){ + return entity.desc; + } + } + return null; + } + +} diff --git a/src/main/java/com/nbclass/activity/service/CaseTypeService.java b/src/main/java/com/nbclass/activity/service/CaseTypeService.java new file mode 100644 index 0000000..ef1a4dd --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/CaseTypeService.java @@ -0,0 +1,52 @@ +package com.nbclass.activity.service; + +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.model.Content; +import com.nbclass.activity.model.ContentTags; +import com.nbclass.activity.model.DataDict; +import com.nbclass.activity.model.DataDictItem; + +import java.util.List; + +/** + * + * @author gaojiuqi + * @datetime 2021年10月12日 下午14:42 + */ +public interface CaseTypeService { + + /** + * 查询(案例类型、案例用途、创意形式、节日事件) + * @return + */ + List getTypeCase(Integer dictId); + + /** + * 字典值类型列表查询 + * @return + */ + List getDictionariesType(); + + /** + * 案例搜索框 + * @param dictItemId 案例类型id + * @return + */ + PageInfo getSearchCase(String parameter, Integer dictItemId,Integer page, Integer pageSize); + + /** + * 案例库列表 + * @param page + * @param pageSize + * @return + */ + PageInfo getListCase(Integer page, Integer pageSize); + + /** + * 获取案例标签 + * @param contentId 案例id + * @return + */ + ContentTags getContentTags(Integer contentId); + +} diff --git a/src/main/java/com/nbclass/activity/service/CommentService.java b/src/main/java/com/nbclass/activity/service/CommentService.java new file mode 100644 index 0000000..dee81fc --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/CommentService.java @@ -0,0 +1,208 @@ +package com.nbclass.activity.service; + +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.model.*; + +public interface CommentService { + + /** + * 案例详情和评论列表 + * @param contentId + * @return + */ + Content getComment(Long contentId,Long userId); + + /** + * 评论回复列表 + * @param commentId + * @param userId + * @return + */ + Comment getCommentReplyList(Long commentId,Long userId); + + /** + * 添加用户发布信息 + * @param comment + * @return + */ + void saveComment(Comment comment); + + /** + * 根据评论id获取被点赞人Id + * @param commentId + * @return + */ + Comment getCommentUserId(Long commentId); + + /** + * 添加评论点赞 + * @param praise + * @return + */ + void savePraiseNo(Praise praise); + + /** + * 添加案例点赞 + * @param contentId + * @return + */ + void updateContentPraiseNo(Long contentId); + + /** + * 评论总数加1 + * @param commentId + */ + void updatePraiseno(Long commentId); + + /** + * 用户回复 + * @param comment + * @return + */ + void saveUserReply(Comment comment); + + /** + * 用户举报 + * @param report + * @return + */ + void saveReport(Report report); + + /** + * 举报列表获取 + * @param page + * @param pageSize + * @return + */ + PageInfo getReport(Integer page, Integer pageSize, String userId); + + /** + * 添加标签、索引反馈日志 + * @return + */ + void saveLabelLogger(LabelLogger labelLogger); + + /** + * 新建文件夹 + * @param favoritesFolder + * @return + */ + void saveFavorites(FavoritesFolder favoritesFolder); + + /** + * 案例详情的文件夹列表 + * @return + */ + PageInfo getUserFolder(Long userId, Integer page, Integer pageSize,Long commentId); + + /** + * 收藏案例到文件夹中 + * @param contentId + * @return + */ + void saveFolder(Long folderId,Long contentId); + + /** + * 添加收藏总数 + * @param folderId + */ + void saveFolderTotal(Long folderId); + + /** + * 判断该文件夹是否已被收藏 + * @param folderId + * @param contentId + * @return + */ + int getFolderExistence(Long folderId, Long contentId); + + /** + * 判断文件夹是否重名 + * @param name + * @return + */ + int getFavoritesName(String name); + + /** + * 取消文件夹中的案例收藏 + * @param folderId + * @param contentId + * @return + */ + void deleteFolderResources(Long folderId, Long contentId); + + /** + * 我的收藏 + * @param page + * @param pageSize + * @return + */ + PageInfo getMyCollection(Long userId, Integer page, Integer pageSize,Long folderId); + + /** + * 获取用户文件夹列表 + * @return + */ + PageInfo getUserFolderList(Long userId, Integer page, Integer pageSize); + + /** + * 取消评论点赞 + * @return + */ + void deletePraise(Long commentId, Long userId); + + /** + * 总点赞数减一 + * @param commentId + */ + void updatePraiseNoONE(Long commentId); + + /** + * 评论总回复数加一 + */ + void updateReplyNumberONE(Long commentId); + + /** + * 回复列表 + * @param userId + * @param page + * @param pageSize + * @return + */ + PageInfo getReplyList(Long userId, Integer page, Integer pageSize); + + /** + * 点赞列表 + * @param page + * @param pageSize + * @return + */ + PageInfo getFabulousList(Long userId, Integer page, Integer pageSize); + + /** + * 更新案例的收藏总数+1 + * @param contentId + */ + void updateCollectionNumber(Long contentId); + + /** + * 更新案例的收藏总数-1 + * @param contentId + */ + void updateCollectionNumberReduce(Long contentId); + + /** + * 标签、索引反馈日志列表 + * @param page + * @param pageSize + * @return + */ + PageInfo getLabelLogger(Integer page, Integer pageSize, Long userId); + + /** + * 案例打分 + * @param scoring + */ + void saveScoring(Scoring scoring); + +} diff --git a/src/main/java/com/nbclass/activity/service/ContentService.java b/src/main/java/com/nbclass/activity/service/ContentService.java new file mode 100644 index 0000000..8731b05 --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/ContentService.java @@ -0,0 +1,193 @@ +package com.nbclass.activity.service; + +import java.util.List; + +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.model.*; + +/** + * + * @author Leon + * @datetime 2020年5月28日 下午4:42:15 + */ +public interface ContentService { + + /** + * 添加 + * @param entity + */ + public void add(Content entity); + + /** + * 修改 + * @param entity + */ + public void update(Content entity); + + /** + * 删除 + * @param ids + */ + public void delete(List ids); + + /** + * 根据id查询 + * @param id + * @return entity + */ + public Content findById(Long id); + + /** + * 根据条件查询列表 + * @param entity + * @return + */ + public List getList(Content entity); + + /** + * 根据条件查询列表 - 前端页面查询 + * @param entity + * @return + */ + public List getListByFront(Content entity); + + /** + * 添加点赞数阅读数 + * @param type [view, like] + * @param id 案例ID + */ + public int updateContentNum(String type, Long id); + + /** + * 同步数据库数据到ES + * @param entity + * @param isReBuild 是否重建,true:删除所有文档重建 + */ + public long syncEsData(Content entity, boolean isReBuild); + + /** + * 查询案例标题和评论数和点赞数 + * @return + */ + PageInfo getCaseTitle(Integer page,Integer pageSize); + + /** + * 标签关联案例 + * @param contentTags + */ + void addContentTags(ContentTags contentTags); + + /** + * 获取自定义标签列表 + * @return + */ + List getOtherLabels(); + + /** + * 取消案例关联标签的关系 + * @param idList + */ + void deleteContentTags(List idList); + + /** + * 案例索引标签反馈次数+1 + * @param contentId + */ + void updateLabelFeedback(Long contentId); + + /** + * 根据条件查询列表 + * @param entity + * @return + */ + List getList2(Content entity); + + /** + * 更新案例总评论数 + * @param contentId + */ + void updateComments(Long contentId); + + /** + * 添加案例评分 + * @param userId + * @param contentId + * @param score + */ + void saveScore(Long userId, Long contentId, Double score); + + /** + * 查看用户是否已经打过分 + * @param userId + * @param contentId + * @return + */ + int getScore(Long userId, Long contentId); + + /** + * 根据案例id查询案例详情 + * @param contentId + * @return + */ + Content getEditContent(Long contentId); + + /** + * 提交审核申请单 + * @param application + */ + void saveApplication(Application application); + + /** + * 查询案例是否已提交过审核 + * @param contentId + * @return + */ + int getApplicationCount(Long contentId); + + /** + * 删除案例审核申请 + * @param applicationId + */ + void deleteApplication(Long applicationId); + + /** + * 更新审核申请单状态 + * @param applicationId + */ + void updateApplicationStatus(Long applicationId); + + /** + * 获取审核列表 + * @param entity + * @return + */ + List getContentApplication(Content entity); + + /** + * 审核案例 + * @param check + * @param userId + */ + void updateApplication(Check check, Long userId); + + /** + * 根据userId获取微信用户信息 + * @param username + * @return + */ + WxUser getQyWxUser(String username); + + /** + * 根据部门id获取用户领导 + * @param department + * @return + */ + List getUserLeader(String department); + + /** + * 下线案例 + * @param contentId + */ + void deleteRelease(Long contentId); + +} diff --git a/src/main/java/com/nbclass/activity/service/DataDictService.java b/src/main/java/com/nbclass/activity/service/DataDictService.java new file mode 100644 index 0000000..9344a84 --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/DataDictService.java @@ -0,0 +1,86 @@ +package com.nbclass.activity.service; + +import java.util.List; + +import com.nbclass.activity.model.ContentTags; +import com.nbclass.activity.model.DataDict; +import com.nbclass.activity.model.DataDictItem; + +/** + * + * @author Leon + * @datetime 2020年5月28日 下午3:13:52 + */ +public interface DataDictService { + + /** + * 添加 + * @param entity + */ + public void add(DataDict entity); + + /** + * 修改 + * @param entity + */ + public void update(DataDict entity); + + /** + * 删除 + * @param ids + */ + public void delete(List ids); + + /** + * 根据id查询 + * @param id + * @return entity + */ + public DataDict findById(Long id); + + /** + * 根据条件查询列表 + * @param entity + * @return + */ + public List getList(DataDict entity); + + /** + * 通过字典Key获取子集 + * @param datakey + * @return + */ + public List getItemsByKey(String datakey); + + /** + * 根据dictid查询字典item列表 + * @param entity + * @return + */ + public List getItemList(DataDictItem entity); + + /** + * 添加或修改字典子项 + * @param entity + */ + public void mergeDataDictItem(DataDictItem entity); + + /** + * 删除字典子项 + * @param id + */ + public void deleteDataDictItem(Long id); + + /** + * 获取字典值 + * @return + */ + List getDropDownBox(); + + /** + * 根据名字查询tasId + * @param name + * @return + */ + String[] getTagId(String name); +} diff --git a/src/main/java/com/nbclass/activity/service/ElasticSearchService.java b/src/main/java/com/nbclass/activity/service/ElasticSearchService.java new file mode 100644 index 0000000..0e258f9 --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/ElasticSearchService.java @@ -0,0 +1,60 @@ +package com.nbclass.activity.service; + +import java.util.List; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.activity.model.Content; + +/** + * ES搜索 + * @author Leon + * @datetime 2020年6月1日 下午3:00:41 + */ +public interface ElasticSearchService { + + /** + * 添加或修改文档索引 + * @param entity + */ + public void merge(Content entity); + + /** + * 删除 + * @param ids + */ + public void deleteDocByIds(List ids); + + /** + * 根据条件删除 + * @param entity + */ + public void deleteDocByQuery(Content entity); + + /** + * 判断一个文档是否存在 + * @param id + * @return + */ + public boolean existDoc(Long id); + + /** + * 根据id查询 + * @param id + * @return entity + */ + public Content findById(Long id); + + /** + * 根据条件查询列表 + * @param pageNum + * @param pageSize + * @param type 案例类型 + * @param keyWord 关键词 + * @param tagIds 标签ID, 多个英文逗号分隔 + * @param sourceIds 案例来源ID, 多个英文逗号分隔 + * @return + */ + public JSONObject getList(Integer pageNum, Integer pageSize, String type, String keyWord, String tagIds, String sourceIds); + + +} diff --git a/src/main/java/com/nbclass/activity/service/WxService.java b/src/main/java/com/nbclass/activity/service/WxService.java new file mode 100644 index 0000000..6c4cc03 --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/WxService.java @@ -0,0 +1,47 @@ +package com.nbclass.activity.service; + +import com.nbclass.activity.model.WeiXiUser; + +public interface WxService { + + /** + * 数据库查询是否有该openid,如果有直接从数据库获取用户信息 + * @param openid + * @return + */ + int getOpenId(String openid); + + /** + * 添加用户信息到数据库 + * @param weiXiUser + */ + void saveWxUser(WeiXiUser weiXiUser); + + /** + * 数据库获取用户信息 + * @param openid + * @return + */ + WeiXiUser getWxUser(String openid); + + /** + * 根据用户id获取信息 + * @param userId + * @return + */ + WeiXiUser getUser(String userId); + + /** + * 更新用户基础信息 + * @param weiXiUser + * @return + */ + void updateBasicData(WeiXiUser weiXiUser); + + /** + * 查看用户基础信息 + * @return + */ + WeiXiUser getUserBasicData(Long userId); + +} diff --git a/src/main/java/com/nbclass/activity/service/WxUserService.java b/src/main/java/com/nbclass/activity/service/WxUserService.java new file mode 100644 index 0000000..b8d139d --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/WxUserService.java @@ -0,0 +1,30 @@ +package com.nbclass.activity.service; + +import java.util.List; + +import com.nbclass.activity.model.WxUser; + +/** + * + * @author Leon + * @datetime 2020年6月2日 下午4:50:28 + */ +public interface WxUserService{ + + /** + * 批量保存用户 + * @param userList + */ + public void merge(List userList); + + /** + * 同步企业微信成员 + */ + public void syncWxUser(); + + /** + * 同步企业微信部门 + */ + public void syncWxDepartment(); + +} diff --git a/src/main/java/com/nbclass/activity/service/impl/CaseTypeServiceImpl.java b/src/main/java/com/nbclass/activity/service/impl/CaseTypeServiceImpl.java new file mode 100644 index 0000000..c70c279 --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/impl/CaseTypeServiceImpl.java @@ -0,0 +1,51 @@ +package com.nbclass.activity.service.impl; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.mapper.CaseTypeMapper; +import com.nbclass.activity.model.Content; +import com.nbclass.activity.model.ContentTags; +import com.nbclass.activity.model.DataDict; +import com.nbclass.activity.model.DataDictItem; +import com.nbclass.activity.service.CaseTypeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class CaseTypeServiceImpl implements CaseTypeService { + + @Autowired + private CaseTypeMapper caseTypeMapper; + + @Override + public List getTypeCase(Integer dictId) { + return caseTypeMapper.getTypeCase(dictId); + } + + @Override + public List getDictionariesType() { + return caseTypeMapper.getDictionariesType(); + } + + @Override + public PageInfo getSearchCase(String parameter, Integer dictItemId,Integer page, Integer pageSize) { + PageHelper.startPage(page,pageSize); + List list = caseTypeMapper.getSearchCase(parameter,dictItemId); + return new PageInfo<>(list); + } + + @Override + public PageInfo getListCase(Integer page, Integer pageSize) { + PageHelper.startPage(page,pageSize); + List list = caseTypeMapper.getListCase(); + return new PageInfo<>(list); + } + + @Override + public ContentTags getContentTags(Integer contentId) { + return caseTypeMapper.getContentTags(contentId); + } + +} diff --git a/src/main/java/com/nbclass/activity/service/impl/CommentServiceImpl.java b/src/main/java/com/nbclass/activity/service/impl/CommentServiceImpl.java new file mode 100644 index 0000000..f14854d --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/impl/CommentServiceImpl.java @@ -0,0 +1,178 @@ +package com.nbclass.activity.service.impl; + +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.mapper.CommentMapper; +import com.nbclass.activity.model.*; +import com.nbclass.activity.service.CommentService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class CommentServiceImpl implements CommentService { + + @Autowired + private CommentMapper commentMapper; + + @Override + public Content getComment(Long contentId,Long userId) { + return commentMapper.getComment(contentId,userId); + } + + @Override + public Comment getCommentReplyList(Long commentId, Long userId) { + return commentMapper.getCommentReplyList(commentId,userId); + } + + @Override + public void saveComment(Comment comment) { + commentMapper.saveComment(comment); + } + + @Override + public Comment getCommentUserId(Long commentId) { + return commentMapper.getCommentUserId(commentId); + } + + @Override + public void savePraiseNo(Praise praise) { + commentMapper.savePraiseNo(praise); + } + + @Override + public void updateContentPraiseNo(Long contentId) { + commentMapper.updateContentPraiseNo(contentId); + } + + @Override + public void updatePraiseno(Long commentId) { + commentMapper.updatePraiseno(commentId); + } + + @Override + public void saveUserReply(Comment comment) { + commentMapper.saveUserReply(comment); + } + + @Override + public void saveReport(Report report) { + commentMapper.saveReport(report); + } + + @Override + public PageInfo getReport(Integer page, Integer pageSize, String userId) { + PageHelper.startPage(page,pageSize); + List list = commentMapper.getReport(userId); + return new PageInfo<>(list); + } + + @Override + public void saveLabelLogger(LabelLogger labelLogger) { + commentMapper.saveLabelLogger(labelLogger); + } + + @Override + public void saveFavorites(FavoritesFolder favoritesFolder) { + commentMapper.saveFavorites(favoritesFolder); + } + + @Override + public PageInfo getUserFolder(Long userId, Integer page, Integer pageSize,Long commentId) { + PageHelper.startPage(page,pageSize); + List list = commentMapper.getUserFolder(userId,commentId); + return new PageInfo<>(list); + } + + @Override + public void saveFolder(Long folderId,Long contentId) { + commentMapper.saveFolder(folderId,contentId); + } + + @Override + public void saveFolderTotal(Long folderId) { + commentMapper.saveFolderTotal(folderId); + } + + @Override + public int getFolderExistence(Long folderId, Long contentId) { + return commentMapper.getFolderExistence(folderId,contentId); + } + + @Override + public int getFavoritesName(String name) { + return commentMapper.getFavoritesName(name); + } + + @Override + public void deleteFolderResources(Long folderId, Long contentId) { + commentMapper.deleteFolderResources(folderId,contentId); + } + + @Override + public PageInfo getMyCollection(Long userId, Integer page, Integer pageSize,Long folderId) { + PageHelper.startPage(page,pageSize); + List list = commentMapper.getMyCollection(userId,folderId); + return new PageInfo<>(list); + } + + @Override + public PageInfo getUserFolderList(Long userId, Integer page, Integer pageSize) { + PageHelper.startPage(page,pageSize); + List list = commentMapper.getUserFolderList(userId); + return new PageInfo<>(list); + } + + @Override + public void deletePraise(Long commentId, Long userId) { + commentMapper.deletePraise(commentId,userId); + } + + @Override + public void updatePraiseNoONE(Long commentId) { + commentMapper.updatePraiseNoONE(commentId); + } + + @Override + public void updateReplyNumberONE(Long commentId) { + commentMapper.updateReplyNumberONE(commentId); + } + + @Override + public PageInfo getReplyList(Long userId, Integer page, Integer pageSize) { + PageHelper.startPage(page,pageSize); + List list = commentMapper.getReplyList(userId); + return new PageInfo<>(list); + } + + @Override + public PageInfo getFabulousList(Long userId, Integer page, Integer pageSize) { + PageHelper.startPage(page,pageSize); + List list = commentMapper.getFabulousList(userId); + return new PageInfo<>(list); + } + + @Override + public void updateCollectionNumber(Long contentId) { + commentMapper.updateCollectionNumber(contentId); + } + + @Override + public void updateCollectionNumberReduce(Long contentId) { + commentMapper.updateCollectionNumberReduce(contentId); + } + + @Override + public PageInfo getLabelLogger(Integer page, Integer pageSize, Long userId) { + PageHelper.startPage(page,pageSize); + List list = commentMapper.getLabelLogger(userId); + return new PageInfo<>(list); + } + + @Override + public void saveScoring(Scoring scoring) { + commentMapper.saveScoring(scoring); + } + +} diff --git a/src/main/java/com/nbclass/activity/service/impl/ContentServiceImpl.java b/src/main/java/com/nbclass/activity/service/impl/ContentServiceImpl.java new file mode 100644 index 0000000..1ead819 --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/impl/ContentServiceImpl.java @@ -0,0 +1,470 @@ +package com.nbclass.activity.service.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import javax.annotation.Resource; +import javax.transaction.Transactional; + +import com.nbclass.activity.mapper.WXMapper; +import com.nbclass.activity.model.*; +import com.nbclass.activity.service.WxService; +import com.nbclass.system.service.UserService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.mapper.ContentMapper; +import com.nbclass.activity.mapper.DataDictMapper; +import com.nbclass.activity.model.enums.ContentType; +import com.nbclass.activity.service.ContentService; +import com.nbclass.activity.service.ElasticSearchService; +import com.nbclass.exception.ParameterException; +import com.nbclass.util.CommonUtils; + +/** + * + * @author Leon + * @datetime 2020年5月28日 下午4:42:45 + */ +@Service +public class ContentServiceImpl implements ContentService { + + @Resource + private ContentMapper mapper; + + @Resource + private DataDictMapper dataDictMapper; + + @Resource + private ElasticSearchService esService; + + @Resource + private DataDictMapper dataDictMapper2; + + @Resource + private WXMapper wxMapper; + + + private void baseSaveVerify(Content entity) { + /*if(StringUtils.isBlank(entity.getType())) { + throw new ParameterException("案例类型不能为空"); + }*/ + if(StringUtils.isBlank(entity.getTitle())) { + throw new ParameterException("标题不能为空"); + } + /*if(entity.getFromid() == null || entity.getFromid() < 1) { + throw new ParameterException("案例来源不能为空"); + }*/ + if(StringUtils.isBlank(entity.getDesct())) { + throw new ParameterException("描述不能为空"); + } + if(StringUtils.isBlank(entity.getListicon())) { + throw new ParameterException("封面图不能为空"); + } + + // 链接形式的案例 + if(ContentType.h5.key().equals(entity.getType()) || ContentType.video.key().equals(entity.getType())) { + if(StringUtils.isBlank(entity.getUrl())) { + throw new ParameterException("URL不能为空"); + } + } + + // 富文本编辑的案例 + // 0527开发群确认的,记录关键词“都是富文本” + if(ContentType.design.key().equals(entity.getType()) + || ContentType.ppqa.key().equals(entity.getType()) + || ContentType.ggqa.key().equals(entity.getType()) + || ContentType.ldhd.key().equals(entity.getType()) + || ContentType.others.key().equals(entity.getType())) { + if(StringUtils.isBlank(entity.getContent())) { + throw new ParameterException("内容不能为空"); + } + } + + if(StringUtils.isBlank(entity.getAttachment()))entity.setAttachment(null); // 空字符串导致MySQL存储json类型异常 + } + +// // 修改案例标签和图集 +// private void updateLabelAndImages(Content o){ +// long cid = o.getId(); +// List newTagList = new ArrayList(); +// if(StringUtils.isNotBlank(o.getTags())){ +// ArrayList postTagList = CommonUtils.str2List(o.getTags()); +// // 查询出 dictid +// DataDict dataDict = new DataDict(); +// dataDict.setDatakey("tag_"+o.getType()); +// dataDict = dataDictMapper.selectOne(dataDict); +// +// +// List taglist = dataDictMapper.getItemsByNames("tag_"+o.getType(), postTagList); +// HashMap tagMap = new HashMap(); +// for (DataDictItem dataDictItem : taglist) { +// tagMap.put(dataDictItem.getName(), dataDictItem); +// } +// for (String tag : postTagList) { +// if(StringUtils.isBlank(tag))continue; +// DataDictItem ddItem = null; +// if(tagMap.containsKey(tag)) { +// ddItem = tagMap.get(tag); +// }else { +// // 字典表里没有则新增标签 +// ddItem = new DataDictItem(); +// ddItem.setDictid(dataDict.getId()); +// ddItem.setName(tag); +// ddItem.setValue(tag); +// dataDictMapper.insertDataDictItem(ddItem); +// } +// ContentTags label = new ContentTags(); +// label.setCid(cid); +// label.setTid(ddItem.getId()); +// newTagList.add(label); +// } +// } +// mapper.deleteTags(cid); +// if(newTagList.size() > 0)mapper.insertContentTags(newTagList); +// +// +// List imgList = new ArrayList(); +// String images = o.getImages(); +// if(StringUtils.isNotBlank(images)){ +// JSONArray array = JSONArray.parseArray(images); +// for(int i=0, j=array.size();i 0){ +// mapper.insertContentImages(imgList); +// } +// } + + // 修改案例标签和图集 + private void updateLabelAndImages(Content o){ + long cid = o.getId(); + List newTagList = new ArrayList(); + if(StringUtils.isNotBlank(o.getTags())){ + ArrayList postTagList = CommonUtils.str2List(o.getTags()); + // 查询自定义标签的dictid + Long dictId = dataDictMapper2.getDataDictID(); + + //根据标签名称查询标签信息 + List tagList = dataDictMapper2.getDataDictTemName(postTagList); + HashMap tagMap = new HashMap(); + for (DataDictItem dataDictItem : tagList) { + tagMap.put(dataDictItem.getName(), dataDictItem); + } + for (String tag : postTagList) { + if(StringUtils.isBlank(tag))continue; + DataDictItem ddItem = null; + if(tagMap.containsKey(tag)) { + ddItem = tagMap.get(tag); + }else { + // 字典表里没有则新增标签 + ddItem = new DataDictItem(); + ddItem.setDictid(dictId); + ddItem.setName(tag); + ddItem.setValue(tag); + dataDictMapper.insertDataDictItem(ddItem); + } + ContentTags label = new ContentTags(); + label.setCid(cid); + label.setTid(ddItem.getId()); + newTagList.add(label); + } + } + mapper.deleteTags(cid); + if(o.getTagLists() != null){ + String[] tagList1 = o.getTagLists(); + for (String s : tagList1) { + ContentTags contentTags = new ContentTags(); + contentTags.setCid(cid); + contentTags.setTid(Long.parseLong(s)); + newTagList.add(contentTags); + } + } + if(newTagList.size() > 0)mapper.insertContentTags(newTagList); + + + List imgList = new ArrayList(); + String images = o.getImages(); + if(StringUtils.isNotBlank(images)){ + JSONArray array = JSONArray.parseArray(images); + for(int i=0, j=array.size();i 0){ + mapper.insertContentImages(imgList); + } + } + + @Transactional + @Override + public void add(Content entity) { + baseSaveVerify(entity); + entity.setType(null); + mapper.insertSelective(entity); + updateLabelAndImages(entity); + + // 提交到ElasticSearch + entity = findById(entity.getId()); + esService.merge(entity); + } + + @Transactional + @Override + public void update(Content entity) { + baseSaveVerify(entity); + entity.setType(null); + mapper.updateByPrimaryKeySelective(entity); + updateLabelAndImages(entity); + + // 提交到ElasticSearch + entity = findById(entity.getId()); + esService.merge(entity); + } + + @Override + public void delete(List ids) { + if(ids == null || ids.isEmpty()){ + throw new ParameterException("id不能为空"); + } + // 从数据库中删除 + mapper.deleteByIds(ids); + // 从ES中删除 + esService.deleteDocByIds(ids); + } + + @Override + public Content findById(Long id) { + if(id == null || id < 1){ + throw new ParameterException("id不能为空"); + } + Content entity = mapper.findById(id); + if(entity!=null&&entity.getId()!=null&&entity.getId()>0) { + if(entity.getImgList()!=null)entity.setImages(JSON.toJSONString(entity.getImgList())); + if(entity.getTagList()!=null)entity.setTags(JSON.toJSONString(entity.getTagList())); + } + return entity; + } + + @Override + public List getList(Content entity) { + return mapper.getList(entity); + } + + @Override + public List getListByFront(Content entity) { + return mapper.getListByFront(entity); + } + + @Override + public int updateContentNum(String type, Long id) { + if(StringUtils.isBlank(type)){ + throw new ParameterException("类型不能为空"); + } + if(id == null || id < 1){ + throw new ParameterException("id不能为空"); + } + mapper.updateContentNum(type, id); + Content entity = findById(id); + + // 提交到ElasticSearch + esService.merge(entity); + + Integer result = 0; + if("view".equals(type)){ + result = entity.getViewno(); + }else if("like".equals(type)){ + result = entity.getPraiseno(); + } + if(result == null)result=1; + return result; + } + + @Override + public long syncEsData(Content entity, boolean isReBuild) { + if(StringUtils.isBlank(entity.getType())) { + throw new ParameterException("案例类型不能为空"); + } + if(isReBuild) { + // 如果重建索引,则先把所有记录删除,这个也可用于删除脏数据。 + esService.deleteDocByQuery(entity); + } + PageHelper.startPage(1, 1000); + List dataList = getListByFront(entity); + PageInfo pages = new PageInfo<>(dataList); + for (Content content : dataList) { + // 提交到ElasticSearch + esService.merge(content); + } + return pages.getTotal(); + } + + @Override + public PageInfo getCaseTitle(Integer page,Integer pageSize) { + PageHelper.startPage(page,pageSize); + List list = mapper.getCaseTitle(); + return new PageInfo<>(list); + } + + @Override + public void addContentTags(ContentTags contentTags) { + mapper.addContentTags(contentTags); + } + + @Override + public List getOtherLabels() { + List list = mapper.getOtherLabels(); + return list; + } + + @Override + public void deleteContentTags(List idList) { + mapper.deleteContentTags(idList); + } + + @Override + public void updateLabelFeedback(Long contentId) { + mapper.updateLabelFeedback(contentId); + } + + @Override + public List getList2(Content entity) { + return mapper.getList2(entity); + } + + @Override + public void updateComments(Long contentId) { + mapper.updateComments(contentId); + } + + @Override + public void saveScore(Long userId, Long contentId, Double score) { + mapper.saveScore(userId,contentId,score); + } + + @Override + public int getScore(Long userId, Long contentId) { + return mapper.getScore(userId,contentId); + } + + @Override + public Content getEditContent(Long contentId) { + return mapper.getEditContent(contentId); + } + + @Override + @Transactional + public void saveApplication(Application application) { + //添加审核申请单 + mapper.saveApplication(application); + //添加审核人信息 + Check check = new Check(); + check.setApplicationId(application.getId()); + List checks = application.getChecks(); + if(checks == null ) throw new ParameterException("审核人不能为空!"); + for (Check check1 : checks) { + check.setCheckId(check1.getCheckId()); + mapper.saveCheck(check); + } + //更新案例状态(审核可能是下架重新申请的) + mapper.updateContentRelease(application.getContentId(),1); + } + + @Override + public int getApplicationCount(Long contentId) { + return mapper.getApplicationCount(contentId); + } + + @Override + @Transactional + public void deleteApplication(Long applicationId) { + //删除案例审核申请 + mapper.deleteApplication(applicationId); + //删除审核人信息 + mapper.deleteCheck(applicationId); + } + + @Override + @Transactional + public void updateApplicationStatus(Long applicationId) { + //更新审核申请单状态 + mapper.updateApplicationStatus(applicationId); + //更新审核人状态 + mapper.updateCheckStatus(applicationId); + } + + @Override + public List getContentApplication(Content entity) { + return mapper.getContentApplication(entity); + } + + @Override + @Transactional + public void updateApplication(Check check, Long userId) { + //更新审核表状态 + check.setCheckId(userId); + mapper.updateCheck(check); + Application application = new Application(); + application.setId(check.getApplicationId()); + //更新申请单状态 + if(check.getStatus() == 2){ + //审核通过 + application.setStatus(2); + //查询案例id + Long contentId = mapper.getContentId(check.getApplicationId()); + //更新案例状态 + mapper.updateContentRelease(contentId,2); + }else { + //审核驳回 + application.setStatus(3); + } + mapper.updateApplication(application); + + } + + @Override + public WxUser getQyWxUser(String username) { + return wxMapper.getQyWxUser(username); + } + + @Override + public List getUserLeader(String department) { + return wxMapper.getUserLeader(department); + } + + @Override + @Transactional + public void deleteRelease(Long contentId) { + //根据案例id查询申请单信息 + Application application = mapper.getApplication(contentId); + //删除申请单信息 + mapper.deleteApplication(application.getId()); + //删除审核表信息 + mapper.deleteCheck(application.getId()); + //案例表状态修改为已下线 + mapper.updateContentRelease(contentId,4); + } + + +} diff --git a/src/main/java/com/nbclass/activity/service/impl/DataDictServiceImpl.java b/src/main/java/com/nbclass/activity/service/impl/DataDictServiceImpl.java new file mode 100644 index 0000000..aae6a6b --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/impl/DataDictServiceImpl.java @@ -0,0 +1,146 @@ +package com.nbclass.activity.service.impl; + +import java.util.List; + +import javax.annotation.Resource; + +import com.nbclass.activity.model.ContentTags; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import com.nbclass.activity.mapper.DataDictItemMapper; +import com.nbclass.activity.mapper.DataDictMapper; +import com.nbclass.activity.model.DataDict; +import com.nbclass.activity.model.DataDictItem; +import com.nbclass.activity.service.DataDictService; +import com.nbclass.exception.ParameterException; + +/** + * + * @author Leon + * @datetime 2020年5月28日 下午3:23:26 + */ +@Service +public class DataDictServiceImpl implements DataDictService { + + @Resource + private DataDictMapper mapper; + + @Resource + DataDictItemMapper dataDictItemMapper; + + private void baseSaveVerify(DataDict entity) { + + } + + @Override + public void add(DataDict entity) { + baseSaveVerify(entity); + mapper.insertSelective(entity); + } + + @Override + public void update(DataDict entity) { + baseSaveVerify(entity); + mapper.updateByPrimaryKeySelective(entity); + } + + @Override + public void delete(List ids) { + if(ids == null || ids.isEmpty()){ + throw new ParameterException("id不能为空"); + } + Long cnt = mapper.delCheck(ids); + if(cnt!=null && cnt>0) { + throw new ParameterException("请先删除子项再删除字典!"); + } + Long dataDictID = mapper.getDataDictID(); + for (Long id : ids) { + if(dataDictID.equals(id)) throw new ParameterException("该字典不可删除!"); + } + mapper.deleteByIds(ids); + } + + @Override + public DataDict findById(Long id) { + if(id == null || id < 1){ + throw new ParameterException("id不能为空"); + } + DataDict search = new DataDict(); + search.setId(id); + DataDict entity = mapper.selectOne(search); + return entity; + } + + @Override + public List getList(DataDict entity) { + return mapper.getList(entity); + } + + @Override + public List getItemsByKey(String datakey) { + if(StringUtils.isBlank(datakey)) { + throw new ParameterException("参数不能为空"); + } + return mapper.getItemsByKey(datakey); + } + + @Override + public List getItemList(DataDictItem entity) { + return dataDictItemMapper.getList(entity); + } + + @Override + public void mergeDataDictItem(DataDictItem entity) { + if(entity.getDictid() == null || entity.getDictid() < 1) { + throw new ParameterException("字典ID不能为空"); + } + if(StringUtils.isBlank(entity.getValue())) { + throw new ParameterException("编码不能为空"); + } + if(StringUtils.isBlank(entity.getName())) { + throw new ParameterException("名称不能为空"); + } + + Long cnt = dataDictItemMapper.checkExist(entity); + if(cnt!=null && cnt>0) { + throw new ParameterException("该记录已存在"); + } + if(entity.getId()!=null && entity.getId()>0) { + dataDictItemMapper.updateByPrimaryKeySelective(entity); + }else { + entity.setId(null); + dataDictItemMapper.insertSelective(entity); + } + } + + @Override + public void deleteDataDictItem(Long id) { + if(id == null || id < 1) { + throw new ParameterException("ID不能为空"); + } + Long cnt = dataDictItemMapper.checkContentTag(id); + if(cnt!=null && cnt>0) { + throw new ParameterException("该标签正在使用,请先从案例中解除此标签"); + } + + Long cnt2 = dataDictItemMapper.checkContentFrom(id); + if(cnt2!=null && cnt2>0) { + throw new ParameterException("该案例来源正在使用,请先从案例中解除此来源"); + } + + dataDictItemMapper.deleteByPrimaryKey(id); + } + + @Override + public List getDropDownBox() { + return dataDictItemMapper.getDropDownBox(); + } + + @Override + public String[] getTagId(String name) { + return dataDictItemMapper.getTagId(name); + } + + +} diff --git a/src/main/java/com/nbclass/activity/service/impl/ElasticSearchServiceImpl.java b/src/main/java/com/nbclass/activity/service/impl/ElasticSearchServiceImpl.java new file mode 100644 index 0000000..4245dba --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/impl/ElasticSearchServiceImpl.java @@ -0,0 +1,349 @@ +package com.nbclass.activity.service.impl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Resource; + +import org.apache.commons.lang3.StringUtils; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; +import org.elasticsearch.action.admin.indices.get.GetIndexRequest; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.delete.DeleteResponse; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.action.update.UpdateResponse; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.indices.CreateIndexRequest; +import org.elasticsearch.client.indices.CreateIndexResponse; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.query.BoolQueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.reindex.BulkByScrollResponse; +import org.elasticsearch.index.reindex.DeleteByQueryRequest; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.search.SearchHits; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.sort.SortOrder; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.nbclass.activity.model.Content; +import com.nbclass.activity.model.ContentTags; +import com.nbclass.activity.service.ContentService; +import com.nbclass.activity.service.ElasticSearchService; +import com.nbclass.exception.ParameterException; +import com.nbclass.exception.ServiceException; + +import lombok.extern.slf4j.Slf4j; + +/** + * 案例内容ES操作 + * @author Leon + * @datetime 2020年6月1日 下午3:02:00 + */ +@Slf4j +@Service +public class ElasticSearchServiceImpl implements ElasticSearchService { + + @Resource + private RestHighLevelClient restHighLevelClient; + + @Resource + private ContentService contentService; + + private static final String es_index_content = "es-cases-content"; + + + /** + * 检查索引是否存在 + * @throws IOException + */ + private boolean existIndex() throws IOException{ + GetIndexRequest request = new GetIndexRequest().indices(es_index_content); + @SuppressWarnings("deprecation") + boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT); + return exists; + } + + // 创建索引 + public void createIndex(){ + try { + // 检查索引是否存在 + if(existIndex())return; + + CreateIndexRequest indexRequest = new CreateIndexRequest(es_index_content); + CreateIndexResponse indexResponse = restHighLevelClient.indices().create(indexRequest, RequestOptions.DEFAULT); + log.info("创建索引, 返回值=>{}, 确认状态=>{}", indexResponse.index(), indexResponse.isAcknowledged()); + }catch (Exception e) { + log.info("创建索引异常", e); + } + } + + // 删除索引 + public void deleteIndex(){ + try { + DeleteIndexRequest indexRequest = new DeleteIndexRequest(es_index_content); + AcknowledgedResponse delete = restHighLevelClient.indices().delete(indexRequest, RequestOptions.DEFAULT); + log.info("删除索引, 返回值=>{}, 确认状态=>{}", delete.isFragment(), delete.isAcknowledged()); + }catch (Exception e) { + log.info("删除索引异常", e); + } + } + + // 创建或者更新文档 + @Override + public void merge(Content entity) { + try { + if(entity!=null && entity.getTagList()!=null && entity.getTagList().size()>0) { + String tags = ""; + for(ContentTags tag : entity.getTagList()) { + if(StringUtils.isBlank(tag.getTagName()))continue; + tags += (StringUtils.isBlank(tags)?"":",")+tag.getTagName(); + } + entity.setTags(tags); // 将标签组到字符串里,便于搜索。 + } + + createIndex(); // 判断索引是否存在,不存在创建索引 + + // 判断document是否存在,存在修改,不存在新增 + GetRequest getRequest = new GetRequest(es_index_content, String.valueOf(entity.getId())); + boolean exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT); + if(!exists) { + // 创建文档索引 + IndexRequest indexRequest = new IndexRequest(es_index_content); + indexRequest.id(String.valueOf(entity.getId())).source(JSON.toJSONString(entity), XContentType.JSON); + @SuppressWarnings("unused") + IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT); + log.info("添加文档,新建ES索引成功,id = " + entity.getId()); + }else { + // 修改文档索引 + UpdateRequest updateRequest = new UpdateRequest(es_index_content, String.valueOf(entity.getId())); + updateRequest.doc(JSON.toJSONString(entity), XContentType.JSON); + @SuppressWarnings("unused") + UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT); + log.info("修改文档,重建ES索引成功,id = " + entity.getId()); + } + } catch (IOException e) { + log.error("添加文档,新建ES索引失败,id = " + entity.getId(), e); + throw new ServiceException("案例更新ES索引失败"); + } + } + + @Override + public void deleteDocByIds(List ids) { + if(ids == null || ids.isEmpty()){ + return; + } + try { + for (Long id : ids) { + // DeleteRequest + DeleteRequest deleteRequest = new DeleteRequest(es_index_content, String.valueOf(id)); + @SuppressWarnings("unused") + DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT); + } + } catch (IOException e) { + log.error("删除案例ES索引失败", e); + throw new ServiceException("删除案例ES索引失败"); + } + } + + // matchQuery:会将搜索词分词,再与目标查询字段进行匹配,若分词中的任意一个词与目标字段匹配上,则可查询到。 + // termQuery:不会对搜索词进行分词处理,而是作为一个整体与目标字段进行匹配,若完全匹配,则可查询到。 + @Override + public void deleteDocByQuery(Content entity) { + if(entity.getType()!=null) { + throw new ParameterException("案例类型不能为空"); + } + try { + DeleteByQueryRequest request = new DeleteByQueryRequest(es_index_content); + request.setConflicts("proceed"); // 发生冲突即略过 + request.setQuery(QueryBuilders.termQuery("type", entity.getType())); + BulkByScrollResponse bulkResponse = restHighLevelClient.deleteByQuery(request, RequestOptions.DEFAULT); + + TimeValue timeTaken = bulkResponse.getTook(); + boolean timedOut = bulkResponse.isTimedOut(); + long totalDocs = bulkResponse.getTotal(); + long updatedDocs = bulkResponse.getUpdated(); + long deletedDocs = bulkResponse.getDeleted(); + long batches = bulkResponse.getBatches(); + long noops = bulkResponse.getNoops(); + long versionConflicts = bulkResponse.getVersionConflicts(); + log.info("根据案例类型删除文档,花费时间:" + timeTaken + ",是否超时:" + timedOut + ",总文档数:" + totalDocs + ",更新数:" + + updatedDocs + ",删除数:" + deletedDocs + ",批量次数:" + batches + ",跳过数:" + noops + ",冲突数:" + versionConflicts); + } catch (IOException e) { + log.error("根据案例类型删除文档异常", e); + } + } + + @Override + public boolean existDoc(Long id){ + boolean exists = false; + GetRequest getRequest = new GetRequest(es_index_content, String.valueOf(id)); + try { + exists = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT); + } catch (IOException e) { + e.printStackTrace(); + } + return exists; + } + + // 获取文档 + @Override + public Content findById(Long id) { + // GetRequest + GetRequest getRequest = new GetRequest(es_index_content, String.valueOf(id)); + // 查询ES + GetResponse getResponse; + Content content = null; + try { + getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT); + content = JSON.parseObject(getResponse.getSourceAsString(), Content.class); + if(content == null) { + content = contentService.findById(id); + } + if(content!=null && content.getId()!=null && content.getId()>0) { + content.setTags(null); + content.setImages(null); + } + } catch (IOException e) { + log.error("从ES查询数据失败", e); + throw new ServiceException("从ES查询数据失败"); + } + return content; + } + + /** + * 从数据库查询 + * @return + */ + private JSONObject getListByMySQL(Integer pageNum, Integer pageSize, String type, String keyWord, String tagIds, String sourceIds) { + JSONObject result = new JSONObject(); + Content param = new Content(); + param.setType(type); + param.setTitle(keyWord); + param.setTags(tagIds); + param.setFromids(sourceIds); + PageHelper.startPage(pageNum, pageSize); + List dataList = contentService.getListByFront(param); + PageInfo pages = new PageInfo<>(dataList); + if(dataList!=null && dataList.size()>0) { + for (Content entity : dataList) { + // 清空一些前端用不上的字段 + entity.setTags(null); + entity.setImages(null); + entity.setImgList(null); + } + } + + result.put("total", pages.getTotal()); // 总记录数 + result.put("rows", dataList); // 数据体 + int totalPage = (int)(pages.getTotal() % pageSize==0 ? pages.getTotal()/pageSize : pages.getTotal()/pageSize+1); + result.put("totalPage", totalPage); // 总页数 + return result; + } + + @Override + public JSONObject getList(Integer pageNum, Integer pageSize, String type, String keyWord, String tagIds, String sourceIds) { + // 封装Map参数返回 + JSONObject result = new JSONObject(); + try { + // 选择标签或来源过滤时,从数据库查询记录 + /*if(StringUtils.isNotBlank(tagIds) || StringUtils.isNotBlank(sourceIds)) { + return getListByMySQL(pageNum, pageSize, type, keyWord, tagIds, sourceIds); + }*/ + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); + // 分页采用简单的from + size分页,适用数据量小的,了解更多分页方式可自行查阅资料 + searchSourceBuilder.from((pageNum - 1) * pageSize); // 从0开始 + searchSourceBuilder.size(pageSize); + + // 查询条件,只有查询关键字不为空才带查询条件 + if (StringUtils.isNoneBlank(keyWord)) { + // QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, "name", "desc"); + // searchSourceBuilder.query(queryBuilder); + } + + BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); + if(StringUtils.isNotBlank(type)) { // 案例类型 + boolQueryBuilder.must(QueryBuilders.termQuery("type", type)); + if (StringUtils.isNoneBlank(keyWord)){ + BoolQueryBuilder keywordQuery = QueryBuilders.boolQuery(); + keywordQuery.should(QueryBuilders.matchQuery("title", keyWord)); + keywordQuery.should(QueryBuilders.matchQuery("desct", keyWord)); + keywordQuery.should(QueryBuilders.matchQuery("fromName", keyWord)); + keywordQuery.should(QueryBuilders.matchQuery("tags", keyWord)); + keywordQuery.should(QueryBuilders.matchQuery("content_text", keyWord)); + boolQueryBuilder.must(keywordQuery); + } + }else { + if (StringUtils.isNoneBlank(keyWord)){ + boolQueryBuilder.should(QueryBuilders.matchQuery("title", keyWord)); //.boost(3) // 给name字段更高的权重 + boolQueryBuilder.should(QueryBuilders.matchQuery("desct", keyWord)); // description 默认权重 1 + boolQueryBuilder.should(QueryBuilders.matchQuery("fromName", keyWord)); + boolQueryBuilder.should(QueryBuilders.matchQuery("tags", keyWord)); + boolQueryBuilder.should(QueryBuilders.matchQuery("content_text", keyWord)); + boolQueryBuilder.minimumShouldMatch(1); // 至少一个should条件满足 + } + } + //选择标签或来源过滤 + if(StringUtils.isNotBlank(tagIds)){ + List tagIdList = Arrays.asList(tagIds.split(",")); + for(String tagId:tagIdList) { + boolQueryBuilder.must(QueryBuilders.termQuery("tagList.id", tagId)); + } + } + if(StringUtils.isNotBlank(sourceIds)){ + List sourIdList = Arrays.asList(sourceIds.split(",")); + for(String sourId:sourIdList) { + boolQueryBuilder.must(QueryBuilders.termQuery("fromid", sourId)); + } + } + searchSourceBuilder.query(boolQueryBuilder); + + + // 排序,根据ID倒叙 + searchSourceBuilder.sort("id", SortOrder.DESC); + // SearchRequest + SearchRequest searchRequest = new SearchRequest(); + searchRequest.source(searchSourceBuilder); + // 查询ES + SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); + SearchHits hits = searchResponse.getHits(); + // 获取总数 + Long total = hits.getTotalHits().value; + // 遍历封装列表对象 + List dataList = new ArrayList<>(); + SearchHit[] searchHits = hits.getHits(); + for (SearchHit searchHit : searchHits) { + Content entity = JSON.parseObject(searchHit.getSourceAsString(), Content.class); + // 清空一些前端用不上的字段 + entity.setTags(null); + entity.setImages(null); + entity.setImgList(null); + dataList.add(entity); + } + result.put("total", total); // 总记录数 + result.put("rows", dataList); // 数据体 + int totalPage = (int)(total % pageSize==0 ? total/pageSize : total/pageSize+1); + result.put("totalPage", totalPage); // 总页数 + }catch (Exception e) { + log.error("从es查询数据列表异常", e); + } + return result; + } + +} diff --git a/src/main/java/com/nbclass/activity/service/impl/WxServiceImpl.java b/src/main/java/com/nbclass/activity/service/impl/WxServiceImpl.java new file mode 100644 index 0000000..2d1b614 --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/impl/WxServiceImpl.java @@ -0,0 +1,46 @@ +package com.nbclass.activity.service.impl; + +import com.nbclass.activity.mapper.WXMapper; +import com.nbclass.activity.model.WeiXiUser; +import com.nbclass.activity.service.WxService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class WxServiceImpl implements WxService { + + @Autowired + private WXMapper wxMapper; + + + @Override + public int getOpenId(String openid) { + return wxMapper.getOpenId(openid); + } + + @Override + public void saveWxUser(WeiXiUser weiXiUser) { + wxMapper.saveWxUser(weiXiUser); + } + + @Override + public WeiXiUser getWxUser(String openid) { + return wxMapper.getWxUser(openid); + } + + @Override + public WeiXiUser getUser(String userId) { + return wxMapper.getUser(userId); + } + + @Override + public void updateBasicData(WeiXiUser weiXiUser) { + wxMapper.updateBasicData(weiXiUser); + } + + @Override + public WeiXiUser getUserBasicData(Long userId) { + return wxMapper.getUserBasicData(userId); + } + +} diff --git a/src/main/java/com/nbclass/activity/service/impl/WxUserServiceImpl.java b/src/main/java/com/nbclass/activity/service/impl/WxUserServiceImpl.java new file mode 100644 index 0000000..f34b7b4 --- /dev/null +++ b/src/main/java/com/nbclass/activity/service/impl/WxUserServiceImpl.java @@ -0,0 +1,145 @@ +package com.nbclass.activity.service.impl; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Resource; +import javax.transaction.Transactional; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.nbclass.activity.constant.Const; +import com.nbclass.activity.mapper.WxDepartmentMapper; +import com.nbclass.activity.mapper.WxUserMapper; +import com.nbclass.activity.model.WxDepartment; +import com.nbclass.activity.model.WxUser; +import com.nbclass.activity.service.WxUserService; +import com.nbclass.system.model.User; +import com.nbclass.system.service.UserService; +import com.nbclass.util.HttpUtils; +import com.nbclass.util.PasswordHelper; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class WxUserServiceImpl implements WxUserService { + + @Resource + private WxUserMapper wxUserMapper; + + @Resource + private UserService userService; + + @Resource + private WxDepartmentMapper deptMapper; + + + @Transactional + @Override + public void merge(List userList){ + if(userList == null || userList.size() == 0)return; + + List dbUserIds = wxUserMapper.getUserIds(); // 查询数据库已有的数据 + List existIds = new ArrayList(); + for (WxUser wxUser : userList) { + User account = new User(); + account.setUserId(wxUser.getName()); // 将姓名存储到userid字段 + account.setUsername(wxUser.getUserid()); // 企业微信userid,作为登录账号 + account.setPassword("password"); + account.setEmail(wxUser.getEmail()); + account.setPhone(wxUser.getMobile()); + account.setSex(StringUtils.isBlank(wxUser.getGender()) ? null : Integer.parseInt(wxUser.getGender())); + account.setStatus(1); + account.setPassword("szxgl.cn"); // 默认登录密码 + PasswordHelper.encryptPassword(account); + + existIds.add(wxUser.getUserid()); + if(dbUserIds.contains(wxUser.getUserid())){ + wxUserMapper.updateByUserId(wxUser); + }else{ + wxUserMapper.insertSelective(wxUser); + } + userService.updateQyWxUser(account); + } + // 删除当前企业微信不存在的用户 + if(existIds.size()>0)wxUserMapper.delOtherUsers(existIds); + } + + @Override + public void syncWxUser() { + try { + String url = Const.QYWX_API_DOMAIN + "/open/api/user/list?secretKey=yunlei.888"; + JSONObject jsonObject = HttpUtils.httpPost(url); + if(jsonObject !=null && jsonObject.getIntValue("ret") == 0){ + List userList = new ArrayList(); + JSONArray jsonArray = jsonObject.getJSONArray("list"); + if(jsonArray!=null && jsonArray.size() > 0){ + /* + for(int i=0; i list = new ArrayList(); + JSONArray jsonArray = jsonObject.getJSONArray("list"); + if(jsonArray!=null && jsonArray.size() > 0){ + for (Object object : jsonArray) { + WxDepartment wxDepartment = JSON.toJavaObject((JSONObject)object, WxDepartment.class); + list.add(wxDepartment); + } + } + if(list.size() > 0)deptMapper.insertBatch(list); + log.info("同步企业部门成功,部门数量为:"+list.size()); + }else{ + log.error("同步企业部门出错: "+jsonObject); + } + } catch (Exception e) { + log.error("同步企业部门异常", e); + } + } + + public static void main(String[] args) { + String jsonstr = "{\"id\":1,\"Name\":\"信广龙\",\"Order\":100000000,\"parentId\":5}"; + WxDepartment wxUser = JSON.toJavaObject(JSONObject.parseObject(jsonstr), WxDepartment.class); + System.out.println(JSON.toJSON(wxUser)); + } + +} diff --git a/src/main/java/com/nbclass/activity/task/ActivityRunner.java b/src/main/java/com/nbclass/activity/task/ActivityRunner.java new file mode 100644 index 0000000..d64ac40 --- /dev/null +++ b/src/main/java/com/nbclass/activity/task/ActivityRunner.java @@ -0,0 +1,25 @@ +package com.nbclass.activity.task; + +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * 启动时加载的任务 + * Created by Leon on 2019/12/11 15:10 + */ +@Component +@Order(1) +public class ActivityRunner implements ApplicationRunner { + + //@Resource + // private UserInfoService userInfoService; + + @SuppressWarnings("unused") + @Override + public void run(ApplicationArguments appArgs) throws Exception { + + } + +} diff --git a/src/main/java/com/nbclass/activity/task/MultiThreadScheduleTask.java b/src/main/java/com/nbclass/activity/task/MultiThreadScheduleTask.java new file mode 100644 index 0000000..cbbed20 --- /dev/null +++ b/src/main/java/com/nbclass/activity/task/MultiThreadScheduleTask.java @@ -0,0 +1,56 @@ +package com.nbclass.activity.task; + +import javax.annotation.Resource; + +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.nbclass.activity.service.WxUserService; + +import lombok.extern.slf4j.Slf4j; + +/** + * 定时任务 + * @author Leon + * @datetime 2020年6月2日 下午6:53:05 + */ +@Slf4j +@Component +@EnableScheduling // 1.开启定时任务 +@EnableAsync // 2.开启多线程/异步注解 +public class MultiThreadScheduleTask { + + @Resource + private WxUserService wxUserService; + + /** + * 定时同步企业微信用户 + */ + @Async + @Scheduled(cron="0 0 */6 * * ? ") // 每6小时运行一次 + public void syncWxUser() { + log.info("同步企业微信用户 ......"); + wxUserService.syncWxUser(); + } + + /** + * 定时同步企业微信部门 + */ + @Async + /* @Scheduled(fixedDelay = 1000*120) */ + @Scheduled(cron="0 0 1 * * ?") // 每天凌晨1点执行一次 + public void syncWxDepartment() { + log.info("同步企业微信部门 ......"); + wxUserService.syncWxDepartment(); + } + + /** + * 每天中午12点触发:0 0 12 * * ? + * 每天凌晨1点执行一次:0 0 1 * * ? + * 每隔1分钟执行一次:0 *\/1 * * * ? + */ + +} diff --git a/src/main/java/com/nbclass/aliyun/sdk/AliyunConstant.java b/src/main/java/com/nbclass/aliyun/sdk/AliyunConstant.java new file mode 100644 index 0000000..d81d7c4 --- /dev/null +++ b/src/main/java/com/nbclass/aliyun/sdk/AliyunConstant.java @@ -0,0 +1,40 @@ +package com.nbclass.aliyun.sdk; + +/** + * + * @author Leon + * @datetime 2019年4月12日 下午5:27:12 + */ +public class AliyunConstant { + + // 编码类型 + public static final String ENCODE_TYPE = "UTF-8"; + // 阿里云API签名方式,目前支持HMAC-SHA1 + public static final String ALGORITHM = "HmacSHA1"; + + // AccessKey ID + public static final String accessKeyId = "sSJ5t0yC1CaKhPJ4"; + // AccessKey Secret + public static final String accessKeySecret = "PsbdUTexU95BkiqO4ADELXpIaYdWGk"; + + // 访问域名 + public static final String access_doamin = "https://szxgl.oss-cn-shenzhen.aliyuncs.com/"; + // endpoint外网地址 + public static final String endpoint_ww = "oss-cn-shenzhen.aliyuncs.com"; + // endpoint内网地址 + public static final String endpoint_nw = "oss-cn-shenzhen-internal.aliyuncs.com"; + // 区域ID + public static final String regionId = "cn-shenzhen"; + // OSS区域 + public static final String ossLocation = "oss-cn-shenzhen"; + // OSS Bucket + public static final String ossBucketName = "szxgl"; + + + /** + * oss文件存放目录前缀 + */ + public static final String key_prefix = "xgl-cases/"; + + +} diff --git a/src/main/java/com/nbclass/aliyun/sdk/AliyunOSSUtils.java b/src/main/java/com/nbclass/aliyun/sdk/AliyunOSSUtils.java new file mode 100644 index 0000000..e5c1c07 --- /dev/null +++ b/src/main/java/com/nbclass/aliyun/sdk/AliyunOSSUtils.java @@ -0,0 +1,174 @@ +package com.nbclass.aliyun.sdk; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.util.Date; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.aliyun.oss.OSSClient; +import com.aliyun.oss.OSSClientBuilder; +import com.nbclass.exception.ParameterException; +import com.nbclass.exception.ServiceException; +import com.nbclass.util.CommonUtils; + +/** + * 阿里云OSS操作 + * @author leiyun + * @datetime 2017年1月17日 下午3:58:16 + */ +public class AliyunOSSUtils { + + private static Logger logger = LoggerFactory.getLogger(AliyunOSSUtils.class); + + public static void main(String[] args) throws Exception { + boolean flag = true; + deleteObject(AliyunConstant.key_prefix + "casetype-h5/attachments/202005/20200529104151_wsblg.jpg"); + if(flag)return; + File file = new File("D:\\Temp\\images\\2.jpg"); + byte[] bt = Files.readAllBytes(file.toPath()); + String imgurl = uploadBytes("test","oppo.jpg", bt); + System.out.println("imgurl="+imgurl); + } + + /** + * 删除文件 + * @param key + */ + public static void deleteObject(String key){ + if(StringUtils.isBlank(key)) { + throw new ParameterException("key不能为空"); + } + OSSClient ossClient = null; + try { + // 创建OSSClient实例 + OSSClientBuilder builder = new OSSClientBuilder(); + // 国内服务器使用内网上传到oss + if(CommonUtils.isNw()){ + // 使用内网节点 + ossClient = (OSSClient) builder.build(AliyunConstant.endpoint_nw, AliyunConstant.accessKeyId, AliyunConstant.accessKeySecret); + }else{ + // 使用外网节点 + ossClient = (OSSClient) builder.build(AliyunConstant.endpoint_ww, AliyunConstant.accessKeyId, AliyunConstant.accessKeySecret); + } + ossClient.deleteObject(AliyunConstant.ossBucketName, key); + } catch (RuntimeException e) { + logger.error("阿里云OSS上传文件出错", e); + throw new ServiceException("网络异常,请稍候再试!"); + } finally{ + if(ossClient!=null){ + // 关闭OSSClient。 + ossClient.shutdown(); + } + } + } + + /** + * 上传Byte数组 + * @param filename + * @param content + * @return + */ + public static String uploadBytes(String filename, byte[] content){ + return uploadBytes(null, filename, content); + } + + public static String uploadBytes(String key, String filename, byte[] content){ + String url = null; + OSSClient ossClient = null; + try { + // 创建OSSClient实例 + // ossClient = new OSSClient(AliyunConstant.endpoint_nw, AliyunConstant.accessKeyId, AliyunConstant.accessKeySecret); + OSSClientBuilder builder = new OSSClientBuilder(); + + // 国内服务器使用内网上传到oss + if(CommonUtils.isNw()){ + logger.info("上传文件到OSS, 使用内网节点"); + ossClient = (OSSClient) builder.build(AliyunConstant.endpoint_nw, AliyunConstant.accessKeyId, AliyunConstant.accessKeySecret); + }else{ + logger.info("上传文件到OSS, 使用外网节点"); + ossClient = (OSSClient) builder.build(AliyunConstant.endpoint_ww, AliyunConstant.accessKeyId, AliyunConstant.accessKeySecret); + + } + + if(StringUtils.isNotBlank(key)){ + key = AliyunConstant.key_prefix + key +"/"; + }else{ + key = AliyunConstant.key_prefix; + } + // String daydir = Tools.getSimpleDate(new Date(), "yyyyMM")+"/"; + String daydir = ""; + key = key + daydir + filename; + ossClient.putObject(AliyunConstant.ossBucketName, key, new ByteArrayInputStream(content)); + url = AliyunConstant.access_doamin + key; + } catch (RuntimeException e) { + logger.error("阿里云OSS上传文件出错", e); + throw new ServiceException("网络异常,请稍候再试!"); + } finally{ + if(ossClient!=null){ + // 关闭OSSClient。 + ossClient.shutdown(); + } + } + return url; + } + + /** + * 上传网络流 + * @param filename + * @param sourceUrl + * @return + */ + public static String uploadByURL(String filename, String sourceUrl){ + if(StringUtils.isBlank(sourceUrl))return null; + sourceUrl = sourceUrl.trim(); + if(!sourceUrl.startsWith("http://") && !sourceUrl.startsWith("https://"))return null; + String url = null; + OSSClient ossClient = null; + InputStream inputStream = null; + try { + // 创建OSSClient实例 + // ossClient = new OSSClient(AliyunConstant.endpoint_ww, AliyunConstant.accessKeyId, AliyunConstant.accessKeySecret); + OSSClientBuilder builder = new OSSClientBuilder(); + + // 国内服务器使用内网上传到oss + if(CommonUtils.isNw()){ + logger.info("上传文件到OSS, 使用内网节点"); + ossClient = (OSSClient) builder.build(AliyunConstant.endpoint_nw, AliyunConstant.accessKeyId, AliyunConstant.accessKeySecret); + }else{ + logger.info("上传文件到OSS, 使用外网节点"); + ossClient = (OSSClient) builder.build(AliyunConstant.endpoint_ww, AliyunConstant.accessKeyId, AliyunConstant.accessKeySecret); + + } + + String daydir = CommonUtils.getSimpleDate(new Date(), "yyyyMM")+"/"; + String key = AliyunConstant.key_prefix + daydir + filename; + // 上传网络流 + inputStream = new URL(sourceUrl).openStream(); + ossClient.putObject(AliyunConstant.ossBucketName, key, inputStream); + + url = AliyunConstant.access_doamin + key; + } catch (IOException | RuntimeException e) { + logger.error("阿里云OSS上传文件出错", e); + throw new ServiceException("网络异常,请稍候再试!"); + } finally{ + if(ossClient!=null){ + ossClient.shutdown(); + } + try { + if(inputStream!=null)inputStream.close(); + inputStream=null; + } catch (IOException e) { + e.printStackTrace(); + } + } + return url; + } + +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/component/MyErrorAttributes.java b/src/main/java/com/nbclass/component/MyErrorAttributes.java new file mode 100644 index 0000000..8b03b51 --- /dev/null +++ b/src/main/java/com/nbclass/component/MyErrorAttributes.java @@ -0,0 +1,27 @@ +package com.nbclass.component; + +import java.util.Map; + +import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.WebRequest; + +/** + * @version V1.0 + * @date 2018年7月11日 + * @author superzheng + */ +@Component +public class MyErrorAttributes extends DefaultErrorAttributes{ + + @Override + public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { + @SuppressWarnings("deprecation") + Map map = super.getErrorAttributes(webRequest, includeStackTrace); + @SuppressWarnings("unchecked") + Map ext = (Map)webRequest.getAttribute("ext", 0); + map.put("ext",ext); + return map; + } + +} diff --git a/src/main/java/com/nbclass/config/CorsConfig.java b/src/main/java/com/nbclass/config/CorsConfig.java new file mode 100644 index 0000000..49e6479 --- /dev/null +++ b/src/main/java/com/nbclass/config/CorsConfig.java @@ -0,0 +1,31 @@ +package com.nbclass.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 跨域请求配置 + * @author Leon + * @datetime 2019年4月3日 下午4:45:49 + */ +@Configuration +public class CorsConfig { + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry + .addMapping("/api/**") // 允许跨域请求的地址 + .allowedOrigins("*") // 允许任何域请求 + .allowedMethods("*") // 允许任何方法(post、get等) + .allowedHeaders("*") // 允许任何头 + .allowCredentials(true) + ; + } + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/nbclass/config/DateConverter.java b/src/main/java/com/nbclass/config/DateConverter.java new file mode 100644 index 0000000..ec30ec2 --- /dev/null +++ b/src/main/java/com/nbclass/config/DateConverter.java @@ -0,0 +1,21 @@ +package com.nbclass.config; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +@Component +public class DateConverter implements Converter { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + @Override + public Date convert(String source) { + try { + return sdf.parse(source); + } catch (ParseException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/com/nbclass/config/DefaultViewConfig.java b/src/main/java/com/nbclass/config/DefaultViewConfig.java new file mode 100644 index 0000000..e434883 --- /dev/null +++ b/src/main/java/com/nbclass/config/DefaultViewConfig.java @@ -0,0 +1,23 @@ +package com.nbclass.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 默认首页 + * @author Leon + * @datetime 2019年4月11日 下午5:14:24 + */ +@Configuration +public class DefaultViewConfig implements WebMvcConfigurer { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("forward:/index.html"); + registry.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设置为最高级,哪怕其他controller里有映射(也就是 /)那么根据优先级,也会先加载这个配置 + WebMvcConfigurer.super.addViewControllers(registry); + } + +} diff --git a/src/main/java/com/nbclass/enums/ResponseStatus.java b/src/main/java/com/nbclass/enums/ResponseStatus.java new file mode 100644 index 0000000..b36b6b2 --- /dev/null +++ b/src/main/java/com/nbclass/enums/ResponseStatus.java @@ -0,0 +1,33 @@ +package com.nbclass.enums; + +/** + * @version V1.0 + * @date 2018年7月13日 + * @author superzheng + */ +public enum ResponseStatus { + + /** + * 返回状态 + */ + SUCCESS(200, "操作成功!"), + FORBIDDEN(403, "您没有权限访问!"), + NOT_FOUND(404, "资源不存在!"), + ERROR(500, "服务器内部错误!"); + + private Integer code; + private String message; + + ResponseStatus(Integer code, String message) { + this.code = code; + this.message = message; + } + + public Integer getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/com/nbclass/exception/CommonExceptionAdvice.java b/src/main/java/com/nbclass/exception/CommonExceptionAdvice.java new file mode 100644 index 0000000..bf58a2c --- /dev/null +++ b/src/main/java/com/nbclass/exception/CommonExceptionAdvice.java @@ -0,0 +1,81 @@ +package com.nbclass.exception; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.nbclass.vo.base.Result; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.authz.AuthorizationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import com.alibaba.fastjson.JSONObject; +import com.nbclass.util.CoreConst; + +/** + * 实现spring注解进行异常统一处理 + * @author Leon + * @datetime 2019年3月5日 下午6:08:54 + */ +@ControllerAdvice +public class CommonExceptionAdvice { + + private static Logger logger = LoggerFactory.getLogger(CommonExceptionAdvice.class); + + @ResponseStatus(HttpStatus.OK) + @ExceptionHandler(Exception.class) + @ResponseBody + public void defaultErrorHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) { + response.setContentType("application/json;charset=UTF-8"); + response.setCharacterEncoding("UTF-8"); + String error = null; + try { + logger.info("RequestURL = "+request.getRequestURL()); + if(exception instanceof ParameterException || exception instanceof ServiceException || exception instanceof LogicalException){ + logger.info(exception.toString()); + }else if(exception instanceof org.springframework.web.multipart.MaxUploadSizeExceededException){ + error = "customer.file.upload.exceedlimit"; + }else{ + logger.error("系统异常: ", exception); + } + if(StringUtils.isBlank(error))error = exception.getMessage(); + + PrintWriter out = response.getWriter(); + if(StringUtils.isBlank(error)){ + response.setHeader("Content-Type", "text/html;charset=UTF-8"); + out.print("