From 9bcb19959eeb9da9bde2561e7278f6d0a55eb151 Mon Sep 17 00:00:00 2001 From: inleft <inleft@qq.com> Date: Wed, 09 Feb 2022 15:43:22 +0800 Subject: [PATCH] 管理后台代码初始化 --- snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/LogAnnotionOpTypeEnum.java | 107 _web/src/views/system/menu/index.vue | 187 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/exception/MailSendException.java | 48 _web/src/components/Search/index.less | 25 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/provider/CaptchaCacheServiceProvider.java | 72 _web/src/views/system/exception/404.vue | 17 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpPosServiceImpl.java | 121 _web/src/views/system/fileOnline/index.vue | 254 _web/src/utils/mixin.js | 76 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmp.java | 62 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/controller/SmsSenderController.java | 108 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/service/impl/SysPosServiceImpl.java | 202 snowy-base/snowy-gen/src/main/resources/template/Mapper.xml.vm | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/flag/FlagValueValidator.java | 64 _web/postcss.config.js | 5 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleServiceImpl.java | 358 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/controller/SysTimersController.java | 168 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/factory/LoginUserFactory.java | 154 snowy-base/snowy-gen/src/main/resources/template/Controller.java.vm | 148 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/jwt/JwtTokenUtil.java | 121 _web/src/views/userLoginReg/RegisterResult.vue | 50 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/entity/SysOauthUser.java | 105 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/SymbolConstant.java | 90 _web/src/components/IconSelector/IconSelector.vue | 86 _web/src/views/system/account/settings/securityItem/updPwd.vue | 115 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/date/DateValueValidator.java | 59 _web/src/components/ArticleListContent/index.js | 3 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/TimerTaskRunListener.java | 75 _web/src/components/tools/UserMenu.vue | 191 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/context/TenantDbNameHolder.java | 48 _web/src/components/Charts/smooth.area.less | 14 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleDataScopeServiceImpl.java | 88 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsVerifyEnum.java | 67 _web/src/api/modular/main/README.md | 1 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/timer/TimerTaskRunner.java | 45 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/redis/FastJson2JsonRedisSerializer.java | 90 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/PermissionException.java | 55 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/consts/GenConstant.java | 158 _web/src/components/Charts/chart.less | 13 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/CommonBaseTreeNode.java | 90 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/IpAddressUtil.java | 109 _web/src/assets/logo.svg | 203 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/enums/SysUserExceptionEnum.java | 90 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/param/SysTimersParam.java | 78 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/email/controler/EmailController.java | 122 _web/src/components/Trend/Trend.vue | 41 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/consts/TenantExpEnumConstant.java | 45 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/service/SysAreaService.java | 50 _web/src/components/StandardFormRow/index.js | 3 _web/.eslintrc.js | 77 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/SmsSender.java | 50 _web/src/components/UserSelect/index.js | 3 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpPosService.java | 80 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/result/SysNoticeReceiveResult.java | 104 _web/src/api/modular/system/areaManage.js | 16 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/enums/TimerJobStatusEnum.java | 54 _web/src/views/404.vue | 15 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/controller/SysRoleController.java | 191 snowy-main/src/main/java/vip/xiaonuo/SnowyServletInitializer.java | 19 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/dbid/SnowyDatabaseIdProvider.java | 64 _web/src/views/system/config/editForm.vue | 158 _web/src/components/Tree/Tree.jsx | 124 snowy-base/snowy-gen/src/main/resources/template/ServiceImpl.java.vm | 136 _web/src/views/system/dashboard/Analysis.vue | 385 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserRoleMapper.xml | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/DataScope.java | 39 _web/src/components/DescriptionList/DescriptionList.vue | 153 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpExtOrgPosMapper.java | 37 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserMapper.java | 52 _web/src/components/SettingDrawer/SettingDrawer.vue | 352 _web/src/views/main/blogarticle/addForm.vue | 256 _web/src/components/IconSelector/icons.js | 36 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/cryptogram/CryptogramConfigs.java | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/service/SysMenuService.java | 163 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/SysOpLogMapper.java | 37 _web/src/views/system/log/oplog/index.vue | 230 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/SysNoticeUserService.java | 82 _web/src/components/Exception/index.js | 2 _web/public/loading/loading.css | 1 _web/src/views/system/account/center/page/index.js | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/PermissionExceptionEnum.java | 80 _web/src/components/UserSelect/UserSelect.vue | 77 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/OauthSexEnum.java | 61 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/PermissionAop.java | 138 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/error/SnowyErrorAttributes.java | 70 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateormonth/DateOrMonthValueValidator.java | 67 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/resources/ApiResourceContext.java | 93 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/result/SysFileInfoResult.java | 83 _web/src/utils/permissions.js | 26 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpService.java | 98 _web/src/views/system/user/addForm.vue | 468 _web/src/components/Charts/Trend.vue | 82 _web/src/store/mutation-types.js | 18 _web/src/components/Dialog.js | 113 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/SysVisLogService.java | 65 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/SysNoticeService.java | 107 _web/src/components/tools/index.js | 0 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/controller/SysOnlineUserController.java | 80 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserRoleMapper.java | 37 _web/src/views/system/dict/dictdata/index.vue | 181 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/IndexController.java | 50 _web/src/views/system/notice/editForm.vue | 240 _web/webstorm.config.js | 3 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SpringSecurityConfig.java | 117 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/wrapper/SysUserWrapper.java | 36 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/prop/TencentSmsProperties.java | 64 _web/src/views/system/machine/index.vue | 118 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/group/RequestGroupContext.java | 67 .gitattributes | 4 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/StringDateTool.java | 51 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/LibreOfficeException.java | 46 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/impl/SysDictDataServiceImpl.java | 256 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/exception/TenantException.java | 42 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/SysNoticeMapper.java | 52 snowy-base/snowy-gen/src/main/resources/template/ExceptionEnum.java.vm | 64 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/time/TimeValueValidator.java | 59 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/impl/SysOpLogServiceImpl.java | 90 _web/.prettierrc | 5 _web/src/components/FooterToolbar/index.js | 4 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/param/SysPosParam.java | 77 _web/src/views/system/notice/detailForm.vue | 60 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsInfoParam.java | 83 _web/src/views/system/dict/dictdata/editForm.vue | 137 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/email/EmailConfigs.java | 68 _web/public/loading/option2/loading.svg | 1 snowy-base/snowy-gen/src/main/resources/template/XnMysql.sql.vm | 38 _web/src/layouts/UserLayout.vue | 153 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/controller/SysMachineController.java | 60 _web/src/components/NProgress/nprogress.less | 76 _web/src/views/system/org/index.vue | 232 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/security/JwtAuthenticationTokenFilter.java | 100 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/MappingCache.java | 65 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/param/SysFileInfoParam.java | 103 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/login/LoginContextHolder.java | 41 _web/src/components/global.less | 516 _web/src/api/modular/system/onlineUserManage.js | 29 snowy-base/snowy-gen/pom.xml | 39 _web/src/components/FooterToolbar/index.md | 48 snowy-main/src/main/java/vip/xiaonuo/modular/service/DatasourceExampleService.java | 89 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/month/MonthValueValidator.java | 60 _web/src/components/DescriptionList/index.js | 2 snowy-base/snowy-gen/src/main/resources/template/XnOracle.sql.vm | 38 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserRoleService.java | 88 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/LoginEmpInfo.java | 69 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/AopTargetUtil.java | 88 _web/src/layouts/RouteView.vue | 32 _web/src/components/Table/columnSetting.vue | 82 _web/src/views/system/onlineUser/index.vue | 124 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/ParamExceptionEnum.java | 64 snowy-main/src/main/resources/application-local.yml | 59 _web/babel.config.js | 28 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/xss/XssFilter.java | 57 _web/src/views/system/role/addForm.vue | 106 _web/src/components/ArticleListContent/ArticleListContent.vue | 89 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/service/SysPosService.java | 105 _web/src/components/MultiTab/MultiTab.vue | 163 _web/src/api/modular/system/README.md | 1 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/date/DateValue.java | 57 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/enums/QueryTypeEnum.java | 56 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/impl/SysOnlineUserServiceImpl.java | 109 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/entity/SysApp.java | 71 _web/src/api/modular/system/appManage.js | 92 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/controller/SysCodeGenerateConfigController.java | 94 snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/mapper/BlogArticleMapper.java | 37 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/param/SysConfigParam.java | 86 _web/src/components/NoticeIcon/NoticeIcon.vue | 142 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendSourceEnum.java | 58 _web/public/loading/loading.html | 1 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/entity/BaseEntity.java | 72 _web/src/views/system/exception/500.vue | 17 _web/src/views/gen/codeGenerate/index.vue | 263 _web/src/api/modular/system/dictDataManage.js | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/mapper/mapping/SysPosMapper.xml | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/wrapper/BaseWrapper.java | 47 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/prop/AliyunSmsProperties.java | 78 _web/src/views/system/file/index.vue | 256 README.md | 288 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/param/SysNoticeParam.java | 84 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/ErrorResponseData.java | 56 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/xss/XssHttpServletRequestWrapper.java | 91 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/impl/SysMachineServiceImpl.java | 89 _web/src/config/defaultSettings.js | 35 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/common/enums/BucketAuthEnum.java | 50 _web/src/layouts/index.js | 8 _web/src/components/tools/Breadcrumb.vue | 45 _web/src/api/modular/system/posManage.js | 86 _web/src/api/modular/system/userManage.js | 226 snowy-main/src/main/resources/application-prod.yml | 56 _web/src/views/main/blogarticle/index.vue | 400 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmpExtOrgPos.java | 62 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/result/InforMationColumnsResult.java | 58 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/RemoveRequestParamListener.java | 44 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsTypeEnum.java | 56 _web/src/components/CountDown/index.md | 34 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/abs/AbstractBaseExceptionEnum.java | 53 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/exception/enums/TenantExceptionEnum.java | 80 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/entity/SysSms.java | 92 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/entity/SysVisLog.java | 126 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/month/MonthValue.java | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/node/MenuBaseTreeNode.java | 86 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/SysVisLogMapper.java | 37 _web/src/components/SettingDrawer/SettingItem.vue | 38 _web/LICENSE | 21 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/entity/SysCodeGenerateConfig.java | 136 _web/src/components/AvatarList/Item.vue | 46 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/WrapperAop.java | 241 _web/src/views/system/account/settings/Binding.vue | 25 _web/src/views/system/account/center/Index.vue | 308 _web/src/config/router.config.js | 85 _web/src/components/GlobalFooter/index.js | 2 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/entity/SysTimers.java | 77 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/constant/ConstantContextHolder.java | 413 _web/src/components/Trend/index.js | 3 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleService.java | 177 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserDataScopeServiceImpl.java | 86 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserDataScopeMapper.xml | 5 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/enums/SysDictDataExceptionEnum.java | 70 _web/src/components/TextArea/style.less | 12 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/exp/AliyunFileServiceException.java | 70 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/mapping/SysVisLogMapper.xml | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dict/DictValue.java | 66 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/OauthPlatformEnum.java | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/impl/SysTimersServiceImpl.java | 216 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/service/impl/SysOrgServiceImpl.java | 546 _web/src/components/Table/index.js | 449 _web/src/components/tools/TwoStepCaptcha.vue | 89 _web/src/components/Charts/Liquid.vue | 67 _web/src/core/directives/action.js | 34 _web/src/layouts/BasicLayout.vue | 186 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUserDataScope.java | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/service/SysOrgService.java | 128 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/enums/SysDictTypeExceptionEnum.java | 75 _web/src/components/Trend/index.md | 45 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/TreeBuildFactory.java | 128 _web/src/views/system/notice/index.vue | 191 _web/src/components/Charts/ChartCard.vue | 120 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/DataScopeAop.java | 82 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/service/impl/SysMenuServiceImpl.java | 556 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/entity/SysDictType.java | 75 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/service/impl/SysAppServiceImpl.java | 265 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/JavaSqlTool.java | 87 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/MediaTypeConstant.java | 124 _web/src/components/SettingDrawer/themeColor.js | 24 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateormonth/DateOrMonthValue.java | 57 _web/src/router/index.js | 19 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/PageFactory.java | 80 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/fieldfill/CustomMetaObjectHandler.java | 102 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/SysDictDataMapper.java | 50 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/CacheOperator.java | 118 _web/src/components/Charts/TagCloud.vue | 113 _web/src/components/CountDown/index.js | 3 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendStatusEnum.java | 66 _web/src/views/system/dict/index.vue | 168 snowy-base/snowy-gen/src/main/resources/template/index.vue.vm | 379 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/enums/exp/SysTimersExceptionEnum.java | 74 _web/src/views/userLoginReg/Login.vue | 367 _web/src/views/system/config/index.vue | 184 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/ResourceCollectListener.java | 78 _web/src/api/modular/system/timersManage.js | 127 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/mapping/SysCodeGenerateConfigMapper.xml | 5 snowy-base/snowy-gen/src/main/resources/template/Manage.js.vm | 86 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/impl/SysSmsInfoServiceImpl.java | 193 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/mothordatetime/MonthOrDateTimeValueValidator.java | 67 _web/src/components/TagSelect/index.jsx | 113 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/AopSortConstant.java | 72 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/mapper/mapping/SysOrgMapper.xml | 5 snowy-main/src/main/java/vip/xiaonuo/modular/model/AbModel.java | 45 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/param/BaseParam.java | 272 _web/src/views/system/menu/editForm.vue | 627 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/controller/SysConfigController.java | 138 snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/controller/BlogArticleController.java | 148 snowy-base/snowy-core/pom.xml | 188 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/login/LoginContext.java | 168 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/local/prop/LocalFileProperties.java | 48 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/enums/SysOauthExceptionEnum.java | 70 _web/src/components/Menu/index.js | 2 snowy-base/snowy-gen/README.md | 1 _web/src/views/system/account/settings/Security.vue | 70 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/scanner/ApiResourceScanner.java | 207 _web/src/layouts/BlankLayout.vue | 16 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/AdminTypeEnum.java | 56 _web/src/layouts/Iframe.vue | 29 _web/src/components/Editor/WangEditor.vue | 126 _web/src/components/verifition/utils/ase.js | 11 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/local/LocalFileOperator.java | 187 _web/src/views/system/app/index.vue | 194 _web/src/views/system/timers/addForm.vue | 126 _web/src/views/system/user/index.vue | 336 _web/src/utils/util.js | 67 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpMapper.xml | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/context/TenantCodeHolder.java | 48 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/service/impl/SysAreaServiceImpl.java | 59 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleMenuService.java | 77 _web/src/views/system/timers/index.vue | 196 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpMapper.java | 37 .gitignore | 51 _web/src/views/system/noticeReceived/detailForm.vue | 75 snowy-main/src/test/java/vip/xiaonuo/core/BaseJunit.java | 67 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/service/SysConfigService.java | 99 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuWeightEnum.java | 56 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/mapper/SysOauthMapper.java | 37 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/param/SysAreaParam.java | 102 _web/src/store/getters.js | 18 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/error/GlobalExceptionHandler.java | 371 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/result/SysMachineResult.java | 172 _web/.gitignore | 3 _web/src/utils/routeConvert.js | 30 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/prop/AliyunOssProperties.java | 55 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/page/PageResult.java | 118 _web/src/components/SettingDrawer/index.js | 2 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/mapping/CodeGenerateMapper.xml | 241 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/service/SysFileInfoService.java | 168 snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/param/BlogArticleParam.java | 169 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/consts/GenExpEnumConstant.java | 59 snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/enums/BlogArticleExceptionEnum.java | 64 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/ServerExceptionEnum.java | 75 _web/.env | 3 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/mapping/SysOpLogMapper.xml | 5 _web/src/views/system/config/addForm.vue | 130 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/unique/TableUniqueValue.java | 88 _web/src/api/modular/system/machineManage.js | 15 _web/tests/unit/.eslintrc.js | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/AliyunFileOperator.java | 213 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/OauthCache.java | 72 _web/src/utils/utils.less | 50 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/controller/SysDictDataController.java | 149 _web/src/views/system/README.md | 1 _web/src/components/MultiTab/events.js | 2 snowy-base/snowy-gen/src/main/resources/template/Param.java.vm | 74 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/mapper/mapping/SysSmsMapper.xml | 6 _web/src/core/use.js | 30 _web/src/views/system/dashboard/TestWork.vue | 117 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/RequestTypeExceptionEnum.java | 70 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/flag/FlagValue.java | 62 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateordatetime/DateOrDateTimeValue.java | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/LogSuccessStatusEnum.java | 56 snowy-base/snowy-gen/src/main/resources/template/Mapper.java.vm | 37 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpServiceImpl.java | 164 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/email/enums/SysEmailExceptionEnum.java | 80 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/SysOpLogService.java | 65 snowy-main/src/test/java/vip/xiaonuo/core/Test2.java | 41 _web/src/utils/filter.js | 101 snowy-main/pom.xml | 61 _web/src/components/verifition/utils/util.js | 52 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/result/InformationResult.java | 58 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendExceptionEnum.java | 75 _web/src/components/Charts/RankList.vue | 77 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/Wrapper.java | 47 _web/src/views/system/org/addForm.vue | 156 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/result/SysOnlineFileInfoResult.java | 187 snowy-main/src/main/resources/application-dev.yml | 56 _web/src/components/SettingDrawer/settingConfig.js | 46 _web/src/components/GlobalHeader/GlobalHeader.vue | 165 _web/src/components/MultiTab/index.less | 25 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/MybatisConfig.java | 95 _web/src/views/system/log/oplog/details.vue | 137 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PageUtil.java | 61 _web/src/views/system/account/settings/AvatarModal.vue | 183 _web/src/components/AvatarList/index.less | 60 _web/src/components/Charts/MiniBar.vue | 57 _web/src/views/system/dict/addForm.vue | 106 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/enums/TableFilteredFieldsEnum.java | 63 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/mapping/SysDictTypeMapper.xml | 5 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/ResourceCache.java | 63 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/entity/SysNotice.java | 102 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRoleMenu.java | 57 _web/.travis.yml | 7 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserMapper.xml | 33 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/DbIdEnum.java | 79 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/msign/impl/MapBasedMultiSignManager.java | 88 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cryptogram/keypair.java | 51 _web/src/components/Ellipsis/index.js | 3 _web/src/views/system/exception/403.vue | 17 _web/src/api/modular/system/loginManage.js | 125 snowy-base/pom.xml | 24 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/HttpServletUtil.java | 72 _web/src/components/Charts/MiniProgress.vue | 75 _web/src/api/modular/system/menuManage.js | 114 _web/src/assets/logo.png | 0 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/base/AbstractRedisCacheOperator.java | 103 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/mapper/mapping/SysMenuMapper.xml | 5 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/controller/SysPosController.java | 148 _web/src/views/userLoginReg/Register.vue | 322 _web/src/api/modular/gen/codeGenerateManage.js | 106 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserDataScopeMapper.java | 37 _web/src/views/system/account/center/page/Project.vue | 215 snowy-main/src/main/resources/logback-spring.xml | 120 _web/src/views/system/account/settings/Index.vue | 155 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/mapper/SysSmsMapper.java | 38 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/FileOperator.java | 171 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/SysLoginUser.java | 217 _web/src/views/system/dict/editForm.vue | 128 _web/src/core/icons.js | 11 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/param/SysDictTypeParam.java | 78 _web/src/store/index.js | 32 _web/src/api/modular/main/blogarticle/blogArticleManage.js | 86 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/BusinessLog.java | 51 _web/src/views/system/log/vislog/details.vue | 57 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/ServiceException.java | 56 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/enums/SysOrgExceptionEnum.java | 90 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/context/XnVelocityContext.java | 102 _web/src/components/CountDown/CountDown.vue | 102 _web/src/views/system/log/vislog/index.vue | 230 _web/src/components/AvatarList/List.vue | 99 _web/src/components/Menu/SideMenu.vue | 61 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleMapper.xml | 5 _web/src/components/Ellipsis/index.md | 38 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/DocumentFormatEnum.java | 130 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/controller/SysLoginController.java | 146 _web/src/views/system/pos/editForm.vue | 129 _web/src/components/GlobalHeader/index.js | 2 _web/src/components/tools/Logo.vue | 53 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/entity/SysNoticeUser.java | 69 snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/mapper/mapping/BlogArticleMapper.xml | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/dbs/CurrentDataSourceContext.java | 67 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/ConstantsInitListener.java | 121 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/entity/SysArea.java | 125 _web/public/index.html | 37 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/param/SysUserParam.java | 146 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleDataScopeMapper.xml | 5 _web/src/views/system/account/settings/BaseSetting.vue | 216 _web/src/components/IconSelector/README.md | 48 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserService.java | 275 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/impl/CodeGenerateServiceImpl.java | 364 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/entity/SysDictData.java | 79 _web/src/assets/icons/bx-analyse.svg | 1 _web/src/views/main/blogarticle/editForm.vue | 298 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/param/SysOnlineUserParam.java | 83 _web/src/components/IconSelector/index.js | 2 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/CryptogramUtil.java | 139 _web/src/App.vue | 47 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/system/SystemContextHolder.java | 41 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/enums/AliyunSmsResultEnum.java | 148 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/AuthExceptionEnum.java | 115 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/SmsSenderService.java | 70 _web/src/views/system/notice/addForm.vue | 213 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/service/SysOauthService.java | 62 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/util/DownloadUtil.java | 120 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsVerifyParam.java | 64 _web/src/views/main/README.md | 1 _web/src/components/tools/HeadInfo.vue | 67 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/controller/SysAreaController.java | 63 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/sqlfilter/DemoProfileSqlInterceptor.java | 81 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/LibreOfficeUtil.java | 141 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/common/exp/FileServiceException.java | 42 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/controller/SysFileInfoController.java | 180 snowy-base/snowy-core/README.md | 3 pom.xml | 270 _web/src/store/modules/async-router.js | 33 _web/src/utils/helper/permission.js | 51 _web/src/views/system/timers/editForm.vue | 151 _web/src/components/xnComponents/XDown.vue | 54 snowy-base/snowy-system/README.md | 1 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleMenuServiceImpl.java | 88 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/impl/HutoolTimerExeServiceImpl.java | 82 snowy-base/snowy-gen/src/main/resources/template/editForm.vue.vm | 274 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/MailSender.java | 54 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUser.java | 136 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/entity/SysMenu.java | 153 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/StatusExceptionEnum.java | 75 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/VisLogTypeEnum.java | 56 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/service/AuthService.java | 144 _web/src/components/TextArea/index.jsx | 69 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/entity/TenantInfo.java | 95 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/entity/SysOrg.java | 90 _web/src/components/tools/LangSelect.vue | 46 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/time/TimeValue.java | 57 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/exp/AliyunSmsException.java | 47 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/Permission.java | 52 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/SysTimersService.java | 126 _web/src/views/system/file/detailForm.vue | 99 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserServiceImpl.java | 572 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/param/SysCodeGenerateConfigParam.java | 138 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/entity/SysPos.java | 80 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/enums/CodeGenerateExceptionEnum.java | 75 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/ExpEnumType.java | 56 _web/src/components/NoticeIcon/index.js | 2 _web/src/utils/device.js | 33 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpPosMapper.xml | 5 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/controller/CodeGenerateController.java | 160 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/DemoException.java | 46 _web/src/components/PageHeader/PageHeader.vue | 202 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/factory/SysUserFactory.java | 80 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/enums/SysCodeGenerateConfigExceptionEnum.java | 64 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/controller/SysAppController.java | 148 LICENSE | 53 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/impl/SmsSenderServiceImpl.java | 122 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/system/SystemContext.java | 142 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/CommonStatusEnum.java | 80 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/sms/AliyunSmsConfigs.java | 63 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/AuthException.java | 54 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/group/RequestParamIdContext.java | 69 _web/src/views/system/dashboard/Workplace.vue | 526 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/mapper/mapping/SysAreaMapper.xml | 5 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/NoticeUserStatusEnum.java | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsSendParam.java | 70 _web/src/components/MultiTab/index.js | 40 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/mapper/mapping/SysAppMapper.xml | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/ResponseUtil.java | 72 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/SimpleMailSender.java | 96 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/factory/LogTaskFactory.java | 133 _web/src/components/Result/index.js | 2 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/JavaEffTool.java | 54 _web/src/utils/request.js | 96 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/service/SysAppService.java | 119 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/mapper/mapping/SysFileInfoMapper.xml | 5 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/LogManager.java | 178 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/result/SysDictTreeNode.java | 80 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/enums/SysFileLocationEnum.java | 64 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/datetime/DateTimeValue.java | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/controller/SysMenuController.java | 177 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/impl/SysNoticeServiceImpl.java | 257 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRoleDataScope.java | 57 _web/src/views/system/fileOnline/onlineEditForm.vue | 91 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/param/SysEmpParam.java | 79 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/mapper/SysTimersMapper.java | 40 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateordatetime/DateOrDateTimeValueValidator.java | 65 _web/.editorconfig | 39 _web/jest.config.js | 23 _web/src/api/modular/system/configManage.js | 85 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/WrapperExceptionEnum.java | 75 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PastTimeFormatUtil.java | 173 _web/src/api/modular/system/dictManage.js | 85 _web/src/components/Exception/type.js | 19 _web/package.json | 88 _web/.env.preview | 3 _web/src/components/Menu/menu.js | 177 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/context/LoginContextSpringSecurityImpl.java | 291 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/impl/SysVisLogServiceImpl.java | 88 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/param/TableField.java | 72 _web/src/components/xnComponents/EditorDiv.vue | 95 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/mapper/SysConfigMapper.java | 39 _web/src/components/verifition/Verify.vue | 473 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/CacheConfig.java | 106 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/service/impl/SysOauthServiceImpl.java | 206 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/SpringSecurityConstant.java | 72 _web/src/views/system/sms/index.vue | 163 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/param/XnCodeGenParam.java | 111 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserRoleServiceImpl.java | 106 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/TimerExeService.java | 61 _web/src/api/modular/system/roleManage.js | 141 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/entity/SysOpLog.java | 164 snowy-main/src/main/resources/banner.txt | 9 _web/src/views/system/fileOnline/detailForm.vue | 99 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/service/TenantInfoService.java | 87 snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/entity/BlogArticle.java | 165 snowy-main/src/test/java/vip/xiaonuo/core/Test.java | 39 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/enums/SysPosExceptionEnum.java | 80 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/result/SysEmpInfo.java | 69 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/param/CodeGenerateParam.java | 111 _web/src/components/Charts/TransferBar.vue | 64 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/enums/SysConfigExceptionEnum.java | 90 _web/src/components/TagSelect/TagSelectOption.jsx | 45 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/RequestNoFilter.java | 67 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/enums/SysNoticeExceptionEnum.java | 80 _web/src/components/StandardFormRow/StandardFormRow.vue | 122 _web/src/components/Editor/QuillEditor.vue | 82 _web/src/views/system/menu/addForm.vue | 567 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/consts/SysExpEnumConstant.java | 124 _web/src/components/Search/GlobalSearch.jsx | 63 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/TencentSmsSender.java | 113 snowy-main/src/main/java/vip/xiaonuo/modular/controller/DatasourceExampleController.java | 76 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/DataSourceConfig.java | 90 _web/public/loading/option2/loading.css | 1 _web/src/components/index.js | 72 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/WebMvcConfig.java | 158 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/SysOnlineUserService.java | 57 _web/src/components/FooterToolbar/index.less | 23 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleMenuMapper.java | 37 _web/src/components/FooterToolbar/FooterToolBar.vue | 30 _web/src/assets/welcome.png | 0 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleMenuMapper.xml | 5 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/param/RequestParamContext.java | 89 _web/src/views/system/index/welcome.vue | 15 _web/src/store/modules/user.js | 172 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/SexEnum.java | 61 _web/src/api/modular/system/smsManage.js | 43 _web/src/components/Trend/index.less | 42 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/web/SnowyRequestResponseBodyMethodProcessor.java | 81 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/SysNoticeUserMapper.java | 37 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/requestno/RequestNoContext.java | 66 _web/src/views/system/pos/addForm.vue | 106 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/result/SysOnlineUserResult.java | 81 _web/src/views/system/fileOnline/previewForm.vue | 96 _web/src/views/system/role/index.vue | 156 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpExtOrgPosPosServiceImpl.java | 157 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/controller/SysUserController.java | 281 _web/src/api/modular/system/noticeReceivedManage.js | 15 snowy-base/snowy-gen/src/main/resources/template/addForm.vue.vm | 216 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/param/SysOpLogParam.java | 127 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuOpenTypeEnum.java | 66 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/security/entrypoint/JwtAuthenticationEntryPoint.java | 99 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/LoginMenuTreeNode.java | 111 snowy-base/snowy-system/src/main/resources/META-INF/spring.factories | 4 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleMapper.java | 37 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/JoinPointUtil.java | 73 _web/src/core/lazy_lib/components_use.js | 111 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/mapper/SysAreaMapper.java | 38 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/DataScopeTypeEnum.java | 71 _web/src/router/generator-routers.js | 261 _web/src/core/lazy_use.js | 27 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/NamingConTool.java | 72 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateortime/DateOrTimeValue.java | 57 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/sms/TencentSmsConfigs.java | 64 _web/src/views/system/file/previewForm.vue | 60 _web/src/views/system/account/center/page/Article.vue | 91 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/param/SysMenuParam.java | 148 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/RequestMethodExceptionEnum.java | 70 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/controller/SysOauthController.java | 77 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/SuccessResponseData.java | 46 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/controller/SysLogController.java | 135 _web/src/views/gen/codeGenerate/addForm.vue | 316 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/tasks/RefreshConstantsTaskRunner.java | 71 _web/src/views/system/app/editForm.vue | 108 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmpPos.java | 57 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/param/SysVisLogParam.java | 97 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/CodeGenerateMapper.java | 58 snowy-base/snowy-system/pom.xml | 75 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/param/SysDictDataParam.java | 84 _web/.env.development | 3 _web/src/components/AvatarList/index.md | 64 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/UserCache.java | 57 _web/src/components/Menu/menu.render.js | 156 _web/src/components/NumberInfo/index.less | 55 _web/src/components/Charts/MiniArea.vue | 56 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/tasks/SystemOutTaskRunner.java | 44 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/enums/SysAppExceptionEnum.java | 85 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/SysCodeGenerateConfigService.java | 90 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/mapper/mapping/SysConfigMapper.xml | 6 _web/src/utils/domUtil.js | 19 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserDataScopeService.java | 77 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/util/OnlineDocumentUtil.java | 246 _web/src/components/AvatarList/index.js | 4 _web/src/views/system/role/editForm.vue | 128 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/constant/ConstantContext.java | 78 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/mapper/SysMenuMapper.java | 37 _web/src/components/PageLoading/index.jsx | 106 _web/src/components/verifition/utils/axios.js | 30 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUserRole.java | 57 _web/public/logo.png | 0 _web/src/views/system/role/roleMenuForm.vue | 184 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/CodeGenerateService.java | 107 _web/vue.config.js | 123 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/ExpEnumConstant.java | 89 _web/public/loading/option2/html_code_segment.html | 5 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/controller/SysNoticeController.java | 148 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/mapping/SysNoticeUserMapper.xml | 5 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/mapper/SysPosMapper.java | 37 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/druid/DruidProperties.java | 188 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/UaUtil.java | 96 snowy-main/src/main/resources/application.yml | 76 _web/src/views/system/dashboard/Monitor.vue | 15 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/mapping/SysDictDataMapper.xml | 17 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/controller/SysDictTypeController.java | 172 _web/src/components/DepartmentSelect/DepartmentSelect.vue | 48 _web/src/api/modular/system/logManage.js | 87 _web/src/components/NumberInfo/index.js | 3 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/factory/LogFactory.java | 196 _web/config/plugin.config.js | 46 _web/src/views/system/email/index.vue | 178 _web/src/components/Charts/Bar.vue | 62 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/param/SysOrgParam.java | 78 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/mothordatetime/MonthOrDateTimeValue.java | 57 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/exp/TencentFileServiceException.java | 70 _web/src/views/system/account/settings/Custom.vue | 75 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/MailSenderConfig.java | 59 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRole.java | 79 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/NoticeStatusEnum.java | 67 _web/src/layouts/PageView.vue | 177 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/AliyunSmsSender.java | 180 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/mapper/SysAppMapper.java | 37 _web/src/core/bootstrap.js | 31 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/base/AbstractMemoryCacheOperator.java | 105 _web/src/views/gen/codeGenerate/indexConfig.vue | 230 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/SysDictTypeMapper.java | 37 _web/src/components/GlobalFooter/GlobalFooter.vue | 46 _web/src/views/system/user/editForm.vue | 494 snowy-main/src/main/docker/docker-assembly.xml | 10 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/FileConfig.java | 72 _web/src/api/modular/system/fileManage.js | 115 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/mapping/SysNoticeMapper.xml | 30 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/validate/UniqueValidateParam.java | 80 _web/src/utils/axios.js | 35 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/enums/SysFileInfoExceptionEnum.java | 110 _web/src/components/DepartmentSelect/index.js | 3 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/oauth/OauthConfigs.java | 52 _web/src/components/NumberInfo/index.md | 43 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/enums/SysRoleExceptionEnum.java | 75 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/AopConfig.java | 86 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/impl/SysNoticeUserServiceImpl.java | 89 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpExtOrgPosMapper.xml | 5 _web/src/views/system/org/editForm.vue | 178 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/consts/TenantConstants.java | 55 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/ResponseData.java | 99 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/exp/TencentSmsException.java | 47 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/SysCodeGenerateConfigMapper.java | 37 _web/src/components/Result/Result.vue | 109 _web/src/views/system/app/addForm.vue | 91 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/node/BaseTreeNode.java | 64 _web/src/permission.js | 113 snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/service/impl/BlogArticleServiceImpl.java | 196 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/YesOrNotEnum.java | 57 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/util/Util.java | 119 _web/src/components/NumberInfo/NumberInfo.vue | 54 _web/src/views/system/user/userRoleForm.vue | 117 _web/src/views/system/user/userOrgForm.vue | 150 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/CommonConstant.java | 134 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/mapper/SysFileInfoMapper.java | 40 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/context/SystemContextImpl.java | 218 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SwaggerConfig.java | 124 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/entity/SysFileInfo.java | 93 _web/src/components/Charts/MiniSmoothArea.vue | 40 _web/src/components/Ellipsis/Ellipsis.vue | 64 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/msign/MultiSignManager.java | 46 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleDataScopeMapper.java | 37 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/SysSmsInfoService.java | 84 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SmsSenderConfig.java | 65 _web/.browserslistrc | 3 _web/src/components/Charts/Radar.vue | 68 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateortime/DateOrTimeValueValidator.java | 65 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/mapper/SysOrgMapper.java | 37 _web/src/components/PageHeader/index.js | 2 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpExtOrgPosService.java | 90 _web/src/api/modular/system/orgManage.js | 100 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/model/SendMailParam.java | 52 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpPosMapper.java | 37 _web/src/store/modules/permission.js | 77 snowy-main/src/main/java/vip/xiaonuo/SnowyApplication.java | 23 _web/src/components/xnComponents/XCard.vue | 16 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/AntdBaseTreeNode.java | 87 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/enums/SysMenuExceptionEnum.java | 120 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dict/DictValueValidator.java | 57 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/entity/CodeGenerate.java | 100 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/service/impl/SysFileInfoServiceImpl.java | 540 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/params/TenantInfoParam.java | 78 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuTypeEnum.java | 61 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/SysMachineService.java | 45 _web/src/components/verifition/Verify/VerifyPoints.vue | 260 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/service/impl/AuthServiceImpl.java | 382 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/RequestMethodException.java | 48 _web/src/api/modular/gen/sysCodeGenerateConfigManage.js | 29 _web/src/views/gen/codeGenerate/editForm.vue | 327 _web/src/views/system/account/center/page/App.vue | 113 snowy-base/README.md | 1 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/datetime/DateTimeValueValidator.java | 59 _web/src/components/_util/util.js | 46 _web/src/views/system/noticeReceived/index.vue | 136 snowy-base/snowy-gen/src/main/resources/template/Service.java.vm | 97 _web/src/views/system/role/roleOrgForm.vue | 195 _web/src/components/Table/index.less | 54 _web/src/components/index.less | 6 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/mapper/mapping/SysOauthMapper.xml | 5 _web/src/views/system/dict/dictdata/addForm.vue | 123 _web/src/components/Table/README.md | 340 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/LogicTypeEnum.java | 44 _web/src/utils/onlyofficeUtil.js | 22 snowy-main/src/test/sql/test.sql | 19 _web/src/api/modular/system/noticeManage.js | 85 _web/src/components/verifition/Verify/VerifySlide.vue | 374 _web/jsconfig.json | 11 snowy-base/snowy-gen/src/main/resources/template/entity.java.vm | 70 _web/src/components/tools/DetailList.vue | 5 snowy-main/README.md | 3 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/jwt/JwtPayLoad.java | 62 _web/src/api/modular/system/emailManage.js | 29 _web/src/components/Exception/ExceptionPage.vue | 130 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/entity/SysConfig.java | 87 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/unique/TableUniqueValueValidator.java | 122 _web/src/views/system/pos/index.vue | 186 _web/src/views/system/account/settings/Notification.vue | 25 _web/src/store/modules/app.js | 129 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/BusinessLogAop.java | 106 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/validator/SnowyValidator.java | 118 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleDataScopeService.java | 77 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/SysDictTypeService.java | 128 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/SnowyAuthority.java | 48 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/param/SysAppParam.java | 70 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/service/impl/SysConfigServiceImpl.java | 210 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/SysDictDataService.java | 137 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/result/SysUserResult.java | 109 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/prop/TenCosProperties.java | 53 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/result/SysNoticeDetailResult.java | 109 snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/service/BlogArticleService.java | 97 _web/src/main.js | 28 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/mapper/mapping/SysTimersMapper.xml | 5 _web/public/avatar2.jpg | 0 snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/impl/SysCodeGenerateConfigServiceImpl.java | 159 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/ExpEnumCodeFactory.java | 54 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/impl/SysDictTypeServiceImpl.java | 266 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/controller/SysOrgController.java | 169 _web/src/utils/applocation.js | 11 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/TenFileOperator.java | 262 _web/src/views/system/area/index.vue | 130 snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PoiUtil.java | 160 snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/param/SysRoleParam.java | 93 795 files changed, 75,977 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..53e0ee6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.js linguist-language=java +*.css linguist-language=java +*.html linguist-language=java +*.btl linguist-language=java \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23461b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# Compiled class file +*.class +*.iml +*.idea +target/ +logs/ + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*velocity.log* + +# Eclipse # +.classpath +.project +.settings/ + +.DS_Store + +_dockerCerts/ + +.factorypath + +node_modules/ +dist/ +package-lock.json +yarn.lock + +rebel.xml + +!DmJdbcDriver18.jar +!kingbase8-8.6.0.jar + +_images +_sql \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d6aac6c --- /dev/null +++ b/LICENSE @@ -0,0 +1,53 @@ +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.md b/README.md index 7394361..3342c91 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,288 @@ -## blog-admin +<div align="center"> + <p align="center"> + <img src="./_web/public/logo.png" height="150" alt="logo"/> + </p> +</div> -blog的后端代码 +### 框架介绍 +<div><h5>Snowy是一款基于国产密码算法后台权限管理系统,其中采用了SM2、SM3、SM4及签名验签,软件层面完全符合等保测评要求,让更多的人认识密码,使用密码。技术框架与密码结合,让前后分离“密”不可分。</h5></div> +<div><h4>结合SpringBoot+AntDesignVue开发,注释丰富,代码简洁。适配国产数据库(金仓、达梦)、主流数据库Mysql、Oracle、Mssql、Postgresql,小诺的产品一致追求简洁干净,一套代码搞定!同时支持国产中间件部署、麒麟操作系统、Windows、Linux部署使用。</h4></div> +<div align="center"><h5 align="center">Snowy谐音“小诺”,恰应小诺团队名称;意思为”下雪的、纯洁的“,寓意框架追求简洁至上,大道至简。</h5></div> + +<p align="center"> + <p align="center"> + <a href="https://gitee.com/xiaonuobase/snowy"> + <img src="https://gitee.com/xiaonuobase/snowy/badge/star.svg?theme=dark" alt="Gitee star"> + </a> + <a href="https://gitee.com/xiaonuobase/snowy"> + <img src="https://gitee.com/xiaonuobase/snowy/badge/fork.svg?theme=dark" alt="Gitee fork"> + </a> + <a href="https://www.antdv.com/docs/vue/introduce-cn/"> + <img src="https://img.shields.io/badge/vue-2.x-blue.svg" alt="bootstrap"> + </a> + <a href="https://www.antdv.com/docs/vue/introduce-cn/"> + <img src="https://img.shields.io/badge/vue--ant--design-1.5.6-blue.svg" alt="bootstrap"> + </a> + <a href="http://spring.io/projects/spring-boot"> + <img src="https://img.shields.io/badge/spring--boot-2.3.1-green.svg" alt="spring-boot"> + </a> + <a href="http://mp.baomidou.com"> + <img src="https://img.shields.io/badge/mybatis--plus-3.3.2-blue.svg" alt="mybatis-plus"> + </a> + <a href="./LICENSE"> + <img src="https://img.shields.io/badge/license-Apache%202-red" alt="license Apache 2.0"> + </a> + </p> +</p> + +### 快速启动 + +您的开发电脑需要安装:NodeJs(14.x)、npm或yarn(最新版)建议使用yarn、Mysql5.7、Jdk1.8、Maven3.6.3(最新版)、开发工具推荐idea + +* 启动前端:打开_web文件夹,进行依赖下载,运行npm install或yarn命令,再运行npm run serve或 yarn run serve +* 启动后端:打开application-local中配置数据库信息,运行SnowyApplication类即可启动 +* 浏览器访问:http://localhost:81 (默认前端端口为:81,后端端口为:82) + +### 快速链接 +* 演示地址(superAdmin/123456):https://snowy.xiaonuo.vip +* 在线文档:https://doc.xiaonuo.vip +* layui单体版本:https://gitee.com/xiaonuobase/snowy-layui +* vue前后分离版本:https://gitee.com/xiaonuobase/snowy +* cloud微服务前后分离版本:https://gitee.com/xiaonuobase/snowy-cloud +* 我们的其他产品线同样开源,如需关注最新动态可加入QQ群聊探讨:[732230670](https://wpa.qq.com/msgrd?v=3&uin=732230670&_blank) +* 如果我们的产品能满足您的需求,很期待您给我们右上角点个 star + +<img src="https://pan.xiaonuo.vip/?explorer/share/fileOut&shareID=7qwJHENw&path=%7BshareItemLink%3A7qwJHENw%7D%2F"/> + +### 密码分步:fire: + +| 功能 | 算法类型 | +| ---------------------- | ------------- | +| 登录 | SM2前端加密,后端解密 | +| 登录登出日志 | SM2对登录登出日志做签名完整性保护存储 | +| 操作日志 | SM2对操作日志做签名完整性保护存储 | +| Token | SM4(cbc模式)加密,Token不再曝光暴露 | +| 用户密码 | SM3完整性保护存储,登录时做完整性校验 | +| 用户手机号 | SM4(cbc模式)加解密使用字段脱敏 | + +### 视频教程:fire: + +| 序号 | 链接地址 | +| ---------------------- | ------------- | +| 1 | [小诺开源技术团队及框架介绍](https://www.bilibili.com/video/BV1Yf4y1N7YU?from=search&seid=16730766915542181758) | +| 2 | [小诺框架Snowy基础环境介绍](https://www.bilibili.com/video/BV1yA411c7d3) | +| 3 | [Snowy代码下载及启动](https://www.bilibili.com/video/BV1SP4y1p7M8) | +| 4 | [Snowy生成一个完整的前后端模块](https://www.bilibili.com/video/BV1Ry4y1G7er) | + +更新中。。。 + + ### 升级计划:fire: + +2.0版本正在全力打造中 + + +### 架构原理图 +* 业务架构 +<p align="center"> + <img src="https://pan.xiaonuo.vip/?explorer/share/fileOut&shareID=7qwKTmEw&path=%7BshareItemLink%3A7qwKTmEw%7D%2F"/> +</p> + +* 应用架构 +<p align="center"> + <img src="https://pan.xiaonuo.vip/?explorer/share/fileOut&shareID=7qwKfxFw&path=%7BshareItemLink%3A7qwKfxFw%7D%2F"/> +</p> + +* 数据架构 +<p align="center"> + <img src="https://pan.xiaonuo.vip/?explorer/share/fileOut&shareID=7qwKrjRw&path=%7BshareItemLink%3A7qwKrjRw%7D%2F"/> +</p> + +* 技术架构 +<p align="center"> + <img src="https://pan.xiaonuo.vip/?explorer/share/fileOut&shareID=7qwK4RoA&path=%7BshareItemLink%3A7qwK4RoA%7D%2F"/> +</p> + +* 部署架构 +<p align="center"> + <img src="https://pan.xiaonuo.vip/?explorer/share/fileOut&shareID=7qwLD35w&path=%7BshareItemLink%3A7qwLD35w%7D%2F"/> +</p> + +### 效果图 + +<table> + <tr> + <td><img src="https://images.gitee.com/uploads/images/2021/0413/111529_02708b11_1980003.png"/></td> + <td><img src="https://images.gitee.com/uploads/images/2021/0413/111909_5957b35a_1980003.png"/></td> + </tr> + <tr> + <td><img src="https://images.gitee.com/uploads/images/2021/0413/112254_5e8a3a0b_1980003.png"/></td> + <td><img src="https://images.gitee.com/uploads/images/2021/0413/112510_90191be8_1980003.png"/></td> + </tr> + <tr> + <td><img src="https://images.gitee.com/uploads/images/2021/0413/112640_73ea49e9_1980003.png"/></td> + <td><img src="https://images.gitee.com/uploads/images/2021/0413/112804_a21e5aef_1980003.png"/></td> + </tr> +</table> + +### 框架亮点及优势 + +1. 模块化架构设计,层次清晰,业务层推荐写到单独模块,框架升级不影响业务。 +``` +模块树 +├─snowy ->项目工程 +│ ├─snowy-base ->框架基础模块 +│ ├─snowy-core ->核心模块 +│ ├─snowy-gen ->代码生成 +│ ├─snowy-system ->基础业务 +│ ├─snowy-main ->业务开始模块 +│ ├─业务 ->您的业务 +``` +2、独创前端字典翻译 + +全部字典数据储存前端store,后端接口数据统一过滤器翻译 + +下拉框,多选框等取值只需1行代码:('dictData'为过滤器名称,'sex'为字典类型code)返回数组字典 +``` +this.$options.filters['dictData']('sex') +或直接给值 +{{ code | dictData }} +``` + +列表数据中字典翻译:('code'为字典类型唯一code,'value'为待翻译的值)返回name +``` +{{ code | dictType(value) }} +``` + +3、独创的数据权限范围机制 + +数据范围的分配也来自于给用户单独分配的数据范围,最终决定用户有几个公司的数据范围的是,用户拥有的角色的数据范围 + 用户直接分配的数据范围 + +若一个用户有多个角色,系统最终判定用户有哪些数据范围是以多个角色和用户数据范围的 并集 为准。 + +仅通过注解就可以获取当前用户的数据范围,不强制联查sql可根据业务需求极其灵活的使用 +``` +@DataScope +``` +param类继承baseparam,使用param.getDadaScope即可获取到数据权限列表 + +``` +@EqualsAndHashCode(callSuper = true) +@Data +public class SysUserParam extends BaseParam { +``` + +4、独创的文件预览系统 + +支持txt.doc.docx.ppt.pptx.xls.xlsx.pdf.png.jpg.jpeg.bmp.gif等 + +预览速度快,兼容性好,支持常见文本格式.只需在运行环境一键安装libreoffice即可,运行简单,操作方便。 + +``` +#libreoffice文档在线预览配置 +# CentOS 下安装 libreoffice: +# 安装:yum -y install libreoffice +# Linux 中文字体乱码解决: +# 1、上传 C:\Windows\Fonts 下的字体到 /usr/share/fonts/windows 目录 +# 2、执行命令: chmod 644 /usr/share/fonts/windows/* && fc-cache -fv +jodconverter: + local: + #暂时关闭预览,启动时会有点慢 + enabled: false + #设置libreoffice主目录 linux地址如:/usr/lib64/libreoffice + office-home: C:\Program Files\LibreOffice + #开启多个libreoffice进程,每个端口对应一个进程 + port-numbers: 8100 + #libreoffice进程重启前的最大进程数 + max-tasks-per-process: 100 +``` +5、其他优势 + +前后端分离架构,分离开发,分离部署,前后端互不影响。 + +前端技术采用vue + antdvPro + axios。 + +后端采用spring boot + mybatis-plus + hutool等,开源可靠。 + +基于spring security(jwt) + 用户UUID双重认证。 + +基于AOP实现的接口粒度的鉴权,最细粒度过滤权限资源。 + +基于hibernate validator实现的校验框架,支持自定义校验注解。 + +提供Request-No的响应header快速定位线上异常问题。 + +在线用户可查,可在线踢人,同账号登录可同时在线,可单独在线(通过系统参数配置)。 + +支持前端 + 后端在线代码生成。 + +文件,短信,缓存,邮件等,利用接口封装,方便拓展。 + +短信默认使用阿里云sms,缓存默认使用内存缓存。 + +### 框架说明及后续补充 + +* 纯手研发搭建框架脚手架,在自己用的时候,也为各位小伙伴打下坚固的接私活利器。 +* 后续我们会行发多个版本,将适配多个数据库环境,国产化环境,并且根据多年经验会出相关系统中用到的案例,提供给大家使用! +* 如需了解我们更多,请移步官网:https://xiaonuo.vip +* 当然,有问题讨论的小伙伴还可以加入我们的QQ技术群:[732230670](https://wpa.qq.com/msgrd?v=3&uin=732230670&_blank),一起学习讨论。 + +### 详细功能 + +1. 主控面板、控制台页面,可进行工作台,分析页,统计等功能的展示。 +2. 用户管理、对企业用户和系统管理员用户的维护,可绑定用户职务,机构,角色,数据权限等。 +3. 应用管理、通过应用来控制不同维度的菜单展示。 +4. 机构管理、公司组织架构维护,支持多层级结构的树形结构。 +5. 职位管理、用户职务管理,职务可作为用户的一个标签,职务目前没有和权限等其他功能挂钩。 +6. 菜单管理、菜单目录,菜单,和按钮的维护是权限控制的基本单位。 +7. 角色管理、角色绑定菜单后,可限制相关角色的人员登录系统的功能范围。角色也可以绑定数据授权范围。 +8. 字典管理、系统内各种枚举类型的维护。 +9. 访问日志、用户的登录和退出日志的查看和管理。 +10. 操作日志、用户的操作业务的日志的查看和管理。 +11. 服务监控、服务器的运行状态,Java虚拟机信息,jvm等数据的查看。 +12. 在线用户、当前系统在线用户的查看。 +13. 数据监控、druid控制台功能,可查看sql的运行信息。 +14. 公告管理、系统的公告的管理。 +15. 文件管理、文件的上传下载查看等操作,文件可使用本地存储,阿里云oss,腾讯cos接入,支持拓展。 +16. 定时任务、定时任务的维护,通过cron表达式控制任务的执行频率。 +17. 系统配置、系统运行的参数的维护,参数的配置与系统运行机制息息相关。 +18. 邮件发送、发送邮件功能。 +19. 短信发送、短信发送功能,可使用阿里云sms,腾讯云sms,支持拓展。 + +### 官方微信群 + +##### 因群达到200人以上,需加微信拉群 + +<table> + <tr> + <td>微信群</td> + <td><img src="https://pan.xiaonuo.vip/?explorer/share/fileOut&shareID=7qwFVcdA&path=%7BshareItemLink%3A7qwFVcdA%7D%2F" width="120"/></td> + </tr> +</table> + +### 参与贡献 + +- 欢迎各路英雄好汉参与Snowy全系版本代码贡献,期待您的加入! +- 1. Fork 本仓库 +- 2. 新建 Feat_xxx 分支 +- 3. 提交代码 +- 4. 新建 Pull Request + +### 更新日志: + +更新日志:https://doc.xiaonuo.vip/snowy_vue/#%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97 + +### 版权说明 + +- Snowy生态技术框架全系版本采用 Apache License2.0协议 +- 代码可用于个人项目等接私活或企业项目脚手架使用,Snowy全系开源版完全免费 +- 二次开发如用于商业性质或开源竞品请先联系群主审核。 +- 请不要删除和修改Snowy源码头部的版权与作者声明及出处。 + +### 小诺技术团队荣誉作品 + +| 成员组成 | 负责内容 | +| :---: | :---: | +| 俞宝山 | 全栈 | +| 徐玉祥 | 全栈 | +| 董夏雨 | 全栈 | diff --git a/_web/.browserslistrc b/_web/.browserslistrc new file mode 100644 index 0000000..8f96043 --- /dev/null +++ b/_web/.browserslistrc @@ -0,0 +1,3 @@ +> 1% +last 2 versions +not ie <= 10 diff --git a/_web/.editorconfig b/_web/.editorconfig new file mode 100644 index 0000000..6f77dff --- /dev/null +++ b/_web/.editorconfig @@ -0,0 +1,39 @@ +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=false +indent_style=space +indent_size=2 + +[{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}] +indent_style=space +indent_size=2 + +[{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}] +indent_style=space +indent_size=2 + +[{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}] +indent_style=space +indent_size=2 + +[*.svg] +indent_style=space +indent_size=2 + +[*.js.map] +indent_style=space +indent_size=2 + +[*.less] +indent_style=space +indent_size=2 + +[*.vue] +indent_style=space +indent_size=2 + +[{.analysis_options,*.yml,*.yaml}] +indent_style=space +indent_size=2 + diff --git a/_web/.env b/_web/.env new file mode 100644 index 0000000..7afe3f1 --- /dev/null +++ b/_web/.env @@ -0,0 +1,3 @@ +NODE_ENV=production +VUE_APP_PREVIEW=true +VUE_APP_API_BASE_URL=http://localhost:82 \ No newline at end of file diff --git a/_web/.env.development b/_web/.env.development new file mode 100644 index 0000000..2275404 --- /dev/null +++ b/_web/.env.development @@ -0,0 +1,3 @@ +NODE_ENV=development +VUE_APP_PREVIEW=true +VUE_APP_API_BASE_URL=http://localhost:82 \ No newline at end of file diff --git a/_web/.env.preview b/_web/.env.preview new file mode 100644 index 0000000..7856e29 --- /dev/null +++ b/_web/.env.preview @@ -0,0 +1,3 @@ +NODE_ENV=production +VUE_APP_PREVIEW=false +VUE_APP_API_BASE_URL=http://localhost:82 \ No newline at end of file diff --git a/_web/.eslintrc.js b/_web/.eslintrc.js new file mode 100644 index 0000000..e4f4dae --- /dev/null +++ b/_web/.eslintrc.js @@ -0,0 +1,77 @@ +module.exports = { + root: true, + env: { + node: true + }, + 'extends': [ + 'plugin:vue/strongly-recommended', + '@vue/standard' + ], + rules: { + 'no-console': 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'generator-star-spacing': 'off', + 'no-mixed-operators': 0, + 'vue/max-attributes-per-line': [ + 2, + { + 'singleline': 5, + 'multiline': { + 'max': 1, + 'allowFirstLine': false + } + } + ], + 'vue/attribute-hyphenation': 0, + 'vue/html-self-closing': 0, + 'vue/component-name-in-template-casing': 0, + 'vue/html-closing-bracket-spacing': 0, + 'vue/singleline-html-element-content-newline': 0, + 'vue/no-unused-components': 0, + 'vue/multiline-html-element-content-newline': 0, + 'vue/no-use-v-if-with-v-for': 0, + 'vue/html-closing-bracket-newline': 0, + 'vue/no-parsing-error': 0, + 'no-tabs': 0, + 'quotes': [ + 2, + 'single', + { + 'avoidEscape': true, + 'allowTemplateLiterals': true + } + ], + 'semi': [ + 2, + 'never', + { + 'beforeStatementContinuationChars': 'never' + } + ], + 'no-delete-var': 2, + 'prefer-const': [ + 2, + { + 'ignoreReadBeforeAssign': false + } + ], + 'template-curly-spacing': 'off', + 'indent': 'off', + "space-before-function-paren": 0, + 'no-multi-spaces': 2, //不能用多余的空格 + }, + parserOptions: { + parser: 'babel-eslint' + }, + overrides: [ + { + files: [ + '**/__tests__/*.{j,t}s?(x)', + '**/tests/unit/**/*.spec.{j,t}s?(x)' + ], + env: { + jest: true + } + } + ] +} diff --git a/_web/.gitignore b/_web/.gitignore new file mode 100644 index 0000000..2576880 --- /dev/null +++ b/_web/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.idea/ \ No newline at end of file diff --git a/_web/.prettierrc b/_web/.prettierrc new file mode 100644 index 0000000..cbe842a --- /dev/null +++ b/_web/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "semi": false, + "singleQuote": true +} diff --git a/_web/.travis.yml b/_web/.travis.yml new file mode 100644 index 0000000..a08bfcb --- /dev/null +++ b/_web/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - 10.15.0 +cache: yarn +script: + - yarn + - yarn run lint --no-fix && yarn run build diff --git a/_web/LICENSE b/_web/LICENSE new file mode 100644 index 0000000..66eef0b --- /dev/null +++ b/_web/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Anan Yang + +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. \ No newline at end of file diff --git a/_web/babel.config.js b/_web/babel.config.js new file mode 100644 index 0000000..e80ad97 --- /dev/null +++ b/_web/babel.config.js @@ -0,0 +1,28 @@ +const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) + +const plugins = [] +if (IS_PROD) { + plugins.push('transform-remove-console') +} + +// lazy load ant-design-vue +// if your use import on Demand, Use this code +plugins.push(['import', { + 'libraryName': 'ant-design-vue', + 'libraryDirectory': 'es', + 'style': true // `style: true` 会加载 less 文件 +}]) + +module.exports = { + presets: [ + '@vue/cli-plugin-babel/preset', + [ + '@babel/preset-env', + { + 'useBuiltIns': 'entry', + 'corejs': 3 + } + ] + ], + plugins +} diff --git a/_web/config/plugin.config.js b/_web/config/plugin.config.js new file mode 100644 index 0000000..2ad9b19 --- /dev/null +++ b/_web/config/plugin.config.js @@ -0,0 +1,46 @@ +const ThemeColorReplacer = require('webpack-theme-color-replacer') +const generate = require('@ant-design/colors/lib/generate').default + +const getAntdSerials = (color) => { + // 淡化(即less的tint) + const lightens = new Array(9).fill().map((t, i) => { + return ThemeColorReplacer.varyColor.lighten(color, i / 10) + }) + const colorPalettes = generate(color) + const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace('#', '')).join(',') + return lightens.concat(colorPalettes).concat(rgb) +} + +const themePluginOption = { + fileName: 'css/theme-colors-[contenthash:8].css', + matchColors: getAntdSerials('#1890ff'), // 主色系列 + // 改变样式选择器,解决样式覆盖问题 + changeSelector (selector) { + switch (selector) { + case '.ant-calendar-today .ant-calendar-date': + return ':not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)' + selector + case '.ant-btn:focus,.ant-btn:hover': + return '.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)' + case '.ant-btn.active,.ant-btn:active': + return '.ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)' + case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon': + case '.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon': + return ':not(.ant-steps-item-process)' + selector + case '.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover': + case '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover': + return '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover' + case '.ant-menu-horizontal > .ant-menu-item-selected > a': + case '.ant-menu-horizontal>.ant-menu-item-selected>a': + return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a' + case '.ant-menu-horizontal > .ant-menu-item > a:hover': + case '.ant-menu-horizontal>.ant-menu-item>a:hover': + return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover' + default : + return selector + } + } +} + +const createThemeColorReplacerPlugin = () => new ThemeColorReplacer(themePluginOption) + +module.exports = createThemeColorReplacerPlugin diff --git a/_web/jest.config.js b/_web/jest.config.js new file mode 100644 index 0000000..29fee32 --- /dev/null +++ b/_web/jest.config.js @@ -0,0 +1,23 @@ +module.exports = { + moduleFileExtensions: [ + 'js', + 'jsx', + 'json', + 'vue' + ], + transform: { + '^.+\\.vue$': 'vue-jest', + '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', + '^.+\\.jsx?$': 'babel-jest' + }, + moduleNameMapper: { + '^@/(.*)$': '<rootDir>/src/$1' + }, + snapshotSerializers: [ + 'jest-serializer-vue' + ], + testMatch: [ + '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' + ], + testURL: 'http://localhost/' +} diff --git a/_web/jsconfig.json b/_web/jsconfig.json new file mode 100644 index 0000000..1bd0da4 --- /dev/null +++ b/_web/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es6", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "exclude": ["node_modules", "dist"], + "include": ["src/**/*"] +} diff --git a/_web/package.json b/_web/package.json new file mode 100644 index 0000000..18825ed --- /dev/null +++ b/_web/package.json @@ -0,0 +1,88 @@ +{ + "name": "vue-antd-pro", + "version": "2.1.0", + "private": true, + "scripts": { + "serve": "vue-cli-service serve", + "build": "vue-cli-service build", + "test:unit": "vue-cli-service test:unit", + "build:preview": "vue-cli-service build --mode preview", + "postinstall": "opencollective-postinstall" + }, + "dependencies": { + "@antv/data-set": "^0.10.2", + "ant-design-vue": "1.5.0-rc.6", + "axios": "^0.19.0", + "babel-polyfill": "^6.26.0", + "clipboard": "^2.0.6", + "compression-webpack-plugin": "5.0.1", + "core-js": "^3.1.2", + "crypto-js": "^4.0.0", + "default-passive-events": "^1.0.10", + "enquire.js": "^2.1.6", + "font-awesome": "^4.7.0", + "jquery": "^3.5.1", + "lodash.clonedeep": "^4.5.0", + "lodash.get": "^4.4.2", + "lodash.pick": "^4.4.0", + "md5": "^2.2.1", + "mockjs2": "1.0.8", + "moment": "^2.24.0", + "nprogress": "^0.2.0", + "print-js": "^1.0.63", + "raphael": "^2.3.0", + "screenfull": "^5.1.0", + "sm-crypto": "^0.3.6", + "viser-vue": "^2.4.6", + "vue": "2.6.10", + "vue-clipboard2": "^0.2.1", + "vue-codemirror-lite": "^1.0.4", + "vue-cropper": "0.4.9", + "vue-ls": "^3.2.1", + "vue-quill-editor": "^3.0.6", + "vue-router": "^3.1.2", + "vue-svg-component-runtime": "^1.0.1", + "vuedraggable": "^2.23.2", + "vuex": "^3.1.1", + "wangeditor": "^3.1.1" + }, + "devDependencies": { + "@ant-design/colors": "^3.2.1", + "@vue/cli-plugin-babel": "^4.0.4", + "@vue/cli-plugin-eslint": "^4.0.4", + "@vue/cli-plugin-router": "^4.0.4", + "@vue/cli-plugin-unit-jest": "^4.0.4", + "@vue/cli-plugin-vuex": "^4.0.4", + "@vue/cli-service": "^4.0.4", + "@vue/eslint-config-prettier": "^5.0.0", + "@vue/eslint-config-standard": "^4.0.0", + "@vue/test-utils": "^1.0.0-beta.29", + "babel-eslint": "^10.0.1", + "babel-plugin-import": "^1.13.0", + "babel-plugin-transform-remove-console": "^6.9.4", + "eslint": "^6.8.0", + "eslint-plugin-html": "^5.0.0", + "eslint-plugin-prettier": "^3.1.0", + "eslint-plugin-vue": "^5.2.3", + "less": "^3.0.4", + "less-loader": "^5.0.0", + "opencollective": "^1.0.3", + "opencollective-postinstall": "^2.0.2", + "prettier": "^1.18.2", + "vue-svg-icon-loader": "^2.1.1", + "vue-template-compiler": "2.6.10", + "webpack-theme-color-replacer": "1.3.18" + }, + "collective": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-pro-vue" + }, + "main": ".eslintrc.js", + "directories": { + "test": "tests" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} diff --git a/_web/postcss.config.js b/_web/postcss.config.js new file mode 100644 index 0000000..961986e --- /dev/null +++ b/_web/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + autoprefixer: {} + } +} diff --git a/_web/public/avatar2.jpg b/_web/public/avatar2.jpg new file mode 100644 index 0000000..9adb2d1 --- /dev/null +++ b/_web/public/avatar2.jpg Binary files differ diff --git a/_web/public/index.html b/_web/public/index.html new file mode 100644 index 0000000..4bbe948 --- /dev/null +++ b/_web/public/index.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html lang="zh-cmn-Hans"> +<head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width,initial-scale=1.0"> + <link rel="icon" href="<%= BASE_URL %>logo.png"> + <title>Snowy快速开发平台</title> + + <style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style> + <!-- <script type="text/javascript" src="https://onlyoffice.xiaonuo.vip/web-apps/apps/api/documents/api.js"></script> --> + <!-- require cdn assets css --> + <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> + <link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" /> + <% } %> + +</head> +<body> +<noscript> + <strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> +</noscript> +<div id="app"> + <div class="first-loading-wrp"> + <h1>Snowy</h1> + <div class="loading-wrp"> + <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span> + </div> + <div style="display: flex; justify-content: center; align-items: center;">Snowy快速开发平台</div> + </div> +</div> +<!-- require cdn assets js --> +<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> +<script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> +<% } %> +<!-- built files will be auto injected --> +</body> +</html> diff --git a/_web/public/loading/loading.css b/_web/public/loading/loading.css new file mode 100644 index 0000000..a899eac --- /dev/null +++ b/_web/public/loading/loading.css @@ -0,0 +1 @@ +#preloadingAnimation{position:fixed;left:0;top:0;height:100%;width:100%;background:#ffffff;user-select:none;z-index: 9999;overflow: hidden}.lds-roller{display:inline-block;position:relative;left:50%;top:50%;transform:translate(-50%,-50%);width:64px;height:64px;}.lds-roller div{animation:lds-roller 1.2s cubic-bezier(0.5,0,0.5,1) infinite;transform-origin:32px 32px;}.lds-roller div:after{content:" ";display:block;position:absolute;width:6px;height:6px;border-radius:50%;background:#13c2c2;margin:-3px 0 0 -3px;}.lds-roller div:nth-child(1){animation-delay:-0.036s;}.lds-roller div:nth-child(1):after{top:50px;left:50px;}.lds-roller div:nth-child(2){animation-delay:-0.072s;}.lds-roller div:nth-child(2):after{top:54px;left:45px;}.lds-roller div:nth-child(3){animation-delay:-0.108s;}.lds-roller div:nth-child(3):after{top:57px;left:39px;}.lds-roller div:nth-child(4){animation-delay:-0.144s;}.lds-roller div:nth-child(4):after{top:58px;left:32px;}.lds-roller div:nth-child(5){animation-delay:-0.18s;}.lds-roller div:nth-child(5):after{top:57px;left:25px;}.lds-roller div:nth-child(6){animation-delay:-0.216s;}.lds-roller div:nth-child(6):after{top:54px;left:19px;}.lds-roller div:nth-child(7){animation-delay:-0.252s;}.lds-roller div:nth-child(7):after{top:50px;left:14px;}.lds-roller div:nth-child(8){animation-delay:-0.288s;}.lds-roller div:nth-child(8):after{top:45px;left:10px;}#preloadingAnimation .load-tips{color: #13c2c2;font-size:2rem;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin-top:80px;text-align:center;width:400px;height:64px;} @keyframes lds-roller{0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);}} \ No newline at end of file diff --git a/_web/public/loading/loading.html b/_web/public/loading/loading.html new file mode 100644 index 0000000..018c402 --- /dev/null +++ b/_web/public/loading/loading.html @@ -0,0 +1 @@ +<div id="preloadingAnimation"><div class=lds-roller><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div><div class=load-tips>Loading</div></div> diff --git a/_web/public/loading/option2/html_code_segment.html b/_web/public/loading/option2/html_code_segment.html new file mode 100644 index 0000000..5c85af3 --- /dev/null +++ b/_web/public/loading/option2/html_code_segment.html @@ -0,0 +1,5 @@ +<div class="preloading-animate"> + <div class="preloading-wrapper"> + <svg class="preloading-balls" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="67.802" cy="59.907" r="6" fill="#51CACC"><animate attributeName="cx" values="75;57.72542485937369" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="50;73.77641290737884" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#51CACC;#9DF871" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="46.079" cy="69.992" r="6" fill="#9DF871"><animate attributeName="cx" values="57.72542485937369;29.774575140626318" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="73.77641290737884;64.69463130731182" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#9DF871;#E0FF77" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="29.775" cy="52.449" r="6" fill="#E0FF77"><animate attributeName="cx" values="29.774575140626318;29.774575140626315" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="64.69463130731182;35.30536869268818" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#E0FF77;#DE9DD6" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="41.421" cy="31.521" r="6" fill="#DE9DD6"><animate attributeName="cx" values="29.774575140626315;57.72542485937368" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="35.30536869268818;26.22358709262116" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#DE9DD6;#FF708E" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="64.923" cy="36.13" r="6" fill="#FF708E"><animate attributeName="cx" values="57.72542485937368;75" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="26.22358709262116;49.99999999999999" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#FF708E;#51CACC" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle></svg> + </div> +</div> diff --git a/_web/public/loading/option2/loading.css b/_web/public/loading/option2/loading.css new file mode 100644 index 0000000..c35cd73 --- /dev/null +++ b/_web/public/loading/option2/loading.css @@ -0,0 +1 @@ +.preloading-animate{background:#ffffff;width:100%;height:100%;position:fixed;left:0;top:0;z-index:299;}.preloading-animate .preloading-wrapper{position:absolute;width:5rem;height:5rem;left:50%;top:50%;transform:translate(-50%,-50%);}.preloading-animate .preloading-wrapper .preloading-balls{font-size:5rem;} \ No newline at end of file diff --git a/_web/public/loading/option2/loading.svg b/_web/public/loading/option2/loading.svg new file mode 100644 index 0000000..7ff7322 --- /dev/null +++ b/_web/public/loading/option2/loading.svg @@ -0,0 +1 @@ +<svg class="preloading-balls" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"><circle cx="67.802" cy="59.907" r="6" fill="#51CACC"><animate attributeName="cx" values="75;57.72542485937369" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="50;73.77641290737884" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#51CACC;#9DF871" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="46.079" cy="69.992" r="6" fill="#9DF871"><animate attributeName="cx" values="57.72542485937369;29.774575140626318" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="73.77641290737884;64.69463130731182" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#9DF871;#E0FF77" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="29.775" cy="52.449" r="6" fill="#E0FF77"><animate attributeName="cx" values="29.774575140626318;29.774575140626315" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="64.69463130731182;35.30536869268818" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#E0FF77;#DE9DD6" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="41.421" cy="31.521" r="6" fill="#DE9DD6"><animate attributeName="cx" values="29.774575140626315;57.72542485937368" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="35.30536869268818;26.22358709262116" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#DE9DD6;#FF708E" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle><circle cx="64.923" cy="36.13" r="6" fill="#FF708E"><animate attributeName="cx" values="57.72542485937368;75" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="cy" values="26.22358709262116;49.99999999999999" keyTimes="0;1" dur="1s" repeatCount="indefinite"/><animate attributeName="fill" values="#FF708E;#51CACC" keyTimes="0;1" dur="1s" repeatCount="indefinite"/></circle></svg> \ No newline at end of file diff --git a/_web/public/logo.png b/_web/public/logo.png new file mode 100644 index 0000000..b26a90b --- /dev/null +++ b/_web/public/logo.png Binary files differ diff --git a/_web/src/App.vue b/_web/src/App.vue new file mode 100644 index 0000000..3a611db --- /dev/null +++ b/_web/src/App.vue @@ -0,0 +1,47 @@ +<template> + <a-config-provider :locale="locale"> + <div id="app" class="app app1"> + <router-view class="scrollbar"/> + </div> + </a-config-provider> +</template> + +<script> +import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN' +import { AppDeviceEnquire } from '@/utils/mixin' + +export default { + mixins: [AppDeviceEnquire], + data () { + return { + locale: zhCN + } + }, + mounted () { + + } +} +</script> +<style> + .app { + overflow: auto; + border : none; + } + .scrollbar { + margin: 0 auto; + } + .app1::-webkit-scrollbar { + /*滚动条整体样式*/ + width : 8px; /*高宽分别对应横竖滚动条的尺寸*/ + } + .app1::-webkit-scrollbar-thumb { + /*滚动条里面小方块*/ + border-radius: 6px; + background : #aaa; + } + .app1::-webkit-scrollbar-track { + /*滚动条里面轨道*/ + border-radius: 8px; + background : #FFFFFF; + } +</style> diff --git a/_web/src/api/modular/gen/codeGenerateManage.js b/_web/src/api/modular/gen/codeGenerateManage.js new file mode 100644 index 0000000..4f13b19 --- /dev/null +++ b/_web/src/api/modular/gen/codeGenerateManage.js @@ -0,0 +1,106 @@ +/** + * 代码生成 + * + * @author yubaoshan + * @date 2020/12/23 15:00 + */ +import { axios } from '@/utils/request' + +/** + * 查询列表 + * + * @author yubaoshan + * @date 2020/12/23 15:00 + */ +export function codeGeneratePage (parameter) { + return axios({ + url: '/codeGenerate/page', + method: 'get', + params: parameter + }) +} + +/** + * 增加 + * + * @author yubaoshan + * @date 2020/12/23 15:00 + */ +export function codeGenerateAdd (parameter) { + return axios({ + url: '/codeGenerate/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑 + * + * @author yubaoshan + * @date 2020/12/23 15:00 + */ +export function codeGenerateEdit (parameter) { + return axios({ + url: '/codeGenerate/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除 + * + * @author yubaoshan + * @date 2020/12/23 15:00 + */ +export function codeGenerateDelete (parameter) { + return axios({ + url: '/codeGenerate/delete', + method: 'post', + data: parameter + }) +} + +/** + * 查询当前数据库用户下的所有表 + * + * @author yubaoshan + * @date 2020/12/23 15:00 + */ +export function codeGenerateInformationList (parameter) { + return axios({ + url: '/codeGenerate/InformationList', + method: 'get', + params: parameter + }) +} + +/** + * 本地生成 + * + * @author yubaoshan + * @date 2020/12/23 15:00 + */ +export function codeGenerateRunLocal (parameter) { + return axios({ + url: '/codeGenerate/runLocal', + method: 'post', + data: parameter + }) +} + +/** + * 压缩包方式下载 + * + * @author yubaoshan + * @date 2020/12/23 15:00 + */ +export function codeGenerateRunDown (parameter) { + return axios({ + url: '/codeGenerate/runDown', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} diff --git a/_web/src/api/modular/gen/sysCodeGenerateConfigManage.js b/_web/src/api/modular/gen/sysCodeGenerateConfigManage.js new file mode 100644 index 0000000..e61944c --- /dev/null +++ b/_web/src/api/modular/gen/sysCodeGenerateConfigManage.js @@ -0,0 +1,29 @@ +import { axios } from '@/utils/request' + +/** + * 代码生成详细配置列表 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ +export function sysCodeGenerateConfigList (parameter) { + return axios({ + url: '/sysCodeGenerateConfig/list', + method: 'get', + params: parameter + }) +} + +/** + * 编辑代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ +export function sysCodeGenerateConfigEdit (parameter) { + return axios({ + url: '/sysCodeGenerateConfig/edit', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/main/README.md b/_web/src/api/modular/main/README.md new file mode 100644 index 0000000..fadf99a --- /dev/null +++ b/_web/src/api/modular/main/README.md @@ -0,0 +1 @@ +/** 您的业务接口文件全写在此文件夹下面,升级底座直接迁移代码即可 **/ diff --git a/_web/src/api/modular/main/blogarticle/blogArticleManage.js b/_web/src/api/modular/main/blogarticle/blogArticleManage.js new file mode 100644 index 0000000..60005c4 --- /dev/null +++ b/_web/src/api/modular/main/blogarticle/blogArticleManage.js @@ -0,0 +1,86 @@ +import { axios } from '@/utils/request' + +/** + * 查询blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +export function blogArticlePage (parameter) { + return axios({ + url: '/blogArticle/page', + method: 'get', + params: parameter + }) +} + +/** + * blog文章主体列表 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +export function blogArticleList (parameter) { + return axios({ + url: '/blogArticle/list', + method: 'get', + params: parameter + }) +} + +/** + * 添加blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +export function blogArticleAdd (parameter) { + return axios({ + url: '/blogArticle/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +export function blogArticleEdit (parameter) { + return axios({ + url: '/blogArticle/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +export function blogArticleDelete (parameter) { + return axios({ + url: '/blogArticle/delete', + method: 'post', + data: parameter + }) +} + +/** + * 导出blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +export function blogArticleExport (parameter) { + return axios({ + url: '/blogArticle/export', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} diff --git a/_web/src/api/modular/system/README.md b/_web/src/api/modular/system/README.md new file mode 100644 index 0000000..46aab4d --- /dev/null +++ b/_web/src/api/modular/system/README.md @@ -0,0 +1 @@ +/** 此文件夹下代码尽量不要动,底座升级直接覆盖替换 **/ diff --git a/_web/src/api/modular/system/appManage.js b/_web/src/api/modular/system/appManage.js new file mode 100644 index 0000000..d3ed844 --- /dev/null +++ b/_web/src/api/modular/system/appManage.js @@ -0,0 +1,92 @@ +/** + * 系统应用 + * + * @author yubaoshan + * @date 2020年4月23日12:10:57 + */ +import { axios } from '@/utils/request' + +/** + * 系统应用列表 + * + * @author yubaoshan + * @date 2020年7月9日15:05:01 + */ +export function getAppPage (parameter) { + return axios({ + url: '/sysApp/page', + method: 'get', + params: parameter + }) +} + +/** + * 系统应用列表 + * + * @author yubaoshan + * @date 2020年7月9日15:05:01 + */ +export function getAppList (parameter) { + return axios({ + url: '/sysApp/list', + method: 'get', + params: parameter + }) +} + +/** + * 新增系统应用 + * + * @author yubaoshan + * @date 2020年7月9日15:05:01 + */ +export function sysAppAdd (parameter) { + return axios({ + url: '/sysApp/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑系统应用 + * + * @author yubaoshan + * @param parameter + * @returns {*} + */ +export function sysAppEdit (parameter) { + return axios({ + url: '/sysApp/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除系统应用 + * + * @author yubaoshan + * @date 2020年7月9日15:05:01 + */ +export function sysAppDelete (parameter) { + return axios({ + url: '/sysApp/delete', + method: 'post', + data: parameter + }) +} + +/** + * 设为默认应用 + * + * @author yubaoshan + * @date 2020年7月9日15:05:01 + */ +export function sysAppSetAsDefault (parameter) { + return axios({ + url: '/sysApp/setAsDefault', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/areaManage.js b/_web/src/api/modular/system/areaManage.js new file mode 100644 index 0000000..aba51fc --- /dev/null +++ b/_web/src/api/modular/system/areaManage.js @@ -0,0 +1,16 @@ +import { axios } from '@/utils/request' + +/** + * 获取区域列表 + * + * @author xuyuxiang + * @param parameter + * @returns {*} + */ +export function getAreaList (parameter) { + return axios({ + url: '/sysArea/list', + method: 'get', + params: parameter + }) +} diff --git a/_web/src/api/modular/system/configManage.js b/_web/src/api/modular/system/configManage.js new file mode 100644 index 0000000..34bdd41 --- /dev/null +++ b/_web/src/api/modular/system/configManage.js @@ -0,0 +1,85 @@ +import { axios } from '@/utils/request' + +/** + * 分页查询配置列表 + * + * @author yubaoshan + * @date 2020/5/25 01:57 + */ +export function sysConfigPage (parameter) { + return axios({ + url: '/sysConfig/page', + method: 'get', + params: parameter + }) +} + +/** + * 添加系统参数配置 + * + * @author yubaoshan + * @date 2020/5/25 01:57 + */ +export function sysConfigAdd (parameter) { + return axios({ + url: '/sysConfig/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑系统参数配置 + * + * @author yubaoshan + * @date 2020/5/25 01:57 + */ +export function sysConfigEdit (parameter) { + return axios({ + url: '/sysConfig/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除系统参数配置 + * + * @author yubaoshan + * @date 2020/5/25 01:57 + */ +export function sysConfigDelete (parameter) { + return axios({ + url: '/sysConfig/delete', + method: 'post', + data: parameter + }) +} + +/** + * 获取字典类型下所有字典,举例,返回格式为:[{code:"M",value:"男"},{code:"F",value:"女"}] + * + * @author yubaoshan + * @date 2020/5/25 02:06 + */ +export function sysDictTypeDropDown (parameter) { + return axios({ + url: '/sysDictType/dropDown', + method: 'get', + params: parameter + }) +} + +/** + * 获取系统的所有任务列表 + * + * @author yubaoshan + * @date 2020/7/8 20:46 + */ +export function sysTimersGetActionClasses (parameter) { + return axios({ + url: '/sysTimers/getActionClasses', + method: 'get', + params: parameter + }) +} diff --git a/_web/src/api/modular/system/dictDataManage.js b/_web/src/api/modular/system/dictDataManage.js new file mode 100644 index 0000000..2240b84 --- /dev/null +++ b/_web/src/api/modular/system/dictDataManage.js @@ -0,0 +1,57 @@ +import { axios } from '@/utils/request' + +/** + * 查询系统字典值 + * + * @author yubaoshan + * @date 2020/5/17 02:24 + */ +export function sysDictDataPage (parameter) { + return axios({ + url: '/sysDictData/page', + method: 'get', + params: parameter + }) +} + +/** + * 添加系统字典值 + * + * @author yubaoshan + * @date 2020/5/17 02:24 + */ +export function sysDictDataAdd (parameter) { + return axios({ + url: '/sysDictData/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑系统字典值 + * + * @author yubaoshan + * @date 2020/5/17 02:25 + */ +export function sysDictDataEdit (parameter) { + return axios({ + url: '/sysDictData/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除系统字典值 + * + * @author yubaoshan + * @date 2020/5/17 02:25 + */ +export function sysDictDataDelete (parameter) { + return axios({ + url: '/sysDictData/delete', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/dictManage.js b/_web/src/api/modular/system/dictManage.js new file mode 100644 index 0000000..bbc44bd --- /dev/null +++ b/_web/src/api/modular/system/dictManage.js @@ -0,0 +1,85 @@ +import { axios } from '@/utils/request' + +/** + * 分页查询系统字典类型 + * + * @author yubaoshan + * @date 2020/5/17 01:46 + */ +export function sysDictTypePage (parameter) { + return axios({ + url: '/sysDictType/page', + method: 'get', + params: parameter + }) +} + +/** + * 添加系统字典类型 + * + * @author yubaoshan + * @date 2020/5/17 01:46 + */ +export function sysDictTypeAdd (parameter) { + return axios({ + url: '/sysDictType/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑系统字典类型 + * + * @author yubaoshan + * @date 2020/5/17 01:50 + */ +export function sysDictTypeEdit (parameter) { + return axios({ + url: '/sysDictType/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除系统字典类型 + * + * @author yubaoshan + * @date 2020/5/17 01:50 + */ +export function sysDictTypeDelete (parameter) { + return axios({ + url: '/sysDictType/delete', + method: 'post', + data: parameter + }) +} + +/** + * 获取字典类型下所有字典,举例,返回格式为:[{code:"M",value:"男"},{code:"F",value:"女"}] + * + * @author yubaoshan + * @date 2020/6/10 00:10 + */ +export function sysDictTypeDropDown (parameter) { + return axios({ + url: '/sysDictType/dropDown', + method: 'get', + params: parameter + }) +} + +/** + * 获取所有字典,启动时加入缓存使用 + * + * @author yubaoshan + * @date 2020/6/10 00:10 + */ +export function sysDictTypeTree (parameter) { + return axios({ + url: '/sysDictType/tree', + method: 'get', + params: parameter + }) +} diff --git a/_web/src/api/modular/system/emailManage.js b/_web/src/api/modular/system/emailManage.js new file mode 100644 index 0000000..1321bcc --- /dev/null +++ b/_web/src/api/modular/system/emailManage.js @@ -0,0 +1,29 @@ +import { axios } from '@/utils/request' + +/** + * 发送邮件 + * + * @author yubaoshan + * @date 2020/7/3 23:22 + */ +export function emailSendEmail (parameter) { + return axios({ + url: '/email/sendEmail', + method: 'post', + data: parameter + }) +} + +/** + * 发送html邮件 + * + * @author yubaoshan + * @date 2020/7/3 23:23 + */ +export function emailSendEmailHtml (parameter) { + return axios({ + url: '/email/sendEmailHtml', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/fileManage.js b/_web/src/api/modular/system/fileManage.js new file mode 100644 index 0000000..c9a0d06 --- /dev/null +++ b/_web/src/api/modular/system/fileManage.js @@ -0,0 +1,115 @@ +import { axios } from '@/utils/request' + +/** + * 分页查询文件信息表 + * + * @author yubaoshan + * @date 2020/6/30 00:20 + */ +export function sysFileInfoPage (parameter) { + return axios({ + url: '/sysFileInfo/page', + method: 'get', + params: parameter + }) +} + +/** + * 获取全部文件信息表 + * + * @author yubaoshan + * @date 2020/6/30 00:20 + */ +export function sysFileInfoList (parameter) { + return axios({ + url: '/sysFileInfo/list', + method: 'get', + params: parameter + }) +} + +/** + * 上传文件 + * + * @author yubaoshan + * @date 2020/6/30 00:20 + */ +export function sysFileInfoUpload (parameter) { + return axios({ + url: '/sysFileInfo/upload', + method: 'post', + data: parameter + }) +} + +/** + * 下载文件 + * + * @author yubaoshan + * @date 2020/6/30 00:20 + */ +export function sysFileInfoDownload (parameter) { + return axios({ + url: '/sysFileInfo/download', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} + +/** + * 查看图片 + * + * @author yubaoshan + * @date 2020/6/30 00:20 + */ +export function sysFileInfoPreview (parameter) { + return axios({ + url: '/sysFileInfo/preview', + method: 'get', + params: parameter, + responseType: 'arraybuffer' + }) +} + +/** + * 查看详情文件信息表 + * + * @author yubaoshan + * @date 2020/6/30 00:20 + */ +export function sysFileInfoDetail (parameter) { + return axios({ + url: '/sysFileInfo/detail', + method: 'get', + params: parameter + }) +} + +/** + * 删除文件信息表 + * + * @author yubaoshan + * @date 2020/6/30 00:20 + */ +export function sysFileInfoDelete (parameter) { + return axios({ + url: '/sysFileInfo/delete', + method: 'post', + data: parameter + }) +} + +/** + * 获取在线文档配置 + * + * @author yubaoshan + * @date 2020/6/30 00:20 + */ +export function sysFileInfoGetOnlineConfig (parameter) { + return axios({ + url: '/sysFileInfo/getOnlineFileConfig', + method: 'get', + params: parameter + }) +} diff --git a/_web/src/api/modular/system/logManage.js b/_web/src/api/modular/system/logManage.js new file mode 100644 index 0000000..7c830a6 --- /dev/null +++ b/_web/src/api/modular/system/logManage.js @@ -0,0 +1,87 @@ +import { axios } from '@/utils/request' + +/** + * 查询访问日志 + * + * @author yubaoshan + * @date 2020/5/19 11:57 + */ +export function sysVisLogPage (parameter) { + return axios({ + url: '/sysVisLog/page', + method: 'get', + params: parameter + }) +} + +/** + * 查询操作日志 + * + * @author yubaoshan + * @date 2020/5/19 11:57 + */ +export function sysOpLogPage (parameter) { + return axios({ + url: '/sysOpLog/page', + method: 'get', + params: parameter + }) +} + +/** + * 清空访问日志 + * + * @author yubaoshan + * @date 2020/6/23 23:09 + */ +export function sysVisLogDelete (parameter) { + return axios({ + url: '/sysVisLog/delete', + method: 'post', + data: parameter + }) +} + +/** + * 清空登录日志 + * + * @author yubaoshan + * @date 2020/6/23 23:09 + */ +export function sysOpLogDelete (parameter) { + return axios({ + url: '/sysOpLog/delete', + method: 'post', + data: parameter + }) +} + +/** + * 导出登录日志 + * + * @author yubaoshan + * @date 2021/5/30 18:03 + */ +export function sysVisLogExport (parameter) { + return axios({ + url: '/sysVisLog/export', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} + +/** + * 导出操作日志 + * + * @author yubaoshan + * @date 2021/5/30 18:03 + */ +export function sysOpLogExport (parameter) { + return axios({ + url: '/sysOpLog/export', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} diff --git a/_web/src/api/modular/system/loginManage.js b/_web/src/api/modular/system/loginManage.js new file mode 100644 index 0000000..7534b00 --- /dev/null +++ b/_web/src/api/modular/system/loginManage.js @@ -0,0 +1,125 @@ +/** + * 系统应用 + * + * @author yubaoshan + * @date 2020/5/26 19:06 + */ +import { axios } from '@/utils/request' + +/** + * 登录 + * + * @author yubaoshan + * @date 2020/5/26 19:06 + */ +export function login (parameter) { + // 密码采用sm2加密传输密码 + const sm2 = require('sm-crypto').sm2 + const publicKey = '04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54' + const encryptData = sm2.doEncrypt(parameter.password, publicKey, 1) + parameter.password = encryptData + + return axios({ + url: '/login', + method: 'post', + data: parameter + }) +} + +/** + * 登出 + * + * @author yubaoshan + * @date 2020/5/26 19:07 + */ +export function logout (parameter) { + return axios({ + url: '/logout', + method: 'get', + params: parameter + }) +} + +/** + * 获取登录用户信息 + * + * @author yubaoshan + * @date 2020/5/26 19:08 + */ +export function getLoginUser (parameter) { + return axios({ + url: '/getLoginUser', + method: 'get', + params: parameter + }) +} + +/** + * 获取租户开关 + * + * @author yubaoshan + * @date 2020/9/5 1:24 + */ +export function getTenantOpen (parameter) { + return axios({ + url: '/getTenantOpen', + method: 'get', + params: parameter + }) +} + +/** + * 获取短信验证码 + * + * @author yubaoshan + * @date 2020/5/26 19:29 + */ +export function getSmsCaptcha (parameter) { + return axios({ + url: '/getSmsCaptcha', + method: 'get', + params: parameter + }) +} + +/** + * 获取验证码开关 + * + * @author Jax + * @date 2021/1/22 00:00 + */ +export function getCaptchaOpen (parameter) { + return axios({ + url: '/getCaptchaOpen', + method: 'get', + params: parameter + }) +} + +/** + * 获取验证图片 以及token + * + * @author Jax + * @date 2021/1/22 00:00 + */ +export function reqGet(data) { + return axios({ + url: '/captcha/get', + method: 'post', + data + }) +} + +/** + * 滑动或者点选验证 + * + * @author Jax + * @date 2021/1/22 00:00 + */ +export function reqCheck(data) { + return axios({ + url: '/captcha/check', + method: 'post', + data + }) +} diff --git a/_web/src/api/modular/system/machineManage.js b/_web/src/api/modular/system/machineManage.js new file mode 100644 index 0000000..a154d04 --- /dev/null +++ b/_web/src/api/modular/system/machineManage.js @@ -0,0 +1,15 @@ +import { axios } from '@/utils/request' + +/** + * 系统属性监控 + * + * @author yubaoshan + * @date 2020/6/8 19:47 + */ +export function sysMachineQuery (parameter) { + return axios({ + url: '/sysMachine/query', + method: 'get', + params: parameter + }) +} diff --git a/_web/src/api/modular/system/menuManage.js b/_web/src/api/modular/system/menuManage.js new file mode 100644 index 0000000..7ac62c4 --- /dev/null +++ b/_web/src/api/modular/system/menuManage.js @@ -0,0 +1,114 @@ +import { axios } from '@/utils/request' + +/** + * 获取菜单列表 + * + * @author yubaoshan + * @param parameter + * @returns {*} + */ +export function getMenuList (parameter) { + return axios({ + url: '/sysMenu/list', + method: 'get', + params: parameter + }) +} + +/** + * 获取系统菜单树,用于新增,编辑时选择上级节点 + * + * @author yubaoshan + * @date 2020/4/23 12:22 + */ +export function getMenuTree (parameter) { + return axios({ + url: '/sysMenu/tree', + method: 'get', + params: parameter + }) +} + +/** + * 增加菜单 + * + * @author yubaoshan + * @date 2020/4/24 23:23 + */ +export function sysMenuAdd (parameter) { + return axios({ + url: '/sysMenu/add', + method: 'post', + data: parameter + }) +} + +/** + * 增加菜单 + * + * @author yubaoshan + * @date 2020/4/24 23:23 + */ +export function sysMenuDelete (parameter) { + return axios({ + url: '/sysMenu/delete', + method: 'post', + data: parameter + }) +} + +/** + * 查看菜单详情 + * + * @author yubaoshan + * @date 2020/4/25 01:11 + */ +export function sysMenuDetail (parameter) { + return axios({ + url: '/sysMenu/detail', + method: 'post', + data: parameter + }) +} + +/** + * 编辑系统菜单 + * + * @author yubaoshan + * @date 2020/4/25 01:11 + */ +export function sysMenuEdit (parameter) { + return axios({ + url: '/sysMenu/edit', + method: 'post', + data: parameter + }) +} + +/** + * 获取系统菜单树,用于给角色授权时选择 + * + * @author yubaoshan + * @date 2020/6/2 17:30 + */ +export function SysMenuTreeForGrant (parameter) { + return axios({ + url: '/sysMenu/treeForGrant', + method: 'get', + params: parameter + }) +} + +/** + * 根据系统切换菜单 + * + * @author yubaoshan + * @date 2020/6/28 15:25 + */ +export function sysMenuChange (parameter) { + return axios({ + url: '/sysMenu/change', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/noticeManage.js b/_web/src/api/modular/system/noticeManage.js new file mode 100644 index 0000000..d4f7277 --- /dev/null +++ b/_web/src/api/modular/system/noticeManage.js @@ -0,0 +1,85 @@ +import { axios } from '@/utils/request' + +/** + * 查询系统通知公告 + * + * @author yubaoshan + * @date 2020/6/30 01:56 + */ +export function sysNoticePage (parameter) { + return axios({ + url: '/sysNotice/page', + method: 'get', + params: parameter + }) +} + +/** + * 添加系统通知公告 + * + * @author yubaoshan + * @date 2020/6/30 01:56 + */ +export function sysNoticeAdd (parameter) { + return axios({ + url: '/sysNotice/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑系统通知公告 + * + * @author yubaoshan + * @date 2020/6/30 01:56 + */ +export function sysNoticeEdit (parameter) { + return axios({ + url: '/sysNotice/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除系统通知公告 + * + * @author yubaoshan + * @date 2020/6/30 01:56 + */ +export function sysNoticeDelete (parameter) { + return axios({ + url: '/sysNotice/delete', + method: 'post', + data: parameter + }) +} + +/** + * 通知公告详情 + * + * @author yubaoshan + * @date 2020/6/30 01:56 + */ +export function sysNoticeDetail (parameter) { + return axios({ + url: '/sysNotice/detail', + method: 'get', + params: parameter + }) +} + +/** + * 修改状态 + * + * @author yubaoshan + * @date 2020/7/30 02:23 + */ +export function sysNoticeChangeStatus (parameter) { + return axios({ + url: '/sysNotice/changeStatus', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/noticeReceivedManage.js b/_web/src/api/modular/system/noticeReceivedManage.js new file mode 100644 index 0000000..a87237e --- /dev/null +++ b/_web/src/api/modular/system/noticeReceivedManage.js @@ -0,0 +1,15 @@ +import { axios } from '@/utils/request' + +/** + * 查询我收到的系统通知公告 + * + * @author yubaoshan + * @date 2020/7/3 03:02 + */ +export function sysNoticeReceived (parameter) { + return axios({ + url: '/sysNotice/received', + method: 'get', + params: parameter + }) +} diff --git a/_web/src/api/modular/system/onlineUserManage.js b/_web/src/api/modular/system/onlineUserManage.js new file mode 100644 index 0000000..7abc49a --- /dev/null +++ b/_web/src/api/modular/system/onlineUserManage.js @@ -0,0 +1,29 @@ +import { axios } from '@/utils/request' + +/** + * 在线用户列表 + * + * @author yubaoshan + * @date 2020/6/8 11:11 + */ +export function sysOnlineUserList (parameter) { + return axios({ + url: '/sysOnlineUser/list', + method: 'get', + params: parameter + }) +} + +/** + * 强制下线 + * + * @author yubaoshan + * @date 2020/6/8 11:11 + */ +export function sysOnlineUserForceExist (parameter) { + return axios({ + url: '/sysOnlineUser/forceExist', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/orgManage.js b/_web/src/api/modular/system/orgManage.js new file mode 100644 index 0000000..4980769 --- /dev/null +++ b/_web/src/api/modular/system/orgManage.js @@ -0,0 +1,100 @@ +import { axios } from '@/utils/request' + +/** + * 获取机构树 + * + * @author yubaoshan + * @date 2020/4/26 12:08 + */ +export function getOrgTree (parameter) { + return axios({ + url: '/sysOrg/tree', + method: 'get', + params: parameter + }) +} + +/** + * 获取机构列表 + * + * @author yubaoshan + * @date 2020/5/11 12:59 + */ +export function getOrgList (parameter) { + return axios({ + url: '/sysOrg/list', + method: 'get', + params: parameter + }) +} + +/** + * 获取机构列表 + * + * @author yubaoshan + * @date 2020/5/11 16:17 + */ +export function getOrgPage (parameter) { + return axios({ + url: '/sysOrg/page', + method: 'get', + params: parameter + }) +} + +/** + * 新增机构 + * + * @author yubaoshan + * @date 2020/5/11 13:56 + */ +export function sysOrgAdd (parameter) { + return axios({ + url: '/sysOrg/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑机构 + * + * @author yubaoshan + * @date 2020/5/11 13:56 + */ +export function sysOrgEdit (parameter) { + return axios({ + url: '/sysOrg/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除机构 + * + * @author yubaoshan + * @date 2020/5/11 12:59 + */ +export function sysOrgDelete (parameter) { + return axios({ + url: '/sysOrg/delete', + method: 'post', + data: parameter + }) +} + +/** + * 导出系统机构 + * + * @author yubaoshan + * @date 2021/5/30 12:58 + */ +export function sysOrgExport (parameter) { + return axios({ + url: '/sysOrg/export', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} diff --git a/_web/src/api/modular/system/posManage.js b/_web/src/api/modular/system/posManage.js new file mode 100644 index 0000000..e8fd7f2 --- /dev/null +++ b/_web/src/api/modular/system/posManage.js @@ -0,0 +1,86 @@ +import { axios } from '@/utils/request' + +/** + * 查询系统职位 + * + * @author yubaoshan + * @date 2020/5/25 01:31 + */ +export function sysPosPage (parameter) { + return axios({ + url: '/sysPos/page', + method: 'get', + params: parameter + }) +} + +/** + * 系统职位列表 + * + * @author yubaoshan + * @date 2020/6/21 23:50 + */ +export function sysPosList (parameter) { + return axios({ + url: '/sysPos/list', + method: 'get', + params: parameter + }) +} + +/** + * 添加系统职位 + * + * @author yubaoshan + * @date 2020/5/25 01:31 + */ +export function sysPosAdd (parameter) { + return axios({ + url: '/sysPos/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑系统职位 + * + * @author yubaoshan + * @date 2020/5/25 01:31 + */ +export function sysPosEdit (parameter) { + return axios({ + url: '/sysPos/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除系统职位 + * + * @author yubaoshan + * @date 2020/5/25 01:31 + */ +export function sysPosDelete (parameter) { + return axios({ + url: '/sysPos/delete', + method: 'post', + data: parameter + }) +} + +/** + * 导出系统职位 + * + * @author yubaoshan + * @date 2021/5/29 16:19 + */ +export function sysPosExport (parameter) { + return axios({ + url: '/sysPos/export', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} diff --git a/_web/src/api/modular/system/roleManage.js b/_web/src/api/modular/system/roleManage.js new file mode 100644 index 0000000..40bb833 --- /dev/null +++ b/_web/src/api/modular/system/roleManage.js @@ -0,0 +1,141 @@ +import { axios } from '@/utils/request' + +/** + * 获取角色列表 + * + * @author yubaoshan + * @date 2020/5/6 11:44 + */ +export function getRolePage (parameter) { + return axios({ + url: '/sysRole/page', + method: 'get', + params: parameter + }) +} + +/** + * 增加角色 + * + * @author yubaoshan + * @date 2020/5/6 11:44 + */ +export function sysRoleAdd (parameter) { + return axios({ + url: '/sysRole/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑角色 + * + * @author yubaoshan + * @date 2020/5/6 11:44 + */ +export function sysRoleEdit (parameter) { + return axios({ + url: '/sysRole/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除角色 + * + * @author yubaoshan + * @date 2020/5/6 17:51 + */ +export function sysRoleDelete (parameter) { + return axios({ + url: '/sysRole/delete', + method: 'post', + data: parameter + }) +} + +/** + * 删除角色 + * + * @author yubaoshan + * @date 2020/5/7 11:28 + */ +export function sysRoleDeteil (parameter) { + return axios({ + url: '/sysRole/detail', + method: 'get', + params: parameter + }) +} + +/** + * 获取授权角色列表 + * + * @author yubaoshan + * @date 2020/5/26 23:59 + */ +export function sysRoleDropDown (parameter) { + return axios({ + url: '/sysRole/dropDown', + method: 'get', + params: parameter + }) +} + +/** + * 拥有菜单 + * + * @author yubaoshan + * @date 2020/6/02 19:02 + */ +export function sysRoleOwnMenu (parameter) { + return axios({ + url: '/sysRole/ownMenu', + method: 'get', + params: parameter + }) +} + +/** + * 授权菜单 + * + * @author yubaoshan + * @date 2020/6/2 21:10 + */ +export function sysRoleGrantMenu (parameter) { + return axios({ + url: '/sysRole/grantMenu', + method: 'post', + data: parameter + }) +} + +/** + * 拥有数据 + * + * @author yubaoshan + * @date 2020/6/02 21:40 + */ +export function sysRoleOwnData (parameter) { + return axios({ + url: '/sysRole/ownData', + method: 'get', + params: parameter + }) +} + +/** + * 授权数据 + * + * @author yubaoshan + * @date 2020/6/2 21:50 + */ +export function sysRoleGrantData (parameter) { + return axios({ + url: '/sysRole/grantData', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/smsManage.js b/_web/src/api/modular/system/smsManage.js new file mode 100644 index 0000000..9973bd2 --- /dev/null +++ b/_web/src/api/modular/system/smsManage.js @@ -0,0 +1,43 @@ +import { axios } from '@/utils/request' + +/** + * 发送记录查询 + * + * @author yubaoshan + * @date 2020/7/3 22:11 + */ +export function smsPage (parameter) { + return axios({ + url: '/sms/page', + method: 'get', + params: parameter + }) +} + +/** + * 验证短信验证码 + * + * @author yubaoshan + * @date 2020/7/3 22:12 + */ +export function sysSendLoginMessage (parameter) { + return axios({ + url: '/sms/sendLoginMessage', + method: 'post', + data: parameter + }) +} + +/** + * 验证短信验证码 + * + * @author yubaoshan + * @date 2020/7/3 22:12 + */ +export function sysValidateMessage (parameter) { + return axios({ + url: '/sms/validateMessage', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/timersManage.js b/_web/src/api/modular/system/timersManage.js new file mode 100644 index 0000000..7ca18f7 --- /dev/null +++ b/_web/src/api/modular/system/timersManage.js @@ -0,0 +1,127 @@ +import { axios } from '@/utils/request' + +/** + * 分页查询定时任务 + * + * @author yubaoshan + * @date 2020/7/3 03:13 + */ +export function sysTimersPage (parameter) { + return axios({ + url: '/sysTimers/page', + method: 'get', + params: parameter + }) +} + +/** + * 获取全部定时任务 + * + * @author yubaoshan + * @date 2020/7/3 03:23 + */ +export function sysTimersList (parameter) { + return axios({ + url: '/sysTimers/list', + method: 'get', + params: parameter + }) +} + +/** + * 查看详情定时任务 + * + * @author yubaoshan + * @date 2020/7/3 03:23 + */ +export function sysTimersDetail (parameter) { + return axios({ + url: '/sysTimers/detail', + method: 'get', + params: parameter + }) +} + +/** + * 添加定时任务 + * + * @author yubaoshan + * @date 2020/7/3 03:23 + */ +export function sysTimersAdd (parameter) { + return axios({ + url: '/sysTimers/add', + method: 'post', + data: parameter + }) +} + +/** + * 删除定时任务 + * + * @author yubaoshan + * @date 2020/7/3 03:23 + */ +export function sysTimersDelete (parameter) { + return axios({ + url: '/sysTimers/delete', + method: 'post', + data: parameter + }) +} + +/** + * 编辑定时任务 + * + * @author yubaoshan + * @date 2020/7/3 03:23 + */ +export function sysTimersEdit (parameter) { + return axios({ + url: '/sysTimers/edit', + method: 'post', + data: parameter + }) +} + +/** + * 获取系统的所有任务列表 + * + * @author yubaoshan + * @date 2020/7/3 03:23 + */ +export function sysTimersGetActionClasses (parameter) { + return axios({ + url: '/sysTimers/getActionClasses', + method: 'post', + data: parameter + }) +} + +/** + * 启动定时任务 + * + * @author yubaoshan + * @date 2020/7/3 03:23 + */ +export function sysTimersStart (parameter) { + return axios({ + url: '/sysTimers/start', + method: 'post', + data: parameter + }) +} + +/** + * 停止定时任务 + * + * @author yubaoshan + * @date 2020/7/3 03:23 + */ +export function sysTimersStop (parameter) { + return axios({ + url: '/sysTimers/stop', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/api/modular/system/userManage.js b/_web/src/api/modular/system/userManage.js new file mode 100644 index 0000000..9935b8f --- /dev/null +++ b/_web/src/api/modular/system/userManage.js @@ -0,0 +1,226 @@ +import { axios } from '@/utils/request' + +/** + * 获取用户列表 + * + * @author yubaoshan + * @date 2020/4/26 12:08 + */ +export function getUserPage (parameter) { + return axios({ + url: '/sysUser/page', + method: 'get', + params: parameter + }) +} + +/** + * 增加用户 + * + * @author yubaoshan + * @date 2020/5/5 02:08 + */ +export function sysUserAdd (parameter) { + return axios({ + url: '/sysUser/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑用户 + * + * @author yubaoshan + * @date 2020/5/5 02:08 + */ +export function sysUserEdit (parameter) { + return axios({ + url: '/sysUser/edit', + method: 'post', + data: parameter + }) +} + +/** + * 获取用户详情 + * + * @author yubaoshan + * @date 2020/5/5 19:55 + */ +export function sysUserDetail (parameter) { + return axios({ + url: '/sysUser/detail', + method: 'get', + params: parameter + }) +} + +/** + * 删除用户 + * + * @author yubaoshan + * @date 2020/5/7 19:31 + */ +export function sysUserDelete (parameter) { + return axios({ + url: '/sysUser/delete', + method: 'post', + data: parameter + }) +} + +/** + * 导出用户 + * + * @author yubaoshan + * @date 2021/5/30 00:23 + */ +export function sysUserExport (parameter) { + return axios({ + url: '/sysUser/export', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} + +/** + * 拥有角色 + * + * @author yubaoshan + * @date 2020/6/3 11:58 + */ +export function sysUserOwnRole (parameter) { + return axios({ + url: '/sysUser/ownRole', + method: 'get', + params: parameter + }) +} + +/** + * 授权角色 + * + * @author yubaoshan + * @date 2020/5/26 23:59 + */ +export function sysUserGrantRole (parameter) { + return axios({ + url: '/sysUser/grantRole', + method: 'post', + data: parameter + }) +} + +/** + * 拥有数据 + * + * @author yubaoshan + * @date 2020/6/2 23:14 + */ +export function sysUserOwnData (parameter) { + return axios({ + url: '/sysUser/ownData', + method: 'get', + params: parameter + }) +} + +/** + * 授权数据 + * + * @author yubaoshan + * @date 2020/6/2 23:15 + */ +export function sysUserGrantData (parameter) { + return axios({ + url: '/sysUser/grantData', + method: 'post', + data: parameter + }) +} + +/** + * 修改状态 + * + * @author yubaoshan + * @date 2020/6/23 21:36 + */ +export function sysUserChangeStatus (parameter) { + return axios({ + url: '/sysUser/changeStatus', + method: 'post', + data: parameter + }) +} + +/** + * 重置密码 + * + * @author yubaoshan + * @date 2020/6/23 22:04 + */ +export function sysUserResetPwd (parameter) { + return axios({ + url: '/sysUser/resetPwd', + method: 'post', + data: parameter + }) +} + +/** + * 修改密码 + * + * @author yubaoshan + * @date 2020/6/25 00:25 + */ +export function sysUserUpdatePwd (parameter) { + return axios({ + url: '/sysUser/updatePwd', + method: 'post', + data: parameter + }) +} + +/** + * 用户选择器 + * + * @author yubaoshan + * @date 2020/6/25 00:25 + */ +export function sysUserSelector (parameter) { + return axios({ + url: '/sysUser/selector', + method: 'get', + params: parameter + }) +} + +/** + * 修改头像 + * + * @author yubaoshan + * @date 2020/9/20 2:21 + */ +export function sysUserUpdateAvatar (parameter) { + return axios({ + url: '/sysUser/updateAvatar', + method: 'post', + data: parameter + }) +} + +/** + * 更新基本信息 + * + * @author yubaoshan + * @date 2020/9/20 03:12 + */ +export function sysUserUpdateInfo (parameter) { + return axios({ + url: '/sysUser/updateInfo', + method: 'post', + data: parameter + }) +} diff --git a/_web/src/assets/icons/bx-analyse.svg b/_web/src/assets/icons/bx-analyse.svg new file mode 100644 index 0000000..b02a8d6 --- /dev/null +++ b/_web/src/assets/icons/bx-analyse.svg @@ -0,0 +1 @@ +<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1551058675966" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7872" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M85.333333 512h85.333334a340.736 340.736 0 0 1 99.712-241.621333 337.493333 337.493333 0 0 1 108.458666-72.96 346.453333 346.453333 0 0 1 261.546667-1.749334A106.154667 106.154667 0 0 0 746.666667 298.666667C805.802667 298.666667 853.333333 251.136 853.333333 192S805.802667 85.333333 746.666667 85.333333c-29.397333 0-55.978667 11.776-75.221334 30.933334-103.722667-41.514667-222.848-40.874667-325.76 2.517333a423.594667 423.594667 0 0 0-135.68 91.264 423.253333 423.253333 0 0 0-91.306666 135.637333A426.88 426.88 0 0 0 85.333333 512z m741.248 133.205333c-17.109333 40.618667-41.685333 77.141333-72.96 108.416s-67.797333 55.850667-108.458666 72.96a346.453333 346.453333 0 0 1-261.546667 1.749334A106.154667 106.154667 0 0 0 277.333333 725.333333C218.197333 725.333333 170.666667 772.864 170.666667 832S218.197333 938.666667 277.333333 938.666667c29.397333 0 55.978667-11.776 75.221334-30.933334A425.173333 425.173333 0 0 0 512 938.666667a425.941333 425.941333 0 0 0 393.258667-260.352A426.325333 426.325333 0 0 0 938.666667 512h-85.333334a341.034667 341.034667 0 0 1-26.752 133.205333z" p-id="7873"></path><path d="M512 318.378667c-106.752 0-193.621333 86.869333-193.621333 193.621333S405.248 705.621333 512 705.621333s193.621333-86.869333 193.621333-193.621333S618.752 318.378667 512 318.378667z m0 301.909333c-59.690667 0-108.288-48.597333-108.288-108.288S452.309333 403.712 512 403.712s108.288 48.597333 108.288 108.288-48.597333 108.288-108.288 108.288z" p-id="7874"></path></svg> \ No newline at end of file diff --git a/_web/src/assets/logo.png b/_web/src/assets/logo.png new file mode 100644 index 0000000..b26a90b --- /dev/null +++ b/_web/src/assets/logo.png Binary files differ diff --git a/_web/src/assets/logo.svg b/_web/src/assets/logo.svg new file mode 100644 index 0000000..8f03cc0 --- /dev/null +++ b/_web/src/assets/logo.svg @@ -0,0 +1,203 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px" height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve"> <image id="image0" width="500" height="500" x="0" y="0" + href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAH0CAMAAAD8CC+4AAAABGdBTUEAALGPC/xhBQAAACBjSFJN +AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAADAFBMVEX////X7v+w3//L6f/U +7v8ao/+64//i8/8lqP+v3//Z8P8HnP/S7f/v+f87sf8Amf8Onv8tq//n9f/5/f9Vu//R7f8Lnf9G +tf/z+v9txf/Q7P8Mnv9dvv/8/v+Dzf+75P/M6/8Jnf94yf+a1/+85P/I6f+T1P+04f8Bmf/F6P8E +m/8Tof8Cmv+95f/C5/8Qn//G6P/X7/8ip//A5v8Dmv8hpv/a8P/l9f8yrf83r//r9/+24v9Mt//2 ++/9ev/9kwf/9/v9yx/++5f+BzP+Iz/+d2P+i2v+r3f+54//B5v+k2/8Xov/K6v8mqP/f8v/c8f8k +p/89sf/w+f/s9/+e2P9Tuv/7/f/4/P9NuP8Fm/+Z1v9pw/9iwP+Fzv97yv8Gm//L6v+W1f+Q0/+s +3v8InP/O6/+M0f8Yo/8SoP/P7P+Hz//h8//W7/8epf8Knf8+sv/x+f/m9f8vrP9XvP9Bs/9/zP9x +xv/6/f+K0P9vxf+p3f91yP+c1//T7f+y4P/J6f9lwf92yP9gv/9avf9Wu/8RoP/E5//Y7/+g2f9Q +uf80rv9OuP/b8f9jwf/e8v8oqf9Cs/99y/8Uof/d8f/3/P/1+/8jp//0+/8pqf86sP/t+P/y+v9r +xP8Vof/g8//o9v/u+P/H6f/j9P/k9P/p9v81rv8bpP9Jtv/q9/90x/+P0v9sxP9DtP9Zvf8sqv9Y +vP9mwv9qw//q9v84pv8Akv9Itv8Ck//g8v8qqv8Bkv+J0P8wrP+S0/+m2/9Puf95yf+95P+o3P+t +3v/V7v85sP+n3P+i2f9hwP9kwP98y/9PuP9Es/8/sf87sP+/5v8Alf/v+P9EtP/H6P8Mnf8Im/8E +mv+x4P8Dmf8cpP8AmP+r3v+S1P9vxv9bvv9Ouf////88sv8zqf8tpf8ppP8kov/0+v9txP8gof8b +nv8Wm/8Sm/8Omf8Kl/8Glf/d8v9ZvP8Ak/9jwP8jpP8VnP/l9P88r/84q/82qv8Bk//R7P8wp/8q +of9Suf9Js/81rP+Y1f/HFJ4WAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAwMC +AADczCHBAAAm0klEQVR42u2deWDVVZbnM9WmggS8QSDsGBQkhshDSGSRJSyy7xSbCAmI7CFhEURF +EQRBkQCyyB4FBVd0pqi2RXRQGbqqp6e7unssM1tNd/VM9XS3M9U90z379GQhIcs993eX73335eV8 +/tSXc8/vfLm/5d57zklJYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiG +YRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRiGYRKDf/SD0B4o+a3fCu1BMnJH6g9D +u6AgrdWdoV1IRlqnp7YJ7QNJ27tEIv+TbLbckSHa3R3aCYL2HQSL7oOOmUJ06hzaCzldBIvuha7d +KiPbvUdoN2T0rPRM3BPai2Qkq1dVaO8N7YaE+6ocE71Du5GM9Emvju39of1oQt9qx7JZdA88kFMt +enqifRr16VftV+6DoR1JRvrXiC5iA0J70oCHBgoW3RuD8mqiK/IfDu1KPQYPESy6P3pn3wqvGDos +tC91dO4kWHSPPCLqGD4itDO36DFSsOg+6XtbdDGqILQ3NYy+7VLumNDOJCP1RRdjQ3tTzTjBovvl +zvqii0dDu1PJ+Nx6DmUPCu1OMjKhgei9+ob2J2Vit/oOZU8K7U8yMrmB6GLK1MD+ZE1p4E92/9AB +SkYaiS6mTQ/qzoyZDd3JGxA6QMnI+Eaii1mzA3ozZ2gjb3L6hA5QMvKjxqKLufOCOTN/QWNnFoZ+ +3CQljzURXSwK5szjgkWPB/c3FV0sDuTLkqauFBaFDlAyIhM9Z3wQV5ZKXEnPCh2gZGSZJNIiI8TJ +tCeWSzxZ/mToACUjK2Sii5Xxj/WkVTJHeq0OHaBkZI1UdNFqbZz9WFcs9WN919ABSkYWy0UXJaVx +dWPDRsGixw1KdLGpfRy9mL2Z8CKTRffAU5ToYkv8nNjagXIik5/pHlhCii6ejpsT2wSLHk/upUXP +eyZOPoyjfeBnug+epQMuMuJzguG5PNoFfqb7YItCdJG6PQ4ePL9c4UFmx9ABSka6qEQX7TZ4d+CF +VSoH1idu8nwzZpFSdP9ZzA8PVI7PM90HO9Sii5F+s5g3DFEPn8kz3QOjIkQXL/ocvbRTxOjd4vFW +0eLYGSW6WOpv8HndowbPuCN0gJKRXZGie8xifjFycBbdBy9Fxl3s3uNp7BXRY7PoPugQHXhR/LKX +ofdmRw+dsS50gJKRfRqii1fmeBi5d6bGyLHWoQOUjAzXEV28it9nfXKlzsCr9ocOUDIS9c10i9fQ +4x4o0xq334zQAUpCCg7qiS4OYcdtkspCcPj10BFKQnqUaIqe+xxy2COvag7bj0XH015zxgnR7RHg +sFGLvyy6T44M0Q2/mFIEG3WJ9qCHD4SOUBJytJ12/MUx1Ky7X39MFt0Dx9/QF0CcOAkZ84nl+kOy +6B44VWYgupg7HzDkpN0GI7LoHpg9zUR0cdp9xO3FJgMeDlsYIzlp28pIdNHTdcDBBi8RlZxh0fGc +PGYmutjrNl7nzWbDnTkbOkJJyJyBZiKI9U5ZzPM2GQ7HontgWL6hCmKKS85JF+PRWHQ854xeq6op +s9dhjfFgLLoHBqca6yBKyi0H25tnPBbf3j2QNsVYByE62O2u99U5NdF4pse7OEJL4MBhC9HFNpuh +3rQZaspboSOUhDyUYSO6WGE+0gzT74RqVp4PHaEkpON6K9EXTjYd6MLbVgO94z+ZruUxNd1KCxEz +bLNx6qLdOMXnQkcoCXkg104MUWx0IL19ZCoLNcyl0BFKQsZYiiFEuwsGw7xrO0p+4rSQSh762qoh +RKej2qNopLKw6PFjgrUcBr2YJ9s+Q1h0L+x1EF23F/PEXvZDsOgeWGqvh9DsxfykzaJfneg+Eqpa +Ou85iZ7+g+gR1pa5jMCie2CxiyJCrHogaoA5J5wGOMai4znkJrrIf0FtXzuVhWAmi47nWTdNhDio +NL8/utCFmlYsOp73XUX/QGn+Plfz01h0PKNcVbmoNO/2cVAlOia/gqmP6+1XDFead/s4YNH9MNdV +lX1K8+NczbPoHrDc8bxNB6X5p1zNtyoPHaEkxDD3oCkfKs3f62q+VdvQEUo+tmrXJKDYpbS/xdV8 +WWnoECUf7qKPVNp3/iIs812FugWiXXKGZKfSvnaZEYqP9DftGU0KnJ/p6lpjI13Nvx2ur3fyollG +jmaU0vxlV/MHQwcoGXEW/WOleZ3Ks0r26V0GY4Lzd/oipfkFrua7hw5QMuKsyidK8843kkWa18EY +oFUEWoWyCs3WElfzXpuJtFScH7pdVNbnux2bEYAaN0xTNBo7qFEmsHbWr0dJ8F7oACUjzjN9tMr6 +bLNSUhIeCx2gOFL+j+M0kPPWqlL0YWZV6iQ4FrNqVmz/JyaZYg44nluMSHg4Z5WTXp8J8QjCsNnx +CXYE+398JT47DXrdPGxFT7OoaNMQZL1xisEXH4pLrKN4+Cfipa3xGMhZ9CUq6wdckluqyJvkPwQj +Oq1PjMI2rX8StcAJwnlxRin6yzFH68uz/IfgcTHlt+MR6kiqRFfHE4Sz6E+prN9hV9HmNhn+b7xL +hDiTGO0jqkW3Kedjil/RV9tVtLmN/8rfVed1dydG87ca0fOgzXKkOIuuXDKzrWhTh/c6Q5NzRML0 +9qwRXSz319v2Fs5r74tV1vfkOFo/5vkEdO9uVaOsf9J3mLW4JbqIjfE8kPPizDiV9UH2JShq+PSU +16u/1QUyvchzlPVo/Tu3rnqlZ3+cZ/pnKusTXa0Pwbf7rEdtF8jCPn6DrEmd6OKYn47GtTg/05Uv +m8+7Wh9a4PHaT9YeBV44wGuMdam9vVdydbDPgZwXZ5TbYC5ljKop8XjpW+uuPcdXg3gz6oluX2lb +B+fjUp+rrF9ztb7Z46WPqhslHut+Gty+vVfyxRF/AzmfZ1LWGnKqXVXFB7rXYc6Xt0fJ/af+hjGg +gegRWSQuuJ97X6oy/5ir9U7errxBErVTXxoYrX/c4NpHu1uUM2+WqyzKUw4G/TXlXPF14dcL6w/z +vK9hjHi5oejejoq5i/4jlXn78qC3GK57IYb0jjUY5itPw5jxdSPRs7XK9Jmz9RtXWcarzPdMUNGz +VjYc5pqfYQy5o5HoIv0ZL+PMf8VVlhsq8/otswm+8HLVBxq3nTRuWeCF7Y1FF7F7fIwzYqOrLMrz +TKNdrXvJaprTJD87MURv00R0McXHWuHxj1xlURYKPe1qfa6Pa266IPXPPAxjTsemoov8rz0E4FNX +WZQvvh+7Wu+geyH69JCkT/vfwtZBJrpoh29XVWrYVLkpypOLNxNQdFkZnKXuZgFIRRezytHjlDuf +UVaKbtu5pY5N8Mgukw2TGHk0XaWii+EjwOMMM2+12pBs5fulaQ9l/6Jfkx7rGOduGAAhutil20FD +k0vOovdWmXc+orFL90I06S3v/JkYaZIdCdHF+9hxLJoqNyRXebTHeeMWLPpq4hz+IbR+VpCii7HQ +cWyaKjcgWym68x4ethBF7UmZJnyJV9ACWvTsZchx0lYKN/IGKay77+FB9xcvkWvOmh2IPEOLLhYi +EznXOucd9VdYb+9ck2Cn9pVEc5RO1nzXk4xmKEQXmX1x45w946iK8qTRUeeaBI8DY6ooZLjFl45G +qEQXZ/q7D3CLGascVVGeKTzlXJPgNd0LiUbVreYTd/MAlKKL4jaocVrHHFUpfFNh/WSZq+gfwyK6 +TDXM73qUUp+uStHFtBmgcda5Zhimq7aBLjiv98Eqit3IUw2TGNXqIkQXQ0CFKu7IFG6kT1VYP++6 +CqCuXWXAGPU/7su+9dRidYToohOmUEWWqyrLixTWp7t+EKLesNq8ox7mJe+C6vBklOiiA6Q8cmvX +9+teWSrrrq+J6oJl2kx/I2KYxBA9K1J0sQMy0PHHHUVXJXx+HXMVHXI0cFhkJwMfZzXMmRotOqiC +5tafZruosn61wrZrTYJeSxH7S/O/iBzoZ3ERNYoXNERXJ4zqM76fgy6ZHRWWpy530nwmprSURneJ +i+6jANASPe8+zGBFDqcju6mWDPo71SRYcBZydToncn/Pv6IaaIkueoFK6w22z1LPUB3cG+NQk6Cw +JyY1fZnOYP+8wLuiGvTREl3sfhAzXPsXrUVfpzDrUJMgH7TBcKNQZ7TfB59NsUNvpguxMgs04HOW +X1cZqpoJfa01vwhachwT0xru7bgUaoxCV3RxDFUN6wG72r0xlehPWEqeswQkwup39Ab0W+VEF83b +eyUbUUW31lrVJ4g9rDB53U7zVFRNrellmiM2N9HF5lLQmO23WQi0ar/Col1Ngs2oKpHl2u0lr873 +K6ceRfqiiy9gHj9mvv2iLOloVZNgC+qY93z9Ikof+a1cpsm/+AODMO0sQA3b+5ipRFOmK8xpfS41 +sjde29koDBaYy/7Qn5T6PG8iOrBQxdqfG4q0Mk1h7TNjzU/gCnaapEnPvORRS20eNBIdmKFxapGZ +Su+cVxgba6r5x7hu6UZ3mWLfJWi1MHmmV5IHzMBbZrRgXnxOYcqwJkEM2KVncqHJyKlea/XpYvD2 +Xk36Dfcxa5lo8mDPV90Ytxhdw5Ai3DXcY3YMLPW8+5DumIouYhNxg88wqD40s1xhyOhRMRLYqqir +4ZGdZio6bkG2ktmjtIedpmp0ZPD+nInMFl4bdVKmMVMwG3qO/JGx6CJ/nfuwdazopTnqG6qzeiM1 +jQjx6SBt16IZZlwy6wzqdLETA8xFFx8hew5N1DzIqhT9Q13XO9wNdP2oeWXrWEL06HrAQnTxTTnQ +g3V606Wdav1Ms5Hr8jXQ2FkUuvnj7T5ENMVKdDH8KNCFUq3aIUrRow+nVTEQ23DvkNagDfmT1e7j +urPHSnSxC7ov/JlG2512qoV/rZoEC7Cd8KzK0f7LIg8aGtPfTnRwJt630YnMG1Wia6zpLhyLPb/w +jNWxvF+86T6yO3a3d4EupNHm7ajxlDvRJZHufvctNm6T7HLzfjEArqAF4//AUnQBLVSRMizqXWyo +QvT2kfvZncBd8L7+zi5ov3jAg4bGLKuwFT0H23G8R091LsQ3irvziIicqex3kS+elay1LXr6C1zG +vwP2oov14AZ+zyiLVZxQiF6qrkmQ+gQ4aMOsq51UgE4Vu/GevehiN7gNTdZVxWAlijxKdU2Cu9C9 +LkfYn96vAO5c2OMw04Uo7ujuQH3uVtQAVIl+Ll/h5Gn4CSXDkwANRAeW8bHHZaYLUQbuRTzvSzJV +5aAiTUBRk2AK9s2jCptFmTrRvfe01cFNdDEEuZZdxWQqyVEl+gFyg3MovqOtU4+gCvTrhRWOoouD +6JtnH+LFuFMB/TetqX8pN3GnomqxW5RJLtFFB/RJ7vPyvStVku/LMemfxDw0IXowJlyo+Fd4l8xx +Fl18XAB2aeto2Rf7McUuft9CmWPtPCyEdNVMXyJFB542s8dddA/1TsfL1jhXkgupj0o/8Hd5OG18 +tswxVhX490oLAKKrm1xb0V8W3MLFBbLfys9SL3/aQ7DKnUvQVgDP4dqDED33UbhbadJa3pclRxr7 +SNfdy3qbjxmJQfoSKfq/9uCXMQjRRS98N8mj0lPN7QY0/t3nMdnv9uE7D6XUb4ltLzr+rmjBCoTo +IjbG3ZPGSJMcVzV8Jl7YJfMmZyyk9F1jXBZl6kTHHtiyBCO6SMUvgqSMmSkbqUu9DbNJ0o0WVEGR +RixFxKkC2y/DEpDoYubD7r40Zr/0QMzB2pIU8z6TJjxf2e80KEXDltjWoj/lT0p9xoFEFxs9ZGkd +l1YvuDWTD0hPQy5ccsRLnBq1xG7eoi9GiS5Kyj24t0w2mXMW90hJ6Su9+b/jaUMjqtCvtuhLfIqp +y1iY6GIfuoFfFfJciF0vj5WWVd/soUtsFW85d46oFT0hOvc8hRNd7PRRL+vlEtlQu2X/MXcL+FRU +LW3vQoWo4t94VVMTpOh+2tJ0fk1z9DPXPcXoiHOrv9uiwzpIuHAIKbpY7MXHFYU6Yw+F9ZtpjHOf +7nqij/KmpAFY0bP9LC1/q5EF/nGprxAZ1zZRiY5sBWYNVnSx0M/WYWSS425QnWoJS53K1DcWHdgK +zB7oM72SD/y4OUed5DhkqvsQBM8UIsNTAW3vaQt4posrvhxdrGh91d1foa7+rq3FGon+b715agBa +9E7ePP2KOuecgU2wasgabHgqOnj0VZvmIzpxKEoc3uNumuY9sOj/zqezuqBF9/RMr+TBq/I3ql6H +/Ky21/A5WPSEaOLSXESft4auSHQFnHJRH8iGaj3RO/3Sn6/aoEU/6MfNs6+qBp3pL0PsPrDom5NR +9M0FPrz8Nl89arq3U0h2leRp0WclguhLwKKXeOhMM+Le6KyS7if9xGcyix7NLLzoD3XSGXhIVy/x +sWwUQor+7xNB9HvBop+Ai34tughRNYcn+4iPbUsgij9NhB5dz4JFvwt8Uae2affZy37RQ1ecr8Ci +/1kiNHHZgr69F0Dde0G7JU4VF/HfbmjRf+XpnIcR29AvclDvTDv35cNLuqBv73/+H+ImLc1/TGDR +z+membkNfB3+TrDov06Edk3vo0UvgLnW3+o04s5h0Ph8y6JHg/tkW9HNzoO3odWPHmHRo7kLlEb2 +lmY1bwkrQX2/q/khWPS/KA2hciNOg0X/BnMMemKZgw/ZP8V9F41BHpaqEt3TyqERaNGHIkSfP3ah +mxfDYVW+92gvE+jxn7CvHHagb+8I0Q+4nzNvhSpm2cepmFRT/vKcu0/OoGf62+531jstKyw3IBPU +NbDI8Z7TmL9KhG58aNFfcT3GcuTePHcvqnitFBGfrEJsfP7aS40MQ9CiD3EUfftmmCt3IVJe0DP9 ++98OrbgH0a+6pa5O1txS02IKoDwjWvSf/OfQiiea6KVdsM4sPFTgGp8sjZ5CRqJ7PM8XTHSX23uR +0ZaaFvvS7N2pZvV6rEPfJ+NMH2L/9r60n+YY6xfr55G+4fjt1sZyMZjiJ34q4pixCP3JZnuQ4ZJ2 +v9Srg6jycTIy3L7d1mHTmsTvvOzkDoYd6E82y5k+aYjmANmL5lT9foDu7x2TmB+OYePzY3RvERse +T4hn+rxxuo/OY7XFKU/q3+JnOXy7vW54jCNSdG+lEwzojhbd5u39rbm65jfVezG7T/cdQBx+zjo+ +B7QH0RTdz6FdM3ahP9kszoDJq4NJWNXw+VwU2baxjl22q58zdmuP0XJF33jc1AP9LbUPGtciPXVa +26+BllWI4Lf3RGir/F/AorczFX3GRU3LGT0lrwvj9VfwblpN9umHk1B09Ez/qLPZ+BNSdS1vk/79 +pOXaruXbTPa1yHXhSn6TCH030aK/YST6iHf1t9TktRY3mLxoPW4+2c9rFLYyocL+nTJxRS8zOfi3 +vcTA8mipicFGogw0Pj63AVQUtk500D6/E+hnelmp/th7jQSTl6O8lG9iQ4jXzpvF51yxmf1I0X0W +yAkl+jRt0Q1WV6rpIrXStszQwVZmk930H1Wk6J+FVtyH6LM1Bzb4yK7htNTMcfOMiFEmk/3kMbDo +PUMr7kF03Wf6UuNFD3kp3SP6a/C3fTSY7G1bgUVPhH4eaNH13t43dDe3vAMmeuU3u/Y2++xpLHok +n+qIPuYjC8vyqqpb7frjHdOd7Cy6jujRt/ceK3rZWCbqJ2vVJmlK9g69b3b47T0R+nnAF2ciX+TS +tLfUGnJTbm6frad63+xw0d8NrXiKhxe5qI7lfQdaWiaKZl+29/Xmhuj4lL6BjU/F+6EVr2QT+pNN +Lfr8JYW2lomeCLts7VWS/0xkfDqjWvbcomJHaMUreQks+jFlVubLlk/gKgjRbzq5+3FUatnRq9j4 +JERrh7lg0QfOUQw2wSVLjYiW41H5VhG9gedbfRLSVPxNaMUr2Rc/0Q2qg8nYKbd6r6PDua8ps8vm +gc/iV9xMCc/P4ib6aseO893lZns6u6yc7AVo0XeFVrySV+Ml+qNnHC0T0foM4PQoxTd7VMsgQxKi +tQNa9Hy56MN2OFu+LL8ASOuFMrpFK1r0n4VWvJLhaNGl5TW6Al6HiCkCqsJPthFEi54IrR0uxuP2 +ftThS62OufILeBTjN6nFLGx8Kj5IgDLQaNGl3+kzEFmA++QXcA3j93AqQLgiCdVUlCSA6FfiIfrr +iDSRL+QXACrpSIqOuEnVo+JEEoouvb2fRZwjJmQBVfd7lQqQ7rF8TSq+SULRpS9yadqH2xUskF/A +IEx1vwVUgL7AxichZjr6mZ4v64C5AXGklHim98cUeiNf5Cw3gikqhiah6MUXJINcQpwuJESfanUi +owkfFBAB2oSNT8Ur1EDNWXTZtlU54swR8dTdHoP4PYsqZGxfmFhOIvTzQD/Tv7tbMkjbMoBl4kXu +gOvybg1kedMO2PiIX/lsEhpI9Hdkp1HKEWeOiKeuWV4TyRCqVg56pv95AjRx6YQWXbZ30RZxeyf6 +NW8AiU7N9A+x8RG/bimil9oceW7Mz+UXMAyTbEaKbnFCX8nfeugjFlr072S3986IM0eb5RcAOph+ +lXrUuh3HasqvDRP4m4Po0rf3EaZ5azKIPlCgk4tkBQ3DNMtI/kI32a85iS57e2/veGimmlkF0guY +j/gHpUix1i9ro0ci9POIy+29RwnA8jdy0Xtg9j5blRMB2oaNj/ivl1KCExfRU34OsDyUaP6F2fvM +p7R4Fxsf8Zd3p4Tmlwfj8Z0O2ao6USC/BMQ/KOKxVIXradvG/NV/C615yi83g0VPlZ4xdO/EQ7fu +XQDxeyVVqWAJNj7ir12LkbtT8Hfga5IHDyI6cXu/DPF7CqXFl+AAfT89tOYp8/4e3GxupTR1ACE6 +1dtxFMTvKVQnN/TtPQFE3/r34GuSBw9xEmEosT31PsTvlS1opreHi35WNgziJMJQYs1sC8ZvaqYf +Agfo+xmhNU9p/9/B1zRF+g8ZsWsxhNipeBbi92GqoY572lRDvg/f2gEu+mGp6IhdC6q+9FiI37sf +JiK0Bhyg/xG+tQP89n5GentH7FpQq+MrIH5nPERE6GlwgH7zP0Nr7uGZLn02vgaw3I64vWPymjKp +CYj5N3Wb3zyZEhr47V3+yQYRnWgU8hjE714diQgtAwcoAUTfCn+Rk376IETfSLy93wfxO50qvg/J +iq1HUoouvb0jNqXbEaKPh/jdkkSH397lL3KIBRTq9v4ExO/CLCJC94MDVPFtaM3jJTpiAeUj4u39 +EYjfOVOJCIHy328TvrUD/jtdusjxIsDyG6XySxik3xFEQc4DRIQw74n1CN/aAS56P+kq408Blqla +lEWQZte5Y4gIYd4T6/FeaM3xoq+Srmwh1jKnESWMuur3a1LxQyJCe8EBEuNCa54y/0/Bl7RburSM +WMucOUx+CR0xHc77EhGaDA6QCN/aAS56TLqciVjLzCcqej4UgzhO1Ze6Dg6QOBRa85Qj/wt8SbF1 +3kQnTrFNx+Q1PUFECPNFWI/woh/9M/AlZXztS/RiolD33Zi8Jkr0r8ABEl+G1jzlFFp0+e0dsaxF +nVedg+mnFDfRt6SEpjNcdOnbO2JZi3qmn8T0XqBKv8NF/yS05imdfwW+pFXS73TECkf+BfkllH4K +cXw8EaE7wQES4Wt/n0KLvnu/bBjExy4100dgKrJTq6N9wQES4ctAd/7f4GQH+bEjxMcu9SIHyY6k +V0cxS/v1+D+hNU9J+frvsKrHpIszEwCWvyNyUAowyWz3EwHqDU4MEP83tOSVzOuZgbwk+Xc64mM3 +lTqZfgXiOLUkDipOWEvF/6OO6MSXF5A1b+Xf6YhX4JVnCf8xhTxXENb7Q0Uv/JIqcxJv5q8x7nZL +krFdNgLiFXgKdTIdU8jzacL6m06NZxox5J7QWtdjKibft5JubWT2EaWaD1OpIS6d2W5D9TXPgmzX +V5P+bFSnwvjSYymiUHMl3brKzCNegVdR6QiYqjBUa4fVmNqzlWykdm/DsQ6RWSrEeum5v3sAD8bd +VD7QaIjjVLfjItBML3w2ASoMNaEAMtl7ZclsI440xagcFEyKIdXtuAgz09s9ElpfghmAisfLpaI/ +sNDdsvxjMAWVYvgsYT0LcTAnZ1t5aHFp9jpvU8pn+guAc2wx6va+GKAKvfnVNdPddjvqWE5ikOaa +XyoXfSpA9N2tCZ8x2WZUi+vtzmtX2aeJg16Jww3b/uY1ZErf3iGiUzMdc0iZ2vF0Po31Kd3oL3FI +G+Xyqp0pXWVEPBjJZzomr4kSff8qJ7PZi4h9okSjr0PRZrnoTwJOrMaoZGLM0cVFhPXph12svvGD +0GJqc+609dpjpvQ7vQ3gbSijDeEtYgtPiFGE9bMuon8cvjykAX03Wl7m+iyZua8BO3nruxK+/gAi ++kjC+nT7fYmy5vA0r0/5i3aP4V5FMmsPx9xV6UXl+P4Qsg9GHWiZbts9Mrd5TfMaer9ic6nLpdmf +BwB9N+XLPpXsgayZUX3N11o2BiprPk/z+nQ+ZNEXN/0FZOQaiF5E+PkkJJmNaPuW8pbV4nT2KOIY +Z+Kz5y7jqy0cIDN0HrCsL//nVMlDiJ7NZIfdDTYtQ49NCC2dA0cXm76BLZTmeSP6aBX2IZzcH0OI +TjSDSjlnvjKdvYM4uNtcyDJsJZ2zRyo6YKaTor8OEf0DwvqlfFNLA6lkmebD/BVGMc3rLxUdMNPT +qQIhaZAMRqIDWMoc02Xpm4NTkoA2JscrcifJTCCSDEnRzxnPRRkHCeuG3SPzb4SWC8Q8g+MVudKZ +fmkmQHTqRa4tJK+phOghMLvMxEp3qph0M2Sddu1u+TP9pFHk5BS+STh3HNHqT5wgysmXGrg+MFmm ++S1+pHmDXjhAGjlAx7yFVP2n+VarSI15myg9azDTPwxfvx/Mfr32wnLRO9su5dcjpz/hWfuhCNE3 +Eu0wtUUv3htaIh+M18n+Xyi9vSPuwHmTCL/mQdrxUWXqdG/vu14PrY8fzmvU9V0onY/HATOdrPSW +YriUIIfqu3lK63BBck7zGiZETvacQbK/OwrIIc99kPLqVYjop+TGtf69bqJSrpKCuxdFbGnlSefj +EcBjlxa9A0L0aUQugkbNg9QknuY19FW/iMulaW++c9PUMnl713vFjGAgUZCyfWTX5peSeprXUD5a +lbmQ3Vv2Nz0A2dC06CMRohcTe6HzIm5SqT8KLUh8mKi640lFR7xr0aIvQoj+DnFqtUAteocWMM1r +KH+xkIyCPDUTkB0pv4dUAenMJm8+U4nqyZT6WGgp4kl/8tt4ovT3+wCikwn9kM5sZ6hCFwrRO+xP +aVEcH0ucapZnZyJesMn0bkheUz9qcYUUPfW+0CLEnzflk10uOiAfVpDJvp8jRCdbMJYQf/AFlTqd +1BxZIcsDkN/eEV9VZOonpLcOmRR7UPrzlvU0r09XSTUv+ZMXITp5pvg5iOhUUqz0w+PVr1NaLD2W +NZns8ndsRDEgMl/kBkR06m4t+fCYEr75TlC2L2gUEPntfSdAFvK8ISSvKYPKj2z64XFxe0oLp+Cx +hucS5aLvAMhCniWHlG/NpFLlGn949Fs2L3TME4CGpWrkr1ufAGQhZ/pEhOhkAs3lhr8b3uKn+S32 +fhcl+jaALOQzfQwig5E8gtdA9BhP8zrW3i5VI28l6lX0AYh2fHnUaaz6Hx6dqGdAy+SZ2uPh8g+r +LgBZyNt7V0QyWzYl+uW6n8Teax86zAnG+VulauTzEdFhl3yRax0DWM/dQ1iv+9rslBhFuxOLOz+l +RUdshF2jBk5zqwVUQ/Ygwvqtr83dK3iay7iwLY8SfQlAFqq1Tspgp1pAtVCbeDVfmz8P3+s+UXlk +I3ETRux+kifRLnznbpzez6k6otGPp7mC8k/kL3KIUp6PUoNiOrM9T1h/v/JpztNcjXxKILa8ye1r +RNIU/XGwo9vT/G1uxTKAKuQux5HIA6s6XCes7yXP5jFqlvoUvUcJQvTxJtfDaIAo2ns/aR3SbqqF +HGWOI4iZTosOaTvUAk+8eQYx0+mTC5D2M88ZXA6jA+JEEy064tglz3Q4iJrstOg3EaLfb3A5jA6I +muy0KpC8pvcMLofRAdF3cxlp/TRC9HGhY5R0IFrPryCtQ9rx9Qwdo6QDcXbxadL6UwjRD4WOUdKB +EH0Naf1phOhfho5R0oEQnb7/IvKa8nhFDs0AQBfysaT1R92ND7yzIHSMko55T+gUoVPzFGl9r7Pt +D5OidHPCce5xV2HoNy3XZLbDvBrni+tuDT0VM90xme3KOoOrYMwY/JpTKgr9TJ/oYrfXmvkG18AY +M8Gl7jstev88e6tD+xv4z9iQ5rA3Qt/e+xTa2szd0jZ0SFoCE6yPrv6UtJllm8xW9ryB54w9abYF +Hukls9W97Czu5A+1uHHD7sn+LGlwXczG3sqkr+ObULxlVY1kNGnvgE3btystuFZQGJ6xWKDbQlqz +6JK4akUPA3cZCIPNF+i2kcZOGndmOzHVwFcGxnXT6akQ3XCtL+fd46GvvqXyluFrPP1MLzVrx1fW +18BLBkx0Sxg90UcYdWa72WxbmycHG0xe4+nbew+DJl0rOWctONf138G60Fb0G0csaGEl2hOTs911 +9TpNG1mgaSKDP9QShGuaC3SLaBOajSNKikJfK1PLWr1v9h20Ba3GEelLjmq7xPhH61DN4/Tf6zwi +yibq+8PEg+kaso2k/1wjmW3UBn1vmDgxOfI1vjv9x+9G/W3xdX1PmPixNmqy76L/Nqo24dwW0zKv +2TG+2FZ0dTLb7s8LQl8aQ6JejVfc3pW1CUu49F9iM7nYSvT36L9afog/1BKdtO42ot9H/tGQQfpj +M8EYT73GK57pZHWTRXP0B2YCQn2zX6b/5Cv5X+TfCH0tjDbXpAt0Heg/kJe02cQfas2Js7KOjXPp +38tqHvRr4W0SmyF7mz7Z99G/HtO05sFm/lBrfhxo8mR/lf7xm4WNfrt+LKeiNkuuNfpmH07/tHFe +01Wu0d5cWdvwyX6R/uVDGfV/mNtldmjXGXvGv1NPy0707/bH6n+ofRXabcaJA5e1RJ9RrzPbprWh +nWZcub31phA9LbX2R4cfC+0wAyDtw+hn+oXarIlOnIqaJOwtjhJ9dk1e0/KeR0L7yqA4UD3Zr9A/ +OL6x6gdcMyi5qJrsimf60SFCZHPNoGRjxi4xi/6/R4aKad+GdpHB8w8v0U1ut/7+3LdC+8f4oC3d +8nTrm5yjxjAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAM +wzAMwzAMwzAMwzAMwzAMwzAMwzAMwzAMwzDJyf8H/RgaDtLOxBkAAAAldEVYdGRhdGU6Y3JlYXRl +ADIwMjEtMDMtMDJUMTg6MDA6MDArMDg6MDA9+rsMAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTAz +LTAyVDE4OjAwOjAwKzA4OjAwTKcDsAAAACB0RVh0c29mdHdhcmUAaHR0cHM6Ly9pbWFnZW1hZ2lj +ay5vcme8zx2dAAAAGHRFWHRUaHVtYjo6RG9jdW1lbnQ6OlBhZ2VzADGn/7svAAAAGHRFWHRUaHVt +Yjo6SW1hZ2U6OkhlaWdodAA1MDB4mAPsAAAAF3RFWHRUaHVtYjo6SW1hZ2U6OldpZHRoADUwMOtp +U7EAAAAZdEVYdFRodW1iOjpNaW1ldHlwZQBpbWFnZS9wbmc/slZOAAAAF3RFWHRUaHVtYjo6TVRp +bWUAMTYxNDY3OTIwMCAf/wcAAAATdEVYdFRodW1iOjpTaXplADExMjczQkI7FGLOAAAARnRFWHRU +aHVtYjo6VVJJAGZpbGU6Ly8vYXBwL3RtcC9pbWFnZWxjL2ltZ3ZpZXcyXzlfMTYwOTkwMzUxMTcy +MzMzODZfOTJfWzBdRG9xdgAAAABJRU5ErkJggg==" ></image> +</svg> diff --git a/_web/src/assets/welcome.png b/_web/src/assets/welcome.png new file mode 100644 index 0000000..94abec2 --- /dev/null +++ b/_web/src/assets/welcome.png Binary files differ diff --git a/_web/src/components/ArticleListContent/ArticleListContent.vue b/_web/src/components/ArticleListContent/ArticleListContent.vue new file mode 100644 index 0000000..8f39978 --- /dev/null +++ b/_web/src/components/ArticleListContent/ArticleListContent.vue @@ -0,0 +1,89 @@ +<template> + <div class="antd-pro-components-article-list-content-index-listContent"> + <div class="description"> + <slot> + {{ description }} + </slot> + </div> + <div class="extra"> + <a-avatar :src="avatar" size="small" /> + <a :href="href">{{ owner }}</a> 发布在 <a :href="href">{{ href }}</a> + <em>{{ updateAt | moment }}</em> + </div> + </div> +</template> + +<script> +export default { + name: 'ArticleListContent', + props: { + prefixCls: { + type: String, + default: 'antd-pro-components-article-list-content-index-listContent' + }, + description: { + type: String, + default: '' + }, + owner: { + type: String, + required: true + }, + avatar: { + type: String, + required: true + }, + href: { + type: String, + required: true + }, + updateAt: { + type: String, + required: true + } + } +} +</script> + +<style lang="less" scoped> +@import '../index.less'; + +.antd-pro-components-article-list-content-index-listContent { + .description { + max-width: 720px; + line-height: 22px; + } + .extra { + margin-top: 16px; + color: @text-color-secondary; + line-height: 22px; + + & /deep/ .ant-avatar { + position: relative; + top: 1px; + width: 20px; + height: 20px; + margin-right: 8px; + vertical-align: top; + } + + & > em { + margin-left: 16px; + color: @disabled-color; + font-style: normal; + } + } +} + +@media screen and (max-width: @screen-xs) { + .antd-pro-components-article-list-content-index-listContent { + .extra { + & > em { + display: block; + margin-top: 8px; + margin-left: 0; + } + } + } +} +</style> diff --git a/_web/src/components/ArticleListContent/index.js b/_web/src/components/ArticleListContent/index.js new file mode 100644 index 0000000..37d35c7 --- /dev/null +++ b/_web/src/components/ArticleListContent/index.js @@ -0,0 +1,3 @@ +import ArticleListContent from './ArticleListContent' + +export default ArticleListContent diff --git a/_web/src/components/AvatarList/Item.vue b/_web/src/components/AvatarList/Item.vue new file mode 100644 index 0000000..26e149e --- /dev/null +++ b/_web/src/components/AvatarList/Item.vue @@ -0,0 +1,46 @@ +<template> + <tooltip v-if="tips !== ''"> + <template slot="title">{{ tips }}</template> + <avatar :size="avatarSize" :src="src" /> + </tooltip> + <avatar v-else :size="avatarSize" :src="src" /> +</template> + +<script> +import Avatar from 'ant-design-vue/es/avatar' +import Tooltip from 'ant-design-vue/es/tooltip' + +export default { + name: 'AvatarItem', + components: { + Avatar, + Tooltip + }, + props: { + tips: { + type: String, + default: '', + required: false + }, + src: { + type: String, + default: '' + } + }, + data () { + return { + size: this.$parent.size + } + }, + computed: { + avatarSize () { + return this.size !== 'mini' && this.size || 20 + } + }, + watch: { + '$parent.size' (val) { + this.size = val + } + } +} +</script> diff --git a/_web/src/components/AvatarList/List.vue b/_web/src/components/AvatarList/List.vue new file mode 100644 index 0000000..446ceeb --- /dev/null +++ b/_web/src/components/AvatarList/List.vue @@ -0,0 +1,99 @@ +<!-- +<template> + <div :class="[prefixCls]"> + <ul> + <slot></slot> + <template v-for="item in filterEmpty($slots.default).slice(0, 3)"></template> + + <template v-if="maxLength > 0 && filterEmpty($slots.default).length > maxLength"> + <avatar-item :size="size"> + <avatar :size="size !== 'mini' && size || 20" :style="excessItemsStyle">{{ `+${maxLength}` }}</avatar> + </avatar-item> + </template> + </ul> + </div> +</template> +--> + +<script> +import Avatar from 'ant-design-vue/es/avatar' +import AvatarItem from './Item' +import { filterEmpty } from '@/components/_util/util' + +export default { + AvatarItem, + name: 'AvatarList', + components: { + Avatar, + AvatarItem + }, + props: { + prefixCls: { + type: String, + default: 'ant-pro-avatar-list' + }, + /** + * 头像大小 类型: large、small 、mini, default + * 默认值: default + */ + size: { + type: [String, Number], + default: 'default' + }, + /** + * 要显示的最大项目 + */ + maxLength: { + type: Number, + default: 0 + }, + /** + * 多余的项目风格 + */ + excessItemsStyle: { + type: Object, + default: () => { + return { + color: '#f56a00', + backgroundColor: '#fde3cf' + } + } + } + }, + data () { + return {} + }, + methods: { + getItems (items) { + const classString = { + [`${this.prefixCls}-item`]: true, + [`${this.size}`]: true + } + + if (this.maxLength > 0) { + items = items.slice(0, this.maxLength) + items.push((<Avatar size={ this.size } style={ this.excessItemsStyle }>{`+${this.maxLength}`}</Avatar>)) + } + const itemList = items.map((item) => ( + <li class={ classString }>{ item }</li> + )) + return itemList + } + }, + render () { + const { prefixCls, size } = this.$props + const classString = { + [`${prefixCls}`]: true, + [`${size}`]: true + } + const items = filterEmpty(this.$slots.default) + const itemsDom = items && items.length ? <ul class={`${prefixCls}-items`}>{ this.getItems(items) }</ul> : null + + return ( + <div class={ classString }> + { itemsDom } + </div> + ) + } +} +</script> diff --git a/_web/src/components/AvatarList/index.js b/_web/src/components/AvatarList/index.js new file mode 100644 index 0000000..dd6bb8b --- /dev/null +++ b/_web/src/components/AvatarList/index.js @@ -0,0 +1,4 @@ +import AvatarList from './List' +import './index.less' + +export default AvatarList diff --git a/_web/src/components/AvatarList/index.less b/_web/src/components/AvatarList/index.less new file mode 100644 index 0000000..9ce073f --- /dev/null +++ b/_web/src/components/AvatarList/index.less @@ -0,0 +1,60 @@ +@import "../index"; + +@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list"; +@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item"; + +.@{avatar-list-prefix-cls} { + display: inline-block; + + ul { + list-style: none; + display: inline-block; + padding: 0; + margin: 0 0 0 8px; + font-size: 0; + } +} + +.@{avatar-list-item-prefix-cls} { + display: inline-block; + font-size: @font-size-base; + margin-left: -8px; + width: @avatar-size-base; + height: @avatar-size-base; + + :global { + .ant-avatar { + border: 1px solid #fff; + cursor: pointer; + } + } + + &.large { + width: @avatar-size-lg; + height: @avatar-size-lg; + } + + &.small { + width: @avatar-size-sm; + height: @avatar-size-sm; + } + + &.mini { + width: 20px; + height: 20px; + + :global { + .ant-avatar { + width: 20px; + height: 20px; + line-height: 20px; + + .ant-avatar-string { + font-size: 12px; + line-height: 18px; + } + } + } + } +} + diff --git a/_web/src/components/AvatarList/index.md b/_web/src/components/AvatarList/index.md new file mode 100644 index 0000000..dc9c092 --- /dev/null +++ b/_web/src/components/AvatarList/index.md @@ -0,0 +1,64 @@ +# AvatarList 用户头像列表 + + +一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。 + + + +引用方式: + +```javascript +import AvatarList from '@/components/AvatarList' +const AvatarListItem = AvatarList.AvatarItem + +export default { + components: { + AvatarList, + AvatarListItem + } +} +``` + + + +## 代码演示 [demo](https://pro.loacg.com/test/home) + +```html +<avatar-list size="mini"> + <avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" /> + <avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" /> + <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" /> +</avatar-list> +``` +或 +```html +<avatar-list :max-length="3"> + <avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" /> + <avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" /> + <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" /> + <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" /> + <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" /> + <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" /> + <avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" /> +</avatar-list> +``` + + + +## API + +### AvatarList + +| 参数 | 说明 | 类型 | 默认值 | +| ---------------- | -------- | ---------------------------------- | --------- | +| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` | +| maxLength | 要显示的最大项目 | number | - | +| excessItemsStyle | 多余的项目风格 | CSSProperties | - | + +### AvatarList.Item + +| 参数 | 说明 | 类型 | 默认值 | +| ---- | ------ | --------- | --- | +| tips | 头像展示文案 | string | - | +| src | 头像图片连接 | string | - | + diff --git a/_web/src/components/Charts/Bar.vue b/_web/src/components/Charts/Bar.vue new file mode 100644 index 0000000..4482845 --- /dev/null +++ b/_web/src/components/Charts/Bar.vue @@ -0,0 +1,62 @@ +<template> + <div :style="{ padding: '0 0 32px 32px' }"> + <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4> + <v-chart + height="254" + :data="data" + :forceFit="true" + :padding="['auto', 'auto', '40', '50']"> + <v-tooltip /> + <v-axis /> + <v-bar position="x*y"/> + </v-chart> + </div> +</template> + +<script> +export default { + name: 'Bar', + props: { + title: { + type: String, + default: '' + }, + data: { + type: Array, + default: () => { + return [] + } + }, + scale: { + type: Array, + default: () => { + return [{ + dataKey: 'x', + min: 2 + }, { + dataKey: 'y', + title: '时间', + min: 1, + max: 22 + }] + } + }, + tooltip: { + type: Array, + default: () => { + return [ + 'x*y', + (x, y) => ({ + name: x, + value: y + }) + ] + } + } + }, + data () { + return { + } + } +} +</script> diff --git a/_web/src/components/Charts/ChartCard.vue b/_web/src/components/Charts/ChartCard.vue new file mode 100644 index 0000000..fc1f425 --- /dev/null +++ b/_web/src/components/Charts/ChartCard.vue @@ -0,0 +1,120 @@ +<template> + <a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false"> + <div class="chart-card-header"> + <div class="meta"> + <span class="chart-card-title"> + <slot name="title"> + {{ title }} + </slot> + </span> + <span class="chart-card-action"> + <slot name="action"></slot> + </span> + </div> + <div class="total"> + <slot name="total"> + <span>{{ typeof total === 'function' && total() || total }}</span> + </slot> + </div> + </div> + <div class="chart-card-content"> + <div class="content-fix"> + <slot></slot> + </div> + </div> + <div class="chart-card-footer"> + <div class="field"> + <slot name="footer"></slot> + </div> + </div> + </a-card> +</template> + +<script> +export default { + name: 'ChartCard', + props: { + title: { + type: String, + default: '' + }, + total: { + type: [Function, Number, String], + required: false, + default: null + }, + loading: { + type: Boolean, + default: false + } + } +} +</script> + +<style lang="less" scoped> + .chart-card-header { + position: relative; + overflow: hidden; + width: 100%; + + .meta { + position: relative; + overflow: hidden; + width: 100%; + color: rgba(0, 0, 0, .45); + font-size: 14px; + line-height: 22px; + } + } + + .chart-card-action { + cursor: pointer; + position: absolute; + top: 0; + right: 0; + } + + .chart-card-footer { + border-top: 1px solid #e8e8e8; + padding-top: 9px; + margin-top: 8px; + + > * { + position: relative; + } + + .field { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 0; + } + } + + .chart-card-content { + margin-bottom: 12px; + position: relative; + height: 46px; + width: 100%; + + .content-fix { + position: absolute; + left: 0; + bottom: 0; + width: 100%; + } + } + + .total { + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; + white-space: nowrap; + color: #000; + margin-top: 4px; + margin-bottom: 0; + font-size: 30px; + line-height: 38px; + height: 38px; + } +</style> diff --git a/_web/src/components/Charts/Liquid.vue b/_web/src/components/Charts/Liquid.vue new file mode 100644 index 0000000..4019fb1 --- /dev/null +++ b/_web/src/components/Charts/Liquid.vue @@ -0,0 +1,67 @@ +<template> + <div> + <v-chart + :forceFit="true" + :height="height" + :width="width" + :data="data" + :scale="scale" + :padding="0"> + <v-tooltip /> + <v-interval + :shape="['liquid-fill-gauge']" + position="transfer*value" + color="" + :v-style="{ + lineWidth: 10, + opacity: 0.75 + }" + :tooltip="[ + 'transfer*value', + (transfer, value) => { + return { + name: transfer, + value, + }; + }, + ]" + ></v-interval> + <v-guide + v-for="(row, index) in data" + :key="index" + type="text" + :top="true" + :position="{ + gender: row.transfer, + value: 45 + }" + :content="row.value + '%'" + :v-style="{ + fontSize: 100, + textAlign: 'center', + opacity: 0.75, + }" + /> + </v-chart> + </div> +</template> + +<script> +export default { + name: 'Liquid', + props: { + height: { + type: Number, + default: 0 + }, + width: { + type: Number, + default: 0 + } + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/components/Charts/MiniArea.vue b/_web/src/components/Charts/MiniArea.vue new file mode 100644 index 0000000..58fe92c --- /dev/null +++ b/_web/src/components/Charts/MiniArea.vue @@ -0,0 +1,56 @@ +<template> + <div class="antv-chart-mini"> + <div class="chart-wrapper" :style="{ height: 46 }"> + <v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 0, 18, 0]"> + <v-tooltip /> + <v-smooth-area position="x*y" /> + </v-chart> + </div> + </div> +</template> + +<script> +import moment from 'moment' +const data = [] +const beginDay = new Date().getTime() + +for (let i = 0; i < 10; i++) { + data.push({ + x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), + y: Math.round(Math.random() * 10) + }) +} + +const tooltip = [ + 'x*y', + (x, y) => ({ + name: x, + value: y + }) +] +const scale = [{ + dataKey: 'x', + min: 2 +}, { + dataKey: 'y', + title: '时间', + min: 1, + max: 22 +}] + +export default { + name: 'MiniArea', + data () { + return { + data, + tooltip, + scale, + height: 100 + } + } +} +</script> + +<style lang="less" scoped> + @import "chart"; +</style> diff --git a/_web/src/components/Charts/MiniBar.vue b/_web/src/components/Charts/MiniBar.vue new file mode 100644 index 0000000..beac404 --- /dev/null +++ b/_web/src/components/Charts/MiniBar.vue @@ -0,0 +1,57 @@ +<template> + <div class="antv-chart-mini"> + <div class="chart-wrapper" :style="{ height: 46 }"> + <v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 5, 18, 5]"> + <v-tooltip /> + <v-bar position="x*y" /> + </v-chart> + </div> + </div> +</template> + +<script> +import moment from 'moment' +const data = [] +const beginDay = new Date().getTime() + +for (let i = 0; i < 10; i++) { + data.push({ + x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'), + y: Math.round(Math.random() * 10) + }) +} + +const tooltip = [ + 'x*y', + (x, y) => ({ + name: x, + value: y + }) +] + +const scale = [{ + dataKey: 'x', + min: 2 +}, { + dataKey: 'y', + title: '时间', + min: 1, + max: 30 +}] + +export default { + name: 'MiniBar', + data () { + return { + data, + tooltip, + scale, + height: 100 + } + } +} +</script> + +<style lang="less" scoped> + @import "chart"; +</style> diff --git a/_web/src/components/Charts/MiniProgress.vue b/_web/src/components/Charts/MiniProgress.vue new file mode 100644 index 0000000..e691363 --- /dev/null +++ b/_web/src/components/Charts/MiniProgress.vue @@ -0,0 +1,75 @@ +<template> + <div class="chart-mini-progress"> + <div class="target" :style="{ left: target + '%'}"> + <span :style="{ backgroundColor: color }" /> + <span :style="{ backgroundColor: color }"/> + </div> + <div class="progress-wrapper"> + <div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height }"></div> + </div> + </div> +</template> + +<script> +export default { + name: 'MiniProgress', + props: { + target: { + type: Number, + default: 0 + }, + height: { + type: String, + default: '10px' + }, + color: { + type: String, + default: '#13C2C2' + }, + percentage: { + type: Number, + default: 0 + } + } +} +</script> + +<style lang="less" scoped> + .chart-mini-progress { + padding: 5px 0; + position: relative; + width: 100%; + + .target { + position: absolute; + top: 0; + bottom: 0; + + span { + border-radius: 100px; + position: absolute; + top: 0; + left: 0; + height: 4px; + width: 2px; + + &:last-child { + top: auto; + bottom: 0; + } + } + } + .progress-wrapper { + background-color: #f5f5f5; + position: relative; + + .progress { + transition: all .4s cubic-bezier(.08,.82,.17,1) 0s; + border-radius: 1px 0 0 1px; + background-color: #1890ff; + width: 0; + height: 100%; + } + } + } +</style> diff --git a/_web/src/components/Charts/MiniSmoothArea.vue b/_web/src/components/Charts/MiniSmoothArea.vue new file mode 100644 index 0000000..e5455c2 --- /dev/null +++ b/_web/src/components/Charts/MiniSmoothArea.vue @@ -0,0 +1,40 @@ +<template> + <div :class="prefixCls"> + <div class="chart-wrapper" :style="{ height: 46 }"> + <v-chart :force-fit="true" :height="100" :data="dataSource" :scale="scale" :padding="[36, 0, 18, 0]"> + <v-tooltip /> + <v-smooth-line position="x*y" :size="2" /> + <v-smooth-area position="x*y" /> + </v-chart> + </div> + </div> +</template> + +<script> +export default { + name: 'MiniSmoothArea', + props: { + prefixCls: { + type: String, + default: 'ant-pro-smooth-area' + }, + scale: { + type: [Object, Array], + required: true + }, + dataSource: { + type: Array, + required: true + } + }, + data () { + return { + height: 100 + } + } +} +</script> + +<style lang="less" scoped> + @import "smooth.area.less"; +</style> diff --git a/_web/src/components/Charts/Radar.vue b/_web/src/components/Charts/Radar.vue new file mode 100644 index 0000000..5ee88ad --- /dev/null +++ b/_web/src/components/Charts/Radar.vue @@ -0,0 +1,68 @@ +<template> + <v-chart :forceFit="true" height="400" :data="data" :padding="[20, 20, 95, 20]" :scale="scale"> + <v-tooltip></v-tooltip> + <v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid" /> + <v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid" /> + <v-legend dataKey="user" marker="circle" :offset="30" /> + <v-coord type="polar" radius="0.8" /> + <v-line position="item*score" color="user" :size="2" /> + <v-point position="item*score" color="user" :size="4" shape="circle" /> + </v-chart> +</template> + +<script> +const axis1Opts = { + dataKey: 'item', + line: null, + tickLine: null, + grid: { + lineStyle: { + lineDash: null + }, + hideFirstLine: false + } +} +const axis2Opts = { + dataKey: 'score', + line: null, + tickLine: null, + grid: { + type: 'polygon', + lineStyle: { + lineDash: null + } + } +} + +const scale = [ + { + dataKey: 'score', + min: 0, + max: 80 + }, { + dataKey: 'user', + alias: '类型' + } +] + +export default { + name: 'Radar', + props: { + data: { + type: Array, + default: null + } + }, + data () { + return { + axis1Opts, + axis2Opts, + scale + } + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/components/Charts/RankList.vue b/_web/src/components/Charts/RankList.vue new file mode 100644 index 0000000..afb56a1 --- /dev/null +++ b/_web/src/components/Charts/RankList.vue @@ -0,0 +1,77 @@ +<template> + <div class="rank"> + <h4 class="title">{{ title }}</h4> + <ul class="list"> + <li :key="index" v-for="(item, index) in list"> + <span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span> + <span>{{ item.name }}</span> + <span>{{ item.total }}</span> + </li> + </ul> + </div> +</template> + +<script> +export default { + name: 'RankList', + // ['title', 'list'] + props: { + title: { + type: String, + default: '' + }, + list: { + type: Array, + default: null + } + } +} +</script> + +<style lang="less" scoped> + + .rank { + padding: 0 32px 32px 72px; + + .list { + margin: 25px 0 0; + padding: 0; + list-style: none; + + li { + margin-top: 16px; + + span { + color: rgba(0, 0, 0, .65); + font-size: 14px; + line-height: 22px; + + &:first-child { + background-color: #f5f5f5; + border-radius: 20px; + display: inline-block; + font-size: 12px; + font-weight: 600; + margin-right: 24px; + height: 20px; + line-height: 20px; + width: 20px; + text-align: center; + } + &.active { + background-color: #314659; + color: #fff; + } + &:last-child { + float: right; + } + } + } + } + } + + .mobile .rank { + padding: 0 32px 32px 32px; + } + +</style> diff --git a/_web/src/components/Charts/TagCloud.vue b/_web/src/components/Charts/TagCloud.vue new file mode 100644 index 0000000..74d1b3f --- /dev/null +++ b/_web/src/components/Charts/TagCloud.vue @@ -0,0 +1,113 @@ +<template> + <v-chart :width="width" :height="height" :padding="[0]" :data="data" :scale="scale"> + <v-tooltip :show-title="false" /> + <v-coord type="rect" direction="TL" /> + <v-point position="x*y" color="category" shape="cloud" tooltip="value*category" /> + </v-chart> +</template> + +<script> +import { registerShape } from 'viser-vue' +const DataSet = require('@antv/data-set') + +const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png' + +const scale = [ + { dataKey: 'x', nice: false }, + { dataKey: 'y', nice: false } +] + +registerShape('point', 'cloud', { + draw (cfg, container) { + return container.addShape('text', { + attrs: { + fillOpacity: cfg.opacity, + fontSize: cfg.origin._origin.size, + rotate: cfg.origin._origin.rotate, + text: cfg.origin._origin.text, + textAlign: 'center', + fontFamily: cfg.origin._origin.font, + fill: cfg.color, + textBaseline: 'Alphabetic', + ...cfg.style, + x: cfg.x, + y: cfg.y + } + }) + } +}) + +export default { + name: 'TagCloud', + props: { + tagList: { + type: Array, + required: true + }, + height: { + type: Number, + default: 400 + }, + width: { + type: Number, + default: 640 + } + }, + data () { + return { + data: [], + scale + } + }, + watch: { + tagList: function (val) { + if (val.length > 0) { + this.initTagCloud(val) + } + } + }, + mounted () { + if (this.tagList.length > 0) { + this.initTagCloud(this.tagList) + } + }, + methods: { + initTagCloud (dataSource) { + const { height, width } = this + + const dv = new DataSet.View().source(dataSource) + const range = dv.range('value') + const min = range[0] + const max = range[1] + const imageMask = new Image() + imageMask.crossOrigin = '' + imageMask.src = imgUrl + imageMask.onload = () => { + dv.transform({ + type: 'tag-cloud', + fields: ['name', 'value'], + size: [width, height], + imageMask, + font: 'Verdana', + padding: 0, + timeInterval: 5000, // max execute time + rotate () { + let random = ~~(Math.random() * 4) % 4 + if (random === 2) { + random = 0 + } + return random * 90 // 0, 90, 270 + }, + fontSize (d) { + if (d.value) { + return ((d.value - min) / (max - min)) * (32 - 8) + 8 + } + return 0 + } + }) + this.data = dv.rows + } + } + } +} +</script> diff --git a/_web/src/components/Charts/TransferBar.vue b/_web/src/components/Charts/TransferBar.vue new file mode 100644 index 0000000..7f96f0b --- /dev/null +++ b/_web/src/components/Charts/TransferBar.vue @@ -0,0 +1,64 @@ +<template> + <div :style="{ padding: '0 0 32px 32px' }"> + <h4 :style="{ marginBottom: '20px' }">{{ title }}</h4> + <v-chart + height="254" + :data="data" + :scale="scale" + :forceFit="true" + :padding="['auto', 'auto', '40', '50']"> + <v-tooltip /> + <v-axis /> + <v-bar position="x*y"/> + </v-chart> + </div> +</template> + +<script> +const tooltip = [ + 'x*y', + (x, y) => ({ + name: x, + value: y + }) +] +const scale = [{ + dataKey: 'x', + title: '日期(天)', + alias: '日期(天)', + min: 2 +}, { + dataKey: 'y', + title: '流量(Gb)', + alias: '流量(Gb)', + min: 1 +}] + +export default { + name: 'Bar', + props: { + title: { + type: String, + default: '' + } + }, + data () { + return { + data: [], + scale, + tooltip + } + }, + created () { + this.getMonthBar() + }, + methods: { + getMonthBar () { + this.$http.get('/analysis/month-bar') + .then(res => { + this.data = res.result + }) + } + } +} +</script> diff --git a/_web/src/components/Charts/Trend.vue b/_web/src/components/Charts/Trend.vue new file mode 100644 index 0000000..2dce37e --- /dev/null +++ b/_web/src/components/Charts/Trend.vue @@ -0,0 +1,82 @@ +<template> + <div class="chart-trend"> + {{ term }} + <span>{{ rate }}%</span> + <span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span> + </div> +</template> + +<script> +export default { + name: 'Trend', + props: { + term: { + type: String, + default: '', + required: true + }, + percentage: { + type: Number, + default: null + }, + type: { + type: Boolean, + default: null + }, + target: { + type: Number, + default: 0 + }, + value: { + type: Number, + default: 0 + }, + fixed: { + type: Number, + default: 2 + } + }, + data () { + return { + trend: this.type && 'up' || 'down', + rate: this.percentage + } + }, + created () { + const type = this.type === null ? this.value >= this.target : this.type + this.trend = type ? 'up' : 'down' + this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed) + } +} +</script> + +<style lang="less" scoped> + .chart-trend { + display: inline-block; + font-size: 14px; + line-height: 22px; + + .trend-icon { + font-size: 12px; + + &.up, &.down { + margin-left: 4px; + position: relative; + top: 1px; + + i { + font-size: 12px; + transform: scale(.83); + } + } + + &.up { + color: #f5222d; + } + &.down { + color: #52c41a; + top: -1px; + } + } + } +</style> diff --git a/_web/src/components/Charts/chart.less b/_web/src/components/Charts/chart.less new file mode 100644 index 0000000..e04fa09 --- /dev/null +++ b/_web/src/components/Charts/chart.less @@ -0,0 +1,13 @@ +.antv-chart-mini { + position: relative; + width: 100%; + + .chart-wrapper { + position: absolute; + bottom: -28px; + width: 100%; + +/* margin: 0 -5px; + overflow: hidden;*/ + } +} \ No newline at end of file diff --git a/_web/src/components/Charts/smooth.area.less b/_web/src/components/Charts/smooth.area.less new file mode 100644 index 0000000..eabdb75 --- /dev/null +++ b/_web/src/components/Charts/smooth.area.less @@ -0,0 +1,14 @@ +@import "../index"; + +@smoothArea-prefix-cls: ~"@{ant-pro-prefix}-smooth-area"; + +.@{smoothArea-prefix-cls} { + position: relative; + width: 100%; + + .chart-wrapper { + position: absolute; + bottom: -28px; + width: 100%; + } +} \ No newline at end of file diff --git a/_web/src/components/CountDown/CountDown.vue b/_web/src/components/CountDown/CountDown.vue new file mode 100644 index 0000000..575dd4a --- /dev/null +++ b/_web/src/components/CountDown/CountDown.vue @@ -0,0 +1,102 @@ +<template> + <span> + {{ lastTime | format }} + </span> +</template> + +<script> + +function fixedZero (val) { + return val * 1 < 10 ? `0${val}` : val +} + +export default { + name: 'CountDown', + props: { + format: { + type: Function, + default: undefined + }, + target: { + type: [Date, Number], + required: true + }, + onEnd: { + type: Function, + default: () => ({}) + } + }, + data () { + return { + dateTime: '0', + originTargetTime: 0, + lastTime: 0, + timer: 0, + interval: 1000 + } + }, + filters: { + format (time) { + const hours = 60 * 60 * 1000 + const minutes = 60 * 1000 + + const h = Math.floor(time / hours) + const m = Math.floor((time - h * hours) / minutes) + const s = Math.floor((time - h * hours - m * minutes) / 1000) + return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}` + } + }, + created () { + this.initTime() + this.tick() + }, + methods: { + initTime () { + let lastTime = 0 + let targetTime = 0 + this.originTargetTime = this.target + try { + if (Object.prototype.toString.call(this.target) === '[object Date]') { + targetTime = this.target + } else { + targetTime = new Date(this.target).getTime() + } + } catch (e) { + throw new Error('invalid target prop') + } + + lastTime = targetTime - new Date().getTime() + + this.lastTime = lastTime < 0 ? 0 : lastTime + }, + tick () { + const { onEnd } = this + + this.timer = setTimeout(() => { + if (this.lastTime < this.interval) { + clearTimeout(this.timer) + this.lastTime = 0 + if (typeof onEnd === 'function') { + onEnd() + } + } else { + this.lastTime -= this.interval + this.tick() + } + }, this.interval) + } + }, + beforeUpdate () { + if (this.originTargetTime !== this.target) { + this.initTime() + } + }, + beforeDestroy () { + clearTimeout(this.timer) + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/components/CountDown/index.js b/_web/src/components/CountDown/index.js new file mode 100644 index 0000000..35e954f --- /dev/null +++ b/_web/src/components/CountDown/index.js @@ -0,0 +1,3 @@ +import CountDown from './CountDown' + +export default CountDown diff --git a/_web/src/components/CountDown/index.md b/_web/src/components/CountDown/index.md new file mode 100644 index 0000000..fd46809 --- /dev/null +++ b/_web/src/components/CountDown/index.md @@ -0,0 +1,34 @@ +# CountDown 倒计时 + +倒计时组件。 + + + +引用方式: + +```javascript +import CountDown from '@/components/CountDown/CountDown' + +export default { + components: { + CountDown + } +} +``` + + + +## 代码演示 [demo](https://pro.loacg.com/test/home) + +```html +<count-down :target="new Date().getTime() + 3000000" :on-end="onEndHandle" /> +``` + + + +## API + +| 参数 | 说明 | 类型 | 默认值 | +|----------|------------------------------------------|-------------|-------| +| target | 目标时间 | Date | - | +| onEnd | 倒计时结束回调 | funtion | -| diff --git a/_web/src/components/DepartmentSelect/DepartmentSelect.vue b/_web/src/components/DepartmentSelect/DepartmentSelect.vue new file mode 100644 index 0000000..5203efd --- /dev/null +++ b/_web/src/components/DepartmentSelect/DepartmentSelect.vue @@ -0,0 +1,48 @@ + <template> + <a-tree-select + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + allowClear + :treeData="orgTree" + :placeholder="placeholder" + treeDefaultExpandAll + @change="onchange" + > + <span slot="title" slot-scope="{ id }">{{ id }}</span> + </a-tree-select> +</template> +<script> +import { getOrgTree } from '@/api/modular/system/orgManage' + +export default { + name: 'DepartSelect', + props: { + placeholder: { + type: String + }, + value: { + type: String + } + }, + data() { + return { + orgTree: [] + } + }, + created() { + this.getOrgData() + }, + methods: { + getOrgData() { + getOrgTree().then((res) => { + this.orgTree = res + }) + }, + /** + * 选择树机构,初始化机构名称于表单中 + */ + onchange (value) { + this.$emit('change', value) + } + } +} +</script> diff --git a/_web/src/components/DepartmentSelect/index.js b/_web/src/components/DepartmentSelect/index.js new file mode 100644 index 0000000..68ac87d --- /dev/null +++ b/_web/src/components/DepartmentSelect/index.js @@ -0,0 +1,3 @@ +import DepartmentSelect from './DepartmentSelect' + +export default DepartmentSelect diff --git a/_web/src/components/DescriptionList/DescriptionList.vue b/_web/src/components/DescriptionList/DescriptionList.vue new file mode 100644 index 0000000..7f98fec --- /dev/null +++ b/_web/src/components/DescriptionList/DescriptionList.vue @@ -0,0 +1,153 @@ +<template> + <div :class="['description-list', size, layout === 'vertical' ? 'vertical': 'horizontal']"> + <div v-if="title" class="title">{{ title }}</div> + <a-row> + <slot></slot> + </a-row> + </div> +</template> + +<script> +import { Col } from 'ant-design-vue/es/grid/' + +const Item = { + name: 'DetailListItem', + props: { + term: { + type: String, + default: '', + required: false + } + }, + inject: { + col: { + type: Number + } + }, + render () { + return ( + <Col {...{ props: responsive[this.col] }}> + <div class="term">{this.$props.term}</div> + <div class="content">{this.$slots.default}</div> + </Col> + ) + } +} + +const responsive = { + 1: { xs: 24 }, + 2: { xs: 24, sm: 12 }, + 3: { xs: 24, sm: 12, md: 8 }, + 4: { xs: 24, sm: 12, md: 6 } +} + +export default { + name: 'DetailList', + Item: Item, + components: { + Col + }, + props: { + title: { + type: String, + default: '', + required: false + }, + col: { + type: Number, + required: false, + default: 3 + }, + size: { + type: String, + required: false, + default: 'large' + }, + layout: { + type: String, + required: false, + default: 'horizontal' + } + }, + provide () { + return { + col: this.col > 4 ? 4 : this.col + } + } +} +</script> + +<style lang="less" scoped> + + .description-list { + + .title { + color: rgba(0,0,0,.85); + font-size: 14px; + font-weight: 500; + margin-bottom: 16px; + } + + /deep/ .term { + color: rgba(0,0,0,.85); + display: table-cell; + line-height: 20px; + margin-right: 8px; + padding-bottom: 16px; + white-space: nowrap; + + &:not(:empty):after { + content: ":"; + margin: 0 8px 0 2px; + position: relative; + top: -.5px; + } + } + + /deep/ .content { + color: rgba(0,0,0,.65); + display: table-cell; + min-height: 22px; + line-height: 22px; + padding-bottom: 16px; + width: 100%; + &:empty { + content: ' '; + height: 38px; + padding-bottom: 16px; + } + } + + &.small { + + .title { + font-size: 14px; + color: rgba(0, 0, 0, .65); + font-weight: normal; + margin-bottom: 12px; + } + /deep/ .term, .content { + padding-bottom: 8px; + } + } + + &.large { + /deep/ .term, .content { + padding-bottom: 16px; + } + + .title { + font-size: 16px; + } + } + + &.vertical { + .term { + padding-bottom: 8px; + } + /deep/ .term, .content { + display: block; + } + } + } +</style> diff --git a/_web/src/components/DescriptionList/index.js b/_web/src/components/DescriptionList/index.js new file mode 100644 index 0000000..7aed83d --- /dev/null +++ b/_web/src/components/DescriptionList/index.js @@ -0,0 +1,2 @@ +import DescriptionList from './DescriptionList' +export default DescriptionList diff --git a/_web/src/components/Dialog.js b/_web/src/components/Dialog.js new file mode 100644 index 0000000..78e95b2 --- /dev/null +++ b/_web/src/components/Dialog.js @@ -0,0 +1,113 @@ +import Modal from 'ant-design-vue/es/modal' +export default (Vue) => { + function dialog (component, componentProps, modalProps) { + const _vm = this + modalProps = modalProps || {} + if (!_vm || !_vm._isVue) { + return + } + let dialogDiv = document.querySelector('body>div[type=dialog]') + if (!dialogDiv) { + dialogDiv = document.createElement('div') + dialogDiv.setAttribute('type', 'dialog') + document.body.appendChild(dialogDiv) + } + + const handle = function (checkFunction, afterHandel) { + if (checkFunction instanceof Function) { + const res = checkFunction() + if (res instanceof Promise) { + res.then(c => { + c && afterHandel() + }) + } else { + res && afterHandel() + } + } else { + // checkFunction && afterHandel() + checkFunction || afterHandel() + } + } + + const dialogInstance = new Vue({ + data () { + return { + visible: true + } + }, + router: _vm.$router, + store: _vm.$store, + mounted () { + this.$on('close', (v) => { + this.handleClose() + }) + }, + methods: { + handleClose () { + handle(this.$refs._component.onCancel, () => { + this.visible = false + this.$refs._component.$emit('close') + this.$refs._component.$emit('cancel') + dialogInstance.$destroy() + }) + }, + handleOk () { + handle(this.$refs._component.onOK || this.$refs._component.onOk, () => { + this.visible = false + this.$refs._component.$emit('close') + this.$refs._component.$emit('ok') + dialogInstance.$destroy() + }) + } + }, + render: function (h) { + const that = this + const modalModel = modalProps && modalProps.model + if (modalModel) { + delete modalProps.model + } + const ModalProps = Object.assign({}, modalModel && { model: modalModel } || {}, { + attrs: Object.assign({}, { + ...(modalProps.attrs || modalProps) + }, { + visible: this.visible + }), + on: Object.assign({}, { + ...(modalProps.on || modalProps) + }, { + ok: () => { + that.handleOk() + }, + cancel: () => { + that.handleClose() + } + }) + }) + + const componentModel = componentProps && componentProps.model + if (componentModel) { + delete componentProps.model + } + const ComponentProps = Object.assign({}, componentModel && { model: componentModel } || {}, { + ref: '_component', + attrs: Object.assign({}, { + ...((componentProps && componentProps.attrs) || componentProps) + }), + on: Object.assign({}, { + ...((componentProps && componentProps.on) || componentProps) + }) + }) + + return h(Modal, ModalProps, [h(component, ComponentProps)]) + } + }).$mount(dialogDiv) + } + + Object.defineProperty(Vue.prototype, '$dialog', { + get: () => { + return function () { + dialog.apply(this, arguments) + } + } + }) +} diff --git a/_web/src/components/Editor/QuillEditor.vue b/_web/src/components/Editor/QuillEditor.vue new file mode 100644 index 0000000..731701c --- /dev/null +++ b/_web/src/components/Editor/QuillEditor.vue @@ -0,0 +1,82 @@ +<template> + <div :class="prefixCls"> + <quill-editor + v-model="content" + ref="myQuillEditor" + :options="editorOption" + @blur="onEditorBlur($event)" + @focus="onEditorFocus($event)" + @ready="onEditorReady($event)" + @change="onEditorChange($event)"> + </quill-editor> + + </div> +</template> + +<script> +import 'quill/dist/quill.core.css' +import 'quill/dist/quill.snow.css' +import 'quill/dist/quill.bubble.css' + +import { quillEditor } from 'vue-quill-editor' + +export default { + name: 'QuillEditor', + components: { + quillEditor + }, + props: { + prefixCls: { + type: String, + default: 'ant-editor-quill' + }, + // 表单校验用字段 + // eslint-disable-next-line + value: { + type: String + } + }, + data () { + return { + content: null, + editorOption: { + // some quill options + } + } + }, + methods: { + onEditorBlur (quill) { + console.log('editor blur!', quill) + }, + onEditorFocus (quill) { + console.log('editor focus!', quill) + }, + onEditorReady (quill) { + console.log('editor ready!', quill) + }, + onEditorChange ({ quill, html, text }) { + console.log('editor change!', quill, html, text) + this.$emit('change', html) + } + }, + watch: { + value (val) { + this.content = val + } + } +} +</script> + +<style lang="less" scoped> +@import url('../index.less'); + +/* 覆盖 quill 默认边框圆角为 ant 默认圆角,用于统一 ant 组件风格 */ +.ant-editor-quill { + /deep/ .ql-toolbar.ql-snow { + border-radius: @border-radius-base @border-radius-base 0 0; + } + /deep/ .ql-container.ql-snow { + border-radius: 0 0 @border-radius-base @border-radius-base; + } +} +</style> diff --git a/_web/src/components/Editor/WangEditor.vue b/_web/src/components/Editor/WangEditor.vue new file mode 100644 index 0000000..7a79a9e --- /dev/null +++ b/_web/src/components/Editor/WangEditor.vue @@ -0,0 +1,126 @@ +<template> + <div> + <div id="editor" ref="myEditor"></div> + <slot></slot> + </div> +</template> +<script> + import WangEditor from 'wangeditor' + export default { + name: 'ComponentWangeditor', + data () { + return { + edit: '' + } + }, + props: { + value: { + type: String, + default: '' + }, + config: { + type: Object, + default: () => { + return {} + } + }, + uploadConfig: { + type: Object, + default: () => { + return { + method: 'http', // 支持custom(objurl)和http(服务器)和base64 + url: '/' + } + } + } + }, + computed: { + customConfig () { + return { + pasteFilterStyle: false, // 关闭掉粘贴样式的过滤 + pasteIgnoreImg: false, // 粘贴时不忽略图片 + ...this.config + } + } + }, + watch: { + + }, + components: { + + }, + methods: { + readBlobAsDataURL (blob, callback) { + var a = new FileReader() + a.onload = function (e) { callback(e.target.result) } + a.readAsDataURL(blob) + }, + initEditor () { + var self = this + this.editor = new WangEditor(this.$refs.myEditor) + // 配置 onchange 事件 + this.editor.customConfig = this.customConfig + this.editor.customConfig.uploadImgMaxLength = 5 + this.editor.change = function () { // 编辑区域内容变化时 + self.$emit('input', this.txt.html()) + self.$emit('onchange', this.txt.html(), this.txt) + // editor.txt.html('.....') //设置编辑器内容 + // editor.txt.clear() //清空编辑器内容 + // editor.txt.append('<p>追加的内容</p>')//继续追加内容。 + // editor.txt.text() // 读取 text + // editor.txt.getJSON() // 获取 JSON 格式的内容 + } + this.editor.customConfig.customUploadImg = function (files, insert) { + if (self.uploadConfig.method === 'custom') { + files.forEach(file => { + var fileUrl = URL.createObjectURL(file) + insert(fileUrl) + }) + } + if (self.uploadConfig.method === 'base64') { + files.forEach(file => { + self.readBlobAsDataURL(file, function (dataurl) { + insert(dataurl) + }) + }) + } + if (self.uploadConfig.method === 'http') { + if (self.uploadConfig.callback) { + self.uploadConfig.callback(files, insert) + } else { + var formData = new FormData() + files.forEach(file => { + formData.append('file', file) + }) + self.$axios.post(self.uploadConfig.url, formData).then(({ data }) => { + if (data.status === 'success') { + insert(data.url) + } + }) + } + } + } + + this.editor.create() // 生成编辑器 + this.editor.txt.text(this.value) // 生成编辑器 + this.$emit('oninit', this.editor) + } + }, + beforeCreate () { + }, + created () { + }, + beforeMount () { + }, + mounted () { + this.initEditor() + } + } +</script> + +<style > + .w-e-toolbar{ + flex-wrap:wrap; + } + +</style> diff --git a/_web/src/components/Ellipsis/Ellipsis.vue b/_web/src/components/Ellipsis/Ellipsis.vue new file mode 100644 index 0000000..5d59200 --- /dev/null +++ b/_web/src/components/Ellipsis/Ellipsis.vue @@ -0,0 +1,64 @@ +<script> +import Tooltip from 'ant-design-vue/es/tooltip' +import { cutStrByFullLength, getStrFullLength } from '@/components/_util/util' +/* + const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined; + + const TooltipOverlayStyle = { + overflowWrap: 'break-word', + wordWrap: 'break-word', + }; + */ + +export default { + name: 'Ellipsis', + components: { + Tooltip + }, + props: { + prefixCls: { + type: String, + default: 'ant-pro-ellipsis' + }, + tooltip: { + type: Boolean + }, + length: { + type: Number, + required: true + }, + lines: { + type: Number, + default: 1 + }, + fullWidthRecognition: { + type: Boolean, + default: false + } + }, + methods: { + getStrDom (str, fullLength) { + return ( + <span>{ cutStrByFullLength(str, this.length) + (fullLength > this.length ? '...' : '') }</span> + ) + }, + getTooltip (fullStr, fullLength) { + return ( + <Tooltip> + <template slot="title">{ fullStr }</template> + { this.getStrDom(fullStr, fullLength) } + </Tooltip> + ) + } + }, + render () { + const { tooltip, length } = this.$props + const str = this.$slots.default.map(vNode => vNode.text).join('') + const fullLength = getStrFullLength(str) + const strDom = tooltip && fullLength > length ? this.getTooltip(str, fullLength) : this.getStrDom(str, fullLength) + return ( + strDom + ) + } +} +</script> diff --git a/_web/src/components/Ellipsis/index.js b/_web/src/components/Ellipsis/index.js new file mode 100644 index 0000000..91e3ff4 --- /dev/null +++ b/_web/src/components/Ellipsis/index.js @@ -0,0 +1,3 @@ +import Ellipsis from './Ellipsis' + +export default Ellipsis diff --git a/_web/src/components/Ellipsis/index.md b/_web/src/components/Ellipsis/index.md new file mode 100644 index 0000000..f528ac7 --- /dev/null +++ b/_web/src/components/Ellipsis/index.md @@ -0,0 +1,38 @@ +# Ellipsis 文本自动省略号 + +文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。 + + + +引用方式: + +```javascript +import Ellipsis from '@/components/Ellipsis' + +export default { + components: { + Ellipsis + } +} +``` + + + +## 代码演示 [demo](https://pro.loacg.com/test/home) + +```html +<ellipsis :length="100" tooltip> + There were injuries alleged in three cases in 2015, and a + fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall. +</ellipsis> +``` + + + +## API + + +参数 | 说明 | 类型 | 默认值 +----|------|-----|------ +tooltip | 移动到文本展示完整内容的提示 | boolean | - +length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - \ No newline at end of file diff --git a/_web/src/components/Exception/ExceptionPage.vue b/_web/src/components/Exception/ExceptionPage.vue new file mode 100644 index 0000000..832896c --- /dev/null +++ b/_web/src/components/Exception/ExceptionPage.vue @@ -0,0 +1,130 @@ +<template> + <div class="exception"> + <div class="imgBlock"> + <div class="imgEle" :style="{backgroundImage: `url(${config[type].img})`}"> + </div> + </div> + <div class="content"> + <h1>{{ config[type].title }}</h1> + <div class="desc">{{ config[type].desc }}</div> + <div class="actions"> + <a-button type="primary" @click="handleToHome">返回首页</a-button> + </div> + </div> + </div> +</template> + +<script> +import types from './type' + +export default { + name: 'Exception', + props: { + type: { + type: String, + default: '404' + } + }, + data () { + return { + config: types + } + }, + methods: { + handleToHome () { + this.$router.push({ name: 'Console' }) + } + } +} +</script> +<style lang="less"> +@import "~ant-design-vue/lib/style/index"; + +.exception { + display: flex; + align-items: center; + height: 80%; + min-height: 500px; + + .imgBlock { + flex: 0 0 62.5%; + width: 62.5%; + padding-right: 152px; + zoom: 1; + &::before, + &::after { + content: ' '; + display: table; + } + &::after { + clear: both; + height: 0; + font-size: 0; + visibility: hidden; + } + } + + .imgEle { + float: right; + width: 100%; + max-width: 430px; + height: 360px; + background-repeat: no-repeat; + background-position: 50% 50%; + background-size: contain; + } + + .content { + flex: auto; + + h1 { + margin-bottom: 24px; + color: #434e59; + font-weight: 600; + font-size: 72px; + line-height: 72px; + } + + .desc { + margin-bottom: 16px; + color: @text-color-secondary; + font-size: 20px; + line-height: 28px; + } + + .actions { + button:not(:last-child) { + margin-right: 8px; + } + } + } +} + +@media screen and (max-width: @screen-xl) { + .exception { + .imgBlock { + padding-right: 88px; + } + } +} + +@media screen and (max-width: @screen-sm) { + .exception { + display: block; + text-align: center; + .imgBlock { + margin: 0 auto 24px; + padding-right: 0; + } + } +} + +@media screen and (max-width: @screen-xs) { + .exception { + .imgBlock { + margin-bottom: -24px; + overflow: hidden; + } + } +} +</style> diff --git a/_web/src/components/Exception/index.js b/_web/src/components/Exception/index.js new file mode 100644 index 0000000..dda91be --- /dev/null +++ b/_web/src/components/Exception/index.js @@ -0,0 +1,2 @@ +import ExceptionPage from './ExceptionPage.vue' +export default ExceptionPage diff --git a/_web/src/components/Exception/type.js b/_web/src/components/Exception/type.js new file mode 100644 index 0000000..8158f0f --- /dev/null +++ b/_web/src/components/Exception/type.js @@ -0,0 +1,19 @@ +const types = { + 403: { + img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg', + title: '403', + desc: '抱歉,你无权访问该页面' + }, + 404: { + img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg', + title: '404', + desc: '抱歉,你访问的页面不存在或仍在开发中' + }, + 500: { + img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg', + title: '500', + desc: '抱歉,服务器出错了' + } +} + +export default types diff --git a/_web/src/components/FooterToolbar/FooterToolBar.vue b/_web/src/components/FooterToolbar/FooterToolBar.vue new file mode 100644 index 0000000..f4056dc --- /dev/null +++ b/_web/src/components/FooterToolbar/FooterToolBar.vue @@ -0,0 +1,30 @@ +<template> + <div :class="prefixCls"> + <div style="float: left"> + <slot name="extra">{{ extra }}</slot> + </div> + <div style="float: right"> + <slot></slot> + </div> + </div> +</template> + +<script> +export default { + name: 'FooterToolBar', + props: { + prefixCls: { + type: String, + default: 'ant-pro-footer-toolbar' + }, + extra: { + type: [String, Object], + default: '' + } + } +} +</script> + +<style lang="less" scoped> + +</style> diff --git a/_web/src/components/FooterToolbar/index.js b/_web/src/components/FooterToolbar/index.js new file mode 100644 index 0000000..a0bf145 --- /dev/null +++ b/_web/src/components/FooterToolbar/index.js @@ -0,0 +1,4 @@ +import FooterToolBar from './FooterToolBar' +import './index.less' + +export default FooterToolBar diff --git a/_web/src/components/FooterToolbar/index.less b/_web/src/components/FooterToolbar/index.less new file mode 100644 index 0000000..f56273f --- /dev/null +++ b/_web/src/components/FooterToolbar/index.less @@ -0,0 +1,23 @@ +@import "../index"; + +@footer-toolbar-prefix-cls: ~"@{ant-pro-prefix}-footer-toolbar"; + +.@{footer-toolbar-prefix-cls} { + position: fixed; + width: 100%; + bottom: 0; + right: 0; + height: 56px; + line-height: 56px; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03); + background: #fff; + border-top: 1px solid #e8e8e8; + padding: 0 24px; + z-index: 9; + + &:after { + content: ""; + display: block; + clear: both; + } +} \ No newline at end of file diff --git a/_web/src/components/FooterToolbar/index.md b/_web/src/components/FooterToolbar/index.md new file mode 100644 index 0000000..c1aec2c --- /dev/null +++ b/_web/src/components/FooterToolbar/index.md @@ -0,0 +1,48 @@ +# FooterToolbar 底部工具栏 + +固定在底部的工具栏。 + + + +## 何时使用 + +固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。 + + + +引用方式: + +```javascript +import FooterToolBar from '@/components/FooterToolbar' + +export default { + components: { + FooterToolBar + } +} +``` + + + +## 代码演示 + +```html +<footer-tool-bar> + <a-button type="primary" @click="validate" :loading="loading">提交</a-button> +</footer-tool-bar> +``` +或 +```html +<footer-tool-bar extra="扩展信息提示"> + <a-button type="primary" @click="validate" :loading="loading">提交</a-button> +</footer-tool-bar> +``` + + +## API + +参数 | 说明 | 类型 | 默认值 +----|------|-----|------ +children (slot) | 工具栏内容,向右对齐 | - | - +extra | 额外信息,向左对齐 | String, Object | - + diff --git a/_web/src/components/GlobalFooter/GlobalFooter.vue b/_web/src/components/GlobalFooter/GlobalFooter.vue new file mode 100644 index 0000000..5be08d6 --- /dev/null +++ b/_web/src/components/GlobalFooter/GlobalFooter.vue @@ -0,0 +1,46 @@ +<template> + <div class="footer"> + <div class="links"> + </div> + <div class="copyright"> + Copyright © 2020 <a target="_blank" href="https://www.xiaonuo.vip/">小诺开源技术</a> All rights reserved. Snowy 1.8 + </div> + </div> +</template> + +<script> +export default { + name: 'GlobalFooter', + data () { + return {} + } +} +</script> + +<style lang="less" scoped> +.footer { + padding: 0 16px; + margin: 48px 0 24px; + text-align: center; + + .links { + margin-bottom: 8px; + + a { + color: rgba(0, 0, 0, 0.45); + + &:hover { + color: rgba(0, 0, 0, 0.65); + } + + &:not(:last-child) { + margin-right: 40px; + } + } + } + .copyright { + color: rgba(0, 0, 0, 0.45); + font-size: 14px; + } +} +</style> diff --git a/_web/src/components/GlobalFooter/index.js b/_web/src/components/GlobalFooter/index.js new file mode 100644 index 0000000..832e0bd --- /dev/null +++ b/_web/src/components/GlobalFooter/index.js @@ -0,0 +1,2 @@ +import GlobalFooter from './GlobalFooter' +export default GlobalFooter diff --git a/_web/src/components/GlobalHeader/GlobalHeader.vue b/_web/src/components/GlobalHeader/GlobalHeader.vue new file mode 100644 index 0000000..9c82285 --- /dev/null +++ b/_web/src/components/GlobalHeader/GlobalHeader.vue @@ -0,0 +1,165 @@ +<template> + <transition name="showHeader"> + <div v-if="visible" class="header-animat"> + <a-layout-header + v-if="visible" + :class="[fixedHeader && 'ant-header-fixedHeader', sidebarOpened ? 'ant-header-side-opened' : 'ant-header-side-closed', ]" + :style="{ padding: '0', height: '55px' }"> + <div v-if="mode === 'sidemenu'" class="header"> + + <a-menu + style="height: 55px; border-bottom: 0px;" + mode="horizontal" + :default-selected-keys="this.defApp" + > + <a-icon v-if="device==='mobile'" class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle"/> + <a-icon v-else class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggle" style="padding-left: 20px; padding-right: 20px;"/> + + <a-menu-item v-for="(item) in userInfo.apps" :key="item.code" style="top:0px; line-height: 55px; padding-left: 10px; padding-right: 10px" @click="switchApp(item.code)"> + {{ item.name }} + </a-menu-item> + <user-menu></user-menu> + </a-menu> + + </div> + <div v-else :class="['top-nav-header-index', theme]"> + + <div class="header-index-wide"> + <div class="header-index-left"> + <logo class="top-nav-header" :show-title="device !== 'mobile'"/> + <s-menu v-if="device !== 'mobile'" mode="horizontal" :menu="menus" :theme="theme" /> + <a-icon v-else class="trigger" :type="collapsed ? 'menu-fold' : 'menu-unfold'" @click="toggle" /> + </div> + <user-menu class="header-index-right"></user-menu> + </div> + </div> + </a-layout-header> + </div> + </transition> +</template> + +<script> +import UserMenu from '../tools/UserMenu' +import SMenu from '../Menu/' +import Logo from '../tools/Logo' +import { mixin } from '@/utils/mixin' +import { mapActions, mapGetters } from 'vuex' +import { ALL_APPS_MENU } from '@/store/mutation-types' +import Vue from 'vue' +import { message } from 'ant-design-vue/es' + +export default { + name: 'GlobalHeader', + components: { + UserMenu, + SMenu, + Logo + }, + computed: { + ...mapGetters(['userInfo']) + }, + created () { + this.defApp.push(Vue.ls.get(ALL_APPS_MENU)[0].code) + }, + mixins: [mixin], + props: { + mode: { + type: String, + // sidemenu, topmenu + default: 'sidemenu' + }, + menus: { + type: Array, + required: true + }, + theme: { + type: String, + required: false, + default: 'dark' + }, + collapsed: { + type: Boolean, + required: false, + default: false + }, + device: { + type: String, + required: false, + default: 'desktop' + } + }, + data () { + return { + visible: true, + oldScrollTop: 0, + defApp: [] + } + }, + mounted () { + document.addEventListener('scroll', this.handleScroll, { passive: true }) + }, + methods: { + ...mapActions(['MenuChange']), + + /** + * 应用切换 + */ + switchApp (appCode) { + this.defApp = [] + const applicationData = this.userInfo.apps.filter(item => item.code === appCode) + const hideMessage = message.loading('正在切换应用!', 0) + this.MenuChange(applicationData[0]).then((res) => { + hideMessage() + // eslint-disable-next-line handle-callback-err + }).catch((err) => { + message.error('应用切换异常') + }) + }, + handleScroll () { + if (!this.autoHideHeader) { + return + } + + const scrollTop = document.body.scrollTop + document.documentElement.scrollTop + if (!this.ticking) { + this.ticking = true + requestAnimationFrame(() => { + if (this.oldScrollTop > scrollTop) { + this.visible = true + } else if (scrollTop > 300 && this.visible) { + this.visible = false + } else if (scrollTop < 300 && !this.visible) { + this.visible = true + } + this.oldScrollTop = scrollTop + this.ticking = false + }) + } + }, + toggle () { + this.$emit('toggle') + } + }, + beforeDestroy () { + document.body.removeEventListener('scroll', this.handleScroll, true) + } +} +</script> + +<style lang="less"> +@import '../index.less'; + +.header-animat{ + position: relative; + z-index: @ant-global-header-zindex; +} +.showHeader-enter-active { + transition: all 0.25s ease; +} +.showHeader-leave-active { + transition: all 0.5s ease; +} +.showHeader-enter, .showHeader-leave-to { + opacity: 0; +} +</style> diff --git a/_web/src/components/GlobalHeader/index.js b/_web/src/components/GlobalHeader/index.js new file mode 100644 index 0000000..0807c87 --- /dev/null +++ b/_web/src/components/GlobalHeader/index.js @@ -0,0 +1,2 @@ +import GlobalHeader from './GlobalHeader' +export default GlobalHeader diff --git a/_web/src/components/IconSelector/IconSelector.vue b/_web/src/components/IconSelector/IconSelector.vue new file mode 100644 index 0000000..810d297 --- /dev/null +++ b/_web/src/components/IconSelector/IconSelector.vue @@ -0,0 +1,86 @@ +<template> + <div :class="prefixCls"> + <a-tabs v-model="currentTab" @change="handleTabChange"> + <a-tab-pane v-for="v in icons" :tab="v.title" :key="v.key"> + <ul> + <li v-for="(icon, key) in v.icons" :key="`${v.key}-${key}`" :class="{ 'active': selectedIcon==icon }" @click="handleSelectedIcon(icon)" > + <a-icon :type="icon" :style="{ fontSize: '36px' }" /> + </li> + </ul> + </a-tab-pane> + </a-tabs> + </div> +</template> + +<script> +import icons from './icons' + +export default { + name: 'IconSelect', + props: { + prefixCls: { + type: String, + default: 'ant-pro-icon-selector' + }, + // eslint-disable-next-line + value: { + type: String + } + }, + data () { + return { + selectedIcon: this.value || '', + currentTab: 'directional', + icons + } + }, + watch: { + value (val) { + this.selectedIcon = val + this.autoSwitchTab() + } + }, + created () { + if (this.value) { + this.autoSwitchTab() + } + }, + methods: { + handleSelectedIcon (icon) { + this.selectedIcon = icon + this.$emit('change', icon) + }, + handleTabChange (activeKey) { + this.currentTab = activeKey + }, + autoSwitchTab () { + icons.some(item => item.icons.some(icon => icon === this.value) && (this.currentTab = item.key)) + } + } +} +</script> + +<style lang="less" scoped> + @import "../index.less"; + + ul{ + list-style: none; + padding: 0; + overflow-y: scroll; + height: 250px; + + li{ + display: inline-block; + padding: @padding-sm; + margin: 3px 0; + border-radius: @border-radius-base; + + &:hover, &.active{ + // box-shadow: 0px 0px 5px 2px @primary-color; + cursor: pointer; + color: @white; + background-color: @primary-color; + } + } + } +</style> diff --git a/_web/src/components/IconSelector/README.md b/_web/src/components/IconSelector/README.md new file mode 100644 index 0000000..503095d --- /dev/null +++ b/_web/src/components/IconSelector/README.md @@ -0,0 +1,48 @@ +IconSelector +==== + +> 图标选择组件,常用于为某一个数据设定一个图标时使用 +> eg: 设定菜单列表时,为每个菜单设定一个图标 + +该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装 + + + +### 使用方式 + +```vue +<template> + <div> + <icon-selector @change="handleIconChange"/> + </div> +</template> + +<script> +import IconSelector from '@/components/IconSelector' + +export default { + name: 'YourView', + components: { + IconSelector + }, + data () { + return { + } + }, + methods: { + handleIconChange (icon) { + console.log('change Icon', icon) + } + } +} +</script> +``` + + + +### 事件 + + +| 名称 | 说明 | 类型 | 默认值 | +| ------ | -------------------------- | ------ | ------ | +| change | 当改变了 `icon` 选中项触发 | String | - | diff --git a/_web/src/components/IconSelector/icons.js b/_web/src/components/IconSelector/icons.js new file mode 100644 index 0000000..2afc40a --- /dev/null +++ b/_web/src/components/IconSelector/icons.js @@ -0,0 +1,36 @@ +/** + * 增加新的图标时,请遵循以下数据结构 + * Adding new icon please follow the data structure below + */ +export default [ + { + key: 'directional', + title: '方向性图标', + icons: ['step-backward', 'step-forward', 'fast-backward', 'fast-forward', 'shrink', 'arrows-alt', 'down', 'up', 'left', 'right', 'caret-up', 'caret-down', 'caret-left', 'caret-right', 'up-circle', 'down-circle', 'left-circle', 'right-circle', 'double-right', 'double-left', 'vertical-left', 'vertical-right', 'forward', 'backward', 'rollback', 'enter', 'retweet', 'swap', 'swap-left', 'swap-right', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'play-circle', 'up-square', 'down-square', 'left-square', 'right-square', 'login', 'logout', 'menu-fold', 'menu-unfold', 'border-bottom', 'border-horizontal', 'border-inner', 'border-left', 'border-right', 'border-top', 'border-verticle', 'pic-center', 'pic-left', 'pic-right', 'radius-bottomleft', 'radius-bottomright', 'radius-upleft', 'fullscreen', 'fullscreen-exit'] + }, + { + key: 'suggested', + title: '提示建议性图标', + icons: ['question', 'question-circle', 'plus', 'plus-circle', 'pause', 'pause-circle', 'minus', 'minus-circle', 'plus-square', 'minus-square', 'info', 'info-circle', 'exclamation', 'exclamation-circle', 'close', 'close-circle', 'close-square', 'check', 'check-circle', 'check-square', 'clock-circle', 'warning', 'issues-close', 'stop'] + }, + { + key: 'editor', + title: '编辑类图标', + icons: ['edit', 'form.vue', 'copy', 'scissor', 'delete', 'snippets', 'diff', 'highlight', 'align-center', 'align-left', 'align-right', 'bg-colors', 'bold', 'italic', 'underline', 'strikethrough', 'redo', 'undo', 'zoom-in', 'zoom-out', 'font-colors', 'font-size', 'line-height', 'column-height', 'dash', 'small-dash', 'sort-ascending', 'sort-descending', 'drag', 'ordered-list', 'radius-setting'] + }, + { + key: 'data', + title: '数据类图标', + icons: ['area-chart', 'pie-chart', 'bar-chart', 'dot-chart', 'line-chart', 'radar-chart', 'heat-map', 'fall', 'rise', 'stock', 'box-plot', 'fund', 'sliders'] + }, + { + key: 'brand_logo', + title: '网站通用图标', + icons: ['lock', 'unlock', 'bars', 'book', 'calendar', 'cloud', 'cloud-download', 'code', 'copy', 'credit-card', 'delete', 'desktop', 'download', 'ellipsis', 'file', 'file-text', 'file-unknown', 'file-pdf', 'file-word', 'file-excel', 'file-jpg', 'file-ppt', 'file-markdown', 'file-add', 'folder', 'folder-open', 'folder-add', 'hdd', 'frown', 'meh', 'smile', 'inbox', 'laptop', 'appstore', 'link', 'mail', 'mobile', 'notification', 'paper-clip', 'picture', 'poweroff', 'reload', 'search', 'setting', 'share-alt', 'shopping-cart', 'tablet', 'tag', 'tags', 'to-top', 'upload', 'user', 'video-camera', 'home', 'loading', 'loading-3-quarters', 'cloud-upload', 'star', 'heart', 'environment', 'eye', 'camera', 'save', 'team', 'solution', 'phone', 'filter', 'exception', 'export', 'customer-service', 'qrcode', 'scan', 'like', 'dislike', 'message', 'pay-circle', 'calculator', 'pushpin', 'bulb', 'select', 'switcher', 'rocket', 'bell', 'disconnect', 'database', 'compass', 'barcode', 'hourglass', 'key', 'flag', 'layout', 'printer', 'sound', 'usb', 'skin', 'tool', 'sync', 'wifi', 'car', 'schedule', 'user-add', 'user-delete', 'usergroup-add', 'usergroup-delete', 'man', 'woman', 'shop', 'gift', 'idcard', 'medicine-box', 'red-envelope', 'coffee', 'copyright', 'trademark', 'safety', 'wallet', 'bank', 'trophy', 'contacts', 'global', 'shake', 'api', 'fork', 'dashboard', 'table', 'profile', 'alert', 'audit', 'branches', 'build', 'border', 'crown', 'experiment', 'fire', 'money-collect', 'property-safety', 'read', 'reconciliation', 'rest', 'security-scan', 'insurance', 'interaction', 'safety-certificate', 'project', 'thunderbolt', 'block', 'cluster', 'deployment-unit', 'dollar', 'euro', 'pound', 'file-done', 'file-exclamation', 'file-protect', 'file-search', 'file-sync', 'gateway', 'gold', 'robot', 'shopping'] + }, + { + key: 'application', + title: '品牌和标识', + icons: ['android', 'apple', 'windows', 'ie', 'chrome', 'github', 'aliwangwang', 'dingding', 'weibo-square', 'weibo-circle', 'taobao-circle', 'html5', 'weibo', 'twitter', 'wechat', 'youtube', 'alipay-circle', 'taobao', 'skype', 'qq', 'medium-workmark', 'gitlab', 'medium', 'linkedin', 'google-plus', 'dropbox', 'facebook', 'codepen', 'code-sandbox', 'amazon', 'google', 'codepen-circle', 'alipay', 'ant-design', 'aliyun', 'zhihu', 'slack', 'slack-square', 'behance', 'behance-square', 'dribbble', 'dribbble-square', 'instagram', 'yuque', 'alibaba', 'yahoo'] + } +] diff --git a/_web/src/components/IconSelector/index.js b/_web/src/components/IconSelector/index.js new file mode 100644 index 0000000..2d27d70 --- /dev/null +++ b/_web/src/components/IconSelector/index.js @@ -0,0 +1,2 @@ +import IconSelector from './IconSelector' +export default IconSelector diff --git a/_web/src/components/Menu/SideMenu.vue b/_web/src/components/Menu/SideMenu.vue new file mode 100644 index 0000000..2889535 --- /dev/null +++ b/_web/src/components/Menu/SideMenu.vue @@ -0,0 +1,61 @@ +<template> + <a-layout-sider + :class="['sider', isDesktop() ? null : 'shadow', theme, fixSiderbar ? 'ant-fixed-sidemenu' : null ]" + width="230px" + :collapsible="collapsible" + v-model="collapsed" + :trigger="null"> + <logo /> + <s-menu + :collapsed="collapsed" + :menu="menus" + :theme="theme" + :mode="mode" + @select="onSelect" + ></s-menu> + </a-layout-sider> + +</template> + +<script> +import Logo from '@/components/tools/Logo' +import SMenu from './index' +import { mixin, mixinDevice } from '@/utils/mixin' + +export default { + name: 'SideMenu', + components: { Logo, SMenu }, + mixins: [mixin, mixinDevice], + props: { + mode: { + type: String, + required: false, + default: 'inline' + }, + theme: { + type: String, + required: false, + default: 'dark' + }, + collapsible: { + type: Boolean, + required: false, + default: false + }, + collapsed: { + type: Boolean, + required: false, + default: false + }, + menus: { + type: Array, + required: true + } + }, + methods: { + onSelect (obj) { + this.$emit('menuSelect', obj) + } + } +} +</script> diff --git a/_web/src/components/Menu/index.js b/_web/src/components/Menu/index.js new file mode 100644 index 0000000..4348509 --- /dev/null +++ b/_web/src/components/Menu/index.js @@ -0,0 +1,2 @@ +import SMenu from './menu' +export default SMenu diff --git a/_web/src/components/Menu/menu.js b/_web/src/components/Menu/menu.js new file mode 100644 index 0000000..2b3f7a2 --- /dev/null +++ b/_web/src/components/Menu/menu.js @@ -0,0 +1,177 @@ +import Menu from 'ant-design-vue/es/menu' +import Icon from 'ant-design-vue/es/icon' + +export default { + name: 'SMenu', + props: { + menu: { + type: Array, + required: true + }, + theme: { + type: String, + required: false, + default: 'dark' + }, + mode: { + type: String, + required: false, + default: 'inline' + }, + collapsed: { + type: Boolean, + required: false, + default: false + } + }, + data () { + return { + openKeys: [], + selectedKeys: [], + cachedOpenKeys: [] + } + }, + computed: { + rootSubmenuKeys: vm => { + const keys = [] + vm.menu.forEach(item => keys.push(item.path)) + return keys + } + }, + mounted () { + this.updateMenu() + }, + watch: { + collapsed (val) { + if (val) { + this.cachedOpenKeys = this.openKeys.concat() + this.openKeys = [] + } else { + this.openKeys = this.cachedOpenKeys + } + }, + $route: function () { + this.updateMenu() + } + }, + methods: { + // select menu item + onOpenChange (openKeys) { + // 在水平模式下时执行,并且不再执行后续 + if (this.mode === 'horizontal') { + this.openKeys = openKeys + return + } + // 非水平模式时 + const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key)) + if (!this.rootSubmenuKeys.includes(latestOpenKey)) { + this.openKeys = openKeys + } else { + this.openKeys = latestOpenKey ? [latestOpenKey] : [] + } + }, + onSelect ({ item, key, selectedKeys }) { + this.selectedKeys = selectedKeys + this.$emit('select', { item, key, selectedKeys }) + }, + updateMenu () { + const routes = this.$route.matched.concat() + const { hidden } = this.$route.meta + if (routes.length >= 3 && hidden) { + routes.pop() + this.selectedKeys = [routes[routes.length - 1].path] + } else { + this.selectedKeys = [routes.pop().path] + } + const openKeys = [] + if (this.mode === 'inline') { + routes.forEach(item => { + openKeys.push(item.path) + }) + } + + this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys) + }, + + // render + renderItem (menu) { + if (!menu.hidden) { + return menu.children && !menu.hideChildrenInMenu ? this.renderSubMenu(menu) : this.renderMenuItem(menu) + } + return null + }, + renderMenuItem (menu) { + const target = menu.meta.target || null + const CustomTag = target && 'a' || 'router-link' + const props = { to: { name: menu.name } } + const attrs = { href: menu.path, target: menu.meta.target } + + if (menu.children && menu.hideChildrenInMenu) { + // 把有子菜单的 并且 父菜单是要隐藏子菜单的 + // 都给子菜单增加一个 hidden 属性 + // 用来给刷新页面时, selectedKeys 做控制用 + menu.children.forEach(item => { + item.meta = Object.assign(item.meta, { hidden: true }) + }) + } + + return ( + <Menu.Item {...{ key: menu.path }}> + <CustomTag {...{ props, attrs }}> + {this.renderIcon(menu.meta.icon)} + <span>{menu.meta.title}</span> + </CustomTag> + </Menu.Item> + ) + }, + renderSubMenu (menu) { + const itemArr = [] + if (!menu.hideChildrenInMenu) { + menu.children.forEach(item => itemArr.push(this.renderItem(item))) + } + return ( + <Menu.SubMenu {...{ key: menu.path }}> + <span slot="title"> + {this.renderIcon(menu.meta.icon)} + <span>{menu.meta.title}</span> + </span> + {itemArr} + </Menu.SubMenu> + ) + }, + renderIcon (icon) { + if (icon === 'none' || icon === undefined) { + return null + } + const props = {} + typeof (icon) === 'object' ? props.component = icon : props.type = icon + return ( + <Icon {... { props } }/> + ) + } + }, + + render () { + const dynamicProps = { + props: { + mode: this.mode, + theme: this.theme, + openKeys: this.openKeys, + selectedKeys: this.selectedKeys + }, + on: { + openChange: this.onOpenChange, + select: this.onSelect + } + } + + const menuTree = this.menu.map(item => { + if (item.hidden) { + return null + } + return this.renderItem(item) + }) + + return (<Menu {...dynamicProps}>{menuTree}</Menu>) + } +} diff --git a/_web/src/components/Menu/menu.render.js b/_web/src/components/Menu/menu.render.js new file mode 100644 index 0000000..011639f --- /dev/null +++ b/_web/src/components/Menu/menu.render.js @@ -0,0 +1,156 @@ +import Menu from 'ant-design-vue/es/menu' +import Icon from 'ant-design-vue/es/icon' + +const { Item, SubMenu } = Menu + +export default { + name: 'SMenu', + props: { + menu: { + type: Array, + required: true + }, + theme: { + type: String, + required: false, + default: 'dark' + }, + mode: { + type: String, + required: false, + default: 'inline' + }, + collapsed: { + type: Boolean, + required: false, + default: false + } + }, + data () { + return { + openKeys: [], + selectedKeys: [], + cachedOpenKeys: [] + } + }, + computed: { + rootSubmenuKeys: vm => { + const keys = [] + vm.menu.forEach(item => keys.push(item.path)) + return keys + } + }, + created () { + this.updateMenu() + }, + watch: { + collapsed (val) { + if (val) { + this.cachedOpenKeys = this.openKeys.concat() + this.openKeys = [] + } else { + this.openKeys = this.cachedOpenKeys + } + }, + $route: function () { + this.updateMenu() + } + }, + methods: { + renderIcon: function (h, icon) { + if (icon === 'none' || icon === undefined) { + return null + } + const props = {} + typeof (icon) === 'object' ? props.component = icon : props.type = icon + return h(Icon, { props: { ...props } }) + }, + renderMenuItem: function (h, menu, pIndex, index) { + const target = menu.meta.target || null + return h(Item, { key: menu.path ? menu.path : 'item_' + pIndex + '_' + index }, [ + h('router-link', { attrs: { to: { name: menu.name }, target: target } }, [ + this.renderIcon(h, menu.meta.icon), + h('span', [menu.meta.title]) + ]) + ]) + }, + renderSubMenu: function (h, menu, pIndex, index) { + const this2_ = this + const subItem = [h('span', { slot: 'title' }, [this.renderIcon(h, menu.meta.icon), h('span', [menu.meta.title])])] + const itemArr = [] + const pIndex_ = pIndex + '_' + index + console.log('menu', menu) + if (!menu.hideChildrenInMenu) { + menu.children.forEach(function (item, i) { + itemArr.push(this2_.renderItem(h, item, pIndex_, i)) + }) + } + return h(SubMenu, { key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index }, subItem.concat(itemArr)) + }, + renderItem: function (h, menu, pIndex, index) { + if (!menu.hidden) { + return menu.children && !menu.hideChildrenInMenu + ? this.renderSubMenu(h, menu, pIndex, index) + : this.renderMenuItem(h, menu, pIndex, index) + } + }, + renderMenu: function (h, menuTree) { + const this2_ = this + const menuArr = [] + menuTree.forEach(function (menu, i) { + if (!menu.hidden) { + menuArr.push(this2_.renderItem(h, menu, '0', i)) + } + }) + return menuArr + }, + onOpenChange (openKeys) { + const latestOpenKey = openKeys.find(key => !this.openKeys.includes(key)) + if (!this.rootSubmenuKeys.includes(latestOpenKey)) { + this.openKeys = openKeys + } else { + this.openKeys = latestOpenKey ? [latestOpenKey] : [] + } + }, + updateMenu () { + const routes = this.$route.matched.concat() + + if (routes.length >= 4 && this.$route.meta.hidden) { + routes.pop() + this.selectedKeys = [routes[2].path] + } else { + this.selectedKeys = [routes.pop().path] + } + + const openKeys = [] + if (this.mode === 'inline') { + routes.forEach(item => { + openKeys.push(item.path) + }) + } + + this.collapsed ? (this.cachedOpenKeys = openKeys) : (this.openKeys = openKeys) + } + }, + render (h) { + return h( + Menu, + { + props: { + theme: this.$props.theme, + mode: this.$props.mode, + openKeys: this.openKeys, + selectedKeys: this.selectedKeys + }, + on: { + openChange: this.onOpenChange, + select: obj => { + this.selectedKeys = obj.selectedKeys + this.$emit('select', obj) + } + } + }, + this.renderMenu(h, this.menu) + ) + } +} diff --git a/_web/src/components/MultiTab/MultiTab.vue b/_web/src/components/MultiTab/MultiTab.vue new file mode 100644 index 0000000..563740c --- /dev/null +++ b/_web/src/components/MultiTab/MultiTab.vue @@ -0,0 +1,163 @@ +<script> +import events from './events' + +export default { + name: 'MultiTab', + data () { + return { + fullPathList: [], + pages: [], + activeKey: '', + newTabIndex: 0 + } + }, + created () { + // bind event + events.$on('open', val => { + if (!val) { + throw new Error(`multi-tab: open tab ${val} err`) + } + this.activeKey = val + }).$on('close', val => { + if (!val) { + this.closeThat(this.activeKey) + return + } + this.closeThat(val) + }).$on('rename', ({ key, name }) => { + console.log('rename', key, name) + try { + const item = this.pages.find(item => item.path === key) + item.meta.customTitle = name + this.$forceUpdate() + } catch (e) { + } + }) + + this.pages.push(this.$route) + this.fullPathList.push(this.$route.fullPath) + this.selectedLastPath() + }, + methods: { + onEdit (targetKey, action) { + this[action](targetKey) + }, + remove (targetKey) { + this.pages = this.pages.filter(page => page.fullPath !== targetKey) + this.fullPathList = this.fullPathList.filter(path => path !== targetKey) + // 判断当前标签是否关闭,若关闭则跳转到最后一个还存在的标签页 + if (!this.fullPathList.includes(this.activeKey)) { + this.selectedLastPath() + } + }, + selectedLastPath () { + this.activeKey = this.fullPathList[this.fullPathList.length - 1] + }, + + // content menu + closeThat (e) { + // 判断是否为最后一个标签页,如果是最后一个,则无法被关闭 + if (this.fullPathList.length > 1) { + this.remove(e) + } else { + this.$message.info('这是最后一个标签了, 无法被关闭') + } + }, + closeLeft (e) { + const currentIndex = this.fullPathList.indexOf(e) + if (currentIndex > 0) { + this.fullPathList.forEach((item, index) => { + if (index < currentIndex) { + this.remove(item) + } + }) + } else { + this.$message.info('左侧没有标签') + } + }, + closeRight (e) { + const currentIndex = this.fullPathList.indexOf(e) + if (currentIndex < (this.fullPathList.length - 1)) { + this.fullPathList.forEach((item, index) => { + if (index > currentIndex) { + this.remove(item) + } + }) + } else { + this.$message.info('右侧没有标签') + } + }, + closeAll (e) { + const currentIndex = this.fullPathList.indexOf(e) + this.fullPathList.forEach((item, index) => { + if (index !== currentIndex) { + this.remove(item) + } + }) + }, + closeMenuClick (key, route) { + this[key](route) + }, + renderTabPaneMenu (e) { + return ( + <a-menu {...{ on: { click: ({ key, item, domEvent }) => { this.closeMenuClick(key, e) } } }}> + <a-menu-item key="closeThat">关闭当前标签</a-menu-item> + <a-menu-item key="closeRight">关闭右侧</a-menu-item> + <a-menu-item key="closeLeft">关闭左侧</a-menu-item> + <a-menu-item key="closeAll">关闭全部</a-menu-item> + </a-menu> + ) + }, + // render + renderTabPane (title, keyPath) { + const menu = this.renderTabPaneMenu(keyPath) + + return ( + <a-dropdown overlay={menu} trigger={['contextmenu']}> + <span style={{ userSelect: 'none' }}>{ title }</span> + </a-dropdown> + ) + } + }, + watch: { + '$route': function (newVal) { + this.activeKey = newVal.fullPath + if (this.fullPathList.indexOf(newVal.fullPath) < 0) { + this.fullPathList.push(newVal.fullPath) + this.pages.push(newVal) + } + }, + activeKey: function (newPathKey) { + this.$router.push({ path: newPathKey }) + } + }, + render () { + const { onEdit, $data: { pages } } = this + const panes = pages.map(page => { + return ( + <a-tab-pane + style={{ height: 0, background: '#8999ee', color: '#899ee' }} + tab={this.renderTabPane(page.meta.customTitle || page.meta.title, page.fullPath)} + key={page.fullPath} closable={pages.length > 1} + > + </a-tab-pane>) + }) + + return ( + <div class="ant-pro-multi-tab"> + <div class="ant-pro-multi-tab-wrapper"> + <a-tabs + hideAdd + tabBarGutter={-1} + type={'editable-card'} + v-model={this.activeKey} + tabBarStyle={{ background: '#FFF', margin: 0, paddingLeft: '0px', paddingTop: '0px' }} + {...{ on: { edit: onEdit } }}> + {panes} + </a-tabs> + </div> + </div> + ) + } +} +</script> diff --git a/_web/src/components/MultiTab/events.js b/_web/src/components/MultiTab/events.js new file mode 100644 index 0000000..b0230b5 --- /dev/null +++ b/_web/src/components/MultiTab/events.js @@ -0,0 +1,2 @@ +import Vue from 'vue' +export default new Vue() diff --git a/_web/src/components/MultiTab/index.js b/_web/src/components/MultiTab/index.js new file mode 100644 index 0000000..02a1c77 --- /dev/null +++ b/_web/src/components/MultiTab/index.js @@ -0,0 +1,40 @@ +import events from './events' +import MultiTab from './MultiTab' +import './index.less' + +const api = { + /** + * open new tab on route fullPath + * @param config + */ + open: function (config) { + events.$emit('open', config) + }, + rename: function (key, name) { + events.$emit('rename', { key: key, name: name }) + }, + /** + * close current page + */ + closeCurrentPage: function () { + this.close() + }, + /** + * close route fullPath tab + * @param config + */ + close: function (config) { + events.$emit('close', config) + } +} + +MultiTab.install = function (Vue) { + if (Vue.prototype.$multiTab) { + return + } + api.instance = events + Vue.prototype.$multiTab = api + Vue.component('multi-tab', MultiTab) +} + +export default MultiTab diff --git a/_web/src/components/MultiTab/index.less b/_web/src/components/MultiTab/index.less new file mode 100644 index 0000000..773e3af --- /dev/null +++ b/_web/src/components/MultiTab/index.less @@ -0,0 +1,25 @@ +@import '../index'; + +@multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab"; +@multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper"; + +/* +.topmenu .@{multi-tab-prefix-cls} { + max-width: 1200px; + margin: -23px auto 24px auto; +} +*/ +.@{multi-tab-prefix-cls} { + margin: -23px -24px 24px -24px; + background: #fff; +} + +.topmenu .@{multi-tab-wrapper-prefix-cls} { + max-width: 1200px; + margin: 0 auto; +} + +.topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} { + max-width: 100%; + margin: 0 auto; +} diff --git a/_web/src/components/NProgress/nprogress.less b/_web/src/components/NProgress/nprogress.less new file mode 100644 index 0000000..7826c0e --- /dev/null +++ b/_web/src/components/NProgress/nprogress.less @@ -0,0 +1,76 @@ +@import url('../index.less'); + +/* Make clicks pass-through */ +#nprogress { + pointer-events: none; +} + +#nprogress .bar { + background: @primary-color; + + position: fixed; + z-index: 1031; + top: 0; + left: 0; + + width: 100%; + height: 2px; +} + +/* Fancy blur effect */ +#nprogress .peg { + display: block; + position: absolute; + right: 0px; + width: 100px; + height: 100%; + box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color; + opacity: 1.0; + + -webkit-transform: rotate(3deg) translate(0px, -4px); + -ms-transform: rotate(3deg) translate(0px, -4px); + transform: rotate(3deg) translate(0px, -4px); +} + +/* Remove these to get rid of the spinner */ +#nprogress .spinner { + display: block; + position: fixed; + z-index: 1031; + top: 15px; + right: 15px; +} + +#nprogress .spinner-icon { + width: 18px; + height: 18px; + box-sizing: border-box; + + border: solid 2px transparent; + border-top-color: @primary-color; + border-left-color: @primary-color; + border-radius: 50%; + + -webkit-animation: nprogress-spinner 400ms linear infinite; + animation: nprogress-spinner 400ms linear infinite; +} + +.nprogress-custom-parent { + overflow: hidden; + position: relative; +} + +.nprogress-custom-parent #nprogress .spinner, +.nprogress-custom-parent #nprogress .bar { + position: absolute; +} + +@-webkit-keyframes nprogress-spinner { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} +@keyframes nprogress-spinner { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + diff --git a/_web/src/components/NoticeIcon/NoticeIcon.vue b/_web/src/components/NoticeIcon/NoticeIcon.vue new file mode 100644 index 0000000..f5e341f --- /dev/null +++ b/_web/src/components/NoticeIcon/NoticeIcon.vue @@ -0,0 +1,142 @@ +<template> + <a-popover + v-model="visible" + trigger="click" + placement="bottomRight" + overlayClassName="header-notice-wrapper" + :getPopupContainer="() => $refs.noticeRef.parentElement" + :autoAdjustOverflow="true" + :arrowPointAtCenter="true" + :overlayStyle="{ width: '300px', top: '50px' }" + > + <template slot="content"> + <a-spin :spinning="loading"> + <a-tabs> + <a-tab-pane tab="通知" key="1"> + <a-list> + <a-list-item> + <a-list-item-meta title="你收到了 14 份新周报" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/> + </a-list-item-meta> + </a-list-item> + </a-list> + </a-tab-pane> + <a-tab-pane tab="消息" key="2"> + <a-list> + <a-list-item> + <a-list-item-meta title="你收到了 14 份新周报" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/> + </a-list-item-meta> + </a-list-item> + </a-list> + </a-tab-pane> + <a-tab-pane tab="待办" key="3"> + <a-list> + <a-list-item> + <a-list-item-meta title="你收到了 14 份新周报" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前"> + <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/> + </a-list-item-meta> + </a-list-item> + </a-list> + </a-tab-pane> + </a-tabs> + </a-spin> + </template> + <span @click="fetchNotice" class="header-notice" ref="noticeRef" style="padding: 0 18px"> + <a-badge count="12"> + <a-icon style="font-size: 16px; padding: 4px" type="bell" /> + </a-badge> + </span> + </a-popover> +</template> + +<script> +export default { + name: 'HeaderNotice', + data () { + return { + loading: false, + visible: false + } + }, + methods: { + fetchNotice () { + if (!this.visible) { + this.loading = true + setTimeout(() => { + this.loading = false + }, 100) + } else { + this.loading = false + } + this.visible = !this.visible + } + } +} +</script> + +<style lang="css"> + .header-notice-wrapper { + top: 50px !important; + } +</style> +<style lang="less" scoped> + .header-notice{ + display: inline-block; + transition: all 0.3s; + + span { + vertical-align: initial; + } + } +</style> diff --git a/_web/src/components/NoticeIcon/index.js b/_web/src/components/NoticeIcon/index.js new file mode 100644 index 0000000..659b9ec --- /dev/null +++ b/_web/src/components/NoticeIcon/index.js @@ -0,0 +1,2 @@ +import NoticeIcon from './NoticeIcon' +export default NoticeIcon diff --git a/_web/src/components/NumberInfo/NumberInfo.vue b/_web/src/components/NumberInfo/NumberInfo.vue new file mode 100644 index 0000000..bdde3e0 --- /dev/null +++ b/_web/src/components/NumberInfo/NumberInfo.vue @@ -0,0 +1,54 @@ +<template> + <div :class="[prefixCls]"> + <slot name="subtitle"> + <div :class="[`${prefixCls}-subtitle`]">{{ typeof subTitle === 'string' ? subTitle : subTitle() }}</div> + </slot> + <div class="number-info-value"> + <span>{{ total }}</span> + <span class="sub-total"> + {{ subTotal }} + <icon :type="`caret-${status}`" /> + </span> + </div> + </div> +</template> + +<script> +import Icon from 'ant-design-vue/es/icon' + +export default { + name: 'NumberInfo', + props: { + prefixCls: { + type: String, + default: 'ant-pro-number-info' + }, + total: { + type: Number, + required: true + }, + subTotal: { + type: Number, + required: true + }, + subTitle: { + type: [String, Function], + default: '' + }, + status: { + type: String, + default: 'up' + } + }, + components: { + Icon + }, + data () { + return {} + } +} +</script> + +<style lang="less" scoped> + @import "index"; +</style> diff --git a/_web/src/components/NumberInfo/index.js b/_web/src/components/NumberInfo/index.js new file mode 100644 index 0000000..659a2f3 --- /dev/null +++ b/_web/src/components/NumberInfo/index.js @@ -0,0 +1,3 @@ +import NumberInfo from './NumberInfo' + +export default NumberInfo diff --git a/_web/src/components/NumberInfo/index.less b/_web/src/components/NumberInfo/index.less new file mode 100644 index 0000000..719113d --- /dev/null +++ b/_web/src/components/NumberInfo/index.less @@ -0,0 +1,55 @@ +@import "../index"; + +@numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info"; + +.@{numberInfo-prefix-cls} { + + .ant-pro-number-info-subtitle { + color: @text-color-secondary; + font-size: @font-size-base; + height: 22px; + line-height: 22px; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; + white-space: nowrap; + } + + .number-info-value { + margin-top: 4px; + font-size: 0; + overflow: hidden; + text-overflow: ellipsis; + word-break: break-all; + white-space: nowrap; + + & > span { + color: @heading-color; + display: inline-block; + line-height: 32px; + height: 32px; + font-size: 24px; + margin-right: 32px; + } + + .sub-total { + color: @text-color-secondary; + font-size: @font-size-lg; + vertical-align: top; + margin-right: 0; + i { + font-size: 12px; + transform: scale(0.82); + margin-left: 4px; + } + :global { + .anticon-caret-up { + color: @red-6; + } + .anticon-caret-down { + color: @green-6; + } + } + } + } +} \ No newline at end of file diff --git a/_web/src/components/NumberInfo/index.md b/_web/src/components/NumberInfo/index.md new file mode 100644 index 0000000..147adc4 --- /dev/null +++ b/_web/src/components/NumberInfo/index.md @@ -0,0 +1,43 @@ +# NumberInfo 数据文本 + +常用在数据卡片中,用于突出展示某个业务数据。 + + + +引用方式: + +```javascript +import NumberInfo from '@/components/NumberInfo' + +export default { + components: { + NumberInfo + } +} +``` + + + +## 代码演示 [demo](https://pro.loacg.com/test/home) + +```html +<number-info + :sub-title="() => { return 'Visits this week' }" + :total="12321" + status="up" + :sub-total="17.1"></number-info> +``` + + + +## API + +参数 | 说明 | 类型 | 默认值 +----|------|-----|------ +title | 标题 | ReactNode\|string | - +subTitle | 子标题 | ReactNode\|string | - +total | 总量 | ReactNode\|string | - +subTotal | 子总量 | ReactNode\|string | - +status | 增加状态 | 'up \| down' | - +theme | 状态样式 | string | 'light' +gap | 设置数字和描述之间的间距(像素)| number | 8 diff --git a/_web/src/components/PageHeader/PageHeader.vue b/_web/src/components/PageHeader/PageHeader.vue new file mode 100644 index 0000000..aeb0227 --- /dev/null +++ b/_web/src/components/PageHeader/PageHeader.vue @@ -0,0 +1,202 @@ +<template> + <div class="page-header"> + <div class="page-header-index-wide"> + <s-breadcrumb /> + <div class="detail"> + <div class="main" v-if="!$route.meta.hiddenHeaderContent"> + <div class="row"> + <img v-if="logo" :src="logo" class="logo"/> + <h1 v-if="title" class="title">{{ title }}</h1> + <div class="action"> + <slot name="action"></slot> + </div> + </div> + <div class="row"> + <div v-if="avatar" class="avatar"> + <a-avatar :src="avatar" /> + </div> + <div v-if="this.$slots.content" class="headerContent"> + <slot name="content"></slot> + </div> + <div v-if="this.$slots.extra" class="extra"> + <slot name="extra"></slot> + </div> + </div> + <div> + <slot name="pageMenu"></slot> + </div> + </div> + </div> + </div> + </div> +</template> + +<script> +import Breadcrumb from '@/components/tools/Breadcrumb' + +export default { + name: 'PageHeader', + components: { + 's-breadcrumb': Breadcrumb + }, + props: { + title: { + type: [String, Boolean], + default: true, + required: false + }, + logo: { + type: String, + default: '', + required: false + }, + avatar: { + type: String, + default: '', + required: false + } + }, + data () { + return {} + } +} +</script> + +<style lang="less" scoped> +.page-header { + background: #fff; + padding: 10px 32px 0; + border-bottom: 0px solid #e8e8e8; + + .breadcrumb { + margin-bottom: 10px; + } + + .detail { + display: flex; + /*margin-bottom: 16px;*/ + + .avatar { + flex: 0 1 72px; + margin: 0 24px 8px 0; + + & > span { + border-radius: 72px; + display: block; + width: 72px; + height: 72px; + } + } + + .main { + width: 100%; + flex: 0 1 auto; + + .row { + display: flex; + width: 100%; + + .avatar { + margin-bottom: 10px; + } + } + + .title { + font-size: 20px; + font-weight: 500; + + font-size: 20px; + line-height: 28px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 16px; + flex: auto; + } + .logo { + width: 28px; + height: 28px; + border-radius: 4px; + margin-right: 16px; + } + .content, + .headerContent { + flex: auto; + color: rgba(0, 0, 0, 0.45); + line-height: 22px; + + .link { + margin-top: 16px; + line-height: 24px; + + a { + font-size: 14px; + margin-right: 32px; + } + } + } + .extra { + flex: 0 1 auto; + margin-left: 88px; + min-width: 242px; + text-align: right; + } + .action { + margin-left: 56px; + min-width: 266px; + flex: 0 1 auto; + text-align: right; + &:empty { + display: none; + } + } + } + } +} + +.mobile .page-header { + .main { + .row { + flex-wrap: wrap; + + .avatar { + flex: 0 1 25%; + margin: 0 2% 8px 0; + } + + .content, + .headerContent { + flex: 0 1 70%; + + .link { + margin-top: 16px; + line-height: 24px; + + a { + font-size: 14px; + margin-right: 10px; + } + } + } + + .extra { + flex: 1 1 auto; + margin-left: 0; + min-width: 0; + text-align: right; + } + + .action { + margin-left: unset; + min-width: 266px; + flex: 0 1 auto; + text-align: left; + margin-bottom: 12px; + + &:empty { + display: none; + } + } + } + } +} +</style> diff --git a/_web/src/components/PageHeader/index.js b/_web/src/components/PageHeader/index.js new file mode 100644 index 0000000..ec1078c --- /dev/null +++ b/_web/src/components/PageHeader/index.js @@ -0,0 +1,2 @@ +import PageHeader from './PageHeader' +export default PageHeader diff --git a/_web/src/components/PageLoading/index.jsx b/_web/src/components/PageLoading/index.jsx new file mode 100644 index 0000000..af6d6d6 --- /dev/null +++ b/_web/src/components/PageLoading/index.jsx @@ -0,0 +1,106 @@ +import { Spin } from 'ant-design-vue' + +export const PageLoading = { + name: 'PageLoading', + props: { + tip: { + type: String, + default: 'Loading..' + }, + size: { + type: String, + default: 'large' + } + }, + render () { + const style = { + textAlign: 'center', + background: 'rgba(0,0,0,0.6)', + position: 'fixed', + top: 0, + bottom: 0, + left: 0, + right: 0, + zIndex: 1100 + } + const spinStyle = { + position: 'absolute', + left: '50%', + top: '40%', + transform: 'translate(-50%, -50%)' + } + return (<div style={style}> + <Spin size={this.size} style={spinStyle} tip={this.tip} /> + </div>) + } +} + +const version = '0.0.1' +const loading = {} + +loading.newInstance = (Vue, options) => { + let loadingElement = document.querySelector('body>div[type=loading]') + if (!loadingElement) { + loadingElement = document.createElement('div') + loadingElement.setAttribute('type', 'loading') + loadingElement.setAttribute('class', 'ant-loading-wrapper') + document.body.appendChild(loadingElement) + } + + const cdProps = Object.assign({ visible: false, size: 'large', tip: 'Loading...' }, options) + + const instance = new Vue({ + data () { + return { + ...cdProps + } + }, + render () { + const { tip } = this + const props = {} + this.tip && (props.tip = tip) + if (this.visible) { + return <PageLoading { ...{ props } } /> + } + return null + } + }).$mount(loadingElement) + + function update (config) { + const { visible, size, tip } = { ...cdProps, ...config } + instance.$set(instance, 'visible', visible) + if (tip) { + instance.$set(instance, 'tip', tip) + } + if (size) { + instance.$set(instance, 'size', size) + } + } + + return { + instance, + update + } +} + +const api = { + show: function (options) { + this.instance.update({ ...options, visible: true }) + }, + hide: function () { + this.instance.update({ visible: false }) + } +} + +const install = function (Vue, options) { + if (Vue.prototype.$loading) { + return + } + api.instance = loading.newInstance(Vue, options) + Vue.prototype.$loading = api +} + +export default { + version, + install +} diff --git a/_web/src/components/Result/Result.vue b/_web/src/components/Result/Result.vue new file mode 100644 index 0000000..99f7f19 --- /dev/null +++ b/_web/src/components/Result/Result.vue @@ -0,0 +1,109 @@ +<template> + <div class="result"> + <div> + <a-icon :class="{ 'icon': true, [`${type}`]: true }" :type="localIsSuccess ? 'check-circle' : 'close-circle'"/> + </div> + <div class="title"> + <slot name="title"> + {{ title }} + </slot> + </div> + <div class="description"> + <slot name="description"> + {{ description }} + </slot> + </div> + <div class="extra" v-if="$slots.default"> + <slot></slot> + </div> + <div class="action" v-if="$slots.action"> + <slot name="action"></slot> + </div> + </div> +</template> + +<script> +const resultEnum = ['success', 'error'] + +export default { + name: 'Result', + props: { + /** @Deprecated */ + isSuccess: { + type: Boolean, + default: false + }, + type: { + type: String, + default: resultEnum[0], + validator (val) { + return (val) => resultEnum.includes(val) + } + }, + title: { + type: String, + default: '' + }, + description: { + type: String, + default: '' + } + }, + computed: { + localIsSuccess: function () { + return this.type === resultEnum[0] + } + } +} +</script> + +<style lang="less" scoped> + .result { + text-align: center; + width: 72%; + margin: 0 auto; + padding: 24px 0 8px; + + .icon { + font-size: 72px; + line-height: 72px; + margin-bottom: 24px; + } + .success { + color: #52c41a; + } + .error { + color: red; + } + .title { + font-size: 24px; + color: rgba(0, 0, 0, .85); + font-weight: 500; + line-height: 32px; + margin-bottom: 16px; + } + .description { + font-size: 14px; + line-height: 22px; + color: rgba(0, 0, 0, 0.45); + margin-bottom: 24px; + } + .extra { + background: #fafafa; + padding: 24px 40px; + border-radius: 2px; + text-align: left; + } + .action { + margin-top: 32px; + } + } + + .mobile { + .result { + width: 100%; + margin: 0 auto; + padding: unset; + } + } +</style> diff --git a/_web/src/components/Result/index.js b/_web/src/components/Result/index.js new file mode 100644 index 0000000..51cb3b2 --- /dev/null +++ b/_web/src/components/Result/index.js @@ -0,0 +1,2 @@ +import Result from './Result.vue' +export default Result diff --git a/_web/src/components/Search/GlobalSearch.jsx b/_web/src/components/Search/GlobalSearch.jsx new file mode 100644 index 0000000..bd9e604 --- /dev/null +++ b/_web/src/components/Search/GlobalSearch.jsx @@ -0,0 +1,63 @@ +import { Select } from 'ant-design-vue' +import './index.less' + +const GlobalSearch = { + name: 'GlobalSearch', + data () { + return { + visible: false + } + }, + mounted () { + const keyboardHandle = (e) => { + e.preventDefault() + e.stopPropagation() + const { ctrlKey, shiftKey, altKey, keyCode } = e + console.log('keyCode:', e.keyCode, e) + // key is `K` and hold ctrl + if (keyCode === 75 && ctrlKey && !shiftKey && !altKey) { + this.visible = !this.visible + } + } + document.addEventListener('keydown', keyboardHandle) + }, + render () { + const { visible } = this + const handleSearch = (e) => { + this.$emit('search', e) + } + + const handleChange = (e) => { + this.$emit('change', e) + } + if (!visible) { + return null + } + return ( + <div class={'global-search global-search-wrapper'}> + <div class={'global-search-box'}> + <Select + size={'large'} + showSearch + placeholder="Input search text.." + style={{ width: '100%' }} + defaultActiveFirstOption={false} + showArrow={false} + filterOption={false} + onSearch={handleSearch} + onChange={handleChange} + notFoundContent={null} + > + </Select> + <div class={'global-search-tips'}>Open with Ctrl/⌘ + K</div> + </div> + </div> + ) + } +} + +GlobalSearch.install = function (Vue) { + Vue.component(GlobalSearch.name, GlobalSearch) +} + +export default GlobalSearch diff --git a/_web/src/components/Search/index.less b/_web/src/components/Search/index.less new file mode 100644 index 0000000..d397852 --- /dev/null +++ b/_web/src/components/Search/index.less @@ -0,0 +1,25 @@ +@import "~ant-design-vue/es/style/themes/default"; + +.global-search-wrapper { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: @zindex-modal-mask; + background: @modal-mask-bg; + + .global-search-box { + position: absolute; + top: 20%; + left: 50%; + width: 450px; + transform: translate(-50%, -50%); + + .global-search-tips { + color: @white; + font-size: @font-size-lg; + text-align: right; + } + } +} \ No newline at end of file diff --git a/_web/src/components/SettingDrawer/SettingDrawer.vue b/_web/src/components/SettingDrawer/SettingDrawer.vue new file mode 100644 index 0000000..fec9d01 --- /dev/null +++ b/_web/src/components/SettingDrawer/SettingDrawer.vue @@ -0,0 +1,352 @@ +<template> + <div class="setting-drawer"> + <a-drawer + width="300" + placement="right" + @close="onClose" + :closable="false" + :visible="visible" + :drawer-style="{ position: 'absolute' }" + style="position: absolute" + > + <div class="setting-drawer-index-content"> + + <div :style="{ marginBottom: '24px' }"> + <h3 class="setting-drawer-index-title">整体风格设置</h3> + + <div class="setting-drawer-index-blockChecbox"> + <a-tooltip> + <template slot="title"> + 暗色菜单风格 + </template> + <div class="setting-drawer-index-item" @click="handleMenuTheme('dark')"> + <img src="https://gw.alipayobjects.com/zos/rmsportal/LCkqqYNmvBEbokSDscrm.svg" alt="dark"> + <div class="setting-drawer-index-selectIcon" v-if="navTheme === 'dark'"> + <a-icon type="check"/> + </div> + </div> + </a-tooltip> + + <a-tooltip> + <template slot="title"> + 亮色菜单风格 + </template> + <div class="setting-drawer-index-item" @click="handleMenuTheme('light')"> + <img src="https://gw.alipayobjects.com/zos/rmsportal/jpRkZQMyYRryryPNtyIC.svg" alt="light"> + <div class="setting-drawer-index-selectIcon" v-if="navTheme !== 'dark'"> + <a-icon type="check"/> + </div> + </div> + </a-tooltip> + </div> + </div> + + <div :style="{ marginBottom: '24px' }"> + <h3 class="setting-drawer-index-title">主题色</h3> + + <div style="height: 20px"> + <a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index"> + <template slot="title"> + {{ item.key }} + </template> + <a-tag :color="item.color" @click="changeColor(item.color)"> + <a-icon type="check" v-if="item.color === primaryColor"></a-icon> + </a-tag> + </a-tooltip> + + </div> + </div> + <a-divider /> + + <div :style="{ marginBottom: '24px' }"> + <h3 class="setting-drawer-index-title">导航模式</h3> + + <div class="setting-drawer-index-blockChecbox"> + <a-tooltip> + <template slot="title"> + 侧边栏导航 + </template> + <div class="setting-drawer-index-item" @click="handleLayout('sidemenu')"> + <img src="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" alt="sidemenu"> + <div class="setting-drawer-index-selectIcon" v-if="layoutMode === 'sidemenu'"> + <a-icon type="check"/> + </div> + </div> + </a-tooltip> + + <a-tooltip> + <template slot="title"> + 顶部栏导航 + </template> + <div class="setting-drawer-index-item" @click="handleLayout('topmenu')"> + <img src="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" alt="topmenu"> + <div class="setting-drawer-index-selectIcon" v-if="layoutMode !== 'sidemenu'"> + <a-icon type="check"/> + </div> + </div> + </a-tooltip> + </div> + <div :style="{ marginTop: '24px' }"> + <a-list :split="false"> + <a-list-item> + <a-tooltip slot="actions"> + <template slot="title"> + 该设定仅 [顶部栏导航] 时有效 + </template> + <a-select size="small" style="width: 80px;" :defaultValue="contentWidth" @change="handleContentWidthChange"> + <a-select-option value="Fixed">固定</a-select-option> + <a-select-option value="Fluid" v-if="layoutMode !== 'sidemenu'">流式</a-select-option> + </a-select> + </a-tooltip> + <a-list-item-meta> + <div slot="title">内容区域宽度</div> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-switch slot="actions" size="small" :defaultChecked="fixedHeader" @change="handleFixedHeader" /> + <a-list-item-meta> + <div slot="title">固定 Header</div> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-switch slot="actions" size="small" :disabled="!fixedHeader" :defaultChecked="autoHideHeader" @change="handleFixedHeaderHidden" /> + <a-list-item-meta> + <a-tooltip slot="title" placement="left"> + <template slot="title">固定 Header 时可配置</template> + <div :style="{ opacity: !fixedHeader ? '0.5' : '1' }">下滑时隐藏 Header</div> + </a-tooltip> + </a-list-item-meta> + </a-list-item> + <a-list-item > + <a-switch slot="actions" size="small" :disabled="(layoutMode === 'topmenu')" :defaultChecked="fixSiderbar" @change="handleFixSiderbar" /> + <a-list-item-meta> + <div slot="title" :style="{ textDecoration: layoutMode === 'topmenu' ? 'line-through' : 'unset' }">固定侧边菜单</div> + </a-list-item-meta> + </a-list-item> + </a-list> + </div> + </div> + <a-divider /> + + <div :style="{ marginBottom: '24px' }"> + <h3 class="setting-drawer-index-title">其他设置</h3> + <div> + <a-list :split="false"> + <a-list-item> + <a-switch slot="actions" size="small" :defaultChecked="colorWeak" @change="onColorWeak" /> + <a-list-item-meta> + <div slot="title">色弱模式</div> + </a-list-item-meta> + </a-list-item> + <a-list-item> + <a-switch slot="actions" size="small" :defaultChecked="multiTab" @change="onMultiTab" /> + <a-list-item-meta> + <div slot="title">多页签模式</div> + </a-list-item-meta> + </a-list-item> + </a-list> + </div> + </div> + <a-divider /> + <div :style="{ marginBottom: '24px' }"> + <a-button + @click="doCopy" + icon="copy" + block + >拷贝设置</a-button> + <a-alert type="warning" :style="{ marginTop: '24px' }"> + <span slot="message"> + 配置栏只在开发环境用于预览,生产环境不会展现,请手动修改配置文件。修改配置文件后,需要清空本地缓存和LocalStorage + <a href="https://github.com/sendya/ant-design-pro-vue/blob/master/src/config/defaultSettings.js" target="_blank">src/config/defaultSettings.js</a> + </span> + </a-alert> + </div> + </div> + <div class="setting-drawer-index-handle" @click="toggle" slot="handle"> + <a-icon type="setting" v-if="!visible"/> + <a-icon type="close" v-else/> + </div> + </a-drawer> + </div> +</template> + +<script> +/* import { DetailList } from '@/components' +import SettingItem from './SettingItem' */ +import config from '@/config/defaultSettings' +import { updateTheme, updateColorWeak, colorList } from './settingConfig' +import { mixin, mixinDevice } from '@/utils/mixin' + +export default { + components: { + // DetailList, + // SettingItem + }, + mixins: [mixin, mixinDevice], + data () { + return { + visible: false, + colorList + } + }, + watch: { + + }, + mounted () { + updateTheme(this.primaryColor) + if (this.colorWeak !== config.colorWeak) { + updateColorWeak(this.colorWeak) + } + }, + methods: { + showDrawer () { + this.visible = true + }, + onClose () { + this.visible = false + }, + toggle () { + this.visible = !this.visible + }, + onColorWeak (checked) { + this.$store.dispatch('ToggleWeak', checked) + updateColorWeak(checked) + }, + onMultiTab (checked) { + this.$store.dispatch('ToggleMultiTab', checked) + }, + handleMenuTheme (theme) { + this.$store.dispatch('ToggleTheme', theme) + }, + doCopy () { + // get current settings from mixin or this.$store.state.app, pay attention to the property name + const text = `export default { + primaryColor: '${this.primaryColor}', // primary color of ant design + navTheme: '${this.navTheme}', // theme for nav menu + layout: '${this.layoutMode}', // nav menu position: sidemenu or topmenu + contentWidth: '${this.contentWidth}', // layout of content: Fluid or Fixed, only works when layout is topmenu + fixedHeader: ${this.fixedHeader}, // sticky header + fixSiderbar: ${this.fixSiderbar}, // sticky siderbar + autoHideHeader: ${this.autoHideHeader}, // auto hide header + colorWeak: ${this.colorWeak}, + multiTab: ${this.multiTab}, + production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true', + // vue-ls options + storageOptions: { + namespace: 'pro__', + name: 'ls', + storage: 'local', + } +}` + this.$copyText(text).then(message => { + console.log('copy', message) + this.$message.success('复制完毕') + }).catch(err => { + console.log('copy.err', err) + this.$message.error('复制失败') + }) + }, + handleLayout (mode) { + this.$store.dispatch('ToggleLayoutMode', mode) + // 因为顶部菜单不能固定左侧菜单栏,所以强制关闭 + this.handleFixSiderbar(false) + }, + handleContentWidthChange (type) { + this.$store.dispatch('ToggleContentWidth', type) + }, + changeColor (color) { + if (this.primaryColor !== color) { + this.$store.dispatch('ToggleColor', color) + updateTheme(color) + } + }, + handleFixedHeader (fixed) { + this.$store.dispatch('ToggleFixedHeader', fixed) + }, + handleFixedHeaderHidden (autoHidden) { + this.$store.dispatch('ToggleFixedHeaderHidden', autoHidden) + }, + handleFixSiderbar (fixed) { + if (this.layoutMode === 'topmenu') { + this.$store.dispatch('ToggleFixSiderbar', false) + return + } + this.$store.dispatch('ToggleFixSiderbar', fixed) + } + } +} +</script> + +<style lang="less" scoped> + + .setting-drawer-index-content { + + .setting-drawer-index-blockChecbox { + display: flex; + + .setting-drawer-index-item { + margin-right: 16px; + position: relative; + border-radius: 4px; + cursor: pointer; + + img { + width: 48px; + } + + .setting-drawer-index-selectIcon { + position: absolute; + top: 0; + right: 0; + width: 100%; + padding-top: 15px; + padding-left: 24px; + height: 100%; + color: #1890ff; + font-size: 14px; + font-weight: 700; + } + } + } + .setting-drawer-theme-color-colorBlock { + width: 20px; + height: 20px; + border-radius: 2px; + float: left; + cursor: pointer; + margin-right: 8px; + padding-left: 0px; + padding-right: 0px; + text-align: center; + color: #fff; + font-weight: 700; + + i { + font-size: 14px; + } + } + } + + .setting-drawer-index-handle { + position: absolute; + top: 240px; + background: #1890ff; + width: 48px; + height: 48px; + right: 300px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + pointer-events: auto; + z-index: 1001; + text-align: center; + font-size: 16px; + border-radius: 4px 0 0 4px; + + i { + color: rgb(255, 255, 255); + font-size: 20px; + } + } +</style> diff --git a/_web/src/components/SettingDrawer/SettingItem.vue b/_web/src/components/SettingDrawer/SettingItem.vue new file mode 100644 index 0000000..2b3b553 --- /dev/null +++ b/_web/src/components/SettingDrawer/SettingItem.vue @@ -0,0 +1,38 @@ +<template> + <div class="setting-drawer-index-item"> + <h3 class="setting-drawer-index-title">{{ title }}</h3> + <slot></slot> + <a-divider v-if="divider"/> + </div> +</template> + +<script> +export default { + name: 'SettingItem', + props: { + title: { + type: String, + default: '' + }, + divider: { + type: Boolean, + default: false + } + } +} +</script> + +<style lang="less" scoped> + + .setting-drawer-index-item { + margin-bottom: 24px; + + .setting-drawer-index-title { + font-size: 14px; + color: rgba(0, 0, 0, .85); + line-height: 22px; + margin-bottom: 12px; + } + + } +</style> diff --git a/_web/src/components/SettingDrawer/index.js b/_web/src/components/SettingDrawer/index.js new file mode 100644 index 0000000..8260f2d --- /dev/null +++ b/_web/src/components/SettingDrawer/index.js @@ -0,0 +1,2 @@ +import SettingDrawer from './SettingDrawer' +export default SettingDrawer diff --git a/_web/src/components/SettingDrawer/settingConfig.js b/_web/src/components/SettingDrawer/settingConfig.js new file mode 100644 index 0000000..c45911e --- /dev/null +++ b/_web/src/components/SettingDrawer/settingConfig.js @@ -0,0 +1,46 @@ +import { message } from 'ant-design-vue/es' +import themeColor from './themeColor.js' + +const colorList = [ + { + key: '薄暮', color: '#F5222D' + }, + { + key: '火山', color: '#FA541C' + }, + { + key: '日暮', color: '#FAAD14' + }, + { + key: '明青', color: '#13C2C2' + }, + { + key: '极光绿', color: '#52C41A' + }, + { + key: '拂晓蓝(默认)', color: '#1890FF' + }, + { + key: '极客蓝', color: '#2F54EB' + }, + { + key: '酱紫', color: '#722ED1' + } +] + +const updateTheme = newPrimaryColor => { + const hideMessage = message.loading('正在切换主题!', 0) + themeColor.changeColor(newPrimaryColor).finally(t => { + setTimeout(() => { + hideMessage() + }, 10) + }) +} + +const updateColorWeak = colorWeak => { + // document.body.className = colorWeak ? 'colorWeak' : ''; + const app = document.body.querySelector('#app') + colorWeak ? app.classList.add('colorWeak') : app.classList.remove('colorWeak') +} + +export { updateTheme, colorList, updateColorWeak } diff --git a/_web/src/components/SettingDrawer/themeColor.js b/_web/src/components/SettingDrawer/themeColor.js new file mode 100644 index 0000000..10dfbd4 --- /dev/null +++ b/_web/src/components/SettingDrawer/themeColor.js @@ -0,0 +1,24 @@ +import client from 'webpack-theme-color-replacer/client' +import generate from '@ant-design/colors/lib/generate' + +export default { + getAntdSerials (color) { + // 淡化(即less的tint) + const lightens = new Array(9).fill().map((t, i) => { + return client.varyColor.lighten(color, i / 10) + }) + // colorPalette变换得到颜色值 + const colorPalettes = generate(color) + const rgb = client.varyColor.toNum3(color.replace('#', '')).join(',') + return lightens.concat(colorPalettes).concat(rgb) + }, + changeColor (newColor) { + var options = { + newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors` + changeUrl (cssUrl) { + return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path + } + } + return client.changer.changeColor(options, Promise) + } +} diff --git a/_web/src/components/StandardFormRow/StandardFormRow.vue b/_web/src/components/StandardFormRow/StandardFormRow.vue new file mode 100644 index 0000000..a4e261b --- /dev/null +++ b/_web/src/components/StandardFormRow/StandardFormRow.vue @@ -0,0 +1,122 @@ +<template> + <div :class="[prefixCls, lastCls, blockCls, gridCls]"> + <div v-if="title" class="antd-pro-components-standard-form-row-index-label"> + <span>{{ title }}</span> + </div> + <div class="antd-pro-components-standard-form-row-index-content"> + <slot></slot> + </div> + </div> +</template> + +<script> +const classes = [ + 'antd-pro-components-standard-form-row-index-standardFormRowBlock', + 'antd-pro-components-standard-form-row-index-standardFormRowGrid', + 'antd-pro-components-standard-form-row-index-standardFormRowLast' +] +export default { + name: 'StandardFormRow', + props: { + prefixCls: { + type: String, + default: 'antd-pro-components-standard-form-row-index-standardFormRow' + }, + title: { + type: String, + default: undefined + }, + last: { + type: Boolean + }, + block: { + type: Boolean + }, + grid: { + type: Boolean + } + }, + computed: { + lastCls () { + return this.last ? classes[2] : null + }, + blockCls () { + return this.block ? classes[0] : null + }, + gridCls () { + return this.grid ? classes[1] : null + } + } +} +</script> + +<style lang="less" scoped> +@import '../index.less'; + +.antd-pro-components-standard-form-row-index-standardFormRow { + display: flex; + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom: 1px dashed @border-color-split; + + /deep/ .ant-form-item { + margin-right: 24px; + } + /deep/ .ant-form-item-label label { + margin-right: 0; + color: @text-color; + } + /deep/ .ant-form-item-label, + .ant-form-item-control { + padding: 0; + line-height: 32px; + } + + .antd-pro-components-standard-form-row-index-label { + flex: 0 0 auto; + margin-right: 24px; + color: @heading-color; + font-size: @font-size-base; + text-align: right; + & > span { + display: inline-block; + height: 32px; + line-height: 32px; + &::after { + content: ':'; + } + } + } + + .antd-pro-components-standard-form-row-index-content { + flex: 1 1 0; + /deep/ .ant-form-item:last-child { + margin-right: 0; + } + } + + &.antd-pro-components-standard-form-row-index-standardFormRowLast { + margin-bottom: 0; + padding-bottom: 0; + border: none; + } + + &.antd-pro-components-standard-form-row-index-standardFormRowBlock { + /deep/ .ant-form-item, + div.ant-form-item-control-wrapper { + display: block; + } + } + + &.antd-pro-components-standard-form-row-index-standardFormRowGrid { + /deep/ .ant-form-item, + div.ant-form-item-control-wrapper { + display: block; + } + /deep/ .ant-form-item-label { + float: left; + } + } +} + +</style> diff --git a/_web/src/components/StandardFormRow/index.js b/_web/src/components/StandardFormRow/index.js new file mode 100644 index 0000000..8155cc7 --- /dev/null +++ b/_web/src/components/StandardFormRow/index.js @@ -0,0 +1,3 @@ +import StandardFormRow from './StandardFormRow' + +export default StandardFormRow diff --git a/_web/src/components/Table/README.md b/_web/src/components/Table/README.md new file mode 100644 index 0000000..04ce447 --- /dev/null +++ b/_web/src/components/Table/README.md @@ -0,0 +1,340 @@ +Table 重封装组件说明 +==== + + +封装说明 +---- + +> 基础的使用方式与 API 与 [官方版(Table)](https://vuecomponent.github.io/ant-design-vue/components/table-cn/) 本一致,在其基础上,封装了加载数据的方法。 +> +> 你无需在你是用表格的页面进行分页逻辑处理,仅需向 Table 组件传递绑定 `:data="Promise"` 对象即可 + +该 `table` 由 [@Saraka](https://github.com/saraka-tsukai) 完成封装 + +由 `小诺` 完成二次改进使用 + +例子1 +---- +(基础使用) + +```vue + +<template> + <s-table + ref="table" + :rowKey="(record) => record.data.id" + :columns="columns" + :data="loadData" + :rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + > + </s-table> +</template> + +<script> + import STable from '@/components' + + export default { + components: { + STable + }, + data() { + return { + columns: [ + { + title: '规则编号', + dataIndex: 'no' + }, + { + title: '描述', + dataIndex: 'description' + }, + { + title: '服务调用次数', + dataIndex: 'callNo', + sorter: true, + needTotal: true, + customRender: (text) => text + ' 次' + }, + { + title: '状态', + dataIndex: 'status', + needTotal: true + }, + { + title: '更新时间', + dataIndex: 'updatedAt', + sorter: true + } + ], + // 查询条件参数 + queryParam: {}, + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return this.$http.get('/service', { + params: Object.assign(parameter, this.queryParam) + }).then(res => { + return res.result + }) + }, + selectedRowKeys: [], + selectedRows: [] + } + }, + methods: { + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> + +``` + + + +例子2 +---- + +(简单的表格,最后一列是各种操作) + +```vue +<template> + <s-table + ref="table" + :columns="columns" + :data="loadData" + > + <span slot="action" slot-scope="text, record"> + <a>编辑</a> + <a-divider type="vertical"/> + <a-dropdown> + <a class="ant-dropdown-link"> + 更多 <a-icon type="down"/> + </a> + <a-menu slot="overlay"> + <a-menu-item> + <a href="javascript:;">1st menu item</a> + </a-menu-item> + <a-menu-item> + <a href="javascript:;">2nd menu item</a> + </a-menu-item> + <a-menu-item> + <a href="javascript:;">3rd menu item</a> + </a-menu-item> + </a-menu> + </a-dropdown> + </span> + </s-table> +</template> + +<script> + import STable from '@/components/table/' + + export default { + components: { + STable + }, + data() { + return { + columns: [ + { + title: '规则编号', + dataIndex: 'no' + }, + { + title: '描述', + dataIndex: 'description' + }, + { + title: '服务调用次数', + dataIndex: 'callNo', + }, + { + title: '状态', + dataIndex: 'status', + }, + { + title: '更新时间', + dataIndex: 'updatedAt', + }, + { + table: '操作', + dataIndex: 'action', + scopedSlots: {customRender: 'action'}, + } + ], + // 查询条件参数 + queryParam: {}, + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return this.$http.get('/service', { + params: Object.assign(parameter, this.queryParam) + }).then(res => { + return res.result + }) + }, + } + }, + methods: { + edit(row) { + // axios 发送数据到后端 修改数据成功后 + // 调用 refresh() 重新加载列表数据 + // 这里 setTimeout 模拟发起请求的网络延迟.. + setTimeout(() => { + this.$refs.table.refresh() // refresh() 不传参默认值 false 不刷新到分页第一页 + }, 1500) + + } + } + } +</script> +``` + + + +内置方法 +---- + +通过 `this.$refs.table` 调用 + +`this.$refs.table.refresh(true)` 刷新列表 (用户新增/修改数据后,重载列表数据) + +> 注意:要调用 `refresh(bool)` 需要给表格组件设定 `ref` 值 +> +> `refresh()` 方法可以传一个 `bool` 值,当有传值 或值为 `true` 时,则刷新时会强制刷新到第一页(常用户页面 搜索 按钮进行搜索时,结果从第一页开始分页) + + +内置属性 +---- +> 除去 `a-table` 自带属性外,还而外提供了一些额外属性属性 + + +| 属性 | 说明 | 类型 | 默认值 | +| -------------- | ----------------------------------------------- | ----------------- | ------ | +| alert | 设置是否显示表格信息栏 | [object, boolean] | null | +| showPagination | 显示分页选择器,可传 'auto' \| boolean | [string, boolean] | 'auto' | +| data | 加载数据方法 必须为 `Promise` 对象 **必须绑定** | Promise | - | + + +`alert` 属性对象: + +```javascript +alert: { + show: Boolean, + clear: [Function, Boolean] +} +``` + +注意事项 +---- + +> 你可能需要为了与后端提供的接口返回结果一致而去修改以下代码: +> (需要注意的是,这里的修改是全局性的,意味着整个项目所有使用该 table 组件都需要遵守这个返回结果定义的字段。) +> +> 文档中的结构有可能由于组件 bug 进行修正而改动。实际修改请以当时最新版本为准 + +修改 `@/components/table/index.js` 第 156 行起 + + + +```javascript +result.then(r => { + this.localPagination = this.showPagination && Object.assign({}, this.localPagination, { + current: r.pageNo, // 返回结果中的当前分页数 + total: r.totalCount, // 返回结果中的总记录数 + showSizeChanger: this.showSizeChanger, + pageSize: (pagination && pagination.pageSize) || + this.localPagination.pageSize + }) || false + // 为防止删除数据后导致页面当前页面数据长度为 0 ,自动翻页到上一页 + if (r.data.length === 0 && this.showPagination && this.localPagination.current > 1) { + this.localPagination.current-- + this.loadData() + return + } + + // 这里用于判断接口是否有返回 r.totalCount 且 this.showPagination = true 且 pageNo 和 pageSize 存在 且 totalCount 小于等于 pageNo * pageSize 的大小 + // 当情况满足时,表示数据不满足分页大小,关闭 table 分页功能 + try { + if ((['auto', true].includes(this.showPagination) && r.totalCount <= (r.pageNo * this.localPagination.pageSize))) { + this.localPagination.hideOnSinglePage = true + } + } catch (e) { + this.localPagination = false + } + console.log('loadData -> this.localPagination', this.localPagination) + this.localDataSource = r.data // 返回结果中的数组数据 + this.localLoading = false + }) +``` +返回 JSON 例子: +```json +{ + "message": "", + "result": { + "data": [{ + id: 1, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', + title: 'Alipay', + description: '那是一种内在的东西, 他们到达不了,也无法触及的', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 2, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', + title: 'Angular', + description: '希望是一个好东西,也许是最好的,好东西是不会消亡的', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 3, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', + title: 'Ant Design', + description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 4, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', + title: 'Snowy', + description: '那时候我只会想自己想要什么,从不想自己拥有什么', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 5, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', + title: 'Bootstrap', + description: '凛冬将至', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 6, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', + title: 'Vue', + description: '生命就像一盒巧克力,结果往往出人意料', + status: 1, + updatedAt: '2018-07-26 00:00:00' + } + ], + "pageSize": 10, + "pageNo": 0, + "totalPage": 6, + "totalCount": 57 + }, + "status": 200, + "timestamp": 1534955098193 +} +``` + + + +更新时间 +---- + +该文档最后更新于: 2019-06-23 PM 17:19 \ No newline at end of file diff --git a/_web/src/components/Table/columnSetting.vue b/_web/src/components/Table/columnSetting.vue new file mode 100644 index 0000000..266fdb9 --- /dev/null +++ b/_web/src/components/Table/columnSetting.vue @@ -0,0 +1,82 @@ +<template> + <div slot="overlay" class="ant-dropdown-menu s-tool-column ant-dropdown-content"> + <div class="s-tool-column-header s-tool-column-item"> + <a-checkbox :indeterminate="indeterminate" :checked="checkAll" @change="onCheckAllChange"> + 列展示 + </a-checkbox> + <a @click="reset">重置</a> + </div> + <a-divider /> + <div class="ant-checkbox-group"> + <div> + <draggable v-model="columnsSetting" animation="300" @end="emitColumnChange"> + <div class="s-tool-column-item" v-for="item in columnsSetting" :key="item.title"> + <div class="s-tool-column-handle" > + <a-icon type="more"/> + <a-icon type="more"/> + </div> + <a-checkbox v-model="item.checked" @change="onChange">{{ item.title }}</a-checkbox> + </div> + </draggable> + </div> + </div> + </div> +</template> + +<script> + import draggable from 'vuedraggable' + + export default { + props: { + columns: { + type: Array, + default: () => ([]) + } + }, + components: { + draggable + }, + data() { + return { + indeterminate: false, + checkAll: true, + columnsSetting: [], + originColumns: [] + } + }, + methods: { + reset() { + this.columnsSetting = JSON.parse(JSON.stringify(this.originColumns)) + this.indeterminate = false + this.checkAll = true + this.emitColumnChange() + }, + onChange() { + const checkedList = this.columnsSetting.filter(value => value.checked) + this.indeterminate = !!checkedList.length && checkedList.length < this.columnsSetting.length + this.checkAll = checkedList.length === this.columnsSetting.length + this.emitColumnChange() + }, + onCheckAllChange(e) { + const val = e.target.checked + Object.assign(this, { + indeterminate: false, + checkAll: val, + columnsSetting: this.columns.map(value => ({ ...value, checked: val })) + }) + this.emitColumnChange() + }, + emitColumnChange() { + this.$emit('columnChange', this.columnsSetting) + } + }, + mounted() { + this.columnsSetting = this.columns.map(value => ({ ...value, checked: true })) + this.originColumns = JSON.parse(JSON.stringify(this.columnsSetting)) + } + } +</script> + +<style lang="less" scoped> + +</style> diff --git a/_web/src/components/Table/index.js b/_web/src/components/Table/index.js new file mode 100644 index 0000000..b2286f0 --- /dev/null +++ b/_web/src/components/Table/index.js @@ -0,0 +1,449 @@ +import T from 'ant-design-vue/es/table/Table' +import get from 'lodash.get' +import draggable from 'vuedraggable' +import columnSetting from './columnSetting' +import './index.less' + +export default { + components: { + draggable, columnSetting + }, + data () { + return { + needTotalList: [], + + selectedRows: [], + selectedRowKeys: [], + + localLoading: false, + localDataSource: [], + localPagination: Object.assign({}, this.pagination), + isFullscreen: false, + customSize: this.size, + columnsSetting: [] + } + }, + props: Object.assign({}, T.props, { + rowKey: { + type: [String, Function], + default: 'key' + }, + data: { + type: Function, + required: true + }, + pageNum: { + type: Number, + default: 1 + }, + pageSize: { + type: Number, + default: 10 + }, + showSizeChanger: { + type: Boolean, + default: true + }, + size: { + type: String, + default: 'middle' + }, + /** + * alert: { + * show: true, + * clear: Function + * } + */ + alert: { + type: [Object, Boolean], + default: null + }, + rowSelection: { + type: Object, + default: null + }, + /** @Deprecated */ + showAlertInfo: { + type: Boolean, + default: false + }, + showPagination: { + type: String | Boolean, + default: 'auto' + }, + /** + * enable page URI mode + * + * e.g: + * /users/1 + * /users/2 + * /users/3?queryParam=test + * ... + */ + pageURI: { + type: Boolean, + default: false + }, + extraTool: { + type: Array, + default: () => ([]) + } + }), + watch: { + 'localPagination.current' (val) { + this.pageURI && this.$router.push({ + ...this.$route, + name: this.$route.name, + params: Object.assign({}, this.$route.params, { + pageNo: val + }) + }) + }, + pageNum (val) { + Object.assign(this.localPagination, { + current: val + }) + }, + pageSize (val) { + Object.assign(this.localPagination, { + pageSize: val + }) + }, + showSizeChanger (val) { + Object.assign(this.localPagination, { + showSizeChanger: val + }) + }, + columns(v) { + this.columnsSetting = v + } + }, + created () { + const { pageNo } = this.$route.params + const localPageNum = this.pageURI && (pageNo && parseInt(pageNo)) || this.pageNum + this.localPagination = ['auto', true].includes(this.showPagination) && Object.assign({}, this.localPagination, { + current: localPageNum, + pageSize: this.pageSize, + showSizeChanger: this.showSizeChanger, + showTotal: (total, range) => { + return range[0] + '-' + range[1] + '共' + total + '条' + } + }) || false + this.needTotalList = this.initTotalList(this.columns) + this.loadData() + this.columnsSetting = this.columns + }, + methods: { + /** + * 表格重新加载方法 + * 如果参数为 true, 则强制刷新到第一页 + * @param Boolean bool + */ + refresh (bool = false) { + bool && (this.localPagination = Object.assign({}, { + current: 1, pageSize: this.pageSize + })) + this.loadData() + }, + /** + * 加载数据方法 + * @param {Object} pagination 分页选项器 + * @param {Object} filters 过滤条件 + * @param {Object} sorter 排序条件 + */ + loadData (pagination, filters, sorter) { + this.localLoading = true + const parameter = Object.assign({ + pageNo: (pagination && pagination.current) || + this.showPagination && this.localPagination.current || this.pageNum, + pageSize: (pagination && pagination.pageSize) || + this.showPagination && this.localPagination.pageSize || this.pageSize + }, + (sorter && sorter.field && { + sortField: sorter.field + }) || {}, + (sorter && sorter.order && { + sortOrder: sorter.order + }) || {}, { + ...filters + } + ) + const result = this.data(parameter) + // 对接自己的通用数据接口需要修改下方代码中的 r.pageNo, r.totalCount, r.data + // eslint-disable-next-line + if ((typeof result === 'object' || typeof result === 'function') && typeof result.then === 'function') { + result.then(r => { + if (r == null) { + this.localLoading = false + return + } + this.localPagination = this.showPagination && Object.assign({}, this.localPagination, { + current: r.pageNo, // pageNo, // 返回结果中的当前分页数 + total: r.totalRows, // totalCount, // 返回结果中的总记录数 + showSizeChanger: this.showSizeChanger, + pageSize: (pagination && pagination.pageSize) || + this.localPagination.pageSize + }) || false + // 后端数据rows为null保存修复 + if (r.rows == null) { + r.rows = [] + } + // 为防止删除数据后导致页面当前页面数据长度为 0 ,自动翻页到上一页 + if (r.rows.length === 0 && this.showPagination && this.localPagination.current > 1) { + this.localPagination.current-- + this.loadData() + return + } + + // 这里用于判断接口是否有返回 r.totalCount 且 this.showPagination = true 且 pageNo 和 pageSize 存在 且 totalCount 小于等于 pageNo * pageSize 的大小 + // 当情况满足时,表示数据不满足分页大小,关闭 table 分页功能 + try { + if ((['auto', true].includes(this.showPagination) && r.totalCount <= (r.totalPage * this.localPagination.pageSize))) { + this.localPagination.hideOnSinglePage = true + } + } catch (e) { + this.localPagination = false + } + // 返回结果中的数组数据 + if (this.showPagination === false) { + // 因为按住小诺的套路,不分页的直接是在data中,我们在界面中直接就是返回了data + this.localDataSource = r + } else { + this.localDataSource = r.rows + } + this.localLoading = false + }) + } + }, + initTotalList (columns) { + const totalList = [] + columns && columns instanceof Array && columns.forEach(column => { + if (column.needTotal) { + totalList.push({ + ...column, + total: 0 + }) + } + }) + return totalList + }, + /** + * 用于更新已选中的列表数据 total 统计 + * @param selectedRowKeys + * @param selectedRows + */ + updateSelect (selectedRowKeys, selectedRows) { + this.selectedRows = selectedRows + this.selectedRowKeys = selectedRowKeys + const list = this.needTotalList + this.needTotalList = list.map(item => { + return { + ...item, + total: selectedRows.reduce((sum, val) => { + const total = sum + parseInt(get(val, item.dataIndex)) + return isNaN(total) ? 0 : total + }, 0) + } + }) + }, + /** + * 清空 table 已选中项 + */ + clearSelected () { + if (this.rowSelection) { + this.rowSelection.onChange([], []) + this.updateSelect([], []) + } + }, + /** + * 刷新并清空已选 + */ + clearRefreshSelected (bool = false) { + this.refresh(bool) + this.clearSelected() + }, + /** + * 处理交给 table 使用者去处理 clear 事件时,内部选中统计同时调用 + * @param callback + * @returns {*} + */ + renderClear (callback) { + if (this.selectedRowKeys.length <= 0) return null + return ( + <a style="margin-left: 24px" onClick={() => { + callback() + this.clearSelected() + }}>清空</a> + ) + }, + renderAlert () { + // 绘制统计列数据 + // eslint-disable-next-line no-unused-vars + const needTotalItems = this.needTotalList.map((item) => { + return (<span style="margin-right: 12px"> + {item.title}总计 <a style="font-weight: 600">{!item.customRender ? item.total : item.customRender(item.total)}</a> + </span>) + }) + + // 绘制 清空 按钮 + // eslint-disable-next-line no-unused-vars + const clearItem = (typeof this.alert.clear === 'boolean' && this.alert.clear) ? ( + this.renderClear(this.clearSelected) + ) : (this.alert !== null && typeof this.alert.clear === 'function') ? ( + this.renderClear(this.alert.clear) + ) : null + + // 绘制 alert 组件 + // 统一先去除alert组件 + return ( + <a-alert showIcon={true} style="margin-bottom: 16px"> + <template slot="message"> + <span style="margin-right: 12px">已选择: <a style="font-weight: 600">{this.selectedRows.length}</a></span> + {needTotalItems} + {clearItem} + </template> + </a-alert> + ) + }, + columnChange(val) { + this.columnsSetting = val + }, + renderHeader () { + let tools = [ + { + icon: 'reload', + title: '刷新', + onClick: () => { + this.refresh() + } + }, + { + icon: 'column-height', + title: '密度', + isDropdown: true, + menu: () => { + const onClick = ({ key }) => { + this.customSize = key + } + return ( + <a-menu slot="overlay" onClick={onClick} selectable defaultSelectedKeys={[this.customSize]}> + <a-menu-item key="default">默认</a-menu-item> + <a-menu-item key="middle">中等</a-menu-item> + <a-menu-item key="small">紧凑</a-menu-item> + </a-menu> + ) + }, + onClick: () => { + } + }, + { + icon: 'setting', + title: '列设置', + isDropdown: true, + menu: () => { + return <columnSetting slot="overlay" columns={this.columns} onColumnChange={this.columnChange} /> + }, + onClick: () => { + } + } + ] + if (this.extraTool.length) { + tools = tools.concat(this.extraTool) + } + + return ( + <div class="s-table-tool"> + <div class="s-table-tool-left"> + {this.$scopedSlots.operator && this.$scopedSlots.operator()} + </div> + <div class="s-table-tool-right"> + { + tools.map(tool => { + if (tool.isDropdown) { + return ( + <a-dropdown trigger={['click']}> + <a-tooltip title={tool.title} class="s-tool-item" onClick={tool.onClick}> + <a-icon type={tool.icon}/> + </a-tooltip> + { tool.menu() } + </a-dropdown> + ) + } + return ( + <a-tooltip title={tool.title} class="s-tool-item" onClick={tool.onClick}> + <a-icon type={tool.icon} /> + </a-tooltip> + ) + }) + } + </div> + </div> + ) + /* + return ( + <a-alert showIcon={true} style="margin-bottom: 16px"> + <template slot="message"> + <span style="margin-right: 12px">已选择: <a style="font-weight: 600">{this.selectedRows.length}</a></span> + {needTotalItems} + {clearItem} + </template> + </a-alert> + ) + */ + } + }, + + render () { + let props = {} + const localKeys = Object.keys(this.$data) + const showAlert = (typeof this.alert === 'object' && this.alert !== null && this.alert.show) && typeof this.rowSelection.selectedRowKeys !== 'undefined' || this.alert + + Object.keys(T.props).forEach(k => { + const localKey = `local${k.substring(0, 1).toUpperCase()}${k.substring(1)}` + if (localKeys.includes(localKey)) { + props[k] = this[localKey] + return props[k] + } + if (k === 'rowSelection') { + if (showAlert && this.rowSelection) { + // 如果需要使用alert,则重新绑定 rowSelection 事件 + props[k] = { + ...this.rowSelection, + selectedRows: this.selectedRows, + selectedRowKeys: this.selectedRowKeys, + onChange: (selectedRowKeys, selectedRows) => { + this.updateSelect(selectedRowKeys, selectedRows) + typeof this[k].onChange !== 'undefined' && this[k].onChange(selectedRowKeys, selectedRows) + } + } + return props[k] + } else if (!this.rowSelection) { + // 如果没打算开启 rowSelection 则清空默认的选择项 + props[k] = null + return props[k] + } + } + this[k] && (props[k] = this[k]) + // 此处配置表格大小与要显示的列 + props = { + ...props, + size: this.customSize, + columns: this.columnsSetting.filter(value => value.checked === undefined || value.checked) + } + return props[k] + }) + const table = ( + <a-table {...{ props, scopedSlots: { ...this.$scopedSlots } }} onChange={this.loadData} onExpand={ (expanded, record) => { this.$emit('expand', expanded, record) } }> + { Object.keys(this.$slots).map(name => (<template slot={name}>{this.$slots[name]}</template>)) } + </a-table> + ) + + return ( + <div class="table-wrapper"> + { this.renderHeader() } + { showAlert ? this.renderAlert() : null } + { table } + </div> + ) + } + } diff --git a/_web/src/components/Table/index.less b/_web/src/components/Table/index.less new file mode 100644 index 0000000..3ef6793 --- /dev/null +++ b/_web/src/components/Table/index.less @@ -0,0 +1,54 @@ +.table-wrapper{ + background: #fff; +} +.s-table-tool{ + display: flex; + margin-bottom: 16px; + .s-table-tool-left{ + flex: 1; + } + .s-table-tool-right{ + display: inline-flex; + align-items: center; + .s-tool-item{ + font-size: 16px; + margin-left: 16px; + cursor: pointer; + + } + } +} + +.s-tool-column-item{ + display: flex; + align-items: center; + padding: 4px 16px 4px 4px; + .ant-checkbox-wrapper{ + flex: 1; + } + .s-tool-column-handle{ + opacity: .8; + cursor: move; + .anticon-more{ + font-size: 12px; + margin-top: 2px; + & + .anticon-more{ + margin: 2px 4px 0 -8px; + } + } + } +} +.s-tool-column-header{ + padding: 5px 16px 10px 24px; + min-width: 180px; +} +.s-tool-column{ + .ant-divider{ + margin: 0; + } + .ant-checkbox-group{ + padding: 4px 0; + display: block; + } +} + diff --git a/_web/src/components/TagSelect/TagSelectOption.jsx b/_web/src/components/TagSelect/TagSelectOption.jsx new file mode 100644 index 0000000..b5ae799 --- /dev/null +++ b/_web/src/components/TagSelect/TagSelectOption.jsx @@ -0,0 +1,45 @@ +import { Tag } from 'ant-design-vue' +const { CheckableTag } = Tag + +export default { + name: 'TagSelectOption', + props: { + prefixCls: { + type: String, + default: 'ant-pro-tag-select-option' + }, + value: { + type: [String, Number, Object], + default: '' + }, + checked: { + type: Boolean, + default: false + } + }, + data () { + return { + localChecked: this.checked || false + } + }, + watch: { + 'checked' (val) { + this.localChecked = val + }, + '$parent.items': { + handler: function (val) { + this.value && val.hasOwnProperty(this.value) && (this.localChecked = val[this.value]) + }, + deep: true + } + }, + render () { + const { $slots, value } = this + const onChange = (checked) => { + this.$emit('change', { value, checked }) + } + return (<CheckableTag key={value} vModel={this.localChecked} onChange={onChange}> + {$slots.default} + </CheckableTag>) + } +} diff --git a/_web/src/components/TagSelect/index.jsx b/_web/src/components/TagSelect/index.jsx new file mode 100644 index 0000000..af98ad7 --- /dev/null +++ b/_web/src/components/TagSelect/index.jsx @@ -0,0 +1,113 @@ +import PropTypes from 'ant-design-vue/es/_util/vue-types' +import Option from './TagSelectOption.jsx' +import { filterEmpty } from '@/components/_util/util' + +export default { + Option, + name: 'TagSelect', + model: { + prop: 'checked', + event: 'change' + }, + props: { + prefixCls: { + type: String, + default: 'ant-pro-tag-select' + }, + defaultValue: { + type: PropTypes.array, + default: null + }, + value: { + type: PropTypes.array, + default: null + }, + expandable: { + type: Boolean, + default: false + }, + hideCheckAll: { + type: Boolean, + default: false + } + }, + data () { + return { + expand: false, + localCheckAll: false, + items: this.getItemsKey(filterEmpty(this.$slots.default)), + val: this.value || this.defaultValue || [] + } + }, + methods: { + onChange (checked) { + const key = Object.keys(this.items).filter(key => key === checked.value) + this.items[key] = checked.checked + const bool = Object.values(this.items).lastIndexOf(false) + if (bool === -1) { + this.localCheckAll = true + } else { + this.localCheckAll = false + } + }, + onCheckAll (checked) { + Object.keys(this.items).forEach(v => { + this.items[v] = checked.checked + }) + this.localCheckAll = checked.checked + }, + getItemsKey (items) { + const totalItem = {} + items.forEach(item => { + totalItem[item.componentOptions.propsData && item.componentOptions.propsData.value] = false + }) + return totalItem + }, + // CheckAll Button + renderCheckAll () { + const props = { + on: { + change: (checked) => { + this.onCheckAll(checked) + checked.value = 'total' + this.$emit('change', checked) + } + } + } + const checkAllElement = <Option key={'total'} checked={this.localCheckAll} {...props}>All</Option> + return !this.hideCheckAll && checkAllElement || null + }, + // expandable + renderExpandable () { + + }, + // render option + renderTags (items) { + const listeners = { + change: (checked) => { + this.onChange(checked) + this.$emit('change', checked) + } + } + + return items.map(vnode => { + const options = vnode.componentOptions + options.listeners = listeners + return vnode + }) + } + }, + render () { + const { $props: { prefixCls } } = this + const classString = { + [`${prefixCls}`]: true + } + const tagItems = filterEmpty(this.$slots.default) + return ( + <div class={classString}> + {this.renderCheckAll()} + {this.renderTags(tagItems)} + </div> + ) + } +} diff --git a/_web/src/components/TextArea/index.jsx b/_web/src/components/TextArea/index.jsx new file mode 100644 index 0000000..00aeb2f --- /dev/null +++ b/_web/src/components/TextArea/index.jsx @@ -0,0 +1,69 @@ +import './style.less' +import { getStrFullLength, cutStrByFullLength } from '../_util/util' +import Input from 'ant-design-vue/es/input' +const TextArea = Input.TextArea + +export default { + name: 'LimitTextArea', + model: { + prop: 'value', + event: 'change' + }, + props: Object.assign({}, TextArea.props, { + prefixCls: { + type: String, + default: 'ant-textarea-limit' + }, + // eslint-disable-next-line + value: { + type: String + }, + limit: { + type: Number, + default: 200 + } + }), + data () { + return { + currentLimit: 0 + } + }, + watch: { + value (val) { + this.calcLimitNum(val) + } + }, + created () { + this.calcLimitNum(this.value) + }, + methods: { + handleChange (e) { + const value = e.target.value + const len = getStrFullLength(value) + if (len <= this.limit) { + this.currentLimit = len + this.$emit('change', value) + return + } else { + const str = cutStrByFullLength(value, this.limit) + this.currentLimit = getStrFullLength(str) + this.$emit('change', str) + } + console.error('limit out! currentLimit:', this.currentLimit) + }, + calcLimitNum (val) { + const len = getStrFullLength(val) + this.currentLimit = len + } + }, + render () { + const { prefixCls, ...props } = this.$props + return ( + <div class={this.prefixCls}> + <TextArea {...{ props }} value={this.value} onChange={this.handleChange}> + </TextArea> + <span class="limit">{this.currentLimit}/{this.limit}</span> + </div> + ) + } +} diff --git a/_web/src/components/TextArea/style.less b/_web/src/components/TextArea/style.less new file mode 100644 index 0000000..6dee494 --- /dev/null +++ b/_web/src/components/TextArea/style.less @@ -0,0 +1,12 @@ +.ant-textarea-limit { + position: relative; + + .limit { + position: absolute; + color: #909399; + background: #fff; + font-size: 12px; + bottom: 5px; + right: 10px; + } +} \ No newline at end of file diff --git a/_web/src/components/Tree/Tree.jsx b/_web/src/components/Tree/Tree.jsx new file mode 100644 index 0000000..e5a2a11 --- /dev/null +++ b/_web/src/components/Tree/Tree.jsx @@ -0,0 +1,124 @@ +import { Menu, Icon, Input } from 'ant-design-vue' + +const { Item, ItemGroup, SubMenu } = Menu +const { Search } = Input + +export default { + name: 'Tree', + props: { + dataSource: { + type: Array, + required: true + }, + openKeys: { + type: Array, + default: () => [] + }, + search: { + type: Boolean, + default: false + } + }, + created () { + this.localOpenKeys = this.openKeys.slice(0) + }, + data () { + return { + localOpenKeys: [] + } + }, + methods: { + handlePlus (item) { + this.$emit('add', item) + }, + handleTitleClick (...args) { + this.$emit('titleClick', { args }) + }, + + renderSearch () { + return ( + <Search + placeholder="input search text" + style="width: 100%; margin-bottom: 1rem" + /> + ) + }, + renderIcon (icon) { + return icon && (<Icon type={icon} />) || null + }, + renderMenuItem (item) { + return ( + <Item key={item.key}> + { this.renderIcon(item.icon) } + { item.title } + <a class="btn" style="width: 20px;z-index:1300" {...{ on: { click: () => this.handlePlus(item) } }}><a-icon type="plus"/></a> + </Item> + ) + }, + renderItem (item) { + return item.children ? this.renderSubItem(item, item.key) : this.renderMenuItem(item, item.key) + }, + renderItemGroup (item) { + const childrenItems = item.children.map(o => { + return this.renderItem(o, o.key) + }) + + return ( + <ItemGroup key={item.key}> + <template slot="title"> + <span>{ item.title }</span> + <a-dropdown> + <a class="btn"><a-icon type="ellipsis" /></a> + <a-menu slot="overlay"> + <a-menu-item key="1">新增</a-menu-item> + <a-menu-item key="2">合并</a-menu-item> + <a-menu-item key="3">移除</a-menu-item> + </a-menu> + </a-dropdown> + </template> + { childrenItems } + </ItemGroup> + ) + }, + renderSubItem (item, key) { + const childrenItems = item.children && item.children.map(o => { + return this.renderItem(o, o.key) + }) + + const title = ( + <span slot="title"> + { this.renderIcon(item.icon) } + <span>{ item.title }</span> + </span> + ) + + if (item.group) { + return this.renderItemGroup(item) + } + // titleClick={this.handleTitleClick(item)} + return ( + <SubMenu key={key}> + { title } + { childrenItems } + </SubMenu> + ) + } + }, + render () { + const { dataSource, search } = this.$props + + // this.localOpenKeys = openKeys.slice(0) + const list = dataSource.map(item => { + return this.renderItem(item) + }) + + return ( + <div class="tree-wrapper"> + { search ? this.renderSearch() : null } + <Menu mode="inline" class="custom-tree" {...{ on: { click: item => this.$emit('click', item), 'update:openKeys': val => { this.localOpenKeys = val } } }} openKeys={this.localOpenKeys}> + { list } + </Menu> + </div> + ) + } +} diff --git a/_web/src/components/Trend/Trend.vue b/_web/src/components/Trend/Trend.vue new file mode 100644 index 0000000..526e1cc --- /dev/null +++ b/_web/src/components/Trend/Trend.vue @@ -0,0 +1,41 @@ +<template> + <div :class="[prefixCls, reverseColor && 'reverse-color' ]"> + <span> + <slot name="term"></slot> + <span class="item-text"> + <slot></slot> + </span> + </span> + <span :class="[flag]"><a-icon :type="`caret-${flag}`"/></span> + </div> +</template> + +<script> +export default { + name: 'Trend', + props: { + prefixCls: { + type: String, + default: 'ant-pro-trend' + }, + /** + * 上升下降标识:up|down + */ + flag: { + type: String, + required: true + }, + /** + * 颜色反转 + */ + reverseColor: { + type: Boolean, + default: false + } + } +} +</script> + +<style lang="less" scoped> + @import "index"; +</style> diff --git a/_web/src/components/Trend/index.js b/_web/src/components/Trend/index.js new file mode 100644 index 0000000..9f14228 --- /dev/null +++ b/_web/src/components/Trend/index.js @@ -0,0 +1,3 @@ +import Trend from './Trend.vue' + +export default Trend diff --git a/_web/src/components/Trend/index.less b/_web/src/components/Trend/index.less new file mode 100644 index 0000000..8a3d24c --- /dev/null +++ b/_web/src/components/Trend/index.less @@ -0,0 +1,42 @@ +@import "../index"; + +@trend-prefix-cls: ~"@{ant-pro-prefix}-trend"; + +.@{trend-prefix-cls} { + display: inline-block; + font-size: @font-size-base; + line-height: 22px; + + .up, + .down { + margin-left: 4px; + position: relative; + top: 1px; + + i { + font-size: 12px; + transform: scale(0.83); + } + } + + .item-text { + display: inline-block; + margin-left: 8px; + color: rgba(0,0,0,.85); + } + + .up { + color: @red-6; + } + .down { + color: @green-6; + top: -1px; + } + + &.reverse-color .up { + color: @green-6; + } + &.reverse-color .down { + color: @red-6; + } +} \ No newline at end of file diff --git a/_web/src/components/Trend/index.md b/_web/src/components/Trend/index.md new file mode 100644 index 0000000..8881f0e --- /dev/null +++ b/_web/src/components/Trend/index.md @@ -0,0 +1,45 @@ +# Trend 趋势标记 + +趋势符号,标记上升和下降趋势。通常用绿色代表“好”,红色代表“不好”,股票涨跌场景除外。 + + + +引用方式: + +```javascript +import Trend from '@/components/Trend' + +export default { + components: { + Trend + } +} +``` + + + +## 代码演示 [demo](https://pro.loacg.com/test/home) + +```html +<trend flag="up">5%</trend> +``` +或 +```html +<trend flag="up"> + <span slot="term">工资</span> + 5% +</trend> +``` +或 +```html +<trend flag="up" term="工资">5%</trend> +``` + + +## API + +| 参数 | 说明 | 类型 | 默认值 | +|----------|------------------------------------------|-------------|-------| +| flag | 上升下降标识:`up|down` | string | - | +| reverseColor | 颜色反转 | Boolean | false | + diff --git a/_web/src/components/UserSelect/UserSelect.vue b/_web/src/components/UserSelect/UserSelect.vue new file mode 100644 index 0000000..c38a54b --- /dev/null +++ b/_web/src/components/UserSelect/UserSelect.vue @@ -0,0 +1,77 @@ +<template> + <a-select + :mode="model" + showSearch + :value="selectValue" + :filter-option="false" + :placeholder="placeholder" + :not-found-content="fetching ? undefined : null" + @search="fetchUser" + @change="handleChange" + > + <a-spin v-if="fetching" slot="notFoundContent" size="small" /> + <a-select-option v-for="d in data" :key="d.value"> + {{ d.text }} + </a-select-option> + </a-select> +</template> +<script> +import debounce from 'lodash/debounce' +import { getUserPage } from '@/api/modular/system/userManage' + +export default { + name: 'UserSelect', + props: { + placeholder: { + type: String + }, + value: { + type: String + }, + multiple: { + type: Boolean, + default: false + } + }, + data() { + const multiple = this.multiple + this.fetchUser = debounce(this.fetchUser, 800) + return { + data: [], + fetching: false, + selectValue: multiple ? [] : undefined, + model: multiple ? 'multiple' : 'default' + } + }, + methods: { + fetchUser(key) { + console.log('fetching user', key) + this.data = [] + this.fetching = true + + const params = { + pageNo: 1, + pageSize: 10, + searchValue: key + } + this.userFetching = true + + getUserPage(params).then((res) => { + this.data = res.data.rows.map(user => ({ + text: `${user.name} ${user.account}`, + value: user.id + })) + }).finally(() => { + this.fetching = false + }) + }, + handleChange(value) { + Object.assign(this, { + selectValue: value, + fetching: false + }) + this.$emit('change', value) + } + } +} +</script> diff --git a/_web/src/components/UserSelect/index.js b/_web/src/components/UserSelect/index.js new file mode 100644 index 0000000..813c055 --- /dev/null +++ b/_web/src/components/UserSelect/index.js @@ -0,0 +1,3 @@ +import UserSelect from './UserSelect' + +export default UserSelect diff --git a/_web/src/components/_util/util.js b/_web/src/components/_util/util.js new file mode 100644 index 0000000..dd33231 --- /dev/null +++ b/_web/src/components/_util/util.js @@ -0,0 +1,46 @@ +/** + * components util + */ + +/** + * 清理空值,对象 + * @param children + * @returns {*[]} + */ +export function filterEmpty (children = []) { + return children.filter(c => c.tag || (c.text && c.text.trim() !== '')) +} + +/** + * 获取字符串长度,英文字符 长度1,中文字符长度2 + * @param {*} str + */ +export const getStrFullLength = (str = '') => + str.split('').reduce((pre, cur) => { + const charCode = cur.charCodeAt(0) + if (charCode >= 0 && charCode <= 128) { + return pre + 1 + } + return pre + 2 + }, 0) + +/** + * 截取字符串,根据 maxLength 截取后返回 + * @param {*} str + * @param {*} maxLength + */ +export const cutStrByFullLength = (str = '', maxLength) => { + let showLength = 0 + return str.split('').reduce((pre, cur) => { + const charCode = cur.charCodeAt(0) + if (charCode >= 0 && charCode <= 128) { + showLength += 1 + } else { + showLength += 2 + } + if (showLength <= maxLength) { + return pre + cur + } + return pre + }, '') +} diff --git a/_web/src/components/global.less b/_web/src/components/global.less new file mode 100644 index 0000000..74c17c3 --- /dev/null +++ b/_web/src/components/global.less @@ -0,0 +1,516 @@ +@import './index.less'; + +body { + + +} + +#app { + height: 100%; + + &.colorWeak { + filter: invert(80%); + } + &.userLayout { + overflow: auto; + } +} + +.layout.ant-layout { + height: auto; + overflow-x: hidden; + + &.mobile, + &.tablet { + .ant-layout-content { + .content { + margin: 24px 0 0; + } + } + + /** + * ant-table-wrapper + * 覆盖的表格手机模式样式,如果想修改在手机上表格最低宽度,可以在这里改动 + */ + .ant-table-wrapper { + .ant-table-content { + overflow-y: auto; + } + .ant-table-body { + min-width: 800px; + } + } + .topmenu { + /* 必须为 topmenu 才能启用流式布局 */ + &.content-width-Fluid { + .header-index-wide { + margin-left: 0; + } + } + } + } + + &.mobile { + .sidemenu { + .ant-header-fixedHeader { + &.ant-header-side-opened, + &.ant-header-side-closed { + width: 100%; + } + } + } + } + + &.ant-layout-has-sider { + flex-direction: row; + } + + .trigger { + font-size: 20px; + line-height: 55px; + padding: 0 24px; + cursor: pointer; + transition: color 0.3s; + &:hover { + background: rgba(0, 0, 0, 0.025); + } + } + + .topmenu { + .ant-header-fixedHeader { + position: fixed; + top: 0; + right: 0; + z-index: 9; + width: 100%; + transition: width 0.2s; + + &.ant-header-side-opened { + width: 100%; + } + + &.ant-header-side-closed { + width: 100%; + } + } + /* 必须为 topmenu 才能启用流式布局 */ + &.content-width-Fluid { + .header-index-wide { + max-width: unset; + .header-index-left { + flex: 1 1 1000px; + .logo{ + margin-left: 25px; + } + .ant-menu.ant-menu-horizontal{ + max-width: calc(100vw - 190px - 238px - 25px); + flex: 1 1 calc(100vw - 190px - 238px - 25px); + } + } + .header-index-right{ + margin-right:25px; + } + } + + .page-header-index-wide { + max-width: unset; + } + } + } + + .sidemenu { + .ant-header-fixedHeader { + position: fixed; + top: 0; + right: 0; + z-index: 9; + width: 100%; + transition: width 0.2s; + + &.ant-header-side-opened { + width: calc(100% - 230px); + } + + &.ant-header-side-closed { + width: calc(100% - 80px); + } + } + } + + .header { + height: 55px; + // padding: 0 12px 0 0; + background: #fff; + box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); + position: relative; + } + + .header, + .top-nav-header-index { + .user-wrapper { + float: right; + height: 100%; + + .action { + line-height: 55px; + cursor: pointer; + padding: 0 12px; + display: inline-block; + transition: all 0.3s; + height: 100%; + color: rgba(0, 0, 0, 0.65); + + &:hover { + background: rgba(0, 0, 0, 0.025); + } + + .avatar { + margin: 15px 8px 15px 0; + color: #1890ff; + background: hsla(0, 0%, 100%, 0.85); + vertical-align: middle; + } + + .icon { + font-size: 16px; + padding: 4px; + } + } + } + + &.dark { + .user-wrapper { + .action { + color: rgba(255, 255, 255, 0.85); + a { + color: rgba(255, 255, 255, 0.85); + } + + &:hover { + background: rgba(255, 255, 255, 0.16); + } + } + } + } + } + + &.mobile, + &.tablet { + .top-nav-header-index { + .header-index-wide { + .header-index-left { + .trigger { + color: rgba(255, 255, 255, 0.85); + padding: 0 12px; + } + + .logo.top-nav-header { + flex: 0 0 56px; + text-align: center; + line-height: 58px; + h1 { + display: none; + } + } + } + } + + &.light { + .header-index-wide { + .header-index-left { + .trigger { + color: rgba(0, 0, 0, 0.65); + } + } + } + } + } + } + + &.tablet { + // overflow: hidden; text-overflow:ellipsis; white-space: nowrap; + .top-nav-header-index { + .header-index-wide { + .header-index-left { + .logo > a { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + .ant-menu.ant-menu-horizontal { + flex: 1 1 auto; + white-space: normal; + } + } + } + } + + .top-nav-header-index { + box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08); + position: relative; + transition: background 0.3s, width 0.2s; + + .header-index-wide { + max-width: 1200px; + margin: auto; + padding-left: 0; + display: flex; + height: 55px; + + .ant-menu.ant-menu-horizontal { + max-width: 835px; + flex: 0 1 835px; + border: none; + height: 55px; + line-height: 55px; + } + + .header-index-left { + flex: 0 1 1000px; + display: flex; + + .logo.top-nav-header { + flex: 0 0 165px; + width: 165px; + height: 55px; + position: relative; + line-height: 55px; + transition: all 0.3s; + overflow: hidden; + + img, + svg { + display: inline-block; + vertical-align: middle; + height: 32px; + width: 32px; + } + + h1 { + color: #fff; + display: inline-block; + vertical-align: top; + font-size: 16px; + margin: 0 0 0 12px; + font-weight: 400; + } + } + } + + .header-index-right { + flex: 0 0 238px; + align-self: flex-end; + height: 55px; + overflow: hidden; + + .content-box { + float: right; + .action { + max-width: 140px; + overflow: hidden; + text-overflow:ellipsis; + white-space:nowrap; + } + } + } + } + + &.light { + background-color: #fff; + + .header-index-wide { + .header-index-left { + .logo { + h1 { + color: #002140; + } + } + } + } + } + } + + // 内容区 + .layout-content { + margin: 24px 24px 0px; + //height: 100%; + //height: 64px; + padding: 0 12px 0 0; + } + + // footer + .ant-layout-footer { + padding: 0; + } +} + +.topmenu { + .page-header-index-wide { + max-width: 1200px; + margin: 0 auto; + } +} + +// drawer-sider 自定义 +.ant-drawer.drawer-sider { + .sider { + box-shadow: none; + } + + &.dark { + .ant-drawer-content { + background-color: rgb(0, 21, 41); + } + } + &.light { + box-shadow: none; + .ant-drawer-content { + background-color: #fff; + } + } + + .ant-drawer-body { + padding: 0; + } +} + +// 菜单样式 +.sider { + box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); + position: relative; + z-index: @ant-global-sider-zindex; + min-height: 100vh; + + .ant-layout-sider-children { + overflow-y: hidden; + + &:hover { + overflow-y: auto; + } + } + + &.ant-fixed-sidemenu { + position: fixed; + height: 100%; + } + + // logo区域样式 + .logo { + position: relative; + height: 55px; + padding-left: 24px; + overflow: hidden; + line-height: 55px; + background: #002140; + transition: all .3s; + + img, + svg, + h1 { + display: inline-block; + vertical-align: middle; + } + + img, + svg { + height: 32px; + width: 32px; + } + + h1 { + color: #fff; + font-size: 20px; + margin: 0 0 0 12px; + font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif; + font-weight: 600; + vertical-align: middle; + } + } + + &.light { + background-color: #fff; + box-shadow: 2px 0px 8px 0px rgba(29, 35, 41, 0.05); + + .logo { + background: #fff; + box-shadow: 1px 1px 0px 0px #e8e8e8; + + h1 { + color: unset; + } + } + + .ant-menu-light { + border-right-color: transparent; + } + } +} + +// 外置的样式控制 +.user-dropdown-menu { + span { + user-select: none; + } +} +.user-dropdown-menu-wrapper.ant-dropdown-menu { + padding: 4px 0; + + .ant-dropdown-menu-item { + width: 160px; + } + + .ant-dropdown-menu-item > .anticon:first-child, + .ant-dropdown-menu-item > a > .anticon:first-child, + .ant-dropdown-menu-submenu-title > .anticon:first-child .ant-dropdown-menu-submenu-title > a > .anticon:first-child { + min-width: 12px; + margin-right: 8px; + } +} + +// 数据列表 样式 +.table-alert { + margin-bottom: 16px; +} + +.table-page-search-wrapper { + .ant-form-inline { + .ant-form-item { + display: flex; + margin-bottom: 24px; + margin-right: 0; + + .ant-form-item-control-wrapper { + flex: 1 1; + display: inline-block; + vertical-align: middle; + } + + > .ant-form-item-label { + line-height: 32px; + padding-right: 8px; + width: auto; + } + .ant-form-item-control { + height: 32px; + line-height: 32px; + } + } + } + + .table-page-search-submitButtons { + display: block; + margin-bottom: 24px; + white-space: nowrap; + } +} + +.content { + .table-operator { + margin-bottom: 18px; + + button { + margin-right: 8px; + } + } +} diff --git a/_web/src/components/index.js b/_web/src/components/index.js new file mode 100644 index 0000000..8dd5b74 --- /dev/null +++ b/_web/src/components/index.js @@ -0,0 +1,72 @@ +// chart +import Bar from '@/components/Charts/Bar' +import ChartCard from '@/components/Charts/ChartCard' +import Liquid from '@/components/Charts/Liquid' +import MiniArea from '@/components/Charts/MiniArea' +import MiniSmoothArea from '@/components/Charts/MiniSmoothArea' +import MiniBar from '@/components/Charts/MiniBar' +import MiniProgress from '@/components/Charts/MiniProgress' +import Radar from '@/components/Charts/Radar' +import RankList from '@/components/Charts/RankList' +import TransferBar from '@/components/Charts/TransferBar' +import TagCloud from '@/components/Charts/TagCloud' + +// pro components +import AvatarList from '@/components/AvatarList' +import CountDown from '@/components/CountDown' +import Ellipsis from '@/components/Ellipsis' +import FooterToolbar from '@/components/FooterToolbar' +import NumberInfo from '@/components/NumberInfo' +import DescriptionList from '@/components/DescriptionList' +import Tree from '@/components/Tree/Tree' +import Trend from '@/components/Trend' +import STable from '@/components/Table' +import MultiTab from '@/components/MultiTab' +import Result from '@/components/Result' +import IconSelector from '@/components/IconSelector' +import TagSelect from '@/components/TagSelect' +import ExceptionPage from '@/components/Exception' +import StandardFormRow from '@/components/StandardFormRow' +import ArticleListContent from '@/components/ArticleListContent' +import AntdEditor from '@/components/Editor/WangEditor' +import Dialog from '@/components/Dialog' + +// xn components +import XCard from '@/components/xnComponents/XCard' +import XDown from '@/components/xnComponents/XDown' + +export { + AvatarList, + Bar, + ChartCard, + Liquid, + MiniArea, + MiniSmoothArea, + MiniBar, + MiniProgress, + Radar, + TagCloud, + RankList, + TransferBar, + Trend, + CountDown, + Ellipsis, + FooterToolbar, + NumberInfo, + DescriptionList, + // 兼容写法,请勿继续使用 + DescriptionList as DetailList, + Tree, + STable, + MultiTab, + Result, + ExceptionPage, + IconSelector, + TagSelect, + StandardFormRow, + ArticleListContent, + AntdEditor, + Dialog, + XCard, + XDown +} diff --git a/_web/src/components/index.less b/_web/src/components/index.less new file mode 100644 index 0000000..e831c41 --- /dev/null +++ b/_web/src/components/index.less @@ -0,0 +1,6 @@ +@import "~ant-design-vue/lib/style/index"; + +// The prefix to use on all css classes from ant-pro. +@ant-pro-prefix : ant-pro; +@ant-global-sider-zindex : 106; +@ant-global-header-zindex : 105; \ No newline at end of file diff --git a/_web/src/components/tools/Breadcrumb.vue b/_web/src/components/tools/Breadcrumb.vue new file mode 100644 index 0000000..9bc141c --- /dev/null +++ b/_web/src/components/tools/Breadcrumb.vue @@ -0,0 +1,45 @@ +<template> + <a-breadcrumb class="breadcrumb"> + <a-breadcrumb-item v-for="(item, index) in breadList" :key="item.name"> + <router-link + v-if="item.name != name && index != 1" + :to="{ path: item.path === '' ? '/' : item.path }" + >{{ item.meta.title }}</router-link> + <span v-else>{{ item.meta.title }}</span> + </a-breadcrumb-item> + </a-breadcrumb> +</template> + +<script> +export default { + data () { + return { + name: '', + breadList: [] + } + }, + created () { + this.getBreadcrumb() + }, + methods: { + getBreadcrumb () { + this.breadList = [] + // this.breadList.push({name: 'index', path: '/dashboard/', meta: {title: '首页'}}) + + this.name = this.$route.name + this.$route.matched.forEach(item => { + // item.name !== 'index' && this.breadList.push(item) + this.breadList.push(item) + }) + } + }, + watch: { + $route () { + this.getBreadcrumb() + } + } +} +</script> + +<style scoped> +</style> diff --git a/_web/src/components/tools/DetailList.vue b/_web/src/components/tools/DetailList.vue new file mode 100644 index 0000000..6745a08 --- /dev/null +++ b/_web/src/components/tools/DetailList.vue @@ -0,0 +1,5 @@ +<script> +/* WARNING: 兼容老引入,请勿继续使用 */ +import DescriptionList from '@/components/DescriptionList' +export default DescriptionList +</script> diff --git a/_web/src/components/tools/HeadInfo.vue b/_web/src/components/tools/HeadInfo.vue new file mode 100644 index 0000000..7fbc692 --- /dev/null +++ b/_web/src/components/tools/HeadInfo.vue @@ -0,0 +1,67 @@ +<template> + <div class="head-info" :class="center && 'center'"> + <span>{{ title }}</span> + <p>{{ content }}</p> + <em v-if="bordered"/> + </div> +</template> + +<script> +export default { + name: 'HeadInfo', + props: { + title: { + type: String, + default: '' + }, + content: { + type: String, + default: '' + }, + bordered: { + type: Boolean, + default: false + }, + center: { + type: Boolean, + default: true + } + } +} +</script> + +<style lang="less" scoped> + .head-info { + position: relative; + text-align: left; + padding: 0 32px 0 0; + min-width: 125px; + + &.center { + text-align: center; + padding: 0 32px; + } + + span { + color: rgba(0, 0, 0, .45); + display: inline-block; + font-size: 14px; + line-height: 22px; + margin-bottom: 4px; + } + p { + color: rgba(0, 0, 0, .85); + font-size: 24px; + line-height: 32px; + margin: 0; + } + em { + background-color: #e8e8e8; + position: absolute; + height: 56px; + width: 1px; + top: 0; + right: 0; + } + } +</style> diff --git a/_web/src/components/tools/LangSelect.vue b/_web/src/components/tools/LangSelect.vue new file mode 100644 index 0000000..283f356 --- /dev/null +++ b/_web/src/components/tools/LangSelect.vue @@ -0,0 +1,46 @@ +<template> + <a-dropdown> + <span class="action global-lang"> + <a-icon type="global" style="font-size: 16px"/> + </span> + <a-menu slot="overlay" style="width: 150px;" @click="SwitchLang"> + <a-menu-item key="zh-CN"> + <a rel="noopener noreferrer"> + <span role="img" aria-label="简体中文">🇨🇳</span> 简体中文 + </a> + </a-menu-item> + <a-menu-item key="zh-TW"> + <a rel="noopener noreferrer"> + <span role="img" aria-label="繁体中文">🇭🇰</span> 繁体中文 + </a> + </a-menu-item> + <a-menu-item key="en-US"> + <a rel="noopener noreferrer"> + <span role="img" aria-label="English">🇬🇧</span> English + </a> + </a-menu-item> + <a-menu-item key="pt-BR"> + <a rel="noopener noreferrer"> + <span role="img" aria-label="Português">🇧🇷</span> Português + </a> + </a-menu-item> + </a-menu> + </a-dropdown> +</template> + +<script> +// import { mixin as langMixin } from '@/store/i18n-mixin' + +export default { + name: 'LangSelect', + // mixins: [langMixin], + data () { + return {} + }, + methods: { + // SwitchLang (row) { + // this.setLang(row.key) + // } + } +} +</script> diff --git a/_web/src/components/tools/Logo.vue b/_web/src/components/tools/Logo.vue new file mode 100644 index 0000000..fc32ec8 --- /dev/null +++ b/_web/src/components/tools/Logo.vue @@ -0,0 +1,53 @@ +<template> + <div class="logo"> + <router-link :to="{name:'Console'}"> + <LogoSvg alt="logo" /> + <h1 v-if="showTitle">{{ this.titles }}</h1> + </router-link> + </div> +</template> + +<script> +import LogoSvg from '@/assets/logo.svg?inline' +import { mixin, mixinDevice } from '@/utils/mixin' + +export default { + name: 'Logo', + components: { + LogoSvg + }, + mixins: [mixin, mixinDevice], + data () { + return { + titles: '' + } + }, + props: { + title: { + type: String, + default: 'Snowy', + required: false + }, + showTitle: { + type: Boolean, + default: true, + required: false + } + }, + created () { + if (this.layoutMode === 'topmenu') { + if (this.title.length > 8) { + this.titles = this.title.substring(0, 7) + '...' + } else { + this.titles = this.title + } + } else { + if (this.title.length > 10) { + this.titles = this.title.substring(0, 9) + '...' + } else { + this.titles = this.title + } + } + } +} +</script> diff --git a/_web/src/components/tools/TwoStepCaptcha.vue b/_web/src/components/tools/TwoStepCaptcha.vue new file mode 100644 index 0000000..01302b4 --- /dev/null +++ b/_web/src/components/tools/TwoStepCaptcha.vue @@ -0,0 +1,89 @@ +<template> + <!-- 两步验证 --> + <a-modal + centered + v-model="visible" + @cancel="handleCancel" + :maskClosable="false" + > + <div slot="title" :style="{ textAlign: 'center' }">两步验证</div> + <template slot="footer"> + <div :style="{ textAlign: 'center' }"> + <a-button key="back" @click="handleCancel">返回</a-button> + <a-button key="submit" type="primary" :loading="stepLoading" @click="handleStepOk"> + 继续 + </a-button> + </div> + </template> + + <a-spin :spinning="stepLoading"> + <a-form layout="vertical" :auto-form-create="(form)=>{this.form = form}"> + <div class="step-form-wrapper"> + <p style="text-align: center" v-if="!stepLoading">请在手机中打开 Google Authenticator 或两步验证 APP<br />输入 6 位动态码</p> + <p style="text-align: center" v-else>正在验证..<br/>请稍后</p> + <a-form-item + :style="{ textAlign: 'center' }" + hasFeedback + fieldDecoratorId="stepCode" + :fieldDecoratorOptions="{rules: [{ required: true, message: '请输入 6 位动态码!', pattern: /^\d{6}$/, len: 6 }]}" + > + <a-input :style="{ textAlign: 'center' }" @keyup.enter.native="handleStepOk" placeholder="000000" /> + </a-form-item> + <p style="text-align: center"> + <a @click="onForgeStepCode">遗失手机?</a> + </p> + </div> + </a-form> + </a-spin> + </a-modal> +</template> + +<script> +export default { + props: { + visible: { + type: Boolean, + default: false + } + }, + data () { + return { + stepLoading: false, + + form: null + } + }, + methods: { + handleStepOk () { + const vm = this + this.stepLoading = true + this.form.validateFields((err, values) => { + if (!err) { + console.log('values', values) + setTimeout(() => { + vm.stepLoading = false + vm.$emit('success', { values }) + }, 2000) + return + } + this.stepLoading = false + this.$emit('error', { err }) + }) + }, + handleCancel () { + this.visible = false + this.$emit('cancel') + }, + onForgeStepCode () { + + } + } +} +</script> +<style lang="less" scoped> + .step-form-wrapper { + margin: 0 auto; + width: 80%; + max-width: 400px; + } +</style> diff --git a/_web/src/components/tools/UserMenu.vue b/_web/src/components/tools/UserMenu.vue new file mode 100644 index 0000000..657f746 --- /dev/null +++ b/_web/src/components/tools/UserMenu.vue @@ -0,0 +1,191 @@ +<template> + <div class="user-wrapper"> + <div class="content-box"> + <span class="action" @click="toggleFullscreen"> + <a-icon type="fullscreen-exit" v-if="isFullscreen"/> + <a-icon type="fullscreen" v-else/> + </span> + <notice-icon class="action"/> + <a-dropdown> + <span class="action ant-dropdown-link user-dropdown-menu"> + <a-avatar class="avatar" size="small" :src="avatar"/> + <span>{{ nickname }}</span> + </span> + <a-menu slot="overlay" class="user-dropdown-menu-wrapper"> + <a-menu-item key="4" v-if="mode === 'sidemenu'"> + <a @click="appToggled()" > + <a-icon type="swap"/> + <span>切换应用</span> + </a> + </a-menu-item> + <a-menu-item key="0"> + <router-link :to="{ name: 'center' }"> + <a-icon type="user"/> + <span>个人中心</span> + </router-link> + </a-menu-item> + <a-menu-item key="1"> + <router-link :to="{ name: 'settings' }"> + <a-icon type="setting"/> + <span>账户设置</span> + </router-link> + </a-menu-item> + <a-menu-divider/> + <a-menu-item key="3"> + <a href="javascript:;" @click="handleLogout"> + <a-icon type="logout"/> + <span>退出登录</span> + </a> + </a-menu-item> + </a-menu> + </a-dropdown> + </div> + <a-modal + title="切换应用" + :visible="visible" + :footer="null" + :confirm-loading="confirmLoading" + @cancel="handleCancel" + > + <a-form :form="form1" > + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="选择应用" + > + <a-menu + mode="inline" + :default-selected-keys="this.defApp" + style="border-bottom:0px;lineHeight:55px;" + > + <a-menu-item v-for="(item) in userInfo.apps" :key="item.code" style="top:0px;" @click="switchApp(item.code)"> + {{ item.name }} + </a-menu-item> + </a-menu> + </a-form-item> + </a-form> + </a-modal> + </div> +</template> + +<script> +import screenfull from 'screenfull' +import NoticeIcon from '@/components/NoticeIcon' +import { mapActions, mapGetters } from 'vuex' +import { ALL_APPS_MENU } from '@/store/mutation-types' +import Vue from 'vue' +import { message } from 'ant-design-vue/es' + +export default { + name: 'UserMenu', + components: { + NoticeIcon, + screenfull + }, + props: { + mode: { + type: String, + // sidemenu, topmenu + default: 'sidemenu' + } + }, + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 16 } + }, + visible: false, + confirmLoading: false, + form1: this.$form.createForm(this), + defApp: [], + isFullscreen: false + } + }, + + computed: { + ...mapGetters(['nickname', 'avatar', 'userInfo']) + }, + methods: { + ...mapActions(['Logout', 'MenuChange']), + + handleLogout () { + this.$confirm({ + title: '提示', + content: '真的要注销登录吗 ?', + okText: '确定', + cancelText: '取消', + onOk: () => { + return this.Logout({}).then(() => { + setTimeout(() => { + window.location.reload() + }, 16) + }).catch(err => { + this.$message.error({ + title: '错误', + description: err.message + }) + }) + }, + onCancel () { + } + }) + }, + + /** + * 打开切换应用框 + */ + appToggled () { + this.visible = true + this.defApp.push(Vue.ls.get(ALL_APPS_MENU)[0].code) + }, + + switchApp (appCode) { + this.visible = false + this.defApp = [] + const applicationData = this.userInfo.apps.filter(item => item.code === appCode) + const hideMessage = message.loading('正在切换应用!', 0) + this.MenuChange(applicationData[0]).then((res) => { + hideMessage() + // eslint-disable-next-line handle-callback-err + }).catch((err) => { + message.error('应用切换异常') + }) + }, + handleCancel () { + this.form1.resetFields() + this.visible = false + }, + /* 全屏切换 */ + toggleFullscreen () { + if (!screenfull.isEnabled) { + message.error('您的浏览器不支持全屏模式') + return + } + screenfull.toggle() + if (screenfull.isFullscreen) { + this.isFullscreen = false + } else { + this.isFullscreen = true + } + } + } +} +</script> + +<style lang="less" scoped> + .appRedio { + border:1px solid #91d5ff; + padding:10px 20px; + background: #e6f7ff; + border-radius:2px; + margin-bottom:10px; + color: #91d5ff; + /*display: inline; + margin-bottom:10px;*/ + } +</style> diff --git a/_web/src/components/tools/index.js b/_web/src/components/tools/index.js new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/_web/src/components/tools/index.js diff --git a/_web/src/components/verifition/Verify.vue b/_web/src/components/verifition/Verify.vue new file mode 100644 index 0000000..e7b6d1a --- /dev/null +++ b/_web/src/components/verifition/Verify.vue @@ -0,0 +1,473 @@ +<template> + <div :class="mode=='pop'?'mask':''" v-show="showBox"> + <div :class="mode=='pop'?'verifybox':''" :style="{'max-width':parseInt(imgSize.width)+30+'px'}"> + <div class="verifybox-top" v-if="mode=='pop'"> + 请完成安全验证 + <span @click="closeBox" class="verifybox-close"> + <i class="iconfont icon-close"></i> + </span> + </div> + <div :style="{padding:mode=='pop'?'15px':'0'}" class="verifybox-bottom"> + <!-- 验证码容器 --> + <components + :arith="arith" + :barSize="barSize" + :blockSize="blockSize" + :captchaType="captchaType" + :explain="explain" + :figure="figure" + :imgSize="imgSize" + :is="componentType" + :mode="mode" + :type="verifyType" + :vSpace="vSpace" + ref="instance" + v-if="componentType"></components> + </div> + </div> + </div> +</template> +<script type="text/babel"> + /** + * Verify 验证码组件 + * @description 分发验证码使用 + * */ + import VerifySlide from './Verify/VerifySlide' + import VerifyPoints from './Verify/VerifyPoints' + + export default { + name: 'Vue2Verify', + props: { + // 双语化 + locale: { + require: false, + type: String, + default() { + // 默认语言不输入为浏览器语言 + if (navigator.language) { + var language = navigator.language + } else { + // eslint-disable-next-line no-redeclare + var language = navigator.browserLanguage + } + return language + } + }, + captchaType: { + type: String, + required: true + }, + // eslint-disable-next-line vue/require-default-prop + figure: { + type: Number + }, + // eslint-disable-next-line vue/require-default-prop + arith: { + type: Number + }, + mode: { + type: String, + default: 'pop' + }, + // eslint-disable-next-line vue/require-default-prop + vSpace: { + type: Number + }, + // eslint-disable-next-line vue/require-default-prop + explain: { + type: String + }, + imgSize: { + type: Object, + default() { + return { + width: '310px', + height: '155px' + } + } + }, + // eslint-disable-next-line vue/require-default-prop + blockSize: { + type: Object + }, + // eslint-disable-next-line vue/require-default-prop + barSize: { + type: Object + } + }, + data() { + return { + // showBox:true, + clickShow: false, + // 内部类型 + verifyType: undefined, + // 所用组件类型 + componentType: undefined + } + }, + methods: { + /** + * i18n + * @description 兼容vue-i18n 调用$t来转换ok + * @param {String} text-被转换的目标 + * @return {String} i18n的结果 + * */ + i18n(text) { + if (this.$t) { + return this.$t(text) + } else { + // 兼容不存在的语言 + const i18n = this.$options.i18n.messages[this.locale] || this.$options.i18n.messages['en-US'] + return i18n[text] + } + }, + /** + * refresh + * @description 刷新 + * */ + refresh() { + if (this.instance.refresh) { + this.instance.refresh() + } + }, + closeBox() { + this.clickShow = false + this.refresh() + }, + show() { + // eslint-disable-next-line eqeqeq + if (this.mode == 'pop') { + this.clickShow = true + } + } + }, + computed: { + instance() { + return this.$refs.instance || {} + }, + showBox() { + // eslint-disable-next-line eqeqeq + if (this.mode == 'pop') { + return this.clickShow + } else { + return true + } + } + }, + watch: { + captchaType: { + immediate: true, + handler(captchaType) { + switch (captchaType.toString()) { + case 'blockPuzzle': + this.verifyType = '2' + this.componentType = 'VerifySlide' + break + case 'clickWord': + this.verifyType = '' + this.componentType = 'VerifyPoints' + break + } + } + } + }, + components: { + VerifySlide, + VerifyPoints + } + } +</script> +<style> + .verifybox{ + position: relative; + box-sizing: border-box; + border-radius: 2px; + border: 1px solid #e4e7eb; + background-color: #fff; + box-shadow: 0 0 10px rgba(0,0,0,.3); + left: 50%; + top:50%; + transform: translate(-50%,-50%); + } + .verifybox-top{ + padding: 0 15px; + height: 50px; + line-height: 50px; + text-align: left; + font-size: 16px; + color: #45494c; + border-bottom: 1px solid #e4e7eb; + box-sizing: border-box; + } + .verifybox-bottom{ + padding: 15px; + box-sizing: border-box; + } + .verifybox-close{ + position: absolute; + top: 13px; + right: 9px; + width: 24px; + height: 24px; + text-align: center; + cursor: pointer; + } + .mask{ + position: fixed; + top: 0; + left:0; + z-index: 1001; + width: 100%; + height: 100vh; + background: rgba(0,0,0,.3); + /* display: none; */ + transition: all .5s; + } + .verify-tips{ + position: absolute; + left: 0px; + bottom:0px; + width: 100%; + height: 30px; + line-height:30px; + color: #fff; + } + .suc-bg{ + background-color:rgba(92, 184, 92,.5); + filter: progid:DXImageTransform.Microsoft.gradient(startcolorstr=#7f5CB85C, endcolorstr=#7f5CB85C); + } + .err-bg{ + background-color:rgba(217, 83, 79,.5); + filter: progid:DXImageTransform.Microsoft.gradient(startcolorstr=#7fD9534F, endcolorstr=#7fD9534F); + } + .tips-enter,.tips-leave-to{ + bottom: -30px; + } + .tips-enter-active,.tips-leave-active{ + transition: bottom .5s; + } + /* ---------------------------- */ + /*常规验证码*/ + .verify-code { + font-size: 20px; + text-align: center; + cursor: pointer; + margin-bottom: 5px; + border: 1px solid #ddd; + } + + .cerify-code-panel { + height: 100%; + overflow: hidden; + } + + .verify-code-area { + float: left; + } + + .verify-input-area { + float: left; + width: 60%; + padding-right: 10px; + + } + + .verify-change-area { + line-height: 30px; + float: left; + } + + .varify-input-code { + display: inline-block; + width: 100%; + height: 25px; + } + + .verify-change-code { + color: #337AB7; + cursor: pointer; + } + + .verify-btn { + width: 200px; + height: 30px; + background-color: #337AB7; + color: #FFFFFF; + border: none; + margin-top: 10px; + } + + /*滑动验证码*/ + .verify-bar-area { + position: relative; + background: #FFFFFF; + text-align: center; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + border: 1px solid #ddd; + -webkit-border-radius: 4px; + } + + .verify-bar-area .verify-move-block { + position: absolute; + top: 0px; + left: 0; + background: #fff; + cursor: pointer; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + box-shadow: 0 0 2px #888888; + -webkit-border-radius: 1px; + } + + .verify-bar-area .verify-move-block:hover { + background-color: #337ab7; + color: #FFFFFF; + } + + .verify-bar-area .verify-left-bar { + position: absolute; + top: -1px; + left: -1px; + background: #f0fff0; + cursor: pointer; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + border: 1px solid #ddd; + } + + .verify-img-panel { + margin: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + border-radius: 3px; + position: relative; + } + + .verify-img-panel .verify-refresh { + width: 25px; + height: 25px; + text-align: center; + padding: 5px; + cursor: pointer; + position: absolute; + top: 0; + right: 0; + z-index: 2; + } + + .verify-img-panel .icon-refresh { + font-size: 20px; + color: #fff; + } + + .verify-img-panel .verify-gap { + background-color: #fff; + position: relative; + z-index: 2; + border: 1px solid #fff; + } + + .verify-bar-area .verify-move-block .verify-sub-block { + position: absolute; + text-align: center; + z-index: 3; + /* border: 1px solid #fff; */ + } + + .verify-bar-area .verify-move-block .verify-icon { + font-size: 18px; + } + + .verify-bar-area .verify-msg { + z-index: 3; + } + + /*字体图标的css*/ + /*@font-face {font-family: "iconfont";*/ + /*src: url('../fonts/iconfont.eot?t=1508229193188'); !* IE9*!*/ + /*src: url('../fonts/iconfont.eot?t=1508229193188#iefix') format('embedded-opentype'), !* IE6-IE8 *!*/ + /*url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAaAAAsAAAAACUwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kiSY21hcAAAAYAAAAB3AAABuM+qBlRnbHlmAAAB+AAAAnQAAALYnrUwT2hlYWQAAARsAAAALwAAADYPNwajaGhlYQAABJwAAAAcAAAAJAfeA4dobXR4AAAEuAAAABMAAAAYF+kAAGxvY2EAAATMAAAADgAAAA4CvAGsbWF4cAAABNwAAAAfAAAAIAEVAF1uYW1lAAAE/AAAAUUAAAJtPlT+fXBvc3QAAAZEAAAAPAAAAE3oPPXPeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sM4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDxbwtzwv4EhhrmBoQEozAiSAwAw1A0UeJzFkcENgCAMRX8RjCGO4gTe9eQcnhzAfXC2rqG/hYsT8MmD9gdS0gJIAAaykAjIBYHppCvuD8juR6zMJ67A89Zdn/f1aNPikUn8RvYo8G20CjKim6Rf6b9m34+WWd/vBr+oW8V6q3vF5qKlYrPRp4L0Ad5nGL8AeJxFUc9rE0EYnTezu8lMsrvtbrqb3TRt0rS7bdOmdI0JbWmCtiItIv5oi14qevCk9SQVLFiQgqAF8Q9QLKIHLx48FkHo3ZNnFUXwD5C2B6dO6sFhmI83w7z3fe8RnZCjb2yX5YlLhskkmScXCIFRxYBFiyjH9Rqtoqes9/g5i8WVuJyqDNTYLPwBI+cljXrkGynDhoU+nCgnjbhGY5yst+gMEq8IBIXwsjPU67CnEPm4b0su0h309Fd67da4XBhr55KSm17POk7gOE/Shq6nKdVsC7d9j+tcGPKVboc9u/0jtB/ZIA7PXTVLBef6o/paccjnwOYm3ELJetPuDrvV3gg91wlSXWY6H5qVwRzWf2TybrYYfSdqoXOwh/Qa8RWIjBTiSI3h614/vKSNRhONOrsnQi6Xf4nQFQDTmJE1NKbhI6crHEJO/+S5QPxhYJRRyvBFBP+5T9EPpEAIVzzRQIrjmJ6jY1WTo+NXTMchuBsKuS8PRZATSMl9oTA4uNLkeIA0V1UeqOoGQh7IAxGo+7T83fn3T+voqCNPPAUazUYUI7LgKSV1Jk2oUeghYGhZ+cKOe2FjVu5ZKEY2VkE13AK1+jI4r1KLbPlZfrKiPhOXKPRj7q9sj9XJ7LFHNmrKJS3VCdhXGSdKrtmoQaWeMjQVt0KD6sGPOx0oH2fgtzoNROxtNq8F3tzYM/n+TjKSX5qf2jx941276TIr9FjXxKr8eX/6bK4yuopwo9py1sw8F9kdw4AmurRpLUM3tYx5ZnKpfHPi8dzz19vJ6MjyxYUrpqeb1uLs3eGV6vr21pSqpeWkqonAN9oUyIiXpv8XvlN5e3icY2BkYGAA4n0vN4fG89t8ZeBmYQCBa9wPPRH0/wcsDMwmQC4HAxNIFABAfAqaAHicY2BkYGBu+N/AEMPCAAJAkpEBFbABAEcMAm94nGNhYGBgfsnAwMKAigESnwEBAAAAAAAAdgCkANoBCAFsAAB4nGNgZGBgYGMIZGBlAAEmIOYCQgaG/2A+AwARSAFzAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nGNgYoAALgbsgI2RiZGZkYWRlZGNkZ2BsYI1OSM1OZs1OSe/OJW1KDM9o4S9KDWtKLU4g4EBAJ79CeQ=') format('woff'),*/ + /*url('../fonts/iconfont.ttf?t=1508229193188') format('truetype'), !* chrome, firefox, opera, Safari, Android, iOS 4.2+*!*/ + /*url('../fonts/iconfont.svg?t=1508229193188#iconfont') format('svg'); !* iOS 4.1- *!*/ + /*}*/ + + .iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + .icon-check:before { + content: " "; + display: block; + width: 16px; + height: 16px; + position: absolute; + margin: auto; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 9999; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIEAYAAAD9yHLdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAIlFJREFUeNrt3X1cVNW6B/BnbcS3xJd7fLmSeo+op/Qmyp4BFcQEwpd8Nyc9iZppgUfE49u1tCwlNcMySCM1S81jCoaioiJvKoYgswfUo5wSJ69SZFKCKSAws+4f2/GetFFRYG3g9/2Hz2xj+O2J4Zm19trrIQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgjmOgAAADwOBhz83TzdPNs397qanW1ujJ2s8fNHjd7FBTkhuSG5IbculVdP1kSfeoAAPBwdFzHdXzgQN0S3RLdkpgY2SJbZMvNm9It6ZZ064cfGmQ2yGyQmZfX3KO5R3OPwkJdsi5Zl5yYKIfL4XL4mDHqs7AqGzhgBAIAoFFdI7pGdI1o1KjFlhZbWmxZv149OmXK4z3r4cPEiROfOFExKSbFVFDwqM+EEQgAgMY8y5/lz/LGjZu3bt66eev9+9Wjj1s4bAYNIkaMWHKyx3mP8x7nmzd/1GdyEP1CAQCASifrZJ3s6FjmWuZa5rprF3uLvcXeGjq0en5au3a8nJfz8k6d8lPyU/JTYmIq+wwYgQAAaIIk0WgaTaO/+IJm0SyaNWJEtf/IPMqjvJde0g/QD9APcHOrdGIhrxMAANzGmJwr58q569ZRLMVS7MSJNfajFVJIYYy/wF/gL7z0UmW/vUGNvk4AAHCHTqfT6XQrVtB4Gk/jg4KEBfmBfqAf+vSp7LdhBAIAUMPUwvH66+oj21eBSqmUStu3r+y3oYAAANQQtXDMmKE+WrlSdB4bvpwv58t/+62y34cCAgBQzeSt8lZ568SJFEiBFLh2reg8d2MD2UA28PTpyn4fCggAQDXRh+pD9aEjR1IABVDA5s20ntbTeklzf3eZF/NiXvv2Vfb7NHciAAC1nRwsB8vBvr5Wf6u/1X/nTubO3Jl7A+0tWvImb/LOyemc3zm/c/6ePZX9dmxlAgBQRfTd9N303Tw8rFusW6xbEhPZLDaLzXJyEp3rHjNoBs24dYt/wj/hn3h5mUwmk8mkKJV9GoxAAAAekz5AH6APeOYZ6znrOeu5Awc0WzgCKZACrVZ2hB1hR15++VELhw1GIAAAj0hdVdWli/ooNVX9WvnlsNUflHSk45wbuZEbg4LUwrFhw+M+LUYgAACV1CuoV1CvoCef5Kv4Kr4qIUE9qsHCcRsv4AW8YOHCqiocNtq7qAMAoFHqZoetW9MgGkSDDh+mhbSQFnbuLDrX/YWGmmJMMaaYsLCqfmZMYQEAPIBt23PLp5ZPLZ8mJ9MROkJHdDrRueyKpViKXbdO6aB0UDoEB1fXj8EUFgCAHX0v973c93KTJpbvLd9bvt+3T+uFg0/mk/nkL79UC0dISHX/PIxAAADuYuvLwQ/xQ/zQnj1sKBvKhj7/vOhc9vA4HsfjYmOd2jm1c2o3btxRdpQdZRUV1f1zMQIBALjNYDAYDAYHB9pEm2jTl19qvXBQGIVRWFKSWjgmTKipwmGDi+gAAERExJhZZ9aZdZGRNJ2m0/Tx40UnssuHfMgnPb2koKSgpGD0aIUpTGGlpTUdAwUEAOo9XbguXBf+/vu0lbbS1ldfFZ3HrgE0gAacPu0423G24+xhw5SOSkel440bouKggABAvaXjOq7j77xDetKTfv580Xns8iIv8srNlfKkPClv8OD0jukd0zv++qvoWLiIDgD1jrpnVXAwb86b8+Yffyw6jz18NV/NV+flWQZaBloGenufYqfYKXbxouhcNriIDgD1hi5Zl6xLnjyZL+AL+ILwcNF57OpLfanv1atsPpvP5vv7a61w2GAEAgB1nrpn1ejRPJNn8szoaM1ur05EREVF6ldfX0VRFEUxmUQnskejLyAAwOPT79fv1+9/7jn+E/+J/7Rjh7YLR3ExceLEhw9XTIpJMWm3cNho9IUEAHh08hB5iDykb1/+M/+Z/7x7N0VSJEU2aiQ61z30pCd9WZl1inWKdcoLL2R5ZnlmeR4/LjrWw8I1EACoM+S2clu5rasr+yv7K/vrgQO0jtbRumbNROe6G4/kkTzSYqFMyqTMgAC1cBw6JDpXZaGAAECt1zukd0jvkG7daBftol2HD1MERVBEq1aic93jdl8O9gv7hf0SGKhOVUVHi471qFBAAKDW0hfri/XFHTs6cAfuwBMS2Bw2h81p1050LruepWfp2fnzlaHKUGXopk2i4zwuFBAAqHVcw1zDXMPatrWSlayUkEBplEZp//VfonPZw86ys+zsm28qE5WJysQPPxSdp6qggABAraHuktuiRYOgBkENgg4dYt7Mm3k/9ZToXHZNpIk0MTzcWGosNZYuXy46TlXDfSAAoHnqfRxNm6qP4uPVr/37i85l11gaS2M3b1YWK4uVxa+8oh7kXHSsqoYRCABoVo+oHlE9oho2pME0mAbHxKhHNVw4IimSImNiXLJdsl2yp09XD9a9wmGDAgIAmmPry9G4f+P+jfv/4x8UT/EUP3iw6Fz3d/hwUXpRelH6Sy9FR0dHR0dbLKITVTfcSAgAGsPYhT4X+lzos2EDG8FGsBHjxolOZA9fxBfxRWlpFeYKc4V57NjckNyQ3JBbt0Tnqim4BgIAmiEvkhfJiz78kMWzeBY/Z47oPPbwpXwpX5qdbRlmGWYZ5uOjbnZYWCg6V03DFBYACKdbq1urW7tiheYLRypP5anffluRU5FTkTN4cH0tHDYYgQCAMOqeVX//O7vKrrKra9aIzmMPP86P8+NmM/fjftzP2zsrLSstK+3HH0XnEg0jEACocXJXuavcdepU1ol1Yp00fGNdP+pH/X78UUqSkqQkf38Ujt9DAQGAGqMP0YfoQ154gbbTdtq+cSMppJDCtDcTwokTLyiwvGh50fKiv79xuHG4cbjZLDqW1mjvfxwA1DluZjezm3nECMkgGSTD11+rRx0dRee6G8/gGTzj+nU+gA/gA/z81BGH0Sg6l1ZhBAIA1Ua9g9zHh/3MfmY/R0WpRzVYOE7yk/xkSYmUI+VIOSNHonA8HIxAAKDK6bvpu+m7eXhYt1i3WLckJrJZbBab5eQkOtcfKy9Xv44Zo7aQjYsTnai2cBAdAADqDn2APkAf8Mwz1gRrgjUhIYG9wF5gL7RsKTrXPQIpkAKtVlbMilnxpElKvBKvxO/eLTpWbYMRCAA8NnWqqksXddXSsWN0gk7QCWdn0bnuDao2dOJGbuTGoCCTyWQymTZsEB2rtsI1EAB4ZL2CegX1CnrySb6Kr+KrEhI0Wzhu4wW8gBcsXIjCUTWwFxYAVJral6N1axpEg2jQ4cO0kBbSws6dRee6v9BQU4wpxhQTFiY6SV2BKSwAeGge5z3Oe5xv3tzyreVby7dJSfQ2vU1v6/Wic9kVS7EUu26d0kHpoHQIDhYdp67BFBYAPFDfy30v973cpElFVkVWRdbevZovHJtpM23etk0tHCEhouPUVRiBAIBd6lSVoyMxYsRsq5SGDROdyx4ex+N4XGysUzundk7txo07yo6yo6yiQnSuugojEACwQ5L4dD6dT9+6VX2s3cJBYRRGYUlJauGYMAGFo2bUWAHps73P9j7b27Xr2bNnz549W7USfeIAYA9jslk2y+YNG9gmtoltmjBBdCJ7bA2dypVypVwZNUotHKWlonPVF1U+hfX7PW8CA9UtAnx9mQfzYB5Nmtz5Dz3IgzwKC+k1eo1ei4+naTSNpq1Zo5gUk2LKyBD9wgDUR/I5+Zx87oMP2CQ2iU2aO1d0HnvQ0EkbHruA9OK9eC/esmWD1AapDVK/+orm0ByaM2TIIz9hNEVT9IYNRfuL9hftDwmpby0iAUSQT8on5ZNLlrAZbAabsXSp6Dz28JV8JV/53XcVpypOVZzy9j694PSC0wt+/ll0rvrqkQuI15+8/uT1Jyen0smlk0snHz9Ox+gYHXN1rdp4KSnlE8onlE8YMUL9Rbl5U/QLBlCXqBfJQ0LUi+Th4aLz3N+lS+o2697e6kzFpUuiE9V3j3wNpHR26ezS2ZGR1VM4bHx8HHs59nLsdeBAj6geUT2imjUT9UIB1CVylBwlR738MulJT/qPPhKdxx6+hq/ha65ckWKlWCnW3x+FQ1sqPQJxN7gb3A29e1tbWVtZW5lMNdUQhifxJJ70zTdNujTp0qTL0KHf/PLNL9/88ttvYl42gNrJ7Te339x+GzuW5bAclhMVpU5ZOWhvU9UQCqGQa9es063TrdN9fLLKs8qzyk+dEh0Lfq/SIxBrf2t/a/+JE2u6kxjzY37Mz8ur9OXSl0tfTklRb2z6j/+o2ZcLoHZyi3aLdov285N2Sjulndu3a7ZwEBFRcTFP4Ak8YdQoFA5tq/wU1l/oL/QXLy9hiY/QETqi05U1L2te1vzgQdtFfGF5ADRMX6wv1hd7eqo9vWNjKZIiKbJRI9G57jGDZtCMW7fYUraULR01yrTNtM20LTVVdCy4v0qPINSLbrm56kW3Ll1EnwAtpaW01Ggse6PsjbI3Bg06c+bMmTNnrl0THQtApDtTza2tra2tU1LoJJ2kk9r7oMUzeSbPrKhg7syduRsMakOnPXtE54KHU+kRCF/Gl/FlGrr2cHtPHseVjisdVyYn39klFKAe6h3SO6R3SLduln9Y/mH5x8GDWi0ctr4cLJ7Fs/igIBSO2qnyU1i9qTf1zskRHfxu7G32Nnu7d2+1oCQmopBAfaL+vnfqJIVJYVJYUhLrx/qxfv/5n6Jz2cNSWApLCQlRhipDlaGbNonOA4+m8gWkM3WmzrGxooPbtYyW0bJevdQptuRk1zDXMNewtm1FxwKoDrYtgugNeoPeSExknsyTeXbsKDqXPewsO8vOvvmm8bzxvPH82rWi88DjqXQB6TK6y+guo3ftosW0mBafOyf6BO6vZ0/Hrxy/cvzq6FE3TzdPN0/tdkoDqAx1xNGiRfmI8hHlIw4epPfoPXqvWzfRueyaSBNpYni4sdRYaixdvlx0HKgaj7wMV5ZlWZZ1OsYYY+zYMfVo06aiT8genspTeeq331rmWuZa5vr5nfr01KenPv3hB9G5ACpD7T1ue5/Fx6tf+/cXncuusTSWxm7erCxWFiuLX3lFPci56FhQNR75TnS1p7Ci8Ml8Mp8cEKAeLS8XfUL2MG/mzbyfesphrMNYh7HJybZezqJzATyMrhFdI7pGNGrE5/F5fJ5tClm7hYNP49P4tB071MIxbdrtoygcdUyV3Qioy9Pl6fKef57n8Tye9/XXbCabyWY2biz6BO1aQAtowcWLFeMrxleMt+3mefGi6FgA/85gMBgMBgcH8wXzBfOFr75Sr+0ZDKJz3d/hw0VTiqYUTRk5Epuh1m1Vfie5foN+g37D0KFWV6ur1TUmRvOFxJM8yfN//9fhosNFh4s+Pif3ndx3ct/334uOBfD/fTk2bmQGZmAG2yd57bH15agwV5grzIMGYfPT+qHatiKRF8mL5EWDB1MohVLo7t339APRJNsmbb6+6rr0CxdEJ4L6SX3/fPihep/EnDmi89iDvhz1W7V1JDStMK0wrYiPV+8wHT1abSxVUiL6hO+vUyeextN4WkqKW5pbmlta166iE0H9oivVlepKly/XfOG4vSilIqcipyJn8GAUjvqp2lvaqtsvHz6sbss8ZAjNpJk088YN0Sduj20dPbvFbrFbKSm2O3tF54K6TU6UE+XE2bPJi7zIa9Ei0Xns4cf5cX7cbObP8ef4c76+aOhUv9XYbro2coAcIAd4e9Pf6G/0t7g4NovNYrOcnES/EPbwE/wEP/HTT9Z0a7o13c8ve0D2gOwBWr//BWoLW18OlsgSWeLnn9f0LtcPrR/1o34//siGsCFsiLe3cbhxuHG42Sw6FohV7SOQu9l22WTBLJgFP/88/5h/zD/W0N5ad7FtCSGRRBIlJ7uvdV/rvva//1t0LqjdbH056M/0Z/rzZ59ptnBw4sQLCqSnpaelpwcNQuGAf1fjBcRGndo6flzqLfWWeg8ZwjN4Bs+4fl30C2IPm8PmsDnt2llbWFtYW9g2bezZU3QuqF3U35tBg7Tel8P2frQ2tja2Nh46NDM4Mzgz+OxZ0blAW4QVEBtjU2NTY9O0NPIgD/Lw9eXhPJyH//qr6Fx2fUQf0Udt26pD+qQkua3cVm5bXS19oa6w9eVQf89jYrTal8O22IU5MAfmMGpUVlpWWlaa0Sg6F2iT5obM6lYNsqwWkoQENpvNZrM13HnQ1npzvXW9df2gQXjDwb+rLX05VLadJMaMUZexx8WJTgTaJnwEcjf1F9dkkhZJi6RFzz3H03k6T//lF9G57IqgCIpo1UrqJfWSeiUkuHd27+ze2d1ddCwQSx+qD9WHPvWUdaR1pHVkfLxmC0cgBVKg1cq6s+6s++TJKBxQGZobgdztzie4C9YL1gsJCepWDhru8+FBHuRRWEgZlEEZQ4ao13oyMkTHgpqh36/fr9/v4sIP8UP8UGoqnaATdEKDu0DfbujEjdzIjUFB6t52GzaIjgW1i+YLiI26aqV7d9aINWKNkpO13jBHVVTE2/A2vM2QIaZDpkOmQ+npohNB9bC1C2BJLIklpaay/qw/6+/iIjqXPczMzMy8cKHxmvGa8dr774vOA7WT5qaw7MlyynLKcsrJUQuHj496ND9fdK77a9GCXWVX2dVDh9wC3QLdAvv1E50Iqpat86U0X5ovzU9I0HrhUIWGonBAVag1BcRGnaP917/UR76+thucROe6vxYtJCYxiSUk6LiO6/jAgaITwePxOO9x3uN88+ZqB8yDB2k5LaflPXqIzmVXLMVS7Lp16vtnyRLRcaBuqDVTWPbYLlZyF+7CXZKS6EP6kD7UcJ8Pd3In95s3eQPegDcYOdK01rTWtDY5WXQseDh9L/e93PdykyZlT5Q9UfbEgQPMn/kzfw1/INhMm2nztm1KT6Wn0nPKFPWg1So6FtQNtb6A2Nj2rJLGSGOkMcnJbD6bz+Z36CA61/0VF1tft75ufX3kyCxDliHLkJQkOhH8MXWqytFRXcSxe7d6dNgw0bns4XE8jsfFxjq1c2rn1G7cuKPsKDvKKipE54K6pdZNYdmTHZEdkR1x/rxloGWgZaC3N1/FV/FVWu/r0bSp9J70nvTe3r26Ql2hrtDfX3Qi+COSxKfz6Xz61q3qY+0WDgqjMApLSlILx4QJKBxQnepMAbGxdRbk2TybZ/v42HYPFZ3r/po2pV20i3bt2yevkFfIK4YPF50IiIgY05l1Zp05MpJtYpvYpgkTRCeyy4d8yCc9vaSgpKCkYPRotXCUloqOBXVbnZnCskedeujUSX2UnKxOQXTpIjqXXXrSk76sjHVgHVgHg8H4lvEt41t794qOVd/I8+R58rxVq9gRdoQd+Z//EZ3n/s6ccdzjuMdxz8CB6R3TO6Z31PBWQFCn1LkRyN3UG/kuXWLH2XF23MdH7beQmys6l11GMpKxYUO1t3x0tO5fun/p/jVqlOhY9YW6lc5bb2m+cNz+PZZcJBfJZdAgFA4Qoc4XEBt108bLl6V8KV/K9/amxbSYFmu4r8ftQkJraA2tiYqSw+VwOXzMGNGx6ir5oHxQPvi3v6mPli0Tnccevpqv5qvz8irCK8Irwv39M6MzozOjf/pJdC6on+pNAbGxveEalDYobVDq68vf5e/ydzW8TfXtQsK2sq1s686dd/pIQJVQd1MOCGCX2WV2+eOPReexqy/1pb5Xr6qrC/39bdf6RMeC+q3eFRCbjJcyXsp46coVx2uO1xyv+fnxo/woP/rPf4rOdX+OjiyH5bCcqCh5q7xV3jpxouhEtdWdqcGf6Cf66YsvaD2tp/WSRt8PRUWUTumUPmTI72+kBRBLo2+YmmMrJBWRFZEVkX5+6tEzZ0TnsudOA6Kn6Wl6essW2ydo0blqC7dot2i3aD8/XsgLeeGOHcyduTP3Bg1E5/pjxcW8O+/Ou48YYdulWnQigH9X51dhVVbvY72P9T7Wpo3DbofdDrsTE+kYHaNj2m0YxSN5JI+0WNgNdoPdeOUVxVfxVXxt9yuAjboar08fCqZgCk5MpHW0jtY1ayY61z1ur8KzTrFOsU4ZNSrLM8szy/PQIdGxAP5IvR+B3C17QPaA7AFXr5YlliWWJQ4cSEtpKS3VboMo24iEN+PNeLPPP5ej5Cg56uWXRefSClvrYR7BI3jEgQNaLRy2DwKUSZmUGRCAwgG1AUYgD9CL9+K9eMuWDtcdrjtcj49nvsyX+Xp4iM5l1+0+D6SQQsrMmerUR2Sk6Fg1zS3NLc0trWtXpmd6pk9N1ez2/7b/X2NoDI159VVlqDJUGbppk+hYAA8DI5AHUFe7FBZamluaW5oPHkycOHENN4hSSCGFMfUP07p18gB5gDxg5kzRsWqKuktuhw7SJemSdCkhQbOFw+ZZepaenT8fhQNqI4xAKkmdEmnRgnzJl3wPHaIUSqGUvn1F57If+PYnXH/yJ//ZsxWDYlAMGl6u+ojuXLuKcYhxiDl6lFIplVK7dxedyx52lp1lZ99801hqLDWWLl8uOg/Ao3AQHaC2yc/Pz8/Pv3WrzZg2Y9qM2bFDWiOtkdZ4erIv2Zfsyz//WXS+ewNTPuUzRiVUQiVDhjhzZ+7Mr11Tz0PDI6mHZCvoUrwUL8UnJNAlukSXtLvoQRURoVxWLiuXFy0SnQTgcaCAPKIrCVcSriSUl7dp3aZ1m9a7djn80+GfDv+0dRzs3Fl0vnvYCome9KQfMqR9m/Zt2rcpKsrPzc/Nz619rXbVLUeaNqXn6Dl67sAB+p6+p+81PBIcS2Np7ObNyjZlm7JtxgzRcQCqAq6BPKbTC04vOL3g5k310fDh6lSRhhtE3b5GorbaXbNGDpAD5IDa80m4R1SPqB5RDRvy2Xw2n71rFyVREiV5e4vOZVckRVJkTIxLtku2S/b06epBzkXHAqgKGIFUEXVKqLzcucS5xLlk1y4+j8/j8/r0YSfYCXZCuz2yWQErYAV+fs6hzqHOoRZL/t78vfl7jx0TnetuBoPBYDA4ONzYd2PfjX3bt7MMlsEytL7J5OHDRa2LWhe1Hjfu+AfHPzj+QXm56EQAVQkX0avJndanTcqalDWJjWWD2WA2WPsNo9T7Ed5+2+Rh8jB5aGVTQcZks2yWzRs3MgMzMMO0aaIT2cMX8UV8UVpahbnCXGEeNOj3I1SAugUFpJp1jega0TWiUaMW+hb6FvroaJpFs2jWiBGicz0I/4J/wb9YtcrkanI1ub7+uqgc8jn5nHzugw/YJDaJTZo7V/TrYg9fypfypdnZlmGWYZZhPj625d+icwFUJ1wDqWa5IbkhuSG3bpXkleSV5I0bx2fymXym9htEsalsKpu6cKF8Wj4tn37vvZr++bJJNsmm0FDNF46VfCVf+d13FTkVORU5gwejcEB9ghFIDbNdBG6yqsmqJqt27lSPjh4tOtcDJVESJYWFKS2VlkrL6mu0pC7LDQlRO0eGh4s+7fu7dEm9sdTb29a4THQigJqEEUgNO/fiuRfPvVhWpv7hefFF2yod0bkeyI/8yG/BAvUP/OrVVf306rLcKVPUZcYffST6dO3qR/2o348/sqVsKVvq44PCAfUZVmEJoq7aslr7F/Yv7F/49dfXrl27du1a167qv/bsKTqfXYwYMU/P9lPbT20/tUWL/NT81PzUw4cf9enuNMjqQ32oz7ZtbCPbyDZqsC8HJ068oEDyl/wlfz8/Y4AxwBjw3XeiYwGIpL03aj0THR0dHR1tsbi4uLi4uEyeTJtpM23etk10rgdh8Syexc+ZI+fKuXLuJ5/cPvrQU6K6Ql2hrtDfX9op7ZR2bt9+p8+JxvAMnsEzrl+3NrY2tjYeOjQzODM4M1jDHSwBahCugWiM7X6HC/0v9L/Q/4sv1Fa2kyaJzvVA0RRN0Rs2KC6Ki+Jiu9Paar37P9MX64v1xZ6efC6fy+cePqxuX/7EE6Lj342f5Cf5yZISJjGJSc8/rzCFKezIEdG5ALQEBUSjbIXEbDabzWbbLq1TpojO9UCcOPHPPlOvDQQGqgetVneDu8Hd0Lu3tbW1tbV1SgqdpJN0smVL0XH/mO2GvzFj1O3w4+JEJwLQIs1NGYDq3Llz586d41y9VrJ3r3OKc4pzSqdOFEMxFOPmJjqfXYwYMVluP6/9vPbzOnZ0/sX5F+dfvvvOusS6xLokMZF9zj5nn7duLTrmPQIpkAKtVlbMilnxpElKvBKvxO/eLToWgJZhBFKrSJK6Cmr9evUPtW1vJQ273aKVjGQkY8OGouPc4/Z293wYH8aHBQaaRplGmUZt3Cg6FkBtgAJSKzEmvyO/I78TEcH2sX1sX3Cw6ES1FTMzMzMvXGi8ZrxmvPb++6LzANQmmMKqpfKP5B/JP3LokLOzs7Ozc6tW6tE+fUTnql1CQxWzYlbM774rOglAbYRlvLUa5+pF3r//nQ7SQTqo4RvwtGI8jafxn3yivm5LloiOA1CbYQqrjtGV6kp1pcuXkxd5kVft6fNR7W7fX6P0VHoqPW2r2e5dZgwADw8jkDpGaaw0VhovXsw38o18I6ZmeByP43Gxsc2eafZMs2emTlWPonAAVAUUkDrKJJtkk/zWW/QqvUqvaqWvRw0KozAKS0pyaufUzqndhAlH2VF2lFVUiI4FUJeggNRxSpASpAS9/ba6jHbpUtF5qh0nTjwjo6SgpKCkYPRotXCUloqOBVAXoYDUE+pWHO+8QyEUQiHiGkRVrzNnHGMdYx1jn39e3fX4xg3RiQDqMizjrWfy9+Tvyd/zzTdPlj5Z+mRpSQm1olbUSvutdu3yIi/yys2VHCVHydHX9+T0k9NPTr96VXQsgPoAq7DqOfmYfEw+Nn8+m8PmsDlhYaLzPCy+mq/mq/PyLAMtAy0Dvb3VToAXL4rOBVCfYAqrnjMNMA0wDVi9mubSXJo7b57oPA/Ul/pS36tX2Xw2n83390fhABAHIxD4HV2sLlYXGxREcRRHcZ98QgoppDx8n4/qVVSkfvX1VW8ENJlEJwKoz3ANBH4nf0f+jvwdRmN73p635/n5LIgFsaBhw8QWkuJi3p13592HDTPFm+JN8RkZol8nAMAIBB5AjpVj5dhXX2VX2BV25dNPaT2tp/U10HL29i6+TMd0TDd6tPE142vG1w4eFP16AMD/QwGBh6I7qDuoOzhtGl2my3R5w4bqKiQ8kkfySItFHfn89a9qY6roaNHnDwD3QgGBSpG7yl3lrlOn0nbaTts3bqyqXua2wiEtk5ZJy6ZONe437jfu//JL0ecLAPbhGghUSv6v+b/m/5qd3b5N+zbt22RksLFsLBvbvz+lURqlVb5FLU/lqTz122+l36TfpN8MBuMc4xzjnL17RZ8nADwYlvHCIzGtMK0wrYiPbza+2fhm47t3V48uWcJX8pV85Xff2fu+3//7kiXXP7v+2fXPevUy9jT2NPY8elT0eQHAw8MUFlQL1zDXMNewJ55o2L1h94bd27UryynLKcu5cuX0gtMLTi+4eVN0PgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAO/4PSBxbMqgmA24AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTItMTVUMTU6NTc6MjcrMDg6MDCiEb4vAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEyLTE1VDE1OjU3OjI3KzA4OjAw00wGkwAAAE10RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fY2sxYnphMHpqOWpqZGN4ci9jaGVjay5zdmfbTpDYAAAAAElFTkSuQmCC"); + background-size: contain; + } + + .icon-close:before { + content: " "; + display: block; + width: 16px; + height: 16px; + position: absolute; + margin: auto; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 9999; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIEAYAAAD9yHLdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAADwRJREFUeNrt3V1sU+cZwPHndTAjwZ0mbZPKR/hKm0GqtiJJGZ9CIvMCawJoUksvOpC2XjSi4kMECaa2SO0qFEEhgFCQSqWOVWqJEGJJuyYYWCG9QCIOhQvYlgGCIFmatrVSUhzixO8ujNM1gSZOfPye857/7wYlfPg5xj5/n/fExyIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABATizsWti1sCs/v6y0rLSsdMaMZ/Y8s+eZPZMnm54LQO6kn/fp/UB6v2B6LrdRpgcwZf7e+Xvn7505MxAIBAKBrVt1ja7RNdXVaqlaqpbOmTP0z+u9eq/ee/euFEqhFH7ySeCjwEeBj+rr299of6P9jb//3fT2AMhcWVlZWVnZ3Ln6uD6uj2/eLF3SJV1VVapW1ara6dOH/nn9hf5Cf3HzpupW3aq7qSl5LHkseay+/nLt5drLtbdvm96eXPNZQJQqn1Q+qXzS73+vN+gNesObb0q7tEv7xImZ/kv6kr6kL/X3q0PqkDpUXx/aFNoU2rRz53l1Xp1X/f2mtxTAcMv1cr1cT5jQfb37evf1ujrpkR7p2bxZ1agaVZOXl/E/WCM1UnP/vv5cf64/f+utjg87Puz4cPfu1G9qbXp7neaTgChVeqD0QOmBP/5RHVPH1LHf/CbrN1EplVLZ2iqt0iqtv/51NBqNRqP37pnecgDpI42CgtTz9OTJ1PO0sjLbt6PX6/V6/Z/+1LG5Y3PH5g0bHnzX2pBkXlyPKTtadrTs6Ouvq/fV++r9LVscu6EbckNuPPGEhCUs4UWLpsanxqfGT5yIxWKxWCyRMH0/AH40GI6whCXc3Cyn5bScDoeduj11RV1RV559dkrFlIopFX19sauxq7GrbW2m7wenBEwP4JT0OY7UV6+/nrMbjkhEIitWSIVUSEVLS0ljSWNJYyhk+v4A/GQwHHtkj+xpahp8XuaImqwmq8m7di2oXlC9oHr2bNP3h1OsDUhgfWB9YP2WLdIgDdLwgx/kfICzclbOLluW35Hfkd/x5z8PPqABOGbYEcd22S7bKypyPsiDc6v9df11/XWvvWb6fnGKtQHRj+nH9GOrV5ueY/CVz4MHNCEBsm9YOHJ8xPEo6og6oo64YD/k1PaZHiDbvruD/uYb0/MMUyEVUtHWFi+Pl8fLf/Wray9ee/Haiz09pscCvGjYUpWpI44RBE8FTwVPFRRcLLxYeLEwHjc9T7ZYdwSi2lSbavvxj03P8UgsbQHj5pqlqlFK9iZ7k70u3i+NkXUB6Tvcd7jv8H//a3qOEXGyHciY6ZPjYzXw0sBLAy95YL+UIeuWsNJK75feL71/545arBarxYWFpucZUVjCEj53LvWEqK7mfSTAt9x6jmNEi2WxLL59O3ooeih6aNYs0+Nkm3VHIIO6pEu6Pv3U9Bijxsl2YBjPhiOtUAql0EP7oQxZG5C8SXmT8ibt35++5IjpeUaNpS3As0tVabpBN+iGgQE5Lsfl+KFDpudxirUBuTT90vRL0//xj/S1qkzPkzFOtsOHvHZy/FFUsSpWxfv2pZai//Y30/M4xfpLmRR/VvxZ8Wd//Wvf7b7bfbd//vPBS454xU25KTdnz+YSKbCZ55eq0h5cE2/OB3M+mPPBb3977dq1a9eu2XstLGtPog+Vvp5/X1tfW19bU5N6V72r3v3FL0zPlTHeRwKLeOV9HCPaLbtl94UL8a/jX8e/fv55vzwvfROQNEICmEc47OC7gKQREiD3CIddfBuQNEICOI9w2Mn3AUkjJED2EQ67EZAhCAkwfoTDHwjIIxASIHOEw18IyAgICTAywuFPBGSUCAkwHOHwNwKSIUICEA6kEJAxIiTwI8KB/0dAxomQwA8IBx6GgGQJIYGNCAe+DwHJMkICGxAOjAYBcQghgRcRDmSCgDiMkMALCAfGgoDkCCGBGxEOjAcByTFCAjcgHMgGAmIIIYEJhAPZREAMIyTIBcIBJxAQlyAkcALhgJMIiMsQEmQD4UAuEBCXIiQYC8KBXCIgLkdIMBqEAyYQEI8gJHgYwgGTCIjHEBKIEA64AwHxKELiT4QDbkJAPI6Q+APhgBsREEsQEjsRDrgZAbEMIbED4YAXEBBLERJvIhzwEgJiOULiDYQDXkRAfIKQuBPhgJcREJ8hJO5AOGADAuJThMQMwgGbEBCfIyS5QThgIwICESEkTiEcsBkBwXcQkuwgHPADAoKHIiRjQzjgJwQE34uQjA7hgB8REIwKIXk4wgE/IyDICCFJIRwAAcEY+TUkhAP4FgHBuPglJIQDGI6AICtsDUl+XX5dfl0ySTiA4QgIsmrwlXpYwhJubpaIRCSyYoXpuTIWlrCEz50b/Nrr2xGRiESqq6PRaDQavXfP9FiwAwGBI6w5IvEqjjiQAwQEjiIkOUY4kEMEBDlBSBxGOGAAAUFOEZIsIxwwiIDACEIyToQDLkBAYBQhyRDhgIsQELgCIRkB4YALERC4CiEZgnDAxQgIXMn3ISEc8AACAlfzXUgIBzyEgMATrA8J4YAHERB4inUhIRzwsIDpAYBMJNYm1ibWKqUeV4+rx5X3XwCdkTNyxoLtgC/xwIUnWPN5HI/i8Ge2A04gIHA168MxFCGBhxAQuJLvwjEUIYEHEBC4iu/DMRQhgYsRELgC4RgBIYELERAYRTgyREjgIgQERhCOcSIkcAECgpwiHFlGSGAQAUFOEA6HERIYQEDgKMKRY4QEOURA4AjCYRghQQ7kmR4AdhkMR1jCEm5uliNyRI54MBxhCUv43DkpkiIpunVLbspNuTl7tumxRu2W3JJbM2cGC4IFwYKFC6fGp8anxk+ciMVisVgskTA9HuzAxRSRFcOOOCISkciKFabnylj66ril8dJ46Zo1wY3BjcGNVVV6m96mt505Y3q8jKX/HyqkQipaWkoaSxpLGkMh02PBDixhYVysWaoa4bLq1lxGnqUtZBEBwZj4JRxDERLgWwQEGfFrOIYiJAABwSgRjocjJPAzAoLvRThGh5DAjwgIHopwjA0hgZ8QEHwH4cgOQgI/ICAQEcLhFEICmxEQnyMcuUFIYCMC4lOEwwxCApsQEJ8hHO5ASGADAuIThMOdCAm8jIBYjnB4AyGBFxEQSxEObyIk8BICYhnCYQdCAi8gIJYgHHYiJHAzAuJxhMMfCAnciIB4FOHwJ0ICNyEgHkM4IEJI4A4ExCMIBx6GkMAkAuJyhAOjQUhgAgFxKcKBsSAkyCUC4jKEA9lASJALBMQlCAecQEjgJAJiGOFALhASOIGAGEI4YAIhQTYRkBwjHHADQoJsICA5QjjgRoQE4xEwPYDtbAtH4kriSuIKT1BbXCy8WHixMB6fuGzisonLVq/W2/Q2ve3MGdNzZeysnJWzy5blt+e357f/5S8ljSWNJY2hkOmxbMcRiENsDcfV7Ve3X93+zTemx4IzOCJBJghIlhEO2ICQYDQISJYQDtiIkOD7EJBxIhzwA0KChyEgY0Q44EeEBP+PgGSIcACEBCkEZJQIBzAcIfE3AjICwgGMjJD4EwF5BMIBZI6Q+AsBGYJwAONHSPyBgDxAOIDsIyR2831ACAfgPEJiJ98GhHAAuUdI7OK7gBAOwDxCYgffBIRwAO5DSLzN+oAs18v1cj1hQk95T3lP+aefpr77y1+anitje2SP7Dl7NhW+1auj0Wg0Gr13z/RYQDYMvsALS1jCzc0SkYhEVqwwPVfGKqVSKltbQ++E3gm9U1V1Xp1X51V/v+mxnGL9B0p1X+++3n29ri71FeEA3GjwcR2RiESqq1MhOXfO9FwZa5VWaa2s7DnYc7Dn4O7dpsdxmrUBKX+7/O3yt3/2M5krc2Xupk2m58lYeqkqmogmomvWEA74QfpxHtwY3BjcWFXl1U9I1Iv0Ir1o69b53fO753fPm2d6HqdYG5BkXjIvmbd1q3pOPaeemzDB9Dyjlj7i2Ck7ZeeqVZzjgB+lP2o3dU5kzRqvHZGoGlWjavLyAg2BhkDDa6+Znscp1gZEzVQz1cyqKtNzjBpLVcAwnl/aOi7H5biH9kMZsi4gCzoXdC7o/OEPZZ/sk33TppmeZ0QsVQEj8vbS1owZJY0ljSWNoZDpSbLNuoAMrBtYN7DuRz8yPceIWKoCMubVpa3Q/ND80HwP7JcyZF1ARIkS9e9/mx7jkTjiAMbNa0ckgUmBSYFJ//mP6Tmyzdr3gZTGS+Ol8Rs31FK1VC2dM8f0POkjjuCTwSeDT1ZXp19JmR4LsIFr30eyQ3bIjs7O6AvRF6IvFBebHifb7DsCeUA1qAbV0Nxseg7CATjPrSfb9VP6Kf2UC/ZDDrE2IMlkMplM7t8vNVIjNffv53yAIUtVhANwnluWtvRhfVgf7u1VL6uX1csHDpi+X5xibUAu116uvVx7+3bqqz/8IWc3nD7imBecF5y3ciUnx4HcM36yPSlJSb71VrQj2hHtuHPH9P3hlDzTAzgt1hRrijW1tU3ZMWXHlB1z5qgr6oq68uyzWb+h/bJf9re0BIuCRcGitWs54gDMi8VisVgskZganxqfGj9xInWtqvJyuSE35MYTT2T79vRJfVKfPHas4+mOpzuerq01vf1Osz4gabGWWEus5dSpaV9N+2raV4mE7JJdsmvJEmmXdmnP/J3q+pK+pC/190undErn3r1FkaJIUeR3vzv9yulXTr/S12d6ewF8Kx2S4gvFF4ovfPxxX29fb19vQYE+qo/qowsWqPfUe+q9QMYrMumlKlklq2TVm29+Nxxam95up1n7U1gjKSstKy0rnTFDr9Qr9cotW1SLalEtq1enfgy4qOjhf+vOHVkn62TdJ58M3B24O3C3vv7Lg18e/PJgZ6fp7QGQufQ18/QpfUqf2rw59d3nn0/9OmPGsL+wRJbIkn/+U7+qX9WvNjUFZgVmBWbV17cXtBe0F3R1md6eXPNtQB4l/fkEiTWJNYk1P/1p+n0lvF8D8I/BHwvWokX/5CehaCgaiv7rX6nLs/f2mp4PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtvsf2vlfs7i0WI4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTItMTVUMTU6NTc6MjcrMDg6MDCiEb4vAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEyLTE1VDE1OjU3OjI3KzA4OjAw00wGkwAAAE10RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fY2sxYnphMHpqOWpqZGN4ci9jbG9zZS5zdmdHkn2WAAAAAElFTkSuQmCC"); + background-size: contain; + } + + .icon-right:before { + content: " "; + display: block; + width: 16px; + height: 16px; + position: absolute; + margin: auto; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-size: cover; + z-index: 9999; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIEAYAAAD9yHLdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAJ4pJREFUeNrt3XtcVXW6P/Dn2VwCBxUzNbnkkXRSGzXW2huQRLyMIqKRJF7Q1CkrDS+VGp3Gy9g5YzI6qVsNfTmlqGmipQiIiJqAcnOvhaKRHidshoatpKaBogL7OX+s6Mz8flO5CfzutXne/+zXWhR8QOXZ3+93Pd8vAHuAEKW10lpp7dix0mXpsnR5/34pX8qX8r/7TpZlWZaJGl//9f6+fY3/X+PnEf2dMMYY/yJqYcbbxtvG2/7+lEM5lLN7NyyCRbBowICmfj56m96mt/PzDZGGSEPkxImWNpY2ljYVFaK/T8ZY6+MiOoCzMn1t+tr09a9/TQfpIB0sLITlsByW9+r1Sz8v5mEe5vn7Q3toD+0nT/Y77Xfa73ROTuWNyhuVNyorRX/fjLHWg0cgzUybcmrThvIoj/JUFcMwDMOeeKLFvmA8xEN8TQ2sh/Ww/rnnFFVRFfXwYdE/B8aY8zOIDuBsqDf1pt6vvdbihaPRBtgAG7y8wAQmMKWlyflyvpw/aZLonwNjzPlxAWlWiOiN3ugdH//Av7QFLGBxd4dzcA7O7dgh75H3yHvmzBH9E2GMOS+ewmomplhTrCn2qads5bZyW3lJieg8jWgADaABf/yjul5dr65fvPj7uyQ6F2NM/3gE0kxsb9vetr3do4foHP8vLMACLPj977W1mS1bwimcwsnVVXQuxpj+cQFpLt/Ct/BtmzaiY/y0adNqltYsrVmakqIVEg8P0YkYY/rFj/E2E5+zPmd9znbpAggI+PzzovP8qItwES727n23w90OdzuEhfl86fOlz5f79lmtVqvVeveu6HiMMf3gEUgzqVfqlXqluFi7qqsTnefnYCImYmJ4OOVSLuWeONF/Zv+Z/Wf6+orOxRjTD15Eb2ZSlVQlVWVkYCRGYuSoUaLz3C86QSfoRHk5lVAJlURElISWhJaE/vWvonMxxhwXj0CaGT1Lz9KzS5eCDDLI+nnaCQfiQBwYEID1WI/1J05oi+6SJDoXY8xx8RpIM7tccbnickVlZdekrkldk4gwAzMwY8gQ0bnuF2ZhFmZ5eWkd7pMn+1T4VPhUKIq2RvLll6LzMcYcB09htShE6YJ0Qbqwdi3GYRzG6bCxbxbMgll372ojqilTlEAlUAncu1d0LMaYeDyF1aKI1CfUJ9Qn5s6FuTAX5r71lt6mtiAJkiDpoYeojuqo7uOP5VQ5VU6dOVN0LMaYeDwCecCkFClFSpk+HcbBOBi3eTOa0IQm/TX20RbaQlsSE9V+aj+131tvic7DGHvwuIAIIp+Xz8vno6OpJ/Wknrt2YRAGYZCnp+hcdpsAE2DC++8rbypvKm82TtHZbKJjMcZaHhcQwYxnjWeNZ8PDaTpNp+mpqdrd9u1F52qa/fu9LF4WL8ukSTmYgzl4547oRIyxlsNrIIJZ+lr6Wvrm5GBv7I29Bw6EN+ANeOMf/xCdq2mefbbGWGOsMR48GHQx6GLQxXbtRCdijLUcfozXQVSWVpZWllZV+df51/nX7dtH8RRP8aNGwQk4ASc6dhSdzz7du9NVukpXR4zoFNMpplPM/v1Xsq9kX8m+dUt0MsZY8+ERiIMpTitOK067dMm1zrXOtS4sTLurqqJz2e04HIfjsuw623W26+yCgsD8wPzAfMfbrZgx1nRcQBxUUVxRXFHclSu1CbUJtQnh4dpd/R1V+0OHuxGNaMzLazw3RXQuxtgvx4voOtEnpU9KnxR3d88yzzLPsu3bIQ3SIG38eNG57BYEQRB04wZVUzVVP/OMukPdoe7IyxMdizFmPx6B6ETZ+LLxZePv3Qv4PODzgM/j4mg37abdGzeKzmW3YiiGYm9vHIyDcXB2tlwil8gl48aJjsUYsx+PQHROTpaT5eSEBDCDGcwrVojOYy9KoiRKamgAK1jBOmuWGq1Gq9GbN4vOxRj7eVxAnISUKWVKma++ihVYgRXr1sEm2ASbDPoZYTZu8bIJNsGmd95RUEEF//AH0bEYYz9OP79g2E9SI9VINfL996mWaql23DjaQBtog44a+RRQQEEEIxjBuHSptgml2ax9UEeFkLFWhEcgTko7z2PIECqiIiravx+DMRiD9drYt3MnEBDQ9OmKqqiK6vgnPjLWGvA7OyelKIqiKJ99pj31NHQovAavwWtVVaJzNU1cHKyCVbAqM/Ppjk93fLpj27aiEzHGeATSahjTjenG9IAAOkyH6XBWFpyEk3BSf419tISW0JJTp2wdbB1sHaKiTg86Pej0oG++EZ2LsdaIC0grozXyPfpow7SGaQ3TMjNxKS7FpTps7CMgoPPntYuICG1q6+9/Fx2LsdaEC0gr1Z/6U3/y9nZNcE1wTThwAI7CUTjauHWK3litVEEVVDFypFqlVqlVpaWiEzHWGvAaSCt1Bs/gGbxx46bfTb+bfsOHUxqlUZpej6rt2hVX4kpcefy4sYOxg7HD00+LTsRYa8AjEAYAALGxsbGxsS4u5XK5XC4nJcEe2AN7XnpJdK6muX1bex0/XnuYICNDdCLGnBEXEPZvIMokk0xLlzb2ZYhOZK/GDne8htfw2iuvKJFKpBL5wQeiczHmTPg8EPZvWZdZl1mXHT/uY/Yx+5ivX4cn4Ul4MiLih4Y/B4cZmIEZBgPchJtwc8wY33Lfct/y2trKO5V3Ku+cPCk6H2POwOF/ETDHoDUmxsVpV1u3aq9ubqJzNY3ZrE1tvf66ds1nuDPWFFxAmF0C9wTuCdwzbBj6oi/67tuHc3AOztFfYx9Npak0dft2TMZkTH7xRe5wZ8x+XEBYk5i6m7qbuptMtlG2UbZRGRlQCIVQ2KmT6Fx2i4RIiExPh0zIhMwJE7SRSeMiPGPsp3ABYb+INrXVq5d2lZWlvT72mOhcdiMgoKIi7WL0aG1EcvWq6FiMOTLuA2G/iPaOvbEjPCQEBsEgGKTDRj4EBAwOhkWwCBbl5BhvG28bb/v7i47FmCPjEQhrVn379u3bt2+HDm55bnlueWlpOAyH4TAdNvaFQiiE/u1v2Bk7Y+eICMtiy2LL4gsXRMdizJHwY7ysWVVVVVVVVd2545Ptk+2T/fHH2t3GvbZ+/WvR+e5bBVRAhbc3zaJZNCsu7lG3R90edcvLu6xcVi4rX38tOh5jjoCnsFiLaFyMDggICAgIiI6mPbSH9uivkQ/n4Tyc9/DDBjSgAbOzA/MD8wPzR44UnYsxR8BTWOwBQpTmS/Ol+StW4HE8jsfffFN0IrsZwQjGe/dgGkyDadOnK6FKqBK6a5foWIyJwFNY7IGyFlgLrAVHjnTd3nV71+03buDj+Dg+PmKEXjrcoRIqodLFBaqgCqpiYnzAB3ygpsZqtVqt1oIC0fEYe5C4gDAhrNus26zbiop8yZd86dIlqIEaqBk9Wvuoi+P/vbSCFayNBW/EiK5ZXbO6Znl6WpOsSdako0dFx2PsQXD8d3ysVZCWS8ul5aNH4yf4CX6ye7d2t00b0bmaJjnZy+Jl8bLMmJGDOZiD9fWiEzHWEriAMIciS7IkS8HB2lV6utaf8cgjonPZi+IpnuIPHHAf7j7cffjEiYX+hf6F/rW1onMx1pz4KSzmULQO8KKihjUNaxrWhIdTPuVTfkWF6Fz2wg24ATc888y9gnsF9woyM7XC2L696FyMNScuIMwhnR50etDpQWVltI7W0bqwMMqjPMrTXyMfJmIiJoaHUy7lUu6JE/1n9p/Zf6avr+hcjDUHnsJiuhBSEVIRUvHww3Xn6s7VnUtP17YcGTBAdC57USIlUuKlS7YDtgO2AxERp82nzafNFy+KzsVYU/AIhOmCtoZw/bpWQIYPh9WwGlYfOiQ6l70wARMwoXt3wzjDOMO4vDxtM0pJEp2LsabgAsJ0pXRh6cLShbdu1V6uvVx7OTqaUimVUvXXyIev4+v4epcuEA/xEJ+To62RjBghOhdj9uApLOYEELVfwCtXak9tzZ8vOpHdvu9wJ5lkkp9/Xn1ZfVl9OSVFdCzGforjN2wxdh+0TvDDh31W+KzwWXHnDtRDPdQPG/avDX8OrLHDfQbMgBkxMT6jfUb7jK6qsn5s/dj6scUiOh5j/47j/8NirAm0tYVp0+gUnaJTf/kLmtCEJldX0bnsRVtoC21JTFT7qf3Ufm+9JToPY/+MCwhzavJ5+bx8PjqaelJP6rlrFwZhEAZ5eorOZbcJMAEmvP++8qbypvLmnDnaTZtNdCzWunEBYa2C8azxrPFseDhNp+k0PTVVu6vDxr4oiIKoffu8lnkt81oWF6dtlXLnjuhYrHXip7BYq2Dpa+lr6ZuTg72xN/YeOBDegDfgjX/8Q3Quu2VABmSMHVtjrDHWGA8eDLoYdDHoYrt2omOx1okX0VmrUllaWVpZWlXlX+df51+3b5+2Z9WoUXACTsCJjh1F57NP9+50la7S1REjOsV0iukUs3//lewr2Veyb90SnYy1DjwCYa1ScVpxWnHapUuuda51rnVhYdpdVRWdy27H4Tgcl2XX2a6zXWcXFGgnJvboIToWax24gLBWrSiuKK4o7sqV2oTahNqE8HDt7uHDonPZCwfiQBwYEIBGNKIxL88Ua4o1xTaeRc9Yy+BFdMb+SZ+UPil9UtzdPcs8yzzLtm+HNEiDtPHjReeyWxAEQdCNG1RN1VT9zDPqDnWHuiMvT3Qs5lx4BMLYPykbXza+bPy9ewGfB3we8HlcHO2m3bR740bRuexWDMVQ7O2Ng3EwDs7OlkvkErlk3DjRsZhz4REIY/dBTpaT5eSEBDCDGcwrVojOYy9KoiRKamjQOvNnzVKj1Wg1evNm0bmYvnEBYcwOUqaUKWW++ipWYAVWrFsHm2ATbDLoZyQvgwwykZb7nXcUVFDBP/xBdCymT/r5i8+YA1Aj1Ug18v33qZZqqXbcONpAG2iDjhr5FFBAQdQ2b1y6VLogXZAumM3aB3VUCJlD4BEIY7+AtufWkCFUREVUtH8/BmMwBuu1sW/nTiAgoOnTtaOF6+pEJ2KOjd9xMPYLKIqiKMpnn2lPPQ0dCq/Ba/BaVZXoXE0TFwerYBWsysx8uuPTHZ/u2Lat6ETMsfEIhLFmZEw3phvTAwLoMB2mw1lZcBJOwkn9NfbRElpCS06dsnWwdbB1iIrSzqj/5hvRuZhj4QLCWAvQGvkefbRhWsO0hmmZmbgUl+JSHTb2ERDQ+fPaRUSENrX197+LjsUcAxcQxlpQf+pP/cnb2zXBNcE14cABOApH4Wjj1il6Y7VSBVVQxciRapVapVaVlopOxMTiNRDGWtAZPINn8MaNm343/W76DR9OaZRGaXv3is7VNF274kpciSuPHzd2MHYwdnj6adGJmFg8AmHsAYqNjY2NjXVxKZfL5XI5KQn2wB7Y89JLonM1ze3b2uv48drDBBkZohOxB4sLCGPCIMokk0xLlzb2ZYhOZK/GDne8htfw2iuvKJFKpBL5wQeic7EHg88DYUwg6zLrMuuy48d9zD5mH/P16/AkPAlPRkT80PDn4DADMzDDYICbcBNujhnjW+5b7lteW1t5p/JO5Z2TJ0XnYy3L4f+CMtaaaI2JcXHa1dat2qubm+hcTWM2a1Nbr7+uXfMZ7s6GCwhjDihwT+CewD3DhqEv+qLvvn04B+fgHP019tFUmkpTt2/HZEzG5Bdf5A5358IFhDEHZupu6m7qbjLZRtlG2UZlZEAhFEJhp06ic9ktEiIhMj0dMiETMidM0EYmjYvwTK+4gDCmA9rUVq9e2lVWlvb62GOic9mNgICKigwHDAcMB6KiTvmd8jvld+2a6FisabgPhDEd0N6xN3aEh4TAIBgEg3TYyIeAgMHBtmJbsa04NzfoYtDFoIt+fqJjsabhEQhjOtS3b9++fft26OCW55bnlpeWhsNwGA7TYWNfKIRC6N/+hp2xM3aOiLAstiy2LL5wQXQsdn/4MV7GdKiqqqqqqurOHZ9sn2yf7I8/1u427rX161+LznffKqACKry9aRbNollxcY+6Per2qFte3mXlsnJZ+fpr0fHYT+MpLMZ0rHExOiAgICAgIDqa9tAe2qO/Rj6ch/Nw3sMPG9CABszODswPzA/MHzlSdC7203gKizGngyjNl+ZL81eswON4HI+/+aboRHYzghGM9+7hLbyFt6ZNs+yw7LDsaBxpMUfBU1iMOSFrgbXAWnDkSNftXbd33X7jBj6Oj+PjI0bopcMdKqESKl1coBt0g27PPecDPuADNTVWq9VqtRYUiI7HNFxAGHNi1m3WbdZtRUW+5Eu+dOkS1EAN1IwerX3UxfH//VvBCtbGgjdiRNesrlldszw9rUnWJGvS0aOi47V2jv9OhDHWbKTl0nJp+ejR+Al+gp/s3q3dbdNGdC67xUAMxGzd6vW219teb7/0Ug7mYA7W14uO1dpwAWGsFZIlWZKl4GDtKj1d68945BHRuexFGZRBGamp7nXude51kyYV+hf6F/rX1orO1VrwU1iMtULanlRFRQ1rGtY0rAkPp3zKp/yKCtG57IVRGIVR0dH3Cu4V3CvIzNQKY/v2onO1FlxAGGvFTg86Pej0oLIyWkfraF1YGOVRHuXpr5EPEzERE8PDKZdyKffEif4z+8/sP9PXV3QuZ8dTWIyxHzyV+1TuU7mdOhm+NXxr+DYjA9/Bd/Adk0l0LnvRCTpBJ8rLaRgNo2FhYSX5Jfkl+ZWVonM5Gx6BMMZ+oI1Ivvnmzt07d+/cHTpUu3v4sOhc9sKBOBAHBgQYFhsWGxbv3dsnpU9KnxR3d9G5nA2PQBhjP6rxF69HqEeoR+jWrRiN0Rg9aZLoXE3z6qta535SkugkzoILCGPsPhkM0gXpgnRhzRqMwziMmzNHdKL7thAWwsKvvlImKhOVid27i47jLLiAMMbsJifLyXJyQgKchJNw8t139dLhjs/is/hsr16862/z4DUQxpjdlGnKNGVaYiJFURRFvfIKJVESJTU0iM71s76Bb+Cb3/xGdAxnwQWEMdZkarQarUZv3ky9qTf1Hj8eXoFX4BWbTXSuH0PP0rP07K9+JTqHs+ACwhhrstjY2NjYWBcX3ISbcFNUFGyCTbDJ4Li/VxbCQljIW540F8f9g2aMOSztjPY2bb7c8OWGLzccOIC7cBfueuEF0bl+ViqkQuqNG6JjOAtX0QEYY/rReJQuHaWjdFRHR+nKIINMVLerblfdLotFdBxnwQWEMfazgi4GXQy66OfXcLbhbMPZrCwYBsNgWJ8+onPdL/oT/Yn+lJ9f6l3qXepdVSU6j7PgAsIY+1HaVFWvXg0TGyY2TMzK0u4+9pjoXPYypBhSDCl//KPoHM6G10AYY/8fU3dTd1N3kwlCIARCcnO1u/orHPQcPUfPbdpkednysuXlzEzReZwNj0AYYz+Q3pbelt6OiLBdt123Xf/kEyiEQijU32OvFE/xFH/gwHc139V8VzNvnug8zsrhO0cZYy1Pm6qKi9Outm7VXt3cROeyF31Kn9Kn27bhWByLY2fM0M49qasTnctZ8RQWY62Ysaexp7Hn7NlaA+D27dpd/RUOjdmsdlO7qd2mT+fC8WC4iA7AGHvwftjL6jSchtPvvaeXvaz+7xvQHssld3In94QE9Zh6TD22eLHoWK0Nj0AYawUaO8blcrlcLt+0CcxgBvOKFaJz2YtO0Sk6VV+P5/E8np8xQ/1U/VT9dOVK0blaK/2842CM2a2HuYe5h/mhh9pvbb+1/dbt2wEBAWNjRedqmtu3tU7y2FjFT/FT/A4eFJ2oteMRCGNOSDsIysurXVy7uHZxaWm6LRxzYS7M/fZbLMdyLB8xgguHY+ERCGNOJHhn8M7gnV261I2pG1M3JjMTB+NgHBwYKDqX3QbAABhQWQn5kA/5I0dqi+Jnz4qOxf4Vj0AYcwJBY4LGBI3p3r3erd6t3i0vT7eFIwzCIOyLL7TCMWAAFw7HxiMQxnTMOMU4xTjlN78hb/Im76wsKIACKPDxEZ3LXrSEltCSU6dwGS7DZaNGaYXj6lXRudhP4050xnRIJplkGjyYjGQk4/792t327UXnshfNp/k0/8gRzxc8X/B8ISbm5LWT105eq64WnYvdH57CYkxH5PPyefl8dDQVUREVNe7tpL/CAdEQDdEffYSrcBWuGjWKC4c+8RQWYzogpUgpUsr06TAOxsG4zZvRhCY0uep0BsFsVhRFUZTXX9euHfcIXPbTuIAw5sB+6BjXaeNfY8e4dtTtO+8oqKCCf/iD6Fiseej0HQxjzgxRKpPKpLJVq+B5eB6ef+MN0YnsRUmUREkNDWAFK1hnzVJRRRU3bxadizUvHoEw5gC0xj93d4+rHlc9riYn4wf4AX4wcaLoXHabBbNg1t27WIqlWDp5ssVsMVvMn3wiOhZrGVxAGBOo38p+K/ut/NWv3ILdgt2C9+6F1+F1eH3kSNG57BYEQRB04wZVUzVVP/OMukPdoe7IyxMdi7UsLiCMCRBSEVIRUvHww3Xn6s7VnUtPh0WwCBYNGCA6V9NYrbZSW6mtNDKypK6krqTuzBnRidiDwY/xMvYABa4KXBW4qlu3ex3vdbzXMT9fr4WDTtAJOlFerl2FhXHhaJ14EZ2xB+Cp3Kdyn8rt0weDMRiDDx3CUAzFUH9/0bnstgyWwTKLpX59/fr69VFRpUqpUqpUVYmOxcTgKSzGWpAsyZIsBQdrV+np2q64jzwiOpfdhsNwGH7smMuLLi+6vDh2bHHP4p7FPb/7TnQsJhZPYTHWAqTl0nJp+ejRWsE4dky3hSMKoiBq3z6vd73e9Xo3KooLB/tnPAJhrBlJnaXOUucpU9Af/dH/ww+1uzo8YzwVUiF1wwbt/I25c7Wb3DHO/hWfic5YM5COSEekI/PmYSAGYuDGjdoZ4/rbaoS20Bbakpio9lR7qj0bGxiJROdijkl3f8EZcxyIUqlUKpW++y7+Dn+Hv0tIEJ3IXo0d42hFK1pnz1b7qf3Ufhs3is7F9IGnsBizQ2xsbGxsrItL+ZflX5Z/uXGjtrYxY4boXHb7vmOcbGQj29Sp6svqy+rLKSmiYzF94QLC2H3oYe5h7mF+6KH2Ie1D2ofs3Kn9Ao6JEZ3LbvEQD/E1NRADMRATE6N4K96Kd3a26FhMn7iAMPYT+lN/6k/e3q5GV6OrMS1NuztwoOhc9qLVtJpWX7liWGRYZFgUGWnJteRacktKROdi+sZrIIz9G7Isy7LctSscgANwoPHgpv79ReeyFyVSIiVeumTba9tr2xsRoeaquWruxYuiczHnwCMQxv6JVjgefxwICCgrS1vjePxx0bnsRTmUQznnzjUsaFjQsGDkyDMbz2w8s/Ef/xCdizkXbiRkDAACQwNDA0ONRgiBEAgpKNBt4UigBErIycFBOAgHDRzIhYO1JB6BsFZNmi3NlmYPHQprYA2s2bdP26uqXTvRuexFGZRBGamp7nXude51kyYV+hf6F/rX1orOxZwbr4GwVklaK62V1o4dC8EQDME7d2qFw8NDdC67xUAMxGzd2rZL2y5tu7z0Ug7mYA7W14uOxVoHHoGwVkUaJA2SBsXH4xScglPMZu2sboPupnJ/6Bjvp/ZT+731lug8rHXiAsJaBTlZTpaTExLADGYwr1ghOo/93wDIIBNBOIRD+IIFymRlsjL5vfdEx2Ktm+7eeTF2Pxo7xqW/Sn+V/pqUpNvCYQQjGO/dw9t4G2/HxXHhYI6ERyDMqfzQMX69/fX217dtgzRIg7Tx40XnspsJTGC6dcs21TbVNnXcuJLQktCS0EOHRMdi7J/xCIQ5hT4pfVL6pHh5tYtrF9cuLi1Nr4WD1tJaWnv9uo1sZKPhw7lwMEfGIxCma8E7g3cG7+zSpf7P9X+u//PBg9pdSRKdy26hEAqhf/sbdsbO2DkiwrLYstiy+MIF0bEY+yn8GC/TpaAxQWOCxnTvXu9W71bvlpWl3e3ZU3Quu/0efg+/Lytz6evS16VvRIR24t/XX4uOxdj94ALCdMU4xTjFOOU3v2mIbIhsiDx0CFbACljh6ys6l90ICKioyBBkCDIERUUV+xX7FftduyY6FmP24CkspgvGs8azxrPh4TSdptP01FTtbvv2onPZbR2sg3VpaW7+bv5u/hMmcMc40zNeRGcOzfhfxv8y/tczz9j62PrY+jTuiqu/wkGf0qf06bZtMBtmw+znnuPCwZwBj0CYQ9J2xZ02jU7RKTr1l7+gCU1o0t8Z4xqzWVEURVFee0275jPGmXPgEQhzKD90jMsgg7xli+4Kx/cd49SNulG3N9/UCse8edoHuXAw58IjEOYAEOW18lp57Z/+BNtgG2xbsEB0IntpI6X6esNgw2DD4Fde0U78+/BD0bkYa0n6eWfHnIrW+Ofu7hHqEeoRunUrREM0RE+aJDpX09y+jZVYiZWxsVrhaOxHYcy58RQWe6D6rey3st/KX/3K447HHY87+/djNEajHgvHXJgLc7/9FsuxHMtHjFD8FD/FjwsHa11cRAdgrUNIRUhFSMXDD9Ntuk23MzNxFa7CVUOHis5ltwEwAAZUVsJe2At7f/tb5ZJySblksYiOxZgIvAbCWpR2VKyPj+Gu4a7hbuOeTn37is5ltzAIg7AvvoBcyIXckSMVVVEV9e9/Fx2LMZF4Cou1iMDqwOrA6t698TP8DD8rLNTu6q9w0BJaQktOndIKx6BBXDgY+z88AmHNytjT2NPYMyiI2lJbapuRAQgI+MgjonPZbSWshJVHj3rEesR6xI4de/LayWsnr1VXi47FmCPhEQhrFsZ0Y7ox/be/tSXbkm3JR47otnBEQzREf/QRLIAFsCAykgsHYz+ORyDsF5E6S52lzlOmoD/6o39j34Obm+hc9qKdtJN2rlunPqE+oT7R2DFus4nOxZgj4xEIaxJZkiVZmjsX/xv/G/87OVm7q6PC0XjGuAUsYFm2TCscc+dqH+TCwdj94BEIswOiTDLJtHSpdlb30qWiE9mLkiiJkhoawApWsM6apUar0Wr05s2iczGmR1xA2E+KjY2NjY11cSmXy+VyOSkJ9sAe2PPSS6Jz2W0WzIJZd+9iKZZi6eTJFrPFbDF/8onoWIzpGRcQ9m/1MPcw9zA/9FA7j3Ye7Tw++gg34Sbc9NxzonPZLQiCIOjGDaqmaqp+5hl1h7pD3ZGXJzoWY86A10DYv+hP/ak/eXu3/7r91+2/zs7Wa+GgAiqggsuXDVcNVw1XhwzhwsFY8+OtTBgAAJhiTbGm2EcfhTbQBtpkZ+OH+CF+GBwsOpe96ASdoBPl5aSSSurQocp8Zb4yv6xMdC7GnBEXkFZO698ICKAqqqKqY8dwG27DbX36iM5lt8EwGAYrSn1ZfVl92dChZyaemXhmYkWF6FiMOTPezr2VkiRJkiRZpm/pW/r24EE4CSfhZOfOonM1zWefucx0meky89lnlZ5KT6Xnd9+JTsRYa8BrIK2MdlTskCFQDMVQfOwYrIE1sEaHhSMKoiBq3z4vi5fFyzJqVHHP4p7FXDgYe6D4KaxWQlorrZXWjh0LwRAMwTt3YjzGY7yHh+hcdkuFVEjdsEE7f4Mb/xgTiUcgTk7KlDKlzFdfRU/0RM+9e/VaOGgLbaEtiYla4Zg9W7vLhYMxkXgNxEnJyXKynJyQAItgESxasUJ0Hns1doyjFa1onT1b7af2U/tt3Cg6F2Ps//BTWE6isWPc44DHAY8D77+PC3EhLnzrLdG57PZ9x7i21ciUKepkdbI6uXGvLcaYI+E1EJ3rk9InpU+Ku7tnmWeZZ9n27ZAGaZA2frzoXHaLh3iIr6mBGIiBmJgYxVvxVryzs0XHYoz9OC4gOqUVDi8vz0TPRM/Exj2dRowQnctetJpW0+orVwyLDIsMiyIjLbmWXEtuSYnoXIyxn8drIDoTvDN4Z/DOLl3qE+sT6xMPHtTuSpLoXPaiREqkxEuXbHtte217IyLUXDVXzb14UXQuxtj946ewdELbo+o//qPukbpH6h7JzdXu6rBw5FAO5Zw717C3YW/D3rCw0+bT5tNmLhyM6RFPYTk403rTetP6J5+0dbB1sHXIyoL34D14z9dXdC57UQIlUEJODq7AFbgiOlpRFVVRb94UnYsx1nRcQByUNFIaKY0MCdEWxdPTMQRDMKRjR9G57EUZlEEZqanude517nWTJhX6F/oX+tfWis7FGPvleA3EwQSWB5YHlo8ZA92gG3TbvRuDMAiDPD1F57JbDMRAzNatbbu07dK2y0sv5WAO5mB9vehYjLHmwyMQByEfk4/Jx6ZOpcE0mAZ/8AGa0IQmV90V+MaOca3xT4d9KIyx+8YFRDDpiHREOjJvHqZgCqasXg0KKKCgfv5cZJBBJoJwCIfwBQuUycpkZfJ774mOxRhrebp7h+scEOUb8g35RmIiDINhMGzhQtGJ7GYEIxjv3cNbeAtvTZtmmWyZbJn88ceiYzHGHhwuIA9IOIVTOLm6Vv+5+s/Vf960SSscL7wgOpfdTGAC061btqm2qbap48aVhJaEloQeOiQ6FmPsweM+kBamnb/Rpk31N9XfVH+Tmoq7cBfu0l/hoLW0ltZev24jG9lo+HAuHIwx/cy168zTHZ/u+HTHtm3v/O7O7+787vBh+Aw+g89CQkTnsttCWAgLv/rKMNAw0DAwIuKU3ym/U37/8z+iYzHGxOMC0iIQ5Xw5X85PTYU5MAfmjBkjOlHTnD1re8j2kO2hkSNL8kvyS/IrK0UnYow5Di4gzcw4xTjFOGXiRPqCvqAvdu0SncduBARUVGQ4YDhgOBAVpY04rl0THYsx5nh4DaSZUSfqRJ3+8z9F57DbOlgH69LS3FLdUt1ShwzhwsEY+zlcQJqJMd2YbkwPCIBcyIXcfv1E57lvH8FH8NGWLV4DvAZ4DYiJ4a1GGGP3ix/jbSbUg3pQj759Reewj9ms9FJ6Kb1ee+3774JEJ2KM6QePQJoJlVIplXboIDrHj/q+Y1w7Y/yNNxRFURRl3rzv03PhYIzZjQtIMyEjGcnoeGsGdIpO0an6ejyP5/H8jBmWSkulpXL1atG5GGP65yI6gLN4rPyx8sfK6+qomqqpuvGdvUDfd4wbrAarwRoTY1lvWW9Zv2eP6FiMMefBI5BmUpxWnFacdukSLIElsOTMGVE5qJAKqfDaNfqKvqKvfvtby8uWly0vZ2aK/vkwxpwPF5BmRlfoCl1ZvlzMV7dawRd8wXfoUPWQekg9VFgo+ufBGHNe3EjYIhCly9Jl6fK+fRiFURgVHd1iXyoMwiDsiy9wOS7H5RERljaWNpY2FRWifwKMMefHI5AWQeT5pOeTnk8+/zy8C+/Cu7m5zf4lvv+8hgWGBYYFYWFcOBhjDxovoreQitqK2orae/d8Pvf53Ofzjz4CBAR0c6NiKqZiWcbNuBk3u7nd7+fT/r/aWqzHeqxftQpWwkpY+cILloWWhZaFNTWiv1/GWOvDU1gPWGBoYGhgqI+Py1cuX7l8NWEC7aW9tHfIELpO1+m6v3/jf4cP48P4cEUFvUPv0DvHjtF39B19l5LCmxoyxhzF/wKeYeMy/zPC/wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0xMi0xNVQxNTo1NzoyNyswODowMKIRvi8AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMTItMTVUMTU6NTc6MjcrMDg6MDDTTAaTAAAATXRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FkbWluL2ljb24tZm9udC90bXAvaWNvbl9jazFiemEwemo5ampkY3hyL3JpZ2h0LnN2Z7O3J80AAAAASUVORK5CYII="); + background-size: contain; + } + + .icon-refresh:before { + content: " "; + display: block; + width: 16px; + height: 16px; + position: absolute; + margin: auto; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 9999; + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIEAYAAAD9yHLdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAMQpJREFUeNrt3XlcVHX3B/Bz7rCISi6IC+ijkpZpIswMyBLgluVuKm4pqWmEuG/hUpr5uFYoiuaSFrklZvroo+jPFRURZgYVxZ1K3HIXUBSGe35/XC9PWpYL8J2B8/6H1wwGn3sb5sz93u/3fAEYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOM/QUUHYCx59F0ddPVTVdXq5YXkxeTF1O3Ll7H63jdzY3eoDfojTp1UIta1FatCm/D2/C2kxPchttwu0oVyIRMyKxShVpSS2pZuTIkQzIklyuHv+Av+IudHURBFERJkvJbKlQo+IWhEAqhsgz2YA/2d+8WPP/oMXWkjtTx4UMMwAAMuH4d2kE7aHf9OoVQCIX8/jvuxJ2489o1WkJLaMmlS+AHfuB37hwmYAImnDtnNBlNRlNGhvJDiUSfX/ZygiiIgqhMmayJWROzJgYF4Xbcjtv9/akX9aJerq7QE3pCTwcHiIEYiMnMxNpYG2ufOYNTcApOOXDAcNZw1nA2KUn0cTwrLiBMKO+z3me9z9asKa+V18prtVr5tHxaPv3mmzgaR+Nod3cYCANhYMOGyr9+9VXla9myonMXFoqmaIp+8ADDMRzDz56FTtAJOh07RgmUQAkGA17Da3jNYMjrldcrr1dKyrGxx8YeG3vvnujc7I8QdbG6WF3skCFUjapRtYkTcSSOxJHVqr3Yz0tNVb6OH280Go1G43//K/oIn3rkogOwkgzR09bT1tPW3V3jrHHWOLdoIRtkg2zw84PTcBpO+/jgGByDY2rWFJ3U0tEiWkSL8vNxDa7BNSdOkAM5kMOuXTgYB+PgnTvz1uStyVuzbx8XmOKh0+q0Oq2tLW2hLbRl9WrsgB2wQ7duhf17aAWtoBWzZpncTe4m94gI0cf9JC4g7KU0oSbUhCpW1FTTVNNUa98eTGACU9u2uAf34J6WLWEuzIW5VauKzlni6UEP+txcZYju0CGoDtWh+pYt+QH5AfkB69cfxaN4FH/9VXTMkkJ3UXdRd3HBAuWKMTy8yH8hAQENH64MeUZFiT5+FRcQ9kwaN27cuHHjSpVsbW1tbW2DgxEREbt2Vb7bvLny1dZWdE721+gz+ow+S06W+kn9pH7r1+fdyruVd2vdOi4sz0f7rvZd7bs+Pvgv/Bf+KyEBjGAEIxb5+yjNp/k0PytLE6mJ1ES+9lpybHJscuzVq6LPBxcQ9hjlJqCNTbY+W5+tb98eFsEiWNS3LxyDY3CsXTvlsb296JzsJT2aHEBdqAt12bULMzADM5YsgQ/hQ/hw0yblk25enuiYlka7XLtcu3zTJozGaIzu2LG4fz85kzM5jxxpijPFmeLmzhV9PriAlHKefp5+nn4uLtgQG2LDQYOwDJbBMh99BIfgEBxycRGdjxUvOkSH6NDVq7gEl+CSFSvMx83Hzcejo49+c/Sbo99cuiQ6nyjKPY8qVchABjJcuYJe6IVeNjbFHqQNtIE2W7YYpxmnGad16CD6vEiiA7DipfwhNG6sS9Wl6lJ/+EF6KD2UHv76K6ZgCqZMmcKFo3RDX/RF3+rVYQWsgBXjx9uQDdlQero2XZuuTV+2zOui10Wvi6+9JjpncaMP6AP6ICBAWOFQc0RQBEXUqSP6fKj4CqSEKxizvY7X8fqkSaADHejati2usVtWwqhDX+2pPbXfsIFqU22qPW1aSl5KXkre0aOi4xUV3QPdA92Df/8b/MEf/CdMEJvmwgVlem/t2qLPC1+BlDAe8R7xHvENGypXGuvWFdzsAwCAdu24cLCXshgWw2JJUqetSv2l/lL/lBT19abfot+i3+LmJjpmYaMbdINu1K0rOgf4gi/4irsCehIXECvX5OMmHzf52NVVO087TzsvJkZzSnNKcyo1FRAQMDiYCwYrUurr69HrTa4iV5GrnDihu6O7o7sze7Y6e090zJeFC3ABLnjRhYGFiICALGe2IxcQK+OT4ZPhk+HgoNPpdDrdp5/agA3YwOnTGIMxGNO3r/oJUXROVjopK+rLlIGW0BJajh1rF2gXaBd4+rRut263bndIyKN/ZX0faHbADthRrpzoGCCDDDIXEPacPL/0/NLzy8DAXKdcp1ynlBTl2alT1Z5OovMx9pcSIRESnZ1hLIyFsd9/rxunG6cbt2+fOtQqOt4zQ0BAC3jj9gIvEHgT/0lcQCyUcqVRubJypfHdd9IZ6Yx0Zu9epWnf66+LzsfYC9kFu2BXQIDmjOaM5ozJpCMd6WjKFLU1iOh4Fo+vQNjfUWdN5Z7OPZ172mBQnv3gA76XwUoUdUGqHvSgnzwZpsJUmJqQoP9C/4X+C/6A9DTkTu7kzlcg7JHg4ODg4GCNRv0kpvwhHTiAn+An+IkFzPpgrDhMhskwWa+nS3SJLhmNWq1Wq9V+9JHoWJYGwzAMwzQa5ZH4e53CA5RW+vv6+/r7tWqlD0oflD5o9271k9jjLxDGShl1nxZERFy8WHtVe1V7deNGtWmn6HiWol5Uvah6UeKHsizmUqi00J3SndKd6tRJNskm2bR8OY7H8Ti+cmXRuUobSqIkSsrJUVYW37sHw2E4DH+Gwn0QDsLBihV5SLF4YDtsh+06dbLxt/G38U9OVu6VdOmi9OpS980oerSX9tJeRGyGzbCZ6LMCUPZh2YdlH6pDWQ8fisrBfwDFRNtH20fbZ8IELItlsey0afwG9ILCIAzCHj6kntSTep49C6thNaw+fRpDMARDTp/GbtgNu506BTNgBsy4cEFpQXHrltnb7G32vnXLYaLDRIeJt24l1kqslVgrJ+d5f/3jzSadneVj8jH5WNWqmmhNtCbaxYXqUT2q5+xMy2k5La9ZU9mBsHFj/Ba/xW8bNVKuNF9/HQxgAIOdnejTaXW8wAu87t3DbMzG7IEDDSsNKw0r164t6l+rzdJmabNMJqWAeHqKPg1mg9lgNlSqpHRTvnNHVA5+Aysij88qWbhQmQY4cKDoXJaODtABOpCeDtfgGlw7cADfw/fwvf37lfN34ICbm5ubm9vZs7GxsbGxsfn5ovM+L/V1kT83f27+3Pr1bZbYLLFZ4u5OJ+kknQwMpP20n/a3aMGz7Z4RAQF99ZVyRTJunPKkLBf2r9Fu0W7RbklJwck4GSd7eIg+bOW4nZ2V475xQ1QMHsIqZMoWra+8kt8zv2d+z9hY5dnWrUXnshjhEA7h2dlwAS7Aha1boTW0htYbN5pjzDHmmPj4ow5HHY46PL3rq9IDSPRBvLiCNumBEAiBaWnKs+rXtWuhLJSFsn/oknwOz+G5Fi0wHuMxvkUL6A29oXeHDkpBrVJF9PEIh4CAo0frknRJuqS6dW1r2NawrdGnz4teYVoLZYtjSVI2cBOXgwtIIVH/4M3VzdXN1bdsUXo7iL/UFev+faX99O7d0AJaQIvY2JwbOTdybmzYkDYlbUralOxsmAJTYIronJYnJSElISXh8mXl0cqV0AAaQIOVK9VZe+nn08+nn/f1LWhZQ0BAvXuX2sISBmEQ1qVLHuVRHu3Z4z7HfY77nI4dlS1+r1172R+P+ZiP+Tzk/CQuIC9JWejXoIHyyXrnTmgGzaCZq6voXMVN3fEOHdERHRcsKN+8fPPyzdet24f7cB8+eADTYBpME53S+j0+dHfggPpVmZUzblyF7yt8X+H7Nm0gEiIhMjQUVsJKWPnOO6XmnhsCAjZtalvHto5tnQMHlL/PNm2UK9fz50XHKyz2SfZJ9knip/GW/BdUEfFM8EzwTKhXT1otrZZW79tXavbReLT3tjLdctMmnIpTceqSJYb2hvaG9jt3io7HHlfwOh0qDZWGDh2q3IT+8MPS0gKHIimSIn//HbpBN+jWurXpmuma6dqxY8/7c3QjdSN1I48ehXiIh3h3d9HHJblJbpJbjRqit7blAvKcvDt4d/DuULeueb15vXn9vn3oh37oV6uW6FxFpWC6axZmYdaCBTZbbbbabP3qq8O9D/c+3Pv330XnY89H2RDKySn/Qv6F/AsffYRDcAgOGTWqpA99USIlUuLNm+iDPujTurVyRWJ65rsHllZAZHvZXrZ3dX18qLP48RDWM1IX/pkTzAnmhF27SmrhoGRKpmSzGebDfJi/Zk2+lC/lS599drTi0YpHK/76q+h87OUk10yumVzz5k3l0YwZDdc1XNdw3fz5DjkOOQ454eFUn+pT/YkTcSgOxaGOjqLzFhalcDg5KY9271b+ntu2NZQ1lDWUVffL+RvxEA/xljMEKLvL7rI7IiRAAvxz+iIjfAzN0qn7bdBb9Ba9tWdPiWsxogMd6IigA3SADuvWaS5rLmsuN2pkGm4abhoeEqLMM+fCUVKldU/rntY9O9v4gfED4wezZtEYGkNjGjSAYAiG4KVLCz5QlCgVKtBaWktrt29X7pE0b/6P/0kgBEIgkejkKvvR9qPtR4svaMIDWCp1Ixw7WztbO9uEBOUSv0ED0bkKjT/4g/+5c8rK6o8+Ui7p9+wRHYtZFrXtunRdui5dX7oUp+N0nO7nJzpXYVGHaKVvpW+lb7t2NXxk+Mjw0bZtT/47pdCo904aNxadW5l1V7u2Mi38wgVRMfgK5AnqSmPb8bbjbcevW1dSCof6SZKaUTNqNnu27VjbsbZj3d25cLC/cyTwSOCRwLQ0U1dTV1PXgAByJmdyHjlS+e79+6LzvSz0Rm/0dnAgIxnJuHGjsrPne++p31c6SAQEwAgYASMsYEfCR+Tecm+5N1+BWBztae1p7emoKOyNvbH30KGi8xQOkwnLYTksN3CgId4Qb4hXN6Ri7MUon8hffVV5tHSp8vUZhoIsXMGQ3VbYCluPH7eYledPUFqZ1K0reoiZC8gjavtotQuo6DwvTL2nYQADGL7+uryxvLG8MSJCWY9R0saymWVA1LvoXfQuI0bIF+WL8sXZs5UmlZazb0VJo3HRuGhc3NySNidtTtr8yy+icpT6ISx9qj5VnxoUpBSOBQtE53lRdJgO0+HMTPkr+Sv5q27dlLHRMWO4cLCiR2S4bLhsuBwZSV/T1/R1y5Z0iA7RIXHrE1jxKLUFRNlfoE4dpVvr+vXKs+L767+Y1FTNVc1VzVUvrxTHFMcUxw0bRCdipVPKmJQxKWPi45V7bTodTaAJNOEZpsmy55IXlBeUF1T4TSOfV6krIGovIRu9jd5G/8MPVruAahksg2U//qg88PFR5vefOSM6FmMA/+vl9SD/Qf6D/ObN6Uf6kX785hvRuUoKzWDNYM1g8QWk1I1Rnrc/b3/e/pNPlGaHb70lOs9z2wSbYFN0tLGmsaax5rBhypPiX0iM/RVlnUlurvIoLEz3ve573fe//gpREAVRM2eKzmet8lvlt8pvJX47g1JzBaIP1AfqAz09ldlIkyeLzvO8aAWtoBWzZimFY8gQ5VkuHMy6FCxYnEbTaFp4OIRCKITy6/h52bjauNq4ij9vJb6AKF1K7e3pHt2je99/by07wdEiWkSL8vPhM/gMPgsLM7mb3E3uERGiczFWGExtTG1MbRYuhMWwGBb37as8m5cnOpe1eOj90PuhNxeQIlehZ4WeFXqql8oWsIL0n6ifyE7BKTj1wQfGTsZOxk48dsxKJmUh6+rVFEIhFNKjR8EHJ/a3bNfYrrFdwwWkyHh+6fml55eBgbARNsJG9V6B5aOVtJJWDhtmCjGFmEJWrRKdh7HioPRe+/lnyIRMyBw9WnQeS2e7yXaT7SYuIIVOnWUl1ZfqS/WjopRLZPEbr/wT8iRP8pwyxRRvijfFR0eLzsOYCKZWplamVvPm0WbaTJvV6fXsSXmd8jrldRJ/pWbxb6zPKz09PT09/aOPYCpMhalNmojO848ezaoyLTMtMy37/HPRcRizBPI5+Zx8bvBg8AEf8Ll+XXQeS1PmtzK/lfmNr0AKjU+GT4ZPRuXKysYxX3whOs8/WgSLYNGGDY9Px2WMAahNHK9fV3b6DA8XncfSZEVkRWRFcAEpNHmYh3k4derjG8dYHppBM2jGmTOaSppKmkr9+yvPin8hMGaJlJY8sbE8pPW4SmMqjak0hoewXpq+j76Pvs+bb5ILuZBLaKjoPE8VDuEQnp0tl5HLyGXeey+pflL9pPqZmaJjMWYNzKvMq8yrwsOVfTBu3BCdR7RsXbYuWye+gFj9SnQ6SSfp5FdfWXr3TzKTmcwffqjuryA6D2N/5BXsFewVXL268qh6dfm8fF4+b2+PU3EqTnV0pMk0mSaXL6+8gf9Fz7gFsAAW2NjQEBpCQ/6wFe7H8DF8fOcOfoPf4DfPsKMfAgLev4+f4+f4+cOHT36belJP6rluHfwIP8KPgweLPm/F7lG3beMS4xLjEvHrZqy2nbtOq9PqtE2bKi+4xETReZ6G3qF36J3ISNN003TT9FGjROdhpZPSPLRiRRudjc5GFxKi/N107qxcGXt5QTREQ3T58qJzsn+gBz3oc3ONi42LjYvt7UXHsdohLNpKW2nr+PGiczzVRJgIE9PSMqtnVs+sbsE5WYmm3abdpt02eLDmoOag5uD580rhmDdP+W7z5lw4rExTaApN1d5i4lnskM/TFNzz+Iw+o886dhSd508erSSXt8vb5e0DB55bfG7xucV/vhRnrCjpZutm62ZHR8MkmASTSuFQT0mlAQ1oxA9dqazuCoReo9fotYgIMIIRjOL3BP6TztAZOkdFpSxOWZyy+NAh0XFY6aIM7Q4bVmrvEZRwVJfqUl3LuQKxmgKi36Lfot/i5kaTaBJN6tFDdJ4/GQtjYeyvv+bszdmbs/fTT0XHYaWLUjgqVFCGOHhBaollYUNYVlNA5GPyMfnYuHEWO9tqNsyG2aGhyv4H2dmi47DShcpTeSrfpw8kQRIkVawoOg8rIjLIIPMQ1jPzPut91vvsK6/gT/gT/qS2fbY0O3YoC5527BCdhJVO2AybYbOWLUXnYEWMgIC4gDwzcw9zD3OPnj2VR2XLis5T4LGNcHiWFRNMBzrQubmJjsGKFgZgAAbwENYzwxbYAluoLT8syFW4ClfXrFH2MzCZRMdhpRu1ptbU+g8L+FgJxlcg/8gj3iPeI75hQ9gDe2CPj4/oPAXCIAzCHj7UJGuSNcl8s5xZBpyEk3DS7duic7Ai1hyaQ3O+AvlHmhRNiibFAq88FsEiWLR8edLmpM1Jm3/5RXQcxgAAoA/0gT7nzomOwYrYHtgDe65eFR1DZXEFRJmOaGurbLBkQTfNH93zkDZJm6RNc+eKjsPYH+FwHI7DeRJHiXcQDsJBy+mlZ3EFRF4vr5fXv/sujsSROLJaNdF5CiyGxbB427bkmsk1k2ueOSM6DmN/ZH/C/oT9idhYZT+cmzdF52FFQ+or9ZX6xsaKzlGQR3SAPwXqJnWTullgi5JdsAt2qT2EGLMsB28evHnwZlYWtISW0HLyZNF5WFHYuDE5Njk2OfbIEdFJVBZWQBBhNIyG0W3aiE6iomk0jaadOGGsaKxorLhzp+g8jP0dU7wp3hQfHU0hFEIhP/wgOg97Sf7gD/7nzklukpvkFhYmOs6TLKaA6AP1gfpADw/4Gr6Gr11dRedRKbNboqOVR8+wnwFjFsA03DTcNLxfP+XRzJm0iBbRIvEbELFn1BJaQsv9+8255lxzbrNmypWH5dw8V1lMAVFaMLRtKzrG4/LylJWfljPmyNizk2VlndL48VKUFCVFeXjQJtpEm9asocN0mA7zjpjCPdogSpmeq+5r9P77xtnG2cbZzZod/eboN0e/uXRJdMynsZhuttqftD9pfzp4EKfjdJzu5yc6D0RCJETGxRkDjYHGQMsZUmOsMDRc13Bdw3V2duViy8WWi23Y0DzLPMs8q1YtTT9NP00/Z2c5W86Ws//ccw5H42gcXb48mMEM5r/YmTAKoiCqXDlaQStohZ3d8+bCnbgTd5Ypo3S1dnB40eOjZbSMlt27Bz2hJ/QshHUTs2E2zM7MxLfxbXz7+a/kcASOwBFEspPsJDtdvy6Nk8ZJ465exbbYFtsmJSmTc6xv8oPwAuKT4ZPhk1G5cu6V3Cu5V65dwzAMwzCNRnQumANzYM4HHxhbGFsYW8TEiI7DGGOWRnhXW/N483jz+Nat8SSexJPiCwdFUzRFP3hg42TjZOO0caPoPIwxZqmE3wMhLWlJazmtSjAcwzE8Li6pflL9pPo8RswYY08jvIDAG/AGvOHtLTqGSpm2+3//JzoHY4xZOmEFJIiCKIhsbJQuoh4eok+Eit6it+itPXtE52CMMUsn7B7I/e73u9/v/uabmI7pmP7isy0KzQgYASOuXUtxTHFMcTx1SnQcxhizdMKuQPL75PfJ7+PlJfoEqCiLsihr9+5Hj3jBIGOM/QNx90BOwAk4odOJPgEFJ2KptFRaunev6ByMMWYthBUQvIk38aZeL/oEqEgiiaTkZNE5GGPMWggrIDSLZtGs+vVFn4DH9zbnex+MMfasir2AqCvPsSk2xaavvCL6BMBxOA7H09OVnkH374uOwxhj1qLYC4j5ffP75vdr1xZ94AUOwkE4ePy46BiMMWZtir2AyF3lrnLXOnVEH7iKfMmXfE+cEJ2DMcasTfHfA2kEjaDRv/4l+sALTsCv0q/Sr6dPi87BGGPWptgLCLqjO7pb0BXISlpJKy1voxbGGLN0xX8F0gbaQBvLKSDSIGmQNOj6ddE5GGPM2hR/ASEgoBo1RB+4StnA6sYN0TkYY8zaFHsBoZk0k2ZWqiT6wFXZKdkp2SnXronOwRhj1qb4r0DKQBkoU6GC6ANX3L2b1j2te1r3QtjykjHGSpniLyB2YAd2llJAeOEgY4y9qOKfhbUcl+Nye3vRBw6+4Au+3HWXMcZeVLEVkODg4ODgYI0GjGAEI6LoA+cCwhhjL6fYCkhKQEpASoCNsA2sGGOMFa5iKyB21e2q21XnT/yMMVZSFFsBUWY75eWBDnSgs4BCcggOwSELGEpjjDErVcw30YnAG7zBW/y0WepDfahP5cqiczDGmLUq/mm8RjCCMSdH9IFjOIZjeJky/k7+Tv5Ojo6i8zDGmLUp/gISBEEQdOeO6ANXPajzoM6DOlWris7BGGPWpvgLyByYA3Nu3RJ94CpyJmdydnYWnYMxxqxN8ffC2k7bafvNm6IPvOAE+Ev+kj9fgTDG2PMq/pXoC3EhLrSc5oWyXtbL+po1RedgjDFrU/xDWJWhMlS+cEH0gauwMTbGxg0bis7BGGPWpvgLyApYASsyMkQfuIrKUlkq26iR6ByMMWZtir+AAACABV2BfIqf4qdcQBhj7HkVewGR58vz5fmnT4s+8AKJkAiJzs4e8R7xHvE8G4sxxp5VsReQepH1IutF/vILRVM0RT94IPoEFJyIddI6aV3jxqJzMMaYtdAU9y9MS0tLS0sjcnF0cXRx7N4dfoPf4Ldq1USfCGm7tF3afvbsZfNl82Xz/v2i8zDGmKUTdA8EAKpAFahiMok+ASoaQSNoRIsWonMwxpi1EFZA6Cf6iX46dEj0CSjI05k6U2c/P58MnwyfDAcH0XkYY8zSCdvgCQEBwXIKiNpcMdc31zfX19dXeXb3btG5GGPMUgm7AjGajCaj6cQJ5dHdu6JPRIEBMAAGNG8uOgZjjFk6cfdAAABAlpWvhw+LPhEFMiADMt55R3QMxhizdIILCAAYwAAGCxrKmopTcaqXl8cwj2Eew+rXF52HMcYslfgCchfuwt2DB0XHeJLGXeOuce/RQ3QOxhizVMILyN2YuzF3Y+Lj6TAdpsOZmaLzFFgIC2Hh+++LjsEYY5aq2BcSPunWtlvbbm3Lz3eRXCQXydMTzsAZOGMBvakQELBKlZpv1Xyr5lubNl1Ou5x2Oe3qVdGxGGPMUgi/AinQGlpD640bRcd4krxUXiov7d1bdA7GGLM0llNAhsAQGLJ1K+hBD/rcXNFxVHScjtPxDz90n+M+x31OuXKi8zDGmKUQPoSlunLlypUrVx4+dIl0iXSJ9PeH7bAdtterJzoXxmEcxjk4SD2lnlLPS5eurLqy6sqq5GTRuRhjRcfrotdFr4uvvVa9SvUq1av4+ro2c23m2qxBg2oPqz2s9tDRMcAnwCfA5/ff1d5+ovOKImwl+tPgcByOwzduJIkkkt59V3SeglzZmI3ZI0Yoj775RvmqrmNhjFmj4ODg4OBgjSb9fPr59PP9+9NMmkkzx46VO8md5E6vvaZ0zAAgICAAkEACCQDS09PT09Pv3tVO107XTl+7Vr4qX5WvfvXVkagjUUeizp4VfVzFBUUHeJIyVFS1qu0523O25zIylHUidnaic6kohEIopEsX03DTcNPwn38WnYcx9vx0Wp1Wp61ShSIogiLWr8dZOAtnBQW93E/Ny4NBMAgGzZgBS2AJLJk2Tem4kZcn+niLisUVEJUuRZeiS1m7FgbCQBhoOesxaBftol0HD5oqmiqaKr71lug8jLFnpwxNOTnJHeWOcscDB5TZlg0aFPovagNtoM2WLXer3q16t2q3bueGnRt2btjDh6KPv7BZzk30J3mAB3ioQ0WWA1tiS2zp76+7qLuou9i2reg8jLF/pg5VyWlympy2YUORFQ7VNtgG29q3f6XtK21faTt3rujjLyoWW0CMaEQj7tsHARAAASdPis7zJEqlVEqdPVt9YYrOwxh7uvT26e3T248ZA+NhPIwPDCyu34uzcTbODg319PP08/TT60Wfh8Jm8W98NSrWqFijoq0t3sf7eN+Cbqrvxt24u2rVW7du3bp169IlZRaZ0Sg6F2Psf7wWeC3wWtCokTIpZ80a5Z6qTfFNHroCV+AKIprRjGZJUt4nNm8WfV4Ki8VegajyLuVdyrv0/feUREmUlJMjOs+fzIW5MHfqVH8nfyd/J0dH0XEYYwBBFERBZGMj15HryHW++w4WwSJYZG8vNlXJu2dq8QUkNTU1NTX19m2IhEiIXLNGdJ4n4UgciSOrVXtw6cGlB5ciIkTnYYwBZK/OXp29etgwmAyTYbL4oSNKpmRKrl1bdI7CZvEFRCVfkC/IF2bOVP5HmM2i8/yJP/iD/9ix+kB9oD7Q01N0HMZKoybUhJpQnTqwH/bD/qlTRecpkAzJkIwWO+v1RVlNAVEX6OAMnIEzVq4Uneev2dqSjnSk++67husarmu4znLWrzBW8iHa7LfZb7N/0SLlDdtyWg/halyNqy9eFJ2jsFlNASmwATbAhmnTlAcWuEAnHuIh3t29TL0y9crU+/RT0XEYKw309fX19fXDw2EkjISRljPZpkAf6AN9jh0THaOwWV0BMRqNRqPx/HnqRb2o1w8/iM7zVB7gAR4REV51vep61fXyEh2HsZJI30ffR9/nzTflU/Ip+dTs2aLzPA2GYiiG7tghOkdhs7oCorLZZ7PPZp/lXomgF3qhl41N/on8E/knfvjB+6z3We+zr7wiOhdjJYHaHZvSKI3SYmPRG73R28FBdK4/CYMwCHv40DzPPM88b8MG0XEKm9UWkKTNSZuTNv/yC8RCLMSuWCE6z9NgAAZgwOuvmx3NjmbHmJhHz5a4m2mMFSebXja9bHotXVrkK8pfEt2je3Rv3bojgUcCjwRevy46T2Gz2gKiyvsp76e8nz79FIbBMBh2+7boPE+D7bAdtuvUSZukTdIm8b0Rxl6EvpK+kr7SuHHYCTthp169ROd5GlpEi2hRfj4NoAE0YMYM0XmKSon5JKzT6XQ6XViY8mjhQtF5nioUQiFUlukG3aAbnTqZJpgmmCZs2SI6FmOWTDtBO0E74Z13oDN0hs7//S+GYRiGWXALIQICWrZM6cY7aJDoOEWlxBQQhSRpN2k3aTclJuJUnIpTLf3m9d27+Aa+gW+89ZZhpWGlYeXx46ITMWZJ1FYksqPsKDvu3w9REAVRlSqJzvU0lEiJlHjzJjbFpti0QQOlgNy4ITpXUbH6IazHyTJ8Dp/D52Fh6iWk6ER/r0IFeofeoXd27dJ/of9C/8Xrr4tOxJglaPJxk4+bfOzqKq+QV8grtm619MJRYCtsha3jx5f0wqGy3EvAF3TlkRquNVxruDo74xk8g2e8vUXneqpESITEcuWoP/Wn/u3aVS1btWzVsuvX/2743fC7IStLdDzGipNPhk+GT0blyuAADuCwZ4+yolz81tb/hCbQBJqQkGB6z/Se6b2hQx89W+K3ui1xBUTlkumS6ZKZkAB+4Ad+ISFwES7CRcttdog7cSfurFRJ6i/1l/q/+67LWZezLmfXrVPK4f37ovMxVpSUHQIrVJCvydfka9u2QQzEQIzltwRSm7xiCIZgSLt2yt9ryZtt9TQlbAjrf5RLyLt35SA5SA4KCVFvXovO9Y/+Df+GfzdsqExP3L7dI94j3iPe2Vl0LMaKglo4oAW0gBZxcbAH9sAeHx/RuZ7ZQTgIBz/7TFngfOqU6DjFrcQWEFVKcEpwSvCuXeRDPuQzZ47oPM9Hq5UeSA+kBwcOeHfw7uDdoW5d0YkYKwwFhQMAALZvt7bCoW5t/er8V+e/Oj8yUnQeUUrYLKynU/cHyI7LjsuOi4+HSTAJJvn6is71rOgQHaJDV69KraRWUqu2bQ3xhnhDfEqK6FyMPQ9lun2NGsojdfq6Vis61zPzBm/wvnPHvNC80LzQ0/MoHsWj+OuvomOJUuKvQFT7cB/uQ7MZ8zEf8/v0ocN0mA5nZorO9azQF33Rt3p16kf9qF98vO6O7o7uzttvi87F2LPwzPLM8sx64w3lnuShQ8qzVlQ4HsEszMKssLDSXjhUJfYm+tNcXn159eXVt2/XqFejXo16GRl4GA/j4S5dROd6ZsmQDMl2dpAGaZDWo0eNcjXK1Sh3+/aV3678duW35GTR8Rj7Ix3pSEfNmuFaXItrd+yA9bAe1levLjrXi1m0yLjduN24fdYs0UksRakZwnoa5ZJaXbmurmS3VqtX53yS80nOJ6Ghad3Tuqd1z84WnYiVTrpVulW6VaNGKV2zZ81Sm4uKzvW81Om5D/If5D/Ib95c+bvKzRWdy1KUmiGspylvKG8obxg2DN6Bd+Cd7dtF53k5vXs72DjYONgcPlwwZMBYMVA2UCtfXpeiS9GlrF0LX8PX8PVXX1lr4QBf8AXfy5dxOk7H6d26ceH4a6W+gKj3RjT9Nf01/bt3p320j/ZZcUuRR9OApVgpVopNStJqtVqt9qOPlG9yF2BWuLTvat/Vvuvj44AO6IAmEwyEgTCwRw/RuV6Uuq4DEiABErp0UabnXrkiOpel4jeUJ6gtFGwCbAJsAg4fVj5JubqKzlU4DhzAztgZOw8caPjU8Knh09OnRSdi1qVgNmNMdkx2zOjRSouRL75QvmtrKzrfC3u0TkzuJfeSewUHpzimOKY4lrz9OwobF5CnUHcSlCvLleXKe/cqz5YtKzrXyyr4hPVoAZQ6jz02NjY2NtbSe4cxUTxDPUM9Q319sQN2wA4LF+JknIyTPTxE5yoseAWv4JVRowyXDZcNl0vvuo7nxQXkH+hO6U7pTnXqBO/D+/B+bKzyrBV/0noC7aW9tDclheIojuLGjlUXXorOxcTyuuh10euik1N+bn5ufu6sWTgTZ+LMAQPACEYwlqCh0P7QH/rPmGEcYhxiHDJhgug41qbkvBCKmH6Yfph+WNeudJAO0sE1a5RnS04hedyOHVgOy2G5iAhesFg6KLMRy5ZVNmYbOpRepVfp1XHjcDgOx+GVK4vOV9ioA3WgDgsWmKaYppimqM0P2fPiAvKclNlNXbpIzaRmUrO1a5VnS2AhUXuHLYbFsHjtWnm+PF+eP3lyil+KX4rfuXOi47GXUy+qXlS9KHv7ivMrzq84f9Ag+YR8Qj4xcaK6YFV0vqJCsRRLsd9+a3IzuZnc1I2eSn7X3KJS6mdhPa/Hb6699x6EQRiEPXwoOlehWwyLYbH06PXRu7d0XDouHT99Wrtau1q7+v/+zzPdM90zvUMH5fslaEijhFJ7T2l3andqdw4fXsGpglMFp/Pn6RV6hV6ZP7+kFw6IhViIXbJEKRzqrEQuHC+L//BfknLp37kz6EEP+h9/BAMYwGBnJzpXcaHP6XP6/MgRuA/34f68eZlXM69mXl2z5tywc8PODSuBhdVKKAWjaVNl5feAARAMwRDcp4/yXeufDPLMtsE22DZ3rrGqsaqx6qhRypNcOAoLF5BCohSSdu0gHMIhfO1aiIZoiC5fXnSuYjcMhsGw27fpOl2n6z//jANxIA5cu9ZtkNsgt0G7d/Nsr8Klv6+/r79fq5ZskA2yoUcPvIE38Eb//gXbApQ2j4ZeqQE1oAaffGIKNAWaAr/8UnSskooLSCHzCvYK9gr28MgfnD84f/DmzTgGx+CYmjVF5xKNIimSIn//Hd3QDd3WrwdXcAXX9etzYnNic2ITEnil79/TVtVW1VZ1d1dWRnfsCCfhJJzs3BmyIAuytNoSNzvqhdy/L++V98p7+/bldRzFo5S/4IqOp5+nn6efi4s0QZogTdi0CSbDZJis14vOZZnu36fRNJpGJyRIA6QB0oC9e+EW3IJbe/aUcyjnUM4hKUntGCA6aWFTF+Zl2mXaZdo1aiStllZLqwMDyZ/8yT8wEHfhLtwVGAhzYS7MrVpVdF5Lo25zoHld87rm9Y4dk39J/iX5F24qWly4gBQxdXokhVIohcbE4GJcjIu7dhWdy2p4gRd43btHs2gWzTpxQlnwdeKE0uTu5EnpXeld6d3jx/MG5Q3KG3TypNJm+7fflP+4+Me63ee4z3GfU66c3VG7o3ZH69bNn5o/NX9q3bo4GAfj4FdfhVbQClo1boxrcA2u8fBQJmE0agSLYBEssrcXfbqtS2oqEBBQ+/bKDqQXLohOVNpwASlWiLoFugW6Bf/+NxyDY3AsIoKHHgoXJVMyJZvNYAYzmG/cgFzIhdz/fcUojMKoa9cgBEIg5M6dZ/65QECg0WAwBmOwkxO0hJbQ0slJ+blVqkAe5EFelSqQCImQyFsQF5l20A7a/fyzpq+mr6Zvv35J9ZPqJ9W3nn19Shp+4xKkYEOoltASWn7/vfKsulMbYwwAgKIpmqIfPIBsyIbsiAhTK1MrU6t580TnYgouIIJ5xHvEe8Q7O2t2aHZodixfrkw7bN9edC7GRKJpNI2mnTiBE3EiTuzVSxmiSk0VnYs9jguIRUFU2q8PGoSIiKg2dStF8/ZZ6aQDHeiIIAIiIGLpUltbW1tb2xEjEmsl1kqslZMjOh77a1xALJQ6bRPSIR3SV63CIAzCoDffFJ2LsULlB37g99tvShv1jz9WWuXExYmOxZ4NtzKxUKZrpmuma8eOYSAGYqBWq8xCGjGCDtNhOsw3DZk1y8tTvkZF5QTkBOQEvPkmFw7rxFcgVkZdX4I9sAf2mDkTT+AJPNGnD8/mYpZvz578yPzI/MghQ44EHgk8EpiWJjoRezn8hmPl9Kn6VH1qUBD1o37Ub/585dnGjUXnYqXcoz3FoQt0gS7jxxtbGFsYW8TEiI7FChcPYVk5Q2NDY0PjffuUhQo6nTrUBT7gAz7Xr4vOx0oHdUU4jIJRMGr0aDgEh+BQ/fpcOEo2vgIpodQV0TaeNp42ngMHKiu4J0zglhisUIyAETDi2jWQQQb566+VvdHnzzcajUaj8f590fFY8eACUkp4n/U+6332lVfMn5g/MX8SGoou6IIuI0YonxRdXETnYxZuFIyCUZcugR3Ygd2sWeW7le9WvtvSpUqPsgcPRMdjYvAQVimhtnwwbTBtMG2YMycnMCcwJ7BuXWXr2g8/LNjXgzEAUDok7N+PNbAG1ggJuXvz7s27N1991RhsDDYGz5/PhYMB8BUIe4JnqGeoZ6ivLzbEhtgwLAyaQlNoGhyM4RiO4WXKiM7HChfNo3k079YtfA1fw9diYmQH2UF2WLJEaYd+8qTofMyycQFhf6sJNaEmVLGiTZxNnE1c166URVmU1bcv3sE7eCcg4PGtb5lly8tT2ubv26c0m/zuO8e+jn0d+/70E19RsBfBBYS9EHUnPPov/Zf+27mzsg6lc2eaTtNpemAgeqEXetnYiM5ZOt29C8tgGSyLi4McyIGcTZtyQ3NDc0Pj4lJTU1NTU2/fFp2QlQxcQFih8snwyfDJqFw51y3XLdft7bexMTbGxq1awTgYB+NatYI5MAfm1KkjOqfVerRlK1SBKlDl1Ck6Rsfo2O7dOAJH4Ij//CdnR86OnB379vEOj6w4cAFhxUq/Rb9Fv8XNTR4gD5AH+PmhCU1o8vGBTtAJOvn6Kv9KXQhpays6b7FT95RHQsLERGgADaBBYiJshI2w8fBhjMM4jEtMVLrT3r0rOi4r3biAMIui0+q0Oq2tLV2ki3TxjTfgS/gSvmzcGDMxEzMbNYIgCIKgWrXgB/gBfqhdW5k95uqKq3AVrnJ1tZid/fSgB31urrID4W+/QTWoBtXOnwc3cAO38+dhH+yDfenpShfa8+el8lJ5qfzJk8k1k2sm1zx7Vvkhxb+jImPPgwsIK1G8gr2CvYKrVzdfMl8yX6pZU1ouLZeWu7pCb+gNve3sKIIiKKJcOZgJM2GmnZ2UJWVJWXZ2NIkm0aRy5ZQFcYjkS77kW768ci8nK0uZrXT7Ni7ABbggKwuGwlAYmpmpdJHNytL8R/MfzX+ysiAO4iDuxo26H9T9oO4Hly/HxsbGxsbm54s+L4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYKzb/D4DEm9oGCaFQAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTEyLTE1VDE1OjU3OjI3KzA4OjAwohG+LwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0xMi0xNVQxNTo1NzoyNyswODowMNNMBpMAAABPdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWRtaW4vaWNvbi1mb250L3RtcC9pY29uX2NrMWJ6YTB6ajlqamRjeHIvcmVmcmVzaC5zdmejF0ikAAAAAElFTkSuQmCC"); + background-size: contain; + } +</style> diff --git a/_web/src/components/verifition/Verify/VerifyPoints.vue b/_web/src/components/verifition/Verify/VerifyPoints.vue new file mode 100644 index 0000000..da6c042 --- /dev/null +++ b/_web/src/components/verifition/Verify/VerifyPoints.vue @@ -0,0 +1,260 @@ +/* eslint-disable no-unused-vars */ +<template> + <div + style="position: relative" + > + <div class="verify-img-out"> + <div + :style="{'width': setSize.imgWidth, + 'height': setSize.imgHeight, + 'background-size' : setSize.imgWidth + ' '+ setSize.imgHeight, + 'margin-bottom': vSpace + 'px'}" + class="verify-img-panel" + > + <div @click="refresh" class="verify-refresh" style="z-index:3" v-show="showRefresh"> + <i class="iconfont icon-refresh"></i> + </div> + <img + :src="'data:image/png;base64,'+pointBackImgBase" + @click="bindingClick?canvasClick($event):undefined" + alt="" + ref="canvas" + style="width:100%;height:100%;display:block"> + + <div + :key="index" + :style="{ + 'background-color':'#1abd6c', + color:'#fff', + 'z-index':9999, + width:'20px', + height:'20px', + 'text-align':'center', + 'line-height':'20px', + 'border-radius': '50%', + position:'absolute', + top:parseInt(tempPoint.y-10) + 'px', + left:parseInt(tempPoint.x-10) + 'px' + }" + class="point-area" + v-for="(tempPoint, index) in tempPoints"> + {{ index + 1 }} + </div> + </div> + </div> + <!-- 'height': this.barSize.height, --> + <div + :style="{'width': setSize.imgWidth, + 'color': this.barAreaColor, + 'border-color': this.barAreaBorderColor, + 'line-height':this.barSize.height}" + class="verify-bar-area"> + <span class="verify-msg">{{ text }}</span> + </div> + </div> +</template> +<script type="text/babel"> + /** + * VerifyPoints + * @description 点选 + * */ + import { resetSize } from './../utils/util' + import { aesEncrypt } from './../utils/ase' + import { reqGet, reqCheck } from '@/api/modular/system/loginManage' + + export default { + name: 'VerifyPoints', + props: { + // 弹出式pop,固定fixed + mode: { + type: String, + default: 'fixed' + }, + // eslint-disable-next-line vue/require-default-prop + captchaType: { + type: String + }, + // 间隔 + vSpace: { + type: Number, + default: 5 + }, + imgSize: { + type: Object, + default() { + return { + width: '310px', + height: '155px' + } + } + }, + barSize: { + type: Object, + default() { + return { + width: '310px', + height: '40px' + } + } + } + }, + data() { + return { + secretKey: '', // 后端返回的ase加密秘钥 + checkNum: 3, // 默认需要点击的字数 + fontPos: [], // 选中的坐标信息 + checkPosArr: [], // 用户点击的坐标 + num: 1, // 点击的记数 + pointBackImgBase: '', // 后端获取到的背景图片 + poinTextList: [], // 后端返回的点击字体顺序 + backToken: '', // 后端返回的token值 + setSize: { + imgHeight: 0, + imgWidth: 0, + barHeight: 0, + barWidth: 0 + }, + tempPoints: [], + text: '', + barAreaColor: undefined, + barAreaBorderColor: undefined, + showRefresh: true, + bindingClick: true + } + }, + computed: { + resetSize() { + return resetSize + } + }, + methods: { + init() { + // 加载页面 + this.fontPos.splice(0, this.fontPos.length) + this.checkPosArr.splice(0, this.checkPosArr.length) + this.num = 1 + this.getPictrue() + this.$nextTick(() => { + this.setSize = this.resetSize(this) // 重新设置宽度高度 + this.$parent.$emit('ready', this) + }) + }, + canvasClick(e) { + this.checkPosArr.push(this.getMousePos(this.$refs.canvas, e)) + // eslint-disable-next-line eqeqeq + if (this.num == this.checkNum) { + this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e)) + // 按比例转换坐标值 + this.checkPosArr = this.pointTransfrom(this.checkPosArr, this.setSize) + // 等创建坐标执行完 + setTimeout(() => { + // var flag = this.comparePos(this.fontPos, this.checkPosArr); + // 发送后端请求 + var captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify(this.checkPosArr), this.secretKey) : this.backToken + '---' + JSON.stringify(this.checkPosArr) + const data = { + captchaType: this.captchaType, + 'pointJson': this.secretKey ? aesEncrypt(JSON.stringify(this.checkPosArr), this.secretKey) : JSON.stringify(this.checkPosArr), + 'token': this.backToken + } + reqCheck(data).then(res => { + // eslint-disable-next-line eqeqeq + if (res.repCode == '0000') { + this.barAreaColor = '#4cae4c' + this.barAreaBorderColor = '#5cb85c' + this.text = '验证成功' + this.bindingClick = false + // eslint-disable-next-line eqeqeq + if (this.mode == 'pop') { + setTimeout(() => { + this.$parent.clickShow = false + this.refresh() + }, 1500) + } + this.$parent.$emit('success', { captchaVerification }) + } else { + this.$parent.$emit('error', this) + this.barAreaColor = '#d9534f' + this.barAreaBorderColor = '#d9534f' + this.text = '验证失败' + setTimeout(() => { + this.refresh() + }, 700) + } + }) + }, 400) + } + if (this.num < this.checkNum) { + this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e)) + } + }, + + // 获取坐标 + getMousePos: function (obj, e) { + var x = e.offsetX + var y = e.offsetY + return { x, y } + }, + // 创建坐标点 + createPoint: function (pos) { + this.tempPoints.push(Object.assign({}, pos)) + return ++this.num + }, + refresh: function () { + this.tempPoints.splice(0, this.tempPoints.length) + this.barAreaColor = '#000' + this.barAreaBorderColor = '#ddd' + this.bindingClick = true + this.fontPos.splice(0, this.fontPos.length) + this.checkPosArr.splice(0, this.checkPosArr.length) + this.num = 1 + this.getPictrue() + this.text = '验证失败' + this.showRefresh = true + }, + + // 请求背景图片和验证图片 + getPictrue() { + const data = { + captchaType: this.captchaType + } + reqGet(data).then(res => { + // eslint-disable-next-line eqeqeq + if (res.repCode == '0000') { + this.pointBackImgBase = res.repData.originalImageBase64 + this.backToken = res.repData.token + this.secretKey = res.repData.secretKey + this.poinTextList = res.repData.wordList + this.text = '请依次点击【' + this.poinTextList.join(',') + '】' + } else { + this.text = res.repMsg + } + }) + }, + // 坐标转换函数 + pointTransfrom(pointArr, imgSize) { + var newPointArr = pointArr.map(p => { + const x = Math.round(310 * p.x / parseInt(imgSize.imgWidth)) + const y = Math.round(155 * p.y / parseInt(imgSize.imgHeight)) + return { x, y } + }) + // console.log(newPointArr,"newPointArr"); + return newPointArr + } + }, + watch: { + // type变化则全面刷新 + type: { + immediate: true, + handler() { + this.init() + } + } + }, + mounted() { + // 禁止拖拽 + this.$el.onselectstart = function () { + return false + } + } + } +</script> diff --git a/_web/src/components/verifition/Verify/VerifySlide.vue b/_web/src/components/verifition/Verify/VerifySlide.vue new file mode 100644 index 0000000..8e91a5e --- /dev/null +++ b/_web/src/components/verifition/Verify/VerifySlide.vue @@ -0,0 +1,374 @@ +<template> + <div style="position: relative;"> + <div + :style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}" + class="verify-img-out" + v-if="type === '2'" + > + <div + :style="{width: setSize.imgWidth, + height: setSize.imgHeight,}" + class="verify-img-panel"> + <img :src="'data:image/png;base64,'+backImgBase" alt="" style="width:100%;height:100%;display:block"> + <div @click="refresh" class="verify-refresh" v-show="showRefresh"><i class="iconfont icon-refresh"></i> + </div> + <transition name="tips"> + <span :class="passFlag ?'suc-bg':'err-bg'" class="verify-tips" v-if="tipWords">{{ tipWords }}</span> + </transition> + </div> + </div> + <!-- 公共部分 --> + <div + :style="{width: setSize.imgWidth, + height: barSize.height, + 'line-height':barSize.height}" + class="verify-bar-area"> + <span class="verify-msg" v-text="text"></span> + <div + :style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}" + class="verify-left-bar"> + <span class="verify-msg" v-text="finishText"></span> + <div + :style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}" + @mousedown="start" + @touchstart="start" + class="verify-move-block"> + <i + :class="['verify-icon iconfont', iconClass]" + :style="{color: iconColor}"></i> + <div + :style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px', + 'height': setSize.imgHeight, + 'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px', + 'background-size': setSize.imgWidth + ' ' + setSize.imgHeight, + }" + class="verify-sub-block" + v-if="type === '2'"> + <img :src="'data:image/png;base64,'+blockBackImgBase" alt="" style="width:100%;height:100%;display:block"> + </div> + </div> + </div> + </div> + </div> +</template> +<script type="text/babel"> + /** + * VerifySlide + * @description 滑块 + * */ + import { aesEncrypt } from './../utils/ase' + import { resetSize } from './../utils/util' + import { reqGet, reqCheck } from '@/api/modular/system/loginManage' + + // "captchaType":"blockPuzzle", + export default { + name: 'VerifySlide', + props: { + // eslint-disable-next-line vue/require-default-prop + captchaType: { + type: String + }, + type: { + type: String, + default: '1' + }, + // 弹出式pop,固定fixed + mode: { + type: String, + default: 'fixed' + }, + vSpace: { + type: Number, + default: 5 + }, + explain: { + type: String, + default: '向右滑动完成验证' + }, + imgSize: { + type: Object, + default() { + return { + width: '310px', + height: '155px' + } + } + }, + blockSize: { + type: Object, + default() { + return { + width: '50px', + height: '50px' + } + } + }, + barSize: { + type: Object, + default() { + return { + width: '310px', + height: '40px' + } + } + } + }, + data() { + return { + secretKey: '', // 后端返回的加密秘钥 字段 + passFlag: '', // 是否通过的标识 + backImgBase: '', // 验证码背景图片 + blockBackImgBase: '', // 验证滑块的背景图片 + backToken: '', // 后端返回的唯一token值 + startMoveTime: '', // 移动开始的时间 + endMovetime: '', // 移动结束的时间 + tipsBackColor: '', // 提示词的背景颜色 + tipWords: '', + text: '', + finishText: '', + setSize: { + imgHeight: 0, + imgWidth: 0, + barHeight: 0, + barWidth: 0 + }, + top: 0, + left: 0, + moveBlockLeft: undefined, + leftBarWidth: undefined, + // 移动中样式 + moveBlockBackgroundColor: undefined, + leftBarBorderColor: '#ddd', + iconColor: undefined, + iconClass: 'icon-right', + status: false, // 鼠标状态 + isEnd: false, // 是够验证完成 + showRefresh: true, + transitionLeft: '', + transitionWidth: '' + } + }, + computed: { + barArea() { + return this.$el.querySelector('.verify-bar-area') + }, + resetSize() { + return resetSize + } + }, + methods: { + init() { + this.text = this.explain + this.getPictrue() + this.$nextTick(() => { + const setSize = this.resetSize(this) // 重新设置宽度高度 + for (const key in setSize) { + this.$set(this.setSize, key, setSize[key]) + } + this.$parent.$emit('ready', this) + }) + + var _this = this + + window.removeEventListener('touchmove', function (e) { + _this.move(e) + }) + window.removeEventListener('mousemove', function (e) { + _this.move(e) + }) + + // 鼠标松开 + window.removeEventListener('touchend', function () { + _this.end() + }) + window.removeEventListener('mouseup', function () { + _this.end() + }) + + window.addEventListener('touchmove', function (e) { + _this.move(e) + }) + window.addEventListener('mousemove', function (e) { + _this.move(e) + }) + + // 鼠标松开 + window.addEventListener('touchend', function () { + _this.end() + }) + window.addEventListener('mouseup', function () { + _this.end() + }) + }, + + // 鼠标按下 + start: function (e) { + e = e || window.event + if (!e.touches) { // 兼容PC端 + var x = e.clientX + } else { // 兼容移动端 + // eslint-disable-next-line no-redeclare + var x = e.touches[0].pageX + } + this.startLeft = Math.floor(x - this.barArea.getBoundingClientRect().left) + this.startMoveTime = +new Date() // 开始滑动的时间 + // eslint-disable-next-line eqeqeq + if (this.isEnd == false) { + this.text = '' + this.moveBlockBackgroundColor = '#337ab7' + this.leftBarBorderColor = '#337AB7' + this.iconColor = '#fff' + e.stopPropagation() + this.status = true + } + }, + // 鼠标移动 + move: function (e) { + e = e || window.event + // eslint-disable-next-line eqeqeq + if (this.status && this.isEnd == false) { + if (!e.touches) { // 兼容PC端 + var x = e.clientX + } else { // 兼容移动端 + // eslint-disable-next-line no-redeclare + var x = e.touches[0].pageX + } + // eslint-disable-next-line camelcase + var bar_area_left = this.barArea.getBoundingClientRect().left + // eslint-disable-next-line camelcase + var move_block_left = x - bar_area_left // 小方块相对于父元素的left值 + // eslint-disable-next-line camelcase + if (move_block_left >= this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2) { + // eslint-disable-next-line camelcase + move_block_left = this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2 + } + // eslint-disable-next-line camelcase + if (move_block_left <= 0) { + // eslint-disable-next-line camelcase + move_block_left = parseInt(parseInt(this.blockSize.width) / 2) + } + // 拖动后小方块的left值 + // eslint-disable-next-line camelcase + this.moveBlockLeft = (move_block_left - this.startLeft) + 'px' + // eslint-disable-next-line camelcase + this.leftBarWidth = (move_block_left - this.startLeft) + 'px' + } + }, + + // 鼠标松开 + end: function () { + this.endMovetime = +new Date() + var _this = this + // 判断是否重合 + // eslint-disable-next-line eqeqeq + if (this.status && this.isEnd == false) { + var moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', '')) + moveLeftDistance = moveLeftDistance * 310 / parseInt(this.setSize.imgWidth) + const data = { + captchaType: this.captchaType, + 'pointJson': this.secretKey ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : JSON.stringify({ x: moveLeftDistance, y: 5.0 }), + 'token': this.backToken + } + reqCheck(data).then(res => { + // eslint-disable-next-line eqeqeq + if (res.repCode == '0000') { + this.moveBlockBackgroundColor = '#5cb85c' + this.leftBarBorderColor = '#5cb85c' + this.iconColor = '#fff' + this.iconClass = 'icon-check' + this.showRefresh = false + this.isEnd = true + // eslint-disable-next-line eqeqeq + if (this.mode == 'pop') { + setTimeout(() => { + this.$parent.clickShow = false + this.refresh() + }, 1500) + } + this.passFlag = true + this.tipWords = `${((this.endMovetime - this.startMoveTime) / 1000).toFixed(2)}s验证成功` + var captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }) + setTimeout(() => { + this.tipWords = '' + this.$parent.closeBox() + this.$parent.$emit('success', { captchaVerification }) + }, 1000) + } else { + this.moveBlockBackgroundColor = '#d9534f' + this.leftBarBorderColor = '#d9534f' + this.iconColor = '#fff' + this.iconClass = 'icon-close' + this.passFlag = false + setTimeout(function () { + _this.refresh() + }, 1000) + this.$parent.$emit('error', this) + this.tipWords = '验证失败' + setTimeout(() => { + this.tipWords = '' + }, 1000) + } + }) + this.status = false + } + }, + + refresh: function () { + this.showRefresh = true + this.finishText = '' + + this.transitionLeft = 'left .3s' + this.moveBlockLeft = 0 + + this.leftBarWidth = undefined + this.transitionWidth = 'width .3s' + + this.leftBarBorderColor = '#ddd' + this.moveBlockBackgroundColor = '#fff' + this.iconColor = '#000' + this.iconClass = 'icon-right' + this.isEnd = false + + this.getPictrue() + setTimeout(() => { + this.transitionWidth = '' + this.transitionLeft = '' + this.text = this.explain + }, 300) + }, + + // 请求背景图片和验证图片 + getPictrue() { + const data = { + captchaType: this.captchaType + } + reqGet(data).then(res => { + // eslint-disable-next-line eqeqeq + if (res.repCode == '0000') { + this.backImgBase = res.repData.originalImageBase64 + this.blockBackImgBase = res.repData.jigsawImageBase64 + this.backToken = res.repData.token + this.secretKey = res.repData.secretKey + } else { + this.tipWords = res.repMsg + } + }) + } + }, + watch: { + // type变化则全面刷新 + type: { + immediate: true, + handler() { + this.init() + } + } + }, + mounted() { + // 禁止拖拽 + this.$el.onselectstart = function () { + return false + } + } + } +</script> diff --git a/_web/src/components/verifition/utils/ase.js b/_web/src/components/verifition/utils/ase.js new file mode 100644 index 0000000..7556cd7 --- /dev/null +++ b/_web/src/components/verifition/utils/ase.js @@ -0,0 +1,11 @@ +import CryptoJS from 'crypto-js' +/** + * @word 要加密的内容 + * @keyWord String 服务器随机返回的关键字 + */ +export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') { + var key = CryptoJS.enc.Utf8.parse(keyWord) + var srcs = CryptoJS.enc.Utf8.parse(word) + var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) + return encrypted.toString() +} diff --git a/_web/src/components/verifition/utils/axios.js b/_web/src/components/verifition/utils/axios.js new file mode 100644 index 0000000..b98b993 --- /dev/null +++ b/_web/src/components/verifition/utils/axios.js @@ -0,0 +1,30 @@ +import axios from 'axios' + +axios.defaults.baseURL = process.env.BASE_API + +const service = axios.create({ + timeout: 40000, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Content-Type': 'application/json; charset=UTF-8' + } +}) +service.interceptors.request.use( + config => { + return config + }, + error => { + Promise.reject(error) + } +) + +// response interceptor +service.interceptors.response.use( + response => { + const res = response.data + return res + }, + () => { + } +) +export default service diff --git a/_web/src/components/verifition/utils/util.js b/_web/src/components/verifition/utils/util.js new file mode 100644 index 0000000..f6c5746 --- /dev/null +++ b/_web/src/components/verifition/utils/util.js @@ -0,0 +1,52 @@ +export function resetSize(vm) { + // eslint-disable-next-line camelcase + var img_width, img_height, bar_width, bar_height // 图片的宽度、高度,移动条的宽度、高度 + + var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth + var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight + + // eslint-disable-next-line eqeqeq + if (vm.imgSize.width.indexOf('%') != -1) { + // eslint-disable-next-line camelcase + img_width = parseInt(this.imgSize.width) / 100 * parentWidth + 'px' + } else { + // eslint-disable-next-line camelcase + img_width = this.imgSize.width + } + + // eslint-disable-next-line eqeqeq + if (vm.imgSize.height.indexOf('%') != -1) { + // eslint-disable-next-line camelcase + img_height = parseInt(this.imgSize.height) / 100 * parentHeight + 'px' + } else { + // eslint-disable-next-line camelcase + img_height = this.imgSize.height + } + + // eslint-disable-next-line eqeqeq + if (vm.barSize.width.indexOf('%') != -1) { + // eslint-disable-next-line camelcase + bar_width = parseInt(this.barSize.width) / 100 * parentWidth + 'px' + } else { + // eslint-disable-next-line camelcase + bar_width = this.barSize.width + } + + // eslint-disable-next-line eqeqeq + if (vm.barSize.height.indexOf('%') != -1) { + // eslint-disable-next-line camelcase + bar_height = parseInt(this.barSize.height) / 100 * parentHeight + 'px' + } else { + // eslint-disable-next-line camelcase + bar_height = this.barSize.height + } + + return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height } +} + +// eslint-disable-next-line camelcase +export const _code_chars = [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', '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'] +// eslint-disable-next-line camelcase +export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0'] +// eslint-disable-next-line camelcase +export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC'] diff --git a/_web/src/components/xnComponents/EditorDiv.vue b/_web/src/components/xnComponents/EditorDiv.vue new file mode 100644 index 0000000..82f24c3 --- /dev/null +++ b/_web/src/components/xnComponents/EditorDiv.vue @@ -0,0 +1,95 @@ +/* eslint-disable no-undef */ +<!--onlyoffice 编辑器--> +<template> + <div id="editorDiv"></div> +</template> + +<script> + import { handleDocType } from '@/utils/onlyofficeUtil' + + export default { + name: 'Editor', + props: { + option: { + type: Object, + default: () => { + return {} + } + } + }, + data() { + return { + doctype: '' + } + }, + mounted() { + if (this.option.url) { + this.setEditor(this.option) + } + }, + methods: { + setEditor(option) { + this.doctype = handleDocType(option.fileType) + const config = { + document: { + fileType: option.fileType, + key: option.key, + title: option.title, + permissions: { + comment: true, + download: true, + modifyContentControl: true, + modifyFilter: true, + print: false, + edit: option.isEdit, + fillForms: true + // review: false + }, + url: option.url + + }, + type: option.type, + documentType: this.doctype, + editorConfig: { + callbackUrl: option.callbackUrl, + lang: 'zh', + customization: { + commentAuthorOnly: false, + comments: true, + compactHeader: false, + compactToolbar: true, + feedback: false, + plugins: true + }, + user: { + id: option.user.id, + name: option.user.name + } + // mode: option.mode + }, + width: '100%', + height: '100%', + position: 'absolute', + token: option.token + } + // eslint-disable-next-line no-unused-vars + let docEditor = null + // eslint-disable-next-line no-undef + docEditor = new DocsAPI.DocEditor('editorDiv', config) + } + }, + watch: { + option: { + handler: function (n, o) { + this.setEditor(n) + this.doctype = handleDocType(n.fileType) + }, + deep: true + } + } + } +</script> + +<style scoped> + +</style> diff --git a/_web/src/components/xnComponents/XCard.vue b/_web/src/components/xnComponents/XCard.vue new file mode 100644 index 0000000..4fdb719 --- /dev/null +++ b/_web/src/components/xnComponents/XCard.vue @@ -0,0 +1,16 @@ +<template> + <a-card :bordered="false" :bodyStyle="tstyle"> + <slot name="content"></slot> + </a-card> +</template> + +<script> + export default { + name: 'XCard', + data() { + return { + tstyle: { 'padding-bottom': '0px', 'margin-bottom': '10px' } + } + } + } +</script> diff --git a/_web/src/components/xnComponents/XDown.vue b/_web/src/components/xnComponents/XDown.vue new file mode 100644 index 0000000..b1a9ae0 --- /dev/null +++ b/_web/src/components/xnComponents/XDown.vue @@ -0,0 +1,54 @@ +<template> + <a-tooltip placement="top"> + <template slot="title"> + <span>导出所有数据</span> + </template> + <!-- 正常来说,这里加个插槽最好了,但是这个就是为导出准备的,一般这两个字不会变 --> + <a-button type="dashed" @click="batchExport" :loading="batchExportLoading"><a-icon type="export"/>导出</a-button> + </a-tooltip> +</template> + +<script> +export default { + name: 'XDown', + data () { + return { + batchExportLoading: false + } + }, + methods: { + /** + * 本控件中点击按钮事件 + */ + batchExport () { + this.batchExportLoading = true + // 将其传达到上个界面 + this.$emit('batchExport', '') + }, + + /** + * 通过返回的元素通过浏览器下载 + * 也就是接受使用这个组件的地方吧下载的内容传过来, + * 但是这个组件本公子编写的时候只想的适用于导出excel来使用,下载文件呀图片方面的重新整个组件即可 + */ + downloadfile (res) { + this.batchExportLoading = false + var blob = new Blob([res.data], { type: 'application/octet-stream;charset=UTF-8' }) + var contentDisposition = res.headers['content-disposition'] + var patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*') + var result = patt.exec(contentDisposition) + var filename = result[1] + var downloadElement = document.createElement('a') + var href = window.URL.createObjectURL(blob) // 创建下载的链接 + var reg = /^["](.*)["]$/g + downloadElement.style.display = 'none' + downloadElement.href = href + downloadElement.download = decodeURI(filename.replace(reg, '$1')) // 下载后文件名 + document.body.appendChild(downloadElement) + downloadElement.click() // 点击下载 + document.body.removeChild(downloadElement) // 下载完成移除元素 + window.URL.revokeObjectURL(href) + } + } +} +</script> diff --git a/_web/src/config/defaultSettings.js b/_web/src/config/defaultSettings.js new file mode 100644 index 0000000..a95c913 --- /dev/null +++ b/_web/src/config/defaultSettings.js @@ -0,0 +1,35 @@ +/** + * 项目默认配置项 + * primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage + * navTheme - sidebar theme ['dark', 'light'] 两种主题 + * colorWeak - 色盲模式 + * layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局 + * fixedHeader - 固定 Header : boolean + * fixSiderbar - 固定左侧菜单栏 : boolean + * autoHideHeader - 向下滚动时,隐藏 Header : boolean + * contentWidth - 内容区布局: 流式 | 固定 + * + * storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage) + * production: 变量暂先设定为 false,目的是各种环境都正常显示设置抽屉,真实环境请放开注释 + * + * + */ + +export default { + primaryColor: '#1890ff', // primary color of ant design + navTheme: 'dark', // theme for nav menu + layout: 'sidemenu', // nav menu position: sidemenu or topmenu + contentWidth: 'Fixed', // layout of content: Fluid or Fixed, only works when layout is topmenu + fixedHeader: false, // sticky header + fixSiderbar: false, // sticky siderbar + autoHideHeader: false, // auto hide header + colorWeak: false, + multiTab: false, + production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true', + // vue-ls options + storageOptions: { + namespace: 'pro__', // key prefix + name: 'ls', // name variable Vue.[ls] or this.[$ls], + storage: 'local' // storage name session, local, memory + } +} diff --git a/_web/src/config/router.config.js b/_web/src/config/router.config.js new file mode 100644 index 0000000..b808d76 --- /dev/null +++ b/_web/src/config/router.config.js @@ -0,0 +1,85 @@ +// eslint-disable-next-line +import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts' +import { bxAnaalyse } from '@/core/icons' + +export const asyncRouterMap = [ + + { + path: '/', + name: 'MenuIndex.vue', + component: BasicLayout, + meta: { title: '首页' }, + redirect: '/dashboard/workplace', + children: [ + // dashboard + { + path: 'dashboard', + name: 'dashboard', + redirect: '/dashboard/workplace', + component: RouteView, + // eslint-disable-next-line standard/object-curly-even-spacing + meta: { title: '仪表盘', keepAlive: true, icon: bxAnaalyse /* permission: [ 'dashboard' ] */ }, + children: [ + { + path: 'analysis/:pageNo([1-9]\\d*)?', + name: 'Analysis', + component: () => import('@/views/system/dashboard/Analysis'), + // eslint-disable-next-line standard/object-curly-even-spacing + meta: { title: '分析页', keepAlive: true /* permission: [ 'dashboard' ] */ } + }, + { + path: 'workplace', + name: 'Workplace', + component: () => import('@/views/system/dashboard/Workplace'), + // eslint-disable-next-line standard/object-curly-even-spacing + meta: { title: '工作台', keepAlive: false/*, permission: [ 'dashboard' ] */ } + } + ] + } + ] + }, + { + path: '*', redirect: '/404', hidden: true + } +] + +/** + * 基础路由 + * @type { *[] } + */ +export const constantRouterMap = [ + { + path: '/user', + component: UserLayout, + redirect: '/user/login', + hidden: true, + children: [ + { + path: 'login', + name: 'login', + component: () => import(/* webpackChunkName: "user" */ '@/views/userLoginReg/Login') + }, + { + path: 'register', + name: 'register', + component: () => import(/* webpackChunkName: "user" */ '@/views/userLoginReg/Register') + }, + { + path: 'register-result', + name: 'registerResult', + component: () => import(/* webpackChunkName: "user" */ '@/views/userLoginReg/RegisterResult') + }, + { + path: 'recover', + name: 'recover', + component: undefined + } + ] + }, + + { + path: '/404', + component: () => import(/* webpackChunkName: "fail" */ '@/views/system/exception/404') + } + +] diff --git a/_web/src/core/bootstrap.js b/_web/src/core/bootstrap.js new file mode 100644 index 0000000..d80909c --- /dev/null +++ b/_web/src/core/bootstrap.js @@ -0,0 +1,31 @@ +import Vue from 'vue' +import store from '@/store/' +import { + ACCESS_TOKEN, + DEFAULT_COLOR, + DEFAULT_THEME, + DEFAULT_LAYOUT_MODE, + DEFAULT_COLOR_WEAK, + SIDEBAR_TYPE, + DEFAULT_FIXED_HEADER, + DEFAULT_FIXED_HEADER_HIDDEN, + DEFAULT_FIXED_SIDEMENU, + DEFAULT_CONTENT_WIDTH_TYPE, + DEFAULT_MULTI_TAB +} from '@/store/mutation-types' + +import config from '@/config/defaultSettings' + +export default function Initializer () { + store.commit('SET_SIDEBAR_TYPE', Vue.ls.get(SIDEBAR_TYPE, true)) + store.commit('TOGGLE_THEME', Vue.ls.get(DEFAULT_THEME, config.navTheme)) + store.commit('TOGGLE_LAYOUT_MODE', Vue.ls.get(DEFAULT_LAYOUT_MODE, config.layout)) + store.commit('TOGGLE_FIXED_HEADER', Vue.ls.get(DEFAULT_FIXED_HEADER, config.fixedHeader)) + store.commit('TOGGLE_FIXED_SIDERBAR', Vue.ls.get(DEFAULT_FIXED_SIDEMENU, config.fixSiderbar)) + store.commit('TOGGLE_CONTENT_WIDTH', Vue.ls.get(DEFAULT_CONTENT_WIDTH_TYPE, config.contentWidth)) + store.commit('TOGGLE_FIXED_HEADER_HIDDEN', Vue.ls.get(DEFAULT_FIXED_HEADER_HIDDEN, config.autoHideHeader)) + store.commit('TOGGLE_WEAK', Vue.ls.get(DEFAULT_COLOR_WEAK, config.colorWeak)) + store.commit('TOGGLE_COLOR', Vue.ls.get(DEFAULT_COLOR, config.primaryColor)) + store.commit('TOGGLE_MULTI_TAB', Vue.ls.get(DEFAULT_MULTI_TAB, config.multiTab)) + store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN)) +} diff --git a/_web/src/core/directives/action.js b/_web/src/core/directives/action.js new file mode 100644 index 0000000..bdc9ec0 --- /dev/null +++ b/_web/src/core/directives/action.js @@ -0,0 +1,34 @@ +import Vue from 'vue' +import store from '@/store' + +/** + * Action 权限指令 + * 指令用法: + * - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下: + * <i-button v-action:add >添加用户</a-button> + * <a-button v-action:delete>删除用户</a-button> + * <a v-action:edit @click="edit(record)">修改</a> + * + * - 当前用户没有权限时,组件上使用了该指令则会被隐藏 + * - 当后台权限跟 pro 提供的模式不同时,只需要针对这里的权限过滤进行修改即可 + * + * @see https://github.com/sendya/ant-design-pro-vue/pull/53 + */ +const action = Vue.directive('action', { + inserted: function (el, binding, vnode) { + const actionName = binding.arg + const roles = store.getters.roles + const elVal = vnode.context.$route.meta.permission + const permissionId = elVal instanceof String && [elVal] || elVal + roles.permissions.forEach(p => { + if (!permissionId.includes(p.permissionId)) { + return + } + if (p.actionList && !p.actionList.includes(actionName)) { + el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none') + } + }) + } +}) + +export default action diff --git a/_web/src/core/icons.js b/_web/src/core/icons.js new file mode 100644 index 0000000..46b7261 --- /dev/null +++ b/_web/src/core/icons.js @@ -0,0 +1,11 @@ +/** + * Custom icon list + * All icons are loaded here for easy management + * @see https://vue.ant.design/components/icon/#Custom-Font-Icon + * + * 自定义图标加载表 + * 所有图标均从这里加载,方便管理 + */ +import bxAnaalyse from '@/assets/icons/bx-analyse.svg?inline' // path to your '*.svg?inline' file. + +export { bxAnaalyse } diff --git a/_web/src/core/lazy_lib/components_use.js b/_web/src/core/lazy_lib/components_use.js new file mode 100644 index 0000000..c147374 --- /dev/null +++ b/_web/src/core/lazy_lib/components_use.js @@ -0,0 +1,111 @@ +/** + * 该文件是为了按需加载,剔除掉了一些不需要的框架组件。 + * 减少了编译支持库包大小 + * @author yubaoshan + * 当需要更多组件依赖时,在该文件加入即可 + */ +import Vue from 'vue' +import { + ConfigProvider, + Layout, + Input, + InputNumber, + Button, + Switch, + Radio, + Checkbox, + Select, + Card, + Form, + Row, + Col, + Modal, + Table, + Tabs, + Icon, + Badge, + Popover, + Dropdown, + List, + Avatar, + Breadcrumb, + Steps, + Spin, + Menu, + Drawer, + Tooltip, + Alert, + Tag, + Divider, + DatePicker, + TimePicker, + Upload, + Progress, + Skeleton, + Popconfirm, + message, + notification, + TreeSelect, + Tree, + Transfer, + Empty, + PageHeader, + Descriptions, + Result +} from 'ant-design-vue' +// import VueCropper from 'vue-cropper' + +Vue.use(ConfigProvider) +Vue.use(Layout) +Vue.use(Input) +Vue.use(InputNumber) +Vue.use(Button) +Vue.use(Switch) +Vue.use(Radio) +Vue.use(Checkbox) +Vue.use(Select) +Vue.use(Card) +Vue.use(Form) +Vue.use(Row) +Vue.use(Col) +Vue.use(Modal) +Vue.use(Table) +Vue.use(Tabs) +Vue.use(Icon) +Vue.use(Badge) +Vue.use(Popover) +Vue.use(Dropdown) +Vue.use(List) +Vue.use(Avatar) +Vue.use(Breadcrumb) +Vue.use(Steps) +Vue.use(Spin) +Vue.use(Menu) +Vue.use(Drawer) +Vue.use(Tooltip) +Vue.use(Alert) +Vue.use(Tag) +Vue.use(Divider) +Vue.use(DatePicker) +Vue.use(TimePicker) +Vue.use(Upload) +Vue.use(Progress) +Vue.use(Skeleton) +Vue.use(Popconfirm) +// Vue.use(VueCropper) +Vue.use(notification) +Vue.use(TreeSelect) +Vue.use(Tree) +Vue.use(Transfer) +Vue.use(Empty) +Vue.use(PageHeader) +Vue.use(Descriptions) +Vue.use(Result) + +Vue.prototype.$confirm = Modal.confirm +Vue.prototype.$message = message +Vue.prototype.$notification = notification +Vue.prototype.$info = Modal.info +Vue.prototype.$success = Modal.success +Vue.prototype.$error = Modal.error +Vue.prototype.$warning = Modal.warning diff --git a/_web/src/core/lazy_use.js b/_web/src/core/lazy_use.js new file mode 100644 index 0000000..efd2047 --- /dev/null +++ b/_web/src/core/lazy_use.js @@ -0,0 +1,27 @@ +import Vue from 'vue' +import VueStorage from 'vue-ls' +import config from '@/config/defaultSettings' + +// base library +import '@/core/lazy_lib/components_use' +import Viser from 'viser-vue' + +// ext library +import VueClipboard from 'vue-clipboard2' +import VueCropper from 'vue-cropper' +import MultiTab from '@/components/MultiTab' +import PageLoading from '@/components/PageLoading' +import PermissionHelper from '@/utils/helper/permission' +import './directives/action' + +VueClipboard.config.autoSetContainer = true + +Vue.use(Viser) +Vue.use(MultiTab) +Vue.use(PageLoading) +Vue.use(VueStorage, config.storageOptions) +Vue.use(VueClipboard) +Vue.use(PermissionHelper) +Vue.use(VueCropper) + +process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] NOTICE: Antd use lazy-load.') diff --git a/_web/src/core/use.js b/_web/src/core/use.js new file mode 100644 index 0000000..a17c2fc --- /dev/null +++ b/_web/src/core/use.js @@ -0,0 +1,30 @@ +import Vue from 'vue' +import VueStorage from 'vue-ls' +import config from '@/config/defaultSettings' + +// base library +import Antd from 'ant-design-vue' +import Viser from 'viser-vue' +import VueCropper from 'vue-cropper' +import 'ant-design-vue/dist/antd.less' + +// ext library +import VueClipboard from 'vue-clipboard2' +import MultiTab from '@/components/MultiTab' +import PageLoading from '@/components/PageLoading' +import PermissionHelper from '@/utils/helper/permission' +// import '@/components/use' +import './directives/action' + +VueClipboard.config.autoSetContainer = true + +Vue.use(Antd) +Vue.use(Viser) +Vue.use(MultiTab) +Vue.use(PageLoading) +Vue.use(VueStorage, config.storageOptions) +Vue.use(VueClipboard) +Vue.use(PermissionHelper) +Vue.use(VueCropper) + +process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] WARNING: Antd now use fulled imported.') diff --git a/_web/src/layouts/BasicLayout.vue b/_web/src/layouts/BasicLayout.vue new file mode 100644 index 0000000..731e4be --- /dev/null +++ b/_web/src/layouts/BasicLayout.vue @@ -0,0 +1,186 @@ +<template> + <a-layout :class="['layout', device]"> + <!-- SideMenu --> + <a-drawer + v-if="isMobile()" + placement="left" + :wrapClassName="`drawer-sider ${navTheme}`" + :closable="false" + :visible="collapsed" + @close="drawerClose" + > + <side-menu + mode="inline" + :menus="menus" + :theme="navTheme" + :collapsed="false" + :collapsible="true" + @menuSelect="menuSelect" + ></side-menu> + </a-drawer> + + <side-menu + v-else-if="isSideMenu()" + mode="inline" + :menus="menus" + :theme="navTheme" + :collapsed="collapsed" + :collapsible="true" + ></side-menu> + + <a-layout :class="[layoutMode, `content-width-${contentWidth}`]" :style="{ paddingLeft: contentPaddingLeft, minHeight: '100vh' }"> + <!-- layout header --> + <global-header + :mode="layoutMode" + :menus="menus" + :theme="navTheme" + :collapsed="collapsed" + :device="device" + @toggle="toggle" + /> + + <!-- layout content --> + <a-layout-content :style="{ height: '100%', margin: '24px 24px 0', paddingTop: fixedHeader ? '55px' : '0' }"> + <multi-tab v-if="multiTab"></multi-tab> + <transition name="page-transition"> + <route-view /> + </transition> + </a-layout-content> + + <!-- layout footer --> + <a-layout-footer> + <global-footer /> + </a-layout-footer> + + <!-- Setting Drawer (show in development mode) --> + <setting-drawer v-if="!production"></setting-drawer> + </a-layout> + </a-layout> + +</template> + +<script> +import { triggerWindowResizeEvent } from '@/utils/util' +import { mapState, mapActions } from 'vuex' +import { mixin, mixinDevice } from '@/utils/mixin' +import config from '@/config/defaultSettings' + +import RouteView from './RouteView' +import SideMenu from '@/components/Menu/SideMenu' +import GlobalHeader from '@/components/GlobalHeader' +import GlobalFooter from '@/components/GlobalFooter' +import SettingDrawer from '@/components/SettingDrawer' +import { convertRoutes } from '@/utils/routeConvert' + +export default { + name: 'BasicLayout', + mixins: [mixin, mixinDevice], + components: { + RouteView, + SideMenu, + GlobalHeader, + GlobalFooter, + SettingDrawer + }, + data () { + return { + production: config.production, + collapsed: false, + menus: [] + } + }, + computed: { + ...mapState({ + // 动态主路由 + mainMenu: state => state.permission.addRouters + }), + contentPaddingLeft () { + if (!this.fixSidebar || this.isMobile()) { + return '0' + } + if (this.sidebarOpened) { + return '230px' + } + return '80px' + } + }, + watch: { + sidebarOpened (val) { + this.collapsed = !val + }, + // 菜单变化 + mainMenu (val) { + this.setMenus() + } + }, + created () { + this.setMenus() + /* const routes = convertRoutes(this.mainMenu.find(item => item.path === '/')) + this.menus = (routes && routes.children) || [] */ + this.collapsed = !this.sidebarOpened + }, + mounted () { + const userAgent = navigator.userAgent + if (userAgent.indexOf('Edge') > -1) { + this.$nextTick(() => { + this.collapsed = !this.collapsed + setTimeout(() => { + this.collapsed = !this.collapsed + }, 16) + }) + } + }, + methods: { + ...mapActions(['setSidebar']), + // 重新生成 + setMenus () { + const routes = convertRoutes(this.mainMenu.find(item => item.path === '/')) + this.menus = (routes && routes.children) || [] + }, + toggle () { + this.collapsed = !this.collapsed + this.setSidebar(!this.collapsed) + triggerWindowResizeEvent() + }, + paddingCalc () { + let left = '' + if (this.sidebarOpened) { + left = this.isDesktop() ? '230px' : '80px' + } else { + left = (this.isMobile() && '0') || ((this.fixSidebar && '80px') || '0') + } + return left + }, + menuSelect () { + }, + drawerClose () { + this.collapsed = false + } + } +} +</script> + +<style lang="less"> +/* + * The following styles are auto-applied to elements with + * transition="page-transition" when their visibility is toggled + * by Vue.js. + * + * You can easily play with the page transition by editing + * these styles. + */ + +.page-transition-enter { + opacity: 0; +} + +.page-transition-leave-active { + opacity: 0; +} + +.page-transition-enter .page-transition-container, +.page-transition-leave-active .page-transition-container { + -webkit-transform: scale(1.1); + transform: scale(1.1); +} +</style> diff --git a/_web/src/layouts/BlankLayout.vue b/_web/src/layouts/BlankLayout.vue new file mode 100644 index 0000000..1bfbfbf --- /dev/null +++ b/_web/src/layouts/BlankLayout.vue @@ -0,0 +1,16 @@ +<template> + <div> + <router-view /> + </div> +</template> + +<script> + +export default { + name: 'BlankLayout' +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/layouts/Iframe.vue b/_web/src/layouts/Iframe.vue new file mode 100644 index 0000000..e2526a4 --- /dev/null +++ b/_web/src/layouts/Iframe.vue @@ -0,0 +1,29 @@ + +<script> + import Vue from 'vue' + import { ACCESS_TOKEN } from '@/store/mutation-types' + + export default { + name: 'Iframe', + data () { + return { + } + }, + render () { + const { $route: { meta: { link } } } = this + if ({ link }.link === '') { + return '404' + } + let url = '' + if ({ link }.link.indexOf('token=') > -1) { + url = { link }.link + Vue.ls.get(ACCESS_TOKEN) + } else { + url = { link }.link + } + let height = '' + const deviceHeight = document.documentElement.clientHeight + height = (Number(deviceHeight) - 260) + 'px' + return <iframe id="iframe" height={height} src={url} style="width:100%;overflow:hidden;" frameBorder="0"></iframe> + } + } +</script> diff --git a/_web/src/layouts/PageView.vue b/_web/src/layouts/PageView.vue new file mode 100644 index 0000000..7d9c6e1 --- /dev/null +++ b/_web/src/layouts/PageView.vue @@ -0,0 +1,177 @@ +<template> + <div :style="!$route.meta.hiddenHeaderContent ? 'margin: -24px -24px 0px;' : null"> + <!-- pageHeader , route meta :true on hide --> + <page-header v-if="!$route.meta.hiddenHeaderContent" :title="pageTitle" :logo="logo" :avatar="avatar"> + <slot slot="action" name="action"></slot> + <slot slot="content" name="headerContent"></slot> + <div slot="content" v-if="!this.$slots.headerContent && description"> + <p style="font-size: 14px;color: rgba(0,0,0,.65)">{{ description }}</p> + <div class="link"> + </div> + </div> + <slot slot="extra" name="extra"> + <div class="extra-img"> + <img v-if="typeof extraImage !== 'undefined'" :src="extraImage"/> + </div> + </slot> + <div slot="pageMenu"> + <div class="page-menu-search" v-if="search"> + <a-input-search + style="width: 80%; max-width: 522px;" + placeholder="请输入..." + size="large" + enterButton="搜索" + /> + </div> + <div class="page-menu-tabs" v-if="tabs && tabs.items"> + <!-- @change="callback" :activeKey="activeKey" --> + <a-tabs :tabBarStyle="{margin: 0}" :activeKey="tabs.active()" @change="tabs.callback"> + <a-tab-pane v-for="item in tabs.items" :tab="item.title" :key="item.key"></a-tab-pane> + </a-tabs> + </div> + </div> + </page-header> + <div class="content"> + <div class="page-header-index-wide"> + <slot> + <!-- keep-alive --> + <keep-alive v-if="multiTab"> + <router-view ref="content" /> + </keep-alive> + <router-view v-else ref="content" style="margin: -12px -14px 0;"/> + </slot> + </div> + </div> + </div> +</template> + +<script> +import { mapState } from 'vuex' +import PageHeader from '@/components/PageHeader' + +export default { + name: 'PageView', + components: { + PageHeader + }, + props: { + avatar: { + type: String, + default: null + }, + title: { + type: [String, Boolean], + default: true + }, + logo: { + type: String, + default: null + }, + directTabs: { + type: Object, + default: null + } + }, + data () { + return { + pageTitle: null, + description: null, + linkList: [], + extraImage: '', + search: false, + tabs: {} + } + }, + computed: { + ...mapState({ + multiTab: state => state.app.multiTab + }) + }, + mounted () { + this.tabs = this.directTabs + this.getPageMeta() + }, + updated () { + this.getPageMeta() + }, + methods: { + getPageMeta () { + // eslint-disable-next-line + //文字样式//(typeof(this.title) === 'string' || !this.title) ? this.title : this.$route.meta.title + // 为了简洁改为无 + this.pageTitle = '' + + const content = this.$refs.content + if (content) { + if (content.pageMeta) { + Object.assign(this, content.pageMeta) + } else { + this.description = content.description + this.linkList = content.linkList + this.extraImage = content.extraImage + this.search = content.search === true + this.tabs = content.tabs + } + } + } + } +} +</script> + +<style lang="less" scoped> + .content { + margin: 24px 24px 0; + .link { + margin-top: 16px; + &:not(:empty) { + margin-bottom: 16px; + } + a { + margin-right: 32px; + height: 24px; + line-height: 24px; + display: inline-block; + i { + font-size: 24px; + margin-right: 8px; + vertical-align: middle; + } + span { + height: 24px; + line-height: 24px; + display: inline-block; + vertical-align: middle; + } + } + } + } + .page-menu-search { + text-align: center; + margin-bottom: 16px; + } + .page-menu-tabs { + margin-top: 48px; + } + + .extra-img { + margin-top: -60px; + text-align: center; + width: 195px; + + img { + width: 100%; + } + } + + .mobile { + .extra-img{ + margin-top: 0; + text-align: center; + width: 96px; + + img{ + width: 100%; + } + } + } +</style> diff --git a/_web/src/layouts/RouteView.vue b/_web/src/layouts/RouteView.vue new file mode 100644 index 0000000..edae19e --- /dev/null +++ b/_web/src/layouts/RouteView.vue @@ -0,0 +1,32 @@ +<script> +export default { + name: 'RouteView', + props: { + keepAlive: { + type: Boolean, + default: true + } + }, + data () { + return {} + }, + render () { + const { $route: { meta }, $store: { getters } } = this + const inKeep = ( + <keep-alive> + <router-view /> + </keep-alive> + ) + const notKeep = ( + <router-view /> + ) + // 这里增加了 multiTab 的判断,当开启了 multiTab 时 + // 应当全部组件皆缓存,否则会导致切换页面后页面还原成原始状态 + // 若确实不需要,可改为 return meta.keepAlive ? inKeep : notKeep + if (!getters.multiTab && !meta.keepAlive) { + return notKeep + } + return this.keepAlive || getters.multiTab || meta.keepAlive ? inKeep : notKeep + } +} +</script> diff --git a/_web/src/layouts/UserLayout.vue b/_web/src/layouts/UserLayout.vue new file mode 100644 index 0000000..5dfb26a --- /dev/null +++ b/_web/src/layouts/UserLayout.vue @@ -0,0 +1,153 @@ +<template> + <div id="userLayout" :class="['user-layout-wrapper', device]"> + <div class="container"> + <div class="top"> + <div class="header"> + <a href="/"> + <img src="~@/assets/logo.png" class="logo" alt="logo"> + <span class="title">Snowy快速开发平台</span> + </a> + </div> + <div class="desc"> + + </div> + </div> + + <route-view></route-view> + + <div class="footer"> + <div class="links"> + <a href="_self">帮助</a> + <a href="_self">隐私</a> + <a href="_self">条款</a> + </div> + <div class="copyright"> + Copyright © 2020 <a target="_blank" href="https://www.xiaonuo.vip/">小诺开源技术</a> All rights reserved. Snowy 1.8 + </div> + </div> + </div> + </div> +</template> + +<script> +import RouteView from './RouteView' +import { mixinDevice } from '@/utils/mixin' + +export default { + name: 'UserLayout', + components: { RouteView }, + mixins: [mixinDevice], + data () { + return {} + }, + mounted () { + document.body.classList.add('userLayout') + }, + beforeDestroy () { + document.body.classList.remove('userLayout') + } +} +</script> + +<style lang="less" scoped> + #userLayout.user-layout-wrapper { + height: 100%; + + &.mobile { + .container { + .main { + max-width: 368px; + width: 98%; + } + } + } + + .container { + width: 100%; + min-height: 100%; + background-color: #ffffff; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='800' viewBox='0 0 200 200'%3E%3Cdefs%3E%3ClinearGradient id='a' gradientUnits='userSpaceOnUse' x1='88' y1='88' x2='0' y2='0'%3E%3Cstop offset='0' stop-color='%23064e77'/%3E%3Cstop offset='1' stop-color='%230a7dbe'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' gradientUnits='userSpaceOnUse' x1='75' y1='76' x2='168' y2='160'%3E%3Cstop offset='0' stop-color='%238f8f8f'/%3E%3Cstop offset='0.09' stop-color='%23b3b3b3'/%3E%3Cstop offset='0.18' stop-color='%23c9c9c9'/%3E%3Cstop offset='0.31' stop-color='%23dbdbdb'/%3E%3Cstop offset='0.44' stop-color='%23e8e8e8'/%3E%3Cstop offset='0.59' stop-color='%23f2f2f2'/%3E%3Cstop offset='0.75' stop-color='%23fafafa'/%3E%3Cstop offset='1' stop-color='%23FFFFFF'/%3E%3C/linearGradient%3E%3Cfilter id='c' x='0' y='0' width='200%25' height='200%25'%3E%3CfeGaussianBlur in='SourceGraphic' stdDeviation='12' /%3E%3C/filter%3E%3C/defs%3E%3Cpolygon fill='url(%23a)' points='0 174 0 0 174 0'/%3E%3Cpath fill='%23000' fill-opacity='.5' filter='url(%23c)' d='M121.8 174C59.2 153.1 0 174 0 174s63.5-73.8 87-94c24.4-20.9 87-80 87-80S107.9 104.4 121.8 174z'/%3E%3Cpath fill='url(%23b)' d='M142.7 142.7C59.2 142.7 0 174 0 174s42-66.3 74.9-99.3S174 0 174 0S142.7 62.6 142.7 142.7z'/%3E%3C/svg%3E"); + background-attachment: fixed; + background-repeat: no-repeat; + background-position: top left; + padding: 110px 0 144px; + position: relative; + + a { + text-decoration: none; + } + + .top { + text-align: center; + + .header { + height: 44px; + line-height: 44px; + + .badge { + position: absolute; + display: inline-block; + line-height: 1; + vertical-align: middle; + margin-left: -12px; + margin-top: -10px; + opacity: 0.8; + } + + .logo { + height: 44px; + vertical-align: top; + margin-right: 16px; + border-style: none; + } + + .title { + font-size: 33px; + color: rgba(0, 0, 0, .85); + font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; + font-weight: 600; + position: relative; + top: 2px; + } + } + .desc { + font-size: 14px; + color: rgba(0, 0, 0, 0.45); + margin-top: 12px; + margin-bottom: 40px; + } + } + + .main { + min-width: 260px; + width: 368px; + margin: 0 auto; + } + + .footer { + position: absolute; + width: 100%; + bottom: 0; + padding: 0 16px; + margin: 48px 0 24px; + text-align: center; + + .links { + margin-bottom: 8px; + font-size: 14px; + a { + color: rgba(0, 0, 0, 0.45); + transition: all 0.3s; + &:not(:last-child) { + margin-right: 40px; + } + } + } + .copyright { + color: rgba(0, 0, 0, 0.45); + font-size: 14px; + } + } + } + } +</style> diff --git a/_web/src/layouts/index.js b/_web/src/layouts/index.js new file mode 100644 index 0000000..a7ab0c9 --- /dev/null +++ b/_web/src/layouts/index.js @@ -0,0 +1,8 @@ +import UserLayout from './UserLayout' +import BlankLayout from './BlankLayout' +import BasicLayout from './BasicLayout' +import RouteView from './RouteView' +import PageView from './PageView' +import Iframe from './Iframe' + +export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView, Iframe } diff --git a/_web/src/main.js b/_web/src/main.js new file mode 100644 index 0000000..b303487 --- /dev/null +++ b/_web/src/main.js @@ -0,0 +1,28 @@ +import 'core-js/stable' +import 'regenerator-runtime/runtime' +import Vue from 'vue' +import App from './App.vue' +import router from './router' +import store from './store/' +import { VueAxios } from './utils/request' +import bootstrap from './core/bootstrap' +import './core/lazy_use' +import './permission' +import './utils/filter' +import './components/global.less' +import { Dialog } from '@/components' +import { hasBtnPermission } from './utils/permissions' +import { sysApplication } from './utils/applocation' + +Vue.use(VueAxios) +Vue.use(Dialog) +Vue.prototype.hasPerm = hasBtnPermission +Vue.prototype.applocation = sysApplication +Vue.config.productionTip = false + +new Vue({ + router, + store, + created: bootstrap, + render: h => h(App) +}).$mount('#app') diff --git a/_web/src/permission.js b/_web/src/permission.js new file mode 100644 index 0000000..5de7ac6 --- /dev/null +++ b/_web/src/permission.js @@ -0,0 +1,113 @@ +import Vue from 'vue' +import router from './router' +import store from './store' + +import NProgress from 'nprogress' // progress bar +import '@/components/NProgress/nprogress.less' // progress bar custom style +import { setDocumentTitle, domTitle } from '@/utils/domUtil' +import { ACCESS_TOKEN, ALL_APPS_MENU } from '@/store/mutation-types' + +import { Modal, notification } from 'ant-design-vue' // NProgress Configuration +import { timeFix } from '@/utils/util'/// es/notification +NProgress.configure({ showSpinner: false }) +const whiteList = ['login', 'register', 'registerResult'] // no redirect whitelist +// 无默认首页的情况 +const defaultRoutePath = '/welcome' + +router.beforeEach((to, from, next) => { + NProgress.start() // start progress bar + to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`)) + if (Vue.ls.get(ACCESS_TOKEN)) { + /* has token */ + if (to.path === '/user/login') { + next({ path: defaultRoutePath }) + NProgress.done() + } else { + if (store.getters.roles.length === 0) { + store + .dispatch('GetInfo') + .then(res => { + if (res.menus.length < 1) { + Modal.error({ + title: '提示:', + content: '无菜单权限,请联系管理员', + okText: '确定', + onOk: () => { + store.dispatch('Logout').then(() => { + window.location.reload() + }) + } + }) + return + } + // eslint-disable-next-line camelcase + const all_app_menu = Vue.ls.get(ALL_APPS_MENU) + let antDesignmenus + // eslint-disable-next-line camelcase + if (all_app_menu == null) { + const applocation = [] + res.apps.forEach(item => { + const apps = { 'code': '', 'name': '', 'active': '', 'menu': '' } + if (item.active) { + apps.code = item.code + apps.name = item.name + apps.active = item.active + apps.menu = res.menus + antDesignmenus = res.menus + } else { + apps.code = item.code + apps.name = item.name + apps.active = item.active + apps.menu = '' + } + applocation.push(apps) + }) + Vue.ls.set(ALL_APPS_MENU, applocation, 7 * 24 * 60 * 60 * 1000) + // 延迟 1 秒显示欢迎信息 + setTimeout(() => { + notification.success({ + message: '欢迎', + description: `${timeFix()},欢迎回来` + }) + }, 1000) + } else { + antDesignmenus = Vue.ls.get(ALL_APPS_MENU)[0].menu + } + store.dispatch('GenerateRoutes', { antDesignmenus }).then(() => { + // 动态添加可访问路由表 + router.addRoutes(store.getters.addRouters) + // 请求带有 redirect 重定向时,登录自动重定向到该地址 + const redirect = decodeURIComponent(from.query.redirect || to.path) + if (to.path === redirect) { + next({ path: redirect }) + // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record + next({ ...to, replace: true }) + } else { + // 跳转到目的路由 + next({ path: redirect }) + } + }) + }) + .catch(() => { + store.dispatch('Logout').then(() => { + next({ path: '/user/login', query: { redirect: to.fullPath } }) + }) + }) + } else { + next() + } + } + } else { + if (whiteList.includes(to.name)) { + // 在免登录白名单,直接进入 + next() + } else { + next({ path: '/user/login', query: { redirect: to.fullPath } }) + NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it + } + } +}) + +router.afterEach(() => { + NProgress.done() // finish progress bar +}) diff --git a/_web/src/router/generator-routers.js b/_web/src/router/generator-routers.js new file mode 100644 index 0000000..4106ec6 --- /dev/null +++ b/_web/src/router/generator-routers.js @@ -0,0 +1,261 @@ + +import { BasicLayout, BlankLayout, PageView, RouteView, Iframe } from '@/layouts' + +// 前端路由表 +const constantRouterComponents = { + // 基础页面 layout 必须引入 + BasicLayout: BasicLayout, + BlankLayout: BlankLayout, + RouteView: RouteView, + PageView: PageView, + Iframe: Iframe, + '403': () => import('@/views/system/exception/403'), + '404': () => import('@/views/system/exception/404'), + '500': () => import('@/views/system/exception/500'), + + 'Workplace': () => import('@/views/system/dashboard/Workplace'), + // account + 'AccountCenter': () => import('@/views/system/account/center/Index'), + 'AccountSettings': () => import('@/views/system/account/settings/Index'), + 'BaseSettings': () => import('@/views/system/account/settings/BaseSetting'), + 'SecuritySettings': () => import('@/views/system/account/settings/Security'), + 'CustomSettings': () => import('@/views/system/account/settings/Custom'), + 'BindingSettings': () => import('@/views/system/account/settings/Binding'), + 'NotificationSettings': () => import('@/views/system/account/settings/Notification'), + + // 默认首页 + 'Console': () => import('@/views/system/index/welcome') +} + +// 前端未找到页面路由(固定不用改)、原来为 /404 +const notFoundRouter = { + path: '*', redirect: '/welcome', hidden: true +} +// 个人中心页面 +const userAccount = [ + // account + { + 'name': 'account', + 'pid': 0, + 'id': 10028, + 'meta': { + 'title': '个人页', + 'icon': 'user', + 'show': false + }, + 'redirect': '/account/center', + 'component': 'RouteView' + }, + { + 'name': 'center', + 'pid': 10028, + 'id': 10029, + 'meta': { + 'title': '个人中心', + 'show': false + }, + 'component': 'AccountCenter' + }, + // 特殊三级菜单 + { + 'name': 'settings', + 'pid': '10028', + 'id': '10030', + 'meta': { + 'title': '个人设置', + 'hideHeader': true, + 'hideChildren': true, + 'show': false + }, + 'redirect': '/account/settings/base', + 'component': 'AccountSettings' + }, + { + 'name': 'BaseSettings', + 'path': '/account/settings/base', + 'pid': 10030, + 'id': 10031, + 'meta': { + 'title': '基本设置', + 'show': false + }, + 'component': 'BaseSettings' + }, + { + 'name': 'SecuritySettings', + 'path': '/account/settings/security', + 'pid': 10030, + 'id': 10032, + 'meta': { + 'title': '安全设置', + 'show': false + }, + 'component': 'SecuritySettings' + }, + { + 'name': 'CustomSettings', + 'path': '/account/settings/custom', + 'pid': 10030, + 'id': 10033, + 'meta': { + 'title': '个性化设置', + 'show': false + }, + 'component': 'CustomSettings' + }, + { + 'name': 'BindingSettings', + 'path': '/account/settings/binding', + 'pid': 10030, + 'id': 10034, + 'meta': { + 'title': '账户绑定', + 'show': false + }, + 'component': 'BindingSettings' + }, + { + 'name': 'NotificationSettings', + 'path': '/account/settings/notification', + 'pid': 10030, + 'id': 10034, + 'meta': { + 'title': '新消息通知', + 'show': false + }, + 'component': 'NotificationSettings' + }, + { + 'name': 'Console', + 'path': '/welcome', + 'pid': 0, + 'id': 183183, + 'meta': { + 'title': '首页', + 'show': false + }, + 'component': 'Console' + } + +] + +// 根级菜单 +const rootRouter = { + key: '', + name: 'MenuIndex.vue', + path: '', + component: 'BasicLayout', + redirect: '/welcome', + meta: { + title: '首页' + }, + children: [] +} + +/** + * 动态生成菜单 + * @param data + * @returns {Promise<Router>} + */ +export const generatorDynamicRouter = (data) => { + return new Promise((resolve, reject) => { + const resNav = data.antDesignmenus + const menuNav = [] + const childrenNav = [] + // 后端数据, 根级树数组, 根级 PID + listToTree(resNav, childrenNav, 0) + + /** + * 增加静态网页 + */ + listToTree(userAccount, childrenNav, 0) + rootRouter.children = childrenNav + menuNav.push(rootRouter) + const routers = generator(menuNav) + routers.push(notFoundRouter) + resolve(routers) + }).catch(err => { + // reject('加载菜单失败') + return Promise.reject(err) + }) +} + +/** + * 格式化树形结构数据 生成 vue-router 层级路由表 + * + * @param routerMap + * @param parent + * @returns {*} + */ +export const generator = (routerMap, parent) => { + return routerMap.map(item => { + // eslint-disable-next-line no-unused-vars + const { title, show, hideChildren, hiddenHeaderContent, target, icon, link } = item.meta || {} + const currentRouter = { + // 如果路由设置了 path,则作为默认 path,否则 路由地址 动态拼接生成如 /dashboard/workplace + path: item.path || `${parent && parent.path || ''}/${item.key}`, + // 路由名称,建议唯一 + name: item.name || item.key || '', + // 该路由对应页面的 组件 :方案1 + // component: constantRouterComponents[item.component || item.key], + // 该路由对应页面的 组件 :方案2 (动态加载) + component: (constantRouterComponents[item.component || item.key]) || (() => import(`@/views/${item.component}`)), + // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉) + meta: { + title: title, + icon: icon || undefined, + // hiddenHeaderContent: hiddenHeaderContent, + target: target, + link: link + } + } + // 是否设置了隐藏菜单 + if (show === false) { + currentRouter.hidden = true + } + // 是否设置了隐藏子菜单 + if (hideChildren) { + currentRouter.hideChildrenInMenu = true + } + // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠 + if (!currentRouter.path.startsWith('http')) { + currentRouter.path = currentRouter.path.replace('//', '/') + } + // 重定向 + item.redirect && (currentRouter.redirect = item.redirect) + // 是否有子菜单,并递归处理 + if (item.children && item.children.length > 0) { + // Recursion + currentRouter.children = generator(item.children, currentRouter) + } + return currentRouter + }) +} + +/** + * 数组转树形结构 + * @param list 源数组 + * @param tree 树 + * @param parentId 父ID + */ +const listToTree = (list, tree, parentId) => { + list.forEach(item => { + // 判断是否为父级菜单 + // eslint-disable-next-line eqeqeq + if (item.pid == parentId) { + const child = { + ...item, + key: item.key || item.name, + children: [] + } + // 迭代 list, 找到当前菜单相符合的所有子菜单 + listToTree(list, child.children, item.id) + // 删掉不存在 children 值的属性 + if (child.children.length <= 0) { + delete child.children + } + // 加入到树中 + tree.push(child) + } + }) +} diff --git a/_web/src/router/index.js b/_web/src/router/index.js new file mode 100644 index 0000000..dd72772 --- /dev/null +++ b/_web/src/router/index.js @@ -0,0 +1,19 @@ +import Vue from 'vue' +import Router from 'vue-router' +import { constantRouterMap } from '@/config/router.config' + +// hack router push callback +const originalPush = Router.prototype.push +Router.prototype.push = function push (location, onResolve, onReject) { + if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject) + return originalPush.call(this, location).catch(err => err) +} + +Vue.use(Router) + +export default new Router({ + mode: 'history', + base: process.env.BASE_URL, + scrollBehavior: () => ({ y: 0 }), + routes: constantRouterMap +}) diff --git a/_web/src/store/getters.js b/_web/src/store/getters.js new file mode 100644 index 0000000..69929b9 --- /dev/null +++ b/_web/src/store/getters.js @@ -0,0 +1,18 @@ +const getters = { + device: state => state.app.device, + theme: state => state.app.theme, + color: state => state.app.color, + token: state => state.user.token, + avatar: state => state.user.avatar, + nickname: state => state.user.name, + welcome: state => state.user.welcome, + roles: state => state.user.roles, + buttons: state => state.user.buttons, + admintype: state => state.user.admintype, + userInfo: state => state.user.info, + addRouters: state => state.permission.addRouters, + multiTab: state => state.app.multiTab, + lang: state => state.i18n.lang +} + +export default getters diff --git a/_web/src/store/index.js b/_web/src/store/index.js new file mode 100644 index 0000000..687d21b --- /dev/null +++ b/_web/src/store/index.js @@ -0,0 +1,32 @@ +import Vue from 'vue' +import Vuex from 'vuex' + +import app from './modules/app' +import user from './modules/user' + +// default router permission control +// import permission from './modules/permission' + +// dynamic router permission control (Experimental) +import permission from './modules/async-router' +import getters from './getters' + +Vue.use(Vuex) + +export default new Vuex.Store({ + modules: { + app, + user, + permission + }, + state: { + + }, + mutations: { + + }, + actions: { + + }, + getters +}) diff --git a/_web/src/store/modules/app.js b/_web/src/store/modules/app.js new file mode 100644 index 0000000..6d49e58 --- /dev/null +++ b/_web/src/store/modules/app.js @@ -0,0 +1,129 @@ +import Vue from 'vue' +import { + SIDEBAR_TYPE, + DEFAULT_THEME, + DEFAULT_LAYOUT_MODE, + DEFAULT_COLOR, + DEFAULT_COLOR_WEAK, + DEFAULT_FIXED_HEADER, + DEFAULT_FIXED_SIDEMENU, + DEFAULT_FIXED_HEADER_HIDDEN, + DEFAULT_CONTENT_WIDTH_TYPE, + DEFAULT_MULTI_TAB +} from '@/store/mutation-types' + +const app = { + state: { + sidebar: true, + device: 'desktop', + theme: '', + layout: '', + contentWidth: '', + fixedHeader: false, + fixSiderbar: false, + autoHideHeader: false, + color: null, + weak: false, + multiTab: true, + hasError: false + }, + mutations: { + SET_SIDEBAR_TYPE: (state, type) => { + state.sidebar = type + Vue.ls.set(SIDEBAR_TYPE, type) + }, + CLOSE_SIDEBAR: (state) => { + Vue.ls.set(SIDEBAR_TYPE, true) + state.sidebar = false + }, + TOGGLE_DEVICE: (state, device) => { + state.device = device + }, + TOGGLE_THEME: (state, theme) => { + // setStore('_DEFAULT_THEME', theme) + Vue.ls.set(DEFAULT_THEME, theme) + state.theme = theme + }, + TOGGLE_LAYOUT_MODE: (state, layout) => { + Vue.ls.set(DEFAULT_LAYOUT_MODE, layout) + state.layout = layout + }, + TOGGLE_FIXED_HEADER: (state, fixed) => { + Vue.ls.set(DEFAULT_FIXED_HEADER, fixed) + state.fixedHeader = fixed + }, + TOGGLE_FIXED_SIDERBAR: (state, fixed) => { + Vue.ls.set(DEFAULT_FIXED_SIDEMENU, fixed) + state.fixSiderbar = fixed + }, + TOGGLE_FIXED_HEADER_HIDDEN: (state, show) => { + Vue.ls.set(DEFAULT_FIXED_HEADER_HIDDEN, show) + state.autoHideHeader = show + }, + TOGGLE_CONTENT_WIDTH: (state, type) => { + Vue.ls.set(DEFAULT_CONTENT_WIDTH_TYPE, type) + state.contentWidth = type + }, + TOGGLE_COLOR: (state, color) => { + Vue.ls.set(DEFAULT_COLOR, color) + state.color = color + }, + TOGGLE_WEAK: (state, flag) => { + Vue.ls.set(DEFAULT_COLOR_WEAK, flag) + state.weak = flag + }, + TOGGLE_MULTI_TAB: (state, bool) => { + Vue.ls.set(DEFAULT_MULTI_TAB, bool) + state.multiTab = bool + }, + SET_HAS_ERROR: (state, bool) => { + state.hasError = bool + } + }, + actions: { + setSidebar ({ commit }, type) { + commit('SET_SIDEBAR_TYPE', type) + }, + CloseSidebar ({ commit }) { + commit('CLOSE_SIDEBAR') + }, + ToggleDevice ({ commit }, device) { + commit('TOGGLE_DEVICE', device) + }, + ToggleTheme ({ commit }, theme) { + commit('TOGGLE_THEME', theme) + }, + ToggleLayoutMode ({ commit }, mode) { + commit('TOGGLE_LAYOUT_MODE', mode) + }, + ToggleFixedHeader ({ commit }, fixedHeader) { + if (!fixedHeader) { + commit('TOGGLE_FIXED_HEADER_HIDDEN', false) + } + commit('TOGGLE_FIXED_HEADER', fixedHeader) + }, + ToggleFixSiderbar ({ commit }, fixSiderbar) { + commit('TOGGLE_FIXED_SIDERBAR', fixSiderbar) + }, + ToggleFixedHeaderHidden ({ commit }, show) { + commit('TOGGLE_FIXED_HEADER_HIDDEN', show) + }, + ToggleContentWidth ({ commit }, type) { + commit('TOGGLE_CONTENT_WIDTH', type) + }, + ToggleColor ({ commit }, color) { + commit('TOGGLE_COLOR', color) + }, + ToggleWeak ({ commit }, weakFlag) { + commit('TOGGLE_WEAK', weakFlag) + }, + ToggleMultiTab ({ commit }, bool) { + commit('TOGGLE_MULTI_TAB', bool) + }, + SetHasError ({ commit }, bool) { + commit('SET_HAS_ERROR', bool) + } + } +} + +export default app diff --git a/_web/src/store/modules/async-router.js b/_web/src/store/modules/async-router.js new file mode 100644 index 0000000..4491e06 --- /dev/null +++ b/_web/src/store/modules/async-router.js @@ -0,0 +1,33 @@ +/** + * 向后端请求用户的菜单,动态生成路由 + */ +import { constantRouterMap } from '@/config/router.config' +import { generatorDynamicRouter } from '@/router/generator-routers' + +const permission = { + state: { + routers: constantRouterMap, + addRouters: [] + }, + mutations: { + SET_ROUTERS: (state, routers) => { + state.addRouters = routers + state.routers = constantRouterMap.concat(routers) + } + }, + actions: { + GenerateRoutes ({ commit }, data) { + return new Promise(resolve => { + generatorDynamicRouter(data).then(routers => { + commit('SET_ROUTERS', routers) + resolve() + }) + }).catch(err => { + // eslint-disable-next-line no-undef + reject(err) + }) + } + } +} + +export default permission diff --git a/_web/src/store/modules/permission.js b/_web/src/store/modules/permission.js new file mode 100644 index 0000000..0e5e5c3 --- /dev/null +++ b/_web/src/store/modules/permission.js @@ -0,0 +1,77 @@ +import { asyncRouterMap, constantRouterMap } from '@/config/router.config' + +/** + * 过滤账户是否拥有某一个权限,并将菜单从加载列表移除 + * + * @param permission + * @param route + * @returns {boolean} + */ +function hasPermission (permission, route) { + // if (route.meta && route.meta.permission) { + // let flag = false + // for (let i = 0, len = permission.length; i < len; i++) { + // flag = route.meta.permission.includes(permission[i]) + // if (flag) { + // return true + // } + // } + // return false + // } + return true +} + +/** + * 单账户多角色时,使用该方法可过滤角色不存在的菜单 + * + * @param roles + * @param route + * @returns {*} + */ +// eslint-disable-next-line +function hasRole(roles, route) { + if (route.meta && route.meta.roles) { + return route.meta.roles.includes(roles.id) + } else { + return true + } +} + +function filterAsyncRouter (routerMap, roles) { + const accessedRouters = routerMap.filter(route => { + if (hasPermission(roles.permissionList, route)) { + if (route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, roles) + } + return true + } + return false + }) + return accessedRouters +} + +const permission = { + state: { + routers: constantRouterMap, + addRouters: [] + }, + mutations: { + SET_ROUTERS: (state, routers) => { + state.addRouters = routers + state.routers = constantRouterMap.concat(routers) + } + }, + actions: { + GenerateRoutes ({ commit }, data) { + return new Promise(resolve => { + const { roles } = data + const accessedRouters = filterAsyncRouter(asyncRouterMap, roles) + // console.log('动态获取到的菜单列表:'+JSON.stringify(accessedRouters)) + commit('SET_ROUTERS', accessedRouters) + resolve() + }) + } + } +} + +export default permission diff --git a/_web/src/store/modules/user.js b/_web/src/store/modules/user.js new file mode 100644 index 0000000..0382b32 --- /dev/null +++ b/_web/src/store/modules/user.js @@ -0,0 +1,172 @@ +import Vue from 'vue' +import { login, getLoginUser, logout } from '@/api/modular/system/loginManage' +import { sysDictTypeTree } from '@/api/modular/system/dictManage' +import { sysMenuChange } from '@/api/modular/system/menuManage' +import { ACCESS_TOKEN, ALL_APPS_MENU, DICT_TYPE_TREE_DATA } from '@/store/mutation-types' + +import { welcome } from '@/utils/util' +import store from '../index' +import router from '../../router' + +const user = { + state: { + token: '', + name: '', + welcome: '', + avatar: '', + buttons: [], // 按钮权限 + admintype: '', // 是否是超管 + roles: [], + info: {} + }, + + mutations: { + SET_TOKEN: (state, token) => { + state.token = token + }, + SET_NAME: (state, { name, welcome }) => { + state.name = name + state.welcome = welcome + }, + SET_AVATAR: (state, avatar) => { + state.avatar = avatar + }, + SET_ROLES: (state, roles) => { + state.roles = roles + }, + SET_INFO: (state, info) => { + state.info = info + }, + SET_BUTTONS: (state, buttons) => { + state.buttons = buttons + }, + SET_ADMINTYPE: (state, admintype) => { + state.admintype = admintype + } + }, + + actions: { + // 登录 + Login ({ commit }, userInfo) { + return new Promise((resolve, reject) => { + login(userInfo).then(response => { + if (!response.success) { + reject(response.message) + return + } + const result = response.data + Vue.ls.set(ACCESS_TOKEN, result, 7 * 24 * 60 * 60 * 1000) + commit('SET_TOKEN', result) + resolve() + // eslint-disable-next-line handle-callback-err + }).catch(error => { + // eslint-disable-next-line prefer-promise-reject-errors + reject('后端未启动或代理错误') + }) + }) + }, + + // 获取用户信息 + GetInfo ({ commit }) { + return new Promise((resolve, reject) => { + getLoginUser().then(response => { + if (response.success) { + const data = response.data + commit('SET_ADMINTYPE', data.adminType) + commit('SET_ROLES', 1) + commit('SET_BUTTONS', data.permissions) + commit('SET_INFO', data) + commit('SET_NAME', { name: data.name, welcome: welcome() }) + if (data.avatar != null) { + commit('SET_AVATAR', process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + data.avatar) + } + resolve(data) + } else { + // eslint-disable-next-line no-undef + reject(new Error(data.message)) + } + }).catch(error => { + reject(error) + }) + }) + }, + + // 登出 + Logout ({ commit, state }) { + return new Promise((resolve) => { + logout(state.token).then(() => { + resolve() + }).catch(() => { + resolve() + }).finally(() => { + commit('SET_TOKEN', '') + commit('SET_ROLES', []) + commit('SET_BUTTONS', []) + commit('SET_ADMINTYPE', '') + Vue.ls.remove(ACCESS_TOKEN) + Vue.ls.remove(ALL_APPS_MENU) + Vue.ls.remove(DICT_TYPE_TREE_DATA) + }) + }) + }, + + // 加载所有字典数据 + dictTypeData () { + return new Promise((resolve, reject) => { + sysDictTypeTree().then((data) => { + if (data.success) { + const result = data.data + Vue.ls.set(DICT_TYPE_TREE_DATA, result) + resolve() + } else { + // eslint-disable-next-line no-undef + reject(new Error(data.message)) + } + }).catch(error => { + reject(error) + }) + }) + }, + + // 切换应用菜单 + MenuChange ({ commit }, application) { + return new Promise((resolve) => { + sysMenuChange({ application: application.code }).then((res) => { + const apps = { 'code': '', 'name': '', 'active': '', 'menu': '' } + apps.active = true + apps.menu = res.data + // eslint-disable-next-line camelcase + const all_app_menu = Vue.ls.get(ALL_APPS_MENU) + // eslint-disable-next-line camelcase + const new_false_all_app_menu = [] + // 先去除所有默认的,以为此时切换的即将成为前端缓存默认的应用 + all_app_menu.forEach(item => { + if (item.active) { + item.active = false + } + new_false_all_app_menu.push(item) + }) + // 此时缓存中全部都是不默认的应用 + Vue.ls.set(ALL_APPS_MENU, new_false_all_app_menu) + apps.name = application.name + apps.code = application.code + const applocationR = [] + applocationR.push(apps) + Vue.ls.set(ALL_APPS_MENU, applocationR) + resolve(res) + const antDesignmenus = res.data + store.dispatch('GenerateRoutes', { antDesignmenus }).then(() => { + router.addRoutes(store.getters.addRouters) + }) + // 切换应用刷新整体界面,暂且取消 + // window.location.reload() + }).catch(() => { + resolve() + }) + }) + } + + } +} + +export default user diff --git a/_web/src/store/mutation-types.js b/_web/src/store/mutation-types.js new file mode 100644 index 0000000..819c312 --- /dev/null +++ b/_web/src/store/mutation-types.js @@ -0,0 +1,18 @@ +export const ACCESS_TOKEN = 'Access-Token' +export const SIDEBAR_TYPE = 'SIDEBAR_TYPE' +export const ALL_APPS_MENU = 'ALL_APPS_MENU' +export const DEFAULT_THEME = 'DEFAULT_THEME' +export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE' +export const DEFAULT_COLOR = 'DEFAULT_COLOR' +export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK' +export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER' +export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU' +export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN' +export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE' +export const DEFAULT_MULTI_TAB = 'DEFAULT_MULTI_TAB' +export const DICT_TYPE_TREE_DATA = 'DICT_TYPE_TREE_DATA' + +export const CONTENT_WIDTH_TYPE = { + Fluid: 'Fluid', + Fixed: 'Fixed' +} diff --git a/_web/src/utils/applocation.js b/_web/src/utils/applocation.js new file mode 100644 index 0000000..30fdc28 --- /dev/null +++ b/_web/src/utils/applocation.js @@ -0,0 +1,11 @@ +import store from '@/store' + +/** + * 缓存中的已选中应用 + * + * @author yubaoshan + * @date 2020/06/27 02:34 + */ +export function sysApplication () { + return store.getters.applocation +} diff --git a/_web/src/utils/axios.js b/_web/src/utils/axios.js new file mode 100644 index 0000000..3b91f6b --- /dev/null +++ b/_web/src/utils/axios.js @@ -0,0 +1,35 @@ +const VueAxios = { + vm: {}, + // eslint-disable-next-line no-unused-vars + install (Vue, instance) { + if (this.installed) { + return + } + this.installed = true + + if (!instance) { + // eslint-disable-next-line no-console + console.error('You have to install axios') + return + } + + Vue.axios = instance + + Object.defineProperties(Vue.prototype, { + axios: { + get: function get () { + return instance + } + }, + $http: { + get: function get () { + return instance + } + } + }) + } +} + +export { + VueAxios +} diff --git a/_web/src/utils/device.js b/_web/src/utils/device.js new file mode 100644 index 0000000..0f350f3 --- /dev/null +++ b/_web/src/utils/device.js @@ -0,0 +1,33 @@ +import enquireJs from 'enquire.js' + +export const DEVICE_TYPE = { + DESKTOP: 'desktop', + TABLET: 'tablet', + MOBILE: 'mobile' +} + +export const deviceEnquire = function (callback) { + const matchDesktop = { + match: () => { + callback && callback(DEVICE_TYPE.DESKTOP) + } + } + + const matchLablet = { + match: () => { + callback && callback(DEVICE_TYPE.TABLET) + } + } + + const matchMobile = { + match: () => { + callback && callback(DEVICE_TYPE.MOBILE) + } + } + + // screen and (max-width: 1087.99px) + enquireJs + .register('screen and (max-width: 576px)', matchMobile) + .register('screen and (min-width: 576px) and (max-width: 1199px)', matchLablet) + .register('screen and (min-width: 1200px)', matchDesktop) +} diff --git a/_web/src/utils/domUtil.js b/_web/src/utils/domUtil.js new file mode 100644 index 0000000..23662b2 --- /dev/null +++ b/_web/src/utils/domUtil.js @@ -0,0 +1,19 @@ +export const setDocumentTitle = function (title) { + document.title = title + const ua = navigator.userAgent + // eslint-disable-next-line + const regex = /\bMicroMessenger\/([\d\.]+)/ + if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) { + const i = document.createElement('iframe') + i.src = '/favicon.ico' + i.style.display = 'none' + i.onload = function () { + setTimeout(function () { + i.remove() + }, 9) + } + document.body.appendChild(i) + } +} + +export const domTitle = 'Snowy' diff --git a/_web/src/utils/filter.js b/_web/src/utils/filter.js new file mode 100644 index 0000000..5f10322 --- /dev/null +++ b/_web/src/utils/filter.js @@ -0,0 +1,101 @@ +import Vue from 'vue' +import { DICT_TYPE_TREE_DATA } from '@/store/mutation-types' +import moment from 'moment' +import 'moment/locale/zh-cn' +moment.locale('zh-cn') + +Vue.filter('NumberFormat', function (value) { + if (!value) { + return '0' + } + const intPartFormat = value.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') // 将整数部分逢三一断 + return intPartFormat +}) + +Vue.filter('dayjs', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { + return moment(dataStr).format(pattern) +}) + +Vue.filter('moment', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { + return moment(dataStr).format(pattern) +}) + +/** + * 金额格式化 ,使用方法:{{ val | Fmoney }} + * + * @author yubaoshan + * @date 2020-9-15 15:02:20 + */ +Vue.filter('Fmoney', function (val) { + // eslint-disable-next-line no-useless-escape + val = val.toString().replace(/\$|\,/g, '') + if (isNaN(val)) { + val = '0' + } + // eslint-disable-next-line eqeqeq + const sign = (val == (val = Math.abs(val))) + val = Math.floor(val * 100 + 0.50000000001) + let cents = val % 100 + val = Math.floor(val / 100).toString() + if (cents < 10) { + cents = '0' + cents + } + // eslint-disable-next-line no-undef + for (let i = 0; i < Math.floor((val.length - (1 + i)) / 3); I++) { + val = val.substring(0, val.length - (4 * i + 3)) + ',' + val.substring(val.length - (4 * i + 3)) + } + return (((sign) ? '' : '-') + val + '.' + cents) +}) + +/** + * 翻译使用方法,直接返回翻译后的name {{ code | dictType(value) }} + * + * @author yubaoshan + * @date 2020-9-15 15:02:20 + */ +Vue.filter('dictType', function (code, value) { + const dictTypeTree = Vue.ls.get(DICT_TYPE_TREE_DATA) + if (dictTypeTree === undefined) { + return '需重新登录' + } + // eslint-disable-next-line eqeqeq + const tree = dictTypeTree.filter(item => item.code == code)[0].children + if (tree === undefined || tree.length === 0) { + return '无此字典' + } + // eslint-disable-next-line eqeqeq + const values = tree.filter(item => item.code == value) + if (values.length === undefined || values.length === 0) { + return '无此字典' + } + return values[0].name +}) + +/** + * 获取某个code下字典的列表,多用于字典下拉框,使用方法:{{ code | dictData }} + * + * @author yubaoshan + * @date 2020-9-19 22:40:22 + */ +Vue.filter('dictData', function (code) { + const dictTypeTree = Vue.ls.get(DICT_TYPE_TREE_DATA) + if (dictTypeTree === undefined) { + return [] + } + // eslint-disable-next-line eqeqeq + const tree = dictTypeTree.filter(item => item.code == code)[0].children + if (tree === undefined) { + return [] + } + return tree +}) + +/** + * 获取所有字典数组 + * + * @author yubaoshan + * @date 2021-2-8 01:13 + */ +Vue.filter('dictDataAll', function () { + return Vue.ls.get(DICT_TYPE_TREE_DATA) +}) diff --git a/_web/src/utils/helper/permission.js b/_web/src/utils/helper/permission.js new file mode 100644 index 0000000..f0f6a32 --- /dev/null +++ b/_web/src/utils/helper/permission.js @@ -0,0 +1,51 @@ +export const PERMISSION_ENUM = { + 'add': { key: 'add', label: '新增' }, + 'delete': { key: 'delete', label: '删除' }, + 'edit': { key: 'edit', label: '修改' }, + 'query': { key: 'query', label: '查询' }, + 'get': { key: 'get', label: '详情' }, + 'enable': { key: 'enable', label: '启用' }, + 'disable': { key: 'disable', label: '禁用' }, + 'import': { key: 'import', label: '导入' }, + 'export': { key: 'export', label: '导出' } +} + +function plugin (Vue) { + if (plugin.installed) { + return + } + + !Vue.prototype.$auth && Object.defineProperties(Vue.prototype, { + $auth: { + get () { + const _this = this + return (permissions) => { + const [permission, action] = permissions.split('.') + const permissionList = _this.$store.getters.roles.permissions + return permissionList.find((val) => { + return val.permissionId === permission + }).actionList.findIndex((val) => { + return val === action + }) > -1 + } + } + } + }) + + !Vue.prototype.$enum && Object.defineProperties(Vue.prototype, { + $enum: { + get () { + // const _this = this; + return (val) => { + let result = PERMISSION_ENUM + val && val.split('.').forEach(v => { + result = result && result[v] || null + }) + return result + } + } + } + }) +} + +export default plugin diff --git a/_web/src/utils/mixin.js b/_web/src/utils/mixin.js new file mode 100644 index 0000000..217732d --- /dev/null +++ b/_web/src/utils/mixin.js @@ -0,0 +1,76 @@ +// import Vue from 'vue' +import { deviceEnquire, DEVICE_TYPE } from '@/utils/device' +import { mapState } from 'vuex' + +// const mixinsComputed = Vue.config.optionMergeStrategies.computed +// const mixinsMethods = Vue.config.optionMergeStrategies.methods + +const mixin = { + computed: { + ...mapState({ + layoutMode: state => state.app.layout, + navTheme: state => state.app.theme, + primaryColor: state => state.app.color, + colorWeak: state => state.app.weak, + fixedHeader: state => state.app.fixedHeader, + fixSiderbar: state => state.app.fixSiderbar, + fixSidebar: state => state.app.fixSiderbar, + contentWidth: state => state.app.contentWidth, + autoHideHeader: state => state.app.autoHideHeader, + sidebarOpened: state => state.app.sidebar, + multiTab: state => state.app.multiTab + }) + }, + methods: { + isTopMenu () { + return this.layoutMode === 'topmenu' + }, + isSideMenu () { + return !this.isTopMenu() + } + } +} + +const mixinDevice = { + computed: { + ...mapState({ + device: state => state.app.device + }) + }, + methods: { + isMobile () { + return this.device === DEVICE_TYPE.MOBILE + }, + isDesktop () { + return this.device === DEVICE_TYPE.DESKTOP + }, + isTablet () { + return this.device === DEVICE_TYPE.TABLET + } + } +} + +const AppDeviceEnquire = { + mounted () { + const { $store } = this + deviceEnquire(deviceType => { + switch (deviceType) { + case DEVICE_TYPE.DESKTOP: + $store.commit('TOGGLE_DEVICE', 'desktop') + $store.dispatch('setSidebar', true) + break + case DEVICE_TYPE.TABLET: + $store.commit('TOGGLE_DEVICE', 'tablet') + $store.dispatch('setSidebar', false) + break + case DEVICE_TYPE.MOBILE: + default: + $store.commit('TOGGLE_DEVICE', 'mobile') + $store.dispatch('setSidebar', true) + break + } + }) + } +} + +export { mixin, AppDeviceEnquire, mixinDevice } diff --git a/_web/src/utils/onlyofficeUtil.js b/_web/src/utils/onlyofficeUtil.js new file mode 100644 index 0000000..845e5a9 --- /dev/null +++ b/_web/src/utils/onlyofficeUtil.js @@ -0,0 +1,22 @@ +export function handleDocType(fileType) { + let docType = '' + const fileTypesDoc = [ + 'doc', 'docm', 'docx', 'dot', 'dotm', 'dotx', 'epub', 'fodt', 'htm', 'html', 'mht', 'odt', 'ott', 'pdf', 'rtf', 'txt', 'djvu', 'xps' + ] + const fileTypesCsv = [ + 'csv', 'fods', 'ods', 'ots', 'xls', 'xlsm', 'xlsx', 'xlt', 'xltm', 'xltx' + ] + const fileTypesPPt = [ + 'fodp', 'odp', 'otp', 'pot', 'potm', 'potx', 'pps', 'ppsm', 'ppsx', 'ppt', 'pptm', 'pptx' + ] + if (fileTypesDoc.includes(fileType)) { + docType = 'text' + } + if (fileTypesCsv.includes(fileType)) { + docType = 'spreadsheet' + } + if (fileTypesPPt.includes(fileType)) { + docType = 'presentation' + } + return docType +} diff --git a/_web/src/utils/permissions.js b/_web/src/utils/permissions.js new file mode 100644 index 0000000..ce0376f --- /dev/null +++ b/_web/src/utils/permissions.js @@ -0,0 +1,26 @@ +import store from '@/store' + +export function actionToObject (json) { + try { + return JSON.parse(json) + } catch (e) { + console.log('err', e.message) + } + return [] +} + +/** + * 控制按钮是否显示 + * + * @author yubaoshan + * @date 2020/06/27 02:34 + */ +export function hasBtnPermission (permission) { + const myBtns = store.getters.buttons + const admintype = store.getters.admintype + // eslint-disable-next-line eqeqeq + if (admintype == '1') { + return true + } + return myBtns.indexOf(permission) > -1 +} diff --git a/_web/src/utils/request.js b/_web/src/utils/request.js new file mode 100644 index 0000000..33520a4 --- /dev/null +++ b/_web/src/utils/request.js @@ -0,0 +1,96 @@ +import Vue from 'vue' +import axios from 'axios' +import store from '@/store' +// import router from './router' +import { message, Modal, notification } from 'ant-design-vue' /// es/notification +import { VueAxios } from './axios' +import { ACCESS_TOKEN } from '@/store/mutation-types' + +// 创建 axios 实例 +const service = axios.create({ + baseURL: '/api', // api base_url + timeout: 6000 // 请求超时时间 +}) + +const err = (error) => { + if (error.response) { + const data = error.response.data + const token = Vue.ls.get(ACCESS_TOKEN) + + if (error.response.status === 403) { + console.log('服务器403啦,要重新登录!') + notification.error({ + message: 'Forbidden', + description: data.message + }) + } + if (error.response.status === 500) { + if (data.message.length > 0) { + message.error(data.message) + } + } + if (error.response.status === 401 && !(data.result && data.result.isLogin)) { + if (token) { + store.dispatch('Logout').then(() => { + setTimeout(() => { + window.location.reload() + }, 1500) + }) + } + } + } + return Promise.reject(error) +} + +// request interceptor +service.interceptors.request.use(config => { + const token = Vue.ls.get(ACCESS_TOKEN) + if (token) { + config.headers['Authorization'] = 'Bearer ' + token + } + return config +}, err) + +/** + * response interceptor + * 所有请求统一返回 + */ +service.interceptors.response.use((response) => { + if (response.request.responseType === 'blob') { + return response + } + const resData = response.data + const code = response.data.code + if (!store.state.app.hasError) { + if (code === 1011006 || code === 1011007 || code === 1011008 || code === 1011009) { + Modal.error({ + title: '提示:', + content: resData.message, + okText: '重新登录', + onOk: () => { + Vue.ls.remove(ACCESS_TOKEN) + store.dispatch('SetHasError', false) + window.location.reload() + } + }) + store.dispatch('SetHasError', true) + } + if (code === 1013002 || code === 1016002 || code === 1015002) { + message.error(response.data.message) + return response.data + } + } + return resData +}, err) + +const installer = { + vm: {}, + install (Vue) { + Vue.use(VueAxios, service) + } +} + +export { + installer as VueAxios, + service as axios +} diff --git a/_web/src/utils/routeConvert.js b/_web/src/utils/routeConvert.js new file mode 100644 index 0000000..e88b0d6 --- /dev/null +++ b/_web/src/utils/routeConvert.js @@ -0,0 +1,30 @@ +import cloneDeep from 'lodash.clonedeep' + +export function convertRoutes (nodes) { + if (!nodes) return null + + nodes = cloneDeep(nodes) + + let queue = Array.isArray(nodes) ? nodes.concat() : [nodes] + + while (queue.length) { + const levelSize = queue.length + + for (let i = 0; i < levelSize; i++) { + const node = queue.shift() + + if (!node.children || !node.children.length) continue + + node.children.forEach(child => { + // 转化相对路径 + if (child.path[0] !== '/' && !child.path.startsWith('http')) { + child.path = node.path.replace(/(\w*)[/]*$/, `$1/${child.path}`) + } + }) + + queue = queue.concat(node.children) + } + } + + return nodes +} diff --git a/_web/src/utils/util.js b/_web/src/utils/util.js new file mode 100644 index 0000000..311f746 --- /dev/null +++ b/_web/src/utils/util.js @@ -0,0 +1,67 @@ +export function timeFix () { + const time = new Date() + const hour = time.getHours() + return hour < 9 ? '早上好' : hour <= 11 ? '上午好' : hour <= 13 ? '中午好' : hour < 20 ? '下午好' : '晚上好' +} + +export function welcome () { + const arr = ['休息一会儿吧', '准备吃什么呢?', '要不要打一把 LOL', '我猜你可能累了'] + const index = Math.floor(Math.random() * arr.length) + return arr[index] +} + +/** + * 触发 window.resize + */ +export function triggerWindowResizeEvent () { + const event = document.createEvent('HTMLEvents') + event.initEvent('resize', true, true) + event.eventType = 'message' + window.dispatchEvent(event) +} + +export function handleScrollHeader (callback) { + let timer = 0 + + let beforeScrollTop = window.pageYOffset + callback = callback || function () {} + window.addEventListener( + 'scroll', + event => { + clearTimeout(timer) + timer = setTimeout(() => { + let direction = 'up' + const afterScrollTop = window.pageYOffset + const delta = afterScrollTop - beforeScrollTop + if (delta === 0) { + return false + } + direction = delta > 0 ? 'down' : 'up' + callback(direction) + beforeScrollTop = afterScrollTop + }, 50) + }, + false + ) +} + +export function isIE () { + const bw = window.navigator.userAgent + const compare = (s) => bw.indexOf(s) >= 0 + const ie11 = (() => 'ActiveXObject' in window)() + return compare('MSIE') || ie11 +} + +/** + * Remove loading animate + * @param id parent element id or class + * @param timeout + */ +export function removeLoadingAnimate (id = '', timeout = 1500) { + if (id === '') { + return + } + setTimeout(() => { + document.body.removeChild(document.getElementById(id)) + }, timeout) +} diff --git a/_web/src/utils/utils.less b/_web/src/utils/utils.less new file mode 100644 index 0000000..ba75a67 --- /dev/null +++ b/_web/src/utils/utils.less @@ -0,0 +1,50 @@ +.textOverflow() { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + word-break: break-all; +} + +.textOverflowMulti(@line: 3, @bg: #fff) { + position: relative; + max-height: @line * 1.5em; + margin-right: -1em; + padding-right: 1em; + overflow: hidden; + line-height: 1.5em; + text-align: justify; + &::before { + position: absolute; + right: 14px; + bottom: 0; + padding: 0 1px; + background: @bg; + content: '...'; + } + &::after { + position: absolute; + right: 14px; + width: 1em; + height: 1em; + margin-top: 0.2em; + background: white; + content: ''; + } +} + +// mixins for clearfix +// ------------------------ +.clearfix() { + zoom: 1; + &::before, + &::after { + display: table; + content: ' '; + } + &::after { + clear: both; + height: 0; + font-size: 0; + visibility: hidden; + } +} \ No newline at end of file diff --git a/_web/src/views/404.vue b/_web/src/views/404.vue new file mode 100644 index 0000000..8c1d8a1 --- /dev/null +++ b/_web/src/views/404.vue @@ -0,0 +1,15 @@ +<template> + <div> + 404 page + </div> +</template> + +<script> +export default { + name: '404' +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/gen/codeGenerate/addForm.vue b/_web/src/views/gen/codeGenerate/addForm.vue new file mode 100644 index 0000000..3430635 --- /dev/null +++ b/_web/src/views/gen/codeGenerate/addForm.vue @@ -0,0 +1,316 @@ +<template> + <a-modal + title="新增代码生成配置" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="生成表" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择数据库表" v-decorator="['tableName', {rules: [{ required: true, message: '请选择数据库表!' }]}]" > + <a-select-option v-for="(item,index) in tableNameData" :key="index" :value="item.tableName" @click="tableNameSele(item)">{{ item.tableName }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="移除前缀" + > + <a-radio-group v-decorator="['tablePrefix',{rules: [{ required: true, message: '请选择是否移除前缀!' }]}]" > + <a-radio v-for="(item,index) in tablePrefixData" :key="index" :value="item.code" @click="tablePrefixRadio(item.code)">{{ item.name }}</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="功能名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入功能名" v-decorator="['tableComment', {rules: [{required: true, message: '请输入功能名!'}]}]" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + label="类名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入类名" v-decorator="['className', {rules: [{required: true, message: '请输入类名!'}]}]" /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="业务名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入业务名" v-decorator="['busName', {rules: [{required: true, message: '请输入业务名!'}]}]" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="生成方式" + > + <a-radio-group v-decorator="['generateType',{rules: [{ required: true, message: '请选择生成方式!' }]}]" > + <a-radio v-for="(item,index) in generateTypeData" :key="index" :value="item.code" @click="generateTypeRadio(item.code)">{{ item.name }}</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="所属应用" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择应用分类" v-decorator="['appCode', {rules: [{ required: true, message: '请选择应用分类!' }]}]" > + <a-select-option v-for="(item,index) in appData" :key="index" :value="item.code" @click="changeApplication(item.code)">{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="父级菜单" + has-feedback + > + <a-tree-select + v-decorator="['menuPid', {rules: [{ required: true, message: '请选择父级菜单!' }]}]" + style="width: 100%" + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + :treeData="menuTreeData" + placeholder="请选择父级菜单" + treeDefaultExpandAll + > + <span slot="title" slot-scope="{ id }">{{ id }} + </span> + </a-tree-select> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="作者姓名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入作者姓名" v-decorator="['authorName', {rules: [{required: true, message: '请输入作者姓名!'}]}]" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24" v-show="packageNameShow"> + <a-form-item + label="代码包名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入代码包名" v-decorator="['packageName', {rules: [{required: true, message: '请输入代码包名!'}]}]" /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </a-spin> + </a-modal> +</template> + +<script> + import { codeGenerateInformationList, codeGenerateAdd } from '@/api/modular/gen/codeGenerateManage' + import { getAppList } from '@/api/modular/system/appManage' + import { getMenuTree } from '@/api/modular/system/menuManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + tableNameData: [], + tablePrefixData: [], + generateTypeData: [], + confirmLoading: false, + tablePrefixValue: 'N', + tableNameValue: '', + packageNameShow: true, + appData: [], + menuTreeData: [], + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add () { + this.visible = true + this.codeGenerateInformationList() + this.dataTypeItem() + this.selectedByDefault() + this.getSysApplist() + }, + /** + * 默认选中项 + */ + selectedByDefault () { + this.form.getFieldDecorator('packageName', { initialValue: 'vip.xiaonuo' }) + this.form.getFieldDecorator('tablePrefix', { valuePropName: 'checked', initialValue: 'N' }) + this.form.getFieldDecorator('generateType', { valuePropName: 'checked', initialValue: '1' }) + this.tablePrefixValue = 'N' + }, + /** + * 获得所有数据库的表 + */ + codeGenerateInformationList () { + codeGenerateInformationList().then((res) => { + this.tableNameData = res.data + }) + }, + /** + * 获取应用列表 + */ + getSysApplist () { + return getAppList().then((res) => { + if (res.success) { + this.appData = res.data + } else { + this.$message.warning(res.message) + } + }) + }, + /** + * 通过应用获取菜单 + */ + changeApplication (value) { + this.form.resetFields(`menuPid`, []) + getMenuTree({ 'application': value }).then((res) => { + if (res.success) { + this.menuTreeData = [{ + 'id': '-1', + 'parentId': '0', + 'title': '顶级', + 'value': '0', + 'pid': '0', + 'children': res.data + }] + } else { + this.$message.warning(res.message) + } + }) + }, + /** + * 获取字典数据 + */ + dataTypeItem () { + this.tablePrefixData = this.$options.filters['dictData']('yes_or_no') + this.generateTypeData = this.$options.filters['dictData']('code_gen_create_type') + }, + /** + * 提交表单 + */ + handleSubmit () { + const { form: { validateFields } } = this + validateFields((errors, values) => { + if (!errors) { + this.confirmLoading = true + codeGenerateAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + // 清空他们三个 + this.form.getFieldDecorator('className', { initialValue: '' }) + this.form.getFieldDecorator('busName', { initialValue: '' }) + this.form.getFieldDecorator('tableComment', { initialValue: '' }) + }, + /** + * 选择数据库列表 + */ + tableNameSele (item) { + this.tableNameValue = item.tableName + this.form.getFieldDecorator('tableComment', { initialValue: item.tableComment }) + this.settingDefaultValue() + }, + /** + * 选择是否移除前缀触发 + */ + tablePrefixRadio (tablePrefixType) { + this.tablePrefixValue = tablePrefixType + this.settingDefaultValue() + }, + /** + * 设置默认值 + */ + settingDefaultValue () { + const tableName = this.classNameToHump() + this.form.getFieldDecorator('className', { initialValue: tableName }) + this.form.getFieldDecorator('busName', { initialValue: tableName.toLowerCase() }) + }, + /** + * 设置类名为数据库表的驼峰命名 + */ + classNameToHump () { + const arr = this.tableNameValue.toLowerCase().split('_') + if (this.tablePrefixValue === 'Y') { + arr.splice(0, 1) + } + for (let i = 0; i < arr.length; i++) { + // charAt()方法得到第一个字母,slice()得到第二个字母以后的字符串 + arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1) + } + return arr.join('') + }, + /** + * 选择生成方式 + */ + generateTypeRadio (generateType) { + if (generateType === '1') { + this.packageNameShow = true + } else { + this.packageNameShow = false + this.form.setFieldsValue({ packageName: 'vip.xiaonuo' }) + } + } + } + } +</script> diff --git a/_web/src/views/gen/codeGenerate/editForm.vue b/_web/src/views/gen/codeGenerate/editForm.vue new file mode 100644 index 0000000..3843845 --- /dev/null +++ b/_web/src/views/gen/codeGenerate/editForm.vue @@ -0,0 +1,327 @@ +<template> + <a-modal + title="编辑代码生成配置" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item v-show="false"><a-input v-decorator="['id']" /></a-form-item> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="生成表" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择数据库表" v-decorator="['tableName', {rules: [{ required: true, message: '请选择数据库表!' }]}]" > + <a-select-option v-for="(item,index) in tableNameData" :key="index" :value="item.tableName" @click="tableNameSele(item)">{{ item.tableName }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="移除前缀" + > + <a-radio-group v-decorator="['tablePrefix',{rules: [{ required: true, message: '请选择是否移除前缀!' }]}]" > + <a-radio v-for="(item,index) in tablePrefixData" :key="index" :value="item.code" @click="tablePrefixRadio(item.code)">{{ item.name }}</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="功能名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入功能名" v-decorator="['tableComment', {rules: [{required: true, message: '请输入功能名!'}]}]" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + label="类名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入类名" v-decorator="['className', {rules: [{required: true, message: '请输入类名!'}]}]" /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="业务名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入业务名" v-decorator="['busName', {rules: [{required: true, message: '请输入业务名!'}]}]" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="生成方式" + > + <a-radio-group v-decorator="['generateType',{rules: [{ required: true, message: '请选择生成方式!' }]}]" > + <a-radio v-for="(item,index) in generateTypeData" :key="index" :value="item.code" @click="generateTypeRadio(item.code)">{{ item.name }}</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="所属应用" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择应用分类" v-decorator="['appCode', {rules: [{ required: true, message: '请选择应用分类!' }]}]" > + <a-select-option v-for="(item,index) in appData" :key="index" :value="item.code" @click="changeApplication(item.code)">{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="父级菜单" + has-feedback + > + <a-tree-select + v-decorator="['menuPid', {rules: [{ required: true, message: '请选择父级菜单!' }]}]" + style="width: 100%" + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + :treeData="menuTreeData" + placeholder="请选择父级菜单" + treeDefaultExpandAll + > + <span slot="title" slot-scope="{ id }">{{ id }} + </span> + </a-tree-select> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="作者姓名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入作者姓名" v-decorator="['authorName', {rules: [{required: true, message: '请输入作者姓名!'}]}]" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24" v-show="packageNameShow"> + <a-form-item + label="代码包名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入代码包名" v-decorator="['packageName', {rules: [{required: true, message: '请输入代码包名!'}]}]" /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </a-spin> + </a-modal> +</template> + +<script> + import { codeGenerateInformationList, codeGenerateEdit } from '@/api/modular/gen/codeGenerateManage' + import { getAppList } from '@/api/modular/system/appManage' + import { getMenuTree } from '@/api/modular/system/menuManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + tableNameData: [], + tablePrefixData: [], + generateTypeData: [], + confirmLoading: false, + tablePrefixValue: 'N', + tableNameValue: '', + packageNameShow: true, + appData: [], + menuTreeData: [], + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + this.codeGenerateInformationList() + this.dataTypeItem() + this.getSysApplist() + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + tableName: record.tableName, + tablePrefix: record.tablePrefix, + tableComment: record.tableComment, + className: record.className, + busName: record.busName, + generateType: record.generateType, + authorName: record.authorName, + packageName: record.packageName, + appCode: record.appCode, + menuPid: record.menuPid + } + ) + }, 100) + this.changeApplication(record.appCode) + this.tableNameValue = record.tableName + this.tablePrefixValue = record.tablePrefix + }, + /** + * 获得所有数据库的表 + */ + codeGenerateInformationList () { + codeGenerateInformationList().then((res) => { + this.tableNameData = res.data + }) + }, + /** + * 获取应用列表 + */ + getSysApplist () { + return getAppList().then((res) => { + if (res.success) { + this.appData = res.data + } else { + this.$message.warning(res.message) + } + }) + }, + /** + * 通过应用获取菜单 + */ + changeApplication (value) { + this.form.resetFields(`menuPid`, []) + getMenuTree({ 'application': value }).then((res) => { + if (res.success) { + this.menuTreeData = [{ + 'id': '-1', + 'parentId': '0', + 'title': '顶级', + 'value': '0', + 'pid': '0', + 'children': res.data + }] + } else { + this.$message.warning(res.message) + } + }) + }, + /** + * 获取字典数据 + */ + dataTypeItem () { + this.tablePrefixData = this.$options.filters['dictData']('yes_or_no') + this.generateTypeData = this.$options.filters['dictData']('code_gen_create_type') + }, + /** + * 提交表单 + */ + handleSubmit () { + const { form: { validateFields } } = this + validateFields((errors, values) => { + if (!errors) { + this.confirmLoading = true + codeGenerateEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('编辑失败' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + }, + /** + * 选择数据库列表 + */ + tableNameSele (item) { + this.tableNameValue = item.tableName + this.form.setFieldsValue({ className: item.tableComment }) + this.settingDefaultValue() + }, + /** + * 选择是否移除前缀触发 + */ + tablePrefixRadio (tablePrefixType) { + this.tablePrefixValue = tablePrefixType + this.settingDefaultValue() + }, + /** + * 设置默认值 + */ + settingDefaultValue () { + const tableName = this.classNameToHump() + this.form.setFieldsValue( + { + className: tableName, + busName: tableName.toLowerCase() + } + ) + }, + /** + * 设置类名为数据库表的驼峰命名 + */ + classNameToHump () { + const arr = this.tableNameValue.toLowerCase().split('_') + if (this.tablePrefixValue === 'Y') { + arr.splice(0, 1) + } + for (let i = 0; i < arr.length; i++) { + // charAt()方法得到第一个字母,slice()得到第二个字母以后的字符串 + arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].slice(1) + } + return arr.join('') + }, + /** + * 选择生成方式 + */ + generateTypeRadio (generateType) { + if (generateType === '1') { + this.packageNameShow = true + } else { + this.packageNameShow = false + this.form.setFieldsValue({ packageName: 'vip.xiaonuo' }) + } + } + } + } +</script> diff --git a/_web/src/views/gen/codeGenerate/index.vue b/_web/src/views/gen/codeGenerate/index.vue new file mode 100644 index 0000000..816903d --- /dev/null +++ b/_web/src/views/gen/codeGenerate/index.vue @@ -0,0 +1,263 @@ +<template> + <div> + <a-card :bordered="false" v-show="indexOpenShow" :bodyStyle="tstyle"> + <a-spin :spinning="Loading"> + <div class="table-page-search-wrapper" v-if="hasPerm('codeGenerate:page')"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="表名称" > + <a-input v-model="queryParam.tableName" allow-clear placeholder="请输入表名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </a-col> + </a-row> + </a-form> + </div> + </a-spin> + </a-card> + <a-card :bordered="false" v-show="indexOpenShow"> + <a-spin :spinning="Loading"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="true" + :rowKey="(record) => record.id" + :rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + > + <div slot="operator" v-if="hasPerm('codeGenerate:add')" > + <a-button type="primary" v-if="hasPerm('codeGenerate:add')" icon="plus" @click="$refs.addForm.add()">新增</a-button> + </div> + <span slot="tableName" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="packageName" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="busName" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="className" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="tableComment" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="tablePrefix" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ 'yes_or_no' | dictType(text) }}</ellipsis> + </span> + <span slot="generateType" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ 'code_gen_create_type' | dictType(text) }}</ellipsis> + </span> + <span slot="action" slot-scope="text, record"> + <span v-if="record.generateType === '1'"> + <a v-if="hasPerm('codeGenerate:runDown')" @click="runDownCodeGenerate(record)">开始生成</a> + </span> + <span v-else> + <a-popconfirm v-if="hasPerm('codeGenerate:runLocal')" placement="topRight" title="确定生成代码到本项目?" @confirm="() => runLocalCodeGenerate(record)"> + <a>开始生成</a> + </a-popconfirm> + </span> + <a-divider type="vertical" v-if="hasPerm('codeGenerate:config') & hasPerm('codeGenerate:runLocal') || hasPerm('codeGenerate:runDown') "/> + <a v-if="hasPerm('codeGenerate:config')" @click="indexConfigOpen(record)">配置</a> + <a-divider type="vertical" v-if="hasPerm('codeGenerate:config') & hasPerm('codeGenerate:edit')"/> + <a v-if="hasPerm('codeGenerate:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('codeGenerate:edit') & hasPerm('codeGenerate:delete')"/> + <a-popconfirm v-if="hasPerm('codeGenerate:delete')" placement="topRight" title="确认删除?" @confirm="() => codeGenerateDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" v-if="hasPerm('codeGenerate:add')"/> + <edit-form ref="editForm" @ok="handleOk" v-if="hasPerm('codeGenerate:edit')"/> + </a-spin> + </a-card> + <index-config ref="indexConfig" @ok="handleResetOpen" v-if="hasPerm('codeGenerate:config')"/> + </div> +</template> +<script> + import { STable, Ellipsis } from '@/components' + import { codeGeneratePage, codeGenerateDelete, codeGenerateRunDown, codeGenerateRunLocal } from '@/api/modular/gen/codeGenerateManage' + import addForm from './addForm' + import editForm from './editForm' + import indexConfig from './indexConfig' + + export default { + components: { + indexConfig, + STable, + Ellipsis, + addForm, + editForm + }, + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '表名称', + dataIndex: 'tableName', + scopedSlots: { customRender: 'tableName' } + }, + { + title: '代码包名', + dataIndex: 'packageName', + scopedSlots: { customRender: 'packageName' } + }, + { + title: '业务名', + dataIndex: 'busName', + scopedSlots: { customRender: 'busName' } + }, + { + title: '类名', + dataIndex: 'className', + scopedSlots: { customRender: 'className' } + }, + { + title: '功能名', + dataIndex: 'tableComment', + scopedSlots: { customRender: 'tableComment' } + }, + { + title: '作者姓名', + dataIndex: 'authorName' + }, + { + title: '表前缀移除', + dataIndex: 'tablePrefix', + scopedSlots: { customRender: 'tablePrefix' } + }, + { + title: '生成方式', + dataIndex: 'generateType', + scopedSlots: { customRender: 'generateType' } + } + ], + tstyle: { 'padding-bottom': '0px', 'margin-bottom': '10px' }, + loadData: parameter => { + return codeGeneratePage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + selectedRowKeys: [], + selectedRows: [], + Loading: false, + jdbcDriverList: [], + indexOpenShow: true + } + }, + created () { + if (this.hasPerm('codeGenerate:edit') || this.hasPerm('codeGenerate:delete')) { + this.columns.push({ + title: '操作', + width: '230px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + /** + * 开始生成代码(生成压缩包) + */ + runDownCodeGenerate (record) { + this.Loading = true + codeGenerateRunDown({ id: record.id }).then((res) => { + this.Loading = false + this.downloadfile(res) + // eslint-disable-next-line handle-callback-err + }).catch((err) => { + this.Loading = false + this.$message.error('下载错误:获取文件流错误') + }) + }, + downloadfile (res) { + var blob = new Blob([res.data], { type: 'application/octet-stream;charset=UTF-8' }) + var contentDisposition = res.headers['content-disposition'] + var patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*') + var result = patt.exec(contentDisposition) + var filename = result[1] + var downloadElement = document.createElement('a') + var href = window.URL.createObjectURL(blob) // 创建下载的链接 + var reg = /^["](.*)["]$/g + downloadElement.style.display = 'none' + downloadElement.href = href + downloadElement.download = decodeURI(filename.replace(reg, '$1')) // 下载后文件名 + document.body.appendChild(downloadElement) + downloadElement.click() // 点击下载 + document.body.removeChild(downloadElement) // 下载完成移除元素 + window.URL.revokeObjectURL(href) + }, + /** + * 开始生成代码(本地项目) + */ + runLocalCodeGenerate (record) { + codeGenerateRunLocal(record).then((res) => { + if (res.success) { + this.$message.success('生成成功') + this.$refs.table.refresh() + } else { + this.$message.error('生成失败:' + res.message) + } + }) + }, + /** + * 删除 + */ + codeGenerateDelete (record) { + this.Loading = true + codeGenerateDelete([{ id: record.id }]).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }).finally((res) => { + this.Loading = false + }) + }, + /** + * 打开配置界面 + */ + indexConfigOpen (record) { + this.indexOpenShow = false + this.$refs.indexConfig.open(record) + }, + /** + * 详细配置界面返回 + */ + handleResetOpen () { + this.indexOpenShow = true + this.$refs.table.refresh() + }, + /** + * 其他界面返回 + */ + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/gen/codeGenerate/indexConfig.vue b/_web/src/views/gen/codeGenerate/indexConfig.vue new file mode 100644 index 0000000..0f42731 --- /dev/null +++ b/_web/src/views/gen/codeGenerate/indexConfig.vue @@ -0,0 +1,230 @@ +<template> + <a-card :bordered="false" v-show="indexConfigShow"> + <div class="table-operator"> + <a-button class="but_item" type="dashed" @click="handleCancel" icon="rollback">返回</a-button> + <a-button type="primary" icon="plus" @click="handleSubmit">保存</a-button> + </div> + <a-table + ref="table" + size="middle" + :columns="columns" + :dataSource="loadData" + :pagination="false" + :alert="true" + :loading="tableLoading" + :rowKey="(record) => record.id" + > + <template slot="columnComment" slot-scope="text, record"> + <a-input v-model="record.columnComment" /> + </template> + <template slot="javaType" slot-scope="text, record"> + <a-select style="width: 120px" v-model="record.javaType" :disabled="judgeColumns(record)"> + <a-select-option v-for="(item,index) in javaTypeData" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> + </template> + <template slot="effectType" slot-scope="text, record"> + <a-select style="width: 120px" v-model="record.effectType" :disabled="judgeColumns(record)"> + <a-select-option v-for="(item,index) in effectTypeData" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> + </template> + <template slot="dictTypeCode" slot-scope="text, record"> + <a-select + style="width: 120px" + v-model="record.dictTypeCode" + :disabled="record.effectType !== 'radio' && + record.effectType !== 'select' && record.effectType !== 'checkbox'"> + <a-select-option v-for="(item,index) in dictDataAll" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> + </template> + <template slot="whetherTable" slot-scope="text, record"> + <a-checkbox v-model="record.whetherTable"/> + </template> + <template slot="whetherRetract" slot-scope="text, record"> + <a-checkbox v-model="record.whetherRetract"/> + </template> + <template slot="whetherAddUpdate" slot-scope="text, record"> + <a-checkbox v-model="record.whetherAddUpdate" :disabled="judgeColumns(record)"/> + </template> + <template slot="whetherRequired" slot-scope="text, record"> + <a-checkbox v-model="record.whetherRequired" :disabled="judgeColumns(record)"/> + </template> + <template slot="queryWhether" slot-scope="text, record"> + <a-switch v-model="record.queryWhether"> + <a-icon slot="checkedChildren" type="check" /> + <a-icon slot="unCheckedChildren" type="close" /> + </a-switch> + </template> + <template slot="queryType" slot-scope="text, record"> + <a-select style="width: 100px" v-model="record.queryType" :disabled="!record.queryWhether"> + <a-select-option v-for="(item,index) in codeGenQueryTypeData" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> + </template> + </a-table> + </a-card> +</template> +<script> + import { sysCodeGenerateConfigList, sysCodeGenerateConfigEdit } from '@/api/modular/gen/sysCodeGenerateConfigManage' + export default { + data () { + return { + // 表头 + columns: [ + { + title: 'java字段', + dataIndex: 'javaName' + }, + { + title: '字段描述', + dataIndex: 'columnComment', + scopedSlots: { customRender: 'columnComment' } + }, + { + title: '物理类型', + dataIndex: 'dataType' + }, + { + title: 'java类型', + dataIndex: 'javaType', + scopedSlots: { customRender: 'javaType' } + }, + { + title: '作用类型', + dataIndex: 'effectType', + scopedSlots: { customRender: 'effectType' } + }, + { + title: '字典', + dataIndex: 'dictTypeCode', + scopedSlots: { customRender: 'dictTypeCode' } + }, + { + title: '列表显示', + align: 'center', + dataIndex: 'whetherTable', + scopedSlots: { customRender: 'whetherTable' } + }, + { + title: '列字段省略', + align: 'center', + dataIndex: 'whetherRetract', + scopedSlots: { customRender: 'whetherRetract' } + }, + { + title: '增改', + align: 'center', + dataIndex: 'whetherAddUpdate', + scopedSlots: { customRender: 'whetherAddUpdate' } + }, + { + title: '必填', + align: 'center', + dataIndex: 'whetherRequired', + scopedSlots: { customRender: 'whetherRequired' } + }, + { + title: '是否是查询', + align: 'center', + dataIndex: 'queryWhether', + scopedSlots: { customRender: 'queryWhether' } + }, + { + title: '查询方式', + dataIndex: 'queryType', + scopedSlots: { customRender: 'queryType' } + } + ], + indexConfigShow: false, + tableLoading: false, + visible: false, + loadData: [], + javaTypeData: [], + effectTypeData: [], + dictDataAll: [], + codeGenQueryTypeData: [], + yesOrNoData: [] + } + }, + methods: { + /** + * 打开界面 + */ + open (data) { + this.indexConfigShow = true + this.tableLoading = true + const dictOption = this.$options + this.javaTypeData = dictOption.filters['dictData']('code_gen_java_type') + this.effectTypeData = dictOption.filters['dictData']('code_gen_effect_type') + this.dictDataAll = dictOption.filters['dictDataAll']() + this.yesOrNoData = dictOption.filters['dictData']('yes_or_no') + this.codeGenQueryTypeData = dictOption.filters['dictData']('code_gen_query_type') + const params = { + codeGenId: data.id + } + sysCodeGenerateConfigList(params).then((res) => { + this.loadData = res.data + this.loadData.forEach(item => { + for (const key in item) { + if (item[key] === 'Y') { + item[key] = true + } + if (item[key] === 'N') { + item[key] = false + } + } + }) + this.tableLoading = false + }) + }, + /** + * 提交表单 + */ + handleSubmit () { + this.tableLoading = true + // 做数组属性转换, 咱先来一个切断双向绑定,学习的童鞋下回记下啊 + // eslint-disable-next-line prefer-const + let loadDatas = JSON.parse(JSON.stringify(this.loadData)) + loadDatas.forEach(item => { + // 必填那一项转换 + for (const key in item) { + if (item[key] === true) { + item[key] = 'Y' + } + if (item[key] === false) { + item[key] = 'N' + } + } + }) + const param = { + sysCodeGenerateConfigParamList: loadDatas + } + sysCodeGenerateConfigEdit(param).then((res) => { + this.tableLoading = false + if (res.success) { + this.$message.success('编辑成功') + this.handleCancel() + } else { + this.$message.error('编辑失败:' + res.message) + } + }) + }, + /** + * 判断是否(用于是否能选择或输入等) + */ + judgeColumns (data) { + if (data.columnName.indexOf('create_user') > -1 || + data.columnName.indexOf('create_time') > -1 || + data.columnName.indexOf('update_user') > -1 || + data.columnName.indexOf('update_time') > -1 || + data.columnKey === 'PRI') { + return true + } + return false + }, + handleCancel () { + this.$emit('ok') + this.loadData = [] + this.indexConfigShow = false + } + } + } +</script> diff --git a/_web/src/views/main/README.md b/_web/src/views/main/README.md new file mode 100644 index 0000000..fadf99a --- /dev/null +++ b/_web/src/views/main/README.md @@ -0,0 +1 @@ +/** 您的业务接口文件全写在此文件夹下面,升级底座直接迁移代码即可 **/ diff --git a/_web/src/views/main/blogarticle/addForm.vue b/_web/src/views/main/blogarticle/addForm.vue new file mode 100644 index 0000000..979c70d --- /dev/null +++ b/_web/src/views/main/blogarticle/addForm.vue @@ -0,0 +1,256 @@ +<template> + <a-modal + title="新增blog文章主体" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item + label="文章标题" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文章标题" v-decorator="['title', {rules: [{required: true, message: '请输入文章标题!'}]}]" /> + </a-form-item> + <a-form-item + label="文章文件id" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文章文件id" v-decorator="['articleFileId', {rules: [{required: false, message: '请输入文章文件id!'}]}]" /> + </a-form-item> + <a-form-item + label="文件类型 1:markdown 2:html" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文件类型 1:markdown 2:html" v-decorator="['articleFileType', {rules: [{required: true, message: '请输入文件类型 1:markdown 2:html!'}]}]" /> + </a-form-item> + <a-form-item + label="文章分类id 0:没有分类" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文章分类id 0:没有分类" v-decorator="['articleTypeId', {rules: [{required: true, message: '请输入文章分类id 0:没有分类!'}]}]" /> + </a-form-item> + <a-form-item + label="文章引言" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文章引言" v-decorator="['introduce', {rules: [{required: true, message: '请输入文章引言!'}]}]" /> + </a-form-item> + <a-form-item + label="封面文件地址(id)" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入封面文件地址(id)" v-decorator="['coverFileId', {rules: [{required: true, message: '请输入封面文件地址(id)!'}]}]" /> + </a-form-item> + <a-form-item + label="上次编辑时间" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker style="width: 100%" placeholder="请选择上次编辑时间" v-decorator="['lastEditorDate',{rules: [{ required: true, message: '请选择上次编辑时间!' }]}]" @change="lastEditorDateOnChange"/> + </a-form-item> + <a-form-item + label="发布时间" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker style="width: 100%" placeholder="请选择发布时间" v-decorator="['publishDate',{rules: [{ required: true, message: '请选择发布时间!' }]}]" @change="publishDateOnChange"/> + </a-form-item> + <a-form-item + label="是否置顶 0:否 1:是" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入是否置顶 0:否 1:是" v-decorator="['isTop', {rules: [{required: true, message: '请输入是否置顶 0:否 1:是!'}]}]" /> + </a-form-item> + <a-form-item + label="置顶值(越小越靠前)" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入置顶值(越小越靠前)" v-decorator="['topValue', {rules: [{required: true, message: '请输入置顶值(越小越靠前)!'}]}]" /> + </a-form-item> + <a-form-item + label="公开状态 1:公开 2:私密 3:密码授权" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入公开状态 1:公开 2:私密 3:密码授权" v-decorator="['authStatus', {rules: [{required: true, message: '请输入公开状态 1:公开 2:私密 3:密码授权!'}]}]" /> + </a-form-item> + <a-form-item + label="授权密码(在密码授权状态时)" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入授权密码(在密码授权状态时)" v-decorator="['authPassword', {rules: [{required: true, message: '请输入授权密码(在密码授权状态时)!'}]}]" /> + </a-form-item> + <a-form-item + label="编辑状态 0:草稿 1:发布" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入编辑状态 0:草稿 1:发布" v-decorator="['editorStatus', {rules: [{required: true, message: '请输入编辑状态 0:草稿 1:发布!'}]}]" /> + </a-form-item> + <a-form-item + label="归档年份(以初次发布时间为准)" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入归档年份(以初次发布时间为准)" v-decorator="['separateYear', {rules: [{required: true, message: '请输入归档年份(以初次发布时间为准)!'}]}]" /> + </a-form-item> + <a-form-item + label="归档月份" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入归档月份" v-decorator="['separateMonth', {rules: [{required: true, message: '请输入归档月份!'}]}]" /> + </a-form-item> + <a-form-item + label="归档日" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入归档日" v-decorator="['separateDay', {rules: [{required: true, message: '请输入归档日!'}]}]" /> + </a-form-item> + <a-form-item + label="是否启用 0:否 1:是" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入是否启用 0:否 1:是" v-decorator="['isEnable', {rules: [{required: true, message: '请输入是否启用 0:否 1:是!'}]}]" /> + </a-form-item> + <a-form-item + label="更新时间" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker style="width: 100%" placeholder="请选择更新时间" v-decorator="['updateDate',{rules: [{ required: true, message: '请选择更新时间!' }]}]" @change="updateDateOnChange"/> + </a-form-item> + <a-form-item + label="创建时间" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker style="width: 100%" placeholder="请选择创建时间" v-decorator="['createDate',{rules: [{ required: true, message: '请选择创建时间!' }]}]" @change="createDateOnChange"/> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> + +<script> + import { blogArticleAdd } from '@/api/modular/main/blogarticle/blogArticleManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + lastEditorDateDateString: '', + publishDateDateString: '', + updateDateDateString: '', + createDateDateString: '', + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add (record) { + this.visible = true + }, + /** + * 提交表单 + */ + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + for (const key in values) { + if (typeof (values[key]) === 'object' && values[key] != null) { + values[key] = JSON.stringify(values[key]) + } + } + values.lastEditorDate = this.lastEditorDateDateString || null + values.publishDate = this.publishDateDateString || null + values.updateDate = this.updateDateDateString || null + values.createDate = this.createDateDateString || null + blogArticleAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.confirmLoading = false + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('新增失败')// + res.message + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + lastEditorDateOnChange(date, dateString) { + this.lastEditorDateDateString = dateString + }, + publishDateOnChange(date, dateString) { + this.publishDateDateString = dateString + }, + updateDateOnChange(date, dateString) { + this.updateDateDateString = dateString + }, + createDateOnChange(date, dateString) { + this.createDateDateString = dateString + }, + handleCancel () { + this.lastEditorDateDateString ='' + this.form.getFieldDecorator('lastEditorDate', { initialValue: null }) + this.publishDateDateString ='' + this.form.getFieldDecorator('publishDate', { initialValue: null }) + this.updateDateDateString ='' + this.form.getFieldDecorator('updateDate', { initialValue: null }) + this.createDateDateString ='' + this.form.getFieldDecorator('createDate', { initialValue: null }) + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/main/blogarticle/editForm.vue b/_web/src/views/main/blogarticle/editForm.vue new file mode 100644 index 0000000..1ae1364 --- /dev/null +++ b/_web/src/views/main/blogarticle/editForm.vue @@ -0,0 +1,298 @@ +<template> + <a-modal + title="编辑blog文章主体" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item v-show="false"><a-input v-decorator="['id']" /></a-form-item> + <a-form-item + label="文章标题" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文章标题" v-decorator="['title', {rules: [{required: true, message: '请输入文章标题!'}]}]" /> + </a-form-item> + <a-form-item + label="文章文件id" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文章文件id" v-decorator="['articleFileId', {rules: [{required: true, message: '请输入文章文件id!'}]}]" /> + </a-form-item> + <a-form-item + label="文件类型 1:markdown 2:html" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文件类型 1:markdown 2:html" v-decorator="['articleFileType', {rules: [{required: true, message: '请输入文件类型 1:markdown 2:html!'}]}]" /> + </a-form-item> + <a-form-item + label="文章分类id 0:没有分类" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文章分类id 0:没有分类" v-decorator="['articleTypeId', {rules: [{required: true, message: '请输入文章分类id 0:没有分类!'}]}]" /> + </a-form-item> + <a-form-item + label="文章引言" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入文章引言" v-decorator="['introduce', {rules: [{required: true, message: '请输入文章引言!'}]}]" /> + </a-form-item> + <a-form-item + label="封面文件地址(id)" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入封面文件地址(id)" v-decorator="['coverFileId', {rules: [{required: true, message: '请输入封面文件地址(id)!'}]}]" /> + </a-form-item> + <a-form-item + label="上次编辑时间" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker style="width: 100%" placeholder="请选择上次编辑时间" v-decorator="['lastEditorDate',{rules: [{ required: true, message: '请选择上次编辑时间!' }]}]" @change="lastEditorDateOnChange"/> + </a-form-item> + <a-form-item + label="发布时间" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker style="width: 100%" placeholder="请选择发布时间" v-decorator="['publishDate',{rules: [{ required: true, message: '请选择发布时间!' }]}]" @change="publishDateOnChange"/> + </a-form-item> + <a-form-item + label="是否置顶 0:否 1:是" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入是否置顶 0:否 1:是" v-decorator="['isTop', {rules: [{required: true, message: '请输入是否置顶 0:否 1:是!'}]}]" /> + </a-form-item> + <a-form-item + label="置顶值(越小越靠前)" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入置顶值(越小越靠前)" v-decorator="['topValue', {rules: [{required: true, message: '请输入置顶值(越小越靠前)!'}]}]" /> + </a-form-item> + <a-form-item + label="公开状态 1:公开 2:私密 3:密码授权" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入公开状态 1:公开 2:私密 3:密码授权" v-decorator="['authStatus', {rules: [{required: true, message: '请输入公开状态 1:公开 2:私密 3:密码授权!'}]}]" /> + </a-form-item> + <a-form-item + label="授权密码(在密码授权状态时)" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入授权密码(在密码授权状态时)" v-decorator="['authPassword', {rules: [{required: true, message: '请输入授权密码(在密码授权状态时)!'}]}]" /> + </a-form-item> + <a-form-item + label="编辑状态 0:草稿 1:发布" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入编辑状态 0:草稿 1:发布" v-decorator="['editorStatus', {rules: [{required: true, message: '请输入编辑状态 0:草稿 1:发布!'}]}]" /> + </a-form-item> + <a-form-item + label="归档年份(以初次发布时间为准)" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入归档年份(以初次发布时间为准)" v-decorator="['separateYear', {rules: [{required: true, message: '请输入归档年份(以初次发布时间为准)!'}]}]" /> + </a-form-item> + <a-form-item + label="归档月份" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入归档月份" v-decorator="['separateMonth', {rules: [{required: true, message: '请输入归档月份!'}]}]" /> + </a-form-item> + <a-form-item + label="归档日" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入归档日" v-decorator="['separateDay', {rules: [{required: true, message: '请输入归档日!'}]}]" /> + </a-form-item> + <a-form-item + label="是否启用 0:否 1:是" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入是否启用 0:否 1:是" v-decorator="['isEnable', {rules: [{required: true, message: '请输入是否启用 0:否 1:是!'}]}]" /> + </a-form-item> + <a-form-item + label="更新时间" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker style="width: 100%" placeholder="请选择更新时间" v-decorator="['updateDate',{rules: [{ required: true, message: '请选择更新时间!' }]}]" @change="updateDateOnChange"/> + </a-form-item> + <a-form-item + label="创建时间" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker style="width: 100%" placeholder="请选择创建时间" v-decorator="['createDate',{rules: [{ required: true, message: '请选择创建时间!' }]}]" @change="createDateOnChange"/> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> + +<script> + import moment from 'moment' + import { blogArticleEdit } from '@/api/modular/main/blogarticle/blogArticleManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + lastEditorDateDateString: '', + publishDateDateString: '', + updateDateDateString: '', + createDateDateString: '', + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + moment, + // 初始化方法 + edit (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + title: record.title, + articleFileId: record.articleFileId, + articleFileType: record.articleFileType, + articleTypeId: record.articleTypeId, + introduce: record.introduce, + coverFileId: record.coverFileId, + isTop: record.isTop, + topValue: record.topValue, + authStatus: record.authStatus, + authPassword: record.authPassword, + editorStatus: record.editorStatus, + separateYear: record.separateYear, + separateMonth: record.separateMonth, + separateDay: record.separateDay, + isEnable: record.isEnable + } + ) + }, 100) + // 时间单独处理 + if (record.lastEditorDate) { + this.form.getFieldDecorator('lastEditorDate', { initialValue: moment(record.lastEditorDate, 'YYYY-MM-DD') }) + this.lastEditorDateDateString = moment(record.lastEditorDate).format('YYYY-MM-DD') + } + // 时间单独处理 + if (record.publishDate) { + this.form.getFieldDecorator('publishDate', { initialValue: moment(record.publishDate, 'YYYY-MM-DD') }) + this.publishDateDateString = moment(record.publishDate).format('YYYY-MM-DD') + } + // 时间单独处理 + if (record.updateDate) { + this.form.getFieldDecorator('updateDate', { initialValue: moment(record.updateDate, 'YYYY-MM-DD') }) + this.updateDateDateString = moment(record.updateDate).format('YYYY-MM-DD') + } + // 时间单独处理 + if (record.createDate) { + this.form.getFieldDecorator('createDate', { initialValue: moment(record.createDate, 'YYYY-MM-DD') }) + this.createDateDateString = moment(record.createDate).format('YYYY-MM-DD') + } + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + for (const key in values) { + if (typeof (values[key]) === 'object' && values[key] != null) { + values[key] = JSON.stringify(values[key]) + } + } + values.lastEditorDate = this.lastEditorDateDateString || null + values.publishDate = this.publishDateDateString || null + values.updateDate = this.updateDateDateString || null + values.createDate = this.createDateDateString || null + blogArticleEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.confirmLoading = false + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('编辑失败')// + res.message + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + lastEditorDateOnChange(date, dateString) { + this.lastEditorDateDateString = dateString + }, + publishDateOnChange(date, dateString) { + this.publishDateDateString = dateString + }, + updateDateOnChange(date, dateString) { + this.updateDateDateString = dateString + }, + createDateOnChange(date, dateString) { + this.createDateDateString = dateString + }, + handleCancel () { + this.lastEditorDateDateString ='' + this.form.getFieldDecorator('lastEditorDate', { initialValue: null }) + this.publishDateDateString ='' + this.form.getFieldDecorator('publishDate', { initialValue: null }) + this.updateDateDateString ='' + this.form.getFieldDecorator('updateDate', { initialValue: null }) + this.createDateDateString ='' + this.form.getFieldDecorator('createDate', { initialValue: null }) + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/main/blogarticle/index.vue b/_web/src/views/main/blogarticle/index.vue new file mode 100644 index 0000000..331c2ed --- /dev/null +++ b/_web/src/views/main/blogarticle/index.vue @@ -0,0 +1,400 @@ +<template> + <div> + <a-card :bordered="false" :bodyStyle="tstyle"> + <div class="table-page-search-wrapper" v-if="hasPerm('blogArticle:page')"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="文章标题"> + <a-input v-model="queryParam.title" allow-clear placeholder="请输入文章标题"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="文章文件id"> + <a-input v-model="queryParam.articleFileId" allow-clear placeholder="请输入文章文件id"/> + </a-form-item> + </a-col> + <template v-if="advanced"> + <a-col :md="8" :sm="24"> + <a-form-item label="文件类型 1:markdown 2:html"> + <a-input v-model="queryParam.articleFileType" allow-clear placeholder="请输入文件类型 1:markdown 2:html"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="文章分类id 0:没有分类"> + <a-input v-model="queryParam.articleTypeId" allow-clear placeholder="请输入文章分类id 0:没有分类"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="文章引言"> + <a-input v-model="queryParam.introduce" allow-clear placeholder="请输入文章引言"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="封面文件地址(id)"> + <a-input v-model="queryParam.coverFileId" allow-clear placeholder="请输入封面文件地址(id)"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="上次编辑时间"> + <a-date-picker style="width: 100%" placeholder="请选择上次编辑时间" v-model="queryParam.lastEditorDateDate" @change="onChangelastEditorDate"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="发布时间"> + <a-date-picker style="width: 100%" placeholder="请选择发布时间" v-model="queryParam.publishDateDate" @change="onChangepublishDate"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="是否置顶 0:否 1:是"> + <a-input v-model="queryParam.isTop" allow-clear placeholder="请输入是否置顶 0:否 1:是"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="置顶值(越小越靠前)"> + <a-input v-model="queryParam.topValue" allow-clear placeholder="请输入置顶值(越小越靠前)"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="公开状态 1:公开 2:私密 3:密码授权"> + <a-input v-model="queryParam.authStatus" allow-clear placeholder="请输入公开状态 1:公开 2:私密 3:密码授权"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="授权密码(在密码授权状态时)"> + <a-input v-model="queryParam.authPassword" allow-clear placeholder="请输入授权密码(在密码授权状态时)"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="编辑状态 0:草稿 1:发布"> + <a-input v-model="queryParam.editorStatus" allow-clear placeholder="请输入编辑状态 0:草稿 1:发布"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="归档年份(以初次发布时间为准)"> + <a-input v-model="queryParam.separateYear" allow-clear placeholder="请输入归档年份(以初次发布时间为准)"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="归档月份"> + <a-input v-model="queryParam.separateMonth" allow-clear placeholder="请输入归档月份"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="归档日"> + <a-input v-model="queryParam.separateDay" allow-clear placeholder="请输入归档日"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="是否启用 0:否 1:是"> + <a-input v-model="queryParam.isEnable" allow-clear placeholder="请输入是否启用 0:否 1:是"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="更新时间"> + <a-date-picker style="width: 100%" placeholder="请选择更新时间" v-model="queryParam.updateDateDate" @change="onChangeupdateDate"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="创建时间"> + <a-date-picker style="width: 100%" placeholder="请选择创建时间" v-model="queryParam.createDateDate" @change="onChangecreateDate"/> + </a-form-item> + </a-col> + </template> + <a-col :md="8" :sm="24" > + <span class="table-page-search-submitButtons"> + <a-button type="primary" @click="$refs.table.refresh(true)" >查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + <a @click="toggleAdvanced" style="margin-left: 8px"> + {{ advanced ? '收起' : '展开' }} + <a-icon :type="advanced ? 'up' : 'down'"/> + </a> + </span> + </a-col> + </a-row> + </a-form> + </div> + </a-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="options.alert" + :rowKey="(record) => record.id" + :rowSelection="options.rowSelection" + > + <template class="table-operator" slot="operator" v-if="hasPerm('blogArticle:add')" > + <a-button type="primary" v-if="hasPerm('blogArticle:add')" icon="plus" @click="$refs.addForm.add()">新增blog文章主体</a-button> + <a-button type="danger" :disabled="selectedRowKeys.length < 1" v-if="hasPerm('blogArticle:delete')" @click="batchDelete"><a-icon type="delete"/>批量删除</a-button> + <x-down + v-if="hasPerm('blogArticle:export')" + ref="batchExport" + @batchExport="batchExport" + /> + </template> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('blogArticle:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('blogArticle:edit') & hasPerm('blogArticle:delete')"/> + <a-popconfirm v-if="hasPerm('blogArticle:delete')" placement="topRight" title="确认删除?" @confirm="() => singleDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + </a-card> + </div> +</template> +<script> + import { STable, XDown } from '@/components' + import moment from 'moment' + import { blogArticlePage, blogArticleDelete, blogArticleExport } from '@/api/modular/main/blogarticle/blogArticleManage' + import addForm from './addForm.vue' + import editForm from './editForm.vue' + export default { + components: { + STable, + addForm, + editForm, + XDown + }, + data () { + return { + // 高级搜索 展开/关闭 + advanced: false, + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '文章标题', + align: 'center', + dataIndex: 'title' + }, + { + title: '文章文件id', + align: 'center', + dataIndex: 'articleFileId' + }, + { + title: '文件类型 1:markdown 2:html', + align: 'center', + dataIndex: 'articleFileType' + }, + { + title: '文章分类id 0:没有分类', + align: 'center', + dataIndex: 'articleTypeId' + }, + { + title: '文章引言', + align: 'center', + dataIndex: 'introduce' + }, + { + title: '封面文件地址(id)', + align: 'center', + dataIndex: 'coverFileId' + }, + { + title: '上次编辑时间', + align: 'center', + dataIndex: 'lastEditorDate' + }, + { + title: '发布时间', + align: 'center', + dataIndex: 'publishDate' + }, + { + title: '是否置顶 0:否 1:是', + align: 'center', + dataIndex: 'isTop' + }, + { + title: '置顶值(越小越靠前)', + align: 'center', + dataIndex: 'topValue' + }, + { + title: '公开状态 1:公开 2:私密 3:密码授权', + align: 'center', + dataIndex: 'authStatus' + }, + { + title: '授权密码(在密码授权状态时)', + align: 'center', + dataIndex: 'authPassword' + }, + { + title: '编辑状态 0:草稿 1:发布', + align: 'center', + dataIndex: 'editorStatus' + }, + { + title: '归档年份(以初次发布时间为准)', + align: 'center', + dataIndex: 'separateYear' + }, + { + title: '归档月份', + align: 'center', + dataIndex: 'separateMonth' + }, + { + title: '归档日', + align: 'center', + dataIndex: 'separateDay' + }, + { + title: '是否启用 0:否 1:是', + align: 'center', + dataIndex: 'isEnable' + }, + { + title: '更新时间', + align: 'center', + dataIndex: 'updateDate' + }, + { + title: '创建时间', + align: 'center', + dataIndex: 'createDate' + } + ], + tstyle: { 'padding-bottom': '0px', 'margin-bottom': '10px' }, + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return blogArticlePage(Object.assign(parameter, this.switchingDate())).then((res) => { + return res.data + }) + }, + selectedRowKeys: [], + selectedRows: [], + options: { + alert: { show: true, clear: () => { this.selectedRowKeys = [] } }, + rowSelection: { + selectedRowKeys: this.selectedRowKeys, + onChange: this.onSelectChange + } + } + } + }, + created () { + if (this.hasPerm('blogArticle:edit') || this.hasPerm('blogArticle:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + moment, + /** + * 查询参数组装 + */ + switchingDate () { + const queryParamlastEditorDate = this.queryParam.lastEditorDateDate + if (queryParamlastEditorDate != null) { + this.queryParam.lastEditorDate = moment(queryParamlastEditorDate).format('YYYY-MM-DD') + if (queryParamlastEditorDate.length < 1) { + delete this.queryParam.lastEditorDate + } + } + const queryParampublishDate = this.queryParam.publishDateDate + if (queryParampublishDate != null) { + this.queryParam.publishDate = moment(queryParampublishDate).format('YYYY-MM-DD') + if (queryParampublishDate.length < 1) { + delete this.queryParam.publishDate + } + } + const queryParamupdateDate = this.queryParam.updateDateDate + if (queryParamupdateDate != null) { + this.queryParam.updateDate = moment(queryParamupdateDate).format('YYYY-MM-DD') + if (queryParamupdateDate.length < 1) { + delete this.queryParam.updateDate + } + } + const queryParamcreateDate = this.queryParam.createDateDate + if (queryParamcreateDate != null) { + this.queryParam.createDate = moment(queryParamcreateDate).format('YYYY-MM-DD') + if (queryParamcreateDate.length < 1) { + delete this.queryParam.createDate + } + } + const obj = JSON.parse(JSON.stringify(this.queryParam)) + return obj + }, + /** + * 单个删除 + */ + singleDelete (record) { + const param = [{ 'id': record.id }] + this.blogArticleDelete(param) + }, + /** + * 批量删除 + */ + batchDelete () { + const paramIds = this.selectedRowKeys.map((d) => { + return { 'id': d } + }) + this.blogArticleDelete(paramIds) + }, + blogArticleDelete (record) { + blogArticleDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.clearRefreshSelected() + } else { + this.$message.error('删除失败') // + res.message + } + }) + }, + toggleAdvanced () { + this.advanced = !this.advanced + }, + onChangelastEditorDate(date, dateString) { + this.lastEditorDateDateString = dateString + }, + onChangepublishDate(date, dateString) { + this.publishDateDateString = dateString + }, + onChangeupdateDate(date, dateString) { + this.updateDateDateString = dateString + }, + onChangecreateDate(date, dateString) { + this.createDateDateString = dateString + }, + /** + * 批量导出 + */ + batchExport () { + const paramIds = this.selectedRowKeys.map((d) => { + return { 'id': d } + }) + blogArticleExport(paramIds).then((res) => { + this.$refs.batchExport.downloadfile(res) + }) + }, + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/README.md b/_web/src/views/system/README.md new file mode 100644 index 0000000..46aab4d --- /dev/null +++ b/_web/src/views/system/README.md @@ -0,0 +1 @@ +/** 此文件夹下代码尽量不要动,底座升级直接覆盖替换 **/ diff --git a/_web/src/views/system/account/center/Index.vue b/_web/src/views/system/account/center/Index.vue new file mode 100644 index 0000000..e2ffd4a --- /dev/null +++ b/_web/src/views/system/account/center/Index.vue @@ -0,0 +1,308 @@ +<template> + <div class="page-header-index-wide page-header-wrapper-grid-content-main"> + <a-row :gutter="24"> + <a-col :md="24" :lg="7"> + <a-card :bordered="false"> + <div class="account-center-avatarHolder"> + <div class="avatar"> + <img :src="avatar()"> + </div> + <div class="username">{{ nickname() }}</div> + <div class="bio">海纳百川,有容乃大</div> + </div> + <div class="account-center-detail"> + <p> + <i class="title"></i>交互专家 + </p> + <p> + <i class="group"></i>蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED + </p> + <p> + <i class="address"></i> + <span>浙江省</span> + <span>杭州市</span> + </p> + </div> + <a-divider/> + + <div class="account-center-tags"> + <div class="tagsTitle">标签</div> + <div> + <template v-for="(tag, index) in tags"> + <a-tooltip v-if="tag.length > 20" :key="tag" :title="tag"> + <a-tag + :key="tag" + :closable="index !== 0" + >{{ `${tag.slice(0, 20)}...` }}</a-tag> + </a-tooltip> + <a-tag + v-else + :key="tag" + :closable="index !== 0" + >{{ tag }}</a-tag> + </template> + <a-input + v-if="tagInputVisible" + ref="tagInput" + type="text" + size="small" + :style="{ width: '78px' }" + :value="tagInputValue" + @change="handleInputChange" + @blur="handleTagInputConfirm" + @keyup.enter="handleTagInputConfirm" + /> + <a-tag v-else @click="showTagInput" style="background: #fff; borderStyle: dashed;"> + <a-icon type="plus"/>New Tag + </a-tag> + </div> + </div> + <a-divider :dashed="true"/> + + <div class="account-center-team"> + <div class="teamTitle">团队</div> + <a-spin :spinning="teamSpinning"> + <div class="members"> + <a-row> + <a-col :span="12" v-for="(item, index) in teams" :key="index"> + <a> + <a-avatar size="small" :src="item.avatar"/> + <span class="member">{{ item.name }}</span> + </a> + </a-col> + </a-row> + </div> + </a-spin> + </div> + </a-card> + </a-col> + <a-col :md="24" :lg="17"> + <a-card + style="width:100%" + :bordered="false" + :tabList="tabListNoTitle" + :activeTabKey="noTitleKey" + @tabChange="key => handleTabChange(key, 'noTitleKey')" + > + <article-page v-if="noTitleKey === 'article'"></article-page> + <app-page v-else-if="noTitleKey === 'app'"></app-page> + <project-page v-else-if="noTitleKey === 'project'"></project-page> + </a-card> + </a-col> + </a-row> + </div> +</template> + +<script> +import { PageView, RouteView } from '@/layouts' +import { AppPage, ArticlePage, ProjectPage } from './page' +import { mapGetters } from 'vuex' + +export default { + components: { + RouteView, + PageView, + AppPage, + ArticlePage, + ProjectPage + }, + data () { + return { + tags: ['很有想法的', '专注设计', '辣~', '大长腿', '川妹子', '海纳百川'], + + tagInputVisible: false, + tagInputValue: '', + + teams: [], + teamSpinning: true, + + tabListNoTitle: [ + { + key: 'article', + tab: '文章(8)' + }, + { + key: 'app', + tab: '应用(8)' + }, + { + key: 'project', + tab: '项目(8)' + } + ], + noTitleKey: 'app' + } + }, + mounted () { + this.getTeams() + }, + methods: { + ...mapGetters(['nickname', 'avatar']), + + getTeams () { + this.teams = [{ + id: 1, + name: '科学搬砖组', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png' + }, + { + id: 2, + name: '程序员日常', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png' + }, + { + id: 1, + name: '设计天团', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png' + }, + { + id: 1, + name: '中二少女团', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png' + }, + { + id: 1, + name: '骗你学计算机', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png' + } + ] + this.teamSpinning = false + }, + + handleTabChange (key, type) { + this[type] = key + }, + + handleTagClose (removeTag) { + const tags = this.tags.filter(tag => tag !== removeTag) + this.tags = tags + }, + + showTagInput () { + this.tagInputVisible = true + this.$nextTick(() => { + this.$refs.tagInput.focus() + }) + }, + + handleInputChange (e) { + this.tagInputValue = e.target.value + }, + + handleTagInputConfirm () { + const inputValue = this.tagInputValue + let tags = this.tags + if (inputValue && !tags.includes(inputValue)) { + tags = [...tags, inputValue] + } + + Object.assign(this, { + tags, + tagInputVisible: false, + tagInputValue: '' + }) + } + } +} +</script> + +<style lang="less" scoped> +.page-header-wrapper-grid-content-main { + width: 100%; + height: 100%; + min-height: 100%; + transition: 0.3s; + + .account-center-avatarHolder { + text-align: center; + margin-bottom: 24px; + + & > .avatar { + margin: 0 auto; + width: 104px; + height: 104px; + margin-bottom: 20px; + border-radius: 50%; + overflow: hidden; + img { + height: 100%; + width: 100%; + } + } + + .username { + color: rgba(0, 0, 0, 0.85); + font-size: 20px; + line-height: 28px; + font-weight: 500; + margin-bottom: 4px; + } + } + + .account-center-detail { + p { + margin-bottom: 8px; + padding-left: 26px; + position: relative; + } + + i { + position: absolute; + height: 14px; + width: 14px; + left: 0; + top: 4px; + background: url(https://gw.alipayobjects.com/zos/rmsportal/pBjWzVAHnOOtAUvZmZfy.svg); + } + + .title { + background-position: 0 0; + } + .group { + background-position: 0 -22px; + } + .address { + background-position: 0 -44px; + } + } + + .account-center-tags { + .ant-tag { + margin-bottom: 8px; + } + } + + .account-center-team { + .members { + a { + display: block; + margin: 12px 0; + line-height: 24px; + height: 24px; + .member { + font-size: 14px; + color: rgba(0, 0, 0, 0.65); + line-height: 24px; + max-width: 100px; + vertical-align: top; + margin-left: 12px; + transition: all 0.3s; + display: inline-block; + } + &:hover { + span { + color: #1890ff; + } + } + } + } + } + + .tagsTitle, + .teamTitle { + font-weight: 500; + color: rgba(0, 0, 0, 0.85); + margin-bottom: 12px; + } +} +</style> diff --git a/_web/src/views/system/account/center/page/App.vue b/_web/src/views/system/account/center/page/App.vue new file mode 100644 index 0000000..853aeab --- /dev/null +++ b/_web/src/views/system/account/center/page/App.vue @@ -0,0 +1,113 @@ +<template> + <div class="app-list"> + <a-list + :grid="{ gutter: 24, lg: 3, md: 2, sm: 1, xs: 1 }" + :dataSource="dataSource"> + <a-list-item slot="renderItem" slot-scope="item"> + <a-card :hoverable="true"> + <a-card-meta> + <div style="margin-bottom: 3px" slot="title">{{ item.title }}</div> + <a-avatar class="card-avatar" slot="avatar" :src="item.avatar" size="small"/> + <div class="meta-cardInfo" slot="description"> + <div> + <p>活跃用户</p> + <p> + <span>{{ item.activeUser }}<span>万</span></span> + </p> + </div> + <div> + <p>新增用户</p> + <p>{{ item.newUser | NumberFormat }}</p> + </div> + </div> + </a-card-meta> + <template class="ant-card-actions" slot="actions"> + <a> + <a-icon type="download"/> + </a> + <a> + <a-icon type="edit"/> + </a> + <a> + <a-icon type="share-alt"/> + </a> + <a> + <a-dropdown> + <a class="ant-dropdown-link" href="javascript:;"> + <a-icon type="ellipsis"/> + </a> + <a-menu slot="overlay"> + <a-menu-item> + <a href="javascript:;">1st menu item</a> + </a-menu-item> + <a-menu-item> + <a href="javascript:;">2nd menu item</a> + </a-menu-item> + <a-menu-item> + <a href="javascript:;">3rd menu item</a> + </a-menu-item> + </a-menu> + </a-dropdown> + </a> + </template> + </a-card> + </a-list-item> + </a-list> + + </div> +</template> + +<script> +const dataSource = [] +for (let i = 0; i < 11; i++) { + dataSource.push({ + title: 'Alipay', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', + activeUser: 17, + newUser: 1700 + }) +} + +export default { + name: 'Article', + components: {}, + data () { + return { + dataSource + } + } +} +</script> + +<style lang="less" scoped> + + .app-list { + + .meta-cardInfo { + zoom: 1; + margin-top: 16px; + + > div { + position: relative; + text-align: left; + float: left; + width: 50%; + + p { + line-height: 32px; + font-size: 24px; + margin: 0; + + &:first-child { + color: rgba(0, 0, 0, .45); + font-size: 12px; + line-height: 20px; + margin-bottom: 4px; + } + } + + } + } + } + +</style> diff --git a/_web/src/views/system/account/center/page/Article.vue b/_web/src/views/system/account/center/page/Article.vue new file mode 100644 index 0000000..bac1ded --- /dev/null +++ b/_web/src/views/system/account/center/page/Article.vue @@ -0,0 +1,91 @@ +<template> + <a-list + size="large" + rowKey="id" + :loading="loading" + itemLayout="vertical" + :dataSource="data" + > + <a-list-item :key="item.id" slot="renderItem" slot-scope="item"> + <a-list-item-meta> + <a slot="title" href="https://vue.ant.design/">{{ item.title }}</a> + <template slot="description"> + <span> + <a-tag>Ant Design</a-tag> + <a-tag>设计语言</a-tag> + <a-tag>蚂蚁金服</a-tag> + </span> + </template> + </a-list-item-meta> + <article-list-content :description="item.description" :owner="item.owner" :avatar="item.avatar" :href="item.href" :updateAt="item.updatedAt" /> + </a-list-item> + <div slot="footer" v-if="data.length > 0" style="text-align: center; margin-top: 16px;"> + <a-button @click="loadMore" :loading="loadingMore">加载更多</a-button> + </div> + </a-list> +</template> + +<script> +import { ArticleListContent } from '@/components' + +export default { + name: 'Article', + components: { + ArticleListContent + }, + data () { + return { + loading: true, + loadingMore: false, + data: [] + } + }, + mounted () { + this.getList() + }, + methods: { + getList () { + this.data = [ + { + updatedAt: '2021-05-01 12:00:00', + title: '小诺', + owner: '俞宝山', + description: 'snowy是小诺团队产品', + href: 'https://xiaonuo.vip', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', + name: '曲丽丽', + id: 'member1' + }, + { + updatedAt: '2021-05-01 12:00:00', + title: '小诺', + owner: '徐玉祥', + description: 'snowy是小诺团队产品', + href: 'https://xiaonuo.vip', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', + name: '王昭君', + id: 'member2' + }, + { + updatedAt: '2021-05-01 12:00:00', + title: '小诺', + owner: '董夏雨', + description: 'snowy是小诺团队产品', + href: 'https://xiaonuo.vip', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', + name: '董娜娜', + id: 'member3' + } + ] + this.loading = false + }, + loadMore () { + this.loadingMore = false + } + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/account/center/page/Project.vue b/_web/src/views/system/account/center/page/Project.vue new file mode 100644 index 0000000..f5d84bf --- /dev/null +++ b/_web/src/views/system/account/center/page/Project.vue @@ -0,0 +1,215 @@ +<template> + <div class="ant-pro-pages-account-projects-cardList"> + <a-list :loading="loading" :data-source="data" :grid="{ gutter: 24, xxl: 3, xl: 2, lg: 2, md: 2, sm: 2, xs: 1 }"> + <a-list-item slot="renderItem" slot-scope="item"> + <a-card class="ant-pro-pages-account-projects-card" hoverable> + <img slot="cover" :src="item.cover" :alt="item.title" /> + <a-card-meta :title="item.title"> + <template slot="description"> + <ellipsis :length="50">{{ item.description }}</ellipsis> + </template> + </a-card-meta> + <div class="cardItemContent"> + <span>{{ item.updatedAt | fromNow }}</span> + <div class="avatarList"> + <avatar-list size="mini"> + <avatar-list-item + v-for="(member, i) in item.members" + :key="`${item.id}-avatar-${i}`" + :src="member.avatar" + :tips="member.name" + /> + </avatar-list> + </div> + </div> + </a-card> + </a-list-item> + </a-list> + </div> +</template> + +<script> +import moment from 'moment' +import { TagSelect, StandardFormRow, Ellipsis, AvatarList } from '@/components' +const TagSelectOption = TagSelect.Option +const AvatarListItem = AvatarList.AvatarItem + +export default { + name: 'Project', + components: { + AvatarList, + AvatarListItem, + Ellipsis, + TagSelect, + TagSelectOption, + StandardFormRow + }, + data () { + return { + data: [], + form: this.$form.createForm(this), + loading: true + } + }, + filters: { + fromNow (date) { + return moment(date).fromNow() + } + }, + mounted () { + this.getList() + }, + methods: { + handleChange (value) { + console.log(`selected ${value}`) + }, + getList () { + this.data = [ + { + id: '123', + cover: 'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png', + content: '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', + message: '消息', + description: '小诺框架产品', + href: 'https://xiaonuo.vip', + title: '小诺', + updatedAt: '2021-05-01 12:00:00', + members: [ + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', + name: '曲丽丽', + id: 'member1' + }, + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', + name: '王昭君', + id: 'member2' + }, + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', + name: '董娜娜', + id: 'member3' + } + ] + }, + { + id: '1234', + cover: 'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png', + content: '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', + message: '消息', + description: '小诺框架产品', + href: 'https://xiaonuo.vip', + title: '小诺', + updatedAt: '2021-05-01 12:00:00', + members: [ + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', + name: '曲丽丽', + id: 'member1' + }, + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', + name: '王昭君', + id: 'member2' + }, + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', + name: '董娜娜', + id: 'member3' + } + ] + }, + { + id: '12345', + cover: 'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png', + content: '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', + message: '消息', + description: '小诺框架产品', + href: 'https://xiaonuo.vip', + title: '小诺', + updatedAt: '2021-05-01 12:00:00', + members: [ + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', + name: '曲丽丽', + id: 'member1' + }, + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', + name: '王昭君', + id: 'member2' + }, + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', + name: '董娜娜', + id: 'member3' + } + ] + }, + { + id: '1236', + cover: 'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png', + content: '段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。', + message: '消息', + description: '小诺框架产品', + href: 'https://xiaonuo.vip', + title: '小诺', + updatedAt: '2021-05-01 12:00:00', + members: [ + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png', + name: '曲丽丽', + id: 'member1' + }, + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png', + name: '王昭君', + id: 'member2' + }, + { + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png', + name: '董娜娜', + id: 'member3' + } + ] + } + ] + this.loading = false + } + } +} +</script> + +<style lang="less" scoped> + .ant-pro-pages-account-projects-cardList { + margin-top: 24px; + + /deep/ .ant-card-meta-title { + margin-bottom: 4px; + } + + /deep/ .ant-card-meta-description { + height: 44px; + overflow: hidden; + line-height: 22px; + } + + .cardItemContent { + display: flex; + height: 20px; + margin-top: 16px; + margin-bottom: -4px; + line-height: 20px; + + > span { + flex: 1 1; + color: rgba(0,0,0,.45); + font-size: 12px; + } + + /deep/ .ant-pro-avatar-list { + flex: 0 1 auto; + } + } + } +</style> diff --git a/_web/src/views/system/account/center/page/index.js b/_web/src/views/system/account/center/page/index.js new file mode 100644 index 0000000..b579b6a --- /dev/null +++ b/_web/src/views/system/account/center/page/index.js @@ -0,0 +1,5 @@ +import AppPage from './App' +import ArticlePage from './Article' +import ProjectPage from './Project' + +export { AppPage, ArticlePage, ProjectPage } diff --git a/_web/src/views/system/account/settings/AvatarModal.vue b/_web/src/views/system/account/settings/AvatarModal.vue new file mode 100644 index 0000000..289c9d0 --- /dev/null +++ b/_web/src/views/system/account/settings/AvatarModal.vue @@ -0,0 +1,183 @@ +<template> + + <a-modal + title="修改头像" + :visible="visible" + :maskClosable="false" + :confirmLoading="confirmLoading" + :width="800" + :footer="null" + @cancel="cancelHandel"> + <a-row> + <a-col :xs="24" :md="12" :style="{height: '350px'}"> + <vue-cropper + ref="cropper" + :img="options.img" + :info="true" + :autoCrop="options.autoCrop" + :autoCropWidth="options.autoCropWidth" + :autoCropHeight="options.autoCropHeight" + :fixedBox="options.fixedBox" + @realTime="realTime" + > + </vue-cropper> + </a-col> + <a-col :xs="24" :md="12" :style="{height: '350px'}"> + <div class="avatar-upload-preview"> + <img :src="previews.url" :style="previews.img"/> + </div> + </a-col> + </a-row> + <br> + <a-row> + <a-col :lg="2" :md="2"> + <a-upload name="file" :beforeUpload="beforeUpload" :showUploadList="false"> + <a-button icon="upload">选择图片</a-button> + </a-upload> + </a-col> + <a-col :lg="{span: 1, offset: 2}" :md="2"> + <a-button icon="plus" @click="changeScale(1)"/> + </a-col> + <a-col :lg="{span: 1, offset: 1}" :md="2"> + <a-button icon="minus" @click="changeScale(-1)"/> + </a-col> + <a-col :lg="{span: 1, offset: 1}" :md="2"> + <a-button icon="undo" @click="rotateLeft"/> + </a-col> + <a-col :lg="{span: 1, offset: 1}" :md="2"> + <a-button icon="redo" @click="rotateRight"/> + </a-col> + <a-col :lg="{span: 2, offset: 6}" :md="2"> + <a-button type="primary" @click="finish('blob')" :loading="uploading">保存</a-button> + </a-col> + </a-row> + </a-modal> + +</template> +<script> + import { sysFileInfoUpload } from '@/api/modular/system/fileManage' + import { sysUserUpdateAvatar } from '@/api/modular/system/userManage' + + export default { + data () { + return { + visible: false, + id: null, + confirmLoading: false, + fileList: [], + uploading: false, + options: { + img: '', + autoCrop: true, + autoCropWidth: 200, + autoCropHeight: 200, + fixedBox: true + }, + previews: {} + } + }, + methods: { + edit (id) { + this.visible = true + this.id = id + /* 获取原始头像 */ + }, + close () { + this.id = null + this.visible = false + }, + cancelHandel () { + this.close() + }, + changeScale (num) { + num = num || 1 + this.$refs.cropper.changeScale(num) + }, + rotateLeft () { + this.$refs.cropper.rotateLeft() + }, + rotateRight () { + this.$refs.cropper.rotateRight() + }, + beforeUpload (file) { + this.fileList = file + const reader = new FileReader() + // 把Array Buffer转化为blob 如果是base64不需要 + // 转化为base64 + reader.readAsDataURL(file) + reader.onload = () => { + this.options.img = reader.result + } + // 转化为blob + // reader.readAsArrayBuffer(file) + return false + }, + + // 上传图片(点击上传按钮) + finish (type) { + if (type === 'blob') { + this.uploading = true + this.$refs.cropper.getCropBlob((data) => { + const files = new window.File( + [data], + this.fileList.name, + { type: this.fileList.type } + ) + const formData = new FormData() + formData.append('file', files) + sysFileInfoUpload(formData).then((res) => { + if (res.success) { + this.updateAvatar(res.data) + this.$emit('ok', res.data) + } else { + this.uploading = false + this.$message.error(res.message) + } + }) + }) + } else { + this.$refs.cropper.getCropData((data) => { + console.log(data) + }) + } + }, + updateAvatar (data) { + const params = { + id: this.id, + avatar: data + } + sysUserUpdateAvatar(params).then((res) => { + this.uploading = false + if (res.success) { + this.visible = false + this.$message.success('头像上传修改成功') + } else { + this.$message.error(res.message) + } + }) + }, + realTime (data) { + this.previews = data + } + } +} +</script> + +<style lang="less" scoped> + + .avatar-upload-preview { + position: absolute; + top: 50%; + transform: translate(50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; + + img { + width: 100%; + height: 100%; + } + } +</style> diff --git a/_web/src/views/system/account/settings/BaseSetting.vue b/_web/src/views/system/account/settings/BaseSetting.vue new file mode 100644 index 0000000..c7b11cf --- /dev/null +++ b/_web/src/views/system/account/settings/BaseSetting.vue @@ -0,0 +1,216 @@ +<template> + <div class="account-settings-info-view"> + <a-row :gutter="16"> + <a-col :md="24" :lg="16"> + + <a-form layout="vertical" :form="form"> + <a-form-item + label="昵称" + > + <a-input placeholder="给自己起个昵称吧" v-decorator="['nickName']"/> + </a-form-item> + <a-form-item + label="生日" + > + <a-date-picker placeholder="请选择生日" @change="onChange" style="width: 100%" v-decorator="['birthday', {rules: [{required: true, message: '请选择生日!'}]}]" /> + </a-form-item> + <a-form-item + label="性别" + > + <a-radio-group v-decorator="['sex',{rules: [{ required: true, message: '请选择性别!' }]}]" > + <a-radio v-for="(item,index) in sexData" :key="index" :value="item.code">{{ item.name }}</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item + label="手机" + > + <a-input placeholder="请输入手机号" v-decorator="['phone', {rules: [{required: true, message: '请输入手机号!'}]}]"/> + </a-form-item> + <a-form-item + label="电话" + > + <a-input placeholder="请输入电话" v-decorator="['tel', {rules: [{required: true, message: '请输入电话!'}]}]"/> + </a-form-item> + <a-form-item + label="电子邮件" + :required="false" + > + <a-input placeholder="请输入电子邮件地址" v-decorator="['email', {type: 'email',message: '请输入正确的邮箱号',rules: [{required: true, message: '请输入正确的邮箱号!'}]}]"/> + </a-form-item> + <a-form-item> + <a-button type="primary" @click="submitUserInfo">更新基本信息</a-button> + </a-form-item> + </a-form> + + </a-col> + <a-col :md="24" :lg="8" :style="{ minHeight: '180px' }"> + <div class="ant-upload-preview" @click="$refs.modal.edit(userInfo.id)" > + <a-icon type="cloud-upload-o" class="upload-icon"/> + <div class="mask"> + <a-icon type="plus" /> + </div> + <img :src="option.img"/> + </div> + </a-col> + + </a-row> + + <avatar-modal ref="modal" @ok="setavatar"/> + + </div> +</template> + +<script> + import store from '@/store' + import AvatarModal from './AvatarModal' + import { mapGetters } from 'vuex' + import moment from 'moment' + import { sysUserUpdateInfo } from '@/api/modular/system/userManage' +// mapActions +export default { + components: { + AvatarModal + }, + data () { + return { + // cropper + preview: {}, + form: this.$form.createForm(this), + sexData: [], + option: { + img: '/avatar2.jpg', + info: true, + size: 1, + outputType: 'jpeg', + canScale: false, + autoCrop: true, + // 只有自动截图开启 宽度高度才生效 + autoCropWidth: 180, + autoCropHeight: 180, + fixedBox: true, + // 开启宽度和高度比例 + fixed: true, + fixedNumber: [1, 1], + // userInfo + birthdayString: '' + } + } + }, + computed: { + ...mapGetters(['userInfo']) + }, + mounted () { + this.initUserInfo() + }, + methods: { + // ...mapActions(['GetInfo']), + /** + * 初始化用户信息 + */ + initUserInfo () { + setTimeout(() => { + this.form.setFieldsValue( + { + birthday: moment(this.userInfo.birthday, 'YYYY-MM-DD'), + nickName: this.userInfo.nickName, + sex: this.userInfo.sex.toString(), + email: this.userInfo.email, + phone: this.userInfo.phone, + tel: this.userInfo.tel + } + ) + this.birthdayString = moment(this.userInfo.birthday).format('YYYY-MM-DD') + this.option.img = process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + this.userInfo.avatar + this.getSexData() + }, 100) + }, + /** + * 日期需单独转换 + */ + onChange (date, dateString) { + this.birthdayString = dateString + }, + submitUserInfo () { + const { form: { validateFields } } = this + validateFields((err, values) => { + if (!err) { + values.birthday = this.birthdayString + values.id = this.userInfo.id + sysUserUpdateInfo(values).then((res) => { + if (res.success) { + this.$message.success('个人信息更新成功') + store.dispatch('GetInfo').then(() => {}) + } else { + this.$message.error(res.message) + } + }) + } + }) + }, + getSexData () { + this.sexData = this.$options.filters['dictData']('sex') + }, + setavatar (url) { + this.option.img = process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + url + store.dispatch('GetInfo').then(() => {}) + } + } +} +</script> + +<style lang="less" scoped> + + .avatar-upload-wrapper { + height: 200px; + width: 100%; + } + + .ant-upload-preview { + position: relative; + margin: 0 auto; + width: 100%; + max-width: 180px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + + .upload-icon { + position: absolute; + top: 0; + right: 10px; + font-size: 1.4rem; + padding: 0.5rem; + background: rgba(222, 221, 221, 0.7); + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.2); + } + .mask { + opacity: 0; + position: absolute; + background: rgba(0,0,0,0.4); + cursor: pointer; + transition: opacity 0.4s; + + &:hover { + opacity: 1; + } + + i { + font-size: 2rem; + position: absolute; + top: 50%; + left: 50%; + margin-left: -1rem; + margin-top: -1rem; + color: #d6d6d6; + } + } + + img, .mask { + width: 100%; + max-width: 180px; + height: 100%; + border-radius: 50%; + overflow: hidden; + } + } +</style> diff --git a/_web/src/views/system/account/settings/Binding.vue b/_web/src/views/system/account/settings/Binding.vue new file mode 100644 index 0000000..cbea7fc --- /dev/null +++ b/_web/src/views/system/account/settings/Binding.vue @@ -0,0 +1,25 @@ +<template> + <a-list + itemLayout="horizontal" + :dataSource="data" + > + + </a-list> +</template> + +<script> +export default { + data () { + return { + data: [] + } + }, + methods: { + + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/account/settings/Custom.vue b/_web/src/views/system/account/settings/Custom.vue new file mode 100644 index 0000000..c235570 --- /dev/null +++ b/_web/src/views/system/account/settings/Custom.vue @@ -0,0 +1,75 @@ +<script> +import { colorList } from '@/components/SettingDrawer/settingConfig' +import ASwitch from 'ant-design-vue/es/switch' +import AList from 'ant-design-vue/es/list' +import AListItem from 'ant-design-vue/es/list/Item' +import { mixin } from '@/utils/mixin' + +const Meta = AListItem.Meta + +export default { + components: { + AListItem, + AList, + ASwitch, + Meta + }, + mixins: [mixin], + data () { + return { + } + }, + filters: { + themeFilter (theme) { + const themeMap = { + 'dark': '暗色', + 'light': '白色' + } + return themeMap[theme] + } + }, + methods: { + colorFilter (color) { + const c = colorList.find(o => o.color === color) + return c && c.key + }, + + onChange (checked) { + if (checked) { + this.$store.dispatch('ToggleTheme', 'dark') + } else { + this.$store.dispatch('ToggleTheme', 'light') + } + } + }, + render () { + return ( + <AList itemLayout="horizontal"> + <AListItem> + <Meta> + <a slot="title">风格配色</a> + <span slot="description"> + 整体风格配色设置 + </span> + </Meta> + <div slot="actions"> + <ASwitch checkedChildren="暗色" unCheckedChildren="白色" defaultChecked={this.navTheme === 'dark' && true || false} onChange={this.onChange} /> + </div> + </AListItem> + <AListItem> + <Meta> + <a slot="title">主题色</a> + <span slot="description"> + 页面风格配色: <a domPropsInnerHTML={ this.colorFilter(this.primaryColor) }/> + </span> + </Meta> + </AListItem> + </AList> + ) + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/account/settings/Index.vue b/_web/src/views/system/account/settings/Index.vue new file mode 100644 index 0000000..8db2efb --- /dev/null +++ b/_web/src/views/system/account/settings/Index.vue @@ -0,0 +1,155 @@ +<template> + <div class="page-header-index-wide"> + <a-card :bordered="false" :bodyStyle="{ padding: '16px 0', height: '100%' }" :style="{ height: '100%' }"> + <div class="account-settings-info-main" :class="device"> + <div class="account-settings-info-left"> + <a-menu + :mode="device == 'mobile' ? 'horizontal' : 'inline'" + :style="{ border: '0', width: device == 'mobile' ? '560px' : 'auto'}" + :selectedKeys="selectedKeys" + type="inner" + @openChange="onOpenChange" + > + <a-menu-item key="/account/settings/base"> + <router-link :to="{ name: 'BaseSettings' }"> + 基本设置 + </router-link> + </a-menu-item> + <a-menu-item key="/account/settings/security"> + <router-link :to="{ name: 'SecuritySettings' }"> + 安全设置 + </router-link> + </a-menu-item> + <a-menu-item key="/account/settings/custom"> + <router-link :to="{ name: 'CustomSettings' }"> + 个性化 + </router-link> + </a-menu-item> + <a-menu-item key="/account/settings/binding"> + <router-link :to="{ name: 'BindingSettings' }"> + 账户绑定 + </router-link> + </a-menu-item> + <a-menu-item key="/account/settings/notification"> + <router-link :to="{ name: 'NotificationSettings' }"> + 新消息通知 + </router-link> + </a-menu-item> + </a-menu> + </div> + <div class="account-settings-info-right"> + <div class="account-settings-info-title"> + <span>{{ $route.meta.title }}</span> + </div> + <route-view></route-view> + </div> + </div> + </a-card> + </div> +</template> + +<script> +import { PageView, RouteView } from '@/layouts' +import { mixinDevice } from '@/utils/mixin.js' + +export default { + components: { + RouteView, + PageView + }, + mixins: [mixinDevice], + data () { + return { + // horizontal inline + mode: 'inline', + + openKeys: [], + selectedKeys: [], + + // cropper + preview: {}, + option: { + img: '/avatar2.jpg', + info: true, + size: 1, + outputType: 'jpeg', + canScale: false, + autoCrop: true, + // 只有自动截图开启 宽度高度才生效 + autoCropWidth: 180, + autoCropHeight: 180, + fixedBox: true, + // 开启宽度和高度比例 + fixed: true, + fixedNumber: [1, 1] + }, + + pageTitle: '' + } + }, + mounted () { + this.updateMenu() + }, + methods: { + onOpenChange (openKeys) { + this.openKeys = openKeys + }, + updateMenu () { + const routes = this.$route.matched.concat() + this.selectedKeys = [ routes.pop().path ] + } + }, + watch: { + '$route' (val) { + this.updateMenu() + } + } +} +</script> + +<style lang="less" scoped> + .account-settings-info-main { + width: 100%; + display: flex; + height: 100%; + overflow: auto; + + &.mobile { + display: block; + + .account-settings-info-left { + border-right: unset; + border-bottom: 1px solid #e8e8e8; + width: 100%; + height: 50px; + overflow-x: auto; + overflow-y: scroll; + } + .account-settings-info-right { + padding: 20px 40px; + } + } + + .account-settings-info-left { + border-right: 1px solid #e8e8e8; + width: 224px; + } + + .account-settings-info-right { + flex: 1 1; + padding: 8px 40px; + + .account-settings-info-title { + color: rgba(0,0,0,.85); + font-size: 20px; + font-weight: 500; + line-height: 28px; + margin-bottom: 12px; + } + .account-settings-info-view { + padding-top: 12px; + } + } + } + +</style> diff --git a/_web/src/views/system/account/settings/Notification.vue b/_web/src/views/system/account/settings/Notification.vue new file mode 100644 index 0000000..cbea7fc --- /dev/null +++ b/_web/src/views/system/account/settings/Notification.vue @@ -0,0 +1,25 @@ +<template> + <a-list + itemLayout="horizontal" + :dataSource="data" + > + + </a-list> +</template> + +<script> +export default { + data () { + return { + data: [] + } + }, + methods: { + + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/account/settings/Security.vue b/_web/src/views/system/account/settings/Security.vue new file mode 100644 index 0000000..2d3b650 --- /dev/null +++ b/_web/src/views/system/account/settings/Security.vue @@ -0,0 +1,70 @@ +<template> + <div> + <a-list + itemLayout="horizontal" + :dataSource="data" + > + <a-list-item slot="renderItem" slot-scope="item, MenuIndex" :key="MenuIndex"> + <a-list-item-meta> + <a slot="title">{{ item.title }}</a> + <span slot="description"> + <span class="security-list-description">{{ item.description }}</span> + <span v-if="item.value"> : </span> + <span class="security-list-value">{{ item.value }}</span> + </span> + </a-list-item-meta> + <template v-if="item.actions"> + <a slot="actions" @click="item.actions.callback">{{ item.actions.title }}</a> + </template> + </a-list-item> + </a-list> + <upd-pwd ref="updPwd"/> + </div> +</template> + +<script> + import { mapGetters } from 'vuex' + import UpdPwd from './securityItem/updPwd' + export default { + components: { + UpdPwd + }, + data () { + return { + data: [] + } + }, + created () { + if (this.hasPerm('sysUser:updatePwd')) { + const updPwdMenu = { + title: '账户密码', + description: '当前密码强度', + value: '强', + actions: { title: '修改', + callback: () => { + this.$refs.updPwd.open(this.userInfo.id) + } + } + } + this.data.push(updPwdMenu) + } + const encryptedPhone = { title: '密保手机', description: '已绑定手机', value: '138****8293', actions: { title: '修改', callback: () => { this.$message.success('This is a message of success') } } } + const encryptedProblem = { title: '密保问题', description: '未设置密保问题,密保问题可有效保护账户安全', value: '', actions: { title: '设置', callback: () => { this.$message.error('This is a message of error') } } } + const encryptedEmail = { title: '备用邮箱', description: '已绑定邮箱', value: 'ant***sign.com', actions: { title: '修改', callback: () => { this.$message.warning('This is message of warning') } } } + const encryptedMfa = { title: 'MFA 设备', description: '未绑定 MFA 设备,绑定后,可以进行二次确认', value: '', actions: { title: '绑定', callback: () => { this.$message.info('This is a normal message') } } } + this.data.push(encryptedPhone) + this.data.push(encryptedProblem) + this.data.push(encryptedEmail) + this.data.push(encryptedMfa) + }, + computed: { + ...mapGetters(['userInfo']) + }, + methods: { + } + } +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/account/settings/securityItem/updPwd.vue b/_web/src/views/system/account/settings/securityItem/updPwd.vue new file mode 100644 index 0000000..a3bbcb1 --- /dev/null +++ b/_web/src/views/system/account/settings/securityItem/updPwd.vue @@ -0,0 +1,115 @@ +<template> + <a-modal + title="修改密码" + :visible="visible_updPwd" + :confirm-loading="confirmLoading" + @ok="handleOkUpdPwd" + @cancel="handleCancel" + > + <a-form :form="formUpdPwd"> + <a-form-item + label="原密码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入原密码" type="password" v-decorator="['password', {rules: [{required: true, message: '请输入原密码!'}]}]" /> + </a-form-item> + <a-form-item + label="新密码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input + placeholder="请输入新密码" + type="password" + v-decorator="['newPassword', {rules: [{required: true, message: '请输入新密码!'},{ + validator: validateToNextPassword, + },]}]" /> + </a-form-item> + <a-form-item + label="重复新密码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input + placeholder="请再次输入新密码" + type="password" + v-decorator="['confirm', {rules: [{required: true, message: '请再次输入新密码!'}, + { + validator: compareToFirstPassword, + }]}]" /> + </a-form-item> + </a-form> + </a-modal> + +</template> + +<script> + import { sysUserUpdatePwd } from '@/api/modular/system/userManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 16 } + }, + confirmLoading: false, + visible_updPwd: false, + userId: '', + formUpdPwd: this.$form.createForm(this) + } + }, + methods: { + open (id) { + this.userId = id + this.visible_updPwd = true + }, + handleOkUpdPwd () { + const { formUpdPwd: { validateFields } } = this + validateFields((errors, values) => { + if (!errors) { + this.confirmLoading = true + values.id = this.userId + sysUserUpdatePwd(values).then((res) => { + if (res.success) { + this.$message.success('修改成功') + this.handleCancel() + } else { + this.$message.error('修改失败:' + res.message) + } + // eslint-disable-next-line handle-callback-err + }).finally((err) => { + this.confirmLoading = false + }) + } + }) + }, + handleCancel () { + this.visible_updPwd = false + }, + compareToFirstPassword (rule, value, callback) { + const formUpdPwd = this.formUpdPwd + if (value && value !== formUpdPwd.getFieldValue('newPassword')) { + // eslint-disable-next-line standard/no-callback-literal + callback('请确认两次输入密码的一致性!') + } else { + callback() + } + }, + validateToNextPassword (rule, value, callback) { + const formUpdPwd = this.formUpdPwd + if (value && this.confirmDirty) { + formUpdPwd.validateFields(['confirm'], { force: true }) + } + callback() + } + } + } +</script> diff --git a/_web/src/views/system/app/addForm.vue b/_web/src/views/system/app/addForm.vue new file mode 100644 index 0000000..b754565 --- /dev/null +++ b/_web/src/views/system/app/addForm.vue @@ -0,0 +1,91 @@ +<template> + <a-modal + title="新增应用" + :width="500" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input v-decorator="['active']" /> + </a-form-item> + <a-form-item + label="应用名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入应用名称" v-decorator="['name', {rules: [{required: true, message: '请输入应用名称!'}]}]" /> + </a-form-item> + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + import { sysAppAdd } from '@/api/modular/system/appManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add () { + this.visible = true + this.form.getFieldDecorator('active', { initialValue: 'N' }) + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysAppAdd(values).then((res) => { + this.confirmLoading = false + if (res.success) { + this.$message.success('新增成功') + this.handleCancel() + this.$emit('ok', values) + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/app/editForm.vue b/_web/src/views/system/app/editForm.vue new file mode 100644 index 0000000..87497cf --- /dev/null +++ b/_web/src/views/system/app/editForm.vue @@ -0,0 +1,108 @@ +<template> + <a-modal + title="应用编辑" + :width="500" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form" > + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input v-decorator="['id']" /> + </a-form-item> + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input v-decorator="['active']" /> + </a-form-item> + <a-form-item + label="应用名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入应用名称" v-decorator="['name', {rules: [{required: true, message: '请输入应用名称!'}]}]" /> + </a-form-item> + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + import { sysAppEdit } from '@/api/modular/system/appManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 } + }, + visible: false, + confirmLoading: false, + visibleDef: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + name: record.name, + code: record.code, + active: record.active + } + ) + }, 100) + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysAppEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/app/index.vue b/_web/src/views/system/app/index.vue new file mode 100644 index 0000000..f6a3a0d --- /dev/null +++ b/_web/src/views/system/app/index.vue @@ -0,0 +1,194 @@ +/* eslint-disable eqeqeq */ +<template> + <div> + <x-card v-if="hasPerm('sysApp:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="应用名称"> + <a-input v-model="queryParam.name" allow-clear placeholder="请输入应用名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="唯一编码"> + <a-input v-model="queryParam.code" allow-clear placeholder="请输入唯一编码"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <span class="table-page-search-submitButtons"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false" > + <a-spin :spinning="loading"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + > + <template slot="operator" v-if="hasPerm('sysApp:add')"> + <a-button @click="$refs.addForm.add()" icon="plus" type="primary" v-if="hasPerm('sysApp:add')">新增应用</a-button> + </template> + <span slot="active" slot-scope="text"> + {{ activeFilter(text) }} + </span> + <span slot="status" slot-scope="text"> + {{ statusFilter(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysApp:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysApp:edit') & hasPerm('sysApp:delete')" /> + <a-popconfirm v-if="hasPerm('sysApp:delete')" placement="topRight" title="确认删除?" @confirm="() => sysAppDelete(record)"> + <a>删除</a> + </a-popconfirm> + <a-divider type="vertical" v-if="hasPerm('sysApp:setAsDefault') & hasPerm('sysApp:delete') & record.active == 'N' || hasPerm('sysApp:edit') & hasPerm('sysApp:setAsDefault') & record.active == 'N'" /> + <a-popconfirm v-if="hasPerm('sysApp:setAsDefault') & record.active == 'N'" placement="topRight" title="设置为默认应用?" @confirm="() => sysDefault(record)"> + <a>设为默认</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + </a-spin> + </a-card> + </div> +</template> +<script> + import { STable, XCard } from '@/components' + import { getAppPage, sysAppDelete, sysAppSetAsDefault } from '@/api/modular/system/appManage' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + import editForm from './editForm' + import addForm from './addForm' + export default { + components: { + XCard, + STable, + editForm, + addForm + }, + data () { + return { + // description: '面包屑说明', + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '应用名称', + dataIndex: 'name' + }, + { + title: '唯一编码', + dataIndex: 'code' + }, + { + title: '是否默认', + dataIndex: 'active', + scopedSlots: { customRender: 'active' } + }, + { + title: '状态', + dataIndex: 'status', + scopedSlots: { customRender: 'status' } + } + ], + tstyle: { 'padding-bottom': '0px', 'margin-bottom': '10px' }, + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return getAppPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + loading: false, + statusDict: [], + activeDict: [] + } + }, + created () { + this.sysDictTypeDropDown() + if (this.hasPerm('sysApp:edit') || this.hasPerm('sysApp:delete') || this.hasPerm('sysApp:setAsDefault')) { + this.columns.push({ + title: '操作', + width: '200px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + activeFilter (active) { + // eslint-disable-next-line eqeqeq + const values = this.activeDict.filter(item => item.code == active) + if (values.length > 0) { + return values[0].value + } + }, + statusFilter (status) { + // eslint-disable-next-line eqeqeq + const values = this.statusDict.filter(item => item.code == status) + if (values.length > 0) { + return values[0].value + } + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + sysDictTypeDropDown({ code: 'yes_or_no' }).then((res) => { + this.activeDict = res.data + }) + sysDictTypeDropDown({ code: 'common_status' }).then((res) => { + this.statusDict = res.data + }) + }, + handleOk () { + this.$refs.table.refresh() + }, + sysDefault (record) { + this.loading = true + sysAppSetAsDefault({ id: record.id }).then((res) => { + this.loading = false + if (res.success) { + this.$message.success('设置成功') + this.$refs.table.refresh() + } else { + this.$message.error('设置失败:' + res.message) + } + }) + }, + /** + * 删除应用 + */ + sysAppDelete (record) { + this.loading = true + sysAppDelete(record).then((res) => { + this.loading = false + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + } + } + } +</script> +<style scoped> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/area/index.vue b/_web/src/views/system/area/index.vue new file mode 100644 index 0000000..fdc22af --- /dev/null +++ b/_web/src/views/system/area/index.vue @@ -0,0 +1,130 @@ +/* eslint-disable */ +<template> + + <a-card :bordered="false"> + + <a-table + ref="table" + size="middle" + :rowKey="(record) => record.id" + :pagination="false" + :defaultExpandAllRows="true" + :columns="columns" + :dataSource="data" + :loading="loading" + showPagination="auto" + @expand="onExpand"> + </a-table> + + </a-card> + +</template> + +<script> + import { getAreaList } from '@/api/modular/system/areaManage' + + export default { + + data () { + return { + queryParam: {}, + data: [], + loading: true, + // 定义展开过的节点的id数组 + expandedData: [], + columns: [ + { + title: '名称', + dataIndex: 'name' + }, + { + title: '层级', + dataIndex: 'levelCode' + }, + { + title: '简称', + dataIndex: 'shortName' + }, + { + title: '组合名', + dataIndex: 'mergerName' + }, + { + title: '拼音', + dataIndex: 'pinyin' + }, + { + title: '邮编', + dataIndex: 'zipCode' + }, + { + title: '经度', + dataIndex: 'lng' + }, + { + title: '纬度', + dataIndex: 'lat' + } + ], + selectedRowKeys: [] + } + }, + + created () { + this.loadData() + }, + + methods: { + loadData () { + this.loading = true + getAreaList(this.queryParam).then((res) => { + if (res.success) { + this.data = res.data + this.removeEmptyChildren(this.data) + } + }).finally(() => { + this.loading = false + }) + }, + + removeEmptyChildren(data) { + if (data == null || data.length === 0) return + for (let i = 0; i < data.length; i++) { + const item = data[i] + // 如果为最终子节点或其为“市辖区”,则其没有子节点 + if (item.levelCode === 4 || (item.levelCode === 2 && item.name === '市辖区')) { + item.children = null + } + } + }, + onSelectChange (selectedRowKeys) { + this.selectedRowKeys = selectedRowKeys + }, + onExpand(expanded, record) { + if (expanded) { + // 判断其是否已经展开过 + const index = this.expandedData.indexOf(record.id) + // 如果没展开过,则请求接口 + if (index === -1) { + this.queryParam.parentCode = record.areaCode + getAreaList(this.queryParam).then((res) => { + if (res.success) { + // 设置为其子节点 + record.children = res.data + this.removeEmptyChildren(record.children) + // 将其放入展开过的id集合 + this.expandedData.push(record.id) + } + }).finally(() => { + this.loading = false + }) + } + } + } + } + } + +</script> +<style scoped> + +</style> diff --git a/_web/src/views/system/config/addForm.vue b/_web/src/views/system/config/addForm.vue new file mode 100644 index 0000000..ffa836c --- /dev/null +++ b/_web/src/views/system/config/addForm.vue @@ -0,0 +1,130 @@ +<template> + <a-modal + title="新增参数" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + <a-form-item + label="参数名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入参数名称" v-decorator="['name', {rules: [{required: true, message: '请输入参数名称!'}]}]" /> + </a-form-item> + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + <a-form-item + label="系统参数" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-radio-group v-decorator="['sysFlag',{rules: [{ required: true, message: '请选择是否为系统参数!' }]}]" > + <a-radio-button value="Y" > 是 </a-radio-button> + <a-radio-button value="N" > 否 </a-radio-button> + </a-radio-group> + </a-form-item> + <a-form-item + label="所属分类" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择所属分类" v-decorator="['groupCode', {rules: [{ required: true, message: '请选择取所属分类!' }]}]" > + <a-select-option v-for="(item,index) in groupCodeList" :key="index" :value="item.code" >{{ item.value }}</a-select-option> + </a-select> + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="参数值" + > + <a-input placeholder="请输入参数值" v-decorator="['value', {rules: [{required: true, message: '请输入参数值!'}]}]" /> + </a-form-item> + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + import { sysDictTypeDropDown, sysConfigAdd } from '@/api/modular/system/configManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + formLoading: true, + groupCodeList: [], + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add () { + this.visible = true + this.sysDictTypeDropDown() + }, + /** + * 获取所属分类 + */ + sysDictTypeDropDown () { + sysDictTypeDropDown({ code: 'consts_type' }).then((res) => { + this.groupCodeList = res.data + this.formLoading = false + }) + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysConfigAdd(values).then((res) => { + this.confirmLoading = false + if (res.success) { + this.$message.success('新增成功') + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/config/editForm.vue b/_web/src/views/system/config/editForm.vue new file mode 100644 index 0000000..a1354d5 --- /dev/null +++ b/_web/src/views/system/config/editForm.vue @@ -0,0 +1,158 @@ +<template> + <a-modal + title="参数编辑" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input v-decorator="['id']" /> + </a-form-item> + <a-form-item + label="参数名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入参数名称" v-decorator="['name', {rules: [{required: true, message: '请输入参数名称!'}]}]" /> + </a-form-item> + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" :disabled="editDisabled" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + <a-form-item + label="系统参数" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-radio-group :disabled="editDisabled" v-decorator="['sysFlag',{rules: [{ required: true, message: '请选择是否为系统参数!' }]}]" > + <a-radio-button value="Y" > 是 </a-radio-button> + <a-radio-button value="N" > 否 </a-radio-button> + </a-radio-group> + </a-form-item> + <a-form-item + label="所属分类" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-select :disabled="editDisabled" style="width: 100%" placeholder="请选择所属分类" v-decorator="['groupCode', {rules: [{ required: true, message: '请选择取所属分类!' }]}]" > + <a-select-option v-for="(item,index) in groupCodeList" :key="index" :value="item.code" >{{ item.value }}</a-select-option> + </a-select> + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="参数值" + has-feedback + > + <a-input placeholder="请输入参数值" v-decorator="['value', {rules: [{required: true, message: '请输入参数值!'}]}]" /> + </a-form-item> + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + import { sysDictTypeDropDown, sysConfigEdit } from '@/api/modular/system/configManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + formLoading: true, + groupCodeList: [], + editDisabled: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + name: record.name, + code: record.code, + groupCode: record.groupCode, + sysFlag: record.sysFlag, + value: record.value, + remark: record.remark + } + ) + }, 100) + // eslint-disable-next-line eqeqeq + if (record.sysFlag == 'Y') { + this.editDisabled = true + } + this.sysDictTypeDropDown() + }, + /** + * 获取所属分类 + */ + sysDictTypeDropDown () { + sysDictTypeDropDown({ code: 'consts_type' }).then((res) => { + this.groupCodeList = res.data + this.formLoading = false + }) + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysConfigEdit(values).then((res) => { + this.confirmLoading = false + if (res.success) { + this.$message.success('编辑成功') + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + this.editDisabled = false + } + } + } +</script> diff --git a/_web/src/views/system/config/index.vue b/_web/src/views/system/config/index.vue new file mode 100644 index 0000000..aaa7e53 --- /dev/null +++ b/_web/src/views/system/config/index.vue @@ -0,0 +1,184 @@ +<template> + <div> + <x-card v-if="hasPerm('sysConfig:page')"> + <div slot="content" class="table-page-search-wrapper" > + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="参数名称"> + <a-input v-model="queryParam.name" allow-clear placeholder="请输入参数名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="唯一编码"> + <a-input v-model="queryParam.code" allow-clear placeholder="请输入唯一编码"/> + </a-form-item> + </a-col> + <template v-if="advanced"> + <a-col :md="8" :sm="24"> + <a-form-item label="所属分类"> + <a-select v-model="queryParam.groupCode" placeholder="请选择所属分类" allow-clear> + <a-select-option v-for="(item,index) in groupCodeDictTypeDropDown" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + </template> + <a-col :md="!advanced && 8 || 24" :sm="24" > + <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} "> + <a-button type="primary" @click="$refs.table.refresh(true)" >查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + <a @click="toggleAdvanced" style="margin-left: 8px"> + {{ advanced ? '收起' : '展开' }} + <a-icon :type="advanced ? 'up' : 'down'"/> + </a> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.code" + > + <template slot="operator" v-if="hasPerm('sysConfig:add')"> + <a-button @click="$refs.addForm.add()" icon="plus" type="primary" v-if="hasPerm('sysConfig:add')">新增配置</a-button> + </template> + <span slot="name" slot-scope="text"> + <ellipsis :length="20" tooltip>{{ text }}</ellipsis> + </span> + <span slot="code" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="value" slot-scope="text"> + <ellipsis :length="16" tooltip>{{ text }}</ellipsis> + </span> + <span slot="remark" slot-scope="text"> + <ellipsis :length="16" tooltip>{{ text }}</ellipsis> + </span> + <span slot="groupCode" slot-scope="text"> + {{ 'consts_type' | dictType(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysConfig:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysConfig:edit') & hasPerm('sysConfig:delete')"/> + <a-popconfirm v-if="hasPerm('sysConfig:delete')" placement="topRight" title="确认删除?" @confirm="() => sysConfigDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" v-if="hasPerm('sysConfig:add')"/> + <edit-form ref="editForm" @ok="handleOk" v-if="hasPerm('sysConfig:edit')"/> + </a-card> + </div> +</template> +<script> + import { STable, Ellipsis, XCard } from '@/components' + import { sysConfigPage, sysConfigDelete } from '@/api/modular/system/configManage' + import addForm from './addForm' + import editForm from './editForm' + export default { + components: { + XCard, + STable, + Ellipsis, + addForm, + editForm + }, + data () { + return { + // 高级搜索 展开/关闭 + advanced: false, + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '参数名称', + dataIndex: 'name', + scopedSlots: { customRender: 'name' } + }, + { + title: '唯一编码', + dataIndex: 'code', + scopedSlots: { customRender: 'code' } + }, + { + title: '参数值', + dataIndex: 'value', + scopedSlots: { customRender: 'value' } + }, + { + title: '所属分类', + dataIndex: 'groupCode', + scopedSlots: { customRender: 'groupCode' } + }, + { + title: '备注', + dataIndex: 'remark', + scopedSlots: { customRender: 'remark' } + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysConfigPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + groupCodeDictTypeDropDown: [] + } + }, + /** + * 初始化判断按钮权限是否拥有,没有则不现实列 + */ + created () { + this.sysDictTypeDropDown() + if (this.hasPerm('sysConfig:edit') || this.hasPerm('sysConfig:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.groupCodeDictTypeDropDown = this.$options.filters['dictData']('consts_type') + }, + sysConfigDelete (record) { + sysConfigDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + toggleAdvanced () { + this.advanced = !this.advanced + }, + handleOk () { + this.$refs.table.refresh() + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/dashboard/Analysis.vue b/_web/src/views/system/dashboard/Analysis.vue new file mode 100644 index 0000000..6fd22be --- /dev/null +++ b/_web/src/views/system/dashboard/Analysis.vue @@ -0,0 +1,385 @@ +<template> + <div class="page-header-index-wide"> + <a-row :gutter="24"> + <a-col :sm="24" :md="12" :xl="6" :style="{ marginBottom: '24px' }"> + <chart-card :loading="loading" title="总销售额" total="¥126,560"> + <a-tooltip title="指标说明" slot="action"> + <a-icon type="info-circle-o" /> + </a-tooltip> + <div> + <trend flag="up" style="margin-right: 16px;"> + <span slot="term">周同比</span> + 12% + </trend> + <trend flag="down"> + <span slot="term">日同比</span> + 11% + </trend> + </div> + <template slot="footer">日均销售额<span>¥ 234.56</span></template> + </chart-card> + </a-col> + <a-col :sm="24" :md="12" :xl="6" :style="{ marginBottom: '24px' }"> + <chart-card :loading="loading" title="访问量" :total="8846 | NumberFormat"> + <a-tooltip title="指标说明" slot="action"> + <a-icon type="info-circle-o" /> + </a-tooltip> + <div> + <mini-area /> + </div> + <template slot="footer">日访问量<span> {{ '1234' | NumberFormat }}</span></template> + </chart-card> + </a-col> + <a-col :sm="24" :md="12" :xl="6" :style="{ marginBottom: '24px' }"> + <chart-card :loading="loading" title="支付笔数" :total="6560 | NumberFormat"> + <a-tooltip title="指标说明" slot="action"> + <a-icon type="info-circle-o" /> + </a-tooltip> + <div> + <mini-bar /> + </div> + <template slot="footer">转化率 <span>60%</span></template> + </chart-card> + </a-col> + <a-col :sm="24" :md="12" :xl="6" :style="{ marginBottom: '24px' }"> + <chart-card :loading="loading" title="运营活动效果" total="78%"> + <a-tooltip title="指标说明" slot="action"> + <a-icon type="info-circle-o" /> + </a-tooltip> + <div> + <mini-progress color="rgb(19, 194, 194)" :target="80" :percentage="78" height="8px" /> + </div> + <template slot="footer"> + <trend flag="down" style="margin-right: 16px;"> + <span slot="term">同周比</span> + 12% + </trend> + <trend flag="up"> + <span slot="term">日环比</span> + 80% + </trend> + </template> + </chart-card> + </a-col> + </a-row> + <a-card :loading="loading" :bordered="false" :body-style="{padding: '0'}"> + <div class="salesCard"> + <a-tabs default-active-key="1" size="large" :tab-bar-style="{marginBottom: '24px', paddingLeft: '16px'}"> + <div class="extra-wrapper" slot="tabBarExtraContent"> + <div class="extra-item"> + <a>今日</a> + <a>本周</a> + <a>本月</a> + <a>本年</a> + </div> + <a-range-picker :style="{width: '230px'}" /> + </div> + <a-tab-pane loading="true" tab="销售额" key="1"> + <a-row> + <a-col :xl="16" :lg="12" :md="12" :sm="24" :xs="24"> + <bar :data="barData" title="销售额排行" /> + </a-col> + <a-col :xl="8" :lg="12" :md="12" :sm="24" :xs="24"> + <rank-list title="门店销售排行榜" :list="rankList"/> + </a-col> + </a-row> + </a-tab-pane> + <a-tab-pane tab="访问量" key="2"> + <a-row> + <a-col :xl="16" :lg="12" :md="12" :sm="24" :xs="24"> + <bar :data="barData2" title="销售额趋势" /> + </a-col> + <a-col :xl="8" :lg="12" :md="12" :sm="24" :xs="24"> + <rank-list title="门店销售排行榜" :list="rankList"/> + </a-col> + </a-row> + </a-tab-pane> + </a-tabs> + </div> + </a-card> + <div class="antd-pro-pages-dashboard-analysis-twoColLayout" :class="isDesktop() ? 'desktop' : ''"> + <a-row :gutter="24" type="flex" :style="{ marginTop: '24px' }"> + <a-col :xl="12" :lg="24" :md="24" :sm="24" :xs="24"> + <a-card :loading="loading" :bordered="false" title="线上热门搜索" :style="{ height: '100%' }"> + <a-dropdown :trigger="['click']" placement="bottomLeft" slot="extra"> + <a class="ant-dropdown-link" href="#"> + <a-icon type="ellipsis" /> + </a> + <a-menu slot="overlay"> + <a-menu-item> + <a href="javascript:;">操作一</a> + </a-menu-item> + <a-menu-item> + <a href="javascript:;">操作二</a> + </a-menu-item> + </a-menu> + </a-dropdown> + <a-row :gutter="68"> + <a-col :xs="24" :sm="12" :style="{ marginBottom: ' 24px'}"> + <number-info :total="12321" :sub-total="17.1"> + <span slot="subtitle"> + <span>搜索用户数</span> + <a-tooltip title="指标说明" slot="action"> + <a-icon type="info-circle-o" :style="{ marginLeft: '8px' }" /> + </a-tooltip> + </span> + </number-info> + <!-- miniChart --> + <div> + <mini-smooth-area :style="{ height: '45px' }" :dataSource="searchUserData" :scale="searchUserScale" /> + </div> + </a-col> + <a-col :xs="24" :sm="12" :style="{ marginBottom: ' 24px'}"> + <number-info :total="2.7" :sub-total="26.2" status="down"> + <span slot="subtitle"> + <span>人均搜索次数</span> + <a-tooltip title="指标说明" slot="action"> + <a-icon type="info-circle-o" :style="{ marginLeft: '8px' }" /> + </a-tooltip> + </span> + </number-info> + <!-- miniChart --> + <div> + <mini-smooth-area :style="{ height: '45px' }" :dataSource="searchUserData" :scale="searchUserScale" /> + </div> + </a-col> + </a-row> + <div class="ant-table-wrapper"> + <a-table + row-key="index" + size="small" + :columns="searchTableColumns" + :dataSource="searchData" + :pagination="{ pageSize: 5 }" + > + <span slot="range" slot-scope="text, record"> + <trend :flag="record.status === 0 ? 'up' : 'down'"> + {{ text }}% + </trend> + </span> + </a-table> + </div> + </a-card> + </a-col> + <a-col :xl="12" :lg="24" :md="24" :sm="24" :xs="24"> + <a-card class="antd-pro-pages-dashboard-analysis-salesCard" :loading="loading" :bordered="false" title="销售额类别占比" :style="{ height: '100%' }"> + <div slot="extra" style="height: inherit;"> + <!-- style="bottom: 12px;display: inline-block;" --> + <span class="dashboard-analysis-iconGroup"> + <a-dropdown :trigger="['click']" placement="bottomLeft"> + <a-icon type="ellipsis" class="ant-dropdown-link" /> + <a-menu slot="overlay"> + <a-menu-item> + <a href="javascript:;">操作一</a> + </a-menu-item> + <a-menu-item> + <a href="javascript:;">操作二</a> + </a-menu-item> + </a-menu> + </a-dropdown> + </span> + <div class="analysis-salesTypeRadio"> + <a-radio-group defaultValue="a"> + <a-radio-button value="a">全部渠道</a-radio-button> + <a-radio-button value="b">线上</a-radio-button> + <a-radio-button value="c">门店</a-radio-button> + </a-radio-group> + </div> + </div> + <h4>销售额</h4> + <div> + <!-- style="width: calc(100% - 240px);" --> + <div> + <v-chart :force-fit="true" :height="405" :data="pieData" :scale="pieScale"> + <v-tooltip :showTitle="false" dataKey="item*percent" /> + <v-axis /> + <!-- position="right" :offsetX="-140" --> + <v-legend dataKey="item"/> + <v-pie position="percent" color="item" :vStyle="pieStyle" /> + <v-coord type="theta" :radius="0.75" :innerRadius="0.6" /> + </v-chart> + </div> + </div> + </a-card> + </a-col> + </a-row> + </div> + </div> +</template> +<script> +import moment from 'moment' +import { ChartCard, MiniArea, MiniBar, MiniProgress, RankList, Bar, Trend, NumberInfo, MiniSmoothArea } from '@/components' +import { mixinDevice } from '@/utils/mixin' +const barData = [] +const barData2 = [] +for (let i = 0; i < 12; i += 1) { + barData.push({ + x: `${i + 1}月`, + y: Math.floor(Math.random() * 1000) + 200 + }) + barData2.push({ + x: `${i + 1}月`, + y: Math.floor(Math.random() * 1000) + 200 + }) +} +const rankList = [] +for (let i = 0; i < 7; i++) { + rankList.push({ + name: '白鹭岛 ' + (i + 1) + ' 号店', + total: 1234.56 - i * 100 + }) +} +const searchUserData = [] +for (let i = 0; i < 7; i++) { + searchUserData.push({ + x: moment().add(i, 'days').format('YYYY-MM-DD'), + y: Math.ceil(Math.random() * 10) + }) +} +const searchUserScale = [ + { + dataKey: 'x', + alias: '时间' + }, + { + dataKey: 'y', + alias: '用户数', + min: 0, + max: 10 + }] +const searchTableColumns = [ + { + dataIndex: 'MenuIndex.vue', + title: '排名', + width: 90 + }, + { + dataIndex: 'keyword', + title: '搜索关键词' + }, + { + dataIndex: 'count', + title: '用户数' + }, + { + dataIndex: 'range', + title: '周涨幅', + align: 'right', + sorter: (a, b) => a.range - b.range, + scopedSlots: { customRender: 'range' } + } +] +const searchData = [] +for (let i = 0; i < 50; i += 1) { + searchData.push({ + index: i + 1, + keyword: `搜索关键词-${i}`, + count: Math.floor(Math.random() * 1000), + range: Math.floor(Math.random() * 100), + status: Math.floor((Math.random() * 10) % 2) + }) +} +const DataSet = require('@antv/data-set') +const sourceData = [ + { item: '家用电器', count: 32.2 }, + { item: '食用酒水', count: 21 }, + { item: '个护健康', count: 17 }, + { item: '服饰箱包', count: 13 }, + { item: '母婴产品', count: 9 }, + { item: '其他', count: 7.8 } +] +const pieScale = [{ + dataKey: 'percent', + min: 0, + formatter: '.0%' +}] +const dv = new DataSet.View().source(sourceData) +dv.transform({ + type: 'percent', + field: 'count', + dimension: 'item', + as: 'percent' +}) +const pieData = dv.rows +export default { + name: 'Analysis', + mixins: [mixinDevice], + components: { + ChartCard, + MiniArea, + MiniBar, + MiniProgress, + RankList, + Bar, + Trend, + NumberInfo, + MiniSmoothArea + }, + data () { + return { + loading: true, + rankList, + // 搜索用户数 + searchUserData, + searchUserScale, + searchTableColumns, + searchData, + barData, + barData2, + // + pieScale, + pieData, + sourceData, + pieStyle: { + stroke: '#fff', + lineWidth: 1 + } + } + }, + created () { + setTimeout(() => { + this.loading = !this.loading + }, 1000) + } +} +</script> +<style lang="less" scoped> + .extra-wrapper { + line-height: 55px; + padding-right: 24px; + .extra-item { + display: inline-block; + margin-right: 24px; + + a { + margin-left: 24px; + } + } + } + .antd-pro-pages-dashboard-analysis-twoColLayout { + position: relative; + display: flex; + display: block; + flex-flow: row wrap; + } + .antd-pro-pages-dashboard-analysis-salesCard { + height: calc(100% - 24px); + /deep/ .ant-card-head { + position: relative; + } + } + .dashboard-analysis-iconGroup { + i { + margin-left: 16px; + color: rgba(0,0,0,.45); + cursor: pointer; + transition: color .32s; + color: black; + } + } + .analysis-salesTypeRadio { + position: absolute; + right: 54px; + bottom: 12px; + } +</style> diff --git a/_web/src/views/system/dashboard/Monitor.vue b/_web/src/views/system/dashboard/Monitor.vue new file mode 100644 index 0000000..2b9c6c1 --- /dev/null +++ b/_web/src/views/system/dashboard/Monitor.vue @@ -0,0 +1,15 @@ +<template> + <div> + Monitor + </div> +</template> + +<script> +export default { + name: 'Monitor' +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/dashboard/TestWork.vue b/_web/src/views/system/dashboard/TestWork.vue new file mode 100644 index 0000000..ae80f93 --- /dev/null +++ b/_web/src/views/system/dashboard/TestWork.vue @@ -0,0 +1,117 @@ +<template> + <div> + <h2>本页面内容均为测试功能,暂不提供稳定性保证</h2> + <a-divider /> + <div class="multi-tab-test"> + <h4>多标签组件测试功能</h4> + <a-button @click="handleCloseCurrentTab" style="margin-right: 16px;">关闭当前页</a-button> + <a-button @click="handleOpenTab" style="margin-right: 16px;">打开 任务列表</a-button> + <a-popconfirm :visible="visible" @confirm="confirm" @cancel="cancel" okText="确定" cancelText="取消"> + <template v-slot:title> + <div> + <a-form :form="form" layout="inline"> + <a-form-item label="自定义名称"> + <a-input v-decorator="['tabName', {rules: [{required: true, message: '请输入新的 Tab 名称'}]}]"/> + </a-form-item> + </a-form> + </div> + </template> + <a-button @click="() => visible = !visible" style="margin-right: 16px;">修改当前 Tab 名称</a-button> + </a-popconfirm> + + <a-popconfirm :visible="visible2" @confirm="confirm2" @cancel="() => visible2 = false" okText="确定" cancelText="取消"> + <template v-slot:title> + <div> + <p>页面 KEY 是由页面的路由 <code>path</code> 决定的</p> + <p>如果要修改某一个页面标题,该页面必须已经被打开在 Tab 栏</p> + <p>后期可以考虑优化到编程式 Tab 栏,就可以没有这种限制</p> + <a-form :form="form2" layout="inline"> + <a-form-item label="页面KEY"> + <a-input v-decorator="['tabKey', { initialValue: '/dashboard/workplace' }]" /> + </a-form-item> + <a-form-item label="自定义名称"> + <a-input v-decorator="['tabName', {rules: [{required: true, message: '请输入新的 Tab 名称'}]}]"/> + </a-form-item> + </a-form> + </div> + </template> + <a-button @click="() => visible2 = !visible2">修改某一个 Tab 名称</a-button> + </a-popconfirm> + </div> + <a-divider /> + <div class="page-loading-test"> + <h4>全局遮罩测试</h4> + <a-button @click="handleOpenLoading" style="margin-right: 16px;">打开遮罩(5s 自动关闭)</a-button> + <a-button @click="handleOpenLoadingCustomTip">打开遮罩(自定义提示语)</a-button> + </div> + </div> +</template> + +<script> +export default { + name: 'TestWork', + data () { + return { + visible: false, + visible2: false + } + }, + created () { + this.form = this.$form.createForm(this) + this.form2 = this.$form.createForm(this) + }, + methods: { + handleCloseCurrentTab () { + this.$multiTab.closeCurrentPage() // or this.$multiTab.close() + }, + handleOpenTab () { + this.$multiTab.open('/features/task') + }, + + handleOpenLoading () { + this.$nextTick(function () { + console.log('this', this) + console.log('this.$refs.tInput', this.$refs.tInput) + }) + this.$loading.show() + setTimeout(() => { + this.$loading.hide() + }, 5000) + }, + handleOpenLoadingCustomTip () { + this.$loading.show({ tip: '自定义提示语' }) + setTimeout(() => { + this.$loading.hide() + }, 5000) + }, + + // confirm + confirm (e) { + e.stopPropagation() + const { path } = this.$route + this.form.validateFields((err, values) => { + if (!err) { + this.$multiTab.rename(path, values.tabName) + this.visible = false + } + }) + }, + cancel () { + this.visible = false + }, + confirm2 (e) { + e.stopPropagation() + this.form2.validateFields((err, values) => { + if (!err) { + this.$multiTab.rename(values.tabKey, values.tabName) + this.visible2 = false + } + }) + } + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/dashboard/Workplace.vue b/_web/src/views/system/dashboard/Workplace.vue new file mode 100644 index 0000000..1b4c9e9 --- /dev/null +++ b/_web/src/views/system/dashboard/Workplace.vue @@ -0,0 +1,526 @@ +<template> + <page-view :avatar="avatar" :title="false"> + <div slot="headerContent"> + <div class="title">{{ timeFix }},{{ user.name }}<span class="welcome-text">,{{ welcome }}</span></div> + <div style="margin-bottom:10px">前端工程师 | 蚂蚁金服 - 某某某事业群 - VUE平台</div> + </div> + <div slot="extra"> + <a-row class="more-info"> + <a-col :span="8"> + <head-info title="项目" content="56" :center="false" :bordered="false"/> + </a-col> + <a-col :span="8"> + <head-info title="团队排名" content="8/24" :center="false" :bordered="false"/> + </a-col> + <a-col :span="8"> + <head-info title="项目数" content="2,223" :center="false" /> + </a-col> + </a-row> + </div> + + <div> + <a-row :gutter="24"> + <a-col :xl="16" :lg="24" :md="24" :sm="24" :xs="24"> + <a-card + class="project-list" + :loading="loading" + style="margin-bottom: 24px;" + :bordered="false" + title="进行中的项目" + :body-style="{ padding: 0 }"> + <a slot="extra">全部项目</a> + <div> + <a-card-grid class="project-card-grid" :key="i" v-for="(item, i) in projects"> + <a-card :bordered="false" :body-style="{ padding: 0 }"> + <a-card-meta> + <div slot="title" class="card-title"> + <a-avatar size="small" :src="item.cover"/> + <a>{{ item.title }}</a> + </div> + <div slot="description" class="card-description"> + {{ item.description }} + </div> + </a-card-meta> + <div class="project-item"> + <a href="/#/">科学搬砖组</a> + <span class="datetime">9小时前</span> + </div> + </a-card> + </a-card-grid> + </div> + </a-card> + + <a-card :loading="loading" title="动态" :bordered="false"> + <a-list> + <a-list-item :key="index" v-for="(item, index) in activities"> + <a-list-item-meta> + <a-avatar slot="avatar" :src="item.user.avatar" /> + <div slot="title"> + <span>{{ item.user.nickname }}</span> + 在 <a href="#">{{ item.project.name }}</a> + <span>{{ item.project.action }}</span> + <a href="#">{{ item.project.event }}</a> + </div> + <div slot="description">{{ item.time }}</div> + </a-list-item-meta> + </a-list-item> + </a-list> + </a-card> + </a-col> + <a-col + style="padding: 0 12px" + :xl="8" + :lg="24" + :md="24" + :sm="24" + :xs="24"> + <a-card title="快速开始 / 便捷导航" style="margin-bottom: 24px" :bordered="false" :body-style="{padding: 0}"> + <div class="item-group"> + <a>操作一</a> + <a>操作二</a> + <a>操作三</a> + <a>操作四</a> + <a>操作五</a> + <a>操作六</a> + <a-button size="small" type="primary" ghost icon="plus">添加</a-button> + </div> + </a-card> + <a-card title="XX 指数" style="margin-bottom: 24px" :loading="radarLoading" :bordered="false" :body-style="{ padding: 0 }"> + <div style="min-height: 400px;"> + <!-- :scale="scale" :axis1Opts="axis1Opts" :axis2Opts="axis2Opts" --> + <radar :data="radarData" /> + </div> + </a-card> + <a-card :loading="loading" title="团队" :bordered="false"> + <div class="members"> + <a-row> + <a-col :span="12" v-for="(item, index) in teams" :key="index"> + <a> + <a-avatar size="small" :src="item.avatar" /> + <span class="member">{{ item.name }}</span> + </a> + </a-col> + </a-row> + </div> + </a-card> + </a-col> + </a-row> + </div> + </page-view> +</template> + +<script> + import { timeFix } from '@/utils/util' + import { mapState } from 'vuex' + import { PageView } from '@/layouts' + import HeadInfo from '@/components/tools/HeadInfo' + import { Radar } from '@/components' + const DataSet = require('@antv/data-set') + + export default { + name: 'Workplace', + components: { + PageView, + HeadInfo, + Radar + }, + data () { + return { + timeFix: timeFix(), + avatar: '', + user: {}, + + projects: [], + loading: true, + radarLoading: true, + activities: [], + teams: [], + + // data + axis1Opts: { + dataKey: 'item', + line: null, + tickLine: null, + grid: { + lineStyle: { + lineDash: null + }, + hideFirstLine: false + } + }, + axis2Opts: { + dataKey: 'score', + line: null, + tickLine: null, + grid: { + type: 'polygon', + lineStyle: { + lineDash: null + } + } + }, + scale: [{ + dataKey: 'score', + min: 0, + max: 80 + }], + axisData: [ + { item: '引用', a: 70, b: 30, c: 40 }, + { item: '口碑', a: 60, b: 70, c: 40 }, + { item: '产量', a: 50, b: 60, c: 40 }, + { item: '贡献', a: 40, b: 50, c: 40 }, + { item: '热度', a: 60, b: 70, c: 40 }, + { item: '引用', a: 70, b: 50, c: 40 } + ], + radarData: [] + } + }, + computed: { + ...mapState({ + nickname: (state) => state.user.nickname, + welcome: (state) => state.user.welcome + }), + userInfo () { + return this.$store.getters.userInfo + } + }, + created () { + this.user = this.userInfo + this.avatar = process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + this.userInfo.avatar + }, + mounted () { + this.getProjects() + this.getActivity() + this.getTeams() + this.initRadar() + }, + methods: { + getProjects () { + this.projects = [{ + id: 1, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', + title: 'Alipay', + description: '那是一种内在的东西, 他们到达不了,也无法触及的', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 2, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', + title: 'Angular', + description: '希望是一个好东西,也许是最好的,好东西是不会消亡的', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 3, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', + title: 'Ant Design', + description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 4, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', + title: 'Snowy', + description: '那时候我只会想自己想要什么,从不想自己拥有什么', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 5, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', + title: 'Bootstrap', + description: '凛冬将至', + status: 1, + updatedAt: '2018-07-26 00:00:00' + }, + { + id: 6, + cover: 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', + title: 'Vue', + description: '生命就像一盒巧克力,结果往往出人意料', + status: 1, + updatedAt: '2018-07-26 00:00:00' + } + ] + this.loading = false + }, + getActivity () { + this.activities = [{ + id: 1, + user: { + nickname: '@name', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png' + }, + project: { + name: '白鹭酱油开发组', + action: '更新', + event: '番组计划' + }, + time: '2018-08-23 14:47:00' + }, + { + id: 1, + user: { + nickname: '蓝莓酱', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png' + }, + project: { + name: '白鹭酱油开发组', + action: '更新', + event: '番组计划' + }, + time: '2018-08-23 09:35:37' + }, + { + id: 1, + user: { + nickname: '@name', + avatar: '@image(64x64)' + }, + project: { + name: '白鹭酱油开发组', + action: '创建', + event: '番组计划' + }, + time: '2017-05-27 00:00:00' + }, + { + id: 1, + user: { + nickname: '曲丽丽', + avatar: '@image(64x64)' + }, + project: { + name: '高逼格设计天团', + action: '更新', + event: '六月迭代' + }, + time: '2018-08-23 14:47:00' + }, + { + id: 1, + user: { + nickname: '@name', + avatar: '@image(64x64)' + }, + project: { + name: '高逼格设计天团', + action: 'created', + event: '六月迭代' + }, + time: '2018-08-23 14:47:00' + }, + { + id: 1, + user: { + nickname: '曲丽丽', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png' + }, + project: { + name: '高逼格设计天团', + action: 'created', + event: '六月迭代' + }, + time: '2018-08-23 14:47:00' + } + ] + }, + getTeams () { + this.teams = [{ + id: 1, + name: '科学搬砖组', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png' + }, + { + id: 2, + name: '程序员日常', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png' + }, + { + id: 1, + name: '设计天团', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png' + }, + { + id: 1, + name: '中二少女团', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png' + }, + { + id: 1, + name: '骗你学计算机', + avatar: 'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png' + } + ] + }, + initRadar () { + this.radarLoading = true + const dv = new DataSet.View().source( + [{ + item: '引用', + '个人': 70, + '团队': 30, + '部门': 40 + }, + { + item: '口碑', + '个人': 60, + '团队': 70, + '部门': 40 + }, + { + item: '产量', + '个人': 50, + '团队': 60, + '部门': 40 + }, + { + item: '贡献', + '个人': 40, + '团队': 50, + '部门': 40 + }, + { + item: '热度', + '个人': 60, + '团队': 70, + '部门': 40 + }, + { + item: '引用', + '个人': 70, + '团队': 50, + '部门': 40 + } + ] + ) + dv.transform({ + type: 'fold', + fields: ['个人', '团队', '部门'], + key: 'user', + value: 'score' + }) + + this.radarData = dv.rows + this.radarLoading = false + } + } + } +</script> + +<style lang="less" scoped> + .project-list { + + .card-title { + font-size: 0; + + a { + color: rgba(0, 0, 0, 0.85); + margin-left: 12px; + line-height: 24px; + height: 24px; + display: inline-block; + vertical-align: top; + font-size: 14px; + + &:hover { + color: #1890ff; + } + } + } + .card-description { + color: rgba(0, 0, 0, 0.45); + height: 44px; + line-height: 22px; + overflow: hidden; + } + .project-item { + display: flex; + margin-top: 8px; + overflow: hidden; + font-size: 12px; + height: 20px; + line-height: 20px; + a { + color: rgba(0, 0, 0, 0.45); + display: inline-block; + flex: 1 1 0; + + &:hover { + color: #1890ff; + } + } + .datetime { + color: rgba(0, 0, 0, 0.25); + flex: 0 0 auto; + float: right; + } + } + .ant-card-meta-description { + color: rgba(0, 0, 0, 0.45); + height: 44px; + line-height: 22px; + overflow: hidden; + } + } + + .item-group { + padding: 20px 0 8px 24px; + font-size: 0; + a { + color: rgba(0, 0, 0, 0.65); + display: inline-block; + font-size: 14px; + margin-bottom: 13px; + width: 25%; + } + } + + .members { + a { + display: block; + margin: 12px 0; + line-height: 24px; + height: 24px; + .member { + font-size: 14px; + color: rgba(0, 0, 0, .65); + line-height: 24px; + max-width: 100px; + vertical-align: top; + margin-left: 12px; + transition: all 0.3s; + display: inline-block; + } + &:hover { + span { + color: #1890ff; + } + } + } + } + + .mobile { + + .project-list { + + .project-card-grid { + width: 100%; + } + } + + .more-info { + border: 0; + padding-top: 16px; + margin: 16px 0 16px; + } + + .headerContent .title .welcome-text { + display: none; + } + } + +</style> diff --git a/_web/src/views/system/dict/addForm.vue b/_web/src/views/system/dict/addForm.vue new file mode 100644 index 0000000..94dad70 --- /dev/null +++ b/_web/src/views/system/dict/addForm.vue @@ -0,0 +1,106 @@ +<template> + <a-modal + title="新增字典类型" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item + label="类型名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入类型名称" v-decorator="['name', {rules: [{required: true, message: '请输入类型名称!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + > + <a-input-number placeholder="请输入排序" style="width: 100%" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysDictTypeAdd } from '@/api/modular/system/dictManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add (record) { + this.visible = true + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysDictTypeAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/dict/dictdata/addForm.vue b/_web/src/views/system/dict/dictdata/addForm.vue new file mode 100644 index 0000000..2dce343 --- /dev/null +++ b/_web/src/views/system/dict/dictdata/addForm.vue @@ -0,0 +1,123 @@ +<template> + <a-modal + title="新增字典值" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input v-decorator="['typeId']" /> + </a-form-item> + + <a-form-item + label="字典值" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入字典值" v-decorator="['value', {rules: [{required: true, message: '请输入字典值!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + > + <a-input-number placeholder="请输入排序" style="width: 100%" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysDictDataAdd } from '@/api/modular/system/dictDataManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add (record) { + this.visible = true + // 增加上级类型ID + setTimeout(() => { + this.form.setFieldsValue( + { + typeId: record + } + ) + }, 100) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysDictDataAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/dict/dictdata/editForm.vue b/_web/src/views/system/dict/dictdata/editForm.vue new file mode 100644 index 0000000..a6bfeb1 --- /dev/null +++ b/_web/src/views/system/dict/dictdata/editForm.vue @@ -0,0 +1,137 @@ +<template> + <a-modal + title="字典值编辑" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input v-decorator="['id']" /> + </a-form-item> + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input v-decorator="['typeId']" /> + </a-form-item> + + <a-form-item + label="字典值" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入字典值" v-decorator="['value', {rules: [{required: true, message: '请输入字典值!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + has-feedback + > + <a-input-number style="width: 100%" placeholder="请输入排序" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysDictDataEdit } from '@/api/modular/system/dictDataManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + typeId: record.typeId, + value: record.value, + code: record.code, + sort: record.sort, + remark: record.remark + } + ) + }, 100) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysDictDataEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/dict/dictdata/index.vue b/_web/src/views/system/dict/dictdata/index.vue new file mode 100644 index 0000000..b2a4ca2 --- /dev/null +++ b/_web/src/views/system/dict/dictdata/index.vue @@ -0,0 +1,181 @@ +<template> + <a-modal + title="字典值管理" + :width="900" + :visible="visible" + :footer="null" + @cancel="handleCancel" + > + <x-card v-if="hasPerm('sysDictData:page')"> + <div slot="content" class="table-page-search-wrapper" > + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="字典值" > + <a-input v-model="queryParam.value" allow-clear placeholder="请输入字典值"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="唯一编码" > + <a-input v-model="queryParam.code" allow-clear placeholder="请输入唯一编码"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <span class="table-page-search-submitButtons"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.code" + > + <template slot="operator" v-if="hasPerm('sysDictData:add')" > + <a-button @click="$refs.addForm.add(typeId)" icon="plus" type="primary" v-if="hasPerm('sysDictData:add')">新增数据</a-button> + </template> + <span slot="status" slot-scope="text"> + {{ statusFilter(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysDictData:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysDictData:edit') & hasPerm('sysDictData:delete')"/> + <a-popconfirm v-if="hasPerm('sysDictData:delete')" placement="topRight" title="确认删除?" @confirm="() => sysDictDataDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + </a-card> + </a-modal> +</template> +<script> + import { STable, XCard } from '@/components' + import { sysDictDataPage, sysDictDataDelete } from '@/api/modular/system/dictDataManage' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + import addForm from './addForm' + import editForm from './editForm' + export default { + components: { + XCard, + STable, + addForm, + editForm + }, + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '字典值', + dataIndex: 'value' + }, + { + title: '唯一编码', + dataIndex: 'code' + }, + { + title: '排序', + dataIndex: 'sort' + }, + { + title: '备注', + dataIndex: 'remark', + width: 200 + }, + { + title: '状态', + dataIndex: 'status', + scopedSlots: { customRender: 'status' } + } + ], + visible: false, + typeId: [], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + this.queryParam.typeId = this.typeId + return sysDictDataPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + statusDict: [] + } + }, + created () { + this.sysDictTypeDropDown() + if (this.hasPerm('sysDictData:edit') || this.hasPerm('sysDictData:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + // 打开此页面首先加载此方法 + index (record) { + this.visible = true + this.typeId = record.id + this.queryParam.typeId = record.id + try { + this.$refs.table.refresh() + } catch (e) { + // 首次进入界面,因表格加载顺序,会抛异常,我们不予理会 + } + }, + statusFilter (status) { + // eslint-disable-next-line eqeqeq + const values = this.statusDict.filter(item => item.code == status) + if (values.length > 0) { + return values[0].value + } + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + sysDictTypeDropDown({ code: 'common_status' }).then((res) => { + this.statusDict = res.data + }) + }, + handleCancel () { + this.queryParam = {} + this.visible = false + }, + sysDictDataDelete (record) { + sysDictDataDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + handleOk () { + this.$refs.table.refresh() + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/dict/editForm.vue b/_web/src/views/system/dict/editForm.vue new file mode 100644 index 0000000..49ee823 --- /dev/null +++ b/_web/src/views/system/dict/editForm.vue @@ -0,0 +1,128 @@ +<template> + <a-modal + title="字典类型编辑" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input v-decorator="['id']" /> + </a-form-item> + + <a-form-item + label="类型名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入类型名称" v-decorator="['name', {rules: [{required: true, message: '请输入类型名称!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + has-feedback + > + <a-input-number style="width: 100%" placeholder="请输入排序" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysDictTypeEdit } from '@/api/modular/system/dictManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + name: record.name, + code: record.code, + sort: record.sort, + remark: record.remark + } + ) + }, 100) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysDictTypeEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/dict/index.vue b/_web/src/views/system/dict/index.vue new file mode 100644 index 0000000..155a4e6 --- /dev/null +++ b/_web/src/views/system/dict/index.vue @@ -0,0 +1,168 @@ +<template> + <div> + <x-card v-if="hasPerm('sysDictType:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="类型名称" > + <a-input v-model="queryParam.name" allow-clear placeholder="请输入类型名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="唯一编码" v-if="hasPerm('sysDictType:page')"> + <a-input v-model="queryParam.code" allow-clear placeholder="请输入唯一编码"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <span class="table-page-search-submitButtons"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.code" + > + <template slot="operator" v-if="hasPerm('sysDictType:add')"> + <a-button @click="$refs.addForm.add()" icon="plus" type="primary" v-if="hasPerm('sysDictType:add')">新增类型</a-button> + </template> + <span slot="status" slot-scope="text"> + {{ statusFilter(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <a @click="$refs.dataIndex.index(record)">字典</a> + <a-divider type="vertical" v-if="hasPerm('sysDictType:edit') || hasPerm('sysDictType:delete')"/> + <a-dropdown v-if="hasPerm('sysDictType:edit') || hasPerm('sysDictType:delete')"> + <a class="ant-dropdown-link"> + 更多 <a-icon type="down" /> + </a> + <a-menu slot="overlay"> + <a-menu-item v-if="hasPerm('sysDictType:edit')"> + <a @click="$refs.editForm.edit(record)">编辑</a> + </a-menu-item> + <a-menu-item v-if="hasPerm('sysDictType:delete')"> + <a-popconfirm placement="topRight" title="确认删除?" @confirm="() => sysDictTypeDelete(record)"> + <a>删除</a> + </a-popconfirm> + </a-menu-item> + </a-menu> + </a-dropdown> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + <data-index ref="dataIndex" @ok="handleOk" /> + </a-card> + </div> +</template> +<script> + import { STable, XCard } from '@/components' + import { sysDictTypePage, sysDictTypeDelete, sysDictTypeDropDown } from '@/api/modular/system/dictManage' + import addForm from './addForm' + import editForm from './editForm' + import dataIndex from './dictdata/index' + export default { + components: { + XCard, + STable, + addForm, + editForm, + dataIndex + }, + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '类型名称', + dataIndex: 'name' + }, + { + title: '唯一编码', + dataIndex: 'code' + }, + { + title: '排序', + dataIndex: 'sort' + }, + { + title: '备注', + dataIndex: 'remark', + width: 200 + }, + { + title: '状态', + dataIndex: 'status', + scopedSlots: { customRender: 'status' } + }, { + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysDictTypePage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + statusDict: [] + } + }, + created () { + this.sysDictTypeDropDown() + }, + methods: { + statusFilter (status) { + // eslint-disable-next-line eqeqeq + const values = this.statusDict.filter(item => item.code == status) + if (values.length > 0) { + return values[0].value + } + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + sysDictTypeDropDown({ code: 'common_status' }).then((res) => { + this.statusDict = res.data + }) + }, + sysDictTypeDelete (record) { + sysDictTypeDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + handleOk () { + this.$refs.table.refresh() + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/email/index.vue b/_web/src/views/system/email/index.vue new file mode 100644 index 0000000..bf53e32 --- /dev/null +++ b/_web/src/views/system/email/index.vue @@ -0,0 +1,178 @@ +<template> + <a-card :bordered="false"> + <a-spin :spinning="confirmLoading"> + <a-tabs default-active-key="1" > + <a-tab-pane key="1" tab="发送邮件" @change="tabsCallback" v-if="hasPerm('email:sendEmail')"> + <a-form :form="form1"> + <a-form-item + label="收件邮箱" + > + <a-input placeholder="请输入收件邮箱" v-decorator="['to', {rules: [{type: 'email',message: '请输入正确的邮箱!'},{required: true, message: '请输入收件邮箱!'}]}]" /> + </a-form-item> + <a-form-item + label="邮件标题" + > + <a-input placeholder="请输入邮件标题" v-decorator="['title', {rules: [{required: true, message: '请输入邮件标题!'}]}]" /> + </a-form-item> + <a-form-item + label="邮件内容" + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['content', {rules: [{required: true, message: '请输入邮件内容!'}]}]"></a-textarea> + </a-form-item> + <a-form-item class="subForm-item"> + <a-button type="primary" @click="handleSubmit1" :loading="confirmLoading">发送</a-button> + </a-form-item> + </a-form> + </a-tab-pane> + <a-tab-pane key="2" tab="发送Html邮件" @change="tabsCallback" v-if="hasPerm('email:sendEmailHtml')"> + <a-form :form="form2"> + <a-form-item + label="收件邮箱" + > + <a-input placeholder="请输入收件邮箱" v-decorator="['to',{rules: [ {type: 'email',message: '请输入正确的邮箱!'},{required: true, message: '请输入收件邮箱!'}]}]" /> + </a-form-item> + <a-form-item + label="邮件标题" + > + <a-input placeholder="请输入邮件标题" v-decorator="['title', {rules: [{required: true, message: '请输入邮件标题!'}]}]" /> + </a-form-item> + <a-form-item + label="邮件内容" + > + <antd-editor :uploadConfig="editorUploadConfig" v-model="editorContent" @onchange="changeEditor" @oninit="getEditor" /> + </a-form-item> + <a-form-item class="subForm-item"> + <a-button type="primary" @click="handleSubmit2" :loading="confirmLoading">发送</a-button> + </a-form-item> + </a-form> + </a-tab-pane> + </a-tabs> + </a-spin> + </a-card> +</template> +<script> + import { emailSendEmail, emailSendEmailHtml } from '@/api/modular/system/emailManage' + import { AntdEditor } from '@/components' + // eslint-disable-next-line no-unused-vars + import { sysFileInfoUpload, sysFileInfoDownload } from '@/api/modular/system/fileManage' + export default { + components: { + AntdEditor + }, + data () { + return { + editorContentText: '', + editorUploadConfig: { + method: 'http', + callback: this.editorUploadImage + }, + confirmLoading: false, + editorContent: '', + form1: this.$form.createForm(this), + form2: this.$form.createForm(this) + } + }, + methods: { + tabsCallback (key) { + if (key === '1') { + // eslint-disable-next-line no-labels + form1: this.$form.createForm(this) + this.form2.resetFields() + this.editor.txt.clear() + } + if (key === '2') { + // eslint-disable-next-line no-labels + form2: this.$form.createForm(this) + this.form1.resetFields() + } + }, + /** + * 编辑器回调上传及回传图片url + */ + editorUploadImage (files, insert) { + const formData = new FormData() + files.forEach(file => { + formData.append('file', file) + }) + sysFileInfoUpload(formData).then((res) => { + if (res.success) { + insert(process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + res.data) + } else { + this.$message.error('编辑器上传图片失败:' + res.message) + } + }) + }, + getEditor (editor) { + this.editor = editor + }, + changeEditor (html, ele) { + this.editorContent = html + this.editorContentText = ele.text() + }, + /** + * 发送邮件 + */ + handleSubmit1 () { + const { form1: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + emailSendEmail(values).then((res) => { + if (res.success) { + this.$message.success('发送成功') + this.confirmLoading = false + this.form1.resetFields() + } else { + this.$message.error('发送失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + /** + * 发送Html邮件 + */ + handleSubmit2 () { + const { form2: { validateFields } } = this + // eslint-disable-next-line eqeqeq + if (this.editorContent == '') { + this.$message.error('请填写邮件内容') + return + } + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + values.content = this.editorContent + emailSendEmailHtml(values).then((res) => { + if (res.success) { + this.$message.success('发送成功') + this.confirmLoading = false + this.editor.txt.clear() + this.form2.resetFields() + } else { + this.$message.error('发送失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + } + + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/exception/403.vue b/_web/src/views/system/exception/403.vue new file mode 100644 index 0000000..ffc3799 --- /dev/null +++ b/_web/src/views/system/exception/403.vue @@ -0,0 +1,17 @@ +<template> + <exception-page type="403" /> +</template> + +<script> +import { ExceptionPage } from '@/components' + +export default { + components: { + ExceptionPage + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/exception/404.vue b/_web/src/views/system/exception/404.vue new file mode 100644 index 0000000..16f767f --- /dev/null +++ b/_web/src/views/system/exception/404.vue @@ -0,0 +1,17 @@ +<template> + <exception-page type="404" /> +</template> + +<script> +import { ExceptionPage } from '@/components' + +export default { + components: { + ExceptionPage + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/exception/500.vue b/_web/src/views/system/exception/500.vue new file mode 100644 index 0000000..cc5d7ab --- /dev/null +++ b/_web/src/views/system/exception/500.vue @@ -0,0 +1,17 @@ +<template> + <exception-page type="500" /> +</template> + +<script> +import { ExceptionPage } from '@/components' + +export default { + components: { + ExceptionPage + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/src/views/system/file/detailForm.vue b/_web/src/views/system/file/detailForm.vue new file mode 100644 index 0000000..f25b719 --- /dev/null +++ b/_web/src/views/system/file/detailForm.vue @@ -0,0 +1,99 @@ +<template> + <a-modal + title="文件信息详情" + :footer="null" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item v-show="false"> + <a-input v-decorator="['id']" /> + </a-form-item> + <a-form-item + label="文件存储位置" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + {{ fileDetail.fileLocation }} + </a-form-item> + <a-form-item + label="文件仓库" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + {{ fileDetail.fileBucket }} + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="文件名称" + > + {{ fileDetail.fileOriginName }} + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="文件后缀" + > + {{ fileDetail.fileSuffix }} + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="文件大小" + > + {{ fileDetail.fileSizeKb }} + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="唯一标识" + > + {{ fileDetail.fileObjectName }} + </a-form-item> + + <a-form-item + label="存储路径" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + {{ fileDetail.filePath }} + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 8 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + fileDetail: [], + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + detail (record) { + this.fileDetail = record + this.visible = true + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/file/index.vue b/_web/src/views/system/file/index.vue new file mode 100644 index 0000000..ddad1ba --- /dev/null +++ b/_web/src/views/system/file/index.vue @@ -0,0 +1,256 @@ +<template> + <a-spin :spinning="cardLoading"> + <x-card v-if="hasPerm('sysFileInfo:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="存储位置"> + <a-select v-model="queryParam.fileLocation" placeholder="请选择存储位置" > + <a-select-option v-for="(item,index) in fileLocationDictTypeDropDown" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="文件仓库"> + <a-input v-model="queryParam.fileBucket" placeholder="请输入文件仓库"/> + </a-form-item> + </a-col> + <template v-if="advanced"> + <a-col :md="8" :sm="24"> + <a-form-item label="文件名称"> + <a-input v-model="queryParam.fileOriginName" placeholder="请输入文件名称(上传时候的文件名)"/> + </a-form-item> + </a-col> + </template> + <a-col :md="!advanced && 8 || 24" :sm="24"> + <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} "> + <a-button type="primary" @click="$refs.table.refresh(true)" >查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + <a @click="toggleAdvanced" style="margin-left: 8px"> + {{ advanced ? '收起' : '展开' }} + <a-icon :type="advanced ? 'up' : 'down'"/> + </a> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + :rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + > + <template slot="operator" v-if="hasPerm('sysFileInfo:upload')"> + <a-upload + :customRequest="customRequest" + :multiple="true" + :showUploadList="false" + name="file" + v-if="hasPerm('sysFileInfo:upload')" + > + <a-button> <a-icon type="upload" />上传文件</a-button> + </a-upload> + </template> + <span slot="fileOriginName" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="fileObjectName" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="fileLocation" slot-scope="text"> + {{ 'file_storage_location' | dictType(text) }} + </span> + <span slot="fileSuffix" slot-scope="text"> + <a-tag color="blue">{{ text }}</a-tag> + </span> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysFileInfo:download')" @click="sysFileInfoDownload(record)">下载</a> + <a-divider type="vertical" v-if="hasPerm('sysFileInfo:download') & hasPerm('sysFileInfo:detail')"/> + <a v-if="hasPerm('sysFileInfo:detail')" @click="$refs.detailForm.detail(record)">详情</a> + <a-divider type="vertical" v-if="hasPerm('sysFileInfo:detail') & hasPerm('sysFileInfo:delete')"/> + <a-popconfirm v-if="hasPerm('sysFileInfo:delete')" placement="topRight" title="确认删除?" @confirm="() => sysFileInfoDelete(record)"> + <a>删除</a> + </a-popconfirm> + <a-divider type="vertical" v-if="(hasPerm('sysFileInfo:preview') & record.fileSuffix === 'png' || record.fileSuffix === 'jpeg' || record.fileSuffix === 'jpg'|| record.fileSuffix === 'gif'|| record.fileSuffix === 'tif' || record.fileSuffix === 'bmp' ) & hasPerm('sysFileInfo:delete')"/> + <a v-if="(hasPerm('sysFileInfo:preview') & record.fileSuffix === 'png' || record.fileSuffix === 'jpeg'|| record.fileSuffix === 'jpg'|| record.fileSuffix === 'gif'|| record.fileSuffix === 'tif' || record.fileSuffix === 'bmp' )" @click="$refs.previewForm.preview(record)">预览</a> + <a-divider type="vertical" v-if="(hasPerm('sysFileInfo:preview') & record.fileSuffix === 'doc' || record.fileSuffix === 'docx'|| record.fileSuffix === 'xls'|| record.fileSuffix === 'xlsx') & hasPerm('sysFileInfo:delete')"/> + <a v-if="(hasPerm('sysFileInfo:preview') & record.fileSuffix === 'doc' || record.fileSuffix === 'docx'|| record.fileSuffix === 'xls'|| record.fileSuffix === 'xlsx')" @click="previewMicrosoft(record)">预览</a> + </span> + </s-table> + <detail-form ref="detailForm" @ok="handleOk" v-if="hasPerm('sysFileInfo:detail')"/> + <preview-form ref="previewForm" v-if="hasPerm('sysFileInfo:preview')"/> + </a-card> + </a-spin> +</template> +<script> + import { STable, Ellipsis, XCard } from '@/components' + import { sysFileInfoPage, sysFileInfoDelete, sysFileInfoUpload, sysFileInfoDownload } from '@/api/modular/system/fileManage' + import detailForm from './detailForm' + import previewForm from './previewForm' + export default { + components: { + XCard, + STable, + Ellipsis, + detailForm, + previewForm + }, + data () { + return { + // 高级搜索 展开/关闭 + advanced: false, + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '存储位置', + dataIndex: 'fileLocation', + scopedSlots: { customRender: 'fileLocation' } + }, + { + title: '文件仓库', + dataIndex: 'fileBucket' + }, + { + title: '文件名称', + dataIndex: 'fileOriginName', + scopedSlots: { customRender: 'fileOriginName' } + }, + { + title: '文件后缀', + dataIndex: 'fileSuffix', + scopedSlots: { customRender: 'fileSuffix' } + }, + { + title: '文件大小', + dataIndex: 'fileSizeInfo' + }, + { + title: '唯一标识id', + dataIndex: 'fileObjectName', + scopedSlots: { customRender: 'fileObjectName' } + } + + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysFileInfoPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + cardLoading: false, + fileLocationDictTypeDropDown: [], + selectedRowKeys: [], + selectedRows: [] + } + }, + created () { + this.sysDictTypeDropDown() + if (this.hasPerm('sysPos:edit') || this.hasPerm('sysPos:delete')) { + this.columns.push({ + title: '操作', + width: '200px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + /** + * 预览文件(微软插件) + */ + previewMicrosoft (record) { + window.open('https://view.officeapps.live.com/op/view.aspx?src=' + process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/download?id=' + record.id) + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.fileLocationDictTypeDropDown = this.$options.filters['dictData']('file_storage_location') + }, + /** + * 下载文件(所有文件) + */ + sysFileInfoDownload (record) { + this.cardLoading = true + sysFileInfoDownload({ id: record.id }).then((res) => { + this.cardLoading = false + this.downloadfile(res) + // eslint-disable-next-line handle-callback-err + }).catch((err) => { + this.cardLoading = false + this.$message.error('下载错误:获取文件流错误') + }) + }, + downloadfile (res) { + var blob = new Blob([res.data], { type: 'application/octet-stream;charset=UTF-8' }) + var contentDisposition = res.headers['content-disposition'] + var patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*') + var result = patt.exec(contentDisposition) + var filename = result[1] + var downloadElement = document.createElement('a') + var href = window.URL.createObjectURL(blob) // 创建下载的链接 + var reg = /^["](.*)["]$/g + downloadElement.style.display = 'none' + downloadElement.href = href + downloadElement.download = decodeURI(filename.replace(reg, '$1')) // 下载后文件名 + document.body.appendChild(downloadElement) + downloadElement.click() // 点击下载 + document.body.removeChild(downloadElement) // 下载完成移除元素 + window.URL.revokeObjectURL(href) + }, + sysFileInfoDelete (record) { + sysFileInfoDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + toggleAdvanced () { + this.advanced = !this.advanced + }, + /** + * 上传文件 + */ + customRequest (data) { + const formData = new FormData() + formData.append('file', data.file) + sysFileInfoUpload(formData).then((res) => { + if (res.success) { + this.$message.success('上传成功') + this.$refs.table.refresh() + } else { + this.$message.error('上传失败:' + res.message) + } + }) + }, + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/file/previewForm.vue b/_web/src/views/system/file/previewForm.vue new file mode 100644 index 0000000..f125b93 --- /dev/null +++ b/_web/src/views/system/file/previewForm.vue @@ -0,0 +1,60 @@ +<template> + <a-modal + title="预览图片" + :footer="null" + :width="900" + :visible="visible" + @cancel="handleCancel" + > + <a-spin :spinning="divLoading"> + <div style="text-align: center"> + <img :src="src" style="max-width: 99%"> + </div> + </a-spin> + </a-modal> +</template> +<script> + import { sysFileInfoPreview } from '@/api/modular/system/fileManage' + export default { + data () { + return { + visible: false, + src: '', + divLoading: false + } + }, + methods: { + /** + * 初始化 + */ + preview (record) { + this.visible = true + this.divLoading = true + this.sysFileInfoPreview(record) + }, + /** + * 获取图片并转为链接 + */ + sysFileInfoPreview (record) { + sysFileInfoPreview({ id: record.id }).then((res) => { + this.divLoading = false + this.downloadfile(res) + }).catch((err) => { + this.divLoading = false + this.$message.error('预览错误:' + err.message) + }) + }, + /** + * 转图片类型 + */ + downloadfile (res) { + const blob = new Blob([res]) + this.src = window.URL.createObjectURL(blob) + }, + handleCancel () { + this.src = '' + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/fileOnline/detailForm.vue b/_web/src/views/system/fileOnline/detailForm.vue new file mode 100644 index 0000000..f25b719 --- /dev/null +++ b/_web/src/views/system/fileOnline/detailForm.vue @@ -0,0 +1,99 @@ +<template> + <a-modal + title="文件信息详情" + :footer="null" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item v-show="false"> + <a-input v-decorator="['id']" /> + </a-form-item> + <a-form-item + label="文件存储位置" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + {{ fileDetail.fileLocation }} + </a-form-item> + <a-form-item + label="文件仓库" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + {{ fileDetail.fileBucket }} + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="文件名称" + > + {{ fileDetail.fileOriginName }} + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="文件后缀" + > + {{ fileDetail.fileSuffix }} + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="文件大小" + > + {{ fileDetail.fileSizeKb }} + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="唯一标识" + > + {{ fileDetail.fileObjectName }} + </a-form-item> + + <a-form-item + label="存储路径" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + {{ fileDetail.filePath }} + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 8 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + fileDetail: [], + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + detail (record) { + this.fileDetail = record + this.visible = true + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/fileOnline/index.vue b/_web/src/views/system/fileOnline/index.vue new file mode 100644 index 0000000..8a0a1f6 --- /dev/null +++ b/_web/src/views/system/fileOnline/index.vue @@ -0,0 +1,254 @@ +<template> + <a-spin :spinning="cardLoading"> + <x-card v-if="hasPerm('sysFileInfo:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="存储位置"> + <a-select v-model="queryParam.fileLocation" placeholder="请选择存储位置" > + <a-select-option v-for="(item,index) in fileLocationDictTypeDropDown" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="文件仓库"> + <a-input v-model="queryParam.fileBucket" placeholder="请输入文件仓库"/> + </a-form-item> + </a-col> + <template v-if="advanced"> + <a-col :md="8" :sm="24"> + <a-form-item label="文件名称"> + <a-input v-model="queryParam.fileOriginName" placeholder="请输入文件名称(上传时候的文件名)"/> + </a-form-item> + </a-col> + </template> + <a-col :md="!advanced && 8 || 24" :sm="24"> + <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} "> + <a-button type="primary" @click="$refs.table.refresh(true)" >查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = { fileSuffix: 'doc,docx,xls,xlsx,ppt,pptx' }">重置</a-button> + <a @click="toggleAdvanced" style="margin-left: 8px"> + {{ advanced ? '收起' : '展开' }} + <a-icon :type="advanced ? 'up' : 'down'"/> + </a> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + :rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + > + <span slot="fileOriginName" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="fileObjectName" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="fileLocation" slot-scope="text"> + {{ 'file_storage_location' | dictType(text) }} + </span> + <span slot="fileSuffix" slot-scope="text"> + <a-tag color="blue">{{ text }}</a-tag> + </span> + <span slot="action" slot-scope="text, record"> + <a @click="onlineEdit(record)">在线编辑</a> + <a-divider type="vertical"/> + <a @click="sysFileInfoDownload(record)">下载</a> + <a-divider type="vertical" /> + <a @click="onlinePreview(record, 'desktop')">桌面预览</a> + <a-divider type="vertical" /> + <a @click="onlinePreview(record, 'mobile')">手机预览</a> + <a-divider type="vertical" /> + <a-popconfirm placement="topRight" title="确认删除?" @confirm="() => sysFileInfoDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <preview-form ref="previewForm"/> + <online-edit-form ref="onlineEditForm"/> + </a-card> + </a-spin> +</template> +<script> + import { Ellipsis, STable, XCard } from '@/components' + import { + sysFileInfoDelete, + sysFileInfoDownload, + sysFileInfoGetOnlineConfig, + sysFileInfoPage + } from '@/api/modular/system/fileManage' + import previewForm from './previewForm' + import onlineEditForm from './onlineEditForm' + + export default { + components: { + XCard, + STable, + Ellipsis, + previewForm, + onlineEditForm + }, + data () { + return { + // 高级搜索 展开/关闭 + advanced: false, + // 查询参数 + queryParam: { fileSuffix: 'doc,docx,xls,xlsx,ppt,pptx' }, + // 表头 + columns: [ + { + title: '存储位置', + dataIndex: 'fileLocation', + scopedSlots: { customRender: 'fileLocation' } + }, + { + title: '文件仓库', + dataIndex: 'fileBucket' + }, + { + title: '文件名称', + dataIndex: 'fileOriginName', + scopedSlots: { customRender: 'fileOriginName' } + }, + { + title: '文件后缀', + dataIndex: 'fileSuffix', + scopedSlots: { customRender: 'fileSuffix' } + }, + { + title: '文件大小', + dataIndex: 'fileSizeInfo' + }, + { + title: '唯一标识id', + dataIndex: 'fileObjectName', + scopedSlots: { customRender: 'fileObjectName' } + } + + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysFileInfoPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + cardLoading: false, + fileLocationDictTypeDropDown: [], + selectedRowKeys: [], + selectedRows: [] + } + }, + created () { + this.sysDictTypeDropDown() + this.columns.push({ + title: '操作', + width: '350px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + }, + methods: { + /** + * 在线编辑 + */ + onlineEdit (record) { + this.cardLoading = true + sysFileInfoGetOnlineConfig({ id: record.id }).then((res) => { + this.cardLoading = false + this.$refs.onlineEditForm.onlineEdit(res, 'desktop') + }) + }, + /** + * 在线预览 + */ + onlinePreview (record, type) { + this.cardLoading = true + sysFileInfoGetOnlineConfig({ id: record.id }).then((res) => { + this.cardLoading = false + this.$refs.previewForm.preview(res, type) + }) + }, + /** + * 预览文件(微软插件) + */ + previewMicrosoft (record) { + window.open('https://view.officeapps.live.com/op/view.aspx?src=' + process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/download?id=' + record.id) + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.fileLocationDictTypeDropDown = this.$options.filters['dictData']('file_storage_location') + }, + /** + * 下载文件(所有文件) + */ + sysFileInfoDownload (record) { + this.cardLoading = true + sysFileInfoDownload({ id: record.id }).then((res) => { + this.cardLoading = false + this.downloadfile(res) + // eslint-disable-next-line handle-callback-err + }).catch((err) => { + this.cardLoading = false + this.$message.error('下载错误:获取文件流错误') + }) + }, + downloadfile (res) { + var blob = new Blob([res.data], { type: 'application/octet-stream;charset=UTF-8' }) + var contentDisposition = res.headers['content-disposition'] + var patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*') + var result = patt.exec(contentDisposition) + var filename = result[1] + var downloadElement = document.createElement('a') + var href = window.URL.createObjectURL(blob) // 创建下载的链接 + var reg = /^["](.*)["]$/g + downloadElement.style.display = 'none' + downloadElement.href = href + downloadElement.download = decodeURI(filename.replace(reg, '$1')) // 下载后文件名 + document.body.appendChild(downloadElement) + downloadElement.click() // 点击下载 + document.body.removeChild(downloadElement) // 下载完成移除元素 + window.URL.revokeObjectURL(href) + }, + sysFileInfoDelete (record) { + sysFileInfoDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + toggleAdvanced () { + this.advanced = !this.advanced + }, + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/fileOnline/onlineEditForm.vue b/_web/src/views/system/fileOnline/onlineEditForm.vue new file mode 100644 index 0000000..9b16ab9 --- /dev/null +++ b/_web/src/views/system/fileOnline/onlineEditForm.vue @@ -0,0 +1,91 @@ +<template> + <a-modal + title="在线编辑" + :footer="null" + :width="1500" + :visible="visible" + @cancel="handleCancel" + :destroyOnClose="true" + > + <a-spin :spinning="divLoading"> + <div class="editorview" style="height: 800px;"> + <Editor :option="option"/> + </div> + </a-spin> + </a-modal> +</template> +<script> + import Editor from '../../../components/xnComponents/EditorDiv' + import Vue from 'vue' + import { ACCESS_TOKEN } from '@/store/mutation-types' + + export default { + components: { + Editor + }, + data () { + return { + visible: false, + divLoading: false, + sysOnlineFileInfoResult: {}, + option: { + url: '', + isEdit: true, + fileType: '', + title: '', + token: Vue.ls.get(ACCESS_TOKEN), + user: { + id: '', + name: '' + }, + mode: '', + callbackUrl: '', + key: '', + review: false, + type: 'desktop' + } + } + }, + methods: { + /** + * 初始化 + */ + onlineEdit(record) { + this.visible = true + const data = record.data.sysOnlineFileInfoResult + this.option.user.id = data.editorConfig.user.id + this.option.user.name = data.editorConfig.user.name + this.option.fileType = data.document.fileType + this.option.title = data.document.title + this.option.key = data.document.key + this.option.url = process.env.VUE_APP_API_BASE_URL + data.document.url // res.data.docServiceApiUrl + this.callbackUrl = process.env.VUE_APP_API_BASE_URL + data.editorConfig.callbackUrl + // this.option.type = type + this.option.review = false + }, + handleCancel () { + this.visible = false + this.option = { + url: '', + isEdit: false, + fileType: '', + title: '', + token: Vue.ls.get(ACCESS_TOKEN), + user: { + id: '', + name: '' + }, + mode: '', + callbackUrl: '', + key: '', + review: false + } + } + } + } +</script> +<style> + .editorview iframe{ + position: absolute !important; + } +</style> diff --git a/_web/src/views/system/fileOnline/previewForm.vue b/_web/src/views/system/fileOnline/previewForm.vue new file mode 100644 index 0000000..24ef6cd --- /dev/null +++ b/_web/src/views/system/fileOnline/previewForm.vue @@ -0,0 +1,96 @@ +<template> + <a-modal + title="在线预览" + :footer="null" + :width="1500" + :visible="visible" + @cancel="handleCancel" + :destroyOnClose="true" + > + <a-spin :spinning="divLoading"> + <div class="editorview" style="height: 800px;"> + <Editor :option="option"/> + </div> + </a-spin> + </a-modal> +</template> +<script> + import Editor from '../../../components/xnComponents/EditorDiv' + import Vue from 'vue' + import { ACCESS_TOKEN } from '@/store/mutation-types' + + export default { + components: { + Editor + }, + data () { + return { + visible: false, + divLoading: false, + sysOnlineFileInfoResult: {}, + option: { + url: '', + isEdit: false, + fileType: '', + title: '', + token: Vue.ls.get(ACCESS_TOKEN), + user: { + id: '', + name: '' + }, + mode: '', + callbackUrl: '', + key: '', + review: false + } + } + }, + created () { + + }, + methods: { + /** + * 初始化 + */ + preview(record, type) { + this.visible = true + const data = record.data.sysOnlineFileInfoResult + this.option.user.id = '1265476890672672808' // data.editorConfig.user.id + this.option.user.name = '超级管理员' // data.editorConfig.user.name + this.option.fileType = data.document.fileType + this.option.title = data.document.title + this.option.key = data.document.key + this.option.url = process.env.VUE_APP_API_BASE_URL + data.document.url // res.data.docServiceApiUrl + this.callbackUrl = process.env.VUE_APP_API_BASE_URL + data.editorConfig.callbackUrl + this.option.type = type + }, + handleCancel () { + this.visible = false + this.option = { + url: '', + isEdit: false, + fileType: '', + title: '', + token: Vue.ls.get(ACCESS_TOKEN), + user: { + id: '', + name: '' + }, + mode: '', + callbackUrl: '', + key: '', + review: false + } + const oScript = document.createElement('script') + oScript.type = 'text/javascript' + oScript.src = '' + document.body.appendChild(oScript) + } + } + } +</script> +<style> + .editorview iframe{ + position: absolute !important; + } +</style> diff --git a/_web/src/views/system/index/welcome.vue b/_web/src/views/system/index/welcome.vue new file mode 100644 index 0000000..a5c7575 --- /dev/null +++ b/_web/src/views/system/index/welcome.vue @@ -0,0 +1,15 @@ +<template> + <a-card :bordered="false" style="display: flex;justify-content:center;height: 100%" > + <div style="margin:100px auto;"> + <img src="~@/assets/welcome.png" class="logo" alt="logo"> + </div> + </a-card> +</template> + +<script> + +</script> + +<style lang="less" scoped> + +</style> diff --git a/_web/src/views/system/log/oplog/details.vue b/_web/src/views/system/log/oplog/details.vue new file mode 100644 index 0000000..c7ddd8d --- /dev/null +++ b/_web/src/views/system/log/oplog/details.vue @@ -0,0 +1,137 @@ +<template> + <a-modal + title="日志详情" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="方法名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input v-decorator="['methodName']" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + label="地址" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input v-decorator="['location']" /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="浏览器" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input v-decorator="['browser']" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + label="操作系统" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input v-decorator="['os']" /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="类名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-textarea :rows="4" v-decorator="['className']"/> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + label="具体消息" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-textarea :rows="4" v-decorator="['message']"/> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="请求参数" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-textarea :rows="4" v-decorator="['param']"/> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + label="返回结果" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-textarea :rows="4" v-decorator="['result']"/> + </a-form-item> + </a-col> + </a-row> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + details (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + location: record.location, + browser: record.browser, + os: record.os, + className: record.className, + methodName: record.methodName, + param: record.param, + result: record.result, + message: record.message + } + ) + }, 100) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/log/oplog/index.vue b/_web/src/views/system/log/oplog/index.vue new file mode 100644 index 0000000..711fdc7 --- /dev/null +++ b/_web/src/views/system/log/oplog/index.vue @@ -0,0 +1,230 @@ +<template> + <div> + <x-card v-if="hasPerm('sysOpLog:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="日志名称"> + <a-input v-model="queryParam.name" allow-clear placeholder="请输入日志名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="操作类型"> + <a-select v-model="queryParam.opType" allow-clear placeholder="请选择操作类型" > + <a-select-option v-for="(item,index) in opTypeDict" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <template v-if="advanced"> + <a-col :md="8" :sm="24"> + <a-form-item label="是否成功"> + <a-select v-model="queryParam.success" placeholder="请选择是否成功" > + <a-select-option v-for="(item,index) in successDict" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="10" :sm="24"> + <a-form-item label="操作时间"> + <a-range-picker + v-model="queryParam.dates" + :show-time="{ + hideDisabledOptions: true, + defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], + }" + format="YYYY-MM-DD HH:mm:ss" + /> + </a-form-item> + </a-col> + </template> + <a-col :md="!advanced && 8 || 24" :sm="24"> + <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} "> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + <a @click="toggleAdvanced" style="margin-left: 8px"> + {{ advanced ? '收起' : '展开' }} + <a-icon :type="advanced ? 'up' : 'down'"/> + </a> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + > + <template slot="operator"> + <a-popconfirm v-if="hasPerm('sysOpLog:delete')" @confirm="() => sysOpLogDelete()" placement="top" title="确认清空日志?"> + <a-button type="danger" ghost>清空日志</a-button> + </a-popconfirm> + <x-down + v-if="hasPerm('sysOpLog:export')" + ref="batchExport" + @batchExport="batchExport" + /> + </template> + <span slot="opType" slot-scope="text"> + {{ 'op_type' | dictType(text) }} + </span> + <span slot="success" slot-scope="text"> + {{ 'yes_or_no' | dictType(text) }} + </span> + <span slot="name" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="url" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="opTime" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="action" slot-scope="text, record"> + <span slot="action" > + <a @click="$refs.detailsOplog.details(record)">查看详情</a> + </span> + </span> + </s-table> + <details-oplog ref="detailsOplog"/> + </a-card> + </div> +</template> +<script> + import { STable, Ellipsis, XCard, XDown } from '@/components' + import { sysOpLogPage, sysOpLogDelete, sysOpLogExport } from '@/api/modular/system/logManage' + import detailsOplog from './details' + import moment from 'moment' + export default { + components: { + XDown, + XCard, + STable, + Ellipsis, + detailsOplog + }, + data () { + return { + advanced: false, + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '日志名称', + dataIndex: 'name', + scopedSlots: { customRender: 'name' } + }, + { + title: '操作类型', + dataIndex: 'opType', + scopedSlots: { customRender: 'opType' } + }, + { + title: '执行结果', + dataIndex: 'success', + scopedSlots: { customRender: 'success' } + }, + { + title: 'ip', + dataIndex: 'ip' + }, + { + title: '请求地址', + dataIndex: 'url', + scopedSlots: { customRender: 'url' } + }, + { + title: '操作时间', + dataIndex: 'opTime', + scopedSlots: { customRender: 'opTime' } + }, + { + title: '操作人', + dataIndex: 'account' + }, + { + title: '详情', + dataIndex: 'action', + width: '150px', + scopedSlots: { customRender: 'action' } + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysOpLogPage(Object.assign(parameter, this.switchingDate())).then((res) => { + return res.data + }) + }, + opTypeDict: [], + successDict: [] + } + }, + created () { + this.sysDictTypeDropDown() + }, + methods: { + moment, + /** + * 查询参数组装 + */ + switchingDate () { + const dates = this.queryParam.dates + if (dates != null) { + this.queryParam.searchBeginTime = moment(dates[0]).format('YYYY-MM-DD HH:mm:ss') + this.queryParam.searchEndTime = moment(dates[1]).format('YYYY-MM-DD HH:mm:ss') + if (dates.length < 1) { + delete this.queryParam.searchBeginTime + delete this.queryParam.searchEndTime + } + } + const obj = JSON.parse(JSON.stringify(this.queryParam)) + delete obj.dates + return obj + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.opTypeDict = this.$options.filters['dictData']('op_type') + this.successDict = this.$options.filters['dictData']('yes_or_no') + }, + /** + * 清空日志 + */ + sysOpLogDelete () { + sysOpLogDelete().then((res) => { + if (res.success) { + this.$message.success('清空成功') + this.$refs.table.refresh(true) + } else { + this.$message.error('清空失败:' + res.message) + } + }) + }, + /** + * 批量导出 + */ + batchExport () { + sysOpLogExport().then((res) => { + this.$refs.batchExport.downloadfile(res) + }) + }, + toggleAdvanced () { + this.advanced = !this.advanced + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/log/vislog/details.vue b/_web/src/views/system/log/vislog/details.vue new file mode 100644 index 0000000..697c7d8 --- /dev/null +++ b/_web/src/views/system/log/vislog/details.vue @@ -0,0 +1,57 @@ +<template> + <a-modal + title="日志详情" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item + label="具体消息" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-textarea :rows="4" v-decorator="['message']"/> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + details (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + message: record.message + } + ) + }, 100) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/log/vislog/index.vue b/_web/src/views/system/log/vislog/index.vue new file mode 100644 index 0000000..fb37553 --- /dev/null +++ b/_web/src/views/system/log/vislog/index.vue @@ -0,0 +1,230 @@ +<template> + <div> + <x-card v-if="hasPerm('sysVisLog:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="日志名称"> + <a-input v-model="queryParam.name" allow-clear placeholder="请输入日志名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="访问类型"> + <a-select v-model="queryParam.visType" allow-clear placeholder="请选择访问类型" > + <a-select-option v-for="(item,index) in visTypeDict" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <template v-if="advanced"> + <a-col :md="8" :sm="24"> + <a-form-item label="是否成功"> + <a-select v-model="queryParam.success" placeholder="请选择是否成功" > + <a-select-option v-for="(item,index) in successDict" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="10" :sm="24"> + <a-form-item label="访问时间"> + <a-range-picker + v-model="queryParam.dates" + :show-time="{ + hideDisabledOptions: true, + defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')], + }" + format="YYYY-MM-DD HH:mm:ss" + /> + </a-form-item> + </a-col> + </template> + <a-col :md="!advanced && 8 || 24" :sm="24"> + <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} "> + <a-button type="primary" @click="$refs.table.refresh(true)" >查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + <a @click="toggleAdvanced" style="margin-left: 8px"> + {{ advanced ? '收起' : '展开' }} + <a-icon :type="advanced ? 'up' : 'down'"/> + </a> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + :columns="columns" + :data="loadData" + :rowKey="(record) => record.id" + :alert="false" + ref="table" + > + <template slot="operator"> + <a-popconfirm @confirm="() => sysVisLogDelete()" placement="top" title="确认清空日志?" v-if="hasPerm('sysVisLog:delete')"> + <a-button type="danger" ghost>清空日志</a-button> + </a-popconfirm> + <x-down + v-if="hasPerm('sysVisLog:export')" + ref="batchExport" + @batchExport="batchExport" + /> + </template> + <span slot="name" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="visTime" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="visType" slot-scope="text"> + {{ 'vis_type' | dictType(text) }} + </span> + <span slot="success" slot-scope="text"> + {{ 'yes_or_no' | dictType(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <span slot="action" > + <a @click="$refs.detailsVislog.details(record)">查看详情</a> + </span> + </span> + </s-table> + <details-vislog ref="detailsVislog"/> + </a-card> + </div> +</template> +<script> + import { STable, Ellipsis, XCard, XDown } from '@/components' + import { sysVisLogPage, sysVisLogDelete, sysVisLogExport } from '@/api/modular/system/logManage' + import detailsVislog from './details' + import moment from 'moment' + export default { + components: { + XDown, + XCard, + STable, + Ellipsis, + detailsVislog + }, + data () { + return { + advanced: false, + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '日志名称', + dataIndex: 'name', + scopedSlots: { customRender: 'name' } + }, + { + title: '访问类型', + dataIndex: 'visType', + scopedSlots: { customRender: 'visType' } + }, + { + title: '是否成功', + dataIndex: 'success', + scopedSlots: { customRender: 'success' } + }, + { + title: 'ip', + dataIndex: 'ip' + }, + { + title: '浏览器', + dataIndex: 'browser' + }, + { + title: '访问时间', + dataIndex: 'visTime', + scopedSlots: { customRender: 'visTime' } + }, + { + title: '访问人', + dataIndex: 'account' + }, + { + title: '详情', + dataIndex: 'action', + width: '150px', + scopedSlots: { customRender: 'action' } + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysVisLogPage(Object.assign(parameter, this.switchingDate())).then((res) => { + return res.data + }) + }, + defaultExpandedKeys: [], + visTypeDict: [], + successDict: [] + } + }, + /** + * 相当于html的onload方法,进来初始化 + */ + created () { + this.sysDictTypeDropDown() + }, + methods: { + moment, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.visTypeDict = this.$options.filters['dictData']('vis_type') + this.successDict = this.$options.filters['dictData']('yes_or_no') + }, + /** + * 查询参数组装 + */ + switchingDate () { + const dates = this.queryParam.dates + if (dates != null) { + this.queryParam.searchBeginTime = moment(dates[0]).format('YYYY-MM-DD HH:mm:ss') + this.queryParam.searchEndTime = moment(dates[1]).format('YYYY-MM-DD HH:mm:ss') + if (dates.length < 1) { + delete this.queryParam.searchBeginTime + delete this.queryParam.searchEndTime + } + } + const obj = JSON.parse(JSON.stringify(this.queryParam)) + delete obj.dates + return obj + }, + /** + * 批量导出 + */ + batchExport () { + sysVisLogExport().then((res) => { + this.$refs.batchExport.downloadfile(res) + }) + }, + /** + * 清空日志 + */ + sysVisLogDelete () { + sysVisLogDelete().then((res) => { + if (res.success) { + this.$message.success('清空成功') + this.$refs.table.refresh(true) + } else { + this.$message.error('清空失败:' + res.message) + } + }) + }, + toggleAdvanced () { + this.advanced = !this.advanced + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/machine/index.vue b/_web/src/views/system/machine/index.vue new file mode 100644 index 0000000..0f80386 --- /dev/null +++ b/_web/src/views/system/machine/index.vue @@ -0,0 +1,118 @@ +<template> + <div v-if="hasPerm('sysMachine:query')"> + <!-- 系统信息 Java信息--> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-card :loading="loading" title="系统信息" style="margin-bottom: 20px" :bordered="false"> + <table class="sysInfo_table" > + <tr > + <td class="sysInfo_td">系统名称:</td> + <td class="sysInfo_td">{{ this.sysOsInfo.osName }}</td> + </tr> + <tr > + <td class="sysInfo_td">系统架构:</td> + <td class="sysInfo_td">{{ this.sysOsInfo.osArch }}</td> + </tr> + <tr > + <td class="sysInfo_td">系统版本:</td> + <td class="sysInfo_td">{{ this.sysOsInfo.osVersion }}</td> + </tr> + <tr > + <td class="sysInfo_td">主机名称:</td> + <td class="sysInfo_td">{{ this.sysOsInfo.osHostName }}</td> + </tr> + <tr > + <td >主机IP地址:</td> + <td >{{ this.sysOsInfo.osHostAddress }}</td> + </tr> + </table> + </a-card> + </a-col> + <a-col :md="12" :sm="24"> + <a-card :loading="loading" title="Java信息" style="margin-bottom: 20px"> + <table class="sysInfo_table" > + <tr > + <td class="sysInfo_td">虚拟机名称:</td> + <td class="sysInfo_td">{{ this.sysJavaInfo.jvmName }}</td> + </tr> + <tr > + <td class="sysInfo_td">虚拟机版本:</td> + <td class="sysInfo_td">{{ this.sysJavaInfo.jvmVersion }}</td> + </tr> + <tr > + <td class="sysInfo_td">虚拟机供应商:</td> + <td class="sysInfo_td">{{ this.sysJavaInfo.jvmVendor }}</td> + </tr> + <tr > + <td class="sysInfo_td">java名称:</td> + <td class="sysInfo_td">{{ this.sysJavaInfo.javaName }}</td> + </tr> + <tr > + <td >java版本:</td> + <td >{{ this.sysJavaInfo.javaVersion }}</td> + </tr> + </table> + </a-card> + </a-col> + </a-row> + <a-card :loading="loading" title="JVM内存信息" > + <table class="sysInfo_table" > + <tr > + <td class="sysInfo_td">最大内存:</td> + <td class="sysInfo_td">{{ this.sysJvmMemInfo.jvmMaxMemory }}</td> + <td class="sysInfo_td">可用内存:</td> + <td class="sysInfo_td">{{ this.sysJvmMemInfo.jvmUsableMemory }}</td> + </tr> + <tr > + <td class="sysInfo_td">总内存:</td> + <td class="sysInfo_td">{{ this.sysJvmMemInfo.jvmTotalMemory }}</td> + <td class="sysInfo_td">已使用内存:</td> + <td class="sysInfo_td">{{ this.sysJvmMemInfo.jvmUsedMemory }}</td> + </tr> + <tr class="sysInfo_tr"> + <td >空余内存:</td> + <td >{{ this.sysJvmMemInfo.jvmFreeMemory }}</td> + <td >使用率:</td> + <td >{{ this.sysJvmMemInfo.jvmMemoryUsedRate }}</td> + </tr> + </table> + </a-card> + </div> +</template> +<script> + import { sysMachineQuery } from '@/api/modular/system/machineManage' + export default { + data () { + return { + loading: true, + sysOsInfo: [], + sysJavaInfo: [], + sysJvmMemInfo: [] + } + }, + // 进页面加载 + created () { + this.loadDataList() + }, + methods: { + // 加载数据方法 + loadDataList () { + sysMachineQuery().then((res) => { + this.loading = false + this.sysOsInfo = res.data.sysOsInfo + this.sysJavaInfo = res.data.sysJavaInfo + this.sysJvmMemInfo = res.data.sysJvmMemInfo + }) + } + } + + } +</script> +<style lang="less"> + .sysInfo_table{ + width: 100%; min-height: 45px; line-height: 45px; text-align: center; + } + .sysInfo_td { + border-bottom:1px solid #e8e8e8; + } +</style> diff --git a/_web/src/views/system/menu/addForm.vue b/_web/src/views/system/menu/addForm.vue new file mode 100644 index 0000000..30cad95 --- /dev/null +++ b/_web/src/views/system/menu/addForm.vue @@ -0,0 +1,567 @@ +<template> + <a-modal + title="新增菜单" + :width="1000" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + :destroyOnClose="true" + > + <a-spin :spinning="formLoading"> + <a-form :form="form" > + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="菜单名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + hasFeedback + > + <a-input placeholder="请输入菜单名称" v-decorator="['name',{rules: [{required: true, min: 1, message: '请输入菜单名称!'}]}]" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + style="width: 100%" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="菜单编号" + hasFeedback + > + <a-input placeholder="请输入菜单编号" v-decorator="['code', {rules: [{required: true, min: 1, message: '请输入菜单编号!'}]}]" /> + </a-form-item> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="所属应用" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择应用分类" v-decorator="['application', {rules: [{ required: true, message: '请选择应用分类!' }]}]" > + <a-select-option v-for="(item,index) in appData" :key="index" :value="item.code" @click="changeApplication(item.code)">{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="菜单层级" + > + <a-radio-group v-decorator="['type',{rules: [{ required: true, message: '请选择菜单层级!' }]}]" > + <a-radio v-for="(item,index) in typeData" :key="index" :value="item.code" @click="meneTypeFunc(item.code)">{{ item.value }}</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <div v-show="pidShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="父级菜单" + has-feedback + > + <a-tree-select + v-decorator="['pid', {rules: [{ required: true, message: '请选择父级菜单!' }]}]" + style="width: 100%" + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + :treeData="menuTreeData" + placeholder="请选择父级菜单" + treeDefaultExpandAll + > + <span slot="title" slot-scope="{ id }">{{ id }} + </span> + </a-tree-select> + </a-form-item> + </div> + <div v-show="redirectShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <span slot="label"> + <a-tooltip title="如需打开首页加载此目录下菜单,请填写加载菜单路由,设为首页后其他设置的主页将被替代"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 重定向 + </span> + <a-input prop="redirect" placeholder="请输入重定向地址" v-decorator="['redirect']" /> + </a-form-item> + </div> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <span slot="label"> + <a-tooltip title="按钮:无,菜单:内链、外链、组件"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 打开方式 + </span> + <a-radio-group :disabled="openTypeDisabled" v-decorator="['openType',{rules: [{ required: true, message: '请选择打开方式!' }]}]"> + <a-radio v-for="(item,index) in openTypeData" :key="index" :value="item.code" @click="meneOpenTypeFunc(item.code)">{{ item.value }}</a-radio> + </a-radio-group> + </a-form-item> + + </a-col> + </a-row> + + <a-divider /> + + <a-row :gutter="24" > + <a-col :md="12" :sm="24"> + <div v-show="componentShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + hasFeedback + > + <span slot="label"> + <a-tooltip title="前端vue组件 views文件夹下路径,例:system/menu/index。注:目录级填写:RouteView(不带面包屑),PageView(带面包屑),菜单级内链打开http链接填写:Iframe"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 前端组件 + </span> + <a-input placeholder="请输入前端组件" :disabled="componentDisabled" prop="component" v-decorator="['component',{rules: [{required: componentRequired, message: '请输入前端组件'}]}]"/><!-- ,{rules: [{required: componentRequired, min: 1, message: '请输入前端组件!'}]} --> + </a-form-item> + </div> + </a-col> + <a-col :md="12" :sm="24"> + <div v-show="routerShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + hasFeedback + > + <span slot="label"> + <a-tooltip title="浏览器显示的URL,例:/menu,对应打开页面为菜单页面"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 路由地址 + </span> + <a-input placeholder="请输入路由" v-decorator="['router', {rules: [{required: routerRequired, message: '请输入路由!'}]}]" /> + </a-form-item> + </div> + <div v-show="permissionShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="权限标识" + hasFeedback + > + <a-input placeholder="请输入权限标识" v-decorator="['permission', {rules: [{required: permissionRequired, message: '请输入权限标识!'}]}]" /> + </a-form-item> + </div> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <div v-show="linkShow" > + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + hasFeedback + > + <span slot="label"> + <a-tooltip title="当选择了需要内链或外链打开的选项,此处输入要打开的链接地址,例:https://www.xiaonuo.vip"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 内外链地址 + </span> + <a-input placeholder="请输入内链打开地址" :disabled="linkDisabled" v-decorator="['link', {rules: [{required: linkRequired, message: '请输入权限标识!'}]}]" /> + </a-form-item> + </div> + </a-col> + <a-col :md="12" :sm="24"> + <div v-show="iconShow" > + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="图标" + > + <a-input placeholder="请选择图标" disabled="disabled" v-decorator="['icon']" > + <a-icon slot="addonAfter" @click="openIconSele()" type="setting" /> + </a-input> + </a-form-item> + </div> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <span slot="label"> + <a-tooltip title="系统权重:菜单可分配给任何角色,业务权重:菜单对超级管理员不可见"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 权重 + </span> + <a-radio-group v-decorator="['weight']"> + <a-radio v-for="(item,index) in weightData" :key="index" :value="item.code" >{{ item.value }}</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="是否可见" + > + <a-switch id="visible" checkedChildren="是" unCheckedChildren="否" v-decorator="['visible', { valuePropName: 'checked' }]"/><!-- defaultChecked --> + </a-form-item> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + > + <a-input-number style="width: 100%" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="备注" + hasFeedback + > + <a-input placeholder="请输入备注" v-decorator="['remark']"></a-input> + </a-form-item> + </a-col> + </a-row> + + </a-form> + </a-spin> + <a-modal + :width="850" + :visible="visibleIcon" + @cancel="handleCancelIcon" + footer="" + :mask="false" + :closable="false" + :destroyOnClose="true" + > + <icon-selector v-model="currentSelectedIcon" @change="handleIconChange"/> + </a-modal> + </a-modal> +</template> + +<script> + import { getAppList } from '@/api/modular/system/appManage' + import { getMenuTree, sysMenuAdd } from '@/api/modular/system/menuManage' + import IconSelector from '@/components/IconSelector' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + export default { + components: { IconSelector }, + + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 6 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 16 } + }, + visibleIcon: false, + visible: false, + confirmLoading: false, + appData: [], + menuTreeData: [], + redirectShow: true, + componentShow: true, + componentDisabled: false, + componentRequired: true, + routerRequired: true, + routerShow: true, + iconShow: true, + openTypeShow: true, + pidShow: true, + permissionShow: true, + permissionRequired: true, + // 图标组件 + currentSelectedIcon: 'pause-circle', + typeData: [], + openTypeData: [], + weightData: [], + formLoading: true, + linkShow: true, + openTypeDisabled: false, + openTypeDefault: [], + openType: '', + linkRequired: true, + linkDisabled: false, + type: '', + form: this.$form.createForm(this) + } + }, + + methods: { + // 打开页面初始化 + add (type) { + this.visible = true + // 图标 + this.currentSelectedIcon = type + + // 默认选中菜单项,并初始化 + this.form.getFieldDecorator('type', { valuePropName: 'checked', initialValue: '1' }) + this.meneTypeFunc('1') + + // 默认选中的单选框 + // this.form.getFieldDecorator('type',{valuePropName:'checked',initialValue:'1'}) + this.form.getFieldDecorator('weight', { valuePropName: 'checked', initialValue: '2' }) + this.form.getFieldDecorator('visible', { initialValue: true }) + + // 获取系统应用列表 + this.getSysApplist() + this.sysDictTypeDropDown() + }, + + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.formLoading = true + // 菜单类型 + sysDictTypeDropDown({ code: 'menu_type' }).then((res) => { + this.typeData = res.data + }) + // 权重 + sysDictTypeDropDown({ code: 'menu_weight' }).then((res) => { + this.weightData = res.data + }) + // 内外链 + sysDictTypeDropDown({ code: 'open_type' }).then((res) => { + this.openTypeData = res.data + this.formLoading = false + }) + }, + + getSysApplist () { + return getAppList().then((res) => { + if (res.success) { + this.appData = res.data + } else { + this.$message.warning(res.message) + } + }) + }, + changeApplication (value) { + getMenuTree({ 'application': value }).then((res) => { + if (res.success) { + this.form.resetFields(`pid`, []) + this.menuTreeData = [{ + 'id': '-1', + 'parentId': '0', + 'title': '顶级', + 'value': '0', + 'pid': '0', + 'children': res.data + }] + } else { + this.$message.warning(res.message) + } + }) + }, + + /** + * 选择菜单类型执行初始化表单变量 + */ + meneTypeFunc (type) { + this.type = type + // eslint-disable-next-line eqeqeq + if (type == '0' || type == '1') { + // 内外链地址显示,给空值 + this.linkShow = true + this.form.resetFields(`link`, []) + // 图标选择显示 + this.iconShow = true + // 路由必填,设置空值,并显示 + this.routerRequired = true + this.form.getFieldDecorator('router', { initialValue: '' }) + this.routerShow = true + // 权限标识框隐藏,选填,给空值 + this.permissionShow = false + this.permissionRequired = false + this.form.getFieldDecorator('permission', { initialValue: '' }) + // 打开方式设置为组件 ,禁用选择方式 + this.openType = '1' + this.form.getFieldDecorator('openType', { initialValue: this.openType = '1' }) + this.openTypeDisabled = false + } + // eslint-disable-next-line eqeqeq + if (type == '0') { + // 重定向展示,并给空 + this.redirectShow = true + this.form.resetFields(`redirect`, []) + // 组件默认为显示,设置可输入,给默认组件 PageView,验证必填 + this.componentShow = true + this.componentDisabled = false + this.form.getFieldDecorator('component', { initialValue: 'PageView' }) + this.componentRequired = true + // 父级初始化顶级,并将其隐藏 + this.form.getFieldDecorator('pid', { initialValue: '0' }) + this.pidShow = false + } else { + // eslint-disable-next-line eqeqeq + if (type == '1') { + // 组件可以手输,取消值 + this.componentDisabled = false + this.form.getFieldDecorator('component', { initialValue: '' }) + } + // 重定向输入隐藏,并给空值 + this.redirectShow = false + this.form.getFieldDecorator('redirect', { initialValue: '' }) + // 父级选择放开 + this.pidShow = true + } + // eslint-disable-next-line eqeqeq + if (type == '2') { + // 组件设置不填,不可输入,并给空(手输的跟设置的) + this.componentRequired = false + this.componentDisabled = true + this.form.resetFields(`component`, []) + this.form.getFieldDecorator('component', { initialValue: '' }) + // 路由选填,设置空值,并隐藏 + this.routerRequired = true + this.form.getFieldDecorator('router', { initialValue: '' }) + this.routerShow = false + // 内外链地址隐藏,给空值 + this.linkShow = false + this.form.getFieldDecorator('link', { initialValue: '' }) + // 权限标识框显示,必填,给空值 + this.permissionShow = true + this.permissionRequired = true + this.form.getFieldDecorator('permission', { initialValue: '' }) + // 图标选择隐藏,并给空 + this.iconShow = false + this.form.getFieldDecorator('icon', { initialValue: '' }) + // 打开方式设置为无 ,禁用选择方式 + this.openType = '0' + this.form.getFieldDecorator('openType', { initialValue: this.openType }) + this.openTypeDisabled = true + // 取消icon + this.form.getFieldDecorator('icon', { initialValue: '' }) + } + this.meneOpenTypeFunc(this.openType) + }, + + /** + * 选择打开方式执行方法 + */ + meneOpenTypeFunc (openType) { + this.form.resetFields(`openType`, openType) + // eslint-disable-next-line eqeqeq + if (openType == '2' || openType == '3') { + // 点击内外链的时候保留原值,其他清空 + if (this.linkDisabled === false) { + this.form.resetFields(`link`, []) + } + // 设置内外链可手输,加验证 + this.linkDisabled = false + this.linkRequired = true + } else { + // 设置内外链不可手输,取消值,取消验证 + this.linkDisabled = true + this.form.resetFields(`link`, []) + this.linkRequired = false + } + // 另起一个分支 + // eslint-disable-next-line eqeqeq + if (openType == '3') { + this.componentRequired = false + this.componentDisabled = true + this.form.resetFields(`component`, []) + this.form.getFieldDecorator('component', { initialValue: '' }) + } else { + this.componentRequired = true + // eslint-disable-next-line eqeqeq + if (this.type == '1' || this.type == '2') { + this.form.getFieldDecorator('component', { initialValue: '' }) + } else { + this.form.resetFields(`component`, []) + this.form.getFieldDecorator('component', { initialValue: 'PageView' }) + } + // eslint-disable-next-line eqeqeq + if (openType == '2') { + // 组件设置为 iframe + this.form.resetFields(`component`, []) + this.form.getFieldDecorator('component', { initialValue: 'Iframe' }) + } + } + // eslint-disable-next-line eqeqeq + if (this.type == '2') { + // eslint-disable-next-line eqeqeq + if (openType == '0') { + this.componentRequired = false + this.routerRequired = false + } + } + }, + + openIconSele () { + this.visibleIcon = true + }, + handleIconChange (icon) { + this.form.getFieldDecorator('icon', { initialValue: icon }) + this.visibleIcon = false + }, + handleCancelIcon () { + this.visibleIcon = false + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + if (values.visible) { + values.visible = 'Y' + } else { + values.visible = 'N' + } + sysMenuAdd(values).then((res) => { + this.confirmLoading = false + if (res.success) { + this.$message.success('新增成功') + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.confirmLoading = false + this.visible = false + } + } + + } +</script> diff --git a/_web/src/views/system/menu/editForm.vue b/_web/src/views/system/menu/editForm.vue new file mode 100644 index 0000000..714b24e --- /dev/null +++ b/_web/src/views/system/menu/editForm.vue @@ -0,0 +1,627 @@ +<template> + + <a-modal + title="编辑菜单" + :width="1000" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + :destroyOnClose="true" + > + <a-spin :spinning="formLoading"> + <a-form :form="form" > + + <a-form-item v-show="false" > + <a-input v-decorator="['id']" /> + </a-form-item> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + label="菜单名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + hasFeedback + > + <a-input placeholder="请输入菜单名称" v-decorator="['name',{rules: [{required: true, min: 1, message: '请输入菜单名称!'}]}]" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + style="width: 100%" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="菜单编号" + hasFeedback + > + <a-input placeholder="请输入菜单编号" v-decorator="['code', {rules: [{required: true, min: 1, message: '请输入菜单编号!'}]}]" /> + </a-form-item> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="所属应用" + has-feedback + > + <a-select style="width: 100%" :disabled="appDisabled" placeholder="请选择应用分类" v-decorator="['application', {rules: [{ required: true, message: '请选择应用分类!' }]}]" > + <a-select-option v-for="(item,index) in appData" :key="index" :value="item.code" @click="changeApplication(item.code)">{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="菜单层级" + > + <a-radio-group v-decorator="['type',{rules: [{ required: true, message: '请选择菜单层级!' }]}]" > + <a-radio v-for="(item,index) in typeData" :key="index" :value="item.code" @click="meneTypeFunc(item.code)">{{ item.value }}</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <div v-show="pidShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="父级菜单" + has-feedback + > + <a-tree-select + v-decorator="['pid', {rules: [{ required: true, message: '请选择父级菜单!' }]}]" + style="width: 100%" + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + :treeData="menuTreeData" + placeholder="请选择父级菜单" + treeDefaultExpandAll + @change="setPid" + > + <span slot="title" slot-scope="{ id }">{{ id }} + </span> + </a-tree-select> + </a-form-item> + </div> + <div v-show="redirectShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <span slot="label"> + <a-tooltip title="如需打开首页加载此目录下菜单,请填写加载菜单路由,设为首页后其他设置的主页将被替代"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 重定向 + </span> + <a-input prop="redirect" placeholder="请输入重定向地址" v-decorator="['redirect']" /> + </a-form-item> + </div> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <span slot="label"> + <a-tooltip title="按钮:无,菜单:内链、外链、组件"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 打开方式 + </span> + <a-radio-group :disabled="openTypeDisabled" v-decorator="['openType',{rules: [{ required: true, message: '请选择打开方式!' }]}]"> + <a-radio v-for="(item,index) in openTypeData" :key="index" :value="item.code" @click="meneOpenTypeFunc(item.code)">{{ item.value }}</a-radio> + </a-radio-group> + </a-form-item> + + </a-col> + </a-row> + + <a-divider /> + + <a-row :gutter="24" > + <a-col :md="12" :sm="24"> + <div v-show="componentShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + hasFeedback + > + <span slot="label"> + <a-tooltip title="前端vue组件 views文件夹下路径,例:system/menu/index。注:目录级填写:RouteView(不带面包屑),PageView(带面包屑),菜单级内链打开http链接填写:Iframe"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 前端组件 + </span> + <a-input placeholder="请输入前端组件" :disabled="componentDisabled" prop="component" v-decorator="['component',{rules: [{required: componentRequired, message: '请输入前端组件'}]}]"/> + </a-form-item> + </div> + </a-col> + <a-col :md="12" :sm="24"> + <div v-show="routerShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + hasFeedback + > + <span slot="label"> + <a-tooltip title="浏览器显示的URL,例:/menu,对应打开页面为菜单页面"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 路由地址 + </span> + <a-input placeholder="请输入路由" v-decorator="['router', {rules: [{required: routerRequired, message: '请输入路由!'}]}]" /> + </a-form-item> + </div> + <div v-show="permissionShow"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="权限标识" + hasFeedback + > + <a-input placeholder="请输入权限标识" v-decorator="['permission', {rules: [{required: permissionRequired, message: '请输入权限标识!'}]}]" /> + </a-form-item> + </div> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <div v-show="linkShow" > + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + hasFeedback + > + <span slot="label"> + <a-tooltip title="当选择了需要内链或外链打开的选项,此处输入要打开的链接地址,例:https://www.xiaonuo.vip"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 内外链地址 + </span> + <a-input placeholder="请输入内链打开地址" :disabled="linkDisabled" v-decorator="['link', {rules: [{required: linkRequired, message: '请输入权限标识!'}]}]" /> + </a-form-item> + </div> + </a-col> + <a-col :md="12" :sm="24"> + <div v-show="iconShow" > + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="图标" + > + <a-input placeholder="请选择图标" disabled="disabled" v-decorator="['icon']" > + <a-icon slot="addonAfter" @click="openIconSele()" type="setting" /> + </a-input> + </a-form-item> + </div> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <span slot="label"> + <a-tooltip title="系统权重:菜单可分配给任何角色,业务权重:菜单对超级管理员不可见"> + <a-icon type="question-circle-o" /> + </a-tooltip> + 权重 + </span> + <a-radio-group v-decorator="['weight']"> + <a-radio v-for="(item,index) in weightData" :key="index" :value="item.code" >{{ item.value }}</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="是否可见" + > + <a-switch id="visible" checkedChildren="是" unCheckedChildren="否" v-decorator="['visible', { valuePropName: 'checked' }]"/> + </a-form-item> + </a-col> + </a-row> + + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + > + <a-input-number style="width: 100%" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + </a-col> + <a-col :md="12" :sm="24"> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="备注" + hasFeedback + > + <a-input placeholder="请输入备注" v-decorator="['remark']"></a-input> + </a-form-item> + </a-col> + </a-row> + + </a-form> + </a-spin> + <a-modal + :width="850" + :visible="visibleIcon" + @cancel="handleCancelIcon" + footer="" + :mask="false" + :closable="false" + :destroyOnClose="true" + > + <icon-selector v-model="currentSelectedIcon" @change="handleIconChange"/> + </a-modal> + </a-modal> +</template> + +<script> + import { getAppList } from '@/api/modular/system/appManage' + import { getMenuTree, sysMenuEdit } from '@/api/modular/system/menuManage' + import IconSelector from '@/components/IconSelector' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + export default { + name: 'MenuEdit', + components: { IconSelector }, + + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 6 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 16 } + }, + visibleIcon: false, + visible: false, + confirmLoading: false, + appData: [], + menuTreeData: [], + redirectShow: true, + componentShow: true, + componentDisabled: false, + componentRequired: true, + routerRequired: true, + routerShow: true, + iconShow: true, + openTypeShow: true, + pidShow: true, + permissionShow: true, + permissionRequired: true, + // 图标组件 + currentSelectedIcon: 'pause-circle', + typeData: [], + openTypeData: [], + weightData: [], + formLoading: true, + linkShow: true, + openTypeDisabled: false, + openTypeDefault: [], + openType: '', + linkRequired: true, + linkDisabled: false, + type: '', + pid: '', + appDisabled: false, + form: this.$form.createForm(this) + } + }, + + watch: { + pid (val) { + if (val === '0') { + // 再不能切换应用 + this.appDisabled = false + } else { + this.appDisabled = true + } + } + }, + + methods: { + // 打开页面初始化 + edit (record) { + this.visible = true + // 获取系统应用列表 + this.getSysApplist() + this.sysDictTypeDropDown() + + // 图标 + this.currentSelectedIcon = record.icon + // 默认选中菜单项,并初始化 + this.form.getFieldDecorator('type', { valuePropName: 'checked', initialValue: record.type.toString() }) + this.meneTypeFunc(record.type.toString(), record.openType.toString()) + + // 默认选中的单选框 + // eslint-disable-next-line no-unused-vars + const visibleDef = false + // eslint-disable-next-line eqeqeq + if (record.visible == 'Y') { + this.visibleDef = true + } + this.form.getFieldDecorator('weight', { valuePropName: 'checked', initialValue: record.weight.toString() }) + this.form.getFieldDecorator('visible', { valuePropName: 'checked', initialValue: this.visibleDef }) + this.form.getFieldDecorator('icon', { initialValue: record.icon }) + setTimeout(() => { + this.setMenuItem(record) + this.changeApplication(record.application) + }, 100) + }, + + setMenuItem (record) { + this.form.setFieldsValue( + { + id: record.id, + name: record.name, + code: record.code, + application: record.application, + redirect: record.redirect, + component: record.component, + permission: record.permission, + link: record.link, + router: record.router, + sort: record.sort, + remark: record.remark + } + ) + this.form.getFieldDecorator('pid', { initialValue: record.pid }) + this.pid = record.pid + }, + + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.formLoading = true + // 菜单类型 + sysDictTypeDropDown({ code: 'menu_type' }).then((res) => { + this.typeData = res.data + }) + // 权重 + sysDictTypeDropDown({ code: 'menu_weight' }).then((res) => { + this.weightData = res.data + }) + // 内外链 + sysDictTypeDropDown({ code: 'open_type' }).then((res) => { + this.openTypeData = res.data + this.formLoading = false + }) + }, + + /** + * 选择父级 + */ + setPid (value) { + this.pid = value + }, + + getSysApplist () { + return getAppList().then((res) => { + if (res.success) { + this.appData = res.data + } else { + this.$message.warning(res.message) + } + }) + }, + changeApplication (value) { + getMenuTree({ 'application': value }).then((res) => { + if (res.success) { + this.form.resetFields(`pid`, []) + this.menuTreeData = [{ + 'id': '-1', + 'parentId': '0', + 'title': '顶级', + 'value': '0', + 'pid': '0', + 'children': res.data + }] + } else { + this.$message.warning(res.message) + } + }) + }, + + /** + * 选择菜单类型执行初始化表单变量 + */ + meneTypeFunc (type, openType) { + this.type = type + // eslint-disable-next-line eqeqeq + if (type == '0' || type == '1') { + // 内外链地址显示,给空值 + this.linkShow = true + this.form.resetFields(`link`, []) + // 图标选择显示 + this.iconShow = true + // 路由必填,设置空值,并显示 + this.routerRequired = true + this.form.getFieldDecorator('router', { initialValue: '' }) + this.routerShow = true + // 权限标识框隐藏,选填,给空值 + this.permissionShow = false + this.permissionRequired = false + this.form.getFieldDecorator('permission', { initialValue: '' }) + // 打开方式设置为组件 ,禁用选择方式 + this.openType = openType + this.form.getFieldDecorator('openType', { initialValue: this.openType }) + this.openTypeDisabled = false + } + // eslint-disable-next-line eqeqeq + if (type == '0') { + // 重定向展示,并给空 + this.redirectShow = true + this.form.resetFields(`redirect`, []) + // 组件默认为显示,设置可输入,给默认组件 PageView,验证必填 + this.componentShow = true + this.componentDisabled = false + this.form.getFieldDecorator('component', { initialValue: 'PageView' }) + this.componentRequired = true + // 父级初始化顶级,并将其隐藏 + this.form.getFieldDecorator('pid', { initialValue: '0' }) + this.pid = '0' + this.pidShow = false + } else { + // eslint-disable-next-line eqeqeq + if (type == '1') { + // 组件可以手输,取消值 + this.componentDisabled = false + this.form.getFieldDecorator('component', { initialValue: '' }) + } + // 重定向输入隐藏,并给空值 + this.redirectShow = false + this.form.getFieldDecorator('redirect', { initialValue: '' }) + // 父级选择放开 + this.pidShow = true + } + // eslint-disable-next-line eqeqeq + if (type == '2') { + // 组件设置不填,不可输入,并给空(手输的跟设置的) + this.componentRequired = false + this.componentDisabled = true + this.form.resetFields(`component`, []) + this.form.getFieldDecorator('component', { initialValue: '' }) + // 路由选填,设置空值,并隐藏 + this.routerRequired = true + this.form.getFieldDecorator('router', { initialValue: '' }) + this.routerShow = false + // 内外链地址隐藏,给空值 + this.linkShow = false + this.form.getFieldDecorator('link', { initialValue: '' }) + // 权限标识框显示,必填,给空值 + this.permissionShow = true + this.permissionRequired = true + this.form.getFieldDecorator('permission', { initialValue: '' }) + // 图标选择隐藏,并给空 + this.iconShow = false + this.form.getFieldDecorator('icon', { initialValue: '' }) + // 打开方式设置为无 ,禁用选择方式 + this.openType = '0' + this.form.getFieldDecorator('openType', { initialValue: this.openType }) + this.openTypeDisabled = true + // 取消icon + this.form.getFieldDecorator('icon', { initialValue: '' }) + } + this.meneOpenTypeFunc(this.openType) + }, + + /** + * 选择打开方式执行方法 + */ + meneOpenTypeFunc (openType) { + this.form.resetFields(`openType`, openType) + // eslint-disable-next-line eqeqeq + if (openType == '2' || openType == '3') { + // 点击内外链的时候保留原值,其他清空 + if (this.linkDisabled === false) { + this.form.resetFields(`link`, []) + } + // 设置内外链可手输,加验证 + this.linkDisabled = false + this.linkRequired = true + } else { + // 设置内外链不可手输,取消值,取消验证 + this.linkDisabled = true + this.form.resetFields(`link`, []) + this.linkRequired = false + } + // 另起一个分支 + // eslint-disable-next-line eqeqeq + if (openType == '3') { + this.componentRequired = false + this.componentDisabled = true + this.form.resetFields(`component`, []) + this.form.getFieldDecorator('component', { initialValue: '' }) + } else { + this.componentRequired = true + // eslint-disable-next-line eqeqeq + if (this.type == '1' || this.type == '2') { + this.form.getFieldDecorator('component', { initialValue: '' }) + } else { + this.form.resetFields(`component`, []) + this.form.getFieldDecorator('component', { initialValue: 'PageView' }) + } + // eslint-disable-next-line eqeqeq + if (openType == '2') { + // 组件设置为 iframe + this.form.resetFields(`component`, []) + this.form.getFieldDecorator('component', { initialValue: 'Iframe' }) + } + } + // eslint-disable-next-line eqeqeq + if (this.type == '2') { + // eslint-disable-next-line eqeqeq + if (openType == '0') { + this.componentRequired = false + this.routerRequired = false + } + } + }, + + openIconSele () { + this.visibleIcon = true + }, + handleIconChange (icon) { + // console.log('新图标:'+icon) + this.form.getFieldDecorator('icon', { initialValue: icon }) + // this.form.resetFields(`icon`,icon); + + this.visibleIcon = false + }, + handleCancelIcon () { + this.visibleIcon = false + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + if (values.visible) { + values.visible = 'Y' + } else { + values.visible = 'N' + } + sysMenuEdit(values).then((res) => { + this.confirmLoading = false + if (res.success) { + this.$message.success('编辑成功') + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.confirmLoading = false + this.visible = false + } + } + + } +</script> diff --git a/_web/src/views/system/menu/index.vue b/_web/src/views/system/menu/index.vue new file mode 100644 index 0000000..5a6b70d --- /dev/null +++ b/_web/src/views/system/menu/index.vue @@ -0,0 +1,187 @@ +/* eslint-disable */ +<template> + <div> + <x-card v-if="hasPerm('sysMenu:list')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="菜单名称"> + <a-input v-model="queryParam.name" allow-clear placeholder="请输入菜单名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="选择应用"> + <a-select v-model="queryParam.application" placeholder="请选择选择应用" allow-clear> + <a-select-option v-for="(item,index) in this.userInfo.apps" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <span class="table-page-search-submitButtons"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :rowKey="(record) => record.id" + :columns="columns" + :alert="false" + :data="loadData" + :showPagination="false" + :expandRowByClick="true" + > + <template slot="operator"> + <a-button type="primary" v-if="hasPerm('sysMenu:add')" icon="plus" @click="$refs.addForm.add()">新增菜单</a-button> + </template> + <span slot="type" slot-scope="text"> + <a-tag color="cyan" v-if="text === 0"> + {{ 'menu_type' | dictType(text) }} + </a-tag> + <a-tag color="blue" v-if="text === 1"> + {{ 'menu_type' | dictType(text) }} + </a-tag> + <a-tag color="purple" v-if="text === 2"> + {{ 'menu_type' | dictType(text) }} + </a-tag> + </span> + <span slot="icon" slot-scope="text"> + <div v-if="text != null && text != ''"> + <a-icon :type="text"/> + </div> + </span> + <span slot="action" slot-scope="text, record"> + <template> + <a v-if="hasPerm('sysMenu:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysMenu:edit') & hasPerm('sysMenu:delete')"/> + <a-popconfirm v-if="hasPerm('sysMenu:delete')" placement="topRight" title="删除本菜单与下级?" @confirm="() => handleDel(record)"> + <a>删除</a> + </a-popconfirm> + </template> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk"/> + <edit-form ref="editForm" @ok="handleOk"/> + </a-card> + </div> +</template> + +<script> +import { STable, XCard } from '@/components' +import { getMenuList, sysMenuDelete } from '@/api/modular/system/menuManage' +import addForm from './addForm' +import editForm from './editForm' +import { mapGetters } from 'vuex' + +export default { + components: { + STable, + XCard, + addForm, + editForm + }, + data () { + return { + data: [], + queryParam: {}, + loading: true, + columns: [ + { + title: '菜单名称', + dataIndex: 'name', + width: '20%' + }, + { + title: '菜单类型', + dataIndex: 'type', + scopedSlots: { customRender: 'type' } + }, + { + title: '图标', + dataIndex: 'icon', + scopedSlots: { customRender: 'icon' } + }, + { + title: '组件', + dataIndex: 'component', + width: '20%', + ellipsis: true + }, + { + title: '路由地址', + dataIndex: 'router', + key: 'router', + ellipsis: true + }, + { + title: '排序', + dataIndex: 'sort' + } + ], + loadData: parameter => { + return getMenuList(Object.assign(parameter, this.queryParam)).then((res) => { + this.removeEmptyChildren(res.data) + return res.data + }) + } + } + }, + computed: { + ...mapGetters(['userInfo']) + }, + created () { + if (this.hasPerm('sysMenu:edit') || this.hasPerm('sysMenu:delete')) { + this.columns.push({ + title: '操作', + dataIndex: 'action', + width: '150px', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + /** + * 去掉无用的支节点 + */ + removeEmptyChildren(data) { + if (data == null || data.length === 0) return + for (let i = 0; i < data.length; i++) { + const item = data[i] + if (item.children != null && item.children.length === 0) { + item.children = null + } else { + this.removeEmptyChildren(item.children) + } + } + }, + handleDel (record) { + sysMenuDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }) + }, + handleOk () { + this.$refs.table.refresh() + } + } +} + +</script> +<style scoped> +.table-operator { + margin-bottom: 18px; +} +button { + margin-right: 8px; +} +</style> diff --git a/_web/src/views/system/notice/addForm.vue b/_web/src/views/system/notice/addForm.vue new file mode 100644 index 0000000..e6e41ea --- /dev/null +++ b/_web/src/views/system/notice/addForm.vue @@ -0,0 +1,213 @@ +<template> + <a-modal + title="新增通知公告" + :width="1000" + :footer="null" + :visible="visible" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + <a-form-item + label="标题" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input placeholder="请输入标题" v-decorator="['title', {rules: [{required: true, message: '请输入标题!'}]}]" /> + </a-form-item> + <a-form-item + label="类型" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-radio-group v-decorator="['type',{rules: [{ required: true, message: '请选择类型!' }]}]" > + <a-radio-button v-for="(item,index) in typeDictTypeDropDown" :key="index" :value="item.code">{{ item.value }}</a-radio-button> + </a-radio-group> + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="内容" + > + <antd-editor :uploadConfig="editorUploadConfig" v-model="editorContent" @onchange="changeEditor" @oninit="getEditor" /> + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="通知到的人" + > + <a-transfer + :data-source="mockData" + show-search + :list-style="{ + width: '40%', + height: '300px', + }" + :filter-option="filterOption" + :target-keys="targetKeys" + :render="item => item.title" + @change="handleChange" + /> + </a-form-item> + <a-divider /> + <a-form-item class="subForm-item"> + <a-button type="primary" class="subButton" @click="handleSubmit('1')">发布</a-button> + <a-button type="danger" class="subButton" @click="handleSubmit('0')">存为草稿</a-button> + <a-button class="subButton" @click="handleCancel">取消</a-button> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + import { sysNoticeAdd } from '@/api/modular/system/noticeManage' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + import { sysFileInfoUpload } from '@/api/modular/system/fileManage' + import { AntdEditor } from '@/components' + import { sysUserSelector } from '@/api/modular/system/userManage' + export default { + name: 'AddForm', + components: { + AntdEditor + }, + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 3 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this), + editorContent: '', + editorContentText: '', + editorUploadConfig: { + method: 'http', + callback: this.editorUploadImage + }, + mockData: [], + targetKeys: [], + typeDictTypeDropDown: [], // 0通知 1公告 + formLoading: true + } + }, + methods: { + // 初始化方法 + add () { + this.visible = true + this.sysDictTypeDropDown()// 先注释 + this.getMock() + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + sysDictTypeDropDown({ code: 'notice_type' }).then((res) => { + this.typeDictTypeDropDown = res.data + }) + }, + /** + * 编辑器回调上传及回传图片url + */ + editorUploadImage (files, insert) { + const formData = new FormData() + files.forEach(file => { + formData.append('file', file) + }) + sysFileInfoUpload(formData).then((res) => { + if (res.success) { + insert(process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + res.data) + } else { + this.$message.error('编辑器上传图片失败:' + res.message) + } + }).catch((err) => { + this.$message.error('预览错误:' + err.message) + }) + }, + getEditor (editor) { + this.editor = editor + }, + changeEditor (html, ele) { + this.editorContent = html + this.editorContentText = ele.text() + }, + /** + * 穿梭框 + */ + getMock () { + const targetKeys = [] + const mockData = [] + sysUserSelector().then((res) => { + this.formLoading = false + for (let i = 0; i < res.data.length; i++) { + const data = { + key: res.data[i].id.toString(), + title: res.data[i].name, + description: `description of ${res.data[i].name}` + } + mockData.push(data) + } + }) + this.mockData = mockData + this.targetKeys = targetKeys + }, + filterOption (inputValue, option) { + return option.description.indexOf(inputValue) > -1 + }, + handleChange (targetKeys, direction, moveKeys) { + this.targetKeys = targetKeys + }, + handleSubmit (types) { + const { form: { validateFields } } = this + // eslint-disable-next-line eqeqeq + if (this.editorContent == '') { + this.$message.error('请填写内容') + return + } + if (this.targetKeys.length < 1) { + this.$message.error('请选择通知到的人') + return + } + validateFields((errors, values) => { + if (!errors) { + this.formLoading = true + values.content = this.editorContent + values.status = types + values.noticeUserIdList = this.targetKeys + sysNoticeAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.formLoading = false + }) + } + }) + }, + handleCancel () { + this.editor.txt.clear() + this.targetKeys = [] + this.editorContent = '' + this.form.resetFields() + this.visible = false + this.formLoading = true + } + } + } +</script> +<style> + .subButton{ + float: right; + } + .subForm-item{ + margin-bottom: 0px; + } +</style> diff --git a/_web/src/views/system/notice/detailForm.vue b/_web/src/views/system/notice/detailForm.vue new file mode 100644 index 0000000..dc837b0 --- /dev/null +++ b/_web/src/views/system/notice/detailForm.vue @@ -0,0 +1,60 @@ +<template> + <a-modal + title="通知公告详情" + :width="1000" + :confirmLoading="confirmLoading" + :visible="visible" + :footer="null" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <div style="text-align: center;font-size: 30px">{{ this.contentRecord.title }}</div> + <br> + <div style="text-align: right;font-size: 10px"> + <span>(发布人:{{ this.contentRecord.publicUserName }})</span> + <span>发布时间:{{ this.contentRecord.publicTime }} </span> + </div> + <a-divider style="margin-top: 5px"/> + <div > + <label v-html="this.contentRecord.content"></label> + </div> + </a-spin> + </a-modal> +</template> +<script> + import { sysNoticeDetail } from '@/api/modular/system/noticeManage' + + export default { + name: 'DetailForm', + components: { + }, + data () { + return { + visible: false, + confirmLoading: false, + contentRecord: {} + } + }, + methods: { + // 初始化方法 + detail (record) { + this.confirmLoading = true + this.visible = true + this.sysNoticeDetail(record.id) + }, + /** + * 查看详情 + */ + sysNoticeDetail (id) { + sysNoticeDetail({ id: id }).then((res) => { + this.confirmLoading = false + this.contentRecord = res.data + }) + }, + handleCancel () { + this.visible = false + this.contentRecord = {} + } + } + } +</script> diff --git a/_web/src/views/system/notice/editForm.vue b/_web/src/views/system/notice/editForm.vue new file mode 100644 index 0000000..604df46 --- /dev/null +++ b/_web/src/views/system/notice/editForm.vue @@ -0,0 +1,240 @@ +<template> + <a-modal + title="编辑通知公告" + :width="1000" + :footer="null" + :visible="visible" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + <a-form-item v-show="false"> + <a-input v-decorator="['id']" /> + </a-form-item> + <a-form-item + label="标题" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-input placeholder="请输入标题" v-decorator="['title', {rules: [{required: true, message: '请输入标题!'}]}]" /> + </a-form-item> + <a-form-item + label="类型" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-radio-group v-decorator="['type',{rules: [{ required: true, message: '请选择类型!' }]}]" > + <a-radio-button v-for="(item,index) in typeDictTypeDropDown" :key="index" :value="item.code">{{ item.value }}</a-radio-button> + </a-radio-group> + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="内容" + > + <antd-editor :uploadConfig="editorUploadConfig" v-model="editorContent" @onchange="changeEditor" @oninit="getEditor" /> + </a-form-item> + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="通知到的人" + > + <a-transfer + :data-source="mockData" + show-search + :list-style="{ + width: '40%', + height: '300px', + }" + :filter-option="filterOption" + :target-keys="targetKeys" + :render="item => item.title" + @change="handleChange" + /> + </a-form-item> + <a-divider /> + <a-form-item class="subForm-item"> + <a-button type="primary" class="subButton" @click="handleSubmit('1')">发布</a-button> + <a-button type="danger" class="subButton" @click="handleSubmit('0')">存为草稿</a-button> + <a-button class="subButton" @click="handleCancel">取消</a-button> + </a-form-item> + </a-form> + </a-spin> + </a-modal> +</template> +<script> + import { sysNoticeEdit, sysNoticeDetail } from '@/api/modular/system/noticeManage' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + import { sysFileInfoUpload } from '@/api/modular/system/fileManage' + import { AntdEditor } from '@/components' + import { sysUserSelector } from '@/api/modular/system/userManage' + export default { + name: 'AddForm', + components: { + AntdEditor + }, + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 3 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 } + }, + visible: false, + form: this.$form.createForm(this), + typeDictTypeDropDown: [], // 0通知 1公告 + editorContent: '', + editorContentText: '', + editorUploadConfig: { + method: 'http', + callback: this.editorUploadImage + }, + mockData: [], + targetKeys: [], + noticeDetail: [], + formLoading: true + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + this.sysNoticeDetail(record.id) + this.sysDictTypeDropDown() + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + title: record.title, + type: record.type.toString() + } + ) + this.editor.txt.html(record.content) + this.editorContent = record.content + }, 100) + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + sysDictTypeDropDown({ code: 'notice_type' }).then((res) => { + this.typeDictTypeDropDown = res.data + }) + }, + /** + * 编辑器回调上传及回传图片url + */ + editorUploadImage (files, insert) { + const formData = new FormData() + files.forEach(file => { + formData.append('file', file) + }) + sysFileInfoUpload(formData).then((res) => { + if (res.success) { + insert(process.env.VUE_APP_API_BASE_URL + '/sysFileInfo/preview?id=' + res.data) + } else { + this.$message.error('编辑器上传图片失败:' + res.message) + } + }) + }, + getEditor (editor) { + this.editor = editor + }, + changeEditor (html, ele) { + this.editorContent = html + this.editorContentText = ele.text() + }, + /** + * 编辑时获取全部信息 + */ + sysNoticeDetail (id) { + sysNoticeDetail({ id: id }).then((res) => { + this.noticeDetail = res.data + this.getMock(this.noticeDetail) + }) + }, + /** + * 穿梭框 + */ + getMock (noticeDetail) { + const targetKeys = [] + const mockData = [] + sysUserSelector().then((res) => { + this.formLoading = false + for (let i = 0; i < res.data.length; i++) { + const data = { + key: res.data[i].id.toString(), + title: res.data[i].name, + description: `description of ${res.data[i].name}` + } + for (let j = 0; j < noticeDetail.noticeUserIdList.length; j++) { + if (data.key === noticeDetail.noticeUserIdList[j]) { + targetKeys.push(noticeDetail.noticeUserIdList[j]) + } + } + mockData.push(data) + } + }) + this.mockData = mockData + this.targetKeys = targetKeys + }, + filterOption (inputValue, option) { + return option.description.indexOf(inputValue) > -1 + }, + handleChange (targetKeys, direction, moveKeys) { + this.targetKeys = targetKeys + }, + handleSubmit (types) { + const { form: { validateFields } } = this + // eslint-disable-next-line eqeqeq + if (this.editorContent == '') { + this.$message.error('请填写内容') + return + } + if (this.targetKeys.length < 1) { + this.$message.error('请选择通知到的人') + return + } + validateFields((errors, values) => { + if (!errors) { + this.formLoading = true + values.content = this.editorContent + values.status = types + values.noticeUserIdList = this.targetKeys + sysNoticeEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.visible = false + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.formLoading = false + }) + } + }) + }, + handleCancel () { + this.editor.txt.clear() + this.targetKeys = [] + this.editorContent = '' + this.form.resetFields() + this.visible = false + this.formLoading = true + } + } + } +</script> +<style> + .subButton{ + float: right; + } + .subForm-item{ + margin-bottom: 0px; + } +</style> diff --git a/_web/src/views/system/notice/index.vue b/_web/src/views/system/notice/index.vue new file mode 100644 index 0000000..602e142 --- /dev/null +++ b/_web/src/views/system/notice/index.vue @@ -0,0 +1,191 @@ +<template> + <div> + <x-card v-if="hasPerm('sysNotice:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="关键词" > + <a-input v-model="queryParam.searchValue" allow-clear placeholder="请输入标题、内容"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="类型" > + <a-select v-model="queryParam.type" placeholder="请选择类型" allow-clear > + <a-select-option v-for="(item,index) in typeDictTypeDropDown" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + :rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + > + <template slot="operator" v-if="hasPerm('sysNotice:add')"> + <a-button @click="$refs.addForm.add()" icon="plus" type="primary" v-if="hasPerm('sysNotice:add')" >新增公告</a-button> + </template> + <span slot="status" slot-scope="text"> + {{ 'notice_status' | dictType(text) }} + </span> + <span slot="type" slot-scope="text"> + {{ 'notice_type' | dictType(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <div v-if="record.status == 0"> + <a v-if="hasPerm('sysNotice:detail')" @click="$refs.detailForm.detail(record)">查看</a> + <a-divider type="vertical" v-if="hasPerm('sysNotice:detail') & hasPerm('sysNotice:edit')"/> + <a v-if="hasPerm('sysNotice:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysNotice:edit') & hasPerm('sysNotice:changeStatus')"/> + <a-popconfirm v-if="hasPerm('sysNotice:changeStatus')" placement="topRight" title="确认发布该信息?" @confirm="() => editNoticeStatus(1,record)"> + <a>发布</a> + </a-popconfirm> + </div> + <div v-if="record.status == 1"> + <a v-if="hasPerm('sysNotice:detail')" @click="$refs.detailForm.detail(record)">查看</a> + <a-divider type="vertical" v-if="hasPerm('sysNotice:detail') & hasPerm('sysNotice:changeStatus')"/> + <a-popconfirm v-if="hasPerm('sysNotice:changeStatus')" placement="topRight" title="确认撤回该信息?" @confirm="() => editNoticeStatus(2,record)"> + <a>撤回</a> + </a-popconfirm> + </div> + <div v-if="record.status == 2"> + <a v-if="hasPerm('sysNotice:detail')" @click="$refs.detailForm.detail(record)">查看</a> + <a-divider type="vertical" v-if="hasPerm('sysNotice:detail') & hasPerm('sysNotice:delete')"/> + <a-popconfirm v-if="hasPerm('sysNotice:delete')" placement="topRight" title="确认删除?" @confirm="() => sysNoticeDelete(record)"> + <a>删除</a> + </a-popconfirm> + </div> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" v-if="hasPerm('sysNotice:add')"/> + <edit-form ref="editForm" @ok="handleOk" v-if="hasPerm('sysNotice:edit')"/> + <detail-form ref="detailForm" @ok="handleOk" v-if="hasPerm('sysNotice:detail')"/> + <div ref="editor"></div> + </a-card> + </div> +</template> +<script> + import { STable, XCard } from '@/components' + import { sysNoticePage, sysNoticeDelete, sysNoticeChangeStatus } from '@/api/modular/system/noticeManage' + import addForm from './addForm' + import editForm from './editForm' + import detailForm from './detailForm' + export default { + components: { + XCard, + STable, + addForm, + editForm, + detailForm + }, + data () { + return { + // 高级搜索 展开/关闭 + advanced: false, + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '标题', + dataIndex: 'title' + }, + { + title: '类型', + dataIndex: 'type', + scopedSlots: { customRender: 'type' } + }, + { + title: '状态', + dataIndex: 'status', + scopedSlots: { customRender: 'status' } + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysNoticePage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + selectedRowKeys: [], + selectedRows: [], + statusDictTypeDropDown: [], // 0草稿 1发布 2撤回 3删除 + typeDictTypeDropDown: []// 0通知 1公告 + } + }, + created () { + this.sysDictTypeDropDown()// 先注释 + if (this.hasPerm('sysNotice:changeStatus') || this.hasPerm('sysNotice:edit') || this.hasPerm('sysNotice:delete')) { + this.columns.push({ + title: '操作', + width: '300px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.statusDictTypeDropDown = this.$options.filters['dictData']('notice_status') + this.typeDictTypeDropDown = this.$options.filters['dictData']('notice_type') + }, + /** + * 修改状态 + */ + editNoticeStatus (code, record) { + sysNoticeChangeStatus({ id: record.id, status: code.toString() }).then(res => { + if (res.success) { + this.$message.success('操作成功') + this.$refs.table.refresh() + } else { + this.$message.error('操作失败:' + res.message) + } + }) + }, + /** + * 提交 + */ + sysNoticeDelete (record) { + sysNoticeDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/noticeReceived/detailForm.vue b/_web/src/views/system/noticeReceived/detailForm.vue new file mode 100644 index 0000000..98092a5 --- /dev/null +++ b/_web/src/views/system/noticeReceived/detailForm.vue @@ -0,0 +1,75 @@ +<template> + <a-modal + title="通知公告详情" + :width="1000" + :confirmLoading="confirmLoading" + :visible="visible" + :footer="null" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + + <div style="text-align: center;font-size: 30px">{{ this.contentRecord.title }}</div> + <br> + <div style="text-align: right;font-size: 10px"> + <span>(发布人:{{ this.contentRecord.publicUserName }})</span> + <span>发布时间:{{ this.contentRecord.publicTime }} </span> + </div> + <a-divider style="margin-top: 5px"/> + <div > + <label v-html="this.contentRecord.content"></label> + </div> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysNoticeDetail } from '@/api/modular/system/noticeManage' + + export default { + name: 'DetailForm', + components: { + }, + + data () { + return { + visible: false, + confirmLoading: false, + contentRecord: '' + } + }, + + methods: { + + // 初始化方法 + detail (record) { + this.confirmLoading = false + this.visible = true + this.contentRecord = record + }, + + /** + * 查看详情 + */ + sysNoticeDetail (id) { + sysNoticeDetail({ id: id }).then((res) => { + this.confirmLoading = false + this.contentRecord = res.data + }) + }, + + handleCancel () { + this.visible = false + } + } + } +</script> +<style> + .subButton{ + float: right; + } + .subForm-item{ + margin-bottom: 0px; + } +</style> diff --git a/_web/src/views/system/noticeReceived/index.vue b/_web/src/views/system/noticeReceived/index.vue new file mode 100644 index 0000000..fa8d89c --- /dev/null +++ b/_web/src/views/system/noticeReceived/index.vue @@ -0,0 +1,136 @@ +<template> + <div> + <x-card v-if="hasPerm('sysNotice:received')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="关键词" v-if="hasPerm('sysNotice:received')"> + <a-input v-model="queryParam.searchValue" allow-clear placeholder="请输入标题、内容"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="类型" v-if="hasPerm('sysNotice:received')"> + <a-select v-model="queryParam.type" placeholder="请选择类型" allow-clear > + <a-select-option v-for="(item,index) in typeDictTypeDropDown" :key="index" :value="item.code" >{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="!advanced && 8 || 24" :sm="24"> + <span class="table-page-search-submitButtons" > + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + :rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + > + <span slot="status" slot-scope="text"> + {{ 'notice_status' | dictType(text) }} + </span> + <span slot="type" slot-scope="text"> + {{ 'notice_type' | dictType(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysNotice:received')" @click="$refs.detailForm.detail(record)">查看</a> + </span> + </s-table> + <detail-form ref="detailForm" @ok="handleOk" /> + <div ref="editor"></div> + </a-card> + </div> +</template> +<script> + import { STable, XCard } from '@/components' + // eslint-disable-next-line no-unused-vars + import { sysNoticeReceived } from '@/api/modular/system/noticeReceivedManage' + import detailForm from './detailForm' + export default { + components: { + XCard, + STable, + detailForm + }, + data () { + return { + // 高级搜索 展开/关闭 + advanced: false, + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '标题', + dataIndex: 'title' + }, + { + title: '类型', + dataIndex: 'type', + scopedSlots: { customRender: 'type' } + }, + { + title: '状态', + dataIndex: 'status', + scopedSlots: { customRender: 'status' } + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysNoticeReceived(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + selectedRowKeys: [], + selectedRows: [], + statusDictTypeDropDown: [], // 0草稿 1发布 2撤回 3删除 + typeDictTypeDropDown: []// 0通知 1公告 + } + }, + created () { + this.sysDictTypeDropDown()// 先注释 + if (this.hasPerm('sysNotice:received')) { + this.columns.push({ + title: '操作', + width: '200px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.statusDictTypeDropDown = this.$options.filters['dictData']('notice_status') + this.typeDictTypeDropDown = this.$options.filters['dictData']('notice_type') + }, + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> + +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/onlineUser/index.vue b/_web/src/views/system/onlineUser/index.vue new file mode 100644 index 0000000..ad2f662 --- /dev/null +++ b/_web/src/views/system/onlineUser/index.vue @@ -0,0 +1,124 @@ +<template> + <a-card :bordered="false"> + <s-table + ref="table" + :pagination="false" + :loading="loading" + :columns="columns" + :data="loadData" + :rowKey="(record) => record.sessionId" + :rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + > + <span slot="lastLoginAddress" slot-scope="text"> + <ellipsis :length="20" tooltip>{{ text }}</ellipsis> + </span> + <span slot="lastLoginBrowser" slot-scope="text"> + <ellipsis :length="20" tooltip>{{ text }}</ellipsis> + </span> + <span slot="action" slot-scope="text, record"> + <a-popconfirm v-if="hasPerm('sysOnlineUser:forceExist')" placement="topRight" title="是否强制下线该用户?" @confirm="() => forceExist(record)"> + <a>强制下线</a> + </a-popconfirm> + </span> + </s-table> + </a-card> +</template> +<script> + import { STable, Ellipsis } from '@/components' + import { sysOnlineUserForceExist, sysOnlineUserList } from '@/api/modular/system/onlineUserManage' + export default { + components: { + STable, + Ellipsis + }, + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '账号', + dataIndex: 'account' + }, + { + title: '昵称', + dataIndex: 'nickName' + }, + { + title: '最后登录IP', + dataIndex: 'lastLoginIp' + }, + { + title: '最后登录时间', + dataIndex: 'lastLoginTime' + }, + { + title: '最后登录地址', + dataIndex: 'lastLoginAddress', + scopedSlots: { customRender: 'lastLoginAddress' } + }, + { + title: '最后登录浏览器', + dataIndex: 'lastLoginBrowser', + scopedSlots: { customRender: 'lastLoginBrowser' } + }, + { + title: '最后登录所用系统', + dataIndex: 'lastLoginOs' + } + ], + loading: true, + loadData: parameter => { + return sysOnlineUserList(Object.assign(parameter, this.queryParam)).then((res) => { + if (this.hasPerm('sysOnlineUser:list')) { + return res.data + } else { + return new Promise((resolve, reject) => { + return resolve() + }) + } + }) + }, + selectedRowKeys: [], + selectedRows: [] + } + }, + // 进页面加载 + created () { + if (this.hasPerm('sysOnlineUser:forceExist')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + forceExist (record) { + sysOnlineUserForceExist(record).then((res) => { + if (res.success) { + this.$message.success('强制下线成功') + // 重新加载表格 + this.$refs.table.refresh() + } else { + this.$message.error('强制下线失败:' + res.message) + } + }) + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/org/addForm.vue b/_web/src/views/system/org/addForm.vue new file mode 100644 index 0000000..1a60ada --- /dev/null +++ b/_web/src/views/system/org/addForm.vue @@ -0,0 +1,156 @@ +<template> + <a-modal + title="新增机构" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + <a-form-item + label="机构名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入机构名称" v-decorator="['name', {rules: [{required: true, message: '请输入机构名称!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + label="上级机构" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-tree-select + v-decorator="['pid', {rules: [{ required: true, message: '请选择上级机构!' }]}]" + style="width: 100%" + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + :treeData="orgTree" + placeholder="请选择上级机构" + treeDefaultExpandAll + > + <span slot="title" slot-scope="{ id }">{{ id }} + </span> + </a-tree-select> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + > + <a-input-number placeholder="请输入排序" style="width: 100%" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import store from '@/store' + import { sysOrgAdd, getOrgTree } from '@/api/modular/system/orgManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + orgTree: [], + visible: false, + confirmLoading: false, + formLoading: true, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add () { + this.visible = true + this.getOrgTree() + }, + + /** + * 获取机构树,并加载于表单中 + */ + getOrgTree () { + getOrgTree().then((res) => { + this.formLoading = false + if (!res.success) { + this.orgTree = [] + return + } + const admintype = store.getters.admintype + // eslint-disable-next-line eqeqeq + if (admintype == '1') { + this.orgTree = [{ + 'id': '-1', + 'parentId': '0', + 'title': '顶级', + 'value': '0', + 'pid': '0', + 'children': res.data + }] + } else { + this.orgTree = res.data + } + }) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysOrgAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/org/editForm.vue b/_web/src/views/system/org/editForm.vue new file mode 100644 index 0000000..a96ee82 --- /dev/null +++ b/_web/src/views/system/org/editForm.vue @@ -0,0 +1,178 @@ +<template> + <a-modal + title="编辑机构" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input v-decorator="['id']" /> + </a-form-item> + + <a-form-item + label="机构名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入机构名称" v-decorator="['name', {rules: [{required: true, message: '请输入机构名称!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + label="上级机构" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-tree-select + v-decorator="['pid', {rules: [{ required: true, message: '请选择上级机构!' }]}]" + style="width: 100%" + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + :treeData="orgTree" + placeholder="请选择上级机构" + treeDefaultExpandAll + > + <span slot="title" slot-scope="{ id }">{{ id }} + </span> + </a-tree-select> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + > + <a-input-number placeholder="请输入排序" style="width: 100%" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import store from '@/store' + import { sysOrgEdit, getOrgTree } from '@/api/modular/system/orgManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + orgTree: [], + visible: false, + confirmLoading: false, + formLoading: true, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + this.getOrgTree() + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + name: record.name, + code: record.code, + sort: record.sort, + pid: record.pid, + remark: record.remark + } + ) + }, 100) + }, + + /** + * 获取机构树,并加载于表单中 + */ + getOrgTree () { + getOrgTree().then((res) => { + this.formLoading = false + if (!res.success) { + this.orgTree = [] + return + } + const admintype = store.getters.admintype + // eslint-disable-next-line eqeqeq + if (admintype == '1') { + this.orgTree = [{ + 'id': '-1', + 'parentId': '0', + 'title': '顶级', + 'value': '0', + 'pid': '0', + 'children': res.data + }] + } else { + this.orgTree = res.data + } + }) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysOrgEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/org/index.vue b/_web/src/views/system/org/index.vue new file mode 100644 index 0000000..f25fe53 --- /dev/null +++ b/_web/src/views/system/org/index.vue @@ -0,0 +1,232 @@ +<template> + <a-row :gutter="24" > + <a-col :md="5" :sm="24" > + <a-card :bordered="false" :loading="treeLoading"> + <div v-if="this.orgTree!='' "> + <a-tree + style="scroll:true" + :treeData="orgTree" + v-if="orgTree.length" + @select="handleClick" + :defaultExpandAll="true" + :defaultExpandedKeys="defaultExpandedKeys" + :replaceFields="replaceFields" /> + </div> + <div v-else> + <a-empty :image="simpleImage" /> + </div> + </a-card> + </a-col> + <a-col :md="19" :sm="24"> + <x-card v-if="hasPerm('sysOrg:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="机构名称" > + <a-input v-model="queryParam.name" allow-clear placeholder="请输入机构名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="options.alert" + :rowKey="(record) => record.id" + :rowSelection="options.rowSelection" + > + <template slot="operator"> + <a-button @click="$refs.addForm.add()" icon="plus" type="primary" v-if="hasPerm('sysOrg:add')">新增机构</a-button> + <a-button type="danger" :disabled="selectedRowKeys.length < 1" v-if="hasPerm('sysPos:delete')" @click="batchDelete"><a-icon type="delete"/>批量删除</a-button> + <x-down + v-if="hasPerm('sysOrg:export')" + ref="batchExport" + @batchExport="batchExport" + /> + </template> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysOrg:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysOrg:edit') & hasPerm('sysOrg:delete')"/> + <a-popconfirm v-if="hasPerm('sysOrg:delete')" placement="topRight" title="确认删除?" @confirm="() => singleDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + </a-card> + </a-col> + </a-row> +</template> +<script> + import { STable, XCard, XDown } from '@/components' + import { Empty } from 'ant-design-vue' + import { getOrgPage, sysOrgDelete, getOrgTree, sysOrgExport } from '@/api/modular/system/orgManage' + import addForm from './addForm' + import editForm from './editForm' + export default { + components: { + XDown, + XCard, + STable, + addForm, + editForm + }, + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '机构名称', + dataIndex: 'name' + }, + { + title: '唯一编码', + dataIndex: 'code' + }, + { + title: '排序', + dataIndex: 'sort' + }, + { + title: '备注', + dataIndex: 'remark' + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return getOrgPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + orgTree: [], + selectedRowKeys: [], + selectedRows: [], + defaultExpandedKeys: [], + // 搜索的三个参数 + expandedKeys: [], + searchValue: '', + autoExpandParent: true, + treeLoading: true, + simpleImage: Empty.PRESENTED_IMAGE_SIMPLE, + replaceFields: { + key: 'id' + }, + options: { + alert: { show: true, clear: () => { this.selectedRowKeys = [] } }, + rowSelection: { + selectedRowKeys: this.selectedRowKeys, + onChange: this.onSelectChange + } + } + } + }, + created () { + this.getOrgTree() + if (this.hasPerm('sysOrg:edit') || this.hasPerm('sysOrg:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + /** + * 获取到机构树,展开顶级下树节点,考虑到后期数据量变大,不建议全部展开 + */ + getOrgTree () { + getOrgTree(Object.assign(this.queryParam)).then(res => { + this.treeLoading = false + if (!res.success) { + return + } + this.orgTree = res.data + this.queryParam.parentId = this.orgTree[0].id + // 全部展开,上面api方法提供的不生效,先用此方法 + for (var item of res.data) { + // eslint-disable-next-line eqeqeq + if (item.parentId == 0) { + this.defaultExpandedKeys.push(item.id) + } + } + this.$refs.table.refresh() + }) + }, + /** + * 单个删除 + */ + singleDelete (record) { + const param = [{ 'id': record.id }] + this.sysOrgDelete(param) + }, + /** + * 批量删除 + */ + batchDelete () { + const paramIds = this.selectedRowKeys.map((d) => { + return { 'id': d } + }) + this.sysOrgDelete(paramIds) + }, + /** + * 批量导出 + */ + batchExport () { + sysOrgExport().then((res) => { + this.$refs.batchExport.downloadfile(res) + }) + }, + /** + * 删除 + */ + sysOrgDelete (record) { + sysOrgDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.getOrgTree() + this.$refs.table.clearRefreshSelected() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + handleClick (e) { + this.queryParam = { + pid: e.toString() + } + this.$refs.table.refresh(true) + }, + handleOk () { + this.getOrgTree() + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/pos/addForm.vue b/_web/src/views/system/pos/addForm.vue new file mode 100644 index 0000000..61bf4e1 --- /dev/null +++ b/_web/src/views/system/pos/addForm.vue @@ -0,0 +1,106 @@ +<template> + <a-modal + title="新增职位" + :width="500" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item + label="职位名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入职位名称" v-decorator="['name', {rules: [{required: true, message: '请输入职位名称!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + > + <a-input-number placeholder="请输入排序" style="width: 100%" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysPosAdd } from '@/api/modular/system/posManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add (record) { + this.visible = true + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysPosAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/pos/editForm.vue b/_web/src/views/system/pos/editForm.vue new file mode 100644 index 0000000..76c86b6 --- /dev/null +++ b/_web/src/views/system/pos/editForm.vue @@ -0,0 +1,129 @@ +<template> + <a-modal + title="职位编辑" + :width="500" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input v-decorator="['id']" /> + </a-form-item> + + <a-form-item + label="职位名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入职位名称" v-decorator="['name', {rules: [{required: true, message: '请输入职位名称!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + has-feedback + > + <a-input-number style="width: 100%" placeholder="请输入排序" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysPosEdit } from '@/api/modular/system/posManage' + + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + name: record.name, + code: record.code, + sort: record.sort, + remark: record.remark + } + ) + }, 100) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysPosEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/pos/index.vue b/_web/src/views/system/pos/index.vue new file mode 100644 index 0000000..3bc29f1 --- /dev/null +++ b/_web/src/views/system/pos/index.vue @@ -0,0 +1,186 @@ +<template> + <div> + <x-card v-if="hasPerm('sysPos:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="职位名称" > + <a-input v-model="queryParam.name" allow-clear placeholder="请输入职位名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="唯一编码" > + <a-input v-model="queryParam.code" allow-clear placeholder="请输入唯一编码" /> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="options.alert" + :rowKey="(record) => record.id" + :rowSelection="options.rowSelection" + > + <template slot="operator" v-if="hasPerm('sysPos:add')"> + <a-button @click="$refs.addForm.add()" icon="plus" type="primary" v-if="hasPerm('sysPos:add')">新增职位</a-button> + <a-button type="danger" :disabled="selectedRowKeys.length < 1" v-if="hasPerm('sysPos:delete')" @click="batchDelete"><a-icon type="delete"/>批量删除</a-button> + <x-down + v-if="hasPerm('sysPos:export')" + ref="batchExport" + @batchExport="batchExport" + /> + </template> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysPos:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysPos:edit') & hasPerm('sysPos:delete')"/> + <a-popconfirm v-if="hasPerm('sysPos:delete')" placement="topRight" title="确认删除?" @confirm="() => singleDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + </a-card> + </div> +</template> + +<script> +import { STable, XCard, XDown } from '@/components' +import { sysPosPage, sysPosDelete, sysPosExport } from '@/api/modular/system/posManage' +import addForm from './addForm' +import editForm from './editForm' + +export default { + components: { + XDown, + XCard, + STable, + addForm, + editForm + }, + + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '职位名称', + dataIndex: 'name' + }, + { + title: '唯一编码', + dataIndex: 'code' + }, + { + title: '排序', + dataIndex: 'sort' + }, + { + title: '备注', + dataIndex: 'remark' + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysPosPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + selectedRowKeys: [], + selectedRows: [], + options: { + alert: { show: true, clear: () => { this.selectedRowKeys = [] } }, + rowSelection: { + selectedRowKeys: this.selectedRowKeys, + onChange: this.onSelectChange + } + } + } + }, + + created () { + if (this.hasPerm('sysPos:edit') || this.hasPerm('sysPos:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + + methods: { + /** + * 单个删除 + */ + singleDelete (record) { + const param = [{ 'id': record.id }] + this.sysPosDelete(param) + }, + /** + * 批量删除 + */ + batchDelete () { + const paramIds = this.selectedRowKeys.map((d) => { + return { 'id': d } + }) + this.sysPosDelete(paramIds) + }, + /** + * 删除 + */ + sysPosDelete (param) { + sysPosDelete(param).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.clearRefreshSelected() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + /** + * 批量导出 + */ + batchExport () { + const paramIds = this.selectedRowKeys.map((d) => { + return { 'id': d } + }) + sysPosExport(paramIds).then((res) => { + this.$refs.batchExport.downloadfile(res) + }) + }, + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + +} +</script> + +<style lang="less"> +.table-operator { + margin-bottom: 18px; +} +button { + margin-right: 8px; +} +</style> diff --git a/_web/src/views/system/role/addForm.vue b/_web/src/views/system/role/addForm.vue new file mode 100644 index 0000000..68db3ad --- /dev/null +++ b/_web/src/views/system/role/addForm.vue @@ -0,0 +1,106 @@ +<template> + <a-modal + title="新增角色" + :width="500" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + <a-form-item + label="角色名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入角色名" v-decorator="['name', {rules: [{required: true, message: '请输入角色名!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + > + <a-input-number placeholder="请输入排序" style="width: 100%" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysRoleAdd } from '@/api/modular/system/roleManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add (record) { + this.visible = true + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysRoleAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/role/editForm.vue b/_web/src/views/system/role/editForm.vue new file mode 100644 index 0000000..fd1b88b --- /dev/null +++ b/_web/src/views/system/role/editForm.vue @@ -0,0 +1,128 @@ +<template> + <a-modal + title="角色编辑" + :width="500" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> + + <a-form-item + style="display: none;" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input v-decorator="['id']" /> + </a-form-item> + + <a-form-item + label="角色名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入角色名" v-decorator="['name', {rules: [{required: true, message: '请输入角色名!'}]}]" /> + </a-form-item> + + <a-form-item + label="唯一编码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入唯一编码" v-decorator="['code', {rules: [{required: true, message: '请输入唯一编码!'}]}]" /> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="排序" + has-feedback + > + <a-input-number style="width: 100%" placeholder="请输入排序" v-decorator="['sort', { initialValue: 100 }]" :min="1" :max="1000" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysRoleEdit } from '@/api/modular/system/roleManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 18 } + }, + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + name: record.name, + code: record.code, + sort: record.sort, + remark: record.remark + } + ) + }, 100) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysRoleEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/role/index.vue b/_web/src/views/system/role/index.vue new file mode 100644 index 0000000..3f05899 --- /dev/null +++ b/_web/src/views/system/role/index.vue @@ -0,0 +1,156 @@ +<template> + <div> + <x-card v-if="hasPerm('sysRole:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="角色名"> + <a-input v-model="queryParam.name" allow-clear placeholder="请输入角色名"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="唯一编码"> + <a-input v-model="queryParam.code" allow-clear placeholder="请输入唯一编码"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + > + <template slot="operator" v-if="hasPerm('sysRole:add')"> + <a-button @click="$refs.addForm.add()" icon="plus" type="primary" v-if="hasPerm('sysRole:add')">新增角色</a-button> + </template> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysRole:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysRole:edit')"/> + <a-dropdown v-if="hasPerm('sysRole:grantMenu') || hasPerm('sysRole:grantData') || hasPerm('sysRole:delete')"> + <a class="ant-dropdown-link"> + 更多 <a-icon type="down" /> + </a> + <a-menu slot="overlay"> + <a-menu-item v-if="hasPerm('sysRole:grantMenu')"> + <a @click="$refs.roleMenuForm.roleMenu(record)">授权菜单</a> + </a-menu-item> + <a-menu-item v-if="hasPerm('sysRole:grantData')"> + <a @click="$refs.roleOrgForm.roleOrg(record)">授权数据</a> + </a-menu-item> + <a-menu-item v-if="hasPerm('sysRole:delete')"> + <a-popconfirm placement="topRight" title="确认删除?" @confirm="() => sysRoleDelete(record)"> + <a>删除</a> + </a-popconfirm> + </a-menu-item> + </a-menu> + </a-dropdown> + </span> + + </s-table> + + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + <role-menu-form ref="roleMenuForm" @ok="handleOk"/> + <role-org-form ref="roleOrgForm" @ok="handleOk"/> + + </a-card> + </div> +</template> + +<script> + import { STable, XCard } from '@/components' + import { getRolePage, sysRoleDelete } from '@/api/modular/system/roleManage' + import addForm from './addForm' + import editForm from './editForm' + import roleMenuForm from './roleMenuForm' + import roleOrgForm from './roleOrgForm' + export default { + components: { + XCard, + STable, + addForm, + editForm, + roleMenuForm, + roleOrgForm + }, + + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '角色名', + dataIndex: 'name' + }, + { + title: '唯一编码', + dataIndex: 'code' + }, + { + title: '排序', + dataIndex: 'sort' + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return getRolePage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + } + } + }, + + created () { + if (this.hasPerm('sysRole:edit') || this.hasPerm('sysRole:grantMenu') || this.hasPerm('sysRole:grantData') || this.hasPerm('sysRole:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + + methods: { + sysRoleDelete (record) { + sysRoleDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + + handleOk () { + this.$refs.table.refresh() + } + } + + } +</script> + +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } + +</style> diff --git a/_web/src/views/system/role/roleMenuForm.vue b/_web/src/views/system/role/roleMenuForm.vue new file mode 100644 index 0000000..afef4dc --- /dev/null +++ b/_web/src/views/system/role/roleMenuForm.vue @@ -0,0 +1,184 @@ +<template> + <a-modal + title="授权菜单" + :width="600" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + + <a-form-item + label="菜单权限" + :labelCol="labelCol" + :wrapperCol="wrapperCol"> + + <a-tree + v-model="checkedKeys" + multiple + checkable + :auto-expand-parent="autoExpandParent" + :expanded-keys="expandedKeys" + :tree-data="menuTreeData" + :selected-keys="selectedKeys" + :replaceFields="replaceFields" + @expand="onExpand" + @check="onCheck" + /> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { SysMenuTreeForGrant } from '@/api/modular/system/menuManage' + import { sysRoleOwnMenu, sysRoleGrantMenu } from '@/api/modular/system/roleManage' + + export default { + data() { + return { + labelCol: { + style: { 'padding-right': '20px' }, + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + menuTreeData: [], + expandedKeys: [], + checkedKeys: [], + visible: false, + confirmLoading: false, + formLoading: true, + autoExpandParent: true, + selectedKeys: [], + subValues: [], + roleEntity: [], + replaceFields: { + key: 'id' + }, + form: this.$form.createForm(this), + commitKeys: [], + leastChilds: [] + } + }, + methods: { + // 初始化方法 + roleMenu(record) { + this.formLoading = true + this.roleEntity = record + this.visible = true + this.getMenuTree() + }, + + /** + * 获取菜单列表 + */ + getMenuTree() { + const _this = this + SysMenuTreeForGrant().then((res) => { + if (res.success) { + _this.menuTreeData = res.data + _this.getLeastChilds(res.data) + // 默认展开目录级 + _this.menuTreeData.forEach(item => { + _this.expandedKeys.push(item.id) + }) + + _this.expandedMenuKeys(_this.roleEntity) + } + }) + }, + + getLeastChilds(data) { + for (let i = 0; i < data.length; i++) { + this.pushLeastChilds(data[i]) + } + }, + + pushLeastChilds(e) { + if (e.children.length > 0) { + this.getLeastChilds(e.children) + return + } + this.leastChilds.push(e.id) + }, + + /** + * 此角色已有菜单权限 + */ + expandedMenuKeys(record) { + const _this = this + sysRoleOwnMenu({ id: record.id }).then((res) => { + if (res.success) { + _this.pickCheckedKeys(res.data) + _this.commitKeys = res.data + } + _this.formLoading = false + }) + }, + + pickCheckedKeys(data) { + for (let i = 0; i < data.length; i++) { + if (this.leastChilds.includes(data[i])) { + this.checkedKeys.push(data[i]) + } + } + }, + + onExpand(expandedKeys) { + this.expandedKeys = expandedKeys + this.autoExpandParent = false + }, + + onCheck(checkedKeys, info) { + this.checkedKeys = checkedKeys + this.commitKeys = checkedKeys.concat(info.halfCheckedKeys) + }, + + onSelect(selectedKeys, info) { + console.log(selectedKeys) + console.log(info) + this.selectedKeys = selectedKeys + }, + + handleSubmit() { + const _this = this + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysRoleGrantMenu({ id: _this.roleEntity.id, grantMenuIdList: _this.commitKeys }).then((res) => { + if (res.success) { + _this.$message.success('授权成功') + _this.confirmLoading = false + _this.$emit('ok', values) + _this.handleCancel() + } else { + _this.$message.error('授权失败:' + res.message) + } + }).finally((res) => { + _this.confirmLoading = false + }) + } else { + _this.confirmLoading = false + } + }) + }, + handleCancel() { + // 清空已选择的 + this.checkedKeys = [] + // 清空已展开的 + this.expandedKeys = [] + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/role/roleOrgForm.vue b/_web/src/views/system/role/roleOrgForm.vue new file mode 100644 index 0000000..38cfa90 --- /dev/null +++ b/_web/src/views/system/role/roleOrgForm.vue @@ -0,0 +1,195 @@ +<template> + <a-modal + title="授权数据" + :width="600" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + <a-form-item + label="授权范围" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择授权范围" v-decorator="['dataScopeType', {rules: [{ required: true, message: '请选择授权范围!' }]}]" > + <a-select-option v-for="(item,index) in dataScopeTypeData" :key="index" :value="item.code" @click="handleChange(item.code)">{{ item.value }}</a-select-option> + </a-select> + </a-form-item> + <div v-show="orgTreeShow"> + <a-form-item + label="选择机构" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-tree + v-model="checkedKeys" + checkable + checkStrictly + :auto-expand-parent="autoExpandParent" + :expanded-keys="expandedKeys" + :tree-data="orgTreeData" + :selected-keys="selectedKeys" + :replaceFields="replaceFields" + @expand="onExpand" + @select="onSelect" + /> + </a-form-item> + </div> + </a-form> + </a-spin> + </a-modal> +</template> + +<script> + import { getOrgTree } from '@/api/modular/system/orgManage' + import { sysRoleOwnData, sysRoleGrantData } from '@/api/modular/system/roleManage' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + + export default { + data () { + return { + labelCol: { + style: { 'padding-right': '20px' }, + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + orgTreeData: [], + expandedKeys: [], + checkedKeys: [], + visible: false, + confirmLoading: false, + formLoading: true, + autoExpandParent: true, + selectedKeys: [], + subValues: [], + roleEntity: [], + dataScopeTypeData: [], + orgTreeShow: false, + replaceFields: { + key: 'id' + }, + form: this.$form.createForm(this) + } + }, + + methods: { + // 初始化方法 + roleOrg (record) { + this.roleEntity = record + this.visible = true + this.formLoading = true + this.sysDictTypeDropDown() + this.form.getFieldDecorator('dataScopeType', { initialValue: record.dataScopeType.toString() }) + this.handleChange(record.dataScopeType) + }, + + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + // 数据范围 + sysDictTypeDropDown({ code: 'data_scope_type' }).then((res) => { + this.dataScopeTypeData = res.data + this.formLoading = false + }) + }, + + // 范围下拉框事件 + handleChange (value) { + // eslint-disable-next-line eqeqeq + if (value == '5') { + this.formLoading = true + this.orgTreeShow = true + // 获取机构树 + this.getOrgTree() + // 已关联数据 + this.sysRoleOwnData(this.roleEntity) + } else { + this.orgTreeShow = false + // 清理已选中机构 + this.checkedKeys = [] + } + }, + + /** + * 获取机构树 + */ + getOrgTree () { + getOrgTree().then((res) => { + if (res.success) { + this.orgTreeData = res.data + // 默认展开 + this.orgTreeData.forEach(item => { + this.expandedKeys.push(item.id) + }) + } + }) + }, + + /** + * 此角色已有数据列表 + */ + sysRoleOwnData (record) { + sysRoleOwnData({ id: record.id }).then((res) => { + if (res.success) { + this.checkedKeys = res.data + } + this.formLoading = false + }) + }, + + onExpand (expandedKeys) { + this.expandedKeys = expandedKeys + this.autoExpandParent = false + }, + onCheck (checkedKeys) { + this.checkedKeys = checkedKeys + }, + onSelect (selectedKeys, info) { + this.selectedKeys = selectedKeys + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + const checkedKeys = this.checkedKeys.checked === undefined ? this.checkedKeys : this.checkedKeys.checked + sysRoleGrantData({ id: this.roleEntity.id, grantOrgIdList: checkedKeys, dataScopeType: values.dataScopeType }).then((res) => { + this.confirmLoading = false + if (res.success) { + this.$message.success('授权成功') + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('授权失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + // 清空已选择的 + this.checkedKeys = [] + // 清空已展开的 + this.expandedKeys = [] + this.visible = false + // 隐藏机构树 + this.orgTreeShow = false + } + } + } +</script> diff --git a/_web/src/views/system/sms/index.vue b/_web/src/views/system/sms/index.vue new file mode 100644 index 0000000..c5bf0f7 --- /dev/null +++ b/_web/src/views/system/sms/index.vue @@ -0,0 +1,163 @@ +<template> + <div> + <x-card v-if="hasPerm('sysSms:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="手机号"> + <a-input v-model="queryParam.phoneNumbers" placeholder="请输入手机号"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="发送状态"> + <a-select v-model="queryParam.status" placeholder="请选择发送状态" > + <a-select-option v-for="(item,index) in statusDictTypeDropDown" :key="index" :value="item.code" >{{ item.value }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <template v-if="advanced"> + <a-col :md="8" :sm="24"> + <a-form-item label="来源"> + <a-select v-model="queryParam.source" placeholder="请选择来源" > + <a-select-option v-for="(item,index) in sourceDictTypeDropDown" :key="index" :value="item.code" >{{ item.value }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + </template> + <a-col :md="!advanced && 8 || 24" :sm="24"> + <span class="table-page-search-submitButtons" :style="advanced && { float: 'right', overflow: 'hidden' } || {} "> + <a-button type="primary" @click="$refs.table.refresh(true)" >查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + <a @click="toggleAdvanced" style="margin-left: 8px"> + {{ advanced ? '收起' : '展开' }} + <a-icon :type="advanced ? 'up' : 'down'"/> + </a> + </span> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + :rowSelection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + > + <span slot="status" slot-scope="text"> + {{ statusFilter(text) }} + </span> + <span slot="source" slot-scope="text"> + {{ sourceFilter(text) }} + </span> + </s-table> + </a-card> + </div> +</template> +<script> + import { STable, XCard } from '@/components' + import { smsPage } from '@/api/modular/system/smsManage' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + export default { + components: { + XCard, + STable + }, + data () { + return { + // 高级搜索 展开/关闭 + advanced: false, + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '手机号', + dataIndex: 'phoneNumbers' + }, + { + title: '短信验证码', + dataIndex: 'validateCode' + }, + { + title: '短信模板ID', + dataIndex: 'templateCode' + }, + { + title: '发送状态', + dataIndex: 'status', + scopedSlots: { customRender: 'status' } + }, + { + title: '来源', + dataIndex: 'source', + scopedSlots: { customRender: 'source' } + }, + { + title: '失效时间', + dataIndex: 'invalidTime' + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return smsPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + selectedRowKeys: [], + selectedRows: [], + statusDictTypeDropDown: [], + sourceDictTypeDropDown: [] + } + }, + created () { + this.sysDictTypeDropDown() + }, + methods: { + sourceFilter (source) { + // eslint-disable-next-line eqeqeq + const values = this.sourceDictTypeDropDown.filter(item => item.code == source) + if (values.length > 0) { + return values[0].value + } + }, + statusFilter (status) { + // eslint-disable-next-line eqeqeq + const values = this.statusDictTypeDropDown.filter(item => item.code == status) + if (values.length > 0) { + return values[0].value + } + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + sysDictTypeDropDown({ code: 'send_type' }).then((res) => { + this.statusDictTypeDropDown = res.data + }) + sysDictTypeDropDown({ code: 'sms_send_source' }).then((res) => { + this.sourceDictTypeDropDown = res.data + }) + }, + toggleAdvanced () { + this.advanced = !this.advanced + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/timers/addForm.vue b/_web/src/views/system/timers/addForm.vue new file mode 100644 index 0000000..27a6408 --- /dev/null +++ b/_web/src/views/system/timers/addForm.vue @@ -0,0 +1,126 @@ +<template> + <a-modal + title="新增定时任务" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + <a-form-item + label="任务名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入任务名称" v-decorator="['timerName', {rules: [{required: true, message: '请输入任务名称!'}]}]" /> + </a-form-item> + + <a-form-item + label="任务class类名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择任务class类名" v-decorator="['actionClass', {rules: [{ required: true, message: '请选择任务class类名!' }]}]" > + <a-select-option v-for="(item,index) in actionClassData" :key="index" :value="item" >{{ item }}</a-select-option> + </a-select> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="任务表达式" + > + <a-input placeholder="请输入任务表达式" v-decorator="['cron', {rules: [{required: true, message: '请输入任务表达式!'}]}]" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysTimersAdd, sysTimersGetActionClasses } from '@/api/modular/system/timersManage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + actionClassData: [], + formLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add (record) { + this.visible = true + this.formLoading = true + this.getActionClass() + }, + + /** + * 获取选择器下拉框数据 + */ + getActionClass () { + sysTimersGetActionClasses().then((res) => { + this.formLoading = false + if (res.success) { + this.actionClassData = res.data + } else { + this.$message.error('获取选择器下拉框数据') + } + }) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysTimersAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/timers/editForm.vue b/_web/src/views/system/timers/editForm.vue new file mode 100644 index 0000000..22211b5 --- /dev/null +++ b/_web/src/views/system/timers/editForm.vue @@ -0,0 +1,151 @@ +<template> + <a-modal + title="编辑定时任务" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + + <a-form-item + style="display: none;" + > + <a-input v-decorator="['id']" /> + </a-form-item> + <a-form-item + style="display: none;" + > + <a-input v-decorator="['jobStatus']" /> + </a-form-item> + + <a-form-item + label="任务名称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入任务名称" v-decorator="['timerName', {rules: [{required: true, message: '请输入任务名称!'}]}]" /> + </a-form-item> + + <a-form-item + label="任务class类名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-select style="width: 100%" placeholder="请选择任务class类名" v-decorator="['actionClass', {rules: [{ required: true, message: '请选择任务class类名!' }]}]" > + <a-select-option v-for="(item,index) in actionClassData" :key="index" :value="item" >{{ item }}</a-select-option> + </a-select> + </a-form-item> + + <a-form-item + :labelCol="labelCol" + :wrapperCol="wrapperCol" + label="任务表达式" + > + <a-input placeholder="请输入任务表达式" v-decorator="['cron', {rules: [{required: true, message: '请输入任务表达式!'}]}]" /> + </a-form-item> + + <a-form-item + label="备注" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-textarea :rows="4" placeholder="请输入备注" v-decorator="['remark']"></a-textarea> + </a-form-item> + + </a-form> + + </a-spin> + </a-modal> +</template> + +<script> + import { sysTimersEdit, sysTimersGetActionClasses } from '@/api/modular/system/timersManage' + + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + visible: false, + confirmLoading: false, + actionClassData: [], + formLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + edit (record) { + this.visible = true + this.formLoading = true + this.getActionClass() + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + timerName: record.timerName, + actionClass: record.actionClass, + cron: record.cron, + jobStatus: record.jobStatus, + remark: record.remark + } + ) + }, 100) + }, + + /** + * 获取选择器下拉框数据 + */ + getActionClass () { + sysTimersGetActionClasses().then((res) => { + this.formLoading = false + if (res.success) { + this.actionClassData = res.data + } else { + this.$message.error('获取选择器下拉框数据') + } + }) + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysTimersEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.visible = false + this.confirmLoading = false + this.$emit('ok', values) + this.form.resetFields() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/timers/index.vue b/_web/src/views/system/timers/index.vue new file mode 100644 index 0000000..6f2961a --- /dev/null +++ b/_web/src/views/system/timers/index.vue @@ -0,0 +1,196 @@ +<template> + <div> + <x-card v-if="hasPerm('sysTimers:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="任务名称"> + <a-input v-model="queryParam.timerName" allow-clear placeholder="请输入任务名称"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="任务状态"> + <a-select v-model="queryParam.jobStatus" placeholder="请选择状态" > + <a-select-option v-for="(item,index) in jobStatusDictTypeDropDown" :key="index" :value="item.code" >{{ item.value }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="false" + :rowKey="(record) => record.id" + > + <template slot="operator" v-if="hasPerm('sysTimers:add')"> + <a-button @click="$refs.addForm.add()" icon="plus" type="primary" v-if="hasPerm('sysTimers:add')">新增定时器</a-button> + </template> + <span slot="actionClass" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="remark" slot-scope="text"> + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> + </span> + <span slot="jobStatus" slot-scope="text,record" v-if="hasPerm('sysTimers:start') || hasPerm('sysTimers:stop')"> + <a-popconfirm placement="top" :title="text===1? '确定停止该任务?':'确定启动该任务?'" @confirm="() => editjobStatusStatus(text,record)"> + <a-badge :status="text===1? 'processing':'default'" /> + <a>{{ 'run_status' | dictType(text) }}</a> + </a-popconfirm> + </span> + <span slot="jobStatus" v-else> + <a-badge :status="text===1? 'processing':'default'" /> + {{ 'run_status' | dictType(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysTimers:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysTimers:edit') & hasPerm('sysTimers:delete')"/> + <a-popconfirm v-if="hasPerm('sysTimers:delete')" placement="topRight" title="确认删除?" @confirm="() => sysTimersDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + </a-card> + </div> +</template> +<script> + import { STable, Ellipsis, XCard } from '@/components' + import { sysTimersPage, sysTimersDelete, sysTimersStart, sysTimersStop } from '@/api/modular/system/timersManage' + import addForm from './addForm' + import editForm from './editForm' + export default { + name: 'PosIndex', + components: { + XCard, + STable, + Ellipsis, + addForm, + editForm + }, + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '任务名称', + dataIndex: 'timerName' + }, + { + title: '任务class类名', + dataIndex: 'actionClass', + scopedSlots: { customRender: 'actionClass' } + }, + { + title: '定时任务表达式', + dataIndex: 'cron' + }, + { + title: '备注', + dataIndex: 'remark', + scopedSlots: { customRender: 'remark' } + }, + { + title: '状态', + dataIndex: 'jobStatus', + scopedSlots: { customRender: 'jobStatus' } + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return sysTimersPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + jobStatusDictTypeDropDown: [] + } + }, + created () { + this.sysDictTypeDropDown()// 注释掉 + if (this.hasPerm('sysTimers:edit') || this.hasPerm('sysTimers:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + /** + * 获取字典数据 + */ + sysDictTypeDropDown () { + this.jobStatusDictTypeDropDown = this.$options.filters['dictData']('run_status') + }, + // jobStatusFilter (jobStatus) { + // // eslint-disable-next-line eqeqeq + // const values = this.jobStatusDictTypeDropDown.filter(item => item.code == jobStatus) + // if (values.length > 0) { + // return values[0].value + // } + // }, + /** + * 启动停止 + */ + editjobStatusStatus (code, record) { + // eslint-disable-next-line eqeqeq + if (code == 1) { + sysTimersStop({ id: record.id }).then(res => { + if (res.success) { + this.$message.success('停止成功') + this.$refs.table.refresh() + } else { + this.$message.error('停止失败:' + res.message) + } + }) + // eslint-disable-next-line eqeqeq + } else if (code == 2) { + sysTimersStart({ id: record.id }).then(res => { + if (res.success) { + this.$message.success('启动成功') + this.$refs.table.refresh() + } else { + this.$message.error('启动失败:' + res.message) + } + }) + } + }, + sysTimersDelete (record) { + sysTimersDelete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + handleOk () { + this.$refs.table.refresh() + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/user/addForm.vue b/_web/src/views/system/user/addForm.vue new file mode 100644 index 0000000..632a877 --- /dev/null +++ b/_web/src/views/system/user/addForm.vue @@ -0,0 +1,468 @@ +<template> + <a-modal + title="用户增加" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-divider orientation="left">基本信息</a-divider> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="账号" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入账号" v-decorator="['account', {rules: [{required: true, min: 5, message: '请输入至少五个字符的账号!'}]}]" /> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24" > + <a-form :form="form"> + <a-form-item + label="姓名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入姓名" v-decorator="['name', {rules: [{required: true, message: '请输入姓名!'}]}]" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="密码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input + placeholder="请输入密码" + type="password" + v-decorator="['password', {rules: [{required: true, message: '请输入密码!'},{ + validator: validateToNextPassword, + },]}]" /> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="重复密码" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input + placeholder="请再次输入密码" + type="password" + v-decorator="['confirm', {rules: [{required: true, message: '请再次输入密码!'}, + { + validator: compareToFirstPassword, + }]}]" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="昵称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入昵称" v-decorator="['nickName']" /> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="生日" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker placeholder="请选择生日" @change="onChange" style="width: 100%" v-decorator="['birthday']" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="性别" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-radio-group v-decorator="['sex',{rules: [{ required: true, message: '请选择性别!' }]}]" > + <a-radio :value="1">男</a-radio> + <a-radio :value="2">女</a-radio> + </a-radio-group> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="邮箱" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入邮箱" v-decorator="['email']" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="手机号" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入手机号" v-decorator="['phone',{rules: [{ required: true, message: '请输入手机号!' }]}]" /> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="电话" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入电话" v-decorator="['tel']" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-divider orientation="left">员工信息</a-divider> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="机构" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-tree-select + v-decorator="['sysEmpParam.orgId', {rules: [{ required: true, message: '请选择机构!' }]}]" + style="width: 100%" + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + :treeData="orgTree" + placeholder="请选择机构" + treeDefaultExpandAll + @change="e => initrOrgName(e)" + > + <span slot="title" slot-scope="{ id }">{{ id }}</span> + </a-tree-select> + </a-form-item> + <a-form-item v-show="false"> + <a-input v-decorator="['sysEmpParam.orgName']" /> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="工号" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入工号" v-decorator="['sysEmpParam.jobNum']" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="24" :sm="24"> + <a-form :form="form"> + <a-form-item + label="职位信息" + :labelCol="labelCol_JG" + :wrapperCol="wrapperCol_JG" + has-feedback + > + <a-select + mode="multiple" + style="width: 100%" + placeholder="请选择职位信息" + v-decorator="['sysEmpParam.posIdList', {rules: [{ required: true, message: '请选择职位信息!' }]}]" + > + <a-select-option v-for="(item,index) in posList" :key="index" :value="item.id">{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="24" :sm="24"> + <a-form-item + label="附属信息:" + :labelCol="labelCol_JG" + :wrapperCol="wrapperCol_JG" + > + <a-table + size="middle" + :columns="columns" + :dataSource="data" + :pagination="false" + :loading="memberLoading" + > + <template v-for="(col,index) in ['extOrgId','extPosId']" :slot="col" slot-scope="text, record"> + <template v-if="index == 0" > + <a-tree-select + :key="col" + v-if="record.editable" + :treeData="orgTree" + style="width: 100%" + placeholder="请选择附属机构" + treeDefaultExpandAll + @change="e => handleChange(e,record.key,col)" + > + <span slot="title" slot-scope="{ id }">{{ id }} + </span> + </a-tree-select> + <template v-else>{{ record.extOrgName }}</template> + </template> + <template v-if="index == 1"> + <a-select + :key="col" + v-if="record.editable" + style="width: 100%" + placeholder="请选择附属职位" + @change="e => handleChange(e,record.key,col)" + has-feedback + > + <a-select-option v-for="(item,indexs) in posList" :key="indexs" :value="item.id">{{ item.name }}</a-select-option> + </a-select> + <template v-else>{{ record.extPosName }}</template> + </template> + </template> + <template slot="operation" slot-scope="text, record"> + <a @click="remove(record.key)">删除</a> + </template> + </a-table> + <a-button style="width: 100%; margin-top: 16px; margin-bottom: 8px" type="dashed" icon="plus" @click="newMember">增行</a-button> + </a-form-item> + </a-col> + </a-row> + </a-spin> + </a-modal> +</template> + +<script> + import { sysUserAdd } from '@/api/modular/system/userManage' + import { getOrgTree, getOrgList } from '@/api/modular/system/orgManage' + import { sysPosList } from '@/api/modular/system/posManage' + import moment from 'moment' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 6 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 16 } + }, + // 机构行样式 + labelCol_JG: { + xs: { span: 24 }, + sm: { span: 3 } + }, + wrapperCol_JG: { + xs: { span: 24 }, + sm: { span: 20 } + }, + count: 1, + columns: [ + { + title: '附属机构', + dataIndex: 'extOrgId', + width: '45%', + scopedSlots: { customRender: 'extOrgId' } + }, + { + title: '附属岗位', + dataIndex: 'extPosId', + width: '45%', + scopedSlots: { customRender: 'extPosId' } + }, + { + title: '操作', + key: 'action', + scopedSlots: { customRender: 'operation' } + } + ], + visible: false, + confirmLoading: false, + orgTree: [], + orgList: [], + posList: [], + sysEmpParamExtList: [], + memberLoading: false, + form: this.$form.createForm(this), + data: [], + birthdayString: [] + } + }, + methods: { + // 初始化方法 + add () { + this.visible = true + this.getOrgDate() + this.getPosList() + }, + /** + * 增行 + */ + newMember () { + const length = this.data.length + this.data.push({ + key: length === 0 ? '1' : (parseInt(this.data[length - 1].key) + 1).toString(), + extOrgId: '', + extPosId: '', + editable: true, + isNew: true + }) + }, + /** + * 删除 + */ + remove (key) { + const newData = this.data.filter(item => item.key !== key) + this.data = newData + }, + /** + * 选择子表单单项触发 + */ + handleChange (value, key, column) { + const newData = [...this.data] + const target = newData.find(item => key === item.key) + if (target) { + target[column] = value + this.data = newData + } + }, + /** + * 获取机构树,并加载于表单中 + */ + getOrgDate () { + getOrgTree().then((res) => { + this.orgTree = res.data + }) + getOrgList().then((res) => { + this.orgList = res.data + }) + }, + /** + * 获取职位list列表 + */ + getPosList () { + sysPosList().then((res) => { + this.posList = res.data + }) + }, + compareToFirstPassword (rule, value, callback) { + const form = this.form + if (value && value !== form.getFieldValue('password')) { + // eslint-disable-next-line standard/no-callback-literal + callback('请确认两次输入密码的一致性!') + } else { + callback() + } + }, + validateToNextPassword (rule, value, callback) { + const form = this.form + if (value && this.confirmDirty) { + form.validateFields(['confirm'], { force: true }) + } + callback() + }, + /** + * 选择树机构,初始化机构名称于表单中 + */ + initrOrgName (value) { + this.form.getFieldDecorator('sysEmpParam.orgName', { initialValue: this.orgList.find(item => value === item.id).name }) + }, + /** + * 子表单json重构 + */ + JsonReconsitution () { + this.sysEmpParamExtList = [] + const newData = [...this.data] + newData.forEach(item => { + // eslint-disable-next-line eqeqeq + if (item.extOrgId != '' & item.extPosId != '') { + this.sysEmpParamExtList.push({ orgId: item.extOrgId, posId: item.extPosId }) + } + }) + }, + /** + * 日期需单独转换 + */ + onChange (date, dateString) { + if (date == null) { + this.birthdayString = [] + } else { + this.birthdayString = moment(date).format('YYYY-MM-DD') + } + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + this.JsonReconsitution() + values.sysEmpParam['extIds'] = this.sysEmpParamExtList + if (this.birthdayString.length > 0) { + values.birthday = this.birthdayString + } + sysUserAdd(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.confirmLoading = false + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('新增失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + // 清理子表单中数据 + this.data = [] + // 清理时间 + this.birthdayString = [] + } + } + } +</script> diff --git a/_web/src/views/system/user/editForm.vue b/_web/src/views/system/user/editForm.vue new file mode 100644 index 0000000..4f20db9 --- /dev/null +++ b/_web/src/views/system/user/editForm.vue @@ -0,0 +1,494 @@ +/* eslint-disable vue/no-template-shadow */ +<template> + <a-modal + title="编辑用户" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-divider orientation="left">基本信息</a-divider> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + style="display: none;" + > + <a-input v-decorator="['id']" /> + </a-form-item> + <a-form-item + label="账号" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入账号" v-decorator="['account', {rules: [{required: true, min: 5, message: '请输入至少五个字符的账号!'}]}]" /> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24" > + <a-form :form="form"> + <a-form-item + label="姓名" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入姓名" v-decorator="['name', {rules: [{required: true, message: '请输入姓名!'}]}]" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="昵称" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入昵称" v-decorator="['nickName']" /> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="生日" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-date-picker placeholder="请选择生日" @change="onChange" style="width: 100%" v-decorator="['birthday']" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="性别" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + <a-radio-group v-decorator="['sex',{rules: [{ required: true, message: '请选择性别!' }]}]" > + <a-radio :value="1">男</a-radio> + <a-radio :value="2">女</a-radio> + </a-radio-group> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="邮箱" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入邮箱" v-decorator="['email']" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="手机号" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入手机号" v-decorator="['phone',{rules: [{ required: true, message: '请输入手机号!' }]}]" /> + </a-form-item> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="电话" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入电话" v-decorator="['tel']" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-divider orientation="left">员工信息</a-divider> + <a-row :gutter="24"> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="机构" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-tree-select + v-decorator="['sysEmpParam.orgId', {rules: [{ required: true, message: '请选择机构!' }]}]" + style="width: 100%" + :dropdownStyle="{ maxHeight: '300px', overflow: 'auto' }" + :treeData="orgTree" + placeholder="请选择机构" + treeDefaultExpandAll + @change="e => initrOrgName(e)" + > + <span slot="title" slot-scope="{ id }">{{ id }}</span> + </a-tree-select> + </a-form-item> + <a-form :form="form"> + <a-form-item v-show="false"> + <a-input v-decorator="['sysEmpParam.orgName']" /> + </a-form-item> + </a-form> + </a-form> + </a-col> + <a-col :md="12" :sm="24"> + <a-form :form="form"> + <a-form-item + label="工号" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > + <a-input placeholder="请输入工号" v-decorator="['sysEmpParam.jobNum']" /> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="24" :sm="24"> + <a-form :form="form"> + <a-form-item + label="职位信息" + :labelCol="labelCol_JG" + :wrapperCol="wrapperCol_JG" + has-feedback + > + <a-select + mode="multiple" + style="width: 100%" + placeholder="请选择职位信息" + v-decorator="['sysEmpParam.posIdList', {rules: [{ required: true, message: '请选择职位信息!' }]}]" + > + <a-select-option v-for="(item,index) in posList" :key="index" :value="item.id">{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-form> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :md="24" :sm="24"> + <a-form-item + label="附属信息:" + :labelCol="labelCol_JG" + :wrapperCol="wrapperCol_JG" + > + <a-table + size="middle" + :columns="columns" + :dataSource="data" + :pagination="false" + :loading="memberLoading" + > + <template v-for="(col,index) in ['extOrgId','extPosId']" :slot="col" slot-scope="text, record"> + <template v-if="index == 0" > + <template v-if="record.extOrgId !=''"> + <a-tree-select + :key="col" + :treeData="orgTree" + style="width: 100%" + placeholder="请选择附属机构" + :defaultValue="record.extOrgId" + treeDefaultExpandAll + @change="e => handleChange(e,record.key,col)" + > + <span slot="title" slot-scope="{ id }">{{ id }}</span> + </a-tree-select> + </template> + <template v-else> + <a-tree-select + :key="col" + :treeData="orgTree" + style="width: 100%" + placeholder="请选择附属机构" + treeDefaultExpandAll + @change="e => handleChange(e,record.key,col)" + > + <span slot="title" slot-scope="{ id }">{{ id }}</span> + </a-tree-select> + </template> + </template> + <template v-if="index == 1"> + <template v-if="record.extOrgId !=''"> + <a-select + :key="col" + style="width: 100%" + placeholder="请选择附属职位" + :default-value="record.extPosId" + @change="e => handleChange(e,record.key,col)" + has-feedback + > + // eslint-disable-next-line vue/no-template-shadow + <a-select-option v-for="(item,indexs) in posList" :key="indexs" :value="item.id">{{ item.name }}</a-select-option> + </a-select> + </template> + <template v-else> + <a-select + :key="col" + style="width: 100%" + placeholder="请选择附属职位" + @change="e => handleChange(e,record.key,col)" + has-feedback + > + // eslint-disable-next-line vue/no-template-shadow + <a-select-option v-for="(item,indexs) in posList" :key="indexs" :value="item.id">{{ item.name }}</a-select-option> + </a-select> + </template> + </template> + </template> + <template slot="operation" slot-scope="text, record"> + <a @click="remove(record.key)">删除</a> + </template> + </a-table> + <a-button style="width: 100%; margin-top: 16px; margin-bottom: 8px" type="dashed" icon="plus" @click="newMember">增行</a-button> + </a-form-item> + </a-col> + </a-row> + </a-spin> + </a-modal> +</template> +<script> + import { sysUserEdit, sysUserDetail } from '@/api/modular/system/userManage' + import { getOrgTree, getOrgList } from '@/api/modular/system/orgManage' + import { sysPosList } from '@/api/modular/system/posManage' + import moment from 'moment' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 6 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 16 } + }, + // 机构行样式 + labelCol_JG: { + xs: { span: 24 }, + sm: { span: 3 } + }, + wrapperCol_JG: { + xs: { span: 24 }, + sm: { span: 20 } + }, + count: 1, + columns: [ + { + title: '附属机构', + dataIndex: 'extOrgId', + width: '45%', + scopedSlots: { customRender: 'extOrgId' } + }, + { + title: '附属岗位', + dataIndex: 'extPosId', + width: '45%', + scopedSlots: { customRender: 'extPosId' } + }, + { + title: '操作', + key: 'action', + scopedSlots: { customRender: 'operation' } + } + ], + visible: false, + confirmLoading: false, + orgTree: [], + orgList: [], + posList: [], + sysEmpParamExtList: [], + memberLoading: false, + form: this.$form.createForm(this), + data: [], + birthdayString: '' + } + }, + methods: { + // 初始化方法 + edit (record) { + this.confirmLoading = true + this.visible = true + this.getOrgData() + this.getPosList() + // 基本信息加人表单 + setTimeout(() => { + this.form.setFieldsValue( + { + id: record.id, + account: record.account, + name: record.name, + nickName: record.nickName, + sex: record.sex, + email: record.email, + phone: record.phone, + tel: record.tel + } + ) + }, 100) + // 时间单独处理 + if (record.birthday != null) { + this.form.getFieldDecorator('birthday', { initialValue: moment(record.birthday, 'YYYY-MM-DD') }) + } + this.birthdayString = moment(record.birthday).format('YYYY-MM-DD') + // 职位信息加入表单 + this.getUserDetaile(record.id) + }, + /** + * 通过用户ID查询出用户详情,将职位信息填充 + * @param id + */ + getUserDetaile (id) { + sysUserDetail({ 'id': id }).then((res) => { + const SysEmpInfo = res.data.sysEmpInfo + const Positions = [] + SysEmpInfo.positions.forEach(item => { + Positions.push(item.posId) + }) + this.form.getFieldDecorator('sysEmpParam.orgName', { initialValue: SysEmpInfo.orgName }) + this.form.getFieldDecorator('sysEmpParam.posIdList', { initialValue: Positions }) + this.form.getFieldDecorator('sysEmpParam.jobNum', { initialValue: SysEmpInfo.jobNum }) + this.form.getFieldDecorator('sysEmpParam.orgId', { initialValue: SysEmpInfo.orgId }) + SysEmpInfo.extOrgPos.forEach(item => { + const length = this.data.length + this.data.push({ + key: length === 0 ? '1' : (parseInt(this.data[length - 1].key) + 1).toString(), + extOrgId: item.orgId, + extPosId: item.posId + }) + }) + this.confirmLoading = false + }) + }, + /** + * 增行 + */ + newMember () { + const length = this.data.length + this.data.push({ + key: length === 0 ? '1' : (parseInt(this.data[length - 1].key) + 1).toString(), + extOrgId: '', + extPosId: '' + }) + }, + /** + * 删除 + */ + remove (key) { + const newData = this.data.filter(item => item.key !== key) + this.data = newData + }, + /** + * 选择子表单单项触发 + */ + handleChange (value, key, column) { + const newData = [...this.data] + const target = newData.find(item => key === item.key) + if (target) { + target[column] = value + this.data = newData + } + }, + /** + * 获取机构数据,并加载于表单中 + */ + getOrgData () { + getOrgTree().then((res) => { + this.orgTree = res.data + }) + getOrgList().then((res) => { + this.orgList = res.data + }) + }, + /** + * 获取职位list列表 + */ + getPosList () { + sysPosList().then((res) => { + this.posList = res.data + }) + }, + /** + * 选择树机构,初始化机构名称于表单中 + */ + initrOrgName (value) { + this.form.getFieldDecorator('sysEmpParam.orgName', { initialValue: this.orgList.find(item => value === item.id).name }) + }, + /** + * 子表单json重构 + */ + JsonReconsitution () { + this.sysEmpParamExtList = [] + const newData = [...this.data] + newData.forEach(item => { + // eslint-disable-next-line eqeqeq + if (item.extOrgId != '' & item.extPosId != '') { + this.sysEmpParamExtList.push({ orgId: item.extOrgId, posId: item.extPosId }) + } + }) + }, + /** + * 日期需单独转换 + */ + onChange (date, dateString) { + this.birthdayString = moment(date).format('YYYY-MM-DD') + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + this.JsonReconsitution() + values.sysEmpParam['extIds'] = this.sysEmpParamExtList + // eslint-disable-next-line eqeqeq + if (this.birthdayString == 'Invalid date') { + this.birthdayString = null + } + values.birthday = this.birthdayString + sysUserEdit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.confirmLoading = false + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('编辑失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + this.visible = false + // 清理子表单中数据 + this.data = [] + // 清理时间 + this.birthdayString = '' + this.form.getFieldDecorator('birthday', { initialValue: null }) + } + } + } +</script> diff --git a/_web/src/views/system/user/index.vue b/_web/src/views/system/user/index.vue new file mode 100644 index 0000000..7990969 --- /dev/null +++ b/_web/src/views/system/user/index.vue @@ -0,0 +1,336 @@ +<template> + <a-row :gutter="24"> + <a-col :md="5" :sm="24"> + <a-card :bordered="false" :loading="treeLoading"> + <div v-if="this.orgTree != ''"> + <a-tree + :treeData="orgTree" + v-if="orgTree.length" + @select="handleClick" + :defaultExpandAll="true" + :defaultExpandedKeys="defaultExpandedKeys" + :replaceFields="replaceFields" /> + </div> + <div v-else> + <a-empty :image="simpleImage" /> + </div> + </a-card> + </a-col> + <a-col :md="19" :sm="24"> + <x-card v-if="hasPerm('sysUser:page')"> + <div slot="content" class="table-page-search-wrapper"> + <a-form layout="inline"> + <a-row :gutter="48"> + <a-col :md="8" :sm="24"> + <a-form-item label="关键词" > + <a-input v-model="queryParam.searchValue" allow-clear placeholder="请输入姓名或账号"/> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-form-item label="状态"> + <a-select v-model="queryParam.searchStatus" allow-clear placeholder="请选择状态" default-value="0"> + <a-select-option v-for="(item,index) in statusDictTypeDropDown" :key="index" :value="item.code" >{{ item.value }}</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col :md="8" :sm="24"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </a-col> + </a-row> + </a-form> + </div> + </x-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="options.alert" + :rowKey="(record) => record.id" + :rowSelection="options.rowSelection" + > + <template slot="operator"> + <a-button type="primary" v-if="hasPerm('sysUser:add')" icon="plus" @click="$refs.addForm.add()">新增用户</a-button> + <a-button type="danger" :disabled="selectedRowKeys.length < 1" v-if="hasPerm('sysUser:delete')" @click="batchDelete"><a-icon type="delete"/>批量删除</a-button> + <x-down + v-if="hasPerm('sysUser:export')" + ref="batchExport" + @batchExport="batchExport" + /> + </template> + <span slot="sex" slot-scope="text"> + {{ sexFilter(text) }} + </span> + <span slot="status" slot-scope="text,record" v-if="hasPerm('sysUser:changeStatus')"> + <a-popconfirm placement="top" :title="text===0? '确定停用该用户?':'确定启用该用户?'" @confirm="() => editUserStatus(text,record)"> + <a>{{ statusFilter(text) }}</a> + </a-popconfirm> + </span> + <span slot="status" v-else> + {{ statusFilter(text) }} + </span> + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('sysUser:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('sysUser:edit')" /> + <a-dropdown v-if="hasPerm('sysUser:resetPwd') || hasPerm('sysUser:grantRole') || hasPerm('sysUser:grantData') || hasPerm('sysUser:delete')"> + <a class="ant-dropdown-link"> + 更多 <a-icon type="down" /> + </a> + <a-menu slot="overlay"> + <a-menu-item v-if="hasPerm('sysUser:resetPwd')"> + <a-popconfirm placement="topRight" title="确认重置密码?" @confirm="() => resetPwd(record)"> + <a>重置密码</a> + </a-popconfirm> + </a-menu-item> + <a-menu-item v-if="hasPerm('sysUser:grantRole')"> + <a @click="$refs.userRoleForm.userRole(record)">授权角色</a> + </a-menu-item> + <a-menu-item v-if="hasPerm('sysUser:grantData')"> + <a @click="$refs.userOrgForm.userOrg(record)">授权数据</a> + </a-menu-item> + <a-menu-item v-if="hasPerm('sysUser:delete')"> + <a-popconfirm placement="topRight" title="确认删除?" @confirm="() => singleDelete(record)"> + <a>删除</a> + </a-popconfirm> + </a-menu-item> + </a-menu> + </a-dropdown> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + <user-role-form ref="userRoleForm" @ok="handleOk"/> + <user-org-form ref="userOrgForm" @ok="handleOk"/> + </a-card> + </a-col> + </a-row> +</template> +<script> + import { STable, XCard, XDown } from '@/components' + import { Empty } from 'ant-design-vue' + import { getOrgTree } from '@/api/modular/system/orgManage' + import { getUserPage, sysUserDelete, sysUserChangeStatus, sysUserResetPwd, sysUserExport } from '@/api/modular/system/userManage' + import { sysDictTypeDropDown } from '@/api/modular/system/dictManage' + import addForm from './addForm' + import editForm from './editForm' + import userRoleForm from './userRoleForm' + import userOrgForm from './userOrgForm' + export default { + components: { + XDown, + XCard, + STable, + addForm, + editForm, + userRoleForm, + userOrgForm + }, + data () { + return { + // 查询参数 + queryParam: {}, + // 表头 + columns: [ + { + title: '账号', + dataIndex: 'account' + }, + { + title: '姓名', + dataIndex: 'name' + }, + { + title: '性别', + dataIndex: 'sex', + scopedSlots: { customRender: 'sex' } + }, { + title: '手机', + dataIndex: 'phone' + }, + { + title: '状态', + dataIndex: 'status', + scopedSlots: { customRender: 'status' } + } + ], + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { + return getUserPage(Object.assign(parameter, this.queryParam)).then((res) => { + return res.data + }) + }, + orgTree: [], + selectedRowKeys: [], + selectedRows: [], + defaultExpandedKeys: [], + sexDictTypeDropDown: [], + statusDictTypeDropDown: [], + treeLoading: true, + simpleImage: Empty.PRESENTED_IMAGE_SIMPLE, + replaceFields: { + key: 'id' + }, + options: { + alert: { show: true, clear: () => { this.selectedRowKeys = [] } }, + rowSelection: { + selectedRowKeys: this.selectedRowKeys, + onChange: this.onSelectChange + } + } + } + }, + created () { + /** + * 获取到机构树,展开顶级下树节点,考虑到后期数据量变大,不建议全部展开 + */ + getOrgTree(Object.assign(this.queryParam)).then(res => { + this.treeLoading = false + if (!res.success) { + return + } + this.orgTree = res.data + for (var item of res.data) { + // eslint-disable-next-line eqeqeq + if (item.parentId == 0) { + this.defaultExpandedKeys.push(item.id) + } + } + }) + this.sysDictTypeDropDown() + if (this.hasPerm('sysUser:edit') || this.hasPerm('sysUser:resetPwd') || this.hasPerm('sysUser:grantRole') || this.hasPerm('sysUser:grantData') || this.hasPerm('sysUser:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } + }, + methods: { + sexFilter (sex) { + // eslint-disable-next-line eqeqeq + const values = this.sexDictTypeDropDown.filter(item => item.code == sex) + if (values.length > 0) { + return values[0].value + } + }, + statusFilter (status) { + // eslint-disable-next-line eqeqeq + const values = this.statusDictTypeDropDown.filter(item => item.code == status) + if (values.length > 0) { + return values[0].value + } + }, + /** + * 获取字典数据 + */ + sysDictTypeDropDown (text) { + sysDictTypeDropDown({ code: 'sex' }).then((res) => { + this.sexDictTypeDropDown = res.data + }) + sysDictTypeDropDown({ code: 'common_status' }).then((res) => { + this.statusDictTypeDropDown = res.data + }) + }, + /** + * 修改用户状态 + */ + editUserStatus (code, record) { + // eslint-disable-next-line no-unused-vars + const status = 0 + // eslint-disable-next-line eqeqeq + if (code == 0) { + this.status = 1 + // eslint-disable-next-line eqeqeq + } else if (code == 1) { + this.status = 0 + } + sysUserChangeStatus({ id: record.id, status: this.status }).then(res => { + if (res.success) { + this.$message.success('操作成功') + this.$refs.table.refresh() + } else { + this.$message.error('操作失败:' + res.message) + } + }) + }, + /** + * 重置密码 + */ + resetPwd (record) { + sysUserResetPwd({ id: record.id }).then(res => { + if (res.success) { + this.$message.success('重置成功') + // this.$refs.table.refresh() + } else { + this.$message.error('重置失败:' + res.message) + } + }) + }, + /** + * 单个删除 + */ + singleDelete (record) { + const param = [{ 'id': record.id }] + this.sysUserDelete(param) + }, + /** + * 批量删除 + */ + batchDelete () { + const paramIds = this.selectedRowKeys.map((d) => { + return { 'id': d } + }) + this.sysUserDelete(paramIds) + }, + /** + * 删除用户 + */ + sysUserDelete (param) { + sysUserDelete(param).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.refresh() + } else { + this.$message.error('删除失败:' + res.message) + } + }).catch((err) => { + this.$message.error('删除错误:' + err.message) + }) + }, + /** + * 批量导出 + */ + batchExport () { + sysUserExport().then((res) => { + this.$refs.batchExport.downloadfile(res) + }) + }, + /** + * 点击左侧机构树查询列表 + */ + handleClick (e) { + this.queryParam = { + 'sysEmpParam.orgId': e.toString() + } + this.$refs.table.refresh(true) + }, + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/_web/src/views/system/user/userOrgForm.vue b/_web/src/views/system/user/userOrgForm.vue new file mode 100644 index 0000000..8c839dc --- /dev/null +++ b/_web/src/views/system/user/userOrgForm.vue @@ -0,0 +1,150 @@ +<template> + <a-modal + title="授权数据" + :width="600" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="formLoading"> + <a-form :form="form"> + <a-form-item + label="选择机构" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > + + <a-tree + v-model="checkedKeys" + checkable + :auto-expand-parent="autoExpandParent" + :expanded-keys="expandedKeys" + :tree-data="orgTreeData" + :selected-keys="selectedKeys" + :replaceFields="replaceFields" + @expand="onExpand" + @select="onSelect" + /> + </a-form-item> + + </a-form> + </a-spin> + </a-modal> +</template> + +<script> + import { getOrgTree } from '@/api/modular/system/orgManage' + import { sysUserOwnData, sysUserGrantData } from '@/api/modular/system/userManage' + + export default { + data () { + return { + labelCol: { + style: { 'padding-right': '20px' }, + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, + orgTreeData: [], + expandedKeys: [], + checkedKeys: [], + visible: false, + confirmLoading: false, + formLoading: true, + autoExpandParent: true, + selectedKeys: [], + userEntity: [], + replaceFields: { + key: 'id' + }, + form: this.$form.createForm(this) + } + }, + + methods: { + // 初始化方法 + userOrg (record) { + this.userEntity = record + this.visible = true + // 获取机构树 + this.getOrgTree() + // 已关联数据 + this.sysUserOwnData(this.userEntity) + }, + + /** + * 获取机构树 + */ + getOrgTree () { + this.formLoading = true + getOrgTree().then((res) => { + if (res.success) { + this.orgTreeData = res.data + // 默认展开 + this.orgTreeData.forEach(item => { + this.expandedKeys.push(item.id) + }) + } + }) + }, + + /** + * 此用户已有数据列表 + */ + sysUserOwnData (record) { + sysUserOwnData({ id: record.id }).then((res) => { + if (res.success) { + this.checkedKeys = res.data + } + this.formLoading = false + }) + }, + + onExpand (expandedKeys) { + this.expandedKeys = expandedKeys + this.autoExpandParent = false + }, + onCheck (checkedKeys) { + this.checkedKeys = checkedKeys + }, + onSelect (selectedKeys, info) { + this.selectedKeys = selectedKeys + }, + + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + sysUserGrantData({ id: this.userEntity.id, grantOrgIdList: this.checkedKeys }).then((res) => { + if (res.success) { + this.$message.success('授权成功') + this.confirmLoading = false + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('授权失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, + handleCancel () { + this.form.resetFields() + // 清空已选择的 + this.checkedKeys = [] + // 清空已展开的 + this.expandedKeys = [] + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/system/user/userRoleForm.vue b/_web/src/views/system/user/userRoleForm.vue new file mode 100644 index 0000000..c50ba3f --- /dev/null +++ b/_web/src/views/system/user/userRoleForm.vue @@ -0,0 +1,117 @@ +<template> + <a-modal + title="授权角色" + :width="800" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + + <a-card :bordered="false"> + + <div> + <a-table + size="middle" + :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }" + :columns="columns" + :dataSource="loadData" + :pagination="false" + :loading="loading" + :rowKey="(record) => record.id" + /> + </div> + + </a-card> + + </a-modal> +</template> + +<script> + import { getRolePage } from '@/api/modular/system/roleManage' + import { sysUserOwnRole, sysUserGrantRole } from '@/api/modular/system/userManage' + + const columns = [ + { + title: '角色名称', + dataIndex: 'name' + }, + { + title: '唯一编码', + dataIndex: 'code' + } + ] + + export default { + name: 'UserRoleIndex', + + data () { + return { + columns, + loadData: [], + selectedRowKeys: [], // Check here to configure the default column + loading: true, + visible: false, + confirmLoading: false, + recordEntity: [] + } + }, + computed: { + hasSelected () { + return this.selectedRowKeys.length > 0 + } + }, + methods: { + // 初始化方法 + userRole (record) { + this.recordEntity = record + this.visible = true + // 加载已有数据 + this.sysUserOwnRole() + // 获取全部列表,无需分页 + getRolePage().then((res) => { + this.loadData = res.data.rows + }) + }, + + /** + * 获取用户已有角色 + */ + sysUserOwnRole () { + this.loading = true + sysUserOwnRole({ id: this.recordEntity.id }).then((res) => { + // 选中多选框 + this.selectedRowKeys = res.data + this.loading = false + }) + }, + + onSelectChange (selectedRowKeys) { + this.selectedRowKeys = selectedRowKeys + }, + + handleSubmit () { + // eslint-disable-next-line no-unused-expressions + this.confirmLoading = false + this.visible = false + sysUserGrantRole({ id: this.recordEntity.id, grantRoleIdList: this.selectedRowKeys }).then((res) => { + if (res.success) { + this.$message.success('授权成功') + this.confirmLoading = false + this.$emit('ok', this.recordEntity) + this.handleCancel() + } else { + this.$message.error('授权失败:' + res.message) + } + }).finally((res) => { + this.confirmLoading = false + }) + }, + handleCancel () { + this.recordEntity = [] + this.selectedRowKeys = [] + this.visible = false + } + } + } +</script> diff --git a/_web/src/views/userLoginReg/Login.vue b/_web/src/views/userLoginReg/Login.vue new file mode 100644 index 0000000..71781e4 --- /dev/null +++ b/_web/src/views/userLoginReg/Login.vue @@ -0,0 +1,367 @@ +<template> + <div class="main"> + <a-form + id="formLogin" + class="user-layout-login" + ref="formLogin" + :form="form" + @submit="handleSubmit" + > + <a-tabs + :activeKey="customActiveKey" + :tabBarStyle="{ textAlign: 'center', borderBottom: 'unset' }" + @change="handleTabClick" + > + <a-tab-pane key="tab1" tab="账号密码登录"> + <a-alert v-if="isLoginError" type="error" showIcon style="margin-bottom: 24px;" :message="this.accountLoginErrMsg" /> + + <a-form-item> + <a-input + size="large" + type="text" + placeholder="账号" + v-decorator="[ + 'account', + { initialValue:'superAdmin', rules: [{ required: true, message: '请输入帐户名' }, { validator: handleUsernameOrEmail }], validateTrigger: 'change'} + ]" + > + <a-icon slot="prefix" type="user" :style="{ color: 'rgba(0,0,0,.25)' }"/> + </a-input> + </a-form-item> + + <a-form-item> + <a-input + size="large" + type="password" + autocomplete="false" + placeholder="密码" + v-decorator="[ + 'password', + { initialValue:'123456', rules: [{ required: true, message: '请输入密码' }], validateTrigger: 'blur'} + ]" + > + <a-icon slot="prefix" type="lock" :style="{ color: 'rgba(0,0,0,.25)' }"/> + </a-input> + </a-form-item> + </a-tab-pane> + <a-tab-pane key="tab2" tab="手机号登录"> + <a-alert v-if="isLoginError" type="error" showIcon style="margin-bottom: 24px;" :message="this.accountLoginErrMsg" /> + <a-form-item> + <a-input size="large" type="text" placeholder="手机号" v-decorator="['mobile', {rules: [{ required: true, pattern: /^1[34578]\d{9}$/, message: '请输入正确的手机号' }], validateTrigger: 'change'}]"> + <a-icon slot="prefix" type="mobile" :style="{ color: 'rgba(0,0,0,.25)' }"/> + </a-input> + </a-form-item> + + <a-row :gutter="16"> + <a-col class="gutter-row" :span="16"> + <a-form-item> + <a-input size="large" type="text" placeholder="验证码" v-decorator="['captcha', {rules: [{ required: true, message: '请输入验证码' }], validateTrigger: 'blur'}]"> + <a-icon slot="prefix" type="mail" :style="{ color: 'rgba(0,0,0,.25)' }"/> + </a-input> + </a-form-item> + </a-col> + <a-col class="gutter-row" :span="8"> + <a-button + class="getCaptcha" + tabindex="-1" + :disabled="state.smsSendBtn" + @click.stop.prevent="getCaptcha" + v-text="!state.smsSendBtn && '获取验证码' || (state.time+' s')" + ></a-button> + </a-col> + </a-row> + </a-tab-pane> + </a-tabs> + + <a-form-item> + <a-checkbox v-decorator="['rememberMe', { valuePropName: 'checked' }]">自动登录</a-checkbox> + <router-link + :to="{ name: 'recover', params: { user: 'aaa'} }" + class="forge-password" + style="float: right;" + >忘记密码</router-link> + </a-form-item> + + <a-form-item> + <Verify + @success="verifySuccess" + :mode="'pop'" + :captchaType="captchaType" + :imgSize="{ width: '330px', height: '155px' }" + ref="verify" + ></Verify> + </a-form-item> + + <a-form-item style="margin-top:24px"> + <a-button + size="large" + type="primary" + htmlType="submit" + class="login-button" + :loading="state.loginBtn" + :disabled="state.loginBtn" + >确定</a-button> + </a-form-item> + + <div class="user-login-other"> + <span>其他登录方式</span> + <a> + <a-icon class="item-icon" type="alipay-circle"></a-icon> + </a> + <a> + <a-icon class="item-icon" type="taobao-circle"></a-icon> + </a> + <a> + <a-icon class="item-icon" type="weibo-circle"></a-icon> + </a> + <router-link class="register" :to="{ name: 'register' }">注册账户</router-link> + </div> + </a-form> + + <two-step-captcha + v-if="requiredTwoStepCaptcha" + :visible="stepCaptchaVisible" + @success="stepCaptchaSuccess" + @cancel="stepCaptchaCancel" + ></two-step-captcha> + </div> +</template> + +<script> +import TwoStepCaptcha from '@/components/tools/TwoStepCaptcha' +import { mapActions } from 'vuex' +import { getSmsCaptcha, getCaptchaOpen } from '@/api/modular/system/loginManage' +import Verify from '@/components/verifition/Verify' + +export default { + components: { + TwoStepCaptcha, + Verify + }, + data () { + var captchaTypeValue = 'clickWord' + var min = 0 + var max = 100 + var random = Math.floor(Math.random() * (max - min)) + min + + if (random % 2 === 0) { + captchaTypeValue = 'blockPuzzle' + } + if (random % 2 === 1) { + captchaTypeValue = 'clickWord' + } + return { + customActiveKey: 'tab1', + loginBtn: false, + // login type: 0 email, 1 username, 2 telephone + loginType: 0, + isLoginError: false, + requiredTwoStepCaptcha: false, + stepCaptchaVisible: false, + form: this.$form.createForm(this), + state: { + time: 60, + loginBtn: false, + // login type: 0 email, 1 username, 2 telephone + loginType: 0, + smsSendBtn: false + }, + accountLoginErrMsg: '', + tenantOpen: false, + captchaOpen: false, // 是否开启验证码 + tenantsList: [], + loginParams: [], // 登录参数 + captchaType: captchaTypeValue + } + }, + created () { + this.getCaptchaOpen() + }, + methods: { + ...mapActions(['Login', 'Logout', 'dictTypeData']), + /** + * 获取验证码开关 + */ + getCaptchaOpen () { + getCaptchaOpen().then((res) => { + if (res.success) { + this.captchaOpen = res.data + } + }) + }, + // handler + handleUsernameOrEmail (rule, value, callback) { + const { state } = this + const regex = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/ + if (regex.test(value)) { + state.loginType = 0 + } else { + state.loginType = 1 + } + callback() + }, + handleTabClick (key) { + this.isLoginError = false + this.customActiveKey = key + // this.form.resetFields() + }, + handleSubmit (e) { + e.preventDefault() + const { + form: { validateFields }, + state, + customActiveKey, + Login + } = this + + state.loginBtn = true + const validateFieldsKey = customActiveKey === 'tab1' ? ['account', 'password'] : ['mobile', 'captcha'] + if (this.tenantOpen) { + validateFieldsKey.push('tenantCode') + } + validateFields(validateFieldsKey, { force: true }, (err, values) => { + this.loginParams = values + if (!err) { + // 是否开启验证码 + if (this.captchaOpen) { + this.$refs.verify.show() + state.loginBtn = false + return + } + const loginParams = { ...values } + delete loginParams.account + loginParams.account = values.account + + if (this.tenantOpen) { + loginParams.tenantCode = values.tenantCode + } + + Login(loginParams) + .then((res) => this.loginSuccess(res)) + .catch(err => this.requestFailed(err)) + .finally(() => { + state.loginBtn = false + }) + } else { + setTimeout(() => { + state.loginBtn = false + }, 600) + } + }) + }, + /** + * 获取验证码 + */ + verifySuccess(params) { + this.loginParams.code = params.captchaVerification + console.log(JSON.stringify(this.loginParams)) + this.Login(this.loginParams).then((res) => this.loginSuccess(res)) + .catch(err => this.requestFailed(err)) + .finally(() => { + this.state.loginBtn = false + }) + }, + getCaptcha (e) { + e.preventDefault() + const { form: { validateFields }, state } = this + + validateFields(['mobile'], { force: true }, (err, values) => { + if (!err) { + state.smsSendBtn = true + + const interval = window.setInterval(() => { + if (state.time-- <= 0) { + state.time = 60 + state.smsSendBtn = false + window.clearInterval(interval) + } + }, 1000) + + const hide = this.$message.loading('验证码发送中..', 0) + getSmsCaptcha({ mobile: values.mobile }).then(res => { + setTimeout(hide, 2500) + this.$notification['success']({ + message: '提示', + description: '验证码获取成功,您的验证码为:' + res.result.captcha, + duration: 8 + }) + }).catch(err => { + setTimeout(hide, 1) + clearInterval(interval) + state.time = 60 + state.smsSendBtn = false + this.requestFailed(err) + }) + } + }) + }, + stepCaptchaSuccess () { + this.loginSuccess() + }, + stepCaptchaCancel () { + this.Logout().then(() => { + this.loginBtn = false + this.stepCaptchaVisible = false + }) + }, + loginSuccess (res) { + this.$router.push({ path: '/' }) + this.isLoginError = false + // 加载字典所有字典到缓存中 + this.dictTypeData().then((res) => { }) + }, + requestFailed (err) { + this.accountLoginErrMsg = err + this.isLoginError = true + } + } +} +</script> + +<style lang="less" scoped> +.user-layout-login { + label { + font-size: 14px; + } + + .getCaptcha { + display: block; + width: 100%; + height: 40px; + } + + .forge-password { + font-size: 14px; + } + + button.login-button { + padding: 0 15px; + font-size: 16px; + height: 40px; + width: 100%; + } + + .user-login-other { + text-align: left; + margin-top: 24px; + line-height: 22px; + + .item-icon { + font-size: 24px; + color: rgba(0, 0, 0, 0.2); + margin-left: 16px; + vertical-align: middle; + cursor: pointer; + transition: color 0.3s; + + &:hover { + color: #1890ff; + } + } + + .register { + float: right; + } + } +} +</style> diff --git a/_web/src/views/userLoginReg/Register.vue b/_web/src/views/userLoginReg/Register.vue new file mode 100644 index 0000000..2b20264 --- /dev/null +++ b/_web/src/views/userLoginReg/Register.vue @@ -0,0 +1,322 @@ +<template> + <div class="main user-layout-register"> + <h3><span>注册</span></h3> + <a-form ref="formRegister" :form="form" id="formRegister"> + <a-form-item> + <a-input + size="large" + type="text" + placeholder="邮箱" + v-decorator="['email', {rules: [{ required: true, type: 'email', message: '请输入邮箱地址' }], validateTrigger: ['change', 'blur']}]" + ></a-input> + </a-form-item> + + <a-popover + placement="rightTop" + :trigger="['focus']" + :getPopupContainer="(trigger) => trigger.parentElement" + v-model="state.passwordLevelChecked"> + <template slot="content"> + <div :style="{ width: '240px' }" > + <div :class="['user-register', passwordLevelClass]">强度:<span>{{ passwordLevelName }}</span></div> + <a-progress :percent="state.percent" :showInfo="false" :strokeColor=" passwordLevelColor " /> + <div style="margin-top: 10px;"> + <span>请至少输入 6 个字符。请不要使用容易被猜到的密码。</span> + </div> + </div> + </template> + <a-form-item> + <a-input + size="large" + type="password" + @click="handlePasswordInputClick" + autocomplete="false" + placeholder="至少6位密码,区分大小写" + v-decorator="['password', {rules: [{ required: true, message: '至少6位密码,区分大小写'}, { validator: this.handlePasswordLevel }], validateTrigger: ['change', 'blur']}]" + ></a-input> + </a-form-item> + </a-popover> + + <a-form-item> + <a-input + size="large" + type="password" + autocomplete="false" + placeholder="确认密码" + v-decorator="['password2', {rules: [{ required: true, message: '至少6位密码,区分大小写' }, { validator: this.handlePasswordCheck }], validateTrigger: ['change', 'blur']}]" + ></a-input> + </a-form-item> + + <a-form-item> + <a-input size="large" placeholder="11 位手机号" v-decorator="['mobile', {rules: [{ required: true, message: '请输入正确的手机号', pattern: /^1[3456789]\d{9}$/ }, { validator: this.handlePhoneCheck } ], validateTrigger: ['change', 'blur'] }]"> + <a-select slot="addonBefore" size="large" defaultValue="+86"> + <a-select-option value="+86">+86</a-select-option> + <a-select-option value="+87">+87</a-select-option> + </a-select> + </a-input> + </a-form-item> + <!--<a-input-group size="large" compact> + <a-select style="width: 20%" size="large" defaultValue="+86"> + <a-select-option value="+86">+86</a-select-option> + <a-select-option value="+87">+87</a-select-option> + </a-select> + <a-input style="width: 80%" size="large" placeholder="11 位手机号"></a-input> + </a-input-group>--> + + <a-row :gutter="16"> + <a-col class="gutter-row" :span="16"> + <a-form-item> + <a-input size="large" type="text" placeholder="验证码" v-decorator="['captcha', {rules: [{ required: true, message: '请输入验证码' }], validateTrigger: 'blur'}]"> + <a-icon slot="prefix" type="mail" :style="{ color: 'rgba(0,0,0,.25)' }"/> + </a-input> + </a-form-item> + </a-col> + <a-col class="gutter-row" :span="8"> + <a-button + class="getCaptcha" + size="large" + :disabled="state.smsSendBtn" + @click.stop.prevent="getCaptcha" + v-text="!state.smsSendBtn && '获取验证码'||(state.time+' s')"></a-button> + </a-col> + </a-row> + + <a-form-item> + <a-button + size="large" + type="primary" + htmlType="submit" + class="register-button" + :loading="registerBtn" + @click.stop.prevent="handleSubmit" + :disabled="registerBtn">注册 + </a-button> + <router-link class="login" :to="{ name: 'login' }">使用已有账户登录</router-link> + </a-form-item> + + </a-form> + </div> +</template> + +<script> +import { mixinDevice } from '@/utils/mixin.js' +import { getSmsCaptcha } from '@/api/modular/system/loginManage' + +const levelNames = { + 0: '低', + 1: '低', + 2: '中', + 3: '强' +} +const levelClass = { + 0: 'error', + 1: 'error', + 2: 'warning', + 3: 'success' +} +const levelColor = { + 0: '#ff0000', + 1: '#ff0000', + 2: '#ff7e05', + 3: '#52c41a' +} +export default { + name: 'Register', + components: { + }, + mixins: [mixinDevice], + data () { + return { + form: this.$form.createForm(this), + + state: { + time: 60, + smsSendBtn: false, + passwordLevel: 0, + passwordLevelChecked: false, + percent: 10, + progressColor: '#FF0000' + }, + registerBtn: false + } + }, + computed: { + passwordLevelClass () { + return levelClass[this.state.passwordLevel] + }, + passwordLevelName () { + return levelNames[this.state.passwordLevel] + }, + passwordLevelColor () { + return levelColor[this.state.passwordLevel] + } + }, + methods: { + handlePasswordLevel (rule, value, callback) { + let level = 0 + + // 判断这个字符串中有没有数字 + if (/[0-9]/.test(value)) { + level++ + } + // 判断字符串中有没有字母 + if (/[a-zA-Z]/.test(value)) { + level++ + } + // 判断字符串中有没有特殊符号 + if (/[^0-9a-zA-Z_]/.test(value)) { + level++ + } + this.state.passwordLevel = level + this.state.percent = level * 30 + if (level >= 2) { + if (level >= 3) { + this.state.percent = 100 + } + callback() + } else { + if (level === 0) { + this.state.percent = 10 + } + callback(new Error('密码强度不够')) + } + }, + + handlePasswordCheck (rule, value, callback) { + const password = this.form.getFieldValue('password') + console.log('value', value) + if (value === undefined) { + callback(new Error('请输入密码')) + } + if (value && password && value.trim() !== password.trim()) { + callback(new Error('两次密码不一致')) + } + callback() + }, + + handlePhoneCheck (rule, value, callback) { + console.log('handlePhoneCheck, rule:', rule) + console.log('handlePhoneCheck, value', value) + console.log('handlePhoneCheck, callback', callback) + + callback() + }, + + handlePasswordInputClick () { + if (!this.isMobile()) { + this.state.passwordLevelChecked = true + return + } + this.state.passwordLevelChecked = false + }, + + handleSubmit () { + const { form: { validateFields }, state, $router } = this + validateFields({ force: true }, (err, values) => { + if (!err) { + state.passwordLevelChecked = false + $router.push({ name: 'registerResult', params: { ...values } }) + } + }) + }, + + getCaptcha (e) { + e.preventDefault() + const { form: { validateFields }, state, $message, $notification } = this + + validateFields(['mobile'], { force: true }, + (err, values) => { + if (!err) { + state.smsSendBtn = true + + const interval = window.setInterval(() => { + if (state.time-- <= 0) { + state.time = 60 + state.smsSendBtn = false + window.clearInterval(interval) + } + }, 1000) + + const hide = $message.loading('验证码发送中..', 0) + + getSmsCaptcha({ mobile: values.mobile }).then(res => { + setTimeout(hide, 2500) + $notification['success']({ + message: '提示', + description: '验证码获取成功,您的验证码为:' + res.result.captcha, + duration: 8 + }) + }).catch(err => { + setTimeout(hide, 1) + clearInterval(interval) + state.time = 60 + state.smsSendBtn = false + this.requestFailed(err) + }) + } + } + ) + }, + requestFailed (err) { + this.$notification['error']({ + message: '错误', + description: ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试', + duration: 4 + }) + this.registerBtn = false + } + }, + watch: { + 'state.passwordLevel' (val) { + console.log(val) + } + } +} +</script> +<style lang="less"> + .user-register { + + &.error { + color: #ff0000; + } + + &.warning { + color: #ff7e05; + } + + &.success { + color: #52c41a; + } + + } + + .user-layout-register { + .ant-input-group-addon:first-child { + background-color: #fff; + } + } +</style> +<style lang="less" scoped> + .user-layout-register { + + & > h3 { + font-size: 16px; + margin-bottom: 20px; + } + + .getCaptcha { + display: block; + width: 100%; + height: 40px; + } + + .register-button { + width: 50%; + } + + .login { + float: right; + line-height: 40px; + } + } +</style> diff --git a/_web/src/views/userLoginReg/RegisterResult.vue b/_web/src/views/userLoginReg/RegisterResult.vue new file mode 100644 index 0000000..5a807e0 --- /dev/null +++ b/_web/src/views/userLoginReg/RegisterResult.vue @@ -0,0 +1,50 @@ +<template> + <result + :isSuccess="true" + :content="false" + :title="email" + :description="description"> + + <template slot="action"> + <a-button size="large" type="primary">查看邮箱</a-button> + <a-button size="large" style="margin-left: 8px" @click="goHomeHandle">返回首页</a-button> + </template> + + </result> +</template> + +<script> +import { Result } from '@/components' + +export default { + name: 'RegisterResult', + components: { + Result + }, + data () { + return { + description: '激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。', + form: {} + } + }, + computed: { + email () { + const v = this.form && this.form.email || 'xxx' + const title = `你的账户:${v} 注册成功` + return title + } + }, + created () { + this.form = this.$route.params + }, + methods: { + goHomeHandle () { + this.$router.push({ name: 'login' }) + } + } +} +</script> + +<style scoped> + +</style> diff --git a/_web/tests/unit/.eslintrc.js b/_web/tests/unit/.eslintrc.js new file mode 100644 index 0000000..958d51b --- /dev/null +++ b/_web/tests/unit/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + jest: true + } +} diff --git a/_web/vue.config.js b/_web/vue.config.js new file mode 100644 index 0000000..bce23a0 --- /dev/null +++ b/_web/vue.config.js @@ -0,0 +1,123 @@ +const path = require('path') +const webpack = require('webpack') +const createThemeColorReplacerPlugin = require('./config/plugin.config') +const CompressionWebpackPlugin = require('compression-webpack-plugin') +const productionGzipExtensions = ['js', 'css'] + +function resolve (dir) { + return path.join(__dirname, dir) +} + +const isProd = process.env.NODE_ENV === 'production' + +const assetsCDN = { + // webpack build externals + externals: { + vue: 'Vue', + 'vue-router': 'VueRouter', + vuex: 'Vuex', + axios: 'axios' + }, + css: [], + // https://unpkg.com/browse/vue@2.6.10/ + js: [ + '//unpkg.com/vue@2.6.10/dist/vue.min.js', + '//unpkg.com/vue-router@3.1.3/dist/vue-router.min.js', + '//unpkg.com/vuex@3.1.1/dist/vuex.min.js', + '//unpkg.com/axios@0.19.0/dist/axios.min.js' + ] +} + +// vue.config.js +const vueConfig = { + configureWebpack: { + // webpack plugins + plugins: [ + // Ignore all locale files of moment.js + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + // 配置compression-webpack-plugin压缩 + new CompressionWebpackPlugin({ + algorithm: 'gzip', + test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'), + threshold: 10240, + minRatio: 0.8 + }) + ], + // if prod, add externals + externals: isProd ? assetsCDN.externals : {} + }, + + chainWebpack: (config) => { + config.resolve.alias + .set('@$', resolve('src')) + + const svgRule = config.module.rule('svg') + svgRule.uses.clear() + svgRule + .oneOf('inline') + .resourceQuery(/inline/) + .use('vue-svg-icon-loader') + .loader('vue-svg-icon-loader') + .end() + .end() + .oneOf('external') + .use('file-loader') + .loader('file-loader') + .options({ + name: 'assets/[name].[hash:8].[ext]' + }) + + // if prod is on + // assets require on cdn + if (isProd) { + config.plugin('html').tap(args => { + args[0].cdn = assetsCDN + return args + }) + } + }, + + css: { + loaderOptions: { + less: { + modifyVars: { + 'primary-color': '#1890FF', + 'layout-color': '#1890FF', + 'border-radius-base': '2px' + }, + // DO NOT REMOVE THIS LINE + javascriptEnabled: true + } + } + }, + + devServer: { + port: 81, + proxy: { + '/api': { + target: process.env.VUE_APP_API_BASE_URL, + ws: false, + changeOrigin: true, + pathRewrite: { + '^/api': '' // 需要rewrite的, + } + } + } + }, + + // disable source map in production + productionSourceMap: false, + lintOnSave: undefined, + // babel-loader no-ignore node_modules/* + transpileDependencies: [] +} + +// preview.pro.loacg.com only do not use in your production; +if (process.env.VUE_APP_PREVIEW === 'true') { + // eslint-disable-next-line no-labels + // runtimeCompiler: true, + // add `ThemeColorReplacer` plugin to webpack plugins + vueConfig.configureWebpack.plugins.push(createThemeColorReplacerPlugin()) +} + +module.exports = vueConfig diff --git a/_web/webstorm.config.js b/_web/webstorm.config.js new file mode 100644 index 0000000..9117455 --- /dev/null +++ b/_web/webstorm.config.js @@ -0,0 +1,3 @@ +'use strict' +const webpackConfig = require('@vue/cli-service/webpack.config.js') +module.exports = webpackConfig diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1cf050f --- /dev/null +++ b/pom.xml @@ -0,0 +1,270 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.3.1.RELEASE</version> + </parent> + + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy</artifactId> + <version>1.6.0</version> + + <name>snowy</name> + <description>snowy的前后端分离vue版本</description> + + <packaging>pom</packaging> + + <modules> + <module>snowy-base</module> + <module>snowy-main</module> + </modules> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <mysql-connector-java.version>8.0.17</mysql-connector-java.version> + <oracle.version>11.2.0.3</oracle.version> + <mssql.version>9.3.0.jre8-preview</mssql.version> + <postgresql.version>42.2.19</postgresql.version> + <druid.version>1.1.21</druid.version> + <mp.version>3.4.2</mp.version> + <fastjson.version>1.2.75</fastjson.version> + <jwt.version>0.9.1</jwt.version> + <hutool.version>5.5.8</hutool.version> + <lombok.versin>1.18.12</lombok.versin> + <easypoi.version>4.2.0</easypoi.version> + <jodconverter.version>4.2.0</jodconverter.version> + <libreoffice.version>6.4.3</libreoffice.version> + <justauth.version>1.15.6</justauth.version> + <aliyun.oss.version>3.8.0</aliyun.oss.version> + <qcloud.oss.version>5.6.23</qcloud.oss.version> + <aliyun.sms.sdk.version>4.4.6</aliyun.sms.sdk.version> + <aliyun.sms.esc.version>4.17.6</aliyun.sms.esc.version> + <qcloud.sms.sdk.version>3.1.57</qcloud.sms.sdk.version> + <knife4j.version>2.0.8</knife4j.version> + </properties> + + <dependencyManagement> + + <dependencies> + + <!--mybatis-plus--> + <dependency> + <groupId>com.baomidou</groupId> + <artifactId>mybatis-plus-boot-starter</artifactId> + <version>${mp.version}</version> + </dependency> + + <!-- 数据库驱动,可根据自己需要自行删减,默认使用mysql --> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + <version>${mysql-connector-java.version}</version> + </dependency> + + <!-- oracle --> + <!--<dependency> + <groupId>com.oracle</groupId> + <artifactId>ojdbc6</artifactId> + <version>${oracle.version}</version> + </dependency>--> + + <!-- mssql --> + <!-- <dependency> + <groupId>com.microsoft.sqlserver</groupId> + <artifactId>mssql-jdbc</artifactId> + <version>${mssql.version}</version> + </dependency>--> + + <!-- postgresql --> + <!-- <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <version>${postgresql.version}</version> + </dependency>--> + + <!--数据库连接池--> + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>druid</artifactId> + <version>${druid.version}</version> + </dependency> + + <!--jwt token--> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt</artifactId> + <version>${jwt.version}</version> + </dependency> + + <!--fastjson--> + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>fastjson</artifactId> + <version>${fastjson.version}</version> + </dependency> + + <!--hutool--> + <dependency> + <groupId>cn.hutool</groupId> + <artifactId>hutool-all</artifactId> + <version>${hutool.version}</version> + </dependency> + + <!--lombok--> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>${lombok.versin}</version> + </dependency> + + <!-- swagger接口文档 --> + <dependency> + <groupId>com.github.xiaoymin</groupId> + <artifactId>knife4j-spring-boot-starter</artifactId> + <version>${knife4j.version}</version> + </dependency> + + <!--easypoi导入导出--> + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-base</artifactId> + <version>${easypoi.version}</version> + </dependency> + + <!--libreoffice文档在线预览--> + <dependency> + <groupId>org.jodconverter</groupId> + <artifactId>jodconverter-core</artifactId> + <version>${jodconverter.version}</version> + </dependency> + <dependency> + <groupId>org.jodconverter</groupId> + <artifactId>jodconverter-local</artifactId> + <version>${jodconverter.version}</version> + </dependency> + <dependency> + <groupId>org.jodconverter</groupId> + <artifactId>jodconverter-spring-boot-starter</artifactId> + <version>${jodconverter.version}</version> + </dependency> + <dependency> + <groupId>org.libreoffice</groupId> + <artifactId>ridl</artifactId> + <version>${libreoffice.version}</version> + </dependency> + + <!--justauth第三方登录--> + <dependency> + <groupId>me.zhyd.oauth</groupId> + <artifactId>JustAuth</artifactId> + <version>${justauth.version}</version> + </dependency> + + <!--阿里云上传文件客户端,用的时候手动引入--> + <dependency> + <groupId>com.aliyun.oss</groupId> + <artifactId>aliyun-sdk-oss</artifactId> + <version>${aliyun.oss.version}</version> + </dependency> + + <!--腾讯云上传文件客户端,用的时候手动引入--> + <dependency> + <groupId>com.qcloud</groupId> + <artifactId>cos_api</artifactId> + <version>${qcloud.oss.version}</version> + </dependency> + + <!--阿里云短信发送的sdk--> + <dependency> + <groupId>com.aliyun</groupId> + <artifactId>aliyun-java-sdk-core</artifactId> + <version>${aliyun.sms.sdk.version}</version> + </dependency> + <dependency> + <groupId>com.aliyun</groupId> + <artifactId>aliyun-java-sdk-ecs</artifactId> + <version>${aliyun.sms.esc.version}</version> + </dependency> + + <!--腾讯云短信sdk--> + <dependency> + <groupId>com.tencentcloudapi</groupId> + <artifactId>tencentcloud-sdk-java</artifactId> + <version>${qcloud.sms.sdk.version}</version> + </dependency> + </dependencies> + + </dependencyManagement> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.1</version> + <configuration> + <source>${java.version}</source> + <target>${java.version}</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>2.6</version> + <configuration> + <delimiters> + <delimiter>@</delimiter> + </delimiters> + <useDefaultDelimiters>false</useDefaultDelimiters> + </configuration> + </plugin> + </plugins> + <resources> + <resource> + <directory>src/main/webapp</directory> + <filtering>false</filtering> + </resource> + <resource> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + <resource> + <directory>src/main/java</directory> + <includes> + <include>**/*.xml</include> + </includes> + </resource> + </resources> + </build> + + <profiles> + <profile> + <id>local</id> + <properties> + <spring.active>local</spring.active> + </properties> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + </profile> + <profile> + <id>dev</id> + <properties> + <spring.active>dev</spring.active> + </properties> + </profile> + <profile> + <id>prod</id> + <properties> + <spring.active>prod</spring.active> + </properties> + </profile> + </profiles> + +</project> diff --git a/snowy-base/README.md b/snowy-base/README.md new file mode 100644 index 0000000..bc024c9 --- /dev/null +++ b/snowy-base/README.md @@ -0,0 +1 @@ +** 此模块大家尽量不要动,升级的时候只要将snowy-base的包覆盖此模块即可 ** diff --git a/snowy-base/pom.xml b/snowy-base/pom.xml new file mode 100644 index 0000000..71321e9 --- /dev/null +++ b/snowy-base/pom.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy</artifactId> + <version>1.6.0</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>snowy-base</artifactId> + + <packaging>pom</packaging> + + <modules> + <module>snowy-core</module> + <module>snowy-system</module> + <module>snowy-gen</module> + </modules> + +</project> diff --git a/snowy-base/snowy-core/README.md b/snowy-base/snowy-core/README.md new file mode 100644 index 0000000..1000452 --- /dev/null +++ b/snowy-base/snowy-core/README.md @@ -0,0 +1,3 @@ +** 本模块是其他模块都会引用的模块,存放一些通用的工具类,枚举类,实体等 ** + +** 工具类能用hutool的用hutool不要自己封装 ** \ No newline at end of file diff --git a/snowy-base/snowy-core/pom.xml b/snowy-base/snowy-core/pom.xml new file mode 100644 index 0000000..e6be763 --- /dev/null +++ b/snowy-base/snowy-core/pom.xml @@ -0,0 +1,188 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy-base</artifactId> + <version>1.6.0</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>snowy-core</artifactId> + + <packaging>jar</packaging> + + <dependencies> + + <!-- validation --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> + + <!-- web --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + + <!-- aop --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-aop</artifactId> + </dependency> + + <!-- security --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + + <!-- 数据库驱动,可根据自己需要自行删减,默认使用mysql --> + <dependency> + <groupId>mysql</groupId> + <artifactId>mysql-connector-java</artifactId> + </dependency> + + <!-- oracle --> + <!--<dependency> + <groupId>com.oracle</groupId> + <artifactId>ojdbc6</artifactId> + </dependency>--> + + <!-- mssql --> + <!--<dependency> + <groupId>com.microsoft.sqlserver</groupId> + <artifactId>mssql-jdbc</artifactId> + </dependency>--> + + <!-- postgresql --> + <!-- <dependency> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + </dependency>--> + + <!-- h2 --> + <!-- <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + </dependency>--> + + <dependency> + <groupId>com.antherd</groupId> + <artifactId>sm-crypto</artifactId> + <version>0.3.2</version> + </dependency> + + <!-- redis,使用jedis客户端排除lettuce --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-redis</artifactId> + <exclusions> + <exclusion> + <groupId>io.lettuce</groupId> + <artifactId>lettuce-core</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>redis.clients</groupId> + <artifactId>jedis</artifactId> + </dependency> + + <!-- druid连接池 --> + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>druid</artifactId> + </dependency> + + <!-- mybatis-plus --> + <dependency> + <groupId>com.baomidou</groupId> + <artifactId>mybatis-plus-boot-starter</artifactId> + </dependency> + + <!-- hutool --> + <dependency> + <groupId>cn.hutool</groupId> + <artifactId>hutool-all</artifactId> + </dependency> + + <!-- lombok --> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + + <!-- fastjson --> + <dependency> + <groupId>com.alibaba</groupId> + <artifactId>fastjson</artifactId> + </dependency> + + <!--easypoi导入导出--> + <dependency> + <groupId>cn.afterturn</groupId> + <artifactId>easypoi-base</artifactId> + </dependency> + + <!--libreoffice文档在线预览--> + <dependency> + <groupId>org.jodconverter</groupId> + <artifactId>jodconverter-core</artifactId> + </dependency> + <dependency> + <groupId>org.jodconverter</groupId> + <artifactId>jodconverter-local</artifactId> + </dependency> + <dependency> + <groupId>org.jodconverter</groupId> + <artifactId>jodconverter-spring-boot-starter</artifactId> + </dependency> + <dependency> + <groupId>org.libreoffice</groupId> + <artifactId>ridl</artifactId> + </dependency> + + <!--justauth第三方登录--> + <dependency> + <groupId>me.zhyd.oauth</groupId> + <artifactId>JustAuth</artifactId> + </dependency> + + <!--阿里云上传文件客户端--> + <dependency> + <groupId>com.aliyun.oss</groupId> + <artifactId>aliyun-sdk-oss</artifactId> + </dependency> + + <!--腾讯云上传文件客户端--> + <dependency> + <groupId>com.qcloud</groupId> + <artifactId>cos_api</artifactId> + </dependency> + + <!--阿里云短信发送的sdk--> + <dependency> + <groupId>com.aliyun</groupId> + <artifactId>aliyun-java-sdk-core</artifactId> + </dependency> + <dependency> + <groupId>com.aliyun</groupId> + <artifactId>aliyun-java-sdk-ecs</artifactId> + </dependency> + + <!--腾讯云短信sdk--> + <dependency> + <groupId>com.tencentcloudapi</groupId> + <artifactId>tencentcloud-sdk-java</artifactId> + </dependency> + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + </build> +</project> diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/BusinessLog.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/BusinessLog.java new file mode 100644 index 0000000..4dbb931 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/BusinessLog.java @@ -0,0 +1,51 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.annotion; + +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; + +import java.lang.annotation.*; + +/** + * 标记需要做业务日志的方法 + * + * @author yubaoshan + * @date 2017/3/31 12:46 + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface BusinessLog { + + /** + * 业务的名称,例如:"修改菜单" + */ + String title() default ""; + + /** + * 业务操作类型枚举 + */ + LogAnnotionOpTypeEnum opType() default LogAnnotionOpTypeEnum.OTHER; +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/DataScope.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/DataScope.java new file mode 100644 index 0000000..d4d11d2 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/DataScope.java @@ -0,0 +1,39 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.annotion; + +import java.lang.annotation.*; + +/** + * 数据权限注解 + * + * @author xuyuxiang + * @date 2020/3/28 17:16 + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface DataScope { +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/ExpEnumType.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/ExpEnumType.java new file mode 100644 index 0000000..bb0fa77 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/ExpEnumType.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.annotion; + +import java.lang.annotation.*; + +/** + * 标识在ExceptionEnum类上,用来标识类级别异常枚举编码 + * <p> + * 异常枚举编码由3部分组成,如下: + * <p> + * 模块编码(2位) + 分类编码(4位) + 具体项编码(至少1位) + * <p> + * 模块编码和分类编码在ExpEnumCodeConstant类中声明 + * + * @author yubaoshan + * @date 2020/6/19 20:46 + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface ExpEnumType { + + /** + * 模块编码 + */ + int module() default 99; + + /** + * 分类编码 + */ + int kind() default 9999; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/Permission.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/Permission.java new file mode 100644 index 0000000..ce1ee47 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/Permission.java @@ -0,0 +1,52 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.annotion; + +import vip.xiaonuo.core.enums.LogicTypeEnum; + +import java.lang.annotation.*; + +/** + * 权限注解,用于检查权限 + * 使用方式:@Permission表示检查是否有权限访问该资源 + * + * @author xuyuxiang + * @date 2020/3/11 14:44 + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Permission { + + /** + * 加上此注解表示需要有该资源url的才可以访问, 默认值为空,即该url,如果设置了值,则表示有该角色才可以访问 + */ + String[] value() default {}; + + /** + * 逻辑枚举,默认或 + */ + LogicTypeEnum logicType() default LogicTypeEnum.OR; +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/Wrapper.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/Wrapper.java new file mode 100644 index 0000000..973a221 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/annotion/Wrapper.java @@ -0,0 +1,47 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.annotion; + +import vip.xiaonuo.core.pojo.base.wrapper.BaseWrapper; + +import java.lang.annotation.*; + +/** + * 结果包装的注解,一般用在Controller层,给最后响应结果做包装 + * + * @author xuyuxiang + * @date 2020/7/24 17:10 + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Wrapper { + + /** + * 具体包装类 + */ + Class<? extends BaseWrapper<?>>[] value(); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/CacheOperator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/CacheOperator.java new file mode 100644 index 0000000..89dfb43 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/CacheOperator.java @@ -0,0 +1,118 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.cache; + +import java.util.Collection; +import java.util.Map; + +/** + * 缓存操作的基础接口,可以实现不同种缓存实现 + * <p> + * 泛型为cache的值类class类型 + * + * @author yubaoshan + * @date 2020/7/8 22:02 + */ +public interface CacheOperator<T> { + + /** + * 添加缓存 + * + * @param key 键 + * @param value 值 + * @author yubaoshan + * @date 2020/7/8 22:06 + */ + void put(String key, T value); + + /** + * 添加缓存(带过期时间,单位是秒) + * + * @param key 键 + * @param value 值 + * @param timeoutSeconds 过期时间,单位秒 + * @author yubaoshan + * @date 2020/7/8 22:07 + */ + void put(String key, T value, Long timeoutSeconds); + + /** + * 通过缓存key获取缓存 + * + * @param key 键 + * @return 值 + * @author yubaoshan + * @date 2020/7/8 22:08 + */ + Object get(String key); + + /** + * 删除缓存 + * + * @param key 键,多个 + * @author yubaoshan + * @date 2020/7/8 22:09 + */ + void remove(String... key); + + /** + * 获得缓存的所有key列表(不带common prefix的) + * + * @return key列表 + * @author yubaoshan + * @date 2020/7/8 22:11 + */ + Collection<String> getAllKeys(); + + /** + * 获得缓存的所有值列表 + * + * @return 值列表 + * @author yubaoshan + * @date 2020/7/8 22:11 + */ + Collection<T> getAllValues(); + + /** + * 获取所有的key,value + * + * @return 键值map + * @author yubaoshan + * @date 2020/7/8 22:11 + */ + Map<String, T> getAllKeyValues(); + + /** + * 通用缓存的前缀,用于区分不同业务 + * <p> + * 如果带了前缀,所有的缓存在添加的时候,key都会带上这个前缀 + * + * @return 缓存前缀 + * @author yubaoshan + * @date 2020/7/9 11:06 + */ + String getCommonKeyPrefix(); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/base/AbstractMemoryCacheOperator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/base/AbstractMemoryCacheOperator.java new file mode 100644 index 0000000..474a8aa --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/base/AbstractMemoryCacheOperator.java @@ -0,0 +1,105 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.cache.base; + +import cn.hutool.cache.impl.CacheObj; +import cn.hutool.cache.impl.TimedCache; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import vip.xiaonuo.core.cache.CacheOperator; + +import java.util.*; + +/** + * 基于内存的缓存封装 + * + * @author yubaoshan + * @date 2020/7/9 10:09 + */ +public abstract class AbstractMemoryCacheOperator<T> implements CacheOperator<T> { + + private final TimedCache<String, T> timedCache; + + public AbstractMemoryCacheOperator(TimedCache<String, T> timedCache) { + this.timedCache = timedCache; + } + + @Override + public void put(String key, T value) { + timedCache.put(getCommonKeyPrefix() + key, value); + } + + @Override + public void put(String key, T value, Long timeoutSeconds) { + timedCache.put(getCommonKeyPrefix() + key, value, timeoutSeconds * 1000); + } + + @Override + public T get(String key) { + // 如果用户在超时前调用了get(key)方法,会重头计算起始时间,false的作用就是不从头算 + return timedCache.get(getCommonKeyPrefix() + key, true); + } + + @Override + public void remove(String... key) { + if (key.length > 0) { + for (String itemKey : key) { + timedCache.remove(getCommonKeyPrefix() + itemKey); + } + } + } + + @Override + public Collection<String> getAllKeys() { + Iterator<CacheObj<String, T>> cacheObjIterator = timedCache.cacheObjIterator(); + ArrayList<String> keys = CollectionUtil.newArrayList(); + while (cacheObjIterator.hasNext()) { + // 去掉缓存key的common prefix前缀 + String key = cacheObjIterator.next().getKey(); + keys.add(StrUtil.removePrefix(key, getCommonKeyPrefix())); + } + return keys; + } + + @Override + public Collection<T> getAllValues() { + Iterator<CacheObj<String, T>> cacheObjIterator = timedCache.cacheObjIterator(); + ArrayList<T> values = CollectionUtil.newArrayList(); + while (cacheObjIterator.hasNext()) { + values.add(cacheObjIterator.next().getValue()); + } + return values; + } + + @Override + public Map<String, T> getAllKeyValues() { + Collection<String> allKeys = this.getAllKeys(); + HashMap<String, T> results = CollectionUtil.newHashMap(); + for (String key : allKeys) { + results.put(key, this.get(key)); + } + return results; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/base/AbstractRedisCacheOperator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/base/AbstractRedisCacheOperator.java new file mode 100644 index 0000000..156ae30 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cache/base/AbstractRedisCacheOperator.java @@ -0,0 +1,103 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.cache.base; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.data.redis.core.RedisTemplate; +import vip.xiaonuo.core.cache.CacheOperator; +import vip.xiaonuo.core.consts.SymbolConstant; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 基于redis的缓存封装 + * + * @author yubaoshan + * @date 2020/7/9 10:09 + */ +public abstract class AbstractRedisCacheOperator<T> implements CacheOperator<T> { + + private final RedisTemplate<String, T> redisTemplate; + + public AbstractRedisCacheOperator(RedisTemplate<String, T> redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Override + public void put(String key, T value) { + redisTemplate.boundValueOps(getCommonKeyPrefix() + key).set(value); + } + + @Override + public void put(String key, T value, Long timeoutSeconds) { + redisTemplate.boundValueOps(getCommonKeyPrefix() + key).set(value, timeoutSeconds, TimeUnit.SECONDS); + } + + @Override + public T get(String key) { + return redisTemplate.boundValueOps(getCommonKeyPrefix() + key).get(); + } + + @Override + public void remove(String... key) { + ArrayList<String> keys = CollectionUtil.toList(key); + List<String> withPrefixKeys = keys.stream().map(i -> getCommonKeyPrefix() + i).collect(Collectors.toList()); + redisTemplate.delete(withPrefixKeys); + } + + @Override + public Collection<String> getAllKeys() { + Set<String> keys = redisTemplate.keys(getCommonKeyPrefix() + SymbolConstant.ASTERISK); + if (keys != null) { + // 去掉缓存key的common prefix前缀 + return keys.stream().map(key -> StrUtil.removePrefix(key, getCommonKeyPrefix())).collect(Collectors.toSet()); + } else { + return CollectionUtil.newHashSet(); + } + } + + @Override + public Collection<T> getAllValues() { + Set<String> keys = redisTemplate.keys(getCommonKeyPrefix() + SymbolConstant.ASTERISK); + if (keys != null) { + return redisTemplate.opsForValue().multiGet(keys); + } else { + return CollectionUtil.newArrayList(); + } + } + + @Override + public Map<String, T> getAllKeyValues() { + Collection<String> allKeys = this.getAllKeys(); + HashMap<String, T> results = CollectionUtil.newHashMap(); + for (String key : allKeys) { + results.put(key, this.get(key)); + } + return results; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/AopSortConstant.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/AopSortConstant.java new file mode 100644 index 0000000..b5b3d00 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/AopSortConstant.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.consts; + +/** + * aop顺序的常量 + * <p> + * 顺序越小越靠前 + * + * @author yubaoshan + * @date 2020/4/10 15:33 + */ +public interface AopSortConstant { + + /** + * 全局异常拦截器 + */ + int GLOBAL_EXP_HANDLER_AOP = -120; + + /** + * 结果包装的aop + */ + int WRAPPER_AOP = -110; + + /** + * 接口资源权限校验 + */ + int PERMISSION_AOP = -100; + + /** + * 数据范围AOP + */ + int DATA_SCOPE_AOP = -50; + + /** + * 多租户的aop + */ + int TENANT_EXCHANGE_AOP = -10; + + /** + * 多数据源切换的aop + */ + int MULTI_DATA_SOURCE_AOP = 1; + + /** + * 业务日志的AOP + */ + int BUSINESS_LOG_AOP = 100; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/CommonConstant.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/CommonConstant.java new file mode 100644 index 0000000..95dd64b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/CommonConstant.java @@ -0,0 +1,134 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.consts; + +/** + * 通用常量 + * + * @author xuyuxiang yubaoshan + * @date 2020/3/11 16:51 + */ +public interface CommonConstant { + + /** + * id + */ + String ID = "id"; + + /** + * 名称 + */ + String NAME = "name"; + + /** + * 编码 + */ + String CODE = "code"; + + /** + * 值 + */ + String VALUE = "value"; + + /** + * 默认标识状态的字段名称 + */ + String STATUS = "status"; + + /** + * 默认逻辑删除的状态值 + */ + String DEFAULT_LOGIC_DELETE_VALUE = "2"; + + /** + * 用户代理 + */ + String USER_AGENT = "User-Agent"; + + /** + * 请求头token表示 + */ + String AUTHORIZATION = "Authorization"; + + /** + * token名称 + */ + String TOKEN_NAME = "token"; + + /** + * token类型 + */ + String TOKEN_TYPE_BEARER = "Bearer"; + + /** + * 首页提示语 + */ + String INDEX_TIPS = "Welcome To Snowy"; + + /** + * 未知标识 + */ + String UNKNOWN = "Unknown"; + + /** + * 默认包名 + */ + String DEFAULT_PACKAGE_NAME = "vip.xiaonuo"; + + /** + * 默认密码 + */ + String DEFAULT_PASSWORD = "123456"; + + /** + * 请求号在header中的唯一标识 + */ + String REQUEST_NO_HEADER_NAME = "Request-No"; + + /** + * 数据库链接URL标识 + */ + String DATABASE_URL_NAME = "DATABASE_URL_NAME"; + + /** + * 数据库链接驱动标识 + */ + String DATABASE_DRIVER_NAME = "DATABASE_DRIVER_NAME"; + + /** + * 数据库用户标识 + */ + String DATABASE_USER_NAME = "DATABASE_USER_NAME"; + + /** + * 点选验证码 + */ + String IMAGE_CODE_TYPE = "clickWord"; + + /** + * undefined未知 + */ + String UNDEFINED = "undefined"; +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/ExpEnumConstant.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/ExpEnumConstant.java new file mode 100644 index 0000000..5c1d837 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/ExpEnumConstant.java @@ -0,0 +1,89 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.consts; + +/** + * 异常枚举编码构成常量 + * <p> + * 异常枚举编码由3部分组成,如下: + * <p> + * 模块编码(2位) + 分类编码(4位) + 具体项编码(至少1位) + * <p> + * 模块编码和分类编码在ExpEnumCodeConstant类中声明 + * + * @author yubaoshan + * @date 2020/6/19 20:46 + */ +public interface ExpEnumConstant { + + /** + * 模块分类编码(2位) + * <p> + * snowy-core模块异常枚举编码 + */ + int SNOWY_CORE_MODULE_EXP_CODE = 10; + + /* 分类编码(4位) */ + /** + * 认证异常枚举 + */ + int AUTH_EXCEPTION_ENUM = 1100; + + /** + * 参数校验异常枚举 + */ + int PARAM_EXCEPTION_ENUM = 1200; + + /** + * 授权和鉴权异常的枚举 + */ + int PERMISSION_EXCEPTION_ENUM = 1300; + + /** + * 请求方法相关异常枚举 + */ + int REQUEST_METHOD_EXCEPTION_ENUM = 1400; + + /** + * 请求类型相关异常枚举 + */ + int REQUEST_TYPE_EXCEPTION_ENUM = 1500; + + /** + * 服务器内部相关异常枚举 + */ + int SERVER_EXCEPTION_ENUM = 1600; + + /** + * 状态相关异常枚举 + */ + int STATUS_EXCEPTION_ENUM = 1700; + + /** + * 包装相关异常枚举 + */ + int WRAPPER_EXCEPTION_ENUM = 1800; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/MediaTypeConstant.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/MediaTypeConstant.java new file mode 100644 index 0000000..dca68f5 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/MediaTypeConstant.java @@ -0,0 +1,124 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.consts; + +/** + * 常用媒体类型常量 + * + * @author xuyuxiang + * @date 2020/7/9 14:14 + */ +public interface MediaTypeConstant { + + /** + * 图片jpg格式 + */ + String IMG_JPG = "jpg"; + + /** + * 图片png格式 + */ + String IMG_PNG = "png"; + + /** + * 图片jpeg格式 + */ + String IMG_JPEG = "jpeg"; + + /** + * 图片tif格式 + */ + String IMG_TIF = "tif"; + + /** + * 图片gif格式 + */ + String IMG_GIF = "gif"; + + /** + * 图片bmp格式 + */ + String IMG_BMP = "bmp"; + + /** + * 文档txt格式 + */ + String DOC_TXT = "txt"; + + /** + * 文档doc格式 + */ + String DOC_DOC = "doc"; + + /** + * 文档docx格式 + */ + String DOC_DOCX = "docx"; + + /** + * 文档xls格式 + */ + String DOC_XLS = "xls"; + + /** + * 文档xlsx格式 + */ + String DOC_XLSX = "xlsx"; + + /** + * 文档ppt格式 + */ + String DOC_PPT = "ppt"; + + /** + * 文档pptx格式 + */ + String DOC_PPTX = "pptx"; + + /** + * 文档pdf格式 + */ + String DOC_PDF = "pdf"; + + /** + * 文件html格式 + */ + String FILE_HTML = "html"; + + /** + * 文件htm格式 + */ + String FILE_HTM = "htm"; + + /** + * 文件swf格式 + */ + String FILE_SWF = "swf"; + + /** + * 文件flash格式 + */ + String FILE_FLASH = "flash"; +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/SpringSecurityConstant.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/SpringSecurityConstant.java new file mode 100644 index 0000000..e9fcf2e --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/SpringSecurityConstant.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.consts; + +/** + * SpringSecurity相关常量 + * + * @author xuyuxiang + * @date 2020/3/18 17:49 + */ +public interface SpringSecurityConstant { + + /** + * 放开权限校验的接口 + */ + String[] NONE_SECURITY_URL_PATTERNS = { + + //前端的 + "/favicon.ico", + + //swagger相关的 + "/doc.html", + "/webjars/**", + "/swagger-resources/**", + "/v2/api-docs", + "/v2/api-docs-ext", + "/configuration/ui", + "/configuration/security", + + //后端的 + "/", + "/login", + "/logout", + "/oauth/**", + + //文件的 + "/sysFileInfo/upload", + "/sysFileInfo/download", + "/sysFileInfo/preview", + + //druid的 + "/druid/**", + + //获取验证码 + "/captcha/**", + "/getCaptchaOpen", + + }; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/SymbolConstant.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/SymbolConstant.java new file mode 100644 index 0000000..74c83ee --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/consts/SymbolConstant.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.consts; + +/** + * 符号常量 + * + * @author xuyuxiang + * @date 2020/3/16 12:05 + */ +public interface SymbolConstant { + + String PERIOD = "."; + + String COMMA = ","; + + String COLON = ":"; + + String SEMICOLON = ";"; + + String EXCLAMATION_MARK = "!"; + + String QUESTION_MARK = "?"; + + String HYPHEN = "-"; + + String ASTERISK = "*"; + + String APOSTROPHE = "`"; + + String DASH = "-"; + + String UNDER_SCORE = "_"; + + String SINGLE_QUOTATION_MARK = "'"; + + String DOUBLE_QUOTATION_MARK = "\""; + + String LEFT_ROUND_BRACKETS = "("; + + String RIGHT_ROUND_BRACKETS = ")"; + + String LEFT_SQUARE_BRACKETS = "["; + + String RIGHT_SQUARE_BRACKETS = "]"; + + String LEFT_ANGLE_BRACKETS = "<"; + + String RIGHT_ANGLE_BRACKETS = ">"; + + String LEFT_CURLY_BRACKETS = "{"; + + String RIGHT_CURLY_BRACKETS = "}"; + + String DOLLAR = "$"; + + String PERCENT = "%"; + + String LEFT_DIVIDE = "/"; + + String RIGHT_DIVIDE = "\\"; + + String LEFT_DOUBLE_DIVIDE = "//"; + + String RIGHT_DOUBLE_DIVIDE = "\\\\"; + + String EQUAL = "="; +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/constant/ConstantContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/constant/ConstantContext.java new file mode 100644 index 0000000..953303c --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/constant/ConstantContext.java @@ -0,0 +1,78 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.constant; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; + +/** + * 系统参数配置容器 + * + * @author yubaoshan + * @date 2019/6/20 13:37 + */ +public class ConstantContext { + + /** + * 所有的常量,可以增删改查 + */ + private static final Dict CONSTANTS_HOLDER = Dict.create(); + + /** + * 添加系统常量 + * + * @author yubaoshan + * @date 2020/6/20 22:32 + */ + public static void putConstant(String code, Object value) { + if (ObjectUtil.hasEmpty(code, value)) { + return; + } + CONSTANTS_HOLDER.put(code, value); + } + + /** + * 删除常量,系统常量无法删除,在sysConfig已判断 + * + * @author yubaoshan + * @date 2020/6/20 22:32 + */ + public static void deleteConstant(String code) { + if (ObjectUtil.hasEmpty(code)) { + return; + } + CONSTANTS_HOLDER.remove(code); + } + + /** + * 获取系统常量本身 + * + * @author yubaoshan + * @date 2020/6/20 22:32 + */ + public static Dict me() { + return CONSTANTS_HOLDER; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/constant/ConstantContextHolder.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/constant/ConstantContextHolder.java new file mode 100644 index 0000000..f764c9e --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/constant/ConstantContextHolder.java @@ -0,0 +1,413 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.constant; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.Log; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.pojo.cryptogram.CryptogramConfigs; +import vip.xiaonuo.core.pojo.email.EmailConfigs; +import vip.xiaonuo.core.pojo.oauth.OauthConfigs; +import vip.xiaonuo.core.pojo.sms.AliyunSmsConfigs; +import vip.xiaonuo.core.pojo.sms.TencentSmsConfigs; + +import java.util.List; + +import static vip.xiaonuo.core.exception.enums.ServerExceptionEnum.CONSTANT_EMPTY; + +/** + * 系统参数配置获取 + * + * @author xuyuxiang + * @date 2020/4/14 15:34 + */ +public class ConstantContextHolder { + + private static final Log log = Log.get(); + + /** + * 获取租户功能是否开启 + * + * @author xuyuxiang + * @date 2020/9/3 + */ + public static Boolean getTenantOpenFlag() { + return getSysConfigWithDefault("SNOWY_TENANT_OPEN", Boolean.class, false); + } + + /** + * 获取放开xss过滤的接口 + * + * @author yubaoshan + * @date 2020/6/20 22:13 + */ + public static List<String> getUnXssFilterUrl() { + String snowyUnXssFilterUrl = getSysConfigWithDefault("SNOWY_UN_XSS_FILTER_URL", String.class, null); + if (ObjectUtil.isEmpty(snowyUnXssFilterUrl)) { + return CollectionUtil.newArrayList(); + } else { + return CollectionUtil.toList(snowyUnXssFilterUrl.split(SymbolConstant.COMMA)); + } + } + + /** + * 获取演示环境开关是否开启,默认为false + * + * @author yubaoshan + * @date 2020/6/20 22:13 + */ + public static Boolean getDemoEnvFlag() { + return getSysConfigWithDefault("SNOWY_DEMO_ENV_FLAG", Boolean.class, false); + } + + /** + * 邮件的配置 + * + * @author yubaoshan + * @date 2020/6/19 18:08 + */ + public static EmailConfigs getEmailConfigs() { + String host = getSysConfig("SNOWY_EMAIL_HOST", String.class, true); + String username = getSysConfig("SNOWY_EMAIL_USERNAME", String.class, true); + String password = getSysConfig("SNOWY_EMAIL_PASSWORD", String.class, true); + Integer port = getSysConfig("SNOWY_EMAIL_PORT", Integer.class, true); + String from = getSysConfig("SNOWY_EMAIL_FROM", String.class, true); + Boolean ssl = getSysConfig("SNOWY_EMAIL_SSL", Boolean.class, true); + + EmailConfigs emailConfigs = new EmailConfigs(); + emailConfigs.setHost(host); + emailConfigs.setUser(username); + emailConfigs.setPass(password); + emailConfigs.setPort(port); + emailConfigs.setFrom(from); + emailConfigs.setSslEnable(ssl); + return emailConfigs; + } + + /** + * 获取腾讯云短信的配置 + * + * @author yubaoshan + * @date 2020/6/19 18:08 + */ + public static TencentSmsConfigs getTencentSmsConfigs() { + String snowyTencentSmsSecretId = getSysConfig("SNOWY_TENCENT_SMS_SECRET_ID", String.class, true); + String snowyTencentSmsSecretKey = getSysConfig("SNOWY_TENCENT_SMS_SECRET_KEY", String.class, true); + String snowyTencentSmsSdkAppId = getSysConfig("SNOWY_TENCENT_SMS_SDK_APP_ID", String.class, true); + String snowyTencentSmsSign = getSysConfig("SNOWY_TENCENT_SMS_SIGN", String.class, true); + + TencentSmsConfigs tencentSmsConfigs = new TencentSmsConfigs(); + tencentSmsConfigs.setSecretId(snowyTencentSmsSecretId); + tencentSmsConfigs.setSecretKey(snowyTencentSmsSecretKey); + tencentSmsConfigs.setSdkAppId(snowyTencentSmsSdkAppId); + tencentSmsConfigs.setSign(snowyTencentSmsSign); + return tencentSmsConfigs; + } + + /** + * 获取阿里云短信的配置 + * + * @author yubaoshan + * @date 2020/6/19 18:08 + */ + public static AliyunSmsConfigs getAliyunSmsConfigs() { + String snowySmsAccesskeyId = getSysConfig("SNOWY_ALIYUN_SMS_ACCESSKEY_ID", String.class, true); + String snowySmsAccesskeySecret = getSysConfig("SNOWY_ALIYUN_SMS_ACCESSKEY_SECRET", String.class, true); + String snowySmsSignName = getSysConfig("SNOWY_ALIYUN_SMS_SIGN_NAME", String.class, true); + String snowySmsLoginTemplateCode = getSysConfig("SNOWY_ALIYUN_SMS_LOGIN_TEMPLATE_CODE", String.class, true); + String snowySmsInvalidateMinutes = getSysConfig("SNOWY_ALIYUN_SMS_INVALIDATE_MINUTES", String.class, true); + + AliyunSmsConfigs aliyunSmsProperties = new AliyunSmsConfigs(); + aliyunSmsProperties.setAccessKeyId(snowySmsAccesskeyId); + aliyunSmsProperties.setAccessKeySecret(snowySmsAccesskeySecret); + aliyunSmsProperties.setSignName(snowySmsSignName); + aliyunSmsProperties.setLoginTemplateCode(snowySmsLoginTemplateCode); + aliyunSmsProperties.setInvalidateMinutes(Convert.toInt(snowySmsInvalidateMinutes)); + return aliyunSmsProperties; + } + + /** + * 获取jwt密钥,默认是32位随机字符串 + * + * @author yubaoshan + * @date 2020/6/19 18:08 + */ + public static String getJwtSecret() { + return getSysConfigWithDefault("SNOWY_JWT_SECRET", String.class, RandomUtil.randomString(32)); + } + + /** + * 获取默认密码 + * + * @author yubaoshan + * @date 2020/6/19 18:08 + */ + public static String getDefaultPassWord() { + return getSysConfigWithDefault("SNOWY_DEFAULT_PASSWORD", String.class, CommonConstant.DEFAULT_PASSWORD); + } + + /** + * 获取会话过期时间,默认2小时 + * + * @author yubaoshan + * @date 2020/7/9 16:18 + */ + public static Long getSessionTokenExpireSec() { + return getSysConfigWithDefault("SNOWY_SESSION_EXPIRE", Long.class, 2 * 60 * 60L); + } + + /** + * 获取token过期时间(单位:秒) + * <p> + * 默认时间1天 + * + * @author xuyuxiang + * @date 2020/6/19 18:08 + */ + public static Long getTokenExpireSec() { + return getSysConfigWithDefault("SNOWY_TOKEN_EXPIRE", Long.class, 86400L); + } + + /** + * 获取自定义的windows环境本地文件上传路径 + * + * @author xuyuxiang + * @date 2020/6/19 18:09 + */ + public static String getDefaultFileUploadPathForWindows() { + return getSysConfigWithDefault("SNOWY_FILE_UPLOAD_PATH_FOR_WINDOWS", String.class, ""); + } + + /** + * 获取自定义的linux环境本地文件上传路径 + * + * @author xuyuxiang + * @date 2020/6/19 18:09 + */ + public static String getDefaultFileUploadPathForLinux() { + return getSysConfigWithDefault("SNOWY_FILE_UPLOAD_PATH_FOR_LINUX", String.class, ""); + } + + /** + * 获取是否允许单用户登陆的开关, 默认为false + * + * @author xuyuxiang + * @date 2020/6/19 18:09 + */ + public static Boolean getEnableSingleLogin() { + return getSysConfigWithDefault("SNOWY_ENABLE_SINGLE_LOGIN", Boolean.class, false); + } + + /** + * 获取阿里云定位接口 + * + * @author xuyuxiang + * @date 2020/7/20 9:20 + **/ + public static String getIpGeoApi() { + return getSysConfig("SNOWY_IP_GEO_API", String.class, false); + } + + /** + * 获取阿里云定位appCode + * + * @author xuyuxiang + * @date 2020/7/20 10:33 + **/ + public static String getIpGeoAppCode() { + return getSysConfig("SNOWY_IP_GEO_APP_CODE", String.class, false); + } + + /** + * 获取Oauth码云第三方登录的配置 + * + * @author xuyuxiang + * @date 2020/7/28 17:16 + **/ + public static OauthConfigs getGiteeOauthConfigs() { + String snowyClientId = getSysConfig("SNOWY_OAUTH_GITEE_CLIENT_ID", String.class, true); + String snowyClientSecret = getSysConfig("SNOWY_OAUTH_GITEE_CLIENT_SECRET", String.class, true); + String snowyRedirectUri = getSysConfig("SNOWY_OAUTH_GITEE_REDIRECT_URI", String.class, true); + + OauthConfigs oauthConfigs = new OauthConfigs(); + oauthConfigs.setClientId(snowyClientId); + oauthConfigs.setClientSecret(snowyClientSecret); + oauthConfigs.setRedirectUri(snowyRedirectUri); + return oauthConfigs; + } + + /** + * 获取OauthGithub第三方登录的配置 + * + * @author xuyuxiang + * @date 2020/7/28 17:16 + **/ + public static OauthConfigs getGithubOauthConfigs() { + String snowyClientId = getSysConfig("SNOWY_OAUTH_GITHUB_CLIENT_ID", String.class, true); + String snowyClientSecret = getSysConfig("SNOWY_OAUTH_GITHUB_CLIENT_SECRET", String.class, true); + String snowyRedirectUri = getSysConfig("SNOWY_OAUTH_GITHUB_REDIRECT_URI", String.class, true); + + OauthConfigs oauthConfigs = new OauthConfigs(); + oauthConfigs.setClientId(snowyClientId); + oauthConfigs.setClientSecret(snowyClientSecret); + oauthConfigs.setRedirectUri(snowyRedirectUri); + return oauthConfigs; + } + + /** + * 获取是否允许Oauth用户登陆的开关, 默认为false + * + * @author xuyuxiang + * @date 2020/7/28 16:37 + **/ + public static Boolean getEnableOauthLogin() { + return getSysConfigWithDefault("SNOWY_ENABLE_OAUTH_LOGIN", Boolean.class, false); + } + + /** + * 获取前端项目的地址 + * + * @author xuyuxiang + * @date 2020/7/29 14:08 + **/ + public static String getWebUrl() { + return getSysConfig("SNOWY_WEB_URL", String.class, true); + } + + /** + * 获取支付宝支付成功转发地址 + * + * @author xuyuxiang + * @date 2020/7/29 14:08 + **/ + public static String getAlipayReturnUrl() { + return getSysConfig("SNOWY_ALIPAY_RETURN_URL", String.class, true); + } + + /** + * 获取OnlyOffice地址 + * + * @author xuyuxiang + * @date 2020/7/29 14:08 + **/ + public static String getOnlyOfficeUrl() { + return getSysConfig("SNOWY_ONLY_OFFICE_SERVICE_URL", String.class, true); + } + + /** + * 获取config表中的配置,如果为空返回默认值 + * + * @param configCode 变量名称,对应sys_config表中的code + * @param clazz 返回变量值的类型 + * @param defaultValue 如果结果为空返回此默认值 + * @author yubaoshan + * @date 2020/6/20 22:03 + */ + public static <T> T getSysConfigWithDefault(String configCode, Class<T> clazz, T defaultValue) { + String configValue = ConstantContext.me().getStr(configCode); + if (ObjectUtil.isEmpty(configValue)) { + // 将默认值加入到缓存常量 + log.warn(">>> 系统配置sys_config表中存在空项,configCode为:{},系统采用默认值:{}", configCode, defaultValue); + ConstantContext.me().put(configCode, defaultValue); + return defaultValue; + } else { + try { + return Convert.convert(clazz, configValue); + } catch (Exception e) { + return defaultValue; + } + } + } + + /** + * 获取config表中的配置,如果为空,是否抛出异常 + * + * @param configCode 变量名称,对应sys_config表中的code + * @param clazz 返回变量值的类型 + * @param nullThrowExp 若为空是否抛出异常 + * @author yubaoshan + * @date 2020/6/20 22:03 + */ + public static <T> T getSysConfig(String configCode, Class<T> clazz, Boolean nullThrowExp) { + String configValue = ConstantContext.me().getStr(configCode); + if (ObjectUtil.isEmpty(configValue)) { + if (nullThrowExp) { + String format = StrUtil.format(">>> 系统配置sys_config表中存在空项,configCode为:{}", configCode); + log.error(format); + throw new ServiceException(CONSTANT_EMPTY.getCode(), format); + } else { + return null; + } + } else { + try { + return Convert.convert(clazz, configValue); + } catch (Exception e) { + if (nullThrowExp) { + String format = StrUtil.format(">>> 系统配置sys_config表中存在格式错误的值,configCode={},configValue={}", configCode, configValue); + log.error(format); + throw new ServiceException(CONSTANT_EMPTY.getCode(), format); + } else { + return null; + } + } + } + } + + /** + * 获取验证码 开关标识 + * + * @author Jax + * @Date 2021/1/21 15:22 + */ + public static Boolean getCaptchaOpenFlag() { + return getSysConfigWithDefault("SNOWY_CAPTCHA_OPEN", Boolean.class, true); + } + + /** + * 获取加解密的配置 + * + * @author yubaoshan + */ + public static CryptogramConfigs getCryptogramConfigs() { + boolean snowyTokenEncDec = getSysConfigWithDefault("SNOWY_TOKEN_ENCRYPTION_OPEN", Boolean.class, true); + boolean snowyVisLogEnc = getSysConfigWithDefault("SNOWY_VISLOG_ENCRYPTION_OPEN", Boolean.class, true); + boolean snowyOpLogEnc = getSysConfigWithDefault("SNOWY_OPLOG_ENCRYPTION_OPEN", Boolean.class, true); + boolean snowyFieldEncDec = getSysConfigWithDefault("SNOWY_FIELD_ENC_DEC_OPEN", Boolean.class, true); + + CryptogramConfigs cryptogramConfigs = new CryptogramConfigs(); + cryptogramConfigs.setTokenEncDec(snowyTokenEncDec); + cryptogramConfigs.setVisLogEnc(snowyVisLogEnc); + cryptogramConfigs.setOpLogEnc(snowyOpLogEnc); + cryptogramConfigs.setFieldEncDec(snowyFieldEncDec); + return cryptogramConfigs; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/group/RequestGroupContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/group/RequestGroupContext.java new file mode 100644 index 0000000..74c42dc --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/group/RequestGroupContext.java @@ -0,0 +1,67 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.group; + +/** + * 保存控制器的方法上的校验group class + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ +public class RequestGroupContext { + + private static final ThreadLocal<Class<?>> GROUP_CLASS_HOLDER = new ThreadLocal<>(); + + /** + * 设置临时的group class + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void set(Class<?> groupValue) { + GROUP_CLASS_HOLDER.set(groupValue); + } + + /** + * 获取临时缓存的group class + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static Class<?> get() { + return GROUP_CLASS_HOLDER.get(); + } + + /** + * 清除临时缓存的group class + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void clear() { + GROUP_CLASS_HOLDER.remove(); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/group/RequestParamIdContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/group/RequestParamIdContext.java new file mode 100644 index 0000000..7e1932e --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/group/RequestParamIdContext.java @@ -0,0 +1,69 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.group; + +/** + * 临时保存参数id字段值,用于唯一性校验 + * <p> + * 注意:如果要用@TableUniqueValue这个校验,必须得主键的字段名是id,否则会校验失败 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ +public class RequestParamIdContext { + + private static final ThreadLocal<Long> PARAM_ID_HOLDER = new ThreadLocal<>(); + + /** + * 设置id + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void set(Long id) { + PARAM_ID_HOLDER.set(id); + } + + /** + * 获取id + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static Long get() { + return PARAM_ID_HOLDER.get(); + } + + /** + * 清除缓存id + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void clear() { + PARAM_ID_HOLDER.remove(); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/login/LoginContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/login/LoginContext.java new file mode 100644 index 0000000..52b33e2 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/login/LoginContext.java @@ -0,0 +1,168 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.login; + +import vip.xiaonuo.core.pojo.login.SysLoginUser; + +import java.util.List; + +/** + * 登录用户上下文 + * + * @author xuyuxiang + * @date 2020/3/13 12:16 + */ +public interface LoginContext { + + /** + * 获取当前登录用户 + * + * @return 当前登录用户信息 + * @author xuyuxiang + * @date 2020/3/13 14:40 + */ + SysLoginUser getSysLoginUser(); + + /** + * 获取当前登录用户,如未登录,则返回null,不抛异常 + * + * @return 当前登录用户信息 + * @author xuyuxiang + * @date 2020/3/13 14:40 + */ + SysLoginUser getSysLoginUserWithoutException(); + + /** + * 获取当前登录用户的id + * + * @return 当前登录用户的id + * @author xuyuxiang + * @date 2020/3/18 19:25 + */ + Long getSysLoginUserId(); + + /** + * 判断用户是否登录 + * + * @return 是否登录,true是,false否 + * @author xuyuxiang + * @date 2020/3/18 19:22 + */ + boolean hasLogin(); + + /** + * 获取当前登录用户的账户 + * + * @return 当前登陆用户的账户account + * @author xuyuxiang + * @date 2020/3/19 20:37 + */ + String getSysLoginUserAccount(); + + /** + * 判断当前登录用户是否有某资源的访问权限 + * + * @param requestUri 请求的url + * @return 是否有访问权限,true是,false否 + * @author xuyuxiang + * @date 2020/3/23 8:48 + */ + boolean hasPermission(String requestUri); + + /** + * 判断当前登录用户是否包含某个角色 + * + * @param roleCode 角色编码 + * @return 是否包含该角色,true是,false否 + * @author xuyuxiang + * @date 2020/3/23 8:53 + */ + boolean hasRole(String roleCode); + + /** + * 判断当前登录用户是否包含任意一个角色 + * + * @param roleCodes 角色集合,逗号拼接 + * @return 是否包含任一角色,true是,false否 + * @author xuyuxiang + * @date 2020/3/23 8:54 + */ + boolean hasAnyRole(String roleCodes); + + /** + * 判断当前登录用户是否是超级管理员 + * + * @return 当前登录用户是否是超级管理员 + * @author xuyuxiang + * @date 2020/3/23 17:50 + */ + boolean isSuperAdmin(); + + /** + * 判断当前登录用户是否包含所有角色 + * + * @param roleCodes 角色集合,逗号拼接 + * @return 是否包含所有角色,true是,false否 + * @author xuyuxiang + * @date 2020/4/5 10:28 + */ + boolean hasAllRole(String roleCodes); + + /** + * 获取当前登录用户的数据范围集合(组织机构id集合) + * + * @return 数据范围集合(组织机构id集合) + * @author xuyuxiang + * @date 2020/4/5 17:20 + */ + List<Long> getLoginUserDataScopeIdList(); + + /** + * 获取当前登录用户的组织机构id + * + * @return 当前登录用户的组织机构id + * @author xuyuxiang + * @date 2020/4/5 18:31 + */ + Long getSysLoginUserOrgId(); + + /** + * 获取当前登录用户的角色id集合 + * + * @return 当前登录用户角色id集合 + * @author xuyuxiang + * @date 2020/4/20 16:04 + */ + List<String> getLoginUserRoleIds(); + + /** + * 获取最新的用户信息,用于修改之后前端获取 + * + * @return 最新的用户信息 + * @author xuyuxiang + * @date 2020/9/20 15:18 + **/ + SysLoginUser getSysLoginUserUpToDate(); +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/login/LoginContextHolder.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/login/LoginContextHolder.java new file mode 100644 index 0000000..a0da903 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/login/LoginContextHolder.java @@ -0,0 +1,41 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.login; + +import cn.hutool.extra.spring.SpringUtil; + +/** + * 当前登录用户信息获取的接口 + * + * @author xuyuxiang + * @date 2020/3/13 12:21 + */ +public class LoginContextHolder { + + public static LoginContext me() { + return SpringUtil.getBean(LoginContext.class); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/param/RequestParamContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/param/RequestParamContext.java new file mode 100644 index 0000000..d3984b5 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/param/RequestParamContext.java @@ -0,0 +1,89 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.param; + +import cn.hutool.core.lang.Dict; + +/** + * 临时保存http请求的参数 + * <p> + * 可以保存@RequestBody的可以保存parameter方式传参的 + * + * @author xuyuxiang + * @date 2020/8/20 + */ +public class RequestParamContext { + + private static final ThreadLocal<Dict> CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 保存请求参数 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void set(Dict requestParam) { + CONTEXT_HOLDER.set(requestParam); + } + + /** + * 保存请求参数 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void setObject(Object requestParam) { + + if (requestParam == null) { + return; + } + + if (requestParam instanceof Dict) { + CONTEXT_HOLDER.set((Dict) requestParam); + } else { + CONTEXT_HOLDER.set(Dict.parse(requestParam)); + } + } + + /** + * 获取请求参数 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static Dict get() { + return CONTEXT_HOLDER.get(); + } + + /** + * 清除请求参数 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void clear() { + CONTEXT_HOLDER.remove(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/requestno/RequestNoContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/requestno/RequestNoContext.java new file mode 100644 index 0000000..ae16272 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/requestno/RequestNoContext.java @@ -0,0 +1,66 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.requestno; + +/** + * 临时保存当前请求号 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ +public class RequestNoContext { + + private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 保存请求号 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void set(String requestNo) { + CONTEXT_HOLDER.set(requestNo); + } + + /** + * 获取请求号 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static String get() { + return CONTEXT_HOLDER.get(); + } + + /** + * 清除请求号 + * + * @author yubaoshan + * @date 2020/6/21 20:17 + */ + public static void clear() { + CONTEXT_HOLDER.remove(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/resources/ApiResourceContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/resources/ApiResourceContext.java new file mode 100644 index 0000000..7962e6e --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/resources/ApiResourceContext.java @@ -0,0 +1,93 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.resources; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.util.Set; + +/** + * 存放本系统所有@RequestMapping的Url + * + * @author yubaoshan + * @date 2020/6/21 17:32 + */ +public class ApiResourceContext { + + /** + * 所有资源的url + */ + private static final Set<String> API_URLS = CollectionUtil.newHashSet(); + + /** + * 添加一批url + * + * @author yubaoshan + * @date 2020/6/21 17:35 + */ + public static void addBatchUrls(Set<String> urls) { + if (ObjectUtil.isEmpty(urls)) { + return; + } + API_URLS.addAll(urls); + } + + /** + * 添加url + * + * @author yubaoshan + * @date 2020/6/21 17:35 + */ + public static void addUrl(String url) { + if (ObjectUtil.isEmpty(url)) { + return; + } + API_URLS.add(url); + } + + /** + * 删除url + * + * @author yubaoshan + * @date 2020/6/21 17:35 + */ + public static void deleteUrl(String url) { + if (ObjectUtil.isEmpty(url)) { + return; + } + API_URLS.remove(url); + } + + /** + * 获取系统的所有url + * + * @author yubaoshan + * @date 2020/6/21 17:36 + */ + public static Set<String> getApiUrls() { + return API_URLS; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/system/SystemContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/system/SystemContext.java new file mode 100644 index 0000000..4d8fd06 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/system/SystemContext.java @@ -0,0 +1,142 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.system; + +import cn.hutool.core.lang.Dict; +import vip.xiaonuo.core.pojo.base.validate.UniqueValidateParam; +import vip.xiaonuo.core.pojo.login.SysLoginUser; + +import java.util.List; + +/** + * 系统相关上下文接口,在system模块实现,用于某些模块不能引用system模块时,通过此方式调用system相关功能 + * + * @author xuyuxiang + * @date 2020/5/6 14:52 + */ +public interface SystemContext { + + /** + * 根据用户id获取姓名 + * + * @param userId 用户id + * @return 用户姓名 + * @author xuyuxiang + * @date 2020/5/6 14:57 + */ + String getNameByUserId(Long userId); + + /** + * 根据角色id获取角色名称 + * + * @param roleId 角色id + * @return 角色名称 + * @author xuyuxiang + * @date 2020/5/22 15:55 + */ + String getNameByRoleId(Long roleId); + + /** + * 根据token获取登录用户信息 + * + * @param token token + * @return 登录用户信息 + * @author xuyuxiang + * @date 2020/3/13 11:59 + */ + SysLoginUser getLoginUserByToken(String token); + + /** + * 根据用户账号模糊搜索系统用户列表 + * + * @param account 账号 + * @return 增强版hashMap,格式:[{"id:":123, "firstName":"张三"}] + * @author xuyuxiang + * @date 2020/6/1 15:12 + */ + List<Dict> listUser(String account); + + /** + * 根据角色名模糊搜索系统角色列表 + * + * @param name 角色名 + * @return 增强版hashMap,格式:[{"id:":123, "name":"总经理"}] + * @author xuyuxiang + * @date 2020/6/1 15:13 + */ + List<Dict> listRole(String name); + + /** + * 根据id判断是否是用户 + * + * @param userOrRoleId 用户或角色id + * @return true是 false否 + * @author xuyuxiang + * @date 2020/8/4 20:56 + */ + boolean isUser(Long userOrRoleId); + + /** + * 根据id判断是否是角色 + * + * @param userOrRoleId 用户或角色id + * @return true是 false否 + * @author xuyuxiang + * @date 2020/8/4 20:56 + */ + boolean isRole(Long userOrRoleId); + + /** + * 根据字典类型获取字典的code值 + * + * @param dictTypeCodes 字典类型编码值 + * @return 字典的code值 + * @author xuyuxiang + * @date 2020/8/9 14:18 + */ + List<String> getDictCodesByDictTypeCode(String... dictTypeCodes); + + /** + * 校验某个表中,某一列是否存在重复的值 + * <p> + * 一般用于唯一code校验 + * + * @param uniqueValidateParam 被校验的参数 + * @return true-是唯一的值,false-不是唯一的 + * @author xuyuxiang + * @date 2020/8/9 21:41 + */ + boolean tableUniValueFlag(UniqueValidateParam uniqueValidateParam); + + /** + * 获取系统用户id集合 + * + * @return 用户id集合 + * @author xuyuxiang + * @date 2020/9/11 17:53 + **/ + List<Long> getAllUserIdList(); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/system/SystemContextHolder.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/system/SystemContextHolder.java new file mode 100644 index 0000000..f974a23 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/context/system/SystemContextHolder.java @@ -0,0 +1,41 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.context.system; + +import cn.hutool.extra.spring.SpringUtil; + +/** + * 使用system模块相关功能的接口 + * + * @author xuyuxiang + * @date 2020/5/6 14:56 + */ +public class SystemContextHolder { + + public static SystemContext me() { + return SpringUtil.getBean(SystemContext.class); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cryptogram/keypair.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cryptogram/keypair.java new file mode 100644 index 0000000..e427462 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/cryptogram/keypair.java @@ -0,0 +1,51 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.cryptogram; + +/** + * 基于SM2的秘钥对 + * (本项目中配置的,自己使用可根据自己的需求进行更换) + * + * @author yubaoshan + */ +public class keypair { + + /** + * 公钥 + */ + public static String PUBLIC_KEY = "04298364ec840088475eae92a591e01284d1abefcda348b47eb324bb521bb03b0b2a5bc393f6b71dabb8f15c99a0050818b56b23f31743b93df9cf8948f15ddb54"; + + /** + * 私钥 + */ + public static String PRIVATE_KEY = "3037723d47292171677ec8bd7dc9af696c7472bc5f251b2cec07e65fdef22e25"; + + /** + * SM4的对称秘钥(生产环境需要改成自己使用的) + * 16 进制字符串,要求为 128 比特 + */ + public static String KEY = "0123456789abcdeffedcba9876543210"; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/dbs/CurrentDataSourceContext.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/dbs/CurrentDataSourceContext.java new file mode 100644 index 0000000..cdeaf89 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/dbs/CurrentDataSourceContext.java @@ -0,0 +1,67 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.dbs; + +/** + * datasource的上下文 + * + * @author xuyuxiang + * @date 2020/8/24 + */ +public class CurrentDataSourceContext { + + private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源类型 + * + * @param dataSourceType 数据库类型 + * @date 2020/8/24 + */ + public static void setDataSourceType(String dataSourceType) { + CONTEXT_HOLDER.set(dataSourceType); + } + + /** + * 获取数据源类型 + * + * @author xuyuxiang + * @date 2020/8/24 + */ + public static String getDataSourceType() { + return CONTEXT_HOLDER.get(); + } + + /** + * 清除数据源类型 + * + * @author xuyuxiang + * @date 2020/8/24 + */ + public static void clearDataSourceType() { + CONTEXT_HOLDER.remove(); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/MailSender.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/MailSender.java new file mode 100644 index 0000000..58744b3 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/MailSender.java @@ -0,0 +1,54 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.email; + + +import vip.xiaonuo.core.email.modular.model.SendMailParam; + +/** + * 邮件收发统一接口 + * + * @author xuyuxiang + * @date 2018-07-08-下午3:26 + */ +public interface MailSender { + + /** + * 发送普通邮件 + * + * @author xuyuxiang + * @date 2018/7/8 下午3:34 + */ + void sendMail(SendMailParam sendMailParam); + + /** + * 发送html的邮件 + * + * @author xuyuxiang + * @date 2020/6/9 22:58 + */ + void sendMailHtml(SendMailParam sendMailParam); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/SimpleMailSender.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/SimpleMailSender.java new file mode 100644 index 0000000..109f798 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/SimpleMailSender.java @@ -0,0 +1,96 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.email.modular; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.mail.MailAccount; +import cn.hutool.extra.mail.MailUtil; +import vip.xiaonuo.core.email.MailSender; +import vip.xiaonuo.core.email.modular.exception.MailSendException; +import vip.xiaonuo.core.email.modular.model.SendMailParam; + +/** + * 邮件发送器 + * + * @author xuyuxiang + * @date 2020/6/9 22:54 + */ +public class SimpleMailSender implements MailSender { + + /** + * 邮件配置 + */ + private final MailAccount mailAccount; + + public SimpleMailSender(MailAccount mailAccount) { + this.mailAccount = mailAccount; + } + + @Override + public void sendMail(SendMailParam sendMailParam) { + + //校验发送邮件的参数 + assertSendMailParams(sendMailParam); + + //spring发送邮件 + MailUtil.send(mailAccount, CollUtil.newArrayList(sendMailParam.getTo()), sendMailParam.getTitle(), sendMailParam.getContent(), false); + } + + @Override + public void sendMailHtml(SendMailParam sendMailParam) { + + //校验发送邮件的参数 + assertSendMailParams(sendMailParam); + + //spring发送邮件 + MailUtil.send(mailAccount, CollUtil.newArrayList(sendMailParam.getTo()), sendMailParam.getTitle(), sendMailParam.getContent(), true); + } + + /** + * 校验发送邮件的请求参数 + * + * @author xuyuxiang + * @date 2018/7/8 下午6:41 + */ + private void assertSendMailParams(SendMailParam sendMailParam) { + if (sendMailParam == null) { + throw new MailSendException(400, "请求参数为空"); + } + + if (ObjectUtil.isEmpty(sendMailParam.getTo())) { + throw new MailSendException(400, "收件人邮箱为空"); + } + + if (ObjectUtil.isEmpty(sendMailParam.getTitle())) { + throw new MailSendException(400, "邮件标题为空"); + } + + if (ObjectUtil.isEmpty(sendMailParam.getContent())) { + throw new MailSendException(400, "邮件内容为空"); + } + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/exception/MailSendException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/exception/MailSendException.java new file mode 100644 index 0000000..7c2c122 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/exception/MailSendException.java @@ -0,0 +1,48 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.email.modular.exception; + +import lombok.Getter; + +/** + * 邮件发送异常 + * + * @author xuyuxiang + * @date 2018-07-06-下午3:00 + */ +@Getter +public class MailSendException extends RuntimeException { + + private final Integer code; + + private final String errorMessage; + + public MailSendException(Integer code, String errorMessage) { + super(errorMessage); + this.code = code; + this.errorMessage = errorMessage; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/model/SendMailParam.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/model/SendMailParam.java new file mode 100644 index 0000000..f05d4f9 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/email/modular/model/SendMailParam.java @@ -0,0 +1,52 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.email.modular.model; + +import lombok.Data; + +/** + * 发送邮件的请求参数 + * + * @author xuyuxiang + * @date 2018-07-05 21:19 + */ +@Data +public class SendMailParam { + + /** + * 发送给某人的邮箱 + */ + private String to; + + /** + * 邮件标题 + */ + private String title; + + /** + * 邮件内容 + */ + private String content; +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/CommonStatusEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/CommonStatusEnum.java new file mode 100644 index 0000000..6140f1d --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/CommonStatusEnum.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.enums; + +import lombok.Getter; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.StatusExceptionEnum; + +/** + * 公共状态 + * + * @author yubaoshan + * @date 2017/1/22 12:14 + */ +@Getter +public enum CommonStatusEnum { + + /** + * 正常 + */ + ENABLE(0, "正常"), + + /** + * 停用 + */ + DISABLE(1, "停用"), + + /** + * 删除 + */ + DELETED(2, "删除"); + + private final Integer code; + + private final String message; + + CommonStatusEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + /** + * 检查请求参数的状态是否正确 + * + * @author yubaoshan + * @date 2020/4/30 22:43 + */ + public static void validateStatus(Integer code) { + if (code == null) { + throw new ServiceException(StatusExceptionEnum.REQUEST_EMPTY); + } + if (ENABLE.getCode().equals(code) || DISABLE.getCode().equals(code)) { + return; + } + throw new ServiceException(StatusExceptionEnum.NOT_WRITE_STATUS); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/DbIdEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/DbIdEnum.java new file mode 100644 index 0000000..d0a15c6 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/DbIdEnum.java @@ -0,0 +1,79 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.enums; + +import lombok.Getter; + +/** + * 不同数据库类型的枚举 + * <p> + * 用于标识mapping.xml中不同数据库的标识 + * + * @author yubaoshan + * @date 2020/6/20 21:08 + */ +@Getter +public enum DbIdEnum { + + /** + * mysql + */ + MYSQL("mysql", "mysql"), + + /** + * pgsql + */ + PG_SQL("pgsql", "postgresql"), + + /** + * oracle + */ + ORACLE("oracle", "oracle"), + + /** + * mssql + */ + MS_SQL("mssql", "sqlserver"), + + /** + * 达梦数据库 + */ + DM_SQL("dm", "dm"), + + /** + * 人大金仓数据库 + */ + KINGBASE_ES("kingbase", "kingbasees"); + + private final String code; + + private final String name; + + DbIdEnum(String code, String name) { + this.code = code; + this.name = name; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/DocumentFormatEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/DocumentFormatEnum.java new file mode 100644 index 0000000..f26c078 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/DocumentFormatEnum.java @@ -0,0 +1,130 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.enums; + +import org.jodconverter.document.DefaultDocumentFormatRegistry; +import org.jodconverter.document.DocumentFormat; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 文档类型枚举 + * + * @author xuyuxiang + * @date 2020/7/6 15:00 + */ +public enum DocumentFormatEnum { + + /** + * 文档doc格式 + */ + DOC { + @Override + public DocumentFormat getFormFormat() { + return DefaultDocumentFormatRegistry.DOC; + } + }, + + /** + * 文档docx格式 + */ + DOCX { + @Override + public DocumentFormat getFormFormat() { + return DefaultDocumentFormatRegistry.DOCX; + } + }, + + /** + * 演示文稿ppt格式 + */ + PPT { + @Override + public DocumentFormat getFormFormat() { + return DefaultDocumentFormatRegistry.PPT; + } + }, + + /** + * 演示文稿pptx格式 + */ + PPTX { + @Override + public DocumentFormat getFormFormat() { + return DefaultDocumentFormatRegistry.PPTX; + } + }, + + /** + * 电子表格xls格式 + */ + XLS { + @Override + public DocumentFormat getFormFormat() { + return DefaultDocumentFormatRegistry.XLS; + } + + @Override + public DocumentFormat getTargetFormat() { + return DefaultDocumentFormatRegistry.HTML; + } + }, + + /** + * 电子表格xlsx格式 + */ + XLSX { + @Override + public DocumentFormat getFormFormat() { + return DefaultDocumentFormatRegistry.XLSX; + } + + @Override + public DocumentFormat getTargetFormat() { + return DefaultDocumentFormatRegistry.HTML; + } + }, + + /** + * 文本txt格式 + */ + TXT { + @Override + public DocumentFormat getFormFormat() { + return DefaultDocumentFormatRegistry.TXT; + } + }; + + public InputStream getInputStream(InputStream inputStream) throws IOException { + return inputStream; + } + + public abstract DocumentFormat getFormFormat(); + + public DocumentFormat getTargetFormat() { + return DefaultDocumentFormatRegistry.PDF; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/LogAnnotionOpTypeEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/LogAnnotionOpTypeEnum.java new file mode 100644 index 0000000..d713e4b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/LogAnnotionOpTypeEnum.java @@ -0,0 +1,107 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.enums; + +import lombok.Getter; + +/** + * 日志注解操作类型枚举 + * + * @author xuyuxiang + * @date 2020/3/16 17:45 + */ +@Getter +public enum LogAnnotionOpTypeEnum { + + /** + * 其它 + */ + OTHER, + + /** + * 增加 + */ + ADD, + + /** + * 删除 + */ + DELETE, + + /** + * 编辑 + */ + EDIT, + + /** + * 更新 + */ + UPDATE, + + /** + * 查询 + */ + QUERY, + + /** + * 详情 + */ + DETAIL, + + /** + * 树 + */ + TREE, + + /** + * 导入 + */ + IMPORT, + + /** + * 导出 + */ + EXPORT, + + /** + * 授权 + */ + GRANT, + + /** + * 强退 + */ + FORCE, + + /** + * 清空 + */ + CLEAN, + + /** + * 修改状态 + */ + CHANGE_STATUS +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/LogicTypeEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/LogicTypeEnum.java new file mode 100644 index 0000000..a4d2df2 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/LogicTypeEnum.java @@ -0,0 +1,44 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.enums; + +/** + * 逻辑枚举 + * + * @author xuyuxiang + * @date 2020/4/5 10:23 + */ +public enum LogicTypeEnum { + + /** + * 与 + */ + AND, + + /** + * 或 + */ + OR +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/YesOrNotEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/YesOrNotEnum.java new file mode 100644 index 0000000..9fbeb19 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/enums/YesOrNotEnum.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.enums; + +import lombok.Getter; + +/** + * 是或否的枚举 + * + * @author yubaoshan + * @date 2020/4/13 22:59 + */ +@Getter +public enum YesOrNotEnum { + + /** + * 是 + */ + Y("Y", "是"), + + /** + * 否 + */ + N("N", "否"); + + private final String code; + + private final String message; + + YesOrNotEnum(String code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/AuthException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/AuthException.java new file mode 100644 index 0000000..0ecef27 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/AuthException.java @@ -0,0 +1,54 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception; + +import lombok.Getter; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; + +/** + * 认证相关的异常 + * <p> + * 认证和鉴权的区别: + * <p> + * 认证可以证明你能登录系统,认证的过程是校验token的过程 + * 鉴权可以证明你有系统的哪些权限,鉴权的过程是校验角色是否包含某些接口的权限 + * + * @author xuyuxiang + * @date 2020/3/12 9:55 + */ +@Getter +public class AuthException extends RuntimeException { + + private final Integer code; + + private final String errorMessage; + + public AuthException(AbstractBaseExceptionEnum exception) { + super(exception.getMessage()); + this.code = exception.getCode(); + this.errorMessage = exception.getMessage(); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/DemoException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/DemoException.java new file mode 100644 index 0000000..8d81b22 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/DemoException.java @@ -0,0 +1,46 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception; + +import lombok.Getter; + +/** + * 演示环境,无法操作的异常 + * + * @author yubaoshan + * @date 2020/5/5 12:22 + */ +@Getter +public class DemoException extends ServiceException { + + private static final int DEMO_EXP_CODE = 14000; + + private static final String DEMO_EXP_ERROR_MESSAGE = "演示环境,无法操作!"; + + public DemoException() { + super(DEMO_EXP_CODE, DEMO_EXP_ERROR_MESSAGE); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/LibreOfficeException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/LibreOfficeException.java new file mode 100644 index 0000000..9435e21 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/LibreOfficeException.java @@ -0,0 +1,46 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception; + +import lombok.Getter; + +/** + * LibreOffice相关异常 + * + * @author xuyuxiang + * @date 2020/7/7 11:08 + */ +@Getter +public class LibreOfficeException extends ServiceException { + + private static final int LIBRE_OFFICE_EXP_CODE = 15000; + + private static final String LIBRE_OFFICE_EXP_ERROR_MESSAGE = "LibreOffice初始化异常,请检查LibreOffice是否启动!"; + + public LibreOfficeException() { + super(LIBRE_OFFICE_EXP_CODE, LIBRE_OFFICE_EXP_ERROR_MESSAGE); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/PermissionException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/PermissionException.java new file mode 100644 index 0000000..2d3932c --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/PermissionException.java @@ -0,0 +1,55 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception; + +import lombok.Getter; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; + +/** + * 授权和鉴权异常 + * <p> + * 认证和鉴权的区别: + * <p> + * 认证可以证明你能登录系统,认证的过程是校验token的过程 + * 鉴权可以证明你有系统的哪些权限,鉴权的过程是校验角色是否包含某些接口的权限 + * 也包含当前用户是否有操作该数据的权限 + * + * @author yubaoshan + * @date 2019/7/18 22:18 + */ +@Getter +public class PermissionException extends RuntimeException { + + private final Integer code; + + private final String errorMessage; + + public PermissionException(AbstractBaseExceptionEnum exception) { + super(exception.getMessage()); + this.code = exception.getCode(); + this.errorMessage = exception.getMessage(); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/RequestMethodException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/RequestMethodException.java new file mode 100644 index 0000000..a1ad04f --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/RequestMethodException.java @@ -0,0 +1,48 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception; + +import lombok.Getter; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; + +/** + * 请求方法异常 + * + * @author xuyuxiang + * @date 2020/3/11 15:35 + */ +@Getter +public class RequestMethodException extends RuntimeException { + + private final Integer code; + + private final String errorMessage; + + public RequestMethodException(AbstractBaseExceptionEnum exception) { + super(exception.getMessage()); + this.code = exception.getCode(); + this.errorMessage = exception.getMessage(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/ServiceException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/ServiceException.java new file mode 100644 index 0000000..5ecca85 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/ServiceException.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; + +/** + * 业务异常 + * + * @author xuyuxiang + * @date 2020/4/8 15:54 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ServiceException extends RuntimeException { + + private Integer code; + + private String errorMessage; + + public ServiceException(Integer code, String errorMessage) { + super(errorMessage); + this.code = code; + this.errorMessage = errorMessage; + } + + public ServiceException(AbstractBaseExceptionEnum exception) { + super(exception.getMessage()); + this.code = exception.getCode(); + this.errorMessage = exception.getMessage(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/AuthExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/AuthExceptionEnum.java new file mode 100644 index 0000000..69cf95c --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/AuthExceptionEnum.java @@ -0,0 +1,115 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.consts.ExpEnumConstant; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; + +/** + * 认证相关的异常的枚举 + * <p> + * 认证和鉴权的区别: + * <p> + * 认证可以证明你能登录系统,认证的过程是校验token的过程 + * 鉴权可以证明你有系统的哪些权限,鉴权的过程是校验角色是否包含某些接口的权限 + * + * @author yubaoshan + * @date 2019/7/18 22:22 + */ +@ExpEnumType(module = ExpEnumConstant.SNOWY_CORE_MODULE_EXP_CODE, kind = ExpEnumConstant.AUTH_EXCEPTION_ENUM) +public enum AuthExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 账号或密码为空 + */ + ACCOUNT_PWD_EMPTY(1, "账号或密码为空,请检查account或password参数"), + + /** + * 账号密码错误 + */ + ACCOUNT_PWD_ERROR(2, "账号或密码错误,请检查account或password参数"), + + /** + * 验证码错误 + */ + VALID_CODE_ERROR(3, "验证码错误,请检查captcha参数"), + + /** + * 请求token为空 + */ + REQUEST_TOKEN_EMPTY(4, "请求token为空,请携带token访问本接口"), + + /** + * token格式不正确,token请以Bearer开头 + */ + NOT_VALID_TOKEN_TYPE(5, "token格式不正确,token请以Bearer开头,并且Bearer后边带一个空格"), + + /** + * 请求token错误 + */ + REQUEST_TOKEN_ERROR(6, "请求token错误"), + + /** + * 账号被冻结 + */ + ACCOUNT_FREEZE_ERROR(7, "账号被冻结,请联系管理员"), + + /** + * 登录已过期 + */ + LOGIN_EXPIRED(8, "登录已过期,请重新登录"), + + /** + * 无登录用户 + */ + NO_LOGIN_USER(9, "无登录用户"), + + /** + * 验证码错误 + */ + CONSTANT_EMPTY_ERROR(10, "验证码错误"); + + private final Integer code; + + private final String message; + + AuthExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/ParamExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/ParamExceptionEnum.java new file mode 100644 index 0000000..ea978dd --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/ParamExceptionEnum.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.consts.ExpEnumConstant; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; + +/** + * 参数校验异常枚举 + * + * @author xuyuxiang + * @date 2020/3/25 20:11 + */ +@ExpEnumType(module = ExpEnumConstant.SNOWY_CORE_MODULE_EXP_CODE, kind = ExpEnumConstant.PARAM_EXCEPTION_ENUM) +public enum ParamExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 参数错误 + */ + PARAM_ERROR(1, "参数错误"); + + private final Integer code; + + private final String message; + + ParamExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/PermissionExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/PermissionExceptionEnum.java new file mode 100644 index 0000000..9e07427 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/PermissionExceptionEnum.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.consts.ExpEnumConstant; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; + +/** + * 授权和鉴权异常的枚举 + * <p> + * 认证和鉴权的区别: + * <p> + * 认证可以证明你能登录系统,认证的过程是校验token的过程 + * 鉴权可以证明你有系统的哪些权限,鉴权的过程是校验角色是否包含某些接口的权限 + * + * @author xuyuxiang + * @date 2020/3/12 10:14 + */ +@ExpEnumType(module = ExpEnumConstant.SNOWY_CORE_MODULE_EXP_CODE, kind = ExpEnumConstant.PERMISSION_EXCEPTION_ENUM) +public enum PermissionExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 资源路径不存在 + */ + URL_NOT_EXIST(1, "资源路径不存在,请检查请求地址"), + + /** + * 没有权限访问资源 + */ + NO_PERMISSION(2, "没有权限访问资源,请联系管理员"), + + /** + * 没有权限操作该数据 + */ + NO_PERMISSION_OPERATE(3, "没有权限操作该数据,请联系管理员"); + + private final Integer code; + + private final String message; + + PermissionExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/RequestMethodExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/RequestMethodExceptionEnum.java new file mode 100644 index 0000000..eb3676d --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/RequestMethodExceptionEnum.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.consts.ExpEnumConstant; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; + +/** + * 请求方法相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/11 15:33 + */ +@ExpEnumType(module = ExpEnumConstant.SNOWY_CORE_MODULE_EXP_CODE, kind = ExpEnumConstant.REQUEST_METHOD_EXCEPTION_ENUM) +public enum RequestMethodExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 不支持该请求方法,请求方法应为POST + */ + REQUEST_METHOD_IS_POST(1, "不支持该请求方法,请求方法应为POST"), + + /** + * 不支持该请求方法,请求方法应为GET + */ + REQUEST_METHOD_IS_GET(2, "不支持该请求方法,请求方法应为GET"); + + private final Integer code; + + private final String message; + + RequestMethodExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/RequestTypeExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/RequestTypeExceptionEnum.java new file mode 100644 index 0000000..1ed6272 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/RequestTypeExceptionEnum.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.consts.ExpEnumConstant; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; + +/** + * 请求类型相关异常枚举 + * + * @author xuyuxiang + * @date 2020/4/2 15:42 + */ +@ExpEnumType(module = ExpEnumConstant.SNOWY_CORE_MODULE_EXP_CODE, kind = ExpEnumConstant.REQUEST_TYPE_EXCEPTION_ENUM) +public enum RequestTypeExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 参数传递格式不支持 + */ + REQUEST_TYPE_IS_JSON(1, "参数传递格式不支持,请使用JSON格式传参"), + + /** + * 请求JSON参数格式不正确 + */ + REQUEST_JSON_ERROR(2, "请求JSON参数格式不正确,请检查参数格式"); + + private final Integer code; + + private final String message; + + RequestTypeExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/ServerExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/ServerExceptionEnum.java new file mode 100644 index 0000000..41a178b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/ServerExceptionEnum.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.consts.ExpEnumConstant; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; + +/** + * 服务器内部相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/18 19:19 + */ +@ExpEnumType(module = ExpEnumConstant.SNOWY_CORE_MODULE_EXP_CODE, kind = ExpEnumConstant.SERVER_EXCEPTION_ENUM) +public enum ServerExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 当前请求参数为空或数据缺失 + */ + REQUEST_EMPTY(1, "当前请求参数为空或数据缺失,请联系管理员"), + + /** + * 服务器出现未知异常 + */ + SERVER_ERROR(2, "服务器出现异常,请联系管理员"), + + /** + * 常量获取存在空值 + */ + CONSTANT_EMPTY(3, "常量获取存在空值,请检查sys_config中是否配置"); + + private final Integer code; + + private final String message; + + ServerExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/StatusExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/StatusExceptionEnum.java new file mode 100644 index 0000000..3517f3d --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/StatusExceptionEnum.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.consts.ExpEnumConstant; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; + +/** + * 状态枚举 + * + * @author yubaoshan + * @date 2020/4/30 22:45 + */ +@ExpEnumType(module = ExpEnumConstant.SNOWY_CORE_MODULE_EXP_CODE, kind = ExpEnumConstant.STATUS_EXCEPTION_ENUM) +public enum StatusExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 请求状态值为空 + */ + REQUEST_EMPTY(1, "请求状态值为空"), + + /** + * 请求状值为非正确状态值 + */ + NOT_WRITE_STATUS(2, "请求状态值不合法"), + + /** + * 更新状态失败,试图更新被删除的记录 + */ + UPDATE_STATUS_ERROR(3, "更新状态失败,您试图更新被删除的记录"); + + private final Integer code; + + private final String message; + + StatusExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/WrapperExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/WrapperExceptionEnum.java new file mode 100644 index 0000000..7ab0b56 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/WrapperExceptionEnum.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.consts.ExpEnumConstant; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; + +/** + * 对象包装异常 + * + * @author xuyuxiang + * @date 2020/7/24 14:36 + */ +@ExpEnumType(module = ExpEnumConstant.SNOWY_CORE_MODULE_EXP_CODE, kind = ExpEnumConstant.WRAPPER_EXCEPTION_ENUM) +public enum WrapperExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 被包装的值不能是基本类型 + */ + BASIC_TYPE_ERROR(1, "被包装的值不能是基本类型"), + + /** + * 获取转化字段的值异常 + */ + TRANSFER_FILED_VALUE_ERROR(2, "获取转化字段的值异常"), + + /** + * 字段包装转化异常 + */ + TRANSFER_ERROR(3, "字段包装转化异常"); + + private final Integer code; + + private final String message; + + WrapperExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/abs/AbstractBaseExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/abs/AbstractBaseExceptionEnum.java new file mode 100644 index 0000000..6ad63d8 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/exception/enums/abs/AbstractBaseExceptionEnum.java @@ -0,0 +1,53 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.exception.enums.abs; + +/** + * 异常枚举格式规范 + * + * @author yubaoshan + * @date 2017/12/17 22:22 + */ +public interface AbstractBaseExceptionEnum { + + /** + * 获取异常的状态码 + * + * @return 状态码 + * @author yubaoshan + * @date 2020/7/9 14:28 + */ + Integer getCode(); + + /** + * 获取异常的提示信息 + * + * @return 提示信息 + * @author yubaoshan + * @date 2020/7/9 14:28 + */ + String getMessage(); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/ExpEnumCodeFactory.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/ExpEnumCodeFactory.java new file mode 100644 index 0000000..a9d7cb2 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/ExpEnumCodeFactory.java @@ -0,0 +1,54 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.factory; + +import vip.xiaonuo.core.annotion.ExpEnumType; + +/** + * 异常枚举code值快速创建 + * + * @author yubaoshan + * @date 2020/6/19 21:30 + */ +public class ExpEnumCodeFactory { + + public static Integer getExpEnumCode(Class<?> clazz, int code) { + + // 默认的异常响应码 + Integer defaultCode = Integer.valueOf("" + 99 + 9999 + 9); + + if (clazz == null) { + return defaultCode; + } else { + ExpEnumType expEnumType = clazz.getAnnotation(ExpEnumType.class); + if (expEnumType == null) { + return defaultCode; + } + return Integer.valueOf("" + expEnumType.module() + expEnumType.kind() + code); + } + + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/PageFactory.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/PageFactory.java new file mode 100644 index 0000000..ec2c126 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/PageFactory.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.factory; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import vip.xiaonuo.core.util.HttpServletUtil; + +import javax.servlet.http.HttpServletRequest; + + +/** + * 默认分页参数构建 + * + * @author yubaoshan + * @date 2017/11/15 13:52 + */ +public class PageFactory { + + /** + * 每页大小(默认20) + */ + private static final String PAGE_SIZE_PARAM_NAME = "pageSize"; + + /** + * 第几页(从1开始) + */ + private static final String PAGE_NO_PARAM_NAME = "pageNo"; + + /** + * 默认分页,在使用时PageFactory.defaultPage会自动获取pageSize和pageNo参数 + * + * @author xuyuxiang + * @date 2020/3/30 16:42 + */ + public static <T> Page<T> defaultPage() { + + int pageSize = 20; + int pageNo = 1; + + HttpServletRequest request = HttpServletUtil.getRequest(); + + //每页条数 + String pageSizeString = request.getParameter(PAGE_SIZE_PARAM_NAME); + if (ObjectUtil.isNotEmpty(pageSizeString)) { + pageSize = Integer.parseInt(pageSizeString); + } + + //第几页 + String pageNoString = request.getParameter(PAGE_NO_PARAM_NAME); + if (ObjectUtil.isNotEmpty(pageNoString)) { + pageNo = Integer.parseInt(pageNoString); + } + + return new Page<>(pageNo, pageSize); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/TreeBuildFactory.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/TreeBuildFactory.java new file mode 100644 index 0000000..33ebceb --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/factory/TreeBuildFactory.java @@ -0,0 +1,128 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.factory; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; +import vip.xiaonuo.core.pojo.base.node.BaseTreeNode; + +import java.util.ArrayList; +import java.util.List; + +/** + * 默认递归工具类,用于遍历有父子关系的节点,例如菜单树,字典树等等 + * + * @author xuyuxiang + * @date 2020/4/5 14:17 + */ +@Data +public class TreeBuildFactory<T extends BaseTreeNode> { + + /** + * 顶级节点的父节点id(默认0) + */ + private Long rootParentId = 0L; + + /** + * 树节点构造 + * + * @author xuyuxiang + * @date 2020/4/5 14:09 + */ + public List<T> doTreeBuild(List<T> nodes) { + + //具体构建的过程 + List<T> buildComplete = this.executeBuilding(nodes); + + //构建之后的处理工作 + return this.afterBuild(buildComplete); + } + + /** + * 查询子节点集合 + * + * @author xuyuxiang + * @date 2020/4/5 14:10 + */ + private void buildChildNodes(List<T> totalNodes, T node, List<T> childNodeLists) { + if (ObjectUtil.hasEmpty(totalNodes, node)) { + return; + } + List<T> nodeSubLists = this.getSubChildLevelOne(totalNodes, node); + if (ObjectUtil.isNotEmpty(nodeSubLists)) { + nodeSubLists.forEach(t -> this.buildChildNodes(totalNodes, t, CollectionUtil.newArrayList())); + } +// childNodeLists.addAll(nodeSubLists); + node.setChildren(nodeSubLists); + } + + /** + * 获取子一级节点的集合 + * + * @author xuyuxiang + * @date 2020/4/5 14:12 + */ + private List<T> getSubChildLevelOne(List<T> list, T node) { + List<T> nodeList = CollectionUtil.newArrayList(); + if (ObjectUtil.isNotEmpty(list)) { + list.forEach(t -> { + if (t.getPid().equals(node.getId())) { + nodeList.add(t); + } + }); + } + return nodeList; + } + + /** + * 执行构造 + * + * @author xuyuxiang + * @date 2020/4/5 14:13 + */ + private List<T> executeBuilding(List<T> nodes) { + List<T> parentNodes = afterBuild(nodes); + parentNodes.forEach(t -> this.buildChildNodes(nodes, t, CollectionUtil.newArrayList())); + return parentNodes; + } + + /** + * 构造之后 + * + * @author xuyuxiang + * @date 2020/4/5 14:13 + */ + private List<T> afterBuild(List<T> nodes) { + //去掉所有的二级节点 + ArrayList<T> results = CollectionUtil.newArrayList(); + nodes.forEach(t -> { + if (rootParentId.equals(t.getPid())) { + results.add(t); + } + }); + return results; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/FileOperator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/FileOperator.java new file mode 100644 index 0000000..64b0c8c --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/FileOperator.java @@ -0,0 +1,171 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file; + + +import vip.xiaonuo.core.file.common.enums.BucketAuthEnum; + +import java.io.InputStream; + +/** + * 文件操纵者(内网操作) + * <p> + * 如果存在未包含的操作,可以调用getClient()自行获取client进行操作 + * + * @author xuyuxiang + * @date 2018-06-27-下午12:37 + */ +public interface FileOperator { + + /** + * 初始化操作的客户端 + * + * @author xuyuxiang + * @date 2020/5/23 2:32 下午 + */ + void initClient(); + + /** + * 销毁操作的客户端 + * + * @author xuyuxiang + * @date 2020/5/23 2:32 下午 + */ + void destroyClient(); + + /** + * 获取操作的客户端 + * + * @author xuyuxiang + * @date 2020/5/23 2:58 下午 + */ + Object getClient(); + + /** + * 查询存储桶是否存在 + * <p> + * 例如:传入参数examplebucket-1250000000,返回true代表存在此桶 + * + * @author xuyuxiang + * @date 2020/5/23 2:29 下午 + */ + boolean doesBucketExist(String bucketName); + + /** + * 设置预定义策略 + * <p> + * 预定义策略如公有读、公有读写、私有读 + * + * @author xuyuxiang + * @date 2020/5/23 3:02 下午 + */ + void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum); + + /** + * 判断是否存在文件 + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @author xuyuxiang + * @date 2018/6/27 下午1:14 + */ + boolean isExistingFile(String bucketName, String key); + + /** + * 存储文件 + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @param bytes 文件字节数组 + * @author xuyuxiang + * @date 2018/6/27 下午1:16 + */ + void storageFile(String bucketName, String key, byte[] bytes); + + /** + * 存储文件(存放到指定的bucket里边) + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @param inputStream 文件流 + * @author xuyuxiang + * @date 2018年10月19日13:20:37 + */ + void storageFile(String bucketName, String key, InputStream inputStream); + + /** + * 获取某个bucket下的文件字节 + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @author xuyuxiang + * @date 2018/6/27 下午1:15 + */ + byte[] getFileBytes(String bucketName, String key); + + /** + * 文件访问权限管理 + * + * @param bucketName 桶名称 + * @param key 唯一标示id,例如a.txt, doc/a.txt + * @param bucketAuthEnum 文件权限 + * @author xuyuxiang + * @date 2020/5/23 5:30 下午 + */ + void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum); + + /** + * 拷贝文件 + * + * @param originBucketName 源文件桶 + * @param originFileKey 源文件名称 + * @param newBucketName 新文件桶 + * @param newFileKey 新文件名称 + * @author xuyuxiang + * @date 2020/5/23 6:09 下午 + */ + void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey); + + /** + * 获取文件的下载地址(带鉴权的),生成外网地址 + * + * @param bucketName 文件桶 + * @param key 文件唯一标识 + * @author xuyuxiang + * @date 2018/7/7 上午11:27 + */ + String getFileAuthUrl(String bucketName, String key, Long timeoutMillis); + + /** + * 删除文件 + * + * @param bucketName 文件桶 + * @param key 文件唯一标识 + * @author xuyuxiang + * @date 2020/9/18 + */ + void deleteFile(String bucketName, String key); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/common/enums/BucketAuthEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/common/enums/BucketAuthEnum.java new file mode 100644 index 0000000..4b07419 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/common/enums/BucketAuthEnum.java @@ -0,0 +1,50 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.common.enums; + +/** + * 桶的权限策略枚举 + * + * @author xuyuxiang + * @date 2020-05-23-3:03 下午 + */ +public enum BucketAuthEnum { + + /** + * 私有的(仅有 owner 可以读写) + */ + PRIVATE, + + /** + * 公有读,私有写( owner 可以读写, 其他客户可以读) + */ + PUBLIC_READ, + + /** + * 公共读写(即所有人都可以读写,慎用) + */ + PUBLIC_READ_WRITE + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/common/exp/FileServiceException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/common/exp/FileServiceException.java new file mode 100644 index 0000000..8777d2e --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/common/exp/FileServiceException.java @@ -0,0 +1,42 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.common.exp; + +import lombok.Getter; + +/** + * 文件操作业务异常 + * + * @author xuyuxiang + * @date 2020-05-23-2:42 下午 + */ +@Getter +public class FileServiceException extends RuntimeException { + + public FileServiceException(String message) { + super(message); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/AliyunFileOperator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/AliyunFileOperator.java new file mode 100644 index 0000000..25bd2f4 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/AliyunFileOperator.java @@ -0,0 +1,213 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.modular.aliyun; + +import cn.hutool.core.io.IoUtil; +import com.aliyun.oss.ClientException; +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; +import com.aliyun.oss.OSSException; +import com.aliyun.oss.model.CannedAccessControlList; +import com.aliyun.oss.model.OSSObject; +import com.aliyun.oss.model.PutObjectRequest; +import vip.xiaonuo.core.file.FileOperator; +import vip.xiaonuo.core.file.common.enums.BucketAuthEnum; +import vip.xiaonuo.core.file.modular.aliyun.exp.AliyunFileServiceException; +import vip.xiaonuo.core.file.modular.aliyun.prop.AliyunOssProperties; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; + +/** + * 阿里云文件操作 + * + * @author xuyuxiang + * @date 2020/5/25 2:33 下午 + */ +public class AliyunFileOperator implements FileOperator { + + /** + * 阿里云文件操作客户端 + */ + private OSS ossClient; + + /** + * 阿里云oss的配置 + */ + private final AliyunOssProperties aliyunOssProperties; + + public AliyunFileOperator(AliyunOssProperties aliyunOssProperties) { + this.aliyunOssProperties = aliyunOssProperties; + this.initClient(); + } + + @Override + public void initClient() { + String endpoint = aliyunOssProperties.getEndPoint(); + String accessKeyId = aliyunOssProperties.getAccessKeyId(); + String accessKeySecret = aliyunOssProperties.getAccessKeySecret(); + + // 创建OSSClient实例。 + ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); + } + + @Override + public void destroyClient() { + ossClient.shutdown(); + } + + @Override + public Object getClient() { + return ossClient; + } + + @Override + public boolean doesBucketExist(String bucketName) { + try { + return ossClient.doesBucketExist(bucketName); + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } + } + + @Override + public void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum) { + try { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + ossClient.setBucketAcl(bucketName, CannedAccessControlList.Private); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicReadWrite); + } + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } + } + + @Override + public boolean isExistingFile(String bucketName, String key) { + try { + return ossClient.doesObjectExist(bucketName, key); + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } + } + + @Override + public void storageFile(String bucketName, String key, byte[] bytes) { + try { + ossClient.putObject(bucketName, key, new ByteArrayInputStream(bytes)); + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } + } + + @Override + public void storageFile(String bucketName, String key, InputStream inputStream) { + try { + PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, inputStream); + ossClient.putObject(putObjectRequest); + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } + } + + @Override + public byte[] getFileBytes(String bucketName, String key) { + InputStream objectContent = null; + try { + OSSObject ossObject = ossClient.getObject(bucketName, key); + objectContent = ossObject.getObjectContent(); + return IoUtil.readBytes(objectContent); + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } finally { + IoUtil.close(objectContent); + } + + } + + @Override + public void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum) { + try { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + ossClient.setObjectAcl(bucketName, key, CannedAccessControlList.Private); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + ossClient.setObjectAcl(bucketName, key, CannedAccessControlList.PublicRead); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + ossClient.setObjectAcl(bucketName, key, CannedAccessControlList.PublicReadWrite); + } + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } + } + + @Override + public void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) { + try { + ossClient.copyObject(originBucketName, originFileKey, newBucketName, newFileKey); + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } + } + + @Override + public String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) { + try { + Date expiration = new Date(new Date().getTime() + timeoutMillis); + URL url = ossClient.generatePresignedUrl(bucketName, key, expiration); + return url.toString(); + } catch (OSSException e) { + throw new AliyunFileServiceException(e); + } catch (ClientException e) { + throw new AliyunFileServiceException(e); + } + } + + @Override + public void deleteFile(String bucketName, String key) { + ossClient.deleteObject(bucketName, key); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/exp/AliyunFileServiceException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/exp/AliyunFileServiceException.java new file mode 100644 index 0000000..e78203f --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/exp/AliyunFileServiceException.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.modular.aliyun.exp; + +import com.aliyun.oss.ClientException; +import com.aliyun.oss.OSSException; +import lombok.Getter; + +/** + * 腾讯文件操作异常 + * + * @author xuyuxiang + * @date 2020-05-23-2:42 下午 + */ +@Getter +public class AliyunFileServiceException extends RuntimeException { + + /** + * 客户端异常 + * <p> + * 是由于客户端原因导致无法和服务端完成正常的交互而导致的失败,如客户端无法连接到服务端,无法解析服务端返回的数据 + */ + private ClientException clientException; + + /** + * 服务端异常 + * <p> + * 用于指交互正常完成,但是操作失败的场景 + * <p> + * 例如客户端访问一个不存在 Bucket,删除一个不存在的文件,没有权限进行某个操作, 服务端故障异常等 + */ + private OSSException ossException; + + public AliyunFileServiceException(String message) { + super(message); + } + + public AliyunFileServiceException(ClientException clientException) { + super(clientException.getMessage()); + this.clientException = clientException; + } + + public AliyunFileServiceException(OSSException ossException) { + super(ossException.getMessage()); + this.ossException = ossException; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/prop/AliyunOssProperties.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/prop/AliyunOssProperties.java new file mode 100644 index 0000000..73a19ff --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/aliyun/prop/AliyunOssProperties.java @@ -0,0 +1,55 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.modular.aliyun.prop; + +import lombok.Data; + +/** + * 腾讯云cos文件存储配置 + * + * @author xuyuxiang + * @date 2020/5/22 6:56 下午 + */ +@Data +public class AliyunOssProperties { + + /** + * 默认北京,内网 + * <p> + * https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.17.467f45dcjB4WQQ#concept-zt4-cvy-5db + */ + private String endPoint = "http://oss-cn-beijing.aliyuncs.com"; + + /** + * 秘钥id + */ + private String accessKeyId; + + /** + * 秘钥secret + */ + private String accessKeySecret; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/local/LocalFileOperator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/local/LocalFileOperator.java new file mode 100644 index 0000000..62f0e2c --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/local/LocalFileOperator.java @@ -0,0 +1,187 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.modular.local; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import cn.hutool.system.SystemUtil; +import vip.xiaonuo.core.file.FileOperator; +import vip.xiaonuo.core.file.common.enums.BucketAuthEnum; +import vip.xiaonuo.core.file.common.exp.FileServiceException; +import vip.xiaonuo.core.file.modular.local.prop.LocalFileProperties; + +import java.io.File; +import java.io.InputStream; + +/** + * 阿里云文件操作 + * + * @author xuyuxiang + * @date 2020/5/25 2:33 下午 + */ +public class LocalFileOperator implements FileOperator { + + private final LocalFileProperties localFileProperties; + + private String currentSavePath = ""; + + private Dict localClient; + + public LocalFileOperator(LocalFileProperties localFileProperties) { + this.localFileProperties = localFileProperties; + initClient(); + } + + @Override + public void initClient() { + if (SystemUtil.getOsInfo().isWindows()) { + String savePathWindows = localFileProperties.getLocalFileSavePathWin(); + if (!FileUtil.exist(savePathWindows)) { + FileUtil.mkdir(savePathWindows); + } + currentSavePath = savePathWindows; + } else { + String savePathLinux = localFileProperties.getLocalFileSavePathLinux(); + if (!FileUtil.exist(savePathLinux)) { + FileUtil.mkdir(savePathLinux); + } + currentSavePath = savePathLinux; + } + localClient = Dict.create(); + localClient.put("currentSavePath", currentSavePath); + localClient.put("localFileProperties", localFileProperties); + } + + @Override + public void destroyClient() { + // empty + } + + @Override + public Object getClient() { + // empty + return localClient; + } + + @Override + public boolean doesBucketExist(String bucketName) { + String absolutePath = currentSavePath + File.separator + bucketName; + return FileUtil.exist(absolutePath); + } + + @Override + public void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum) { + // empty + } + + @Override + public boolean isExistingFile(String bucketName, String key) { + String absoluteFile = currentSavePath + File.separator + bucketName + File.separator + key; + return FileUtil.exist(absoluteFile); + } + + @Override + public void storageFile(String bucketName, String key, byte[] bytes) { + + // 判断bucket存在不存在 + String bucketPath = currentSavePath + File.separator + bucketName; + if (!FileUtil.exist(bucketPath)) { + FileUtil.mkdir(bucketPath); + } + + // 存储文件 + String absoluteFile = currentSavePath + File.separator + bucketName + File.separator + key; + FileUtil.writeBytes(bytes, absoluteFile); + } + + @Override + public void storageFile(String bucketName, String key, InputStream inputStream) { + + // 判断bucket存在不存在 + String bucketPath = currentSavePath + File.separator + bucketName; + if (!FileUtil.exist(bucketPath)) { + FileUtil.mkdir(bucketPath); + } + + // 存储文件 + String absoluteFile = currentSavePath + File.separator + bucketName + File.separator + key; + FileUtil.writeFromStream(inputStream, absoluteFile); + } + + @Override + public byte[] getFileBytes(String bucketName, String key) { + + // 判断文件存在不存在 + String absoluteFile = currentSavePath + File.separator + bucketName + File.separator + key; + if (!FileUtil.exist(absoluteFile)) { + String message = StrUtil.format("文件不存在,bucket={},key={}", bucketName, key); + throw new FileServiceException(message); + } else { + return FileUtil.readBytes(absoluteFile); + } + } + + @Override + public void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum) { + // empty + } + + @Override + public void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) { + + // 判断文件存在不存在 + String originFile = currentSavePath + File.separator + originBucketName + File.separator + originFileKey; + if (!FileUtil.exist(originFile)) { + String message = StrUtil.format("源文件不存在,bucket={},key={}", originBucketName, originFileKey); + throw new FileServiceException(message); + } else { + + // 拷贝文件 + String destFile = currentSavePath + File.separator + newBucketName + File.separator + newFileKey; + FileUtil.copy(originFile, destFile, true); + } + } + + @Override + public String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) { + // empty + return null; + } + + @Override + public void deleteFile(String bucketName, String key) { + + // 判断文件存在不存在 + String file = currentSavePath + File.separator + bucketName + File.separator + key; + if (!FileUtil.exist(file)) { + return; + } + + // 删除文件 + FileUtil.del(file); + + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/local/prop/LocalFileProperties.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/local/prop/LocalFileProperties.java new file mode 100644 index 0000000..ccbb5aa --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/local/prop/LocalFileProperties.java @@ -0,0 +1,48 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.modular.local.prop; + +import lombok.Data; + +/** + * 本地文件存储配置 + * + * @author xuyuxiang + * @date 2020/6/7 22:30 + */ +@Data +public class LocalFileProperties { + + /** + * 本地文件存储位置(linux) + */ + private String localFileSavePathLinux = "/tmp/tempFilePath"; + + /** + * 本地文件存储位置(windows) + */ + private String localFileSavePathWin = "D:\\tempFilePath"; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/TenFileOperator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/TenFileOperator.java new file mode 100644 index 0000000..74e36cd --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/TenFileOperator.java @@ -0,0 +1,262 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.modular.tencent; + +import cn.hutool.core.io.IoUtil; +import com.qcloud.cos.COSClient; +import com.qcloud.cos.ClientConfig; +import com.qcloud.cos.auth.BasicCOSCredentials; +import com.qcloud.cos.auth.COSCredentials; +import com.qcloud.cos.exception.CosClientException; +import com.qcloud.cos.exception.CosServiceException; +import com.qcloud.cos.http.HttpMethodName; +import com.qcloud.cos.model.*; +import com.qcloud.cos.region.Region; +import com.qcloud.cos.transfer.TransferManager; +import com.qcloud.cos.transfer.TransferManagerConfiguration; +import vip.xiaonuo.core.file.FileOperator; +import vip.xiaonuo.core.file.common.enums.BucketAuthEnum; +import vip.xiaonuo.core.file.modular.tencent.exp.TencentFileServiceException; +import vip.xiaonuo.core.file.modular.tencent.prop.TenCosProperties; + +import javax.activation.MimetypesFileTypeMap; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URL; +import java.util.Date; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * 腾讯云内网文件操作 + * + * @author xuyuxiang + * @date 2020-05-22-6:51 下午 + */ +public class TenFileOperator implements FileOperator { + + private final TenCosProperties tenCosProperties; + + private COSClient cosClient; + + private TransferManager transferManager; + + public TenFileOperator(TenCosProperties tenCosProperties) { + this.tenCosProperties = tenCosProperties; + initClient(); + } + + @Override + public void initClient() { + + // 1.初始化用户身份信息 + String secretId = tenCosProperties.getSecretId(); + String secretKey = tenCosProperties.getSecretKey(); + COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); + + // 2.设置 bucket 的区域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224 + Region region = new Region(tenCosProperties.getRegionId()); + ClientConfig clientConfig = new ClientConfig(region); + + // 3.生成 cos 客户端。 + cosClient = new COSClient(cred, clientConfig); + + // 4.线程池大小,建议在客户端与 COS 网络充足(例如使用腾讯云的 CVM,同地域上传 COS)的情况下,设置成16或32即可,可较充分的利用网络资源 + // 对于使用公网传输且网络带宽质量不高的情况,建议减小该值,避免因网速过慢,造成请求超时。 + ExecutorService threadPool = Executors.newFixedThreadPool(32); + + // 5.传入一个 threadpool, 若不传入线程池,默认 TransferManager 中会生成一个单线程的线程池。 + transferManager = new TransferManager(cosClient, threadPool); + + // 6.设置高级接口的分块上传阈值和分块大小为10MB + TransferManagerConfiguration transferManagerConfiguration = new TransferManagerConfiguration(); + transferManagerConfiguration.setMultipartUploadThreshold(10 * 1024 * 1024); + transferManagerConfiguration.setMinimumUploadPartSize(10 * 1024 * 1024); + transferManager.setConfiguration(transferManagerConfiguration); + } + + @Override + public void destroyClient() { + cosClient.shutdown(); + } + + @Override + public Object getClient() { + return cosClient; + } + + @Override + public boolean doesBucketExist(String bucketName) { + try { + return cosClient.doesBucketExist(bucketName); + } catch (CosServiceException e) { + throw new TencentFileServiceException(e); + } catch (CosClientException e) { + throw new TencentFileServiceException(e); + } + } + + @Override + public void setBucketAcl(String bucketName, BucketAuthEnum bucketAuthEnum) { + try { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + cosClient.setBucketAcl(bucketName, CannedAccessControlList.Private); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + cosClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + cosClient.setBucketAcl(bucketName, CannedAccessControlList.PublicReadWrite); + } + } catch (CosServiceException e) { + throw new TencentFileServiceException(e); + } catch (CosClientException e) { + throw new TencentFileServiceException(e); + } + } + + @Override + public boolean isExistingFile(String bucketName, String key) { + try { + cosClient.getObjectMetadata(bucketName, key); + return true; + } catch (CosServiceException e) { + return false; + } + } + + @Override + public void storageFile(String bucketName, String key, byte[] bytes) { + // 根据文件名获取contentType + String contentType = "application/octet-stream"; + if (key.contains(".")) { + contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(key); + } + + // 上传文件 + ByteArrayInputStream byteArrayInputStream = null; + try { + byteArrayInputStream = new ByteArrayInputStream(bytes); + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(contentType); + cosClient.putObject(bucketName, key, new ByteArrayInputStream(bytes), objectMetadata); + } catch (CosServiceException e) { + throw new TencentFileServiceException(e); + } catch (CosClientException e) { + throw new TencentFileServiceException(e); + } finally { + IoUtil.close(byteArrayInputStream); + } + + } + + @Override + public void storageFile(String bucketName, String key, InputStream inputStream) { + + // 根据文件名获取contentType + String contentType = "application/octet-stream"; + if (key.contains(".")) { + contentType = MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(key); + } + + // 上传文件 + try { + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType(contentType); + cosClient.putObject(bucketName, key, inputStream, objectMetadata); + } catch (CosServiceException e) { + throw new TencentFileServiceException(e); + } catch (CosClientException e) { + throw new TencentFileServiceException(e); + } finally { + IoUtil.close(inputStream); + } + } + + @Override + public byte[] getFileBytes(String bucketName, String key) { + COSObjectInputStream cosObjectInput = null; + try { + GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key); + COSObject cosObject = cosClient.getObject(getObjectRequest); + cosObjectInput = cosObject.getObjectContent(); + return IoUtil.readBytes(cosObjectInput); + } catch (CosServiceException e) { + throw new TencentFileServiceException(e); + } catch (CosClientException e) { + throw new TencentFileServiceException(e); + } finally { + IoUtil.close(cosObjectInput); + } + } + + @Override + public void setFileAcl(String bucketName, String key, BucketAuthEnum bucketAuthEnum) { + if (bucketAuthEnum.equals(BucketAuthEnum.PRIVATE)) { + cosClient.setObjectAcl(bucketName, key, CannedAccessControlList.Private); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ)) { + cosClient.setObjectAcl(bucketName, key, CannedAccessControlList.PublicRead); + } else if (bucketAuthEnum.equals(BucketAuthEnum.PUBLIC_READ_WRITE)) { + cosClient.setObjectAcl(bucketName, key, CannedAccessControlList.PublicReadWrite); + } + } + + @Override + public void copyFile(String originBucketName, String originFileKey, String newBucketName, String newFileKey) { + // 初始化拷贝参数 + Region srcBucketRegion = new Region(tenCosProperties.getRegionId()); + CopyObjectRequest copyObjectRequest = new CopyObjectRequest( + srcBucketRegion, originBucketName, originFileKey, newBucketName, newFileKey); + + // 拷贝对象 + try { + transferManager.copy(copyObjectRequest, cosClient, null); + } catch (CosServiceException e) { + throw new TencentFileServiceException(e); + } catch (CosClientException e) { + throw new TencentFileServiceException(e); + } + } + + @Override + public String getFileAuthUrl(String bucketName, String key, Long timeoutMillis) { + GeneratePresignedUrlRequest presignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, key, HttpMethodName.GET); + Date expirationDate = new Date(System.currentTimeMillis() + timeoutMillis); + presignedUrlRequest.setExpiration(expirationDate); + URL url = null; + try { + url = cosClient.generatePresignedUrl(presignedUrlRequest); + } catch (CosServiceException e) { + throw new TencentFileServiceException(e); + } catch (CosClientException e) { + throw new TencentFileServiceException(e); + } + return url.toString(); + } + + @Override + public void deleteFile(String bucketName, String key) { + cosClient.deleteObject(bucketName, key); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/exp/TencentFileServiceException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/exp/TencentFileServiceException.java new file mode 100644 index 0000000..78f1ac1 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/exp/TencentFileServiceException.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.modular.tencent.exp; + +import com.qcloud.cos.exception.CosClientException; +import com.qcloud.cos.exception.CosServiceException; +import lombok.Getter; + +/** + * 腾讯文件操作异常 + * + * @author xuyuxiang + * @date 2020-05-23-2:42 下午 + */ +@Getter +public class TencentFileServiceException extends RuntimeException { + + /** + * 客户端异常 + * <p> + * 是由于客户端原因导致无法和服务端完成正常的交互而导致的失败,如客户端无法连接到服务端,无法解析服务端返回的数据 + */ + private CosClientException cosClientException; + + /** + * 服务端异常 + * <p> + * 用于指交互正常完成,但是操作失败的场景 + * <p> + * 例如客户端访问一个不存在 Bucket,删除一个不存在的文件,没有权限进行某个操作, 服务端故障异常等 + */ + private CosServiceException cosServiceException; + + public TencentFileServiceException(String message) { + super(message); + } + + public TencentFileServiceException(CosClientException cosClientException) { + super(cosClientException.getMessage()); + this.cosClientException = cosClientException; + } + + public TencentFileServiceException(CosServiceException cosServiceException) { + super(cosServiceException.getMessage()); + this.cosServiceException = cosServiceException; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/prop/TenCosProperties.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/prop/TenCosProperties.java new file mode 100644 index 0000000..10fde3e --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/file/modular/tencent/prop/TenCosProperties.java @@ -0,0 +1,53 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.file.modular.tencent.prop; + +import lombok.Data; + +/** + * 腾讯云cos文件存储配置 + * + * @author xuyuxiang + * @date 2020/5/22 6:56 下午 + */ +@Data +public class TenCosProperties { + + /** + * secretId + */ + private String secretId; + + /** + * secretKey + */ + private String secretKey; + + /** + * 地域id(默认北京) + */ + private String regionId = "ap-beijing"; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/entity/BaseEntity.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/entity/BaseEntity.java new file mode 100644 index 0000000..30e194d --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/entity/BaseEntity.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.base.entity; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * 通用基础字段,需要此通用字段的实体可继承此类 + * + * @author xuyuxiang + * @date 2020/3/10 16:02 + */ +@Data +public class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @Excel(name = "创建时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd", width = 20) + private Date createTime; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + private Long createUser; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.UPDATE) + @Excel(name = "更新时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd", width = 20) + private Date updateTime; + + /** + * 更新人 + */ + @TableField(fill = FieldFill.UPDATE) + private Long updateUser; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/node/BaseTreeNode.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/node/BaseTreeNode.java new file mode 100644 index 0000000..ece6cc7 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/node/BaseTreeNode.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.base.node; + +import java.util.List; + +/** + * 树节点接口 + * + * @author xuyuxiang + * @date 2020/4/5 14:07 + */ +public interface BaseTreeNode { + + + /** + * 获取节点id + * + * @return 节点id + * @author xuyuxiang + * @date 2020/7/9 18:36 + */ + Long getId(); + + /** + * 获取节点父id + * + * @return 节点父id + * @author xuyuxiang + * @date 2020/7/9 18:36 + */ + Long getPid(); + + /** + * 设置children + * + * @param children 子节点集合 + * @author xuyuxiang + * @date 2020/7/9 18:36 + */ + void setChildren(List children); +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/param/BaseParam.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/param/BaseParam.java new file mode 100644 index 0000000..1d7f7b2 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/param/BaseParam.java @@ -0,0 +1,272 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.base.param; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 通用基础参数,相关实体参数校验可继承此类 + * + * @author xuyuxiang + * @date 2020/3/10 16:02 + */ +@Data +public class BaseParam implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 搜索值 + */ + private String searchValue; + + /** + * 数据权限 + */ + private List<Long> dataScope; + + /** + * 开始时间 + */ + private String searchBeginTime; + + /** + * 结束时间 + */ + private String searchEndTime; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + private Integer searchStatus; + + /** + * 参数校验分组:分页 + */ + public @interface page { + } + + /** + * 参数校验分组:列表 + */ + public @interface list { + } + + /** + * 参数校验分组:下拉 + */ + public @interface dropDown { + } + + /** + * 参数校验分组:增加 + */ + public @interface add { + } + + /** + * 参数校验分组:编辑 + */ + public @interface edit { + } + + /** + * 参数校验分组:更新信息 + */ + public @interface updateInfo { + } + + /** + * 参数校验分组:修改密码 + */ + public @interface updatePwd { + } + + /** + * 参数校验分组:重置密码 + */ + public @interface resetPwd { + } + + /** + * 参数校验分组:修改头像 + */ + public @interface updateAvatar { + } + + /** + * 参数校验分组:删除 + */ + public @interface delete { + } + + /** + * 参数校验分组:详情 + */ + public @interface detail { + } + + /** + * 参数校验分组:授权角色 + */ + public @interface grantRole { + } + + /** + * 参数校验分组:授权菜单 + */ + public @interface grantMenu { + } + + /** + * 参数校验分组:授权数据 + */ + public @interface grantData { + } + + /** + * 参数校验分组:强退 + */ + public @interface force { + } + + /** + * 参数校验分组:停用 + */ + public @interface stop { + } + + /** + * 参数校验分组:启用 + */ + public @interface start { + } + + /** + * 参数校验分组:部署 + */ + public @interface deploy { + } + + /** + * 参数校验分组:挂起 + */ + public @interface suspend { + } + + /** + * 参数校验分组:激活 + */ + public @interface active { + } + + /** + * 参数校验分组:委托 + */ + public @interface entrust { + } + + /** + * 参数校验分组:转办 + */ + public @interface turn { + } + + /** + * 参数校验分组:追踪 + */ + public @interface trace { + } + + /** + * 参数校验分组:跳转 + */ + public @interface jump { + } + + /** + * 参数校验分组:提交 + */ + public @interface submit { + } + + /** + * 参数校验分组:退回 + */ + public @interface back { + } + + /** + * 参数校验分组:终止 + */ + public @interface end { + } + + /** + * 参数校验分组:导出 + */ + public @interface export { + } + + /** + * 参数校验分组:映射 + */ + public @interface mapping { + } + + /** + * 参数校验分组:切换 + */ + public @interface change { + } + + /** + * 参数校验分组:历史审批记录 + */ + public @interface commentHistory { + } + + /** + * 参数校验分组:修改状态 + */ + public @interface changeStatus { + } + + /** + * 参数校验分组:加签 + */ + public @interface addSign { + } + + /** + * 参数校验分组:减签 + */ + public @interface deleteSign { + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/validate/UniqueValidateParam.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/validate/UniqueValidateParam.java new file mode 100644 index 0000000..056e25d --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/validate/UniqueValidateParam.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.base.validate; + +import lombok.Builder; +import lombok.Data; + +/** + * 校验参数时用的方法参数 + * + * @author xuyuxiang + * @date 2020/8/17 21:43 + */ +@Data +@Builder +public class UniqueValidateParam { + + /** + * 表名称 + */ + String tableName; + + /** + * 列名称 + */ + String columnName; + + /** + * 被参数校验时候的字段的值 + */ + String value; + + /** + * 校验时,是否排除当前的记录 + */ + Boolean excludeCurrentRecord; + + /** + * 当前记录的主键id + */ + Long id; + + /** + * 排除所有被逻辑删除的记录的控制 + */ + Boolean excludeLogicDeleteItems; + + /** + * 逻辑删除的字段名 + */ + String logicDeleteFieldName; + + /** + * 逻辑删除的字段的值 + */ + String logicDeleteValue; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/wrapper/BaseWrapper.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/wrapper/BaseWrapper.java new file mode 100644 index 0000000..8fb5d9e --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/base/wrapper/BaseWrapper.java @@ -0,0 +1,47 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.base.wrapper; + +import java.util.Map; + +/** + * 基础包装接口, + * + * @author xuyuxiang + * @date 2020/7/24 17:18 + */ +public interface BaseWrapper<T> { + + /** + * 具体包装的过程 + * + * @param beWrappedModel 被包装的原始对象,可以是obj,list,page,PageResult + * @return 包装后增加的增量集合 + * @author xuyuxiang + * @date 2020/7/24 17:22 + */ + Map<String, Object> doWrap(T beWrappedModel); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/cryptogram/CryptogramConfigs.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/cryptogram/CryptogramConfigs.java new file mode 100644 index 0000000..1a51b0b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/cryptogram/CryptogramConfigs.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.cryptogram; + +import lombok.Data; + +/** + * 框架中加解密配置 + * + * @author yubaoshan + **/ +@Data +public class CryptogramConfigs { + + /** + * token是否加解密 + */ + private Boolean tokenEncDec; + + /** + * 操作日志是否加密 + */ + private Boolean opLogEnc; + + /** + * 登录登出日志是否加密 + */ + private Boolean visLogEnc; + + /** + * 铭感字段值是否加解密 + */ + private Boolean fieldEncDec; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/druid/DruidProperties.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/druid/DruidProperties.java new file mode 100644 index 0000000..b0a12f2 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/druid/DruidProperties.java @@ -0,0 +1,188 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.druid; + +import cn.hutool.log.Log; +import com.alibaba.druid.pool.DruidDataSource; +import lombok.Data; +import vip.xiaonuo.core.enums.DbIdEnum; + +import java.sql.SQLException; +import java.util.Properties; + +/** + * <p>数据库数据源配置</p> + * <p>说明:类中属性包含默认值的不要在这里修改,应该在"application.yml"中配置</p> + * + * @author yubaoshan + * @date 2017/5/21 11:18 + */ +@Data +public class DruidProperties { + + private static final Log log = Log.get(); + + /** + * mysql校验语句 + */ + private final String MYSQL_VALIDATE_QUERY_SQL = "select 1"; + + /** + * oracle校验语句 + */ + private final String ORACLE_VALIDATE_QUERY_SQL = "select 1 from dual"; + + /** + * postgresql校验语句 + */ + private final String POSTGRESQL_VALIDATE_QUERY_SQL = "select version()"; + + /** + * sqlserver校验语句 + */ + private final String SQLSERVER_VALIDATE_QUERY_SQL = "select 1"; + + /** + * 达梦数据库校验语句 + */ + private final String DM_VALIDATE_QUERY_SQL = "select 1"; + + /** + * 人大金仓数据库校验语句 + */ + private final String KINGBASEES_VALIDATE_QUERY_SQL = "select 1"; + + private String url; + + private String username; + + private String password; + + private String driverClassName; + + private Integer initialSize = 2; + + private Integer minIdle = 1; + + private Integer maxActive = 20; + + private Integer maxWait = 60000; + + private Integer timeBetweenEvictionRunsMillis = 60000; + + private Integer minEvictableIdleTimeMillis = 300000; + + private String validationQuery; + + private Boolean testWhileIdle = true; + + private Boolean testOnBorrow = true; + + private Boolean testOnReturn = true; + + private Boolean poolPreparedStatements = true; + + private Integer maxPoolPreparedStatementPerConnectionSize = 20; + + private String filters = "stat,slf4j"; + + private String dataSourceName; + + public void config(DruidDataSource dataSource) { + + dataSource.setUrl(url); + dataSource.setUsername(username); + dataSource.setPassword(password); + + dataSource.setDriverClassName(driverClassName); + //定义初始连接数 + dataSource.setInitialSize(initialSize); + //最小空闲 + dataSource.setMinIdle(minIdle); + //定义最大连接数 + dataSource.setMaxActive(maxActive); + //最长等待时间 + dataSource.setMaxWait(maxWait); + + //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + + //配置一个连接在池中最小生存的时间,单位是毫秒 + dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + dataSource.setValidationQuery(getValidateQueryByUrl(url)); + dataSource.setTestWhileIdle(testWhileIdle); + dataSource.setTestOnBorrow(testOnBorrow); + dataSource.setTestOnReturn(testOnReturn); + + //打开PSCache,并且指定每个连接上PSCache的大小 + dataSource.setPoolPreparedStatements(poolPreparedStatements); + dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); + + try { + dataSource.setFilters(filters); + } catch (SQLException e) { + log.error(">>> 数据库连接池初始化异常:{}", e.getMessage()); + } + } + + public Properties createProperties() { + Properties properties = new Properties(); + properties.put("url", this.url); + properties.put("username", this.username); + properties.put("password", this.password); + properties.put("driverClassName", this.driverClassName); + properties.put("initialSize", this.initialSize); + properties.put("maxActive", this.maxActive); + properties.put("minIdle", this.minIdle); + properties.put("maxWait", this.maxWait); + properties.put("poolPreparedStatements", this.poolPreparedStatements); + properties.put("maxPoolPreparedStatementPerConnectionSize", this.maxPoolPreparedStatementPerConnectionSize); + properties.put("validationQuery", getValidateQueryByUrl(this.url)); + properties.put("testOnBorrow", this.testOnBorrow); + properties.put("testOnReturn", this.testOnReturn); + properties.put("testWhileIdle", this.testWhileIdle); + properties.put("timeBetweenEvictionRunsMillis", this.timeBetweenEvictionRunsMillis); + properties.put("minEvictableIdleTimeMillis", this.minEvictableIdleTimeMillis); + properties.put("filters", this.filters); + return properties; + } + + private String getValidateQueryByUrl(String url) { + if (url.contains(DbIdEnum.ORACLE.getName())) { + return ORACLE_VALIDATE_QUERY_SQL; + } else if (url.contains(DbIdEnum.PG_SQL.getName())) { + return POSTGRESQL_VALIDATE_QUERY_SQL; + } else if (url.contains(DbIdEnum.MS_SQL.getName())) { + return SQLSERVER_VALIDATE_QUERY_SQL; + } else if (url.contains(DbIdEnum.DM_SQL.getName())) { + return DM_VALIDATE_QUERY_SQL; + } else if (url.contains(DbIdEnum.KINGBASE_ES.getName())) { + return KINGBASEES_VALIDATE_QUERY_SQL; + } else { + return MYSQL_VALIDATE_QUERY_SQL; + } + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/email/EmailConfigs.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/email/EmailConfigs.java new file mode 100644 index 0000000..a37da80 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/email/EmailConfigs.java @@ -0,0 +1,68 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.email; + +import lombok.Data; + +/** + * 邮件的配置 + * + * @author yubaoshan + * @date 2020/6/9 23:16 + */ +@Data +public class EmailConfigs { + + /** + * host + */ + private String host; + + /** + * 邮箱用户名 + */ + private String user; + + /** + * 邮箱密码或者安全码 + */ + private String pass; + + /** + * 邮箱端口 + */ + private Integer port; + + /** + * 邮箱发件人 + */ + private String from; + + /** + * 使用 SSL安全连接 + */ + private Boolean sslEnable; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/LoginEmpInfo.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/LoginEmpInfo.java new file mode 100644 index 0000000..0e27130 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/LoginEmpInfo.java @@ -0,0 +1,69 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.login; + +import cn.hutool.core.lang.Dict; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 登录用户员工信息 + * + * @author xuyuxiang + * @date 2020/3/11 16:44 + */ +@Data +public class LoginEmpInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 工号 + */ + private String jobNum; + + /** + * 所属机构id + */ + private Long orgId; + + /** + * 所属机构名称 + */ + private String orgName; + + /** + * 附属机构与职位信息 + */ + private List<Dict> extOrgPos; + + /** + * 职位信息 + */ + private List<Dict> positions; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/SnowyAuthority.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/SnowyAuthority.java new file mode 100644 index 0000000..c54dae8 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/SnowyAuthority.java @@ -0,0 +1,48 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.login; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; + +/** + * 用来包装一下角色名称 + * + * @author yubaoshan + * @date 2020/4/10 13:08 + */ +@Data +@AllArgsConstructor +public class SnowyAuthority implements GrantedAuthority { + + private String authority; + + @Override + public String getAuthority() { + return authority; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/SysLoginUser.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/SysLoginUser.java new file mode 100644 index 0000000..7b3785c --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/login/SysLoginUser.java @@ -0,0 +1,217 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.login; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import lombok.Data; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.pojo.node.LoginMenuTreeNode; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * 登录用户模型 + * + * @author xuyuxiang + * @date 2020/3/11 12:21 + */ +@Data +public class SysLoginUser implements UserDetails, Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + private Long id; + + /** + * 账号 + */ + private String account; + + /** + * 昵称 + */ + private String nickName; + + /** + * 姓名 + */ + private String name; + + /** + * 头像 + */ + private String avatar; + + /** + * 生日 + */ + private Date birthday; + + /** + * 性别(字典 1男 2女) + */ + private Integer sex; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机 + */ + private String phone; + + /** + * 电话 + */ + private String tel; + + /** + * 管理员类型(0超级管理员 1非管理员) + */ + private Integer adminType; + + /** + * 最后登陆IP + */ + private String lastLoginIp; + + /** + * 最后登陆时间 + */ + private String lastLoginTime; + + /** + * 最后登陆地址 + */ + private String lastLoginAddress; + + /** + * 最后登陆所用浏览器 + */ + private String lastLoginBrowser; + + /** + * 最后登陆所用系统 + */ + private String lastLoginOs; + + /** + * 员工信息 + */ + private LoginEmpInfo loginEmpInfo; + + /** + * 具备应用信息 + */ + private List<Dict> apps; + + /** + * 角色信息 + */ + private List<Dict> roles; + + /** + * 权限信息 + */ + private List<String> permissions; + + /** + * 登录菜单信息,AntDesign版本菜单 + */ + private List<LoginMenuTreeNode> menus; + + /** + * 数据范围信息 + */ + private List<Long> dataScopes; + + /** + * 租户信息 + */ + private Dict tenants; + + /** + * 角色名称集合 + */ + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + ArrayList<SnowyAuthority> grantedAuthorities = CollectionUtil.newArrayList(); + if (ObjectUtil.isNotEmpty(roles)) { + roles.forEach(dict -> { + String roleName = dict.getStr(CommonConstant.NAME); + SnowyAuthority snowyAuthority = new SnowyAuthority(roleName); + grantedAuthorities.add(snowyAuthority); + }); + } + return grantedAuthorities; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public String getUsername() { + return this.account; + } + + @Override + public boolean isAccountNonExpired() { + //能生成loginUser就是jwt解析成功,没锁定 + return true; + } + + @Override + public boolean isAccountNonLocked() { + //能生成loginUser就是jwt解析成功,没锁定 + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + //能生成loginUser就是jwt解析成功,没锁定 + return true; + } + + @Override + public boolean isEnabled() { + //能生成loginUser就是jwt解析成功,没锁定 + return true; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/AntdBaseTreeNode.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/AntdBaseTreeNode.java new file mode 100644 index 0000000..a5a18b7 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/AntdBaseTreeNode.java @@ -0,0 +1,87 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.node; + +import lombok.Data; +import vip.xiaonuo.core.pojo.base.node.BaseTreeNode; + +import java.util.List; + +/** + * antd通用前端树节点 + * + * @author yubaoshan + * @date 2020/6/9 12:42 + */ +@Data +public class AntdBaseTreeNode implements BaseTreeNode { + + /** + * 主键 + */ + private Long id; + + /** + * 父id + */ + private Long parentId; + + /** + * 名称 + */ + private String title; + + /** + * 值 + */ + private String value; + + /** + * 排序,越小优先级越高 + */ + private Integer weight; + + /** + * 子节点 + */ + private List children; + + /** + * 父id别名 + */ + @Override + public Long getPid() { + return this.parentId; + } + + /** + * 子节点 + */ + @Override + public void setChildren(List children) { + this.children = children; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/CommonBaseTreeNode.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/CommonBaseTreeNode.java new file mode 100644 index 0000000..4c5ed2e --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/CommonBaseTreeNode.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.node; + +import lombok.Data; +import vip.xiaonuo.core.pojo.base.node.BaseTreeNode; + +import java.util.List; + +/** + * 通用树节点 + * + * @author xuyuxiang + * @date 2020/3/26 14:29 + */ +@Data +public class CommonBaseTreeNode implements BaseTreeNode { + + /** + * 节点id + */ + private Long id; + + /** + * 节点父id + */ + private Long pid; + + /** + * 节点名称 + */ + private String nodeName; + + /** + * 子节点集合 + */ + private List children; + + @Override + public Long getId() { + return this.id; + } + + @Override + public Long getPid() { + return this.pid; + } + + @Override + public void setChildren(List children) { + this.children = children; + } + + /*@Override + public String getNodeId() { + return this.nodeId; + } + + @Override + public String getNodeParentId() { + return this.nodeParentId; + } + + @Override + public void setChildrenNodes(List childrenNodes) { + this.childrenNodes = childrenNodes; + }*/ +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/LoginMenuTreeNode.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/LoginMenuTreeNode.java new file mode 100644 index 0000000..f82ceb3 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/node/LoginMenuTreeNode.java @@ -0,0 +1,111 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.node; + +import lombok.Data; + +/** + * 登录菜单 + * + * @author yubaoshan + * @date 2020/4/17 17:35 + */ +@Data +public class LoginMenuTreeNode { + + /** + * id + */ + private Long id; + + /** + * 父id + */ + private Long pid; + + /** + * 路由名称, 必须设置,且不能重名 + */ + private String name; + + /** + * 组件 + */ + private String component; + + /** + * 重定向地址, 访问这个路由时, 自定进行重定向 + */ + private String redirect; + + /** + * 路由元信息(路由附带扩展信息) + */ + private Meta meta; + + /** + * 路径 + */ + private String path; + + /** + * 控制路由和子路由是否显示在 sidebar + */ + private boolean hidden; + + /** + * 路由元信息内部类 + */ + @Data + public class Meta { + + /** + * 路由标题, 用于显示面包屑, 页面标题 *推荐设置 + */ + public String title; + + /** + * 图标 + */ + public String icon; + + /** + * 是否可见 + */ + public boolean show; + + /** + * 如需外部打开,增加:_blank + */ + public String target; + + /** + * 内链打开http链接 + */ + public String link; + + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/oauth/OauthConfigs.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/oauth/OauthConfigs.java new file mode 100644 index 0000000..84dba11 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/oauth/OauthConfigs.java @@ -0,0 +1,52 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.oauth; + +import lombok.Data; + +/** + * Oauth第三方登录配置 + * + * @author xuyuxiang + * @date 2020/7/28 17:18 + **/ +@Data +public class OauthConfigs { + + /** + * clientId + */ + private String clientId; + + /** + * clientSecret + */ + private String clientSecret; + + /** + * 回调地址 + */ + private String redirectUri; +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/page/PageResult.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/page/PageResult.java new file mode 100644 index 0000000..26791f7 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/page/PageResult.java @@ -0,0 +1,118 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.page; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.PageUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 分页结果集 + * + * @author xuyuxiang + * @date 2020/3/30 15:44 + */ +@Data +public class PageResult<T> implements Serializable { + + private static final long serialVersionUID = -1L; + + /** + * 默认分页彩虹展示数量 + */ + public static final int RAINBOW_NUM = 5; + + /** + * 第几页 + */ + private Integer pageNo = 1; + + /** + * 每页条数 + */ + private Integer pageSize = 20; + + /** + * 总页数 + */ + private Integer totalPage = 0; + + /** + * 总记录数 + */ + private Integer totalRows = 0; + + /** + * 结果集 + */ + private List<T> rows; + + /** + * 分页彩虹 + */ + private int[] rainbow; + + public PageResult() { + } + + /** + * 将mybatis-plus的page转成自定义的PageResult,扩展了totalPage总页数,和rainBow彩虹条 + * + * @author xuyuxiang + * @date 2020/4/8 19:20 + */ + public PageResult(Page<T> page) { + this.setRows(page.getRecords()); + this.setTotalRows(Convert.toInt(page.getTotal())); + this.setPageNo(Convert.toInt(page.getCurrent())); + this.setPageSize(Convert.toInt(page.getSize())); + this.setTotalPage(PageUtil.totalPage(Convert.toInt(page.getTotal()), + Convert.toInt(page.getSize()))); + this.setRainbow(PageUtil.rainbow(Convert.toInt(page.getCurrent()), + Convert.toInt(this.getTotalPage()), RAINBOW_NUM)); + } + + /** + * 将mybatis-plus的page转成自定义的PageResult,扩展了totalPage总页数,和rainBow彩虹条 + * 可单独设置rows + * + * @author xuyuxiang + * @date 2020/4/14 20:55 + */ + public PageResult(Page<T> page, List<T> t) { + this.setRows(t); + this.setTotalRows(Convert.toInt(page.getTotal())); + this.setPageNo(Convert.toInt(page.getCurrent())); + this.setPageSize(Convert.toInt(page.getSize())); + this.setTotalPage(PageUtil.totalPage(Convert.toInt(page.getTotal()), + Convert.toInt(page.getSize()))); + this.setRainbow(PageUtil.rainbow(Convert.toInt(page.getCurrent()), + Convert.toInt(this.getTotalPage()), RAINBOW_NUM)); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/ErrorResponseData.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/ErrorResponseData.java new file mode 100644 index 0000000..ea64f2a --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/ErrorResponseData.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.response; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 失败响应结果 + * + * @author xuyuxiang + * @date 2020/3/30 15:05 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class ErrorResponseData extends ResponseData { + + /** + * 异常的具体类名称 + */ + private String exceptionClazz; + + ErrorResponseData(String message) { + super(false, DEFAULT_ERROR_CODE, message, null); + } + + public ErrorResponseData(Integer code, String message) { + super(false, code, message, null); + } + + ErrorResponseData(Integer code, String message, Object object) { + super(false, code, message, object); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/ResponseData.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/ResponseData.java new file mode 100644 index 0000000..35042a6 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/ResponseData.java @@ -0,0 +1,99 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.response; + +import lombok.Data; + +/** + * 响应结果数据 + * + * @author xuyuxiang + * @date 2020/3/30 15:04 + */ +@Data +public class ResponseData { + + public static final String DEFAULT_SUCCESS_MESSAGE = "请求成功"; + + public static final String DEFAULT_ERROR_MESSAGE = "网络异常"; + + public static final Integer DEFAULT_SUCCESS_CODE = 200; + + public static final Integer DEFAULT_ERROR_CODE = 500; + + /** + * 请求是否成功 + */ + private Boolean success; + + /** + * 响应状态码 + */ + private Integer code; + + /** + * 响应信息 + */ + private String message; + + /** + * 响应对象 + */ + private Object data; + + public ResponseData() { + } + + public ResponseData(Boolean success, Integer code, String message, Object data) { + this.success = success; + this.code = code; + this.message = message; + this.data = data; + } + + public static SuccessResponseData success() { + return new SuccessResponseData(); + } + + public static SuccessResponseData success(Object object) { + return new SuccessResponseData(object); + } + + public static SuccessResponseData success(Integer code, String message, Object object) { + return new SuccessResponseData(code, message, object); + } + + public static ErrorResponseData error(String message) { + return new ErrorResponseData(message); + } + + public static ErrorResponseData error(Integer code, String message) { + return new ErrorResponseData(code, message); + } + + public static ErrorResponseData error(Integer code, String message, Object object) { + return new ErrorResponseData(code, message, object); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/SuccessResponseData.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/SuccessResponseData.java new file mode 100644 index 0000000..ee980e0 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/response/SuccessResponseData.java @@ -0,0 +1,46 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.response; + +/** + * 成功响应结果 + * + * @author xuyuxiang + * @date 2020/3/30 15:04 + */ +public class SuccessResponseData extends ResponseData { + + public SuccessResponseData() { + super(true, DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MESSAGE, null); + } + + public SuccessResponseData(Object object) { + super(true, DEFAULT_SUCCESS_CODE, DEFAULT_SUCCESS_MESSAGE, object); + } + + public SuccessResponseData(Integer code, String message, Object object) { + super(true, code, message, object); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/sms/AliyunSmsConfigs.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/sms/AliyunSmsConfigs.java new file mode 100644 index 0000000..477e548 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/sms/AliyunSmsConfigs.java @@ -0,0 +1,63 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.sms; + +import lombok.Data; + +/** + * 阿里云oss相关配置 + * + * @author yubaoshan + * @date 2018/6/27 13:20 + */ +@Data +public class AliyunSmsConfigs { + + /** + * accessKeyId + */ + private String accessKeyId; + + /** + * accessKeySecret + */ + private String accessKeySecret; + + /** + * 签名名称 + */ + private String signName; + + /** + * 登录验证码的模板 + */ + private String loginTemplateCode; + + /** + * 短信失效时间(分钟) + */ + private Integer invalidateMinutes = 2; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/sms/TencentSmsConfigs.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/sms/TencentSmsConfigs.java new file mode 100644 index 0000000..edef7c4 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/pojo/sms/TencentSmsConfigs.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.pojo.sms; + +import lombok.Data; + +/** + * 阿里云oss相关配置 + * + * @author yubaoshan + * @date 2018/6/27 13:20 + */ +@Data +public class TencentSmsConfigs { + + /** + * secretId + */ + private String secretId; + + /** + * secretKey + */ + private String secretKey; + + /** + * 应用控制台应用管理中的应用id + * <p> + * 在 [短信控制台] 添加应用后生成的实际 SDKAppID,例如1400006666 + * <p> + * 短信控制台:https://console.cloud.tencent.com/smsv2 + */ + private String sdkAppId; + + /** + * 签名,一般为中文名 + * <p> + * 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,可登录 [短信控制台] 查看签名信息 + */ + private String sign; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/SmsSender.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/SmsSender.java new file mode 100644 index 0000000..ee0ce31 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/SmsSender.java @@ -0,0 +1,50 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms; + +import java.util.Map; + +/** + * 短信发送服务 + * + * @author xuyuxiang + * @date 2018-07-06-下午2:14 + */ +public interface SmsSender { + + /** + * 发送短信 + * <p> + * 如果是腾讯云,params要用LinkedHashMap,保证顺序 + * + * @param phone 电话号码 + * @param templateCode 模板号码 + * @param params 模板里参数的集合 + * @author xuyuxiang + * @date 2018/7/6 下午2:32 + */ + void sendSms(String phone, String templateCode, Map<String, Object> params); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/AliyunSmsSender.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/AliyunSmsSender.java new file mode 100644 index 0000000..553aa9a --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/AliyunSmsSender.java @@ -0,0 +1,180 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.aliyun; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.aliyuncs.CommonRequest; +import com.aliyuncs.CommonResponse; +import com.aliyuncs.DefaultAcsClient; +import com.aliyuncs.IAcsClient; +import com.aliyuncs.exceptions.ClientException; +import com.aliyuncs.profile.DefaultProfile; +import lombok.extern.slf4j.Slf4j; +import vip.xiaonuo.core.sms.SmsSender; +import vip.xiaonuo.core.sms.modular.aliyun.enums.AliyunSmsResultEnum; +import vip.xiaonuo.core.sms.modular.aliyun.exp.AliyunSmsException; +import vip.xiaonuo.core.sms.modular.aliyun.msign.MultiSignManager; +import vip.xiaonuo.core.sms.modular.aliyun.prop.AliyunSmsProperties; + +import java.util.Map; + +/** + * 阿里云短信发送服务 + * + * @author xuyuxiang + * @date 2018-07-06-下午2:15 + */ +@Slf4j +public class AliyunSmsSender implements SmsSender { + + private final MultiSignManager multiSignManager; + + private final AliyunSmsProperties aliyunSmsProperties; + + public AliyunSmsSender(MultiSignManager multiSignManager, AliyunSmsProperties aliyunSmsProperties) { + this.multiSignManager = multiSignManager; + this.aliyunSmsProperties = aliyunSmsProperties; + } + + @Override + public void sendSms(String phone, String templateCode, Map<String, Object> params) { + + log.info(">>> 开始发送阿里云短信,手机号是:" + phone + ",模板号是:" + templateCode + ",参数是:" + params); + + // 检验参数是否合法 + assertSendSmsParams(phone, templateCode, params, aliyunSmsProperties); + + // 初始化client profile + IAcsClient iAcsClient = initClient(); + + // 组装请求对象 + JSONObject smsRes = createSmsRequest(phone, templateCode, params, iAcsClient); + + // 如果返回ok则发送成功 + if (!AliyunSmsResultEnum.OK.getCode().equals(smsRes.getString("Code"))) { + + // 返回其他状态码根据情况抛出业务异常 + String code = AliyunSmsResultEnum.SYSTEM_ERROR.getCode(); + String errorMessage = AliyunSmsResultEnum.SYSTEM_ERROR.getMessage(); + for (AliyunSmsResultEnum smsExceptionEnum : AliyunSmsResultEnum.values()) { + if (smsExceptionEnum.getCode().equals(smsRes.getString("Code"))) { + code = smsExceptionEnum.getCode(); + errorMessage = smsExceptionEnum.getMessage(); + } + } + log.error(">>> 发送短信异常!code = " + code + ",message = " + errorMessage); + throw new AliyunSmsException(code, errorMessage); + } + } + + /** + * 初始化短信发送的客户端 + * + * @author xuyuxiang + * @date 2018/7/6 下午3:57 + */ + private IAcsClient initClient() { + final String accessKeyId = aliyunSmsProperties.getAccessKeyId(); + final String accessKeySecret = aliyunSmsProperties.getAccessKeySecret(); + + // 创建DefaultAcsClient实例并初始化 + DefaultProfile profile = DefaultProfile.getProfile(aliyunSmsProperties.getRegionId(), accessKeyId, accessKeySecret); + return new DefaultAcsClient(profile); + } + + /** + * 组装请求对象 + * + * @author xuyuxiang + * @date 2018/7/6 下午3:00 + */ + private JSONObject createSmsRequest(String phoneNumber, String templateCode, Map<String, Object> params, IAcsClient acsClient) { + CommonRequest request = new CommonRequest(); + request.setSysDomain(aliyunSmsProperties.getSmsDomain()); + request.setSysVersion(aliyunSmsProperties.getSmsVersion()); + request.setSysAction(aliyunSmsProperties.getSmsSendAction()); + + // 接收短信的手机号码 + request.putQueryParameter("PhoneNumbers", phoneNumber); + + // 短信签名名称。请在控制台签名管理页面签名名称一列查看(必须是已添加、并通过审核的短信签名)。 + request.putQueryParameter("SignName", this.getSmsSign(phoneNumber)); + + // 短信模板ID + request.putQueryParameter("TemplateCode", templateCode); + + // 短信模板变量对应的实际值,JSON格式。 + request.putQueryParameter("TemplateParam", JSON.toJSONString(params)); + + //请求失败这里会抛ClientException异常 + CommonResponse commonResponse; + try { + commonResponse = acsClient.getCommonResponse(request); + String data = commonResponse.getData(); + String jsonResult = data.replaceAll("'\'", ""); + log.info(">>> 获取到发送短信的响应结果!{}", jsonResult); + return JSON.parseObject(jsonResult); + } catch (ClientException e) { + log.error(">>> 初始化阿里云sms异常!可能是accessKey和secret错误!", e); + throw new AliyunSmsException(AliyunSmsResultEnum.INIT_SMS_CLIENT_ERROR.getCode(), + AliyunSmsResultEnum.INIT_SMS_CLIENT_ERROR.getMessage()); + } + } + + /** + * 校验发送短信的参数是否正确 + * + * @author xuyuxiang + * @date 2018/7/6 下午3:19 + */ + private void assertSendSmsParams(String phoneNumber, String templateCode, Map<String, Object> params, + AliyunSmsProperties aliyunSmsProperties) { + if (ObjectUtil.hasEmpty(phoneNumber, templateCode, params, aliyunSmsProperties)) { + log.error(">>> 阿里云短信发送异常!请求参数存在空!"); + throw new AliyunSmsException(AliyunSmsResultEnum.PARAM_NULL.getCode(), AliyunSmsResultEnum.PARAM_NULL.getMessage()); + } + } + + /** + * 获取sms发送的sign标识,参数phone是发送的手机号码 + * + * @author xuyuxiang + * @date 2018/8/13 21:23 + */ + private String getSmsSign(String phone) { + String signName = aliyunSmsProperties.getSignName(); + + // 如果是单个签名就用一个签名发 + if (!signName.contains(",")) { + log.info(">>> 发送短信,签名为:" + signName + ",电话为:" + phone); + return signName; + } else { + return multiSignManager.getSign(phone, signName); + } + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/enums/AliyunSmsResultEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/enums/AliyunSmsResultEnum.java new file mode 100644 index 0000000..5d6ea5f --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/enums/AliyunSmsResultEnum.java @@ -0,0 +1,148 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.aliyun.enums; + + +import lombok.Getter; + +/** + * 短信发送异常相关枚举 + * + * @author xuyuxiang + * @date 2018/1/4 22:40 + */ +@Getter +public enum AliyunSmsResultEnum { + + + /** + * 初始化sms客户端错误,accessKey错误 + */ + INIT_SMS_CLIENT_ERROR("SMS_CLIENT_INIT_ERROR", "初始化sms客户端错误,accessKey错误"), + + /** + * 请求参数为空 + */ + PARAM_NULL("NULL", "请求参数为空"), + + /** + * 请求成功 + */ + OK("OK", "请求成功"), + + /** + * RAM权限DENY + */ + RAM_PERMISSION_DENY("isp.RAM_PERMISSION_DENY", "RAM权限DENY"), + + /** + * 产品未开通 + */ + PRODUCT_UNSUBSCRIBE("isv.PRODUCT_UNSUBSCRIBE", "产品未开通"), + + /** + * 账户不存在 + */ + ACCOUNT_NOT_EXISTS("isv.ACCOUNT_NOT_EXISTS", "账户不存在"), + + /** + * 账户异常 + */ + ACCOUNT_ABNORMAL("isv.ACCOUNT_ABNORMAL", "账户异常"), + + /** + * 短信模板不合法 + */ + SMS_TEMPLATE_ILLEGAL("isv.SMS_TEMPLATE_ILLEGAL", "短信模板不合法"), + + /** + * 短信签名不合法 + */ + SMS_SIGNATURE_ILLEGAL("isv.SMS_SIGNATURE_ILLEGAL", "短信签名不合法"), + + /** + * 参数异常 + */ + INVALID_PARAMETERS("isv.INVALID_PARAMETERS", "参数异常"), + + /** + * 系统错误 + */ + SYSTEM_ERROR("isp.SYSTEM_ERROR", "系统错误"), + + /** + * 非法手机号 + */ + MOBILE_NUMBER_ILLEGAL("isv.MOBILE_NUMBER_ILLEGAL", "非法手机号"), + + /** + * 手机号码数量超过限制 + */ + MOBILE_COUNT_OVER_LIMIT("isv.MOBILE_COUNT_OVER_LIMIT", "手机号码数量超过限制"), + + /** + * 模板缺少变量 + */ + TEMPLATE_MISSING_PARAMETERS("isv.TEMPLATE_MISSING_PARAMETERS", "模板缺少变量"), + + /** + * 发送短信过于频繁,请稍后再试 + */ + BUSINESS_LIMIT_CONTROL("isv.BUSINESS_LIMIT_CONTROL", "发送短信过于频繁,请稍后再试"), + + /** + * JSON参数不合法,只接受字符串值 + */ + INVALID_JSON_PARAM("isv.INVALID_JSON_PARAM", "JSON参数不合法,只接受字符串值"), + + /** + * 黑名单管控 + */ + BLACK_KEY_CONTROL_LIMIT("isv.BLACK_KEY_CONTROL_LIMIT", "黑名单管控"), + + /** + * 参数超出长度限制 + */ + PARAM_LENGTH_LIMIT("isv.PARAM_LENGTH_LIMIT", "参数超出长度限制"), + + /** + * 不支持URL + */ + PARAM_NOT_SUPPORT_URL("isv.PARAM_NOT_SUPPORT_URL", "不支持URL"), + + /** + * 账户余额不足 + */ + AMOUNT_NOT_ENOUGH("isv.AMOUNT_NOT_ENOUGH", "账户余额不足"); + + private String code; + + private final String message; + + AliyunSmsResultEnum(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/exp/AliyunSmsException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/exp/AliyunSmsException.java new file mode 100644 index 0000000..0114fa9 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/exp/AliyunSmsException.java @@ -0,0 +1,47 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.aliyun.exp; + +import lombok.Getter; + +/** + * 短信发送异常 + * + * @author xuyuxiang + * @date 2018-07-06-下午3:00 + */ +@Getter +public class AliyunSmsException extends RuntimeException { + + private final String code; + + private final String errorMessage; + + public AliyunSmsException(String code, String errorMessage) { + super(errorMessage); + this.code = code; + this.errorMessage = errorMessage; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/msign/MultiSignManager.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/msign/MultiSignManager.java new file mode 100644 index 0000000..c8b48a8 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/msign/MultiSignManager.java @@ -0,0 +1,46 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.aliyun.msign; + +/** + * 多个签名的缓存管理,为了打破一个签名发送次数的限制 + * + * @author xuyuxiang + * @date 2018-09-21-上午10:47 + */ +public interface MultiSignManager { + + /** + * 获取签名 + * + * @param phone 电话 + * @param signName 发送短信用的签名,是一个以逗号隔开的字符串 + * @return 签名 + * @author xuyuxiang + * @date 2018/9/21 上午10:51 + */ + String getSign(String phone, String signName); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/msign/impl/MapBasedMultiSignManager.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/msign/impl/MapBasedMultiSignManager.java new file mode 100644 index 0000000..d113cb4 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/msign/impl/MapBasedMultiSignManager.java @@ -0,0 +1,88 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.aliyun.msign.impl; + +import cn.hutool.log.Log; +import vip.xiaonuo.core.sms.modular.aliyun.msign.MultiSignManager; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 获取缓存的map中的签名 + * + * @author xuyuxiang + * @date 2018-09-21-上午10:49 + */ +public class MapBasedMultiSignManager implements MultiSignManager { + + private static final Log log = Log.get(); + + private static final int CLEAR_COUNT = 1000; + + private final Map<String, String> cacheMap = new ConcurrentHashMap<>(); + + @Override + public String getSign(String phone, String signName) { + + //先清除map + clearMap(); + + //分割签名数组 + String[] signNames = signName.split(","); + + //获取上次发送的时候用的哪个签名,这次换一个 + Object lastSignName = cacheMap.get(phone); + if (lastSignName == null) { + cacheMap.put(phone, signNames[0]); + log.info(">>> 发送短信,签名为:" + signNames[0] + ",电话为:" + phone); + return signNames[0]; + } else { + for (String name : signNames) { + if (!name.equals(lastSignName)) { + cacheMap.put(phone, name); + log.info(">>> 发送短信,签名为:" + name + ",电话为:" + phone); + return name; + } + } + cacheMap.put(phone, signNames[0]); + log.info(">>> 发送短信,签名为:" + signNames[0] + ",电话为:" + phone); + return signNames[0]; + } + } + + /** + * 每隔一段时间清除下map + * + * @author xuyuxiang + * @date 2018/9/21 上午10:57 + */ + private void clearMap() { + if (cacheMap.size() >= CLEAR_COUNT) { + cacheMap.clear(); + } + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/prop/AliyunSmsProperties.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/prop/AliyunSmsProperties.java new file mode 100644 index 0000000..364032f --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/aliyun/prop/AliyunSmsProperties.java @@ -0,0 +1,78 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.aliyun.prop; + +import lombok.Data; + +/** + * 阿里云oss相关配置 + * + * @author xuyuxiang + * @date 2018-06-27-下午1:20 + */ +@Data +public class AliyunSmsProperties { + + /** + * accessKeyId + */ + private String accessKeyId; + + /** + * accessKeySecret + */ + private String accessKeySecret; + + /** + * 签名名称 + */ + private String signName; + + /** + * 短信失效时间(分钟) + */ + private Integer invalidateMinutes = 2; + + /** + * 地域id(阿里云sdk默认的,一般不用修改) + */ + private String regionId = "cn-hangzhou"; + + /** + * domain(阿里云sdk默认的,一般不用修改) + */ + private String smsDomain = "dysmsapi.aliyuncs.com"; + + /** + * version(阿里云sdk默认的,一般不用修改) + */ + private String smsVersion = "2017-05-25"; + + /** + * sms发送(阿里云sdk默认的,一般不用修改) + */ + private String smsSendAction = "SendSms"; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/TencentSmsSender.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/TencentSmsSender.java new file mode 100644 index 0000000..9f8d5f5 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/TencentSmsSender.java @@ -0,0 +1,113 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.tencent; + +import cn.hutool.core.util.ArrayUtil; +import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import com.tencentcloudapi.common.profile.ClientProfile; +import com.tencentcloudapi.common.profile.HttpProfile; +import com.tencentcloudapi.sms.v20190711.SmsClient; +import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest; +import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse; +import com.tencentcloudapi.sms.v20190711.models.SendStatus; +import vip.xiaonuo.core.sms.SmsSender; +import vip.xiaonuo.core.sms.modular.tencent.exp.TencentSmsException; +import vip.xiaonuo.core.sms.modular.tencent.prop.TencentSmsProperties; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Map; + +/** + * 腾讯云短信发送 + * + * @author xuyuxiang + * @date 2020/5/24 17:58 + */ +public class TencentSmsSender implements SmsSender { + + private TencentSmsProperties tencentSmsProperties; + + public TencentSmsSender(TencentSmsProperties tencentSmsProperties) { + this.tencentSmsProperties = tencentSmsProperties; + } + + @Override + public void sendSms(String phone, String templateCode, Map<String, Object> params) { + try { + + // 实例化一个认证对象 + Credential cred = new Credential( + tencentSmsProperties.getSecretId(), tencentSmsProperties.getSecretKey()); + + // 实例化一个 http 选项,可选,无特殊需求时可以跳过 + HttpProfile httpProfile = new HttpProfile(); + ClientProfile clientProfile = new ClientProfile(); + clientProfile.setSignMethod("HmacSHA256"); + clientProfile.setHttpProfile(httpProfile); + + // 实例化 SMS 的 client 对象 + SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile); + + // 构建请求参数 + SendSmsRequest req = new SendSmsRequest(); + + // 设置应用id + req.setSmsSdkAppid(tencentSmsProperties.getSdkAppId()); + + // 设置签名 + req.setSign(tencentSmsProperties.getSign()); + + // 设置模板id + req.setTemplateID(templateCode); + + // 默认发送一个手机短信 + String[] phoneNumbers = {"+86" + phone}; + req.setPhoneNumberSet(phoneNumbers); + + // 模板参数 + if (params != null && params.size() > 0) { + LinkedList<String> strings = new LinkedList<>(); + Collection<Object> values = params.values(); + for (Object value : values) { + strings.add(value.toString()); + } + req.setTemplateParamSet(ArrayUtil.toArray(strings, String.class)); + } + + SendSmsResponse res = client.SendSms(req); + + SendStatus[] sendStatusSet = res.getSendStatusSet(); + if (sendStatusSet != null && sendStatusSet.length > 0) { + if (!sendStatusSet[0].getCode().equals("Ok")) { + throw new TencentSmsException(sendStatusSet[0].getCode(), sendStatusSet[0].getMessage()); + } + } + } catch (TencentCloudSDKException e) { + throw new TencentSmsException("500", e.getMessage()); + } + } +} \ No newline at end of file diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/exp/TencentSmsException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/exp/TencentSmsException.java new file mode 100644 index 0000000..e4523cd --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/exp/TencentSmsException.java @@ -0,0 +1,47 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.tencent.exp; + +import lombok.Getter; + +/** + * 短信发送异常 + * + * @author xuyuxiang + * @date 2018-07-06-下午3:00 + */ +@Getter +public class TencentSmsException extends RuntimeException { + + private final String code; + + private final String errorMessage; + + public TencentSmsException(String code, String errorMessage) { + super(errorMessage); + this.code = code; + this.errorMessage = errorMessage; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/prop/TencentSmsProperties.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/prop/TencentSmsProperties.java new file mode 100644 index 0000000..b1d8aaa --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/sms/modular/tencent/prop/TencentSmsProperties.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.sms.modular.tencent.prop; + +import lombok.Data; + +/** + * 腾讯云短信配置 + * + * @author xuyuxiang + * @date 2020/5/24 18:01 + */ +@Data +public class TencentSmsProperties { + + /** + * secretId + */ + private String secretId; + + /** + * secretKey + */ + private String secretKey; + + /** + * 应用控制台应用管理中的应用id + * <p> + * 在 [短信控制台] 添加应用后生成的实际 SDKAppID,例如1400006666 + * <p> + * 短信控制台:https://console.cloud.tencent.com/smsv2 + */ + private String sdkAppId; + + /** + * 签名,一般为中文名 + * <p> + * 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名,可登录 [短信控制台] 查看签名信息 + */ + private String sign; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/consts/TenantConstants.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/consts/TenantConstants.java new file mode 100644 index 0000000..1bf31d8 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/consts/TenantConstants.java @@ -0,0 +1,55 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.consts; + +/** + * 租户的常量 + * + * @author xuyuxiang + * @date 2019-06-18-16:24 + */ +public interface TenantConstants { + + /** + * 租户数据源标识前缀 + */ + String TENANT_DB_PREFIX = "snowy_tenant_db_"; + + /** + * 初始化租户的sql文件名称 + */ + String INIT_SQL_FILE_NAME = "tenant_init.sql"; + + /** + * 租户编码的字段名称 + */ + String TENANT_CODE = "tenantCode"; + + /** + * 租户对应的数据库编码字段名称 + */ + String TENANT_DB_NAME = "tenantDbName"; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/consts/TenantExpEnumConstant.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/consts/TenantExpEnumConstant.java new file mode 100644 index 0000000..6b10c4b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/consts/TenantExpEnumConstant.java @@ -0,0 +1,45 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.consts; + +/** + * 多租户的异常编码规范 + * + * @author xuyuxiang + * @date 2020/9/3 + */ +public interface TenantExpEnumConstant { + + /** + * 模块分类编码(2位) + */ + int TENANT_MODULE_EXP_CODE = 60; + + /** + * 租户操作相关分类编码(4位) + */ + int TENANT_EXCEPTION_ENUM = 1100; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/context/TenantCodeHolder.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/context/TenantCodeHolder.java new file mode 100644 index 0000000..ed01097 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/context/TenantCodeHolder.java @@ -0,0 +1,48 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.context; + +/** + * 租户编码的临时存放 + * + * @author xuyuxiang + * @date 2019-06-19-14:08 + */ +public class TenantCodeHolder { + + private static final ThreadLocal<String> TENANT_CODE_HOLDER = new ThreadLocal<>(); + + public static void put(String value) { + TENANT_CODE_HOLDER.set(value); + } + + public static String get() { + return TENANT_CODE_HOLDER.get(); + } + + public static void remove() { + TENANT_CODE_HOLDER.remove(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/context/TenantDbNameHolder.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/context/TenantDbNameHolder.java new file mode 100644 index 0000000..6d461ac --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/context/TenantDbNameHolder.java @@ -0,0 +1,48 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.context; + +/** + * 租户对应的数据库的临时存放 + * + * @author xuyuxiang + * @date 2019-06-19-14:08 + */ +public class TenantDbNameHolder { + + private static final ThreadLocal<String> DB_NAME_HOLDER = new ThreadLocal<>(); + + public static void put(String value) { + DB_NAME_HOLDER.set(value); + } + + public static String get() { + return DB_NAME_HOLDER.get(); + } + + public static void remove() { + DB_NAME_HOLDER.remove(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/entity/TenantInfo.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/entity/TenantInfo.java new file mode 100644 index 0000000..0c47e79 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/entity/TenantInfo.java @@ -0,0 +1,95 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; + +/** + * <p> + * 租户表 + * </p> + * + * @author yubaoshan + * @since 2019-06-16 + */ +@TableName("sys_tenant_info") +@Data +public class TenantInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + /** + * 租户名称 + */ + @TableField("name") + private String name; + + /** + * 租户的编码 + */ + @TableField("code") + private String code; + + /** + * 关联的数据库名称 + */ + @TableField("db_name") + private String dbName; + + /** + * 创建时间 + */ + @TableField(value = "create_time", fill = FieldFill.INSERT) + private Date createTime; + + /** + * 创建人 + */ + @TableField(value = "create_user", fill = FieldFill.INSERT) + private Long createUser; + + /** + * 更新时间 + */ + @TableField(value = "update_time", fill = FieldFill.UPDATE) + private Date updateTime; + + /** + * 更新人 + */ + @TableField(value = "update_user", fill = FieldFill.UPDATE) + private Long updateUser; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/exception/TenantException.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/exception/TenantException.java new file mode 100644 index 0000000..0441d67 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/exception/TenantException.java @@ -0,0 +1,42 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.exception; + +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; + +/** + * 多租户的异常 + * + * @author xuyuxiang + * @date 2020/9/3 + */ +public class TenantException extends ServiceException { + + public TenantException(AbstractBaseExceptionEnum exception) { + super(exception); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/exception/enums/TenantExceptionEnum.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/exception/enums/TenantExceptionEnum.java new file mode 100644 index 0000000..77849e0 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/exception/enums/TenantExceptionEnum.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.exception.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.core.tenant.consts.TenantExpEnumConstant; + +/** + * 多租户异常枚举 + * + * @author xuyuxiang + * @date 2020/8/24 + */ +@ExpEnumType(module = TenantExpEnumConstant.TENANT_MODULE_EXP_CODE, kind = TenantExpEnumConstant.TENANT_EXCEPTION_ENUM) +public enum TenantExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 更新租户的密码错误 + */ + UPDATE_TENANT_PASSWORD_ERROR(1, "更新租户的密码错误"), + + /** + * 多数据源模块未启用,找不到DatabaseInfoService + */ + DBS_MODULAR_NOT_ENABLE_ERROR(2, "多数据源模块未启用,找不到DatabaseInfoService"), + + /** + * 找不到该租户信息 + */ + CNAT_FIND_TENANT_ERROR(3, "找不到该租户信息"), + + /** + * 多租户模块未启用,找不到TenantInfoService + */ + TENANT_MODULE_NOT_ENABLE_ERROR(4, "多租户模块未启用,找不到TenantInfoService"); + + TenantExceptionEnum(int code, String message) { + this.code = code; + this.message = message; + } + + private final Integer code; + + private final String message; + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/params/TenantInfoParam.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/params/TenantInfoParam.java new file mode 100644 index 0000000..04131ae --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/params/TenantInfoParam.java @@ -0,0 +1,78 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.params; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * <p> + * 租户表 + * </p> + * + * @author yubaoshan + * @since 2019-06-16 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class TenantInfoParam extends BaseParam { + + private static final long serialVersionUID = 1L; + + /** + * 主键id + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 租户名称 + */ + @NotBlank(message = "租户名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 租户的编码 + */ + @NotBlank(message = "租户的编码不能为空,请检查code参数", groups = {add.class, edit.class}) + private String code; + + /** + * 租户管理员账号 + */ + @NotBlank(message = "租户管理员账号不能为空,请检查adminUsername参数", groups = {add.class}) + private String adminUsername; + + /** + * 租户管理员密码 + */ + @NotBlank(message = "租户管理员账号不能为空,请检查adminPassword参数", groups = {add.class}) + private String adminPassword; + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/service/TenantInfoService.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/service/TenantInfoService.java new file mode 100644 index 0000000..45d88d3 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/tenant/service/TenantInfoService.java @@ -0,0 +1,87 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.tenant.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.tenant.entity.TenantInfo; +import vip.xiaonuo.core.tenant.params.TenantInfoParam; + +/** + * 租户表 服务类 + * + * @author yubaoshan + * @since 2019-06-16 + */ +public interface TenantInfoService extends IService<TenantInfo> { + + /** + * 新增租户 + * + * @param param 添加参数 + * @author yubaoshan + * @date 2019-06-16 + */ + void add(TenantInfoParam param); + + /** + * 删除租户 + * + * @param param 删除参数 + * @author yubaoshan + * @date 2019-06-16 + */ + void delete(TenantInfoParam param); + + /** + * 更新租户 + * + * @param param 更新参数 + * @author yubaoshan + * @date 2019-06-16 + */ + void update(TenantInfoParam param); + + /** + * 分页查询租户列表 + * + * @param param 查询参数 + * @return 查询结果 + * @author xuyuxiang + * @date 2020/9/3 + */ + PageResult<TenantInfo> page(TenantInfoParam param); + + /** + * 获取租户信息,通过租户编码 + * + * @param code 租户编码 + * @return 租户信息 + * @author xuyuxiang + * @date 2019-06-19 14:17 + */ + TenantInfo getByCode(String code); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/timer/TimerTaskRunner.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/timer/TimerTaskRunner.java new file mode 100644 index 0000000..1c4e6e2 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/timer/TimerTaskRunner.java @@ -0,0 +1,45 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.timer; + +/** + * 定时器执行者 + * <p> + * 定时器都要实现本接口,并需要把实现类加入到spring容器中 + * + * @author yubaoshan + * @date 2020/6/28 21:28 + */ +public interface TimerTaskRunner { + + /** + * 任务执行的具体内容 + * + * @author yubaoshan + * @date 2020/6/28 21:29 + */ + void action(); + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/AopTargetUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/AopTargetUtil.java new file mode 100644 index 0000000..3b6eb53 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/AopTargetUtil.java @@ -0,0 +1,88 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.log.Log; +import org.springframework.aop.framework.AdvisedSupport; +import org.springframework.aop.framework.AopProxy; +import org.springframework.aop.support.AopUtils; +import vip.xiaonuo.core.context.requestno.RequestNoContext; + +import java.lang.reflect.Field; + +/** + * 获取代理原始对象的工具 + * + * @author yubaoshan + * @date 2018/2/21 17:09 + */ +public class AopTargetUtil { + + private static final Log log = Log.get(); + + /** + * 获取被代理的对象本身 + * + * @author yubaoshan + * @date 2020/6/21 17:02 + */ + public static Object getTarget(Object proxy) { + + // 判断是不是代理对象,如果不是直接返回 + if (!AopUtils.isAopProxy(proxy)) { + return proxy; + } + + try { + if (AopUtils.isJdkDynamicProxy(proxy)) { + return getJdkDynamicProxyTargetObject(proxy); + } else { + return getCglibProxyTargetObject(proxy); + } + } catch (Exception e) { + log.error(">>> 获取代理对象异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + return null; + } + } + + private static Object getCglibProxyTargetObject(Object proxy) throws Exception { + Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0"); + h.setAccessible(true); + Object dynamicAdvisedInterceptor = h.get(proxy); + Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised"); + advised.setAccessible(true); + return ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget(); + } + + private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception { + Field h = proxy.getClass().getSuperclass().getDeclaredField("h"); + h.setAccessible(true); + AopProxy aopProxy = (AopProxy) h.get(proxy); + Field advised = aopProxy.getClass().getDeclaredField("advised"); + advised.setAccessible(true); + return ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget(); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/CryptogramUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/CryptogramUtil.java new file mode 100644 index 0000000..10b133b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/CryptogramUtil.java @@ -0,0 +1,139 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.log.Log; +import com.antherd.smcrypto.sm2.Sm2; +import com.antherd.smcrypto.sm3.Sm3; +import com.antherd.smcrypto.sm4.Sm4; +import com.antherd.smcrypto.sm4.Sm4Options; +import vip.xiaonuo.core.cryptogram.keypair; + +/** + * 加密工具类,本框架目前使用 https://github.com/antherd/sm-crypto 项目中一些加解密方式 + * 使用小伙伴需要过等保密评相关,请在此处更改为自己的加密方法,或加密机,使用加密机同时需要替换公钥,私钥在内部无法导出,提供加密的方法 + * + * @author yubaoshan + */ +public class CryptogramUtil { + + private static final Log log = Log.get(); + + /** + * 加密方法(Sm2 的专门针对前后端分离,非对称秘钥对的方式,暴露出去的公钥,对传输过程中的密码加个密) + * + * @author yubaoshan + * @param str 待加密数据 + * @return 加密后的密文 + */ + public static String doSm2Encrypt (String str) { + return Sm2.doEncrypt(str, keypair.PUBLIC_KEY); + } + + /** + * 解密方法 + * 如果采用加密机的方法,用try catch 捕捉异常,返回原文值即可 + * + * @author yubaoshan + * @param str 密文 + * @return 解密后的明文 + */ + public static String doSm2Decrypt (String str) { + // 解密 + return Sm2.doDecrypt(str, keypair.PRIVATE_KEY); + } + + /** + * 加密方法 + * + * @author yubaoshan + * @param str 待加密数据 + * @return 加密后的密文 + */ + public static String doEncrypt (String str) { + // SM4 加密 cbc模式 + Sm4Options sm4Options4 = new Sm4Options(); + sm4Options4.setMode("cbc"); + sm4Options4.setIv("fedcba98765432100123456789abcdef"); + return Sm4.encrypt(str, keypair.KEY, sm4Options4); + } + + /** + * 解密方法 + * 如果采用加密机的方法,用try catch 捕捉异常,返回原文值即可 + * + * @author yubaoshan + * @param str 密文 + * @return 解密后的明文 + */ + public static String doDecrypt (String str) { + // 解密,cbc 模式,输出 utf8 字符串 + Sm4Options sm4Options8 = new Sm4Options(); + sm4Options8.setMode("cbc"); + sm4Options8.setIv("fedcba98765432100123456789abcdef"); + String docString = Sm4.decrypt(str, keypair.KEY, sm4Options8); + if (docString.equals("")) { + log.warn(">>> 字段解密失败,返回原文值:{}", str); + return str; + } else { + return docString; + } + } + + /** + * 纯签名 + * + * @author yubaoshan + * @param str 待签名数据 + * @return 签名结果 + */ + public static String doSignature (String str) { + return Sm2.doSignature(str, keypair.PRIVATE_KEY); + } + + /** + * 验证签名结果 + * + * @author yubaoshan + * @param originalStr 签名原文数据 + * @param str 签名结果 + * @return 是否通过 + */ + public static boolean doVerifySignature (String originalStr, String str) { + return Sm2.doVerifySignature(originalStr, str, keypair.PUBLIC_KEY); + } + + /** + * 通过杂凑算法取得hash值,用于做数据完整性保护 + * + * @author yubaoshan + * @param str 字符串 + * @return hash 值 + */ + public static String doHashValue (String str) { + return Sm3.sm3(str); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/HttpServletUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/HttpServletUtil.java new file mode 100644 index 0000000..09d465f --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/HttpServletUtil.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.ServerExceptionEnum; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * HttpServlet工具类,获取当前request和response + * + * @author xuyuxiang + * @date 2020/3/30 15:09 + */ +public class HttpServletUtil { + + /** + * 获取当前请求的request对象 + * + * @author xuyuxiang + * @date 2020/3/30 15:10 + */ + public static HttpServletRequest getRequest() { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (requestAttributes == null) { + throw new ServiceException(ServerExceptionEnum.REQUEST_EMPTY); + } else { + return requestAttributes.getRequest(); + } + } + + /** + * 获取当前请求的response对象 + * + * @author xuyuxiang + * @date 2020/3/30 15:10 + */ + public static HttpServletResponse getResponse() { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (requestAttributes == null) { + throw new ServiceException(ServerExceptionEnum.REQUEST_EMPTY); + } else { + return requestAttributes.getResponse(); + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/IpAddressUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/IpAddressUtil.java new file mode 100644 index 0000000..a6e79d7 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/IpAddressUtil.java @@ -0,0 +1,109 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import cn.hutool.log.Log; +import com.alibaba.fastjson.JSONPath; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 根据ip地址定位工具类,使用阿里云定位api,如使用本接口,仅需使用以下地址购买接口,然后替换sys_config表中定位appCode为你自己的即可 + * 接口购买地址:https://market.aliyun.com/products/57002003/cmapi021970.html + * + * @author xuyuxiang + * @date 2020/3/16 11:25 + */ +public class IpAddressUtil { + + private static final Log log = Log.get(); + + private static final String LOCAL_IP = "127.0.0.1"; + + private static final String LOCAL_REMOTE_HOST = "0:0:0:0:0:0:0:1"; + + /** + * 获取客户端ip + * + * @author xuyuxiang + * @date 2020/3/19 9:32 + */ + public static String getIp(HttpServletRequest request) { + if (ObjectUtil.isEmpty(request)) { + return LOCAL_IP; + } else { + String remoteHost = ServletUtil.getClientIP(request); + return LOCAL_REMOTE_HOST.equals(remoteHost) ? LOCAL_IP : remoteHost; + } + } + + /** + * 根据ip地址定位 + * + * @author xuyuxiang + * @date 2020/3/16 15:17 + */ + @SuppressWarnings("unchecked") + public static String getAddress(HttpServletRequest request) { + String resultJson = SymbolConstant.DASH; + + String ip = getIp(request); + + //如果是本地ip或局域网ip,则直接不查询 + if (ObjectUtil.isEmpty(ip) || NetUtil.isInnerIP(ip)) { + return resultJson; + } + + try { + //获取阿里云定位api接口 + String api = ConstantContextHolder.getIpGeoApi(); + //获取阿里云定位appCode + String appCode = ConstantContextHolder.getIpGeoAppCode(); + if (ObjectUtil.isAllNotEmpty(api, appCode)) { + String path = "$['data']['country','region','city','isp']"; + String appCodeSymbol = "APPCODE"; + HttpRequest http = HttpUtil.createGet(String.format(api, ip)); + http.header(CommonConstant.AUTHORIZATION, appCodeSymbol + " " + appCode); + resultJson = http.timeout(3000).execute().body(); + resultJson = String.join("", (List<String>) JSONPath.read(resultJson, path)); + } + } catch (Exception e) { + resultJson = SymbolConstant.DASH; + //注释掉此log,以免频繁打印,可自行开启 + //log.error(">>> 根据ip定位异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + } + return resultJson; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/JoinPointUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/JoinPointUtil.java new file mode 100644 index 0000000..6864a00 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/JoinPointUtil.java @@ -0,0 +1,73 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import org.aspectj.lang.JoinPoint; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Spring切面工具类 + * + * @author xuyuxiang + * @date 2020/3/16 17:56 + */ +public class JoinPointUtil { + + /** + * 获取切面的参数json + * + * @author xuyuxiang + * @date 2020/3/16 17:57 + */ + public static String getArgsJsonString(JoinPoint joinPoint) { + StringBuilder argsJson = new StringBuilder(); + Object[] args = joinPoint.getArgs(); + for (Object arg : args) { + if (!isFilterObject(arg)) { + if (ObjectUtil.isNotNull(arg)) { + String jsonStr = JSON.toJSONString(arg); + argsJson.append(jsonStr).append(" "); + } + } + } + return argsJson.toString().trim(); + } + + /** + * 判断是否需要拼接参数,过滤掉HttpServletRequest,MultipartFile,HttpServletResponse等类型参数 + * + * @author xuyuxiang + * @date 2020/3/16 17:59 + */ + private static boolean isFilterObject(Object arg) { + return arg instanceof MultipartFile || arg instanceof HttpServletRequest || arg instanceof HttpServletResponse; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/LibreOfficeUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/LibreOfficeUtil.java new file mode 100644 index 0000000..8d4a1fb --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/LibreOfficeUtil.java @@ -0,0 +1,141 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.log.Log; +import org.jodconverter.DocumentConverter; +import org.jodconverter.document.DocumentFormat; +import org.jodconverter.office.OfficeException; +import org.springframework.http.MediaType; +import vip.xiaonuo.core.consts.MediaTypeConstant; +import vip.xiaonuo.core.enums.DocumentFormatEnum; +import vip.xiaonuo.core.exception.LibreOfficeException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * LibreOffice工具类,用于将word,excel,ppt等格式文件转为pdf预览 + * + * @author xuyuxiang + * @date 2020/7/6 14:55 + */ +public class LibreOfficeUtil { + + private static final Log log = Log.get(); + + private static DocumentConverter documentConverter; + + private static void init() { + try { + documentConverter = SpringUtil.getBean(DocumentConverter.class); + } catch (Exception e) { + throw new LibreOfficeException(); + } + } + + /** + * 将文件流转换为PDF流 + * + * @param inputStream:输入流 + * @param outputStream:输入pdf流 + * @param fileSuffix:源文件后缀 + * @return 目标类型的contentType + * @author xuyuxiang + * @date 2020/7/6 15:02 + */ + public static void convertToPdf(InputStream inputStream, OutputStream outputStream, String fileSuffix) { + if(!MediaTypeConstant.DOC_PDF.equals(fileSuffix)) { + init(); + final DocumentFormatEnum documentFormatEnum = DocumentFormatEnum.valueOf(fileSuffix.toUpperCase()); + final DocumentFormat format = documentFormatEnum.getFormFormat(); + log.info(">>> 待转换的文档类型:{}", format); + final DocumentFormat targetFormat = documentFormatEnum.getTargetFormat(); + log.info(">>> 转换的目标文档类型:{}", targetFormat); + try { + final InputStream is = documentFormatEnum.getInputStream(inputStream); + documentConverter.convert(is).as(format).to(outputStream).as(targetFormat).execute(); + } catch (IOException | OfficeException e) { + e.printStackTrace(); + } + log.info(">>> 文件转换结束"); + } + } + + /** + * 根据文件后缀判断是否图片 + * + * @author xuyuxiang + * @date 2020/7/6 15:31 + */ + public static boolean isPic(String fileSuffix) { + return MediaTypeConstant.IMG_JPG.equals(fileSuffix) + || MediaTypeConstant.IMG_JPEG.equals(fileSuffix) + || MediaTypeConstant.IMG_PNG.equals(fileSuffix) + || MediaTypeConstant.IMG_GIF.equals(fileSuffix) + || MediaTypeConstant.IMG_TIF.equals(fileSuffix) + || MediaTypeConstant.IMG_BMP.equals(fileSuffix); + } + + /** + * 根据文件后缀判断是否文档 + * + * @author xuyuxiang + * @date 2020/7/6 15:31 + */ + public static boolean isDoc(String fileSuffix) { + return MediaTypeConstant.DOC_TXT.equals(fileSuffix) + || MediaTypeConstant.DOC_DOC.equals(fileSuffix) + || MediaTypeConstant.DOC_DOCX.equals(fileSuffix) + || MediaTypeConstant.DOC_XLS.equals(fileSuffix) + || MediaTypeConstant.DOC_XLSX.equals(fileSuffix) + || MediaTypeConstant.DOC_PPT.equals(fileSuffix) + || MediaTypeConstant.DOC_PPTX.equals(fileSuffix) + || MediaTypeConstant.DOC_PDF.equals(fileSuffix); + } + + /** + * 根据文件后缀获取转换目标类型 + * + * @author xuyuxiang + * @date 2020/7/6 17:03 + */ + public static String getTargetContentTypeBySuffix(String fileSuffix) { + //如果目标类型是pdf + if (MediaTypeConstant.DOC_TXT.equals(fileSuffix) + || MediaTypeConstant.DOC_DOC.equals(fileSuffix) + || MediaTypeConstant.DOC_DOCX.equals(fileSuffix) + || MediaTypeConstant.DOC_PPT.equals(fileSuffix) + || MediaTypeConstant.DOC_PPTX.equals(fileSuffix) + || MediaTypeConstant.DOC_PDF.equals(fileSuffix)) { + return MediaType.APPLICATION_PDF_VALUE; + } else { + //否则是html类型 + return MediaType.TEXT_HTML_VALUE; + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PageUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PageUtil.java new file mode 100644 index 0000000..7f93328 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PageUtil.java @@ -0,0 +1,61 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import java.util.List; + +/** + * 分页工具类针对hutool分页的扩展 + * + * @author xuyuxiang + * @date 2020/9/19 10:30 + **/ +public class PageUtil<T> extends cn.hutool.core.util.PageUtil{ + + /** + * 逻辑分页 + * + * @author xuyuxiang + * @date 2020/9/19 10:36 + **/ + public static <T> List<T> page(Page<T> page, List<T> list) { + setFirstPageNo(1); + int start = getStart(Convert.toInt(page.getCurrent()), Convert.toInt(page.getSize())); + int end = getEnd(Convert.toInt(page.getCurrent()), Convert.toInt(page.getSize())); + if(start > list.size()) { + return CollectionUtil.newArrayList(); + }else if(start > end) { + return CollectionUtil.newArrayList(); + } else if(end > list.size()) { + return list.subList(start, list.size()); + } else { + return list.subList(start, end); + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PastTimeFormatUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PastTimeFormatUtil.java new file mode 100644 index 0000000..7942d6c --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PastTimeFormatUtil.java @@ -0,0 +1,173 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.date.DateUtil; + +import java.util.Date; + +/** + * 过去时间格式化工具类 + * + * @author xuyuxiang + * @date 2020/8/6 14:29 + **/ +public class PastTimeFormatUtil { + + private static final long ONE_MINUTE_SECONDS = 60; + + private static final int BEFORE_DAWN_HOUR = 6; + + private static final int MORNING_END_HOUR = 12; + + private static final int NOON_END_HOUR = 13; + + private static final int AFTERNOON_END_HOUR = 18; + + private static final int NIGHT_END_HOUR = 24; + + /** + * 将日期格式化为仿微信的日期 + * + * @param date 要格式化的日期 + * @return 格式化结果 + * @author xuyuxiang + * @date 2020/8/6 11:41 + **/ + public static String formatPastTime(Date date) { + if (DateUtil.between(date, DateUtil.date(), DateUnit.SECOND, false) < 0) { + //今天之后的时间显示年月日时分 + return DateUtil.format(date, DatePattern.NORM_DATETIME_MINUTE_PATTERN); + } else { + //如果是今年 + if (DateUtil.thisYear() == DateUtil.year(date)) { + //如果是今天 + if (DateUtil.isSameDay(date, DateUtil.date())) { + //相差分钟数 + long betweenMinute = DateUtil.between(date, DateUtil.date(), DateUnit.MINUTE); + //如果在1小时之内 + if (betweenMinute < ONE_MINUTE_SECONDS) { + //一分钟之内,显示刚刚 + if (betweenMinute < 1) { + return "刚刚"; + } else { + //一分钟之外,显示xx分钟前 + return betweenMinute + "分钟前"; + } + } else { + //一小时之外,显示时分 + return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm"); + } + } else if (DateUtil.isSameDay(date, DateUtil.yesterday())) { + //如果是昨天,显示昨天时分 + return "昨天 " + DateUtil.format(date, "HH:mm"); + } else if (isThisWeek(date)) { + //如果是本周 + String weekday; + //获取是本周的第几天 + int dayOfWeek = DateUtil.dayOfWeek(date) - 1; + switch (dayOfWeek) { + case 1: + weekday = "周一"; + break; + case 2: + weekday = "周二"; + break; + case 3: + weekday = "周三"; + break; + case 4: + weekday = "周四"; + break; + case 5: + weekday = "周五"; + break; + case 6: + weekday = "周六"; + break; + default: + weekday = "周日"; + break; + } + //显示本周时分 + return weekday + " " + DateUtil.format(date, "HH:mm"); + } else { + //否则显示月日时分 + return DateUtil.format(date, "MM-dd HH:mm"); + } + } else { + //本年之外显示年月日时分 + return DateUtil.format(date, DatePattern.NORM_DATETIME_MINUTE_PATTERN); + } + } + } + + /** + * 判断日期是否是本周 + * + * @param date 要判断的日期 + * @return boolean + * @author xuyuxiang + * @date 2020/8/6 12:10 + **/ + private static boolean isThisWeek(Date date) { + //获取本周开始时间 + DateTime beginOfWeek = DateUtil.beginOfWeek(DateUtil.date()); + //获取与本周开始时间相差的天数 + long betweenBegin = DateUtil.between(date, beginOfWeek, DateUnit.DAY, false) + 1; + //如果是同一天,或相差天数小于0,则是本周 + return DateUtil.isSameDay(date, beginOfWeek) || betweenBegin < 0; + } + + /** + * 根据今天日期获取早中晚 + * + * @author xuyuxiang + * @date 2020/8/6 14:42 + **/ + private static String getTodayHour(Date date) { + String result = ""; + int hour = DateUtil.hour(date, true); + if (hour >= 0 && hour <= BEFORE_DAWN_HOUR) { + result = "凌晨"; + } + if (hour > BEFORE_DAWN_HOUR && hour < MORNING_END_HOUR) { + result = "上午"; + } + if (hour == MORNING_END_HOUR) { + result = "中午"; + } + if (hour >= NOON_END_HOUR && hour <= AFTERNOON_END_HOUR) { + result = "下午"; + } + if (hour > AFTERNOON_END_HOUR && hour <= NIGHT_END_HOUR) { + result = "晚上"; + } + return result; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PoiUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PoiUtil.java new file mode 100644 index 0000000..cb818bd --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/PoiUtil.java @@ -0,0 +1,160 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.afterturn.easypoi.excel.ExcelExportUtil; +import cn.afterturn.easypoi.excel.ExcelImportUtil; +import cn.afterturn.easypoi.excel.entity.ExportParams; +import cn.afterturn.easypoi.excel.entity.ImportParams; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.log.Log; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.List; + +/** + * 简单导入导出工具类 + * + * @author xuyuxiang + * @date 2020/6/30 17:25 + */ +public class PoiUtil { + + private static final Log log = Log.get(); + + /** + * 使用流的方式导出excel + * + * @param excelName 要导出的文件名称,如Users.xls + * @param pojoClass Excel实体类 + * @param data 要导出的数据集合 + * @author xuyuxiang + * @date 2020/7/1 10:00 + */ + public static void exportExcelWithStream(String excelName, Class<?> pojoClass, Collection<?> data) { + try { + HttpServletResponse response = HttpServletUtil.getResponse(); + String fileName = URLEncoder.encode(excelName, CharsetUtil.UTF_8); + response.reset(); + response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + response.setContentType("application/octet-stream;charset=UTF-8"); + ServletOutputStream outputStream = response.getOutputStream(); + Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), pojoClass, data); + workbook.write(outputStream); + outputStream.close(); + } catch (IOException e) { + log.error(">>> 导出数据异常:{}", e.getMessage()); + } + } + + /** + * 使用文件的方式导出excel + * + * @param filePath 文件路径,如 d:/demo/demo.xls + * @param pojoClass Excel实体类 + * @param data 要导出的数据集合 + * @author xuyuxiang + * @date 2020/7/1 9:58 + */ + public static void exportExcelWithFile(String filePath, Class pojoClass, Collection data) { + + try { + //先创建父文件夹 + FileUtil.mkParentDirs(filePath); + File file = FileUtil.file(filePath); + Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(), pojoClass, data); + FileOutputStream fos = new FileOutputStream(file); + workbook.write(fos); + fos.close(); + } catch (IOException e) { + log.error(">>> 导出数据异常:{}", e.getMessage()); + } + + } + + /** + * 根据文件路径来导入Excel + * + * @param filePath 文件路径 + * @param titleRows 表标题的行数 + * @param headerRows 表头行数 + * @param pojoClass Excel实体类 + * @author xuyuxiang + * @date 2020/7/1 9:58 + */ + public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) { + //判断文件是否存在 + if (ObjectUtil.isEmpty(filePath)) { + return null; + } + ImportParams params = new ImportParams(); + params.setTitleRows(titleRows); + params.setHeadRows(headerRows); + List<T> list = null; + try { + list = ExcelImportUtil.importExcel(new File(filePath), pojoClass, params); + } catch (Exception e) { + log.error(">>> 导入数据异常:{}", e.getMessage()); + } + return list; + } + + /** + * 根据接收的Excel文件来导入Excel,并封装成实体类 + * + * @param file 上传的文件 + * @param titleRows 表标题的行数 + * @param headerRows 表头行数 + * @param pojoClass Excel实体类 + * @author xuyuxiang + * @date 2020/7/1 9:57 + */ + public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) { + if (ObjectUtil.isNull(file)) { + return null; + } + ImportParams params = new ImportParams(); + params.setTitleRows(titleRows); + params.setHeadRows(headerRows); + List<T> list = null; + try { + list = ExcelImportUtil.importExcel(file.getInputStream(), pojoClass, params); + } catch (Exception e) { + log.error(">>> 导入数据异常:{}", e.getMessage()); + } + return list; + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/ResponseUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/ResponseUtil.java new file mode 100644 index 0000000..f2e7175 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/ResponseUtil.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.http.ContentType; +import com.alibaba.fastjson.JSON; +import vip.xiaonuo.core.pojo.response.ErrorResponseData; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 响应工具类 + * + * @author xuyuxiang + * @date 2020/3/20 11:17 + */ +public class ResponseUtil { + + /** + * 响应异常,直接向前端写response,用于异常处理器捕获不到时手动抛出 + * + * @author xuyuxiang + * @date 2020/3/20 11:18 + */ + public static void responseExceptionError(HttpServletResponse response, + Integer code, + String message, + String exceptionClazz) throws IOException { + response.setCharacterEncoding(CharsetUtil.UTF_8); + response.setContentType(ContentType.JSON.toString()); + ErrorResponseData errorResponseData = new ErrorResponseData(code, message); + errorResponseData.setExceptionClazz(exceptionClazz); + String errorResponseJsonData = JSON.toJSONString(errorResponseData); + response.getWriter().write(errorResponseJsonData); + } + + /** + * 响应异常,向前端返回ErrorResponseData的json数据,用于全局异常处理器 + * + * @author xuyuxiang + * @date 2020/3/20 11:31 + */ + public static ErrorResponseData responseDataError(Integer code, String message, String exceptionClazz) { + ErrorResponseData errorResponseData = new ErrorResponseData(code, message); + errorResponseData.setExceptionClazz(exceptionClazz); + return errorResponseData; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/UaUtil.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/UaUtil.java new file mode 100644 index 0000000..37ffb96 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/util/UaUtil.java @@ -0,0 +1,96 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.util; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.http.useragent.Browser; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; + +import javax.servlet.http.HttpServletRequest; + +/** + * 用户代理工具类 + * + * @author xuyuxiang + * @date 2020/3/19 14:52 + */ +public class UaUtil { + + /** + * 获取客户端浏览器 + * + * @author xuyuxiang + * @date 2020/3/19 14:53 + */ + public static String getBrowser(HttpServletRequest request) { + UserAgent userAgent = getUserAgent(request); + if (ObjectUtil.isEmpty(userAgent)) { + return SymbolConstant.DASH; + } else { + String browser = userAgent.getBrowser().toString(); + return CommonConstant.UNKNOWN.equals(browser) ? SymbolConstant.DASH : browser; + } + } + + /** + * 获取客户端操作系统 + * + * @author xuyuxiang + * @date 2020/3/19 14:53 + */ + public static String getOs(HttpServletRequest request) { + UserAgent userAgent = getUserAgent(request); + if (ObjectUtil.isEmpty(userAgent)) { + return SymbolConstant.DASH; + } else { + String os = userAgent.getOs().toString(); + return CommonConstant.UNKNOWN.equals(os) ? SymbolConstant.DASH : os; + } + } + + /** + * 获取请求代理头 + * + * @author xuyuxiang + * @date 2020/3/19 14:54 + */ + private static UserAgent getUserAgent(HttpServletRequest request) { + String userAgentStr = ServletUtil.getHeaderIgnoreCase(request, CommonConstant.USER_AGENT); + UserAgent userAgent = UserAgentUtil.parse(userAgentStr); + //判空 + if (ObjectUtil.isNotEmpty(userAgentStr)) { + //如果根本没获取到浏览器 + if (CommonConstant.UNKNOWN.equals(userAgent.getBrowser().getName())) { + //则将ua设置为浏览器 + userAgent.setBrowser(new Browser(userAgentStr, null, "")); + } + } + return userAgent; + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/date/DateValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/date/DateValue.java new file mode 100644 index 0000000..262df1b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/date/DateValue.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.date; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验日期格式 yyyy-MM-dd + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +@Documented +@Constraint(validatedBy = DateValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateValue { + + String message() default "日期格式不正确,正确格式应为yyyy-MM-dd"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + DateValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/date/DateValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/date/DateValueValidator.java new file mode 100644 index 0000000..9c9ec2b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/date/DateValueValidator.java @@ -0,0 +1,59 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.date; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验日期格式 yyyy-MM-dd + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +public class DateValueValidator implements ConstraintValidator<DateValue, String> { + + @Override + public boolean isValid(String dateValue, ConstraintValidatorContext context) { + //为空则放过,因为在此校验之前会加入@NotNull或@NotBlank校验 + if (ObjectUtil.isEmpty(dateValue)) { + return true; + } + //长度不对直接返回 + if (dateValue.length() != DatePattern.NORM_DATE_PATTERN.length()) { + return false; + } + try { + DateUtil.parseDate(dateValue); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateordatetime/DateOrDateTimeValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateordatetime/DateOrDateTimeValue.java new file mode 100644 index 0000000..58a78a8 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateordatetime/DateOrDateTimeValue.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.dateordatetime; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验日期或时间格式 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +@Documented +@Constraint(validatedBy = DateOrDateTimeValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateOrDateTimeValue { + + String message() default "日期或时间格式不正确,正确格式应为yyyy-MM-dd或yyyy-MM-dd HH:mm:ss"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + DateOrDateTimeValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateordatetime/DateOrDateTimeValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateordatetime/DateOrDateTimeValueValidator.java new file mode 100644 index 0000000..d674f55 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateordatetime/DateOrDateTimeValueValidator.java @@ -0,0 +1,65 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.dateordatetime; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验日期或时间格式 yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +public class DateOrDateTimeValueValidator implements ConstraintValidator<DateOrDateTimeValue, String> { + + @Override + public boolean isValid(String dateValue, ConstraintValidatorContext context) { + //为空则放过,因为在此校验之前会加入@NotNull或@NotBlank校验 + if (ObjectUtil.isEmpty(dateValue)) { + return true; + } + //长度不对直接返回 + if (dateValue.length() != DatePattern.NORM_DATE_PATTERN.length() && + dateValue.length() != DatePattern.NORM_DATETIME_PATTERN.length()) { + return false; + } + try { + DateUtil.parseDate(dateValue); + return true; + } catch (Exception dateParseException) { + try { + DateUtil.parseDateTime(dateValue); + return true; + } catch (Exception dateTimeParseException) { + return false; + } + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateormonth/DateOrMonthValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateormonth/DateOrMonthValue.java new file mode 100644 index 0000000..a9521b2 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateormonth/DateOrMonthValue.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.dateormonth; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验日期格式 yyyy-MM-dd 或 yyyy-MM + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +@Documented +@Constraint(validatedBy = DateOrMonthValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateOrMonthValue { + + String message() default "日期格式不正确,正确格式应为yyyy-MM-dd或yyyy-MM"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + DateOrMonthValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateormonth/DateOrMonthValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateormonth/DateOrMonthValueValidator.java new file mode 100644 index 0000000..4d44bee --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateormonth/DateOrMonthValueValidator.java @@ -0,0 +1,67 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.dateormonth; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验日期格式 yyyy-MM-dd 或 yyyy-MM + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +public class DateOrMonthValueValidator implements ConstraintValidator<DateOrMonthValue, String> { + + public static final String NORM_MONTH_PATTERN = "yyyy-MM"; + + @Override + public boolean isValid(String dateValue, ConstraintValidatorContext context) { + //为空则放过,因为在此校验之前会加入@NotNull或@NotBlank校验 + if (ObjectUtil.isEmpty(dateValue)) { + return true; + } + //长度不对直接返回 + if (dateValue.length() != DatePattern.NORM_DATE_PATTERN.length() && + dateValue.length() != NORM_MONTH_PATTERN.length()) { + return false; + } + try { + DateUtil.parseDate(dateValue); + return true; + } catch (Exception dateParseException) { + try { + DateUtil.parse(dateValue, NORM_MONTH_PATTERN); + return true; + } catch (Exception monthParseException) { + return false; + } + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateortime/DateOrTimeValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateortime/DateOrTimeValue.java new file mode 100644 index 0000000..e55e567 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateortime/DateOrTimeValue.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.dateortime; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验日期或时间格式 yyyy-MM-dd 或 HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +@Documented +@Constraint(validatedBy = DateOrTimeValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateOrTimeValue { + + String message() default "日期或时间格式不正确,正确格式应为yyyy-MM-dd 或 HH:mm:ss"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + DateOrTimeValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateortime/DateOrTimeValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateortime/DateOrTimeValueValidator.java new file mode 100644 index 0000000..54e7419 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dateortime/DateOrTimeValueValidator.java @@ -0,0 +1,65 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.dateortime; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验日期或时间格式 yyyy-MM-dd 或 HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +public class DateOrTimeValueValidator implements ConstraintValidator<DateOrTimeValue, String> { + + @Override + public boolean isValid(String dateValue, ConstraintValidatorContext context) { + //为空则放过,因为在此校验之前会加入@NotNull或@NotBlank校验 + if (ObjectUtil.isEmpty(dateValue)) { + return true; + } + //长度不对直接返回 + if (dateValue.length() != DatePattern.NORM_DATE_PATTERN.length() && + dateValue.length() != DatePattern.NORM_TIME_PATTERN.length()) { + return false; + } + try { + DateUtil.parseDate(dateValue); + return true; + } catch (Exception dateParseException) { + try { + DateUtil.parseTime(dateValue); + return true; + } catch (Exception timeParseException) { + return false; + } + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/datetime/DateTimeValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/datetime/DateTimeValue.java new file mode 100644 index 0000000..c264d5f --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/datetime/DateTimeValue.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.datetime; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验日期时间格式 yyyy-MM-dd HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +@Documented +@Constraint(validatedBy = DateTimeValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DateTimeValue { + + String message() default "日期时间格式不正确,正确格式应为yyyy-MM-dd HH:mm:ss"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + DateTimeValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/datetime/DateTimeValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/datetime/DateTimeValueValidator.java new file mode 100644 index 0000000..ffa307f --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/datetime/DateTimeValueValidator.java @@ -0,0 +1,59 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.datetime; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验日期时间格式 yyyy-MM-dd HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +public class DateTimeValueValidator implements ConstraintValidator<DateTimeValue, String> { + + @Override + public boolean isValid(String dateTimeValue, ConstraintValidatorContext context) { + //为空则放过,因为在此校验之前会加入@NotNull或@NotBlank校验 + if (ObjectUtil.isEmpty(dateTimeValue)) { + return true; + } + //长度不对直接返回 + if (dateTimeValue.length() != DatePattern.NORM_DATETIME_PATTERN.length()) { + return false; + } + try { + DateUtil.parseDateTime(dateTimeValue); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dict/DictValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dict/DictValue.java new file mode 100644 index 0000000..d427373 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dict/DictValue.java @@ -0,0 +1,66 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.dict; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 检验值是否为字典值,验证sys_dict_data表中有没有相关的字典项 + * <p> + * 本注解用的时候,一定要加dictType参数,用来表明验证的哪个字典类型中的值 + * <p> + * dictType值来自数据库中sys_dict_type表的code值 + * + * @author yubaoshan + * @date 2020/4/14 23:49 + */ +@Documented +@Constraint(validatedBy = DictValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface DictValue { + + String message() default "不正确的字典值,请检查数据库中是否录入该字典项"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + /** + * 字典的类型 + */ + String[] dictType(); + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + DictValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dict/DictValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dict/DictValueValidator.java new file mode 100644 index 0000000..34f0458 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/dict/DictValueValidator.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.dict; + +import vip.xiaonuo.core.context.system.SystemContextHolder; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.util.List; + +/** + * 字典值校验 + * + * @author yubaoshan + * @date 2020/4/14 23:49 + */ +public class DictValueValidator implements ConstraintValidator<DictValue, String> { + + private String[] dictType; + + @Override + public void initialize(DictValue constraintAnnotation) { + this.dictType = constraintAnnotation.dictType(); + } + + @Override + public boolean isValid(String dictValue, ConstraintValidatorContext context) { + List<String> dictCodes = SystemContextHolder.me().getDictCodesByDictTypeCode(dictType); + if (dictCodes != null && dictCodes.size() > 0) { + return dictCodes.contains(dictValue); + } else { + return false; + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/flag/FlagValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/flag/FlagValue.java new file mode 100644 index 0000000..5ebd9b7 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/flag/FlagValue.java @@ -0,0 +1,62 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.flag; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验标识,只有Y和N两种状态的标识 + * + * @author yubaoshan + * @date 2020/4/14 23:49 + */ +@Documented +@Constraint(validatedBy = FlagValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface FlagValue { + + String message() default "不正确的状态标识,请传递Y或者N"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + /** + * 是否必填 + */ + boolean required() default true; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + FlagValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/flag/FlagValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/flag/FlagValueValidator.java new file mode 100644 index 0000000..3c67af3 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/flag/FlagValueValidator.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.flag; + +import cn.hutool.core.util.StrUtil; +import vip.xiaonuo.core.enums.YesOrNotEnum; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验标识,只有Y和N两种状态的标识 + * + * @author yubaoshan + * @date 2020/4/14 23:49 + */ +public class FlagValueValidator implements ConstraintValidator<FlagValue, String> { + + private Boolean required; + + @Override + public void initialize(FlagValue constraintAnnotation) { + this.required = constraintAnnotation.required(); + } + + @Override + public boolean isValid(String flagValue, ConstraintValidatorContext context) { + + // 如果是必填的 + if (required) { + return YesOrNotEnum.Y.getCode().equals(flagValue) || YesOrNotEnum.N.getCode().equals(flagValue); + } else { + + //如果不是必填,可以为空 + if (StrUtil.isEmpty(flagValue)) { + return true; + } else { + return YesOrNotEnum.Y.getCode().equals(flagValue) || YesOrNotEnum.N.getCode().equals(flagValue); + } + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/month/MonthValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/month/MonthValue.java new file mode 100644 index 0000000..50a61b5 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/month/MonthValue.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.month; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验日期格式 yyyy-MM + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +@Documented +@Constraint(validatedBy = MonthValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface MonthValue { + + String message() default "日期格式不正确,正确格式应为yyyy-MM"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + MonthValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/month/MonthValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/month/MonthValueValidator.java new file mode 100644 index 0000000..6fb18f5 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/month/MonthValueValidator.java @@ -0,0 +1,60 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.month; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验日期格式 yyyy-MM + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +public class MonthValueValidator implements ConstraintValidator<MonthValue, String> { + + public static final String NORM_MONTH_PATTERN = "yyyy-MM"; + + @Override + public boolean isValid(String dateValue, ConstraintValidatorContext context) { + //为空则放过,因为在此校验之前会加入@NotNull或@NotBlank校验 + if (ObjectUtil.isEmpty(dateValue)) { + return true; + } + //长度不对直接返回 + if (dateValue.length() != NORM_MONTH_PATTERN.length()) { + return false; + } + try { + DateUtil.parse(dateValue, NORM_MONTH_PATTERN); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/mothordatetime/MonthOrDateTimeValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/mothordatetime/MonthOrDateTimeValue.java new file mode 100644 index 0000000..ba470ee --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/mothordatetime/MonthOrDateTimeValue.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.mothordatetime; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验日期格式 yyyy-MM 或 yyyy-MM-dd HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +@Documented +@Constraint(validatedBy = MonthOrDateTimeValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface MonthOrDateTimeValue { + + String message() default "日期格式不正确,正确格式应为yyyy-MM或yyyy-MM-dd HH:mm:ss"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + MonthOrDateTimeValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/mothordatetime/MonthOrDateTimeValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/mothordatetime/MonthOrDateTimeValueValidator.java new file mode 100644 index 0000000..5919f04 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/mothordatetime/MonthOrDateTimeValueValidator.java @@ -0,0 +1,67 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.mothordatetime; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验日期格式 yyyy-MM 或 yyyy-MM-dd HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +public class MonthOrDateTimeValueValidator implements ConstraintValidator<MonthOrDateTimeValue, String> { + + public static final String NORM_MONTH_PATTERN = "yyyy-MM"; + + @Override + public boolean isValid(String dateValue, ConstraintValidatorContext context) { + //为空则放过,因为在此校验之前会加入@NotNull或@NotBlank校验 + if (ObjectUtil.isEmpty(dateValue)) { + return true; + } + //长度不对直接返回 + if (dateValue.length() != DatePattern.NORM_DATETIME_PATTERN.length() && + dateValue.length() != NORM_MONTH_PATTERN.length()) { + return false; + } + try { + DateUtil.parseDateTime(dateValue); + return true; + } catch (Exception dateTimeParseException) { + try { + DateUtil.parse(dateValue, NORM_MONTH_PATTERN); + return true; + } catch (Exception monthParseException) { + return false; + } + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/time/TimeValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/time/TimeValue.java new file mode 100644 index 0000000..93139c6 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/time/TimeValue.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.time; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 校验日期格式 HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +@Documented +@Constraint(validatedBy = TimeValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TimeValue { + + String message() default "日期格式不正确,正确格式应为HH:mm:ss"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + TimeValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/time/TimeValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/time/TimeValueValidator.java new file mode 100644 index 0000000..9361205 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/time/TimeValueValidator.java @@ -0,0 +1,59 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.time; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 校验日期格式 HH:mm:ss + * + * @author xuyuxiang + * @date 2020/5/26 14:48 + */ +public class TimeValueValidator implements ConstraintValidator<TimeValue, String> { + + @Override + public boolean isValid(String dateValue, ConstraintValidatorContext context) { + //为空则放过,因为在此校验之前会加入@NotNull或@NotBlank校验 + if (ObjectUtil.isEmpty(dateValue)) { + return true; + } + //长度不对直接返回 + if (dateValue.length() != DatePattern.NORM_TIME_PATTERN.length()) { + return false; + } + try { + DateUtil.parseTime(dateValue); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/unique/TableUniqueValue.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/unique/TableUniqueValue.java new file mode 100644 index 0000000..c21bada --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/unique/TableUniqueValue.java @@ -0,0 +1,88 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.unique; + +import vip.xiaonuo.core.consts.CommonConstant; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.*; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 验证表的的某个字段值是否在是唯一值 + * + * @author yubaoshan + * @date 2020/4/14 23:49 + */ +@Documented +@Constraint(validatedBy = TableUniqueValueValidator.class) +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface TableUniqueValue { + + String message() default "库中存在重复编码,请更换该编码值"; + + Class[] groups() default {}; + + Class<? extends Payload>[] payload() default {}; + + /** + * 表名称,例如 sys_user + */ + String tableName(); + + /** + * 列名称,例如 user_code + */ + String columnName(); + + /** + * 是否开启状态校验,默认是关闭的 + * <p> + * 关于为何开启状态校验: + * <p> + * 若项目中某个表包含控制逻辑删除的字段,我们在进行唯一值校验的时候要排除这种状态的记录,所以需要用到这个功能 + */ + boolean excludeLogicDeleteItems() default false; + + /** + * 标识状态的字段名 + */ + String logicDeleteFieldName() default CommonConstant.STATUS; + + /** + * 逻辑删除的值(默认2是删除),用string是为了更通用 + */ + String logicDeleteValue() default CommonConstant.DEFAULT_LOGIC_DELETE_VALUE; + + @Target({ElementType.FIELD, ElementType.PARAMETER}) + @Retention(RUNTIME) + @Documented + @interface List { + TableUniqueValue[] value(); + } +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/unique/TableUniqueValueValidator.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/unique/TableUniqueValueValidator.java new file mode 100644 index 0000000..d8a2f8b --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/validation/unique/TableUniqueValueValidator.java @@ -0,0 +1,122 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.validation.unique; + +import cn.hutool.core.util.ObjectUtil; +import vip.xiaonuo.core.context.group.RequestGroupContext; +import vip.xiaonuo.core.context.group.RequestParamIdContext; +import vip.xiaonuo.core.context.system.SystemContextHolder; +import vip.xiaonuo.core.pojo.base.param.BaseParam; +import vip.xiaonuo.core.pojo.base.validate.UniqueValidateParam; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +/** + * 验证表的的某个字段值是否在是唯一值 + * + * @author yubaoshan + * @date 2020/4/14 23:49 + */ +public class TableUniqueValueValidator implements ConstraintValidator<TableUniqueValue, String> { + + private String tableName; + + private String columnName; + + private boolean excludeLogicDeleteItems; + + private String logicDeleteFieldName; + + private String logicDeleteValue; + + @Override + public void initialize(TableUniqueValue constraintAnnotation) { + this.tableName = constraintAnnotation.tableName(); + this.columnName = constraintAnnotation.columnName(); + this.excludeLogicDeleteItems = constraintAnnotation.excludeLogicDeleteItems(); + this.logicDeleteFieldName = constraintAnnotation.logicDeleteFieldName(); + this.logicDeleteValue = constraintAnnotation.logicDeleteValue(); + } + + @Override + public boolean isValid(String fieldValue, ConstraintValidatorContext context) { + + if (ObjectUtil.isNull(fieldValue)) { + return true; + } + + Class<?> validateGroupClass = RequestGroupContext.get(); + + // 如果属于add group,则校验库中所有行 + if (BaseParam.add.class.equals(validateGroupClass)) { + return SystemContextHolder.me().tableUniValueFlag(createAddParam(fieldValue)); + } + + // 如果属于edit group,校验时需要排除当前修改的这条记录 + if (BaseParam.edit.class.equals(validateGroupClass)) { + return SystemContextHolder.me().tableUniValueFlag(createEditParam(fieldValue)); + } + + // 默认校验所有的行 + return SystemContextHolder.me().tableUniValueFlag(createAddParam(fieldValue)); + } + + /** + * 创建校验新增的参数 + * + * @author xuyuxiang + * @date 2020/8/17 21:55 + */ + private UniqueValidateParam createAddParam(String fieldValue) { + return UniqueValidateParam.builder() + .tableName(tableName) + .columnName(columnName) + .value(fieldValue) + .excludeCurrentRecord(Boolean.FALSE) + .excludeLogicDeleteItems(excludeLogicDeleteItems) + .logicDeleteFieldName(logicDeleteFieldName) + .logicDeleteValue(logicDeleteValue).build(); + } + + /** + * 创建修改的参数校验 + * + * @author xuyuxiang + * @date 2020/8/17 21:56 + */ + private UniqueValidateParam createEditParam(String fieldValue) { + return UniqueValidateParam.builder() + .tableName(tableName) + .columnName(columnName) + .value(fieldValue) + .excludeCurrentRecord(Boolean.TRUE) + .id(RequestParamIdContext.get()) + .excludeLogicDeleteItems(excludeLogicDeleteItems) + .logicDeleteFieldName(logicDeleteFieldName) + .logicDeleteValue(logicDeleteValue).build(); + } + +} diff --git a/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/web/SnowyRequestResponseBodyMethodProcessor.java b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/web/SnowyRequestResponseBodyMethodProcessor.java new file mode 100644 index 0000000..d3f3c41 --- /dev/null +++ b/snowy-base/snowy-core/src/main/java/vip/xiaonuo/core/web/SnowyRequestResponseBodyMethodProcessor.java @@ -0,0 +1,81 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core.web; + +import org.springframework.core.Conventions; +import org.springframework.core.MethodParameter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.lang.Nullable; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; +import vip.xiaonuo.core.context.param.RequestParamContext; + +import java.util.List; + +/** + * 拓展原有RequestResponseBodyMethodProcessor,只为缓存临时参数 + * + * @author xuyuxiang + * @date 2020/8/21 20:51 + */ +public class SnowyRequestResponseBodyMethodProcessor extends RequestResponseBodyMethodProcessor { + + public SnowyRequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) { + super(converters); + } + + @Override + public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { + + parameter = parameter.nestedIfOptional(); + Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); + + // 临时缓存一下@RequestBody注解上的参数 + RequestParamContext.setObject(arg); + + String name = Conventions.getVariableNameForParameter(parameter); + + if (binderFactory != null) { + WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); + if (arg != null) { + validateIfApplicable(binder, parameter); + if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { + throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); + } + } + if (mavContainer != null) { + mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); + } + } + + return adaptArgumentIfNecessary(arg, parameter); + } +} diff --git a/snowy-base/snowy-gen/README.md b/snowy-base/snowy-gen/README.md new file mode 100644 index 0000000..7854e6a --- /dev/null +++ b/snowy-base/snowy-gen/README.md @@ -0,0 +1 @@ +** 代码生成 ** diff --git a/snowy-base/snowy-gen/pom.xml b/snowy-base/snowy-gen/pom.xml new file mode 100644 index 0000000..e3017e3 --- /dev/null +++ b/snowy-base/snowy-gen/pom.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy-base</artifactId> + <version>1.6.0</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>snowy-gen</artifactId> + + <packaging>jar</packaging> + + <dependencies> + + <!-- core模块 --> + <dependency> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy-core</artifactId> + <version>1.6.0</version> + </dependency> + + <!-- 代码生成引擎 --> + <dependency> + <groupId>org.apache.velocity</groupId> + <artifactId>velocity</artifactId> + <version>1.7</version> + </dependency> + + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + </build> +</project> diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/consts/GenConstant.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/consts/GenConstant.java new file mode 100644 index 0000000..2eeee20 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/consts/GenConstant.java @@ -0,0 +1,158 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.consts; + + +import java.io.File; + +/** + * 代码生成配置 + * + * @author yubaoshan + * @date 2020-12-19 02:30:56 + */ +public class GenConstant { + + /** + * 路径分离(不通的机器,取不同的路径) + */ + public static String FILE_SEP = File.separator; + + /** + * 存放vm模板位置 + */ + public static String templatePath = "template" + FILE_SEP; + + /** + * 主键标识 + */ + public static String DB_TABLE_COM_KRY = "PRI"; + + /** + * 模块名(一般为modular,无特殊要求一般不改) + */ + public static String MODULAR_NAME = "modular"; + + /** + * 本项目生成时是否覆盖 + */ + public static final boolean FLAG = false; + + /** + * 大模块名称(生成到代码中哪个模块下) + */ + public static String BASE_MODULAR_NAME = "snowy-main"; + + /** + * java文件夹 + */ + public static String BASE_JAVA_PATH = FILE_SEP + "src" + FILE_SEP + "main" + FILE_SEP + "java" + FILE_SEP; + + /** + * vue文件夹 + */ + public static String BASE_VUE_PATH = FILE_SEP + "_web" + FILE_SEP + "src" + FILE_SEP; + + /** + * sql文件夹 + */ + public static String BASE_SQL_PATH = FILE_SEP + "_sql" + FILE_SEP; + + /** + * 代码生成路径 + */ + public static String controllerPath; + public static String entityPath; + public static String enumsPath; + public static String mapperPath; + public static String mappingPath; + public static String paramPath; + public static String servicePath; + public static String serviceImplPath; + public static String manageJsPath; + public static String vueIndexPath; + public static String vueAddFromPath; + public static String vueEditFromPath; + public static String mysqlSqlPath; + public static String oracleSqlPath; + + /** + * 各个代码存放路径文件夹 + */ + public static String[] xnCodeGenFilePath (String busName, String packageName) { + String packageNameString = packageName.replace(".",FILE_SEP) + FILE_SEP; + controllerPath = BASE_JAVA_PATH + packageNameString + MODULAR_NAME + FILE_SEP + busName + FILE_SEP + "controller" + FILE_SEP; + entityPath = BASE_JAVA_PATH + packageNameString + MODULAR_NAME + FILE_SEP + busName + FILE_SEP + "entity" + FILE_SEP; + enumsPath = BASE_JAVA_PATH+ packageNameString + MODULAR_NAME + FILE_SEP + busName + FILE_SEP + "enums" + FILE_SEP; + mapperPath = BASE_JAVA_PATH + packageNameString + MODULAR_NAME + FILE_SEP + busName + FILE_SEP + "mapper" + FILE_SEP; + mappingPath = mapperPath + FILE_SEP + "mapping" + FILE_SEP; + paramPath = BASE_JAVA_PATH+ FILE_SEP + packageNameString + MODULAR_NAME + FILE_SEP + busName + FILE_SEP + "param" + FILE_SEP; + servicePath = BASE_JAVA_PATH+ FILE_SEP + packageNameString + MODULAR_NAME + FILE_SEP + busName + FILE_SEP + "service" + FILE_SEP; + serviceImplPath = servicePath + FILE_SEP + "impl" + FILE_SEP; + manageJsPath = BASE_VUE_PATH + FILE_SEP + "api" + FILE_SEP + MODULAR_NAME + FILE_SEP + "main" + FILE_SEP + busName + FILE_SEP; + vueIndexPath = BASE_VUE_PATH + FILE_SEP + "views" + FILE_SEP + "main" + FILE_SEP + busName + FILE_SEP; + vueAddFromPath = BASE_VUE_PATH + FILE_SEP + "views" + FILE_SEP + "main" + FILE_SEP + busName + FILE_SEP; + vueEditFromPath = BASE_VUE_PATH + FILE_SEP + "views" + FILE_SEP + "main" + FILE_SEP + busName + FILE_SEP; + mysqlSqlPath = BASE_SQL_PATH; + oracleSqlPath = BASE_SQL_PATH; + return new String[] { + controllerPath, entityPath, enumsPath, mapperPath, mappingPath, paramPath, servicePath, serviceImplPath, manageJsPath, vueIndexPath, vueAddFromPath, vueEditFromPath, mysqlSqlPath, oracleSqlPath + }; + } + + /** + * 模板文件 + */ + public static String[] xnCodeGenTempFile = { + "Controller.java.vm", + "entity.java.vm", + "ExceptionEnum.java.vm", + "Mapper.java.vm", + "Mapper.xml.vm", + "Param.java.vm", + "Service.java.vm", + "ServiceImpl.java.vm", + "Manage.js.vm", + "index.vue.vm", + "addForm.vue.vm", + "editForm.vue.vm", + "XnMysql.sql.vm", + "XnOracle.sql.vm", + }; + + /** + * 本地项目根目录 + */ + public static String getLocalPath () { + return System.getProperty("user.dir") + FILE_SEP + BASE_MODULAR_NAME + FILE_SEP; + } + + /** + * vue前端 + */ + public static String getLocalFrontPath () { + return System.getProperty("user.dir") + FILE_SEP ; + } +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/consts/GenExpEnumConstant.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/consts/GenExpEnumConstant.java new file mode 100644 index 0000000..26ebd9d --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/consts/GenExpEnumConstant.java @@ -0,0 +1,59 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.consts; + +/** + * 代码生产 异常枚举编码构成常量 + * <p> + * 异常枚举编码由3部分组成,如下: + * <p> + * 模块编码(2位) + 分类编码(4位) + 具体项编码(至少1位) + * <p> + * 模块编码和分类编码在ExpEnumCodeConstant类中声明 + * + * @author xuyuxiang + * @date 2020/6/19 20:46 + */ +public interface GenExpEnumConstant { + + /** + * 模块分类编码(2位) + * <p> + * snowy-gen模块异常枚举编码 + */ + int SNOWY_GEN_MODULE_EXP_CODE = 60; + + /* 分类编码(4位) */ + /** + * 代码生成表相关异常枚举 + */ + int GEN_CODE_EXCEPTION_ENUM = 1100; + + /** + * 代码生成详细配置相关异常枚举 + */ + int GEN_CONFIG_EXCEPTION_ENUM = 1200; + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/context/XnVelocityContext.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/context/XnVelocityContext.java new file mode 100644 index 0000000..b33fe6e --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/context/XnVelocityContext.java @@ -0,0 +1,102 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.context; + +import org.apache.velocity.VelocityContext; +import vip.xiaonuo.core.enums.YesOrNotEnum; +import vip.xiaonuo.generate.core.param.XnCodeGenParam; +import vip.xiaonuo.generate.modular.entity.SysCodeGenerateConfig; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * 设置上下文缓存 + * + * @author yubaoshan + * @date 2020年12月17日02:04:56 + */ +public class XnVelocityContext { + + /** + * 创建上下文用到的参数 + * + * @author yubaoshan + * @date 2020年12月17日02:04:56 + */ + public VelocityContext createVelContext (XnCodeGenParam xnCodeGenParam) { + VelocityContext velocityContext = new VelocityContext(); + // 取得类名 + String DomainName = xnCodeGenParam.getClassName(); + String domainName = DomainName.substring(0,1).toLowerCase()+DomainName.substring(1); + // 类名称 + velocityContext.put("ClassName",DomainName); + // 类名(首字母小写) + velocityContext.put("className",domainName); + + // 功能名 + velocityContext.put("functionName",xnCodeGenParam.getFunctionName()); + + // 包名称 + velocityContext.put("packageName",xnCodeGenParam.getPackageName()); + // 模块名称 + velocityContext.put("modularName",xnCodeGenParam.getModularNane()); + // 业务名 + velocityContext.put("busName",xnCodeGenParam.getBusName()); + + // 作者姓名 + velocityContext.put("authorName", xnCodeGenParam.getAuthorName()); + // 代码生成时间 + velocityContext.put("createDateString", xnCodeGenParam.getCreateTimeString()); + + // 数据库表名 + velocityContext.put("tableName", xnCodeGenParam.getTableName()); + // 数据库字段 + velocityContext.put("tableField", xnCodeGenParam.getConfigList()); + + // 前端查询所有 + List<SysCodeGenerateConfig> codeGenerateConfigList = new ArrayList<>(); + xnCodeGenParam.getConfigList().forEach(item -> { + if (item.getQueryWhether().equals(YesOrNotEnum.Y.getCode())) { + codeGenerateConfigList.add(item); + } + }); + velocityContext.put("queryWhetherList", codeGenerateConfigList); + + velocityContext.put("appCode", xnCodeGenParam.getAppCode()); + + velocityContext.put("menuPids", xnCodeGenParam.getMenuPids() + "[" + xnCodeGenParam.getMenuPid() + "],"); + + // sql中id的创建 + List<Long> idList = new ArrayList<>(); + for (int a = 0; a <= 7; a++) { + idList.add(Math.abs(UUID.randomUUID().getLeastSignificantBits())); + } + velocityContext.put("sqlMenuId", idList); + + return velocityContext; + } +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/enums/QueryTypeEnum.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/enums/QueryTypeEnum.java new file mode 100644 index 0000000..5b5c3ed --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/enums/QueryTypeEnum.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.enums; + +import lombok.Getter; + +/** + * 查询类型的枚举 + * + * @author yubaoshan + * @date 2021/2/8 20:31 + */ +@Getter +public enum QueryTypeEnum { + + eq("eq", "等于"), + like("like", "模糊"), + gt("gt", "大于"), + lt("lt", "小于"), + ne("ne", "不等于"), + ge("ge", "大于等于"), + le("le", "小于等于"), + isNotNull("isNotNull", "不为空"); + + private final String code; + + private final String message; + + QueryTypeEnum(String code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/enums/TableFilteredFieldsEnum.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/enums/TableFilteredFieldsEnum.java new file mode 100644 index 0000000..e6a3db8 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/enums/TableFilteredFieldsEnum.java @@ -0,0 +1,63 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.enums; + +import lombok.Getter; + +/** + * 代码生成过程中被过滤的字段 + * + * @author yubaoshan + * @date 2020年12月17日00:11:40 + */ +@Getter +public enum TableFilteredFieldsEnum { + + CREATE_TIME("create_time"), + UPDATE_TIME("update_time"), + CREATE_USER("create_user"), + UPDATE_USER("update_user"); + + private final String propertyName; + + TableFilteredFieldsEnum(String propertyName) { + this.propertyName = propertyName; + } + + /** + * 是否本枚举包含该字段 + * + * @author yubaoshan + * @date 2020年12月17日00:11:40 + */ + public static boolean contains(String propertyName) { + for (TableFilteredFieldsEnum xiaonuoFilteredFieldsEnum : TableFilteredFieldsEnum.values()) { + if (xiaonuoFilteredFieldsEnum.propertyName.equals(propertyName)) { + return true; + } + } + return false; + } +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/param/TableField.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/param/TableField.java new file mode 100644 index 0000000..89c48c9 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/param/TableField.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.param; + +import lombok.Data; + +/** + * 数据库表字段实体 + * + * @author yubaoshan + * @date 2020年12月17日00:08:40 + */ +@Data +public class TableField { + + /** + * 字段名 + */ + public String columnName; + + /** + * 数据库中类型 + */ + public String dataType; + + /** + * 字段描述 + */ + public String columnComment; + + /** + * 主外键(用来做判断的) + */ + public String columnKey; + + /** + * 字段名,用来 get set方法使用的 + */ + public String columnKeyName; + + /** + * Java类型(String,Integer,Date等) + */ + private String javaType; + + /** + * 是否是主键 + */ + private Boolean primaryKeyFlag = false; +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/param/XnCodeGenParam.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/param/XnCodeGenParam.java new file mode 100644 index 0000000..6b4ee1d --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/param/XnCodeGenParam.java @@ -0,0 +1,111 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.param; + +import lombok.Data; +import vip.xiaonuo.generate.core.consts.GenConstant; +import vip.xiaonuo.generate.modular.entity.SysCodeGenerateConfig; + +import java.util.List; + +@Data +public class XnCodeGenParam { + + /** + * 作者姓名 + */ + private String authorName; + + /** + * 类名 + */ + private String className; + + /** + * 功能名 + */ + private String functionName; + + /** + * 是否移除表前缀 + */ + private String tablePrefix; + + /** + * 生成方式 + */ + private String generateType; + + /** + * 数据库表名 + */ + private String tableName; + + /** + * 数据库表名(经过组装的) + */ + private String tableNameAss; + + /** + * 代码包名 + */ + private String packageName; + + /** + * 生成时间(String类型的) + */ + private String createTimeString; + + /** + * 数据库表中字段集合 + */ + private List<SysCodeGenerateConfig> configList; + + /** + * 模块名 + */ + private String modularNane = GenConstant.MODULAR_NAME; + + /** + * 业务名 + */ + private String busName; + + /** + * 所属应用 + */ + private String appCode; + + /** + * 菜单上级 + */ + private String menuPid; + + /** + * 菜单上级父ids + */ + private String menuPids = ""; + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/JavaEffTool.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/JavaEffTool.java new file mode 100644 index 0000000..bbb23f8 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/JavaEffTool.java @@ -0,0 +1,54 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.tool; + +/** + * java与effect工具类 + * + * @author yubaoshan + * @date 2021-2-8 02:30 + */ +public class JavaEffTool { + + /** + * java转显示类型 + * + * @author yubaoshan + * @date 2021-2-8 02:30 + */ + public static String javaToEff (String javaType) { + if( javaType == null || javaType.trim().length() == 0 ) return javaType; + switch(javaType){ + case "String":return "input"; + case "Integer":return "input"; + case "Long":return "input"; + case "Date":return "datepicker"; + default: + System.out.println(">>> 转化失败:未发现的类型" + javaType); + break; + } + return javaType; + } +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/JavaSqlTool.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/JavaSqlTool.java new file mode 100644 index 0000000..e1eedc7 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/JavaSqlTool.java @@ -0,0 +1,87 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.tool; + +/** + * java与sql工具类 + * + * @author yubaoshan + * @date 2020-12-17 23:42 + */ +public class JavaSqlTool { + + /** + * 数据类型转化JAVA + * + * @author yubaoshan + * @date 2020-12-17 23:42 + */ + public static String sqlToJava (String sqlType) { + if( sqlType == null || sqlType.trim().length() == 0 ) return sqlType; + sqlType = sqlType.toLowerCase(); + if(sqlType.startsWith("int")) { + //如果以int开头,则直接返回int,兼容pgsql中int2 int8等 + return "Integer"; + } + switch(sqlType){ + case "nvarchar":return "String"; + case "nvarchar2":return "String"; + case "char":return "String"; + case "varchar":return "String"; + case "enum":return "String"; + case "set":return "String"; + case "text":return "String"; + case "nchar":return "String"; + case "blob":return "byte[]"; + case "integer":return "Long"; + case "int":return "Integer"; + case "tinyint":return "Integer"; + case "smallint":return "Integer"; + case "mediumint":return "Integer"; + case "bit":return "Boolean"; + case "bigint":return "Long"; + case "number":return "Long"; + case "float":return "Float"; + case "double":return "Double"; + case "decimal":return "BigDecimal"; + case "boolean":return "Boolean"; + case "id":return "Long"; + case "date":return "Date"; + case "datetime":return "Date"; + case "year":return "Date"; + case "time":return "Time"; + case "timestamp":return "Timestamp"; + case "numeric":return "BigDecimal"; + case "real":return "BigDecimal"; + case "money":return "Double"; + case "smallmoney":return "Double"; + case "image":return "byte[]"; + default: + System.out.println(">>> 转化失败:未发现的类型" + sqlType); + break; + } + return sqlType; + } +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/NamingConTool.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/NamingConTool.java new file mode 100644 index 0000000..c90182d --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/NamingConTool.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.tool; + +/** + * 命名转换 + * + * @author yubaoshan + * @date 2020-12-17 23:55 + */ +public class NamingConTool { + + /** + * 下划线命名转为驼峰命名 + * + * @author yubaoshan + * @date 2020-12-17 23:55 + */ + public static String UnderlineToHump(String para, String prefix){ + StringBuilder result=new StringBuilder(); + String a[]=para.split("_"); + for(String s:a){ + if(result.length()==0){ + result.append(s.toLowerCase()); + }else{ + result.append(s.substring(0, 1).toUpperCase()); + result.append(s.substring(1).toLowerCase()); + } + } + return result.toString(); + } + + /** + * 驼峰命名转为下划线命名 + * + * @author yubaoshan + * @date 2020-12-17 23:55 + */ + public static String HumpToUnderline(String para){ + StringBuilder sb=new StringBuilder(para); + int temp=0;//偏移量,第i个下划线的位置是 当前的位置+ 偏移量(i-1),第一个下划线偏移量是0 + for(int i=0;i<para.length();i++){ + if(Character.isUpperCase(para.charAt(i))){ + sb.insert(i+temp, "_"); + temp+=1; + } + } + return sb.toString().toLowerCase(); + } +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/StringDateTool.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/StringDateTool.java new file mode 100644 index 0000000..bb30e49 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/tool/StringDateTool.java @@ -0,0 +1,51 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.tool; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 时间与String转换工具类 + * + * @author yubaoshan + * @date 2020-12-17 23:42 + */ +public class StringDateTool { + + + /** + * 获取现在时间 + * + * @author yubaoshan + * @date 2020-12-17 23:42 + */ + public static String getStringDate() { + Date currentTime = new Date(); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String dateString = formatter.format(currentTime); + return dateString; + } +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/util/Util.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/util/Util.java new file mode 100644 index 0000000..0109f81 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/core/util/Util.java @@ -0,0 +1,119 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.core.util; + +import org.apache.commons.io.IOUtils; +import org.apache.velocity.app.Velocity; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.context.constant.ConstantContext; +import vip.xiaonuo.core.enums.DbIdEnum; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 代码生成工具类 + * + * @author yubaoshan + * @Date 2020年12月16日23:29:53 + */ +public class Util { + + /** + * 初始化vm + * + * @author yubaoshan + * @Date 2020年12月16日23:29:53 + */ + public static void initVelocity() { + Properties properties = new Properties(); + try { + properties.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + properties.setProperty(Velocity.ENCODING_DEFAULT, "UTF-8"); + properties.setProperty(Velocity.OUTPUT_ENCODING, "UTF-8"); + Velocity.init(properties); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 生成压缩包文件 + * + * @author yubaoshan + * @Date 2020年12月16日23:29:53 + */ + public static void DownloadGen(HttpServletResponse response, byte[] bytes) throws IOException { + response.reset(); + response.setHeader("Content-Disposition", "attachment; filename=\"Snowy.zip\""); + response.addHeader("Content-Length", "" + bytes.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(bytes, response.getOutputStream()); + } + + /** + * 查询某字符串第i次出现的游标 + * + * @param string 字符串 + * @param i 第i次出现 + * @param str 子字符串 + * @author yubaoshan + * @date 2020年12月16日23:29:53 + */ + private static int getIndex(String string, int i, String str) { + Matcher slashMatcher = Pattern.compile(str).matcher(string); + int mIdx = 0; + while (slashMatcher.find()) { + mIdx++; + //当"/"符号第三次出现的位置 + if (mIdx == i) { + break; + } + } + return slashMatcher.start(); + } + + /** + * 获取数据库用户 + * + * @author yubaoshan + * @date 2021-03-11 18:37:00 + */ + public static String getDataBasename () { + String dataUrl = ConstantContext.me().getStr(CommonConstant.DATABASE_URL_NAME); + String driverName = ConstantContext.me().getStr(CommonConstant.DATABASE_DRIVER_NAME); + if (driverName.contains(DbIdEnum.MYSQL.getCode())) { + return dataUrl.substring(getIndex(dataUrl, 3, "/") + 1, dataUrl.indexOf("?")); + } else if (driverName.contains(DbIdEnum.ORACLE.getCode())) { + return ConstantContext.me().getStr(CommonConstant.DATABASE_USER_NAME); + } else { + return ""; + } + } + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/controller/CodeGenerateController.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/controller/CodeGenerateController.java new file mode 100644 index 0000000..19b88a5 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/controller/CodeGenerateController.java @@ -0,0 +1,160 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.exception.DemoException; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.generate.modular.param.CodeGenerateParam; +import vip.xiaonuo.generate.modular.service.CodeGenerateService; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 代码生成器 + * + * @auther yubaoshan + * @date 12/15/20 11:20 PM + */ +@RestController +public class CodeGenerateController { + + @Resource + private CodeGenerateService codeGenerateService; + + /** + * 代码生成基础数据 + * + * @author yubaoshan89 + * @date 2020年12月16日20:58:48 + */ + @Permission + @GetMapping("/codeGenerate/page") + @BusinessLog(title = "代码生成配置_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(CodeGenerateParam codeGenerateParam) { + return new SuccessResponseData(codeGenerateService.page(codeGenerateParam)); + } + + /** + * 代码生成基础配置保存 + * + * @auther yubaoshan + * @date 12/15/20 11:20 PM + */ + @Permission + @PostMapping("/codeGenerate/add") + @BusinessLog(title = "代码生成配置_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(CodeGenerateParam.add.class) CodeGenerateParam codeGenerateParam) { + this.codeGenerateService.add(codeGenerateParam); + return new SuccessResponseData(); + } + + /** + * 代码生成基础配置编辑 + * + * @auther yubaoshan + * @date 2020年12月16日20:56:19 + */ + @Permission + @PostMapping("/codeGenerate/edit") + @BusinessLog(title = "代码生成配置_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(CodeGenerateParam.add.class) CodeGenerateParam codeGenerateParam) { + codeGenerateService.edit(codeGenerateParam); + return new SuccessResponseData(); + } + + /** + * 删除代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日22:13:32 + */ + @Permission + @PostMapping("/codeGenerate/delete") + @BusinessLog(title = "代码生成配置_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(CodeGenerateParam.delete.class) List<CodeGenerateParam> codeGenerateParamList) { + codeGenerateService.delete(codeGenerateParamList); + return new SuccessResponseData(); + } + + /** + * 查询当前数据库用户下的所有表 + * + * @author yubaoshan + * @date 2020-12-16 01:55:48 + */ + @Permission + @GetMapping("/codeGenerate/InformationList") + @BusinessLog(title = "数据库表列表_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData InformationList() { + return ResponseData.success(codeGenerateService.InformationTableList()); + } + + /** + * 代码生成基础配置生成 + * + * @auther yubaoshan + * @date 12/15/20 11:20 PM + */ + @Permission + @PostMapping("/codeGenerate/runLocal") + @BusinessLog(title = "代码生成_本地项目", opType = LogAnnotionOpTypeEnum.OTHER) + public ResponseData runLocal(@RequestBody @Validated(CodeGenerateParam.detail.class) CodeGenerateParam codeGenerateParam) { + // 演示环境开启,则不允许操作 + if (ConstantContextHolder.getDemoEnvFlag()) { + throw new DemoException(); + } + this.codeGenerateService.runLocal(codeGenerateParam); + return new SuccessResponseData(); + } + + /** + * 代码生成基础配置生成 + * + * @auther yubaoshan + * @date 12/15/20 11:20 PM + */ + @Permission + @GetMapping("/codeGenerate/runDown") + @BusinessLog(title = "代码生成_下载方式", opType = LogAnnotionOpTypeEnum.OTHER) + public void runDown(@Validated(CodeGenerateParam.detail.class) CodeGenerateParam codeGenerateParam, HttpServletResponse response) { + // 演示环境开启,则不允许操作 + if (ConstantContextHolder.getDemoEnvFlag()) { + throw new DemoException(); + } + this.codeGenerateService.runDown(codeGenerateParam, response); + } +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/controller/SysCodeGenerateConfigController.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/controller/SysCodeGenerateConfigController.java new file mode 100644 index 0000000..5d01b43 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/controller/SysCodeGenerateConfigController.java @@ -0,0 +1,94 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.generate.modular.param.SysCodeGenerateConfigParam; +import vip.xiaonuo.generate.modular.service.SysCodeGenerateConfigService; + +import javax.annotation.Resource; + +/** + * 代码生成详细配置控制器 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ +@RestController +public class SysCodeGenerateConfigController { + + @Resource + private SysCodeGenerateConfigService sysCodeGenerateConfigService; + + /** + * 编辑代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + @Permission + @PostMapping("/sysCodeGenerateConfig/edit") + @BusinessLog(title = "代码生成详细配置_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysCodeGenerateConfigParam.edit.class) SysCodeGenerateConfigParam sysCodeGenerateConfigParam) { + sysCodeGenerateConfigService.edit(sysCodeGenerateConfigParam.getSysCodeGenerateConfigParamList()); + return new SuccessResponseData(); + } + + /** + * 查看代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + @Permission + @GetMapping("/sysCodeGenerateConfig/detail") + @BusinessLog(title = "代码生成详细配置_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysCodeGenerateConfigParam.detail.class) SysCodeGenerateConfigParam sysCodeGenerateConfigParam) { + return new SuccessResponseData(sysCodeGenerateConfigService.detail(sysCodeGenerateConfigParam)); + } + + /** + * 代码生成详细配置列表 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + @Permission + @GetMapping("/sysCodeGenerateConfig/list") + @BusinessLog(title = "代码生成详细配置_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysCodeGenerateConfigParam sysCodeGenerateConfigParam) { + return new SuccessResponseData(sysCodeGenerateConfigService.list(sysCodeGenerateConfigParam)); + } + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/entity/CodeGenerate.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/entity/CodeGenerate.java new file mode 100644 index 0000000..4df8adb --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/entity/CodeGenerate.java @@ -0,0 +1,100 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日21:04:37 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_code_generate") +public class CodeGenerate extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 作者姓名 + */ + private String authorName; + + /** + * 类名 + */ + private String className; + + /** + * 是否移除表前缀 + */ + private String tablePrefix; + + /** + * 生成方式 + */ + private String generateType; + + /** + * 数据库表名 + */ + private String tableName; + /** + * 包名 + */ + private String packageName; + + /** + * 业务名(业务代码包名称) + */ + private String busName; + + /** + * 功能名(数据库表名称) + */ + private String tableComment; + + /** + * 所属应用 + */ + private String appCode; + + /** + * 菜单上级 + */ + private String menuPid; + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/entity/SysCodeGenerateConfig.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/entity/SysCodeGenerateConfig.java new file mode 100644 index 0000000..5667baa --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/entity/SysCodeGenerateConfig.java @@ -0,0 +1,136 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_code_generate_config") +public class SysCodeGenerateConfig extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 代码生成主表ID + */ + private Long codeGenId; + + /** + * 数据库字段名 + */ + private String columnName; + + /** + * java类字段名 + */ + private String javaName; + + /** + * 字段描述 + */ + private String columnComment; + + /** + * java类型 + */ + private String javaType; + + /** + * 作用类型(字典) + */ + private String effectType; + + /** + * 字典code + */ + private String dictTypeCode; + + /** + * 列表是否缩进(字典) + */ + private String whetherRetract; + + /** + * 是否必填(字典) + */ + private String whetherRequired; + + /** + * 是否是查询条件 + */ + private String queryWhether; + + /** + * 查询方式 + */ + private String queryType; + + /** + * 列表显示 + */ + private String whetherTable; + + /** + * 增改 + */ + private String whetherAddUpdate; + + /** + * 主外键 + */ + public String columnKey; + + /** + * 首字母大写名称(用于代码生成get set方法) + */ + public String columnKeyName; + + /** + * 数据库中类型(物理类型) + */ + public String dataType; + + /** + * 是否是通用字段 + */ + public String whetherCommon; + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/enums/CodeGenerateExceptionEnum.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/enums/CodeGenerateExceptionEnum.java new file mode 100644 index 0000000..d0b3024 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/enums/CodeGenerateExceptionEnum.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.generate.core.consts.GenExpEnumConstant; + +/** + * 代码生成基础配置相关异常枚举 + * + * @author yubaoshan + * @date 2020年12月16日21:21:14 + */ +@ExpEnumType(module = GenExpEnumConstant.GEN_CODE_EXCEPTION_ENUM, kind = GenExpEnumConstant.GEN_CONFIG_EXCEPTION_ENUM) +public enum CodeGenerateExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 代码生成基础配置不存在 + */ + CODE_GEN_NOT_EXIST(1, "代码生成基础配置不存在"), + + /** + * 本地生成代码输出路径错误 + */ + CODE_GEN_NOT_PATH(2,"本地生成代码输出路径错误"), + + /** + * 请检查此数据表中主键的定义 + */ + CODE_GEN_TABLE_NOT_PRI(3,"请检查此数据表中主键的定义"); + + private final Integer code; + + private final String message; + + CodeGenerateExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/enums/SysCodeGenerateConfigExceptionEnum.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/enums/SysCodeGenerateConfigExceptionEnum.java new file mode 100644 index 0000000..3a2817a --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/enums/SysCodeGenerateConfigExceptionEnum.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.generate.core.consts.GenExpEnumConstant; + +/** + * 代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ +@ExpEnumType(module = GenExpEnumConstant.SNOWY_GEN_MODULE_EXP_CODE, kind = GenExpEnumConstant.GEN_CONFIG_EXCEPTION_ENUM) +public enum SysCodeGenerateConfigExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 数据不存在 + */ + NOT_EXIST(1, "此数据不存在"); + + private final Integer code; + + private final String message; + SysCodeGenerateConfigExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/CodeGenerateMapper.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/CodeGenerateMapper.java new file mode 100644 index 0000000..3f008e9 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/CodeGenerateMapper.java @@ -0,0 +1,58 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import vip.xiaonuo.generate.modular.entity.CodeGenerate; +import vip.xiaonuo.generate.modular.result.InforMationColumnsResult; +import vip.xiaonuo.generate.modular.result.InformationResult; + +import java.util.List; + +/** + * 代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日21:07:28 + */ +public interface CodeGenerateMapper extends BaseMapper<CodeGenerate> { + + /** + * 查询指定库中所有表 + * + * @author yubaoshan + * @date 2020年12月17日20:06:05 + */ + List<InformationResult> selectInformationTable(@Param("dbName") String dbName); + + /** + * 查询指定表中所有字段 + * + * @author yubaoshan + * @date 2020年12月17日20:06:05 + */ + List<InforMationColumnsResult> selectInformationColumns(@Param("dbName") String dbName, @Param("tableName") String tableName); +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/SysCodeGenerateConfigMapper.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/SysCodeGenerateConfigMapper.java new file mode 100644 index 0000000..82c7e20 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/SysCodeGenerateConfigMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.generate.modular.entity.SysCodeGenerateConfig; + +/** + * 代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ +public interface SysCodeGenerateConfigMapper extends BaseMapper<SysCodeGenerateConfig> { +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/mapping/CodeGenerateMapper.xml b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/mapping/CodeGenerateMapper.xml new file mode 100644 index 0000000..449c7c9 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/mapping/CodeGenerateMapper.xml @@ -0,0 +1,241 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.generate.modular.mapper.CodeGenerateMapper"> + + <resultMap id="informationResult" type="vip.xiaonuo.generate.modular.result.InformationResult"> + <result column="table_name" property="tableName" /> + <result column="create_time" property="createTime" /> + <result column="update_time" property="updateTime" /> + <result column="table_comment" property="tableComment" /> + </resultMap> + + <resultMap id="inforMationColumnsResult" type="vip.xiaonuo.generate.modular.result.InforMationColumnsResult"> + <result column="column_name" property="columnName" /> + <result column="data_type" property="dataType" /> + <result column="column_comment" property="columnComment" /> + <result column="column_key" property="columnKey" /> + </resultMap> + + <!-- 查询指定库中所有表 mysql --> + <select id="selectInformationTable" parameterType="String" resultMap="informationResult" databaseId = "mysql"> + select table_name,create_time,update_time,table_comment + from information_schema.tables + where + table_schema = '${dbName}' + </select> + + <!-- 查询指定库中所有表 oracle --> + <select id="selectInformationTable" parameterType="String" resultMap="informationResult" databaseId = "oracle"> + select table_name, comments as table_comment + from user_tab_comments + </select> + + <!-- 查询指定库中所有表 mssql --> + <select id="selectInformationTable" parameterType="String" resultMap="informationResult" databaseId = "mssql"> + SELECT DISTINCT + d.name as table_name, + f.value as table_comment + FROM + syscolumns a + LEFT JOIN systypes b ON a.xusertype= b.xusertype + INNER JOIN sysobjects d ON a.id= d.id + AND d.xtype= 'U' + AND d.name != 'dtproperties' + LEFT JOIN syscomments e ON a.cdefault= e.id + LEFT JOIN sys.extended_properties g ON a.id= G.major_id + AND a.colid= g.minor_id + LEFT JOIN sys.extended_properties f ON d.id= f.major_id + AND f.minor_id= 0 + </select> + + <!-- 查询指定库中所有表 pgsql --> + <select id="selectInformationTable" parameterType="String" resultMap="informationResult" databaseId = "pgsql"> + SELECT + relname AS TABLE_NAME, + col_description ( C.oid, 0 ) AS TABLE_COMMENT + FROM + pg_class C + WHERE + relkind = 'r' + AND relname NOT LIKE'pg_%' + AND relname NOT LIKE'sql_%' + ORDER BY + relname + </select> + + <!-- 查询指定库中所有表 达梦数据库 --> + <select id="selectInformationTable" parameterType="String" resultMap="informationResult" databaseId = "dm"> + select table_name, comments as table_comment + from user_tab_comments + </select> + + <!-- 查询指定库中所有表 人大金仓数据库 --> + <select id="selectInformationTable" parameterType="String" resultMap="informationResult" databaseId = "kingbasees"> + select table_name, comments as table_comment + from user_tab_comments + </select> + + <!-- 查询指定表中所有字段 mysql --> + <select id="selectInformationColumns" parameterType="String" resultMap="inforMationColumnsResult" databaseId = "mysql"> + select + column_name,data_type,column_comment,column_key + from information_schema.columns + where + table_schema = '${dbName}' and table_name = '${tableName}'; + </select> + + <!-- 查询指定表中所有字段 oracle --> + <select id="selectInformationColumns" parameterType="String" resultMap="inforMationColumnsResult" databaseId = "oracle"> + select + a.column_name as column_name, + a.data_type as data_type, + b.comments as column_comment, + case + when c.position>0 then 'PRI' + else '' + end column_key + from + (select * from user_tab_columns where table_name='${tableName}') a + left join + (select * from user_col_comments where table_name='${tableName}') b + on a.column_name=b.column_name + left join + ( + select table_name,column_name,position from user_cons_columns + where + table_name='${tableName}' + and constraint_name=(select constraint_name + from + user_constraints where table_name='${tableName}' and constraint_type='P') + and owner='${dbName}' + ) c + on a.column_name=c.column_name + order by a.column_id + </select> + + <!-- 查询指定表中所有字段 mssql --> + <select id="selectInformationColumns" parameterType="String" resultMap="inforMationColumnsResult" databaseId = "mssql"> + SELECT + C.name AS column_name, + T.name AS data_type, + isnull( ETP.value, '' ) AS column_comment, + CASE + + WHEN EXISTS ( + SELECT + 1 + FROM + sysobjects + WHERE + xtype = 'PK' + AND parent_obj = c.id + AND name IN ( SELECT name FROM sysindexes WHERE indid IN ( SELECT indid FROM sysindexkeys WHERE id = c.id AND colid = c.colid ) ) + ) THEN + 'PRI' ELSE '' + END AS column_key + FROM + syscolumns C + INNER JOIN systypes T ON C.xusertype = T.xusertype + LEFT JOIN sys.extended_properties ETP ON ETP.major_id = c.id + AND ETP.minor_id = C.colid + AND ETP.name = 'MS_Description' + LEFT JOIN syscomments CM ON C.cdefault= CM.id + WHERE + C.id = object_id( '${tableName}' ) + </select> + + <!-- 查询指定表中所有字段 pgsql --> + <select id="selectInformationColumns" parameterType="String" resultMap="inforMationColumnsResult" databaseId = "pgsql"> + SELECT + t1.*, + COALESCE(t2.pk_name, '') AS column_key + FROM + ( + SELECT A + .attname AS COLUMN_NAME, + pg_type.typname AS data_type, + col_description ( A.attrelid, A.attnum ) AS column_comment + FROM + pg_class AS C, + pg_attribute + AS A INNER JOIN pg_type ON pg_type.oid = A.atttypid + WHERE + C.relname = '${tableName}' + AND A.attrelid = C.oid + AND A.attnum > 0 + ) t1 + LEFT JOIN ( + SELECT + pg_attribute.attname AS COLUMN_NAME, + CASE WHEN pg_constraint.conname ISNULL THEN '' ELSE 'PRI' END AS pk_name + FROM + pg_constraint + INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid + INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid + AND pg_attribute.attnum = pg_constraint.conkey [ 1 ] + INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid + WHERE + pg_class.relname = '${tableName}' + AND pg_constraint.contype = 'p' + ) t2 ON t1.COLUMN_NAME = t2.COLUMN_NAME + </select> + + <!-- 查询指定表中所有字段 达梦数据库 --> + <select id="selectInformationColumns" parameterType="String" resultMap="inforMationColumnsResult" databaseId = "dm"> + select + a.column_name as column_name, + a.data_type as data_type, + b.comments as column_comment, + case + when c.position>0 then 'PRI' + else '' + end column_key + from + (select * from user_tab_columns where table_name='${tableName}') a + left join + (select * from user_col_comments where table_name='${tableName}') b + on a.column_name=b.column_name + left join + ( + select table_name,column_name,position from user_cons_columns + where + table_name='${tableName}' + and constraint_name=(select constraint_name + from + user_constraints where table_name='${tableName}' and constraint_type='P') + and owner='${dbName}' + ) c + on a.column_name=c.column_name + order by a.column_id + </select> + + <!-- 查询指定表中所有字段 人大金仓数据库 --> + <select id="selectInformationColumns" parameterType="String" resultMap="inforMationColumnsResult" databaseId = "dm"> + select + a.column_name as column_name, + a.data_type as data_type, + b.comments as column_comment, + case + when c.position>0 then 'PRI' + else '' + end column_key + from + (select * from user_tab_columns where table_name='${tableName}') a + left join + (select * from user_col_comments where table_name='${tableName}') b + on a.column_name=b.column_name + left join + ( + select table_name,column_name,position from user_cons_columns + where + table_name='${tableName}' + and constraint_name=(select constraint_name + from + user_constraints where table_name='${tableName}' and constraint_type='P') + and owner='${dbName}' + ) c + on a.column_name=c.column_name + order by a.column_id + </select> + +</mapper> diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/mapping/SysCodeGenerateConfigMapper.xml b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/mapping/SysCodeGenerateConfigMapper.xml new file mode 100644 index 0000000..77b3b20 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/mapper/mapping/SysCodeGenerateConfigMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.generate.modular.mapper.SysCodeGenerateConfigMapper"> + +</mapper> diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/param/CodeGenerateParam.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/param/CodeGenerateParam.java new file mode 100644 index 0000000..802557d --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/param/CodeGenerateParam.java @@ -0,0 +1,111 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; +import vip.xiaonuo.core.validation.flag.FlagValue; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 代码生成参数类 + * + * @author yubaoshan + * @date 2020年12月16日20:41:21 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class CodeGenerateParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 作者姓名 + */ + @NotBlank(message = "作者姓名不能为空,请检查authorName参数", groups = {BaseParam.add.class, edit.class}) + private String authorName; + + /** + * 类名 + */ + @NotBlank(message = "类名不能为空,请检查className参数", groups = {BaseParam.add.class, edit.class}) + private String className; + + /** + * 是否移除表前缀 + */ + @NotBlank(message = "是否移除表前缀不能为空,请检查tablePrefix参数", groups = {BaseParam.add.class, edit.class}) + @FlagValue(message = "是否移除表前缀格式错误,正确格式应该Y或者N,请检查tablePrefix参数", groups = {add.class, edit.class}) + private String tablePrefix; + + /** + * 生成方式 + */ + @NotBlank(message = "生成方式不能为空,请检查generateType参数", groups = {BaseParam.add.class, edit.class}) + private String generateType; + + /** + * 数据库表名 + */ + @NotBlank(message = "数据库表名不能为空,请检查tableName参数", groups = {BaseParam.add.class, edit.class}) + private String tableName; + + /** + * 代码包名 + */ + private String packageName; + + /** + * 业务名(业务代码包名称) + */ + @NotBlank(message = "业务名不能为空,请检查busName参数", groups = {BaseParam.add.class, edit.class}) + private String busName; + + /** + * 功能名(数据库表名称) + */ + @NotBlank(message = "功能名不能为空,请检查tableComment参数", groups = {BaseParam.add.class, edit.class}) + private String tableComment; + + /** + * 所属应用 + */ + @NotBlank(message = "所属应用不能为空,请检查appCode参数", groups = {BaseParam.add.class, edit.class}) + private String appCode; + + /** + * 菜单上级 + */ + @NotBlank(message = "菜单上级不能为空,请检查menuPid参数", groups = {BaseParam.add.class, edit.class}) + private String menuPid; + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/param/SysCodeGenerateConfigParam.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/param/SysCodeGenerateConfigParam.java new file mode 100644 index 0000000..ee18e32 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/param/SysCodeGenerateConfigParam.java @@ -0,0 +1,138 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.param; + +import lombok.Data; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotEmpty; +import java.util.List; + +/** +* 代码生成详细配置参数类 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 +*/ +@Data +public class SysCodeGenerateConfigParam extends BaseParam { + + /** + * 主键 + */ + private Long id; + + /** + * 代码生成主表ID + */ + private Long codeGenId; + + /** + * 数据库字段名 + */ + private String columnName; + + /** + * java类字段名 + */ + private String javaName; + + /** + * 字段描述 + */ + private String columnComment; + + /** + * java类型 + */ + private String javaType; + + /** + * 作用类型(字典) + */ + private String effectType; + + /** + * 字典code + */ + private String dictTypeCode; + + /** + * 列表是否缩进(字典) + */ + private String whetherRetract; + + /** + * 是否必填(字典) + */ + private String whetherRequired; + + /** + * 是否是查询条件 + */ + private String queryWhether; + + /** + * 查询方式 + */ + private String queryType; + + /** + * 列表展示 + */ + private String whetherTable; + + /** + * 增改 + */ + private String whetherAddUpdate; + + /** + * 主外键 + */ + public String columnKey; + + /** + * 主外键名称 + */ + public String columnKeyName; + + /** + * 数据库中类型(物理类型) + */ + public String dataType; + + /** + * 是否是通用字段 + */ + public String whetherCommon; + + /** + * 代码生成配置列表 + */ + @NotEmpty(message = "代码生成配置列表,请检查sysCodeGenerateConfigParamList参数", groups = {edit.class}) + private List<SysCodeGenerateConfigParam> sysCodeGenerateConfigParamList; + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/result/InforMationColumnsResult.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/result/InforMationColumnsResult.java new file mode 100644 index 0000000..bd97a85 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/result/InforMationColumnsResult.java @@ -0,0 +1,58 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.result; + +import lombok.Data; + +/** + * 数据库表中返回对象 + * + * @author yubaoshan + * @date 2020年12月17日20:00:31 + */ +@Data +public class InforMationColumnsResult { + + /** + * 字段名 + */ + public String columnName; + + /** + * 数据库中类型 + */ + public String dataType; + + /** + * 字段描述 + */ + public String columnComment; + + /** + * 主外键 + */ + public String columnKey; + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/result/InformationResult.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/result/InformationResult.java new file mode 100644 index 0000000..b298637 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/result/InformationResult.java @@ -0,0 +1,58 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.result; + +import lombok.Data; + +/** + * 数据表返回对象 + * + * @author yubaoshan + * @date 2020年12月17日20:01:56 + */ +@Data +public class InformationResult { + + /** + * 表名(字母形式的) + */ + public String tableName; + + /** + * 创建时间 + */ + public String createTime; + + /** + * 更新时间 + */ + public String updateTime; + + /** + * 表名称描述(注释)(功能名) + */ + public String tableComment; + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/CodeGenerateService.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/CodeGenerateService.java new file mode 100644 index 0000000..aabd3f4 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/CodeGenerateService.java @@ -0,0 +1,107 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.generate.modular.entity.CodeGenerate; +import vip.xiaonuo.generate.modular.param.CodeGenerateParam; +import vip.xiaonuo.generate.modular.result.InformationResult; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 代码生成基础配置service接口 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ +public interface CodeGenerateService extends IService<CodeGenerate> { + + /** + * 查询代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ + PageResult<CodeGenerate> page(CodeGenerateParam codeGenerateParam); + + /** + * 添加查询代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ + void add(CodeGenerateParam codeGenerateParam); + + /** + * 删除查询代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ + void delete(List<CodeGenerateParam> codeGenerateParamList); + + /** + * 编辑查询代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ + void edit(CodeGenerateParam codeGenerateParam); + + /** + * 查看查询代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ + CodeGenerate detail(CodeGenerateParam codeGenerateParam); + + /** + * 查询当前数据库用户下的所有表 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ + List<InformationResult> InformationTableList (); + + /** + * 本地生成代码 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ + void runLocal(CodeGenerateParam codeGenerateParam); + + /** + * 下载zip方式 + * + * @author yubaoshan + * @date 2020年12月16日21:03:15 + */ + void runDown(CodeGenerateParam codeGenerateParam, HttpServletResponse response); +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/SysCodeGenerateConfigService.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/SysCodeGenerateConfigService.java new file mode 100644 index 0000000..b99c442 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/SysCodeGenerateConfigService.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.generate.modular.entity.CodeGenerate; +import vip.xiaonuo.generate.modular.entity.SysCodeGenerateConfig; +import vip.xiaonuo.generate.modular.param.SysCodeGenerateConfigParam; +import vip.xiaonuo.generate.modular.result.InforMationColumnsResult; + +import java.util.List; + +/** + * 代码生成详细配置service接口 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ +public interface SysCodeGenerateConfigService extends IService<SysCodeGenerateConfig> { + + /** + * 代码生成详细配置列表 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + List<SysCodeGenerateConfig> list(SysCodeGenerateConfigParam sysCodeGenerateConfigParam); + + /** + * 添加代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + void add(SysCodeGenerateConfigParam sysCodeGenerateConfigParam); + + /** + * 添加代码生成详细配置列表 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + void addList(List<InforMationColumnsResult> inforMationColumnsResultList, CodeGenerate codeGenerate); + + /** + * 删除代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + void delete(SysCodeGenerateConfigParam sysCodeGenerateConfigParam); + + /** + * 编辑代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + void edit(List<SysCodeGenerateConfigParam> sysCodeGenerateConfigParamList); + + /** + * 查看代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + SysCodeGenerateConfig detail(SysCodeGenerateConfigParam sysCodeGenerateConfigParam); +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/impl/CodeGenerateServiceImpl.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/impl/CodeGenerateServiceImpl.java new file mode 100644 index 0000000..c6d34af --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/impl/CodeGenerateServiceImpl.java @@ -0,0 +1,364 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.baomidou.mybatisplus.extension.toolkit.SqlRunner; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.app.VelocityEngine; +import org.springframework.stereotype.Service; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.generate.core.consts.GenConstant; +import vip.xiaonuo.generate.core.context.XnVelocityContext; +import vip.xiaonuo.generate.core.param.XnCodeGenParam; +import vip.xiaonuo.generate.core.tool.StringDateTool; +import vip.xiaonuo.generate.core.util.Util; +import vip.xiaonuo.generate.modular.entity.CodeGenerate; +import vip.xiaonuo.generate.modular.entity.SysCodeGenerateConfig; +import vip.xiaonuo.generate.modular.enums.CodeGenerateExceptionEnum; +import vip.xiaonuo.generate.modular.mapper.CodeGenerateMapper; +import vip.xiaonuo.generate.modular.param.CodeGenerateParam; +import vip.xiaonuo.generate.modular.param.SysCodeGenerateConfigParam; +import vip.xiaonuo.generate.modular.result.InforMationColumnsResult; +import vip.xiaonuo.generate.modular.result.InformationResult; +import vip.xiaonuo.generate.modular.service.CodeGenerateService; +import vip.xiaonuo.generate.modular.service.SysCodeGenerateConfigService; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * 代码生成基础配置service接口实现类 + * + * @author yubaoshan + * @date 2020年12月16日21:31:57 + */ +@Service +public class CodeGenerateServiceImpl extends ServiceImpl<CodeGenerateMapper, CodeGenerate> implements CodeGenerateService { + + /** + * 模板后缀 + */ + private static String TEMP_SUFFIX = ".vm"; + + /** + * 转换的编码 + */ + private static String ENCODED = "UTF-8"; + + private static String SELECT_SYS_MENU_SQL = "select * from sys_menu where id = {0}"; + + /** + * 转换模板名称所需变量 + */ + private static String ADD_FORM_PAGE_NAME = "addForm.vue"; + private static String EDIT_FORM_PAGE_NAME = "editForm.vue"; + private static String INDEX_PAGE_NAME = "index.vue"; + private static String MANAGE_JS_NAME = "Manage.js"; + private static String SQL_NAME = ".sql"; + private static String JAVA_SUFFIX = ".java"; + private static String TEMP_ENTITY_NAME = "entity"; + + @Resource + private SysCodeGenerateConfigService sysCodeGenerateConfigService; + + @Override + public PageResult<CodeGenerate> page(CodeGenerateParam codeGenerateParam) { + QueryWrapper<CodeGenerate> queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(codeGenerateParam)) { + //根据表名模糊查询 + if (ObjectUtil.isNotEmpty(codeGenerateParam.getTableName())) { + queryWrapper.lambda().like(CodeGenerate::getTableName, codeGenerateParam.getTableName()); + } + } + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public void add(CodeGenerateParam codeGenerateParam) { + CodeGenerate codeGenerate = new CodeGenerate(); + BeanUtil.copyProperties(codeGenerateParam, codeGenerate); + if (!vldTablePri(codeGenerate.getTableName())) { + throw new ServiceException(CodeGenerateExceptionEnum.CODE_GEN_TABLE_NOT_PRI); + } + this.save(codeGenerate); + + // 加入配置表中 + codeGenerateParam.setId(codeGenerate.getId()); + this.sysCodeGenerateConfigService.addList(this.getInforMationColumnsResultList(codeGenerateParam), codeGenerate); + } + + @Override + public void delete(List<CodeGenerateParam> codeGenerateParamList) { + codeGenerateParamList.forEach(codeGenerateParam -> { + this.removeById(codeGenerateParam.getId()); + SysCodeGenerateConfigParam sysCodeGenerateConfigParam = new SysCodeGenerateConfigParam(); + sysCodeGenerateConfigParam.setCodeGenId(codeGenerateParam.getId()); + this.sysCodeGenerateConfigService.delete(sysCodeGenerateConfigParam); + }); + } + + @Override + public void edit(CodeGenerateParam codeGenerateParam) { + CodeGenerate codeGenerate = this.queryCodeGenerate(codeGenerateParam); + BeanUtil.copyProperties(codeGenerateParam, codeGenerate); + if (!vldTablePri(codeGenerate.getTableName())) { + throw new ServiceException(CodeGenerateExceptionEnum.CODE_GEN_TABLE_NOT_PRI); + } + this.updateById(codeGenerate); + } + + @Override + public CodeGenerate detail(CodeGenerateParam codeGenerateParam) { + return this.queryCodeGenerate(codeGenerateParam); + } + + /** + * 获取代码生成基础配置 + * + * @author yubaoshan + * @date 2020年12月16日21:19:10 + */ + private CodeGenerate queryCodeGenerate(CodeGenerateParam codeGenerateParam) { + CodeGenerate codeGenerate = this.getById(codeGenerateParam.getId()); + if (ObjectUtil.isNull(codeGenerate)) { + throw new ServiceException(CodeGenerateExceptionEnum.CODE_GEN_NOT_EXIST); + } + return codeGenerate; + } + + @Override + public List<InformationResult> InformationTableList () { + return this.baseMapper.selectInformationTable(Util.getDataBasename()); + } + + @Override + public void runLocal(CodeGenerateParam codeGenerateParam) { + XnCodeGenParam xnCodeGenParam = copyParams(codeGenerateParam); + codeGenLocal(xnCodeGenParam); + } + + @Override + public void runDown(CodeGenerateParam codeGenerateParam, HttpServletResponse response) { + XnCodeGenParam xnCodeGenParam = copyParams(codeGenerateParam); + downloadCode(xnCodeGenParam, response); + } + + /** + * 校验表中是否包含主键 + * + * @author yubaoshan + * @date 2020年12月23日 00点32分 + */ + private boolean vldTablePri (String tableName) { + List<InforMationColumnsResult> inforMationColumnsResultList = this.baseMapper.selectInformationColumns(Util.getDataBasename(), tableName); + for (int a = 0; a < inforMationColumnsResultList.size(); a++) { + if (ObjectUtil.isNotNull(inforMationColumnsResultList.get(a).columnKey) + && inforMationColumnsResultList.get(a).columnKey.equals(GenConstant.DB_TABLE_COM_KRY)) { + return true; + } + } + return false; + } + + /** + * 下载方式组装代码基础 + * + * @author yubaoshan + * @date 2020年12月23日 00点32分 + */ + private void downloadCode(XnCodeGenParam xnCodeGenParam, HttpServletResponse response) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream); + codeGenDown(xnCodeGenParam, zipOutputStream); + IOUtils.closeQuietly(zipOutputStream); + outputStream.toByteArray(); + try { + Util.DownloadGen(response, outputStream.toByteArray()); + } catch (Exception e) { + throw new ServiceException(CodeGenerateExceptionEnum.CODE_GEN_NOT_PATH); + } + } + + /** + * 获取表中所有字段集合 + * + * @author yubaoshan + * @date 2021-02-06 22:36 + */ + private List<InforMationColumnsResult> getInforMationColumnsResultList (CodeGenerateParam codeGenerateParam) { + CodeGenerate codeGenerate = this.queryCodeGenerate(codeGenerateParam); + return this.baseMapper.selectInformationColumns(Util.getDataBasename(), codeGenerate.getTableName()); + } + + private XnCodeGenParam copyParams (CodeGenerateParam codeGenerateParam) { + CodeGenerate codeGenerate = this.queryCodeGenerate(codeGenerateParam); + SysCodeGenerateConfigParam sysCodeGenerateConfigParam = new SysCodeGenerateConfigParam(); + sysCodeGenerateConfigParam.setCodeGenId(codeGenerateParam.getId()); + List<SysCodeGenerateConfig> configList = this.sysCodeGenerateConfigService.list(sysCodeGenerateConfigParam); + XnCodeGenParam param = new XnCodeGenParam(); + BeanUtil.copyProperties(codeGenerate, param); + // 功能名 + param.setFunctionName(codeGenerate.getTableComment()); + param.setConfigList(configList); + param.setCreateTimeString(StringDateTool.getStringDate()); + if (!codeGenerate.getMenuPid().equals("0")) { + Map<String, Object> map = SqlRunner.db().selectOne(SELECT_SYS_MENU_SQL, codeGenerate.getMenuPid()); + param.setMenuPids(map.get("pids").toString()); + } + return param; + } + + /** + * 本地项目生成 + */ + private void codeGenLocal (XnCodeGenParam xnCodeGenParam) { + XnVelocityContext context = new XnVelocityContext(); + //初始化参数 + Properties properties=new Properties(); + //设置velocity资源加载方式为class + properties.setProperty("resource.loader", "class"); + //设置velocity资源加载方式为file时的处理类 + properties.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + //实例化一个VelocityEngine对象 + VelocityEngine velocityEngine=new VelocityEngine(properties); + + String[] filePath = GenConstant.xnCodeGenFilePath(xnCodeGenParam.getBusName(), xnCodeGenParam.getPackageName()); + for (int i = 0; i < filePath.length; i++) { + String templateName = GenConstant.xnCodeGenTempFile[i]; + + String fileBaseName = ResetFileBaseName(xnCodeGenParam.getClassName(), + templateName.substring(templateName.indexOf(GenConstant.FILE_SEP) + 1, templateName.lastIndexOf(TEMP_SUFFIX))); + String path = GenConstant.getLocalPath (); + // 前端VUE位置有所变化, sql同样根目录 + if (fileBaseName.contains(INDEX_PAGE_NAME) || fileBaseName.contains(ADD_FORM_PAGE_NAME) || + fileBaseName.contains(EDIT_FORM_PAGE_NAME) ||fileBaseName.contains(MANAGE_JS_NAME) || + fileBaseName.contains(SQL_NAME)) { + path = GenConstant.getLocalFrontPath(); + } + + File file = new File(path + filePath[i] + fileBaseName); + + //判断是否覆盖存在的文件 + if(file.exists() && !GenConstant.FLAG){ + continue; + } + + //获取父目录 + File parentFile = file.getParentFile(); + if(!parentFile.exists()){ + parentFile.mkdirs(); + } + try { + Writer writer = new FileWriter(file); + velocityEngine.mergeTemplate(GenConstant.templatePath + templateName,ENCODED,context.createVelContext(xnCodeGenParam),writer); + writer.close(); + } catch (Exception e) { + throw new ServiceException(CodeGenerateExceptionEnum.CODE_GEN_NOT_PATH); + } + } + } + + /** + * 下载ZIP方式 + */ + private void codeGenDown (XnCodeGenParam xnCodeGenParam,ZipOutputStream zipOutputStream) { + Util.initVelocity(); + XnVelocityContext context = new XnVelocityContext(); + + String[] filePath = GenConstant.xnCodeGenFilePath(xnCodeGenParam.getBusName(), xnCodeGenParam.getPackageName()); + for (int a = 0; a < filePath.length; a++) { + String templateName = GenConstant.xnCodeGenTempFile[a]; + + String fileBaseName = ResetFileBaseName(xnCodeGenParam.getClassName(), + templateName.substring(templateName.indexOf(GenConstant.FILE_SEP) + 1, templateName.lastIndexOf(TEMP_SUFFIX))); + XnZipOutputStream(context.createVelContext(xnCodeGenParam), + GenConstant.templatePath + templateName, + filePath[a] + fileBaseName, + zipOutputStream); + } + } + + /** + * 重置文件名称 + */ + private static String ResetFileBaseName (String className,String fileName) { + String fileBaseName = className + fileName; + // 实体类名称单独处理 + if (fileBaseName.contains(TEMP_ENTITY_NAME)) { + return className + JAVA_SUFFIX; + } + // 首页index.vue界面 + if (fileBaseName.contains(INDEX_PAGE_NAME)) { + return INDEX_PAGE_NAME; + } + // 表单界面名称 + if (fileBaseName.contains(ADD_FORM_PAGE_NAME)) { + return ADD_FORM_PAGE_NAME; + } + if (fileBaseName.contains(EDIT_FORM_PAGE_NAME)) { + return EDIT_FORM_PAGE_NAME; + } + // js名称 + if (fileBaseName.contains(MANAGE_JS_NAME)) { + return className.substring(0,1).toLowerCase() + className.substring(1) + MANAGE_JS_NAME; + } + return fileBaseName; + } + + /** + * 生成ZIP + */ + private void XnZipOutputStream (VelocityContext velContext,String tempName, String fileBaseName, ZipOutputStream zipOutputStream) { + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(tempName, ENCODED); + tpl.merge(velContext, sw); + try { + // 添加到zip + zipOutputStream.putNextEntry(new ZipEntry(fileBaseName)); + IOUtils.write(sw.toString(), zipOutputStream, ENCODED); + IOUtils.closeQuietly(sw); + zipOutputStream.flush(); + zipOutputStream.closeEntry(); + } catch (IOException e) { + throw new ServiceException(CodeGenerateExceptionEnum.CODE_GEN_NOT_PATH); + } + } + +} diff --git a/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/impl/SysCodeGenerateConfigServiceImpl.java b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/impl/SysCodeGenerateConfigServiceImpl.java new file mode 100644 index 0000000..c7c826e --- /dev/null +++ b/snowy-base/snowy-gen/src/main/java/vip/xiaonuo/generate/modular/service/impl/SysCodeGenerateConfigServiceImpl.java @@ -0,0 +1,159 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.generate.modular.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.enums.YesOrNotEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.generate.core.consts.GenConstant; +import vip.xiaonuo.generate.core.enums.QueryTypeEnum; +import vip.xiaonuo.generate.core.enums.TableFilteredFieldsEnum; +import vip.xiaonuo.generate.core.tool.JavaEffTool; +import vip.xiaonuo.generate.core.tool.JavaSqlTool; +import vip.xiaonuo.generate.core.tool.NamingConTool; +import vip.xiaonuo.generate.modular.entity.CodeGenerate; +import vip.xiaonuo.generate.modular.entity.SysCodeGenerateConfig; +import vip.xiaonuo.generate.modular.enums.SysCodeGenerateConfigExceptionEnum; +import vip.xiaonuo.generate.modular.mapper.SysCodeGenerateConfigMapper; +import vip.xiaonuo.generate.modular.param.SysCodeGenerateConfigParam; +import vip.xiaonuo.generate.modular.result.InforMationColumnsResult; +import vip.xiaonuo.generate.modular.service.SysCodeGenerateConfigService; + +import java.util.List; + +/** + * 代码生成详细配置service接口实现类 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ +@Service +public class SysCodeGenerateConfigServiceImpl extends ServiceImpl<SysCodeGenerateConfigMapper, SysCodeGenerateConfig> implements SysCodeGenerateConfigService { + + @Override + public List<SysCodeGenerateConfig> list(SysCodeGenerateConfigParam sysCodeGenerateConfigParam) { + LambdaQueryWrapper<SysCodeGenerateConfig> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysCodeGenerateConfigParam)) { + + // 根据代码生成主表ID 模糊查询 + if (ObjectUtil.isNotEmpty(sysCodeGenerateConfigParam.getCodeGenId())) { + queryWrapper.eq(SysCodeGenerateConfig::getCodeGenId, sysCodeGenerateConfigParam.getCodeGenId()); + } + } + return this.list(queryWrapper); + } + + @Override + public void add(SysCodeGenerateConfigParam sysCodeGenerateConfigParam) { + SysCodeGenerateConfig sysCodeGenerateConfig = new SysCodeGenerateConfig(); + BeanUtil.copyProperties(sysCodeGenerateConfigParam, sysCodeGenerateConfig); + this.save(sysCodeGenerateConfig); + } + + @Override + public void addList(List<InforMationColumnsResult> inforMationColumnsResultList, CodeGenerate codeGenerate) { + for (InforMationColumnsResult inforMationColumnsResult : inforMationColumnsResultList) { + SysCodeGenerateConfig sysCodeGenerateConfig = new SysCodeGenerateConfig(); + + String YesOrNo = YesOrNotEnum.Y.getCode(); + if (ObjectUtil.isNotNull(inforMationColumnsResult.getColumnKey()) + && inforMationColumnsResult.getColumnKey().equals(GenConstant.DB_TABLE_COM_KRY) || + TableFilteredFieldsEnum.contains(inforMationColumnsResult.getColumnName())) { + YesOrNo = YesOrNotEnum.N.getCode(); + } + if (TableFilteredFieldsEnum.contains(inforMationColumnsResult.getColumnName())) { + sysCodeGenerateConfig.setWhetherCommon(YesOrNotEnum.Y.getCode()); + } else { + sysCodeGenerateConfig.setWhetherCommon(YesOrNotEnum.N.getCode()); + } + + sysCodeGenerateConfig.setCodeGenId(codeGenerate.getId()); + sysCodeGenerateConfig.setColumnName(inforMationColumnsResult.getColumnName()); + sysCodeGenerateConfig.setColumnComment(inforMationColumnsResult.getColumnComment()); + sysCodeGenerateConfig.setJavaName(NamingConTool.UnderlineToHump(inforMationColumnsResult.getColumnName(), codeGenerate.getTablePrefix())); + sysCodeGenerateConfig.setJavaType(JavaSqlTool.sqlToJava(inforMationColumnsResult.getDataType())); + sysCodeGenerateConfig.setWhetherRetract(YesOrNotEnum.N.getCode()); + + sysCodeGenerateConfig.setWhetherRequired(YesOrNo); + sysCodeGenerateConfig.setQueryWhether(YesOrNo); + sysCodeGenerateConfig.setWhetherAddUpdate(YesOrNo); + sysCodeGenerateConfig.setWhetherTable(YesOrNo); + + sysCodeGenerateConfig.setColumnKey(inforMationColumnsResult.getColumnKey()); + + // 设置get set方法使用的名称 + String columnName = NamingConTool.UnderlineToHump(sysCodeGenerateConfig.getColumnName(),""); + sysCodeGenerateConfig.setColumnKeyName(columnName.substring(0,1).toUpperCase() + columnName.substring(1,columnName.length())); + + sysCodeGenerateConfig.setDataType(inforMationColumnsResult.getDataType()); + sysCodeGenerateConfig.setEffectType(JavaEffTool.javaToEff(sysCodeGenerateConfig.getJavaType())); + sysCodeGenerateConfig.setQueryType(QueryTypeEnum.eq.getCode()); + + this.save(sysCodeGenerateConfig); + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(SysCodeGenerateConfigParam sysCodeGenerateConfigParam) { + LambdaQueryWrapper<SysCodeGenerateConfig> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysCodeGenerateConfig::getCodeGenId, sysCodeGenerateConfigParam.getCodeGenId()); + this.remove(queryWrapper); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(List<SysCodeGenerateConfigParam> sysCodeGenerateConfigParamList) { + for (SysCodeGenerateConfigParam sysCodeGenerateConfigParam : sysCodeGenerateConfigParamList) { + SysCodeGenerateConfig sysCodeGenerateConfig = this.querySysCodeGenerateConfig(sysCodeGenerateConfigParam); + BeanUtil.copyProperties(sysCodeGenerateConfigParam, sysCodeGenerateConfig); + this.updateById(sysCodeGenerateConfig); + } + } + + @Override + public SysCodeGenerateConfig detail(SysCodeGenerateConfigParam sysCodeGenerateConfigParam) { + return this.querySysCodeGenerateConfig(sysCodeGenerateConfigParam); + } + + /** + * 获取代码生成详细配置 + * + * @author yubaoshan + * @date 2021-02-06 20:19:49 + */ + private SysCodeGenerateConfig querySysCodeGenerateConfig(SysCodeGenerateConfigParam sysCodeGenerateConfigParam) { + SysCodeGenerateConfig sysCodeGenerateConfig = this.getById(sysCodeGenerateConfigParam.getId()); + if (ObjectUtil.isNull(sysCodeGenerateConfig)) { + throw new ServiceException(SysCodeGenerateConfigExceptionEnum.NOT_EXIST); + } + return sysCodeGenerateConfig; + } +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/Controller.java.vm b/snowy-base/snowy-gen/src/main/resources/template/Controller.java.vm new file mode 100644 index 0000000..66f7109 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/Controller.java.vm @@ -0,0 +1,148 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package ${packageName}.${modularName}.${busName}.controller; + +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import ${packageName}.${modularName}.${busName}.param.${ClassName}Param; +import ${packageName}.${modularName}.${busName}.service.${ClassName}Service; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import javax.annotation.Resource; +import java.util.List; + +/** + * ${functionName}控制器 + * + * @author ${authorName} + * @date ${createDateString} + */ +@RestController +public class ${ClassName}Controller { + + @Resource + private ${ClassName}Service ${className}Service; + + /** + * 查询${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + @Permission + @GetMapping("/${className}/page") + @BusinessLog(title = "${functionName}_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(${ClassName}Param ${className}Param) { + return new SuccessResponseData(${className}Service.page(${className}Param)); + } + + /** + * 添加${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + @Permission + @PostMapping("/${className}/add") + @BusinessLog(title = "${functionName}_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(${ClassName}Param.add.class) ${ClassName}Param ${className}Param) { + ${className}Service.add(${className}Param); + return new SuccessResponseData(); + } + + /** + * 删除${functionName},可批量删除 + * + * @author ${authorName} + * @date ${createDateString} + */ + @Permission + @PostMapping("/${className}/delete") + @BusinessLog(title = "${functionName}_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(${ClassName}Param.delete.class) List<${ClassName}Param> ${className}ParamList) { + ${className}Service.delete(${className}ParamList); + return new SuccessResponseData(); + } + + /** + * 编辑${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + @Permission + @PostMapping("/${className}/edit") + @BusinessLog(title = "${functionName}_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(${ClassName}Param.edit.class) ${ClassName}Param ${className}Param) { + ${className}Service.edit(${className}Param); + return new SuccessResponseData(); + } + + /** + * 查看${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + @Permission + @GetMapping("/${className}/detail") + @BusinessLog(title = "${functionName}_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(${ClassName}Param.detail.class) ${ClassName}Param ${className}Param) { + return new SuccessResponseData(${className}Service.detail(${className}Param)); + } + + /** + * ${functionName}列表 + * + * @author ${authorName} + * @date ${createDateString} + */ + @Permission + @GetMapping("/${className}/list") + @BusinessLog(title = "${functionName}_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(${ClassName}Param ${className}Param) { + return new SuccessResponseData(${className}Service.list(${className}Param)); + } + + /** + * 导出系统用户 + * + * @author ${authorName} + * @date ${createDateString} + */ + @Permission + @GetMapping("/${className}/export") + @BusinessLog(title = "${functionName}_导出", opType = LogAnnotionOpTypeEnum.EXPORT) + public void export(${ClassName}Param ${className}Param) { + ${className}Service.export(${className}Param); + } + +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/ExceptionEnum.java.vm b/snowy-base/snowy-gen/src/main/resources/template/ExceptionEnum.java.vm new file mode 100644 index 0000000..217ba3d --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/ExceptionEnum.java.vm @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package ${packageName}.${modularName}.${busName}.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * ${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE) +public enum ${ClassName}ExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 数据不存在 + */ + NOT_EXIST(1, "此数据不存在"); + + private final Integer code; + + private final String message; + ${ClassName}ExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/Manage.js.vm b/snowy-base/snowy-gen/src/main/resources/template/Manage.js.vm new file mode 100644 index 0000000..8d676b5 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/Manage.js.vm @@ -0,0 +1,86 @@ +import { axios } from '@/utils/request' + +/** + * 查询${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ +export function ${className}Page (parameter) { + return axios({ + url: '/${className}/page', + method: 'get', + params: parameter + }) +} + +/** + * ${functionName}列表 + * + * @author ${authorName} + * @date ${createDateString} + */ +export function ${className}List (parameter) { + return axios({ + url: '/${className}/list', + method: 'get', + params: parameter + }) +} + +/** + * 添加${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ +export function ${className}Add (parameter) { + return axios({ + url: '/${className}/add', + method: 'post', + data: parameter + }) +} + +/** + * 编辑${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ +export function ${className}Edit (parameter) { + return axios({ + url: '/${className}/edit', + method: 'post', + data: parameter + }) +} + +/** + * 删除${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ +export function ${className}Delete (parameter) { + return axios({ + url: '/${className}/delete', + method: 'post', + data: parameter + }) +} + +/** + * 导出${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ +export function ${className}Export (parameter) { + return axios({ + url: '/${className}/export', + method: 'get', + params: parameter, + responseType: 'blob' + }) +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/Mapper.java.vm b/snowy-base/snowy-gen/src/main/resources/template/Mapper.java.vm new file mode 100644 index 0000000..c73f3ee --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/Mapper.java.vm @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package ${packageName}.${modularName}.${busName}.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import ${packageName}.${modularName}.${busName}.entity.${ClassName}; + +/** + * ${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ +public interface ${ClassName}Mapper extends BaseMapper<${ClassName}> { +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/Mapper.xml.vm b/snowy-base/snowy-gen/src/main/resources/template/Mapper.xml.vm new file mode 100644 index 0000000..57d67ca --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/Mapper.xml.vm @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="${packageName}.${modularName}.${busName}.mapper.${ClassName}Mapper"> + +</mapper> diff --git a/snowy-base/snowy-gen/src/main/resources/template/Param.java.vm b/snowy-base/snowy-gen/src/main/resources/template/Param.java.vm new file mode 100644 index 0000000..4539a82 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/Param.java.vm @@ -0,0 +1,74 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package ${packageName}.${modularName}.${busName}.param; + +import vip.xiaonuo.core.pojo.base.param.BaseParam; +import lombok.Data; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.NotBlank; +import java.util.*; +#foreach ($column in $tableField) +#if (${column.javaType} == 'BigDecimal') +import java.math.BigDecimal; +#end +#end + +/** +* ${functionName}参数类 + * + * @author ${authorName} + * @date ${createDateString} +*/ +@Data +public class ${ClassName}Param extends BaseParam { +#foreach ($column in $tableField) +#if (${column.columnKey} == "PRI") + + /** + * ${column.columnComment} + */ + @NotNull(message = "${column.columnComment}不能为空,请检查${column.javaName}参数", groups = {edit.class, delete.class, detail.class}) + private ${column.javaType} ${column.javaName}; +#elseif (${column.whetherCommon} == 'N') + + /** + * ${column.columnComment} + */ +#if (${column.whetherRequired} == "Y") +#if (${column.javaType} == "String") + @NotBlank(message = "${column.columnComment}不能为空,请检查${column.javaName}参数", groups = {add.class, edit.class}) +#else + @NotNull(message = "${column.columnComment}不能为空,请检查${column.javaName}参数", groups = {add.class, edit.class}) +#end +#end +#if(${column.javaType} == "Date") + private String ${column.javaName}; +#else + private ${column.javaType} ${column.javaName}; +#end +#end +#end + +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/Service.java.vm b/snowy-base/snowy-gen/src/main/resources/template/Service.java.vm new file mode 100644 index 0000000..cf902e1 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/Service.java.vm @@ -0,0 +1,97 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package ${packageName}.${modularName}.${busName}.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import ${packageName}.${modularName}.${busName}.entity.${ClassName}; +import ${packageName}.${modularName}.${busName}.param.${ClassName}Param; +import java.util.List; + +/** + * ${functionName}service接口 + * + * @author ${authorName} + * @date ${createDateString} + */ +public interface ${ClassName}Service extends IService<${ClassName}> { + + /** + * 查询${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + PageResult<${ClassName}> page(${ClassName}Param ${className}Param); + + /** + * ${functionName}列表 + * + * @author ${authorName} + * @date ${createDateString} + */ + List<${ClassName}> list(${ClassName}Param ${className}Param); + + /** + * 添加${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + void add(${ClassName}Param ${className}Param); + + /** + * 删除${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + void delete(List<${ClassName}Param> ${className}ParamList); + + /** + * 编辑${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + void edit(${ClassName}Param ${className}Param); + + /** + * 查看${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + ${ClassName} detail(${ClassName}Param ${className}Param); + + /** + * 导出${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + void export(${ClassName}Param ${className}Param); + +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/ServiceImpl.java.vm b/snowy-base/snowy-gen/src/main/resources/template/ServiceImpl.java.vm new file mode 100644 index 0000000..c6e6ab6 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/ServiceImpl.java.vm @@ -0,0 +1,136 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package ${packageName}.${modularName}.${busName}.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.PoiUtil; +import ${packageName}.${modularName}.${busName}.entity.${ClassName}; +import ${packageName}.${modularName}.${busName}.enums.${ClassName}ExceptionEnum; +import ${packageName}.${modularName}.${busName}.mapper.${ClassName}Mapper; +import ${packageName}.${modularName}.${busName}.param.${ClassName}Param; +import ${packageName}.${modularName}.${busName}.service.${ClassName}Service; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.annotation.Resource; +import java.util.List; + +/** + * ${functionName}service接口实现类 + * + * @author ${authorName} + * @date ${createDateString} + */ +@Service +public class ${ClassName}ServiceImpl extends ServiceImpl<${ClassName}Mapper, ${ClassName}> implements ${ClassName}Service { + + @Override + public PageResult<${ClassName}> page(${ClassName}Param ${className}Param) { + QueryWrapper<${ClassName}> queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(${className}Param)) { + +#foreach ($column in $tableField) +#if (${column.queryWhether} == "Y") + // 根据${column.columnComment} 查询 + if (ObjectUtil.isNotEmpty(${className}Param.get${column.columnKeyName}())) { + queryWrapper.lambda().${column.queryType}(${ClassName}::get${column.columnKeyName}, ${className}Param.get${column.columnKeyName}()); + } +#end +#end + } + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<${ClassName}> list(${ClassName}Param ${className}Param) { + return this.list(); + } + + @Override + public void add(${ClassName}Param ${className}Param) { + ${ClassName} ${className} = new ${ClassName}(); + BeanUtil.copyProperties(${className}Param, ${className}); + this.save(${className}); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(List<${ClassName}Param> ${className}ParamList) { + ${className}ParamList.forEach(${className}Param -> { +#foreach ($column in $tableField) +#if (${column.columnKey} == "PRI") + this.removeById(${className}Param.get${column.columnKeyName}()); +#end +#end + }); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(${ClassName}Param ${className}Param) { + ${ClassName} ${className} = this.query${ClassName}(${className}Param); + BeanUtil.copyProperties(${className}Param, ${className}); + this.updateById(${className}); + } + + @Override + public ${ClassName} detail(${ClassName}Param ${className}Param) { + return this.query${ClassName}(${className}Param); + } + + /** + * 获取${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ + private ${ClassName} query${ClassName}(${ClassName}Param ${className}Param) { +#foreach ($column in $tableField) +#if (${column.columnKey} == "PRI") + ${ClassName} ${className} = this.getById(${className}Param.get${column.columnKeyName}()); +#end +#end + if (ObjectUtil.isNull(${className})) { + throw new ServiceException(${ClassName}ExceptionEnum.NOT_EXIST); + } + return ${className}; + } + + @Override + public void export(${ClassName}Param ${className}Param) { + List<${ClassName}> list = this.list(${className}Param); + PoiUtil.exportExcelWithStream("Snowy${ClassName}.xls", ${ClassName}.class, list); + } + +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/XnMysql.sql.vm b/snowy-base/snowy-gen/src/main/resources/template/XnMysql.sql.vm new file mode 100644 index 0000000..fd7779c --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/XnMysql.sql.vm @@ -0,0 +1,38 @@ +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +#foreach ($id in $sqlMenuId) +#if($foreach.count == 1) +INSERT INTO `sys_menu` VALUES +('$id', '0', '${menuPids}', '${functionName}', '${busName}_index', '1', null, '/${className}', 'main/${busName}/index', null, '${appCode}', '1', 'Y', null, null, '1', '100', null, '0', null, null, null, null); +#set ($pid=$id) + +#elseif($foreach.count == 2) +INSERT INTO `sys_menu` VALUES +('$id', '$pid', '${menuPids}[$pid],', '${functionName}查询', '${busName}_index_page', '2', null, null, null, '${className}:page', '${appCode}', '0', 'Y', null, null, '1', '100', null, '0', null, null, null, null); + +#elseif($foreach.count == 3) +INSERT INTO `sys_menu` VALUES +('$id', '$pid', '${menuPids}[$pid],', '${functionName}新增', '${busName}_index_add', '2', null, null, null, '${className}:add', '${appCode}', '0', 'Y', null, null, '1', '100', null, '0', null, null, null, null); + +#elseif($foreach.count == 4) +INSERT INTO `sys_menu` VALUES +('$id', '$pid', '${menuPids}[$pid],', '${functionName}编辑', '${busName}_index_edit', '2', null, null, null, '${className}:edit', '${appCode}', '0', 'Y', null, null, '1', '100', null, '0', null, null, null, null); + +#elseif($foreach.count == 5) +INSERT INTO `sys_menu` VALUES +('$id', '$pid', '${menuPids}[$pid],', '${functionName}删除', '${busName}_index_delete', '2', null, null, null, '${className}:delete', '${appCode}', '0', 'Y', null, null, '1', '100', null, '0', null, null, null, null); + +#elseif($foreach.count == 6) +INSERT INTO `sys_menu` VALUES +('$id', '$pid', '${menuPids}[$pid],', '${functionName}查看', '${busName}_index_detail', '2', null, null, null, '${className}:detail', '${appCode}', '0', 'Y', null, null, '1', '100', null, '0', null, null, null, null); + +#elseif($foreach.count == 7) +INSERT INTO `sys_menu` VALUES +('$id', '$pid', '${menuPids}[$pid],', '${functionName}列表', '${busName}_index_list', '2', null, null, null, '${className}:list', '${appCode}', '0', 'Y', null, null, '1', '100', null, '0', null, null, null, null); + +#elseif($foreach.count == 8) +INSERT INTO `sys_menu` VALUES +('$id', '$pid', '${menuPids}[$pid],', '${functionName}导出', '${busName}_index_export', '2', null, null, null, '${className}:export', '${appCode}', '0', 'Y', null, null, '1', '100', null, '0', null, null, null, null); +#end +#end \ No newline at end of file diff --git a/snowy-base/snowy-gen/src/main/resources/template/XnOracle.sql.vm b/snowy-base/snowy-gen/src/main/resources/template/XnOracle.sql.vm new file mode 100644 index 0000000..8f02357 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/XnOracle.sql.vm @@ -0,0 +1,38 @@ +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +#foreach ($id in $sqlMenuId) +#if($foreach.count == 1) +INSERT INTO "sys_menu" VALUES +("$id", "0", "${menuPids}", "${functionName}", "${busName}_index", "1", null, "/${className}", "main/${busName}/index", null, "${appCode}", "1", "Y", null, null, "1", "100", null, "0", null, null, null, null); +#set ($pid=$id) + +#elseif($foreach.count == 2) +INSERT INTO `sys_menu` VALUES +("$id", "$pid", "${menuPids}[$pid],", "${functionName}查询", "${busName}_index_page", "2", null, null, null, "${className}:page", "${appCode}", "0", "Y", null, null, "1", "100", null, "0", null, null, null, null); + +#elseif($foreach.count == 3) +INSERT INTO `sys_menu` VALUES +("$id", "$pid", "${menuPids}[$pid],", "${functionName}新增", "${busName}_index_add", "2", null, null, null, "${className}:add", "${appCode}", "0", "Y", null, null, "1", "100", null, "0", null, null, null, null); + +#elseif($foreach.count == 4) +INSERT INTO `sys_menu` VALUES +("$id", "$pid", "${menuPids}[$pid],", "${functionName}编辑", "${busName}_index_edit", "2", null, null, null, "${className}:edit", "${appCode}", "0", "Y", null, null, "1", "100", null, "0", null, null, null, null); + +#elseif($foreach.count == 5) +INSERT INTO `sys_menu` VALUES +("$id", "$pid", "${menuPids}[$pid],", "${functionName}删除", "${busName}_index_delete", "2", null, null, null, "${className}:delete", "${appCode}", "0", "Y", null, null, "1", "100", null, "0", null, null, null, null); + +#elseif($foreach.count == 6) +INSERT INTO `sys_menu` VALUES +("$id", "$pid", "${menuPids}[$pid],", "${functionName}查看", "${busName}_index_detail", "2", null, null, null, "${className}:detail", "${appCode}", "0", "Y", null, null, "1", "100", null, "0", null, null, null, null); + +#elseif($foreach.count == 7) +INSERT INTO "sys_menu" VALUES +("$id", "$pid", "${menuPids}[$pid],", "${functionName}列表", "${busName}_index_list", "2", null, null, null, "${className}:list", "${appCode}", "0", "Y", null, null, "1", "100", null, "0", null, null, null, null); + +#elseif($foreach.count == 7) +INSERT INTO "sys_menu" VALUES +("$id", "$pid", "${menuPids}[$pid],", "${functionName}导出", "${busName}_index_export", "2", null, null, null, "${className}:export", "${appCode}", "0", "Y", null, null, "1", "100", null, "0", null, null, null, null); +#end +#end diff --git a/snowy-base/snowy-gen/src/main/resources/template/addForm.vue.vm b/snowy-base/snowy-gen/src/main/resources/template/addForm.vue.vm new file mode 100644 index 0000000..bed6ce0 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/addForm.vue.vm @@ -0,0 +1,216 @@ +<template> + <a-modal + title="新增${functionName}" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> +#foreach ($column in $tableField) +#if (${column.columnKey} != "PRI") +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox') + <a-form-item + label="${column.columnComment}" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > +#else + <a-form-item + label="${column.columnComment}" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > +#end +#if (${column.effectType} == "user") +#if (${column.whetherRequired} == "Y") + <user-select placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" /> +#else + <user-select placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}']" /> +#end +#end +#if (${column.effectType} == "depart") +#if (${column.whetherRequired} == "Y") + <depart-select placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" /> +#else + <depart-select placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}']" /> +#end +#end +#if (${column.effectType} == "input") +#if (${column.whetherRequired} == "Y") + <a-input placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" /> +#else + <a-input placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}']" /> +#end +#end +#if (${column.effectType} == "textarea") +#if (${column.whetherRequired} == "Y") + <a-textarea placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" :auto-size="{ minRows: 3, maxRows: 6 }"/> +#else + <a-textarea placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}']" :auto-size="{ minRows: 3, maxRows: 6 }"/> +#end +#end +#if (${column.effectType} == "inputnumber") +#if (${column.whetherRequired} == "Y") + <a-input-number placeholder="请输入${column.columnComment}" style="width: 100%" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" /> +#else + <a-input-number placeholder="请输入${column.columnComment}" style="width: 100%" v-decorator="['${column.javaName}']" /> +#end +#end +#if (${column.effectType} == "select") +#if (${column.whetherRequired} == "Y") + <a-select style="width: 100%" placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{ required: true, message: '请选择${column.columnComment}!' }]}]"> + <a-select-option v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> +#else + <a-select style="width: 100%" placeholder="请选择${column.columnComment}"> + <a-select-option v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> +#end +#end +#if (${column.effectType} == "radio") +#if (${column.whetherRequired} == "Y") + <a-radio-group placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}',{rules: [{ required: true, message: '请选择${column.columnComment}!' }]}]" > + <a-radio v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-radio> + </a-radio-group> +#else + <a-radio-group placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}']" > + <a-radio v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-radio> + </a-radio-group> +#end +#end +#if (${column.effectType} == "checkbox") +#if (${column.whetherRequired} == "Y") + <a-checkbox-group placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}',{rules: [{ required: true, message: '请选择${column.columnComment}!' }]}]" > + <a-checkbox v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-checkbox> + </a-checkbox-group> +#else + <a-checkbox-group placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}']" > + <a-checkbox v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-checkbox> + </a-checkbox-group> +#end +#end +#if (${column.effectType} == "datepicker") +#if (${column.whetherRequired} == "Y") + <a-date-picker style="width: 100%" placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}',{rules: [{ required: true, message: '请选择${column.columnComment}!' }]}]" @change="${column.javaName}OnChange"/> +#else + <a-date-picker style="width: 100%" placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}']" @change="${column.javaName}OnChange"/> +#end +#end + </a-form-item> +#end +#end +#end + </a-form> + </a-spin> + </a-modal> +</template> + +<script> + import { ${className}Add } from '@/api/modular/main/${busName}/${className}Manage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.dictTypeCode}) +#if(${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox') + ${column.javaName}Data: [], +#end +#end +#if (${column.effectType} == "datepicker") + ${column.javaName}DateString: '', +#end +#end +#end + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { + // 初始化方法 + add (record) { + this.visible = true +#foreach ($column in $tableField) +#if (${column.dictTypeCode}) +#if(${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox') + const ${column.javaName}Option = this.$options + this.${column.javaName}Data = ${column.javaName}Option.filters['dictData']('${column.dictTypeCode}') +#end +#end +#end + }, + /** + * 提交表单 + */ + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + for (const key in values) { + if (typeof (values[key]) === 'object' && values[key] != null) { + values[key] = JSON.stringify(values[key]) + } + } +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "datepicker") + values.${column.javaName} = this.${column.javaName}DateString || null +#end +#end +#end + ${className}Add(values).then((res) => { + if (res.success) { + this.$message.success('新增成功') + this.confirmLoading = false + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('新增失败')// + res.message + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "datepicker") + ${column.javaName}OnChange(date, dateString) { + this.${column.javaName}DateString = dateString + }, +#end +#end +#end + handleCancel () { +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "datepicker") + this.${column.javaName}DateString ='' + this.form.getFieldDecorator('${column.javaName}', { initialValue: null }) +#end +#end +#end + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/snowy-base/snowy-gen/src/main/resources/template/editForm.vue.vm b/snowy-base/snowy-gen/src/main/resources/template/editForm.vue.vm new file mode 100644 index 0000000..0a5ff50 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/editForm.vue.vm @@ -0,0 +1,274 @@ +<template> + <a-modal + title="编辑${functionName}" + :width="900" + :visible="visible" + :confirmLoading="confirmLoading" + @ok="handleSubmit" + @cancel="handleCancel" + > + <a-spin :spinning="confirmLoading"> + <a-form :form="form"> +#foreach ($column in $tableField) +#if(${column.columnKey} == "PRI") + <a-form-item v-show="false"><a-input v-decorator="['${column.javaName}']" /></a-form-item> +#else +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox') + <a-form-item + label="${column.columnComment}" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + > +#else + <a-form-item + label="${column.columnComment}" + :labelCol="labelCol" + :wrapperCol="wrapperCol" + has-feedback + > +#end +#if (${column.effectType} == "user") +#if (${column.whetherRequired} == "Y") + <user-select placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" /> +#else + <user-select placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}']" /> +#end +#end +#if (${column.effectType} == "depart") +#if (${column.whetherRequired} == "Y") + <depart-select placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" /> +#else + <depart-select placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}']" /> +#end +#end +#if (${column.effectType} == "input") +#if (${column.whetherRequired} == "Y") + <a-input placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" /> +#else + <a-input placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}']" /> +#end +#end +#if (${column.effectType} == "textarea") +#if (${column.whetherRequired} == "Y") + <a-textarea placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" :auto-size="{ minRows: 3, maxRows: 6 }"/> +#else + <a-textarea placeholder="请输入${column.columnComment}" v-decorator="['${column.javaName}']" :auto-size="{ minRows: 3, maxRows: 6 }"/> +#end +#end +#if (${column.effectType} == "inputnumber") +#if (${column.whetherRequired} == "Y") + <a-input-number placeholder="请输入${column.columnComment}" style="width: 100%" v-decorator="['${column.javaName}', {rules: [{required: true, message: '请输入${column.columnComment}!'}]}]" /> +#else + <a-input-number placeholder="请输入${column.columnComment}" style="width: 100%" v-decorator="['${column.javaName}']" /> +#end +#end +#if (${column.effectType} == "select") +#if (${column.whetherRequired} == "Y") + <a-select style="width: 100%" placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}', {rules: [{ required: true, message: '请选择${column.columnComment}!' }]}]"> + <a-select-option v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> +#else + <a-select style="width: 100%" placeholder="请选择${column.columnComment}"> + <a-select-option v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> +#end +#end +#if (${column.effectType} == "radio") +#if (${column.whetherRequired} == "Y") + <a-radio-group placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}',{rules: [{ required: true, message: '请选择${column.columnComment}!' }]}]" > + <a-radio v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-radio> + </a-radio-group> +#else + <a-radio-group placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}']" > + <a-radio v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-radio> + </a-radio-group> +#end +#end +#if (${column.effectType} == "checkbox") +#if (${column.whetherRequired} == "Y") + <a-checkbox-group placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}',{rules: [{ required: true, message: '请选择${column.columnComment}!' }]}]" > + <a-checkbox v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-checkbox> + </a-checkbox-group> +#else + <a-checkbox-group placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}']" > + <a-checkbox v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-checkbox> + </a-checkbox-group> +#end +#end +#if (${column.effectType} == "datepicker") +#if (${column.whetherRequired} == "Y") + <a-date-picker style="width: 100%" placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}',{rules: [{ required: true, message: '请选择${column.columnComment}!' }]}]" @change="${column.javaName}OnChange"/> +#else + <a-date-picker style="width: 100%" placeholder="请选择${column.columnComment}" v-decorator="['${column.javaName}']" @change="${column.javaName}OnChange"/> +#end +#end + </a-form-item> +#end +#end +#end + </a-form> + </a-spin> + </a-modal> +</template> + +<script> +#set ($editData = 0) +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "datepicker") +#set ($DateQuery="Y") +#end +#if(${column.columnKey} != "PRI") +#if(${column.effectType} != "datepicker") +#set ($editData = $editData+1) +#end +#end +#end +#end +#if($DateQuery == "Y") + import moment from 'moment' +#end + import { ${className}Edit } from '@/api/modular/main/${busName}/${className}Manage' + export default { + data () { + return { + labelCol: { + xs: { span: 24 }, + sm: { span: 5 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 15 } + }, +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.dictTypeCode}) +#if(${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox') + ${column.javaName}Data: [], +#end +#end +#if (${column.effectType} == "datepicker") + ${column.javaName}DateString: '', +#end +#end +#end + visible: false, + confirmLoading: false, + form: this.$form.createForm(this) + } + }, + methods: { +#if($DateQuery == "Y") + moment, +#end + // 初始化方法 + edit (record) { + this.visible = true +#foreach ($column in $tableField) +#if (${column.dictTypeCode}) +#if(${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox') + const ${column.javaName}Option = this.$options + this.${column.javaName}Data = ${column.javaName}Option.filters['dictData']('${column.dictTypeCode}') +#end +#end +#end + setTimeout(() => { + this.form.setFieldsValue( + { +#set ($editDataColumn = 0) +#foreach ($column in $tableField) +#if(${column.columnKey} == "PRI") + ${column.javaName}: record.${column.javaName}#if($foreach.hasNext), +#end +#else +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "checkbox") +#set ($editDataColumn = $editDataColumn+1) + ${column.javaName}: JSON.parse(record.${column.javaName})#if($foreach.hasNext && ($editDataColumn != $editData)), +#else + +#end +#elseif (${column.effectType} != "datepicker") +#set ($editDataColumn = $editDataColumn+1) + ${column.javaName}: record.${column.javaName}#if($foreach.hasNext && ($editDataColumn != $editData)), +#else + +#end +#end +#end +#end +#end + } + ) + }, 100) +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "datepicker") + // 时间单独处理 + if (record.${column.javaName}) { + this.form.getFieldDecorator('${column.javaName}', { initialValue: moment(record.${column.javaName}, 'YYYY-MM-DD') }) + this.${column.javaName}DateString = moment(record.${column.javaName}).format('YYYY-MM-DD') + } +#end +#end +#end + }, + handleSubmit () { + const { form: { validateFields } } = this + this.confirmLoading = true + validateFields((errors, values) => { + if (!errors) { + for (const key in values) { + if (typeof (values[key]) === 'object' && values[key] != null) { + values[key] = JSON.stringify(values[key]) + } + } +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "datepicker") + values.${column.javaName} = this.${column.javaName}DateString || null +#end +#end +#end + ${className}Edit(values).then((res) => { + if (res.success) { + this.$message.success('编辑成功') + this.confirmLoading = false + this.$emit('ok', values) + this.handleCancel() + } else { + this.$message.error('编辑失败')// + res.message + } + }).finally((res) => { + this.confirmLoading = false + }) + } else { + this.confirmLoading = false + } + }) + }, +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "datepicker") + ${column.javaName}OnChange(date, dateString) { + this.${column.javaName}DateString = dateString + }, +#end +#end +#end + handleCancel () { +#foreach ($column in $tableField) +#if (${column.whetherAddUpdate} == "Y") +#if (${column.effectType} == "datepicker") + this.${column.javaName}DateString ='' + this.form.getFieldDecorator('${column.javaName}', { initialValue: null }) +#end +#end +#end + this.form.resetFields() + this.visible = false + } + } + } +</script> diff --git a/snowy-base/snowy-gen/src/main/resources/template/entity.java.vm b/snowy-base/snowy-gen/src/main/resources/template/entity.java.vm new file mode 100644 index 0000000..b99d18a --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/entity.java.vm @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package ${packageName}.${modularName}.${busName}.entity; + +import com.baomidou.mybatisplus.annotation.*; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.*; +import cn.afterturn.easypoi.excel.annotation.Excel; +#foreach ($column in $tableField) +#if (${column.javaType} == 'BigDecimal') +import java.math.BigDecimal; +#end +#end + +/** + * ${functionName} + * + * @author ${authorName} + * @date ${createDateString} + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("${tableName}") +public class ${ClassName} extends BaseEntity { + +#foreach ($column in $tableField) +#if (${column.columnKey} == "PRI") + /** + * ${column.columnComment} + */ + @TableId(type = IdType.ASSIGN_ID) + private ${column.javaType} ${column.javaName}; +#elseif (${column.whetherCommon} == 'N') + + /** + * ${column.columnComment} + */ +#if(${column.javaType} == "Date") + @Excel(name = "${column.columnComment}", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd", width = 20) +#end + @Excel(name = "${column.columnComment}") + private ${column.javaType} ${column.javaName}; +#end +#end + +} diff --git a/snowy-base/snowy-gen/src/main/resources/template/index.vue.vm b/snowy-base/snowy-gen/src/main/resources/template/index.vue.vm new file mode 100644 index 0000000..a030b89 --- /dev/null +++ b/snowy-base/snowy-gen/src/main/resources/template/index.vue.vm @@ -0,0 +1,379 @@ +<template> + <div> + <a-card :bordered="false" :bodyStyle="tstyle"> +#if($queryWhetherList.size() > 0) + <div class="table-page-search-wrapper" v-if="hasPerm('${className}:page')"> + <a-form layout="inline"> + <a-row :gutter="48"> +#foreach ($column in $queryWhetherList) +#if($foreach.count == 3) + <template v-if="advanced"> +#end +#if(${column.effectType} == 'input' || ${column.effectType} == 'textarea') +#if($foreach.count >= 3) + <a-col :md="8" :sm="24"> + <a-form-item label="${column.columnComment}"> + <a-input v-model="queryParam.${column.javaName}" allow-clear placeholder="请输入${column.columnComment}"/> + </a-form-item> + </a-col> +#else + <a-col :md="8" :sm="24"> + <a-form-item label="${column.columnComment}"> + <a-input v-model="queryParam.${column.javaName}" allow-clear placeholder="请输入${column.columnComment}"/> + </a-form-item> + </a-col> +#end +#elseif(${column.effectType} == 'inputnumber') +#if($foreach.count >= 3) + <a-col :md="8" :sm="24"> + <a-form-item label="${column.columnComment}"> + <a-input-number v-model="queryParam.${column.javaName}" style="width: 100%" allow-clear placeholder="请输入${column.columnComment}"/> + </a-form-item> + </a-col> +#else + <a-col :md="8" :sm="24"> + <a-form-item label="${column.columnComment}"> + <a-input-number v-model="queryParam.${column.javaName}" style="width: 100%" allow-clear placeholder="请输入${column.columnComment}"/> + </a-form-item> + </a-col> +#end +#elseif(${column.effectType} == 'select' || ${column.effectType} == 'radio') +#if($foreach.count >= 3) + <a-col :md="8" :sm="24"> + <a-form-item label="${column.columnComment}"> + <a-select style="width: 100%" v-model="queryParam.${column.javaName}" placeholder="请选择${column.columnComment}"> + <a-select-option v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> +#else + <a-col :md="8" :sm="24"> + <a-form-item label="${column.columnComment}"> + <a-select style="width: 100%" v-model="queryParam.${column.javaName}" placeholder="请选择${column.columnComment}"> + <a-select-option v-for="(item,index) in ${column.javaName}Data" :key="index" :value="item.code">{{ item.name }}</a-select-option> + </a-select> + </a-form-item> + </a-col> +#end +#elseif(${column.effectType} == 'datepicker') +#if($foreach.count >= 3) + <a-col :md="8" :sm="24"> + <a-form-item label="${column.columnComment}"> + <a-date-picker style="width: 100%" placeholder="请选择${column.columnComment}" v-model="queryParam.${column.javaName}Date" @change="onChange${column.javaName}"/> + </a-form-item> + </a-col> +#else + <a-col :md="8" :sm="24"> + <a-form-item label="${column.columnComment}"> + <a-date-picker style="width: 100%" placeholder="请选择${column.columnComment}" v-model="queryParam.${column.javaName}Date" @change="onChange${column.javaName}"/> + </a-form-item> + </a-col> +#end +#else +#end +#end +#if($queryWhetherList.size() > 2) + </template> +#end +#if($queryWhetherList.size() > 2) + <a-col :md="8" :sm="24" > + <span class="table-page-search-submitButtons"> + <a-button type="primary" @click="$refs.table.refresh(true)" >查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + <a @click="toggleAdvanced" style="margin-left: 8px"> + {{ advanced ? '收起' : '展开' }} + <a-icon :type="advanced ? 'up' : 'down'"/> + </a> + </span> + </a-col> +#else + <a-col :md="8" :sm="24"> + <span class="table-page-search-submitButtons"> + <a-button type="primary" @click="$refs.table.refresh(true)">查询</a-button> + <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button> + </span> + </a-col> +#end + </a-row> + </a-form> + </div> +#end + </a-card> + <a-card :bordered="false"> + <s-table + ref="table" + :columns="columns" + :data="loadData" + :alert="options.alert" +#foreach ($column in $tableField) +#if (${column.columnKey} == "PRI") + :rowKey="(record) => record.${column.javaName}" +#end +#end + :rowSelection="options.rowSelection" + > + <template class="table-operator" slot="operator" v-if="hasPerm('${className}:add')" > + <a-button type="primary" v-if="hasPerm('${className}:add')" icon="plus" @click="$refs.addForm.add()">新增${functionName}</a-button> + <a-button type="danger" :disabled="selectedRowKeys.length < 1" v-if="hasPerm('${className}:delete')" @click="batchDelete"><a-icon type="delete"/>批量删除</a-button> + <x-down + v-if="hasPerm('${className}:export')" + ref="batchExport" + @batchExport="batchExport" + /> + </template> +#foreach ($column in $tableField) +#if(${column.whetherTable} == "Y") +#if(${column.whetherRetract} == "Y" || ${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox') + <span slot="${column.javaName}ScopedSlots" slot-scope="text"> +#if(${column.whetherRetract} == "Y" && (${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox')) + <ellipsis :length="10" tooltip>{{ '${column.dictTypeCode}' | dictType(text) }}</ellipsis> +#else +#if(${column.whetherRetract} == "Y") + <ellipsis :length="10" tooltip>{{ text }}</ellipsis> +#else +#if(${column.effectType} == 'checkbox') + <a-tag v-for="textData in JSON.parse(text)" :key="textData.code" color="green">{{ 'sex' | dictType(textData) }}</a-tag> +#else + {{ '${column.dictTypeCode}' | dictType(text) }} +#end +#end +#end + </span> +#end +#end +#end + <span slot="action" slot-scope="text, record"> + <a v-if="hasPerm('${className}:edit')" @click="$refs.editForm.edit(record)">编辑</a> + <a-divider type="vertical" v-if="hasPerm('${className}:edit') & hasPerm('${className}:delete')"/> + <a-popconfirm v-if="hasPerm('${className}:delete')" placement="topRight" title="确认删除?" @confirm="() => singleDelete(record)"> + <a>删除</a> + </a-popconfirm> + </span> + </s-table> + <add-form ref="addForm" @ok="handleOk" /> + <edit-form ref="editForm" @ok="handleOk" /> + </a-card> + </div> +</template> +<script> +#set ($columnData = 0) +#foreach ($column in $tableField) +#if(${column.whetherTable} == "Y") +#if(${column.whetherRetract} == "Y") +#set ($Ellipsis="Y") +#end +#set ($columnData = $columnData+1) +#end +#if(${column.queryWhether} == 'Y') +#if(${column.effectType} == 'datepicker') +#set ($DateQuery="Y") +#end +#end +#end +#if($Ellipsis == "Y") + import { STable, XDown, Ellipsis } from '@/components' +#else + import { STable, XDown } from '@/components' +#end +#if($DateQuery == "Y") + import moment from 'moment' +#end + import { ${className}Page, ${className}Delete, ${className}Export } from '@/api/modular/main/${busName}/${className}Manage' + import addForm from './addForm.vue' + import editForm from './editForm.vue' + export default { + components: { +#if($Ellipsis == "Y") + Ellipsis, +#end + STable, + addForm, + editForm, + XDown + }, + data () { + return { +#if($queryWhetherList.size() > 2) + // 高级搜索 展开/关闭 + advanced: false, +#end + // 查询参数 + queryParam: {}, + // 表头 + columns: [ +#set ($columnDataColumns = 0) +#foreach ($column in $tableField) +#if (${column.whetherTable} == "Y") +#set ($columnDataColumns = $columnDataColumns+1) + { + title: '${column.columnComment}', + align: 'center', +#if(${column.whetherRetract} == "Y" || ${column.effectType} == 'select' || ${column.effectType} == 'radio' || ${column.effectType} == 'checkbox') + dataIndex: '${column.javaName}', + scopedSlots: { customRender: '${column.javaName}ScopedSlots' } +#else + dataIndex: '${column.javaName}' +#end + }#if($foreach.hasNext && ($columnDataColumns != $columnData)), +#else + +#end +#end +#end + ], + tstyle: { 'padding-bottom': '0px', 'margin-bottom': '10px' }, + // 加载数据方法 必须为 Promise 对象 + loadData: parameter => { +#if($DateQuery == "Y") + return ${className}Page(Object.assign(parameter, this.switchingDate())).then((res) => { +#else + return ${className}Page(Object.assign(parameter, this.queryParam)).then((res) => { +#end + return res.data + }) + }, +#foreach ($column in $tableField) +#if(${column.queryWhether} == 'Y') +#if(${column.effectType} == 'select' || ${column.effectType} == 'radio') +#if (${column.dictTypeCode}) + ${column.javaName}Data: [], +#end +#end +#end +#end + selectedRowKeys: [], + selectedRows: [], + options: { + alert: { show: true, clear: () => { this.selectedRowKeys = [] } }, + rowSelection: { + selectedRowKeys: this.selectedRowKeys, + onChange: this.onSelectChange + } + } + } + }, + created () { + if (this.hasPerm('${className}:edit') || this.hasPerm('${className}:delete')) { + this.columns.push({ + title: '操作', + width: '150px', + dataIndex: 'action', + scopedSlots: { customRender: 'action' } + }) + } +#foreach ($column in $tableField) +#if(${column.queryWhether} == 'Y') +#if(${column.effectType} == 'select' || ${column.effectType} == 'radio') +#if (${column.dictTypeCode}) + const ${column.javaName}Option = this.$options + this.${column.javaName}Data = ${column.javaName}Option.filters['dictData']('${column.dictTypeCode}') +#end +#end +#end +#end + }, + methods: { +#if($DateQuery == "Y") + moment, + /** + * 查询参数组装 + */ + switchingDate () { +#foreach ($column in $tableField) +#if(${column.queryWhether} == 'Y') +#if(${column.effectType} == 'datepicker') + const queryParam${column.javaName} = this.queryParam.${column.javaName}Date + if (queryParam${column.javaName} != null) { + this.queryParam.${column.javaName} = moment(queryParam${column.javaName}).format('YYYY-MM-DD') + if (queryParam${column.javaName}.length < 1) { + delete this.queryParam.${column.javaName} + } + } +#end +#end +#end + const obj = JSON.parse(JSON.stringify(this.queryParam)) + return obj + }, +#end + /** + * 单个删除 + */ + singleDelete (record) { +#foreach ($column in $tableField) +#if (${column.columnKey} == "PRI") + const param = [{ '${column.javaName}': record.${column.javaName} }] +#end +#end + this.${className}Delete(param) + }, + /** + * 批量删除 + */ + batchDelete () { + const paramIds = this.selectedRowKeys.map((d) => { +#foreach ($column in $tableField) +#if (${column.columnKey} == "PRI") + return { '${column.javaName}': d } +#end +#end + }) + this.${className}Delete(paramIds) + }, + ${className}Delete (record) { + ${className}Delete(record).then((res) => { + if (res.success) { + this.$message.success('删除成功') + this.$refs.table.clearRefreshSelected() + } else { + this.$message.error('删除失败') // + res.message + } + }) + }, +#if($queryWhetherList.size() > 2) + toggleAdvanced () { + this.advanced = !this.advanced + }, +#end +#foreach ($column in $queryWhetherList) +#if(${column.queryWhether} == 'Y') +#if(${column.effectType} == 'datepicker') + onChange${column.javaName}(date, dateString) { + this.${column.javaName}DateString = dateString + }, +#end +#end +#end + /** + * 批量导出 + */ + batchExport () { + const paramIds = this.selectedRowKeys.map((d) => { +#foreach ($column in $tableField) +#if (${column.columnKey} == "PRI") + return { '${column.javaName}': d } +#end +#end + }) + ${className}Export(paramIds).then((res) => { + this.$refs.batchExport.downloadfile(res) + }) + }, + handleOk () { + this.$refs.table.refresh() + }, + onSelectChange (selectedRowKeys, selectedRows) { + this.selectedRowKeys = selectedRowKeys + this.selectedRows = selectedRows + } + } + } +</script> +<style lang="less"> + .table-operator { + margin-bottom: 18px; + } + button { + margin-right: 8px; + } +</style> diff --git a/snowy-base/snowy-system/README.md b/snowy-base/snowy-system/README.md new file mode 100644 index 0000000..62e58d9 --- /dev/null +++ b/snowy-base/snowy-system/README.md @@ -0,0 +1 @@ +** 此模块可以尽量不要动,升级的时候只要将snowy-base的模块覆盖即可 ** diff --git a/snowy-base/snowy-system/pom.xml b/snowy-base/snowy-system/pom.xml new file mode 100644 index 0000000..d796ef2 --- /dev/null +++ b/snowy-base/snowy-system/pom.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy-base</artifactId> + <version>1.6.0</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>snowy-system</artifactId> + + <packaging>jar</packaging> + + <dependencies> + + <!-- core模块 --> + <dependency> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy-core</artifactId> + <version>1.6.0</version> + </dependency> + + <!-- test --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.junit.vintage</groupId> + <artifactId>junit-vintage-engine</artifactId> + </exclusion> + <exclusion> + <groupId>com.vaadin.external.google</groupId> + <artifactId>android-json</artifactId> + </exclusion> + </exclusions> + </dependency> + + <!-- processor --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + </dependency> + + <!-- jwt token --> + <dependency> + <groupId>io.jsonwebtoken</groupId> + <artifactId>jjwt</artifactId> + </dependency> + + <!--swagger接口文档--> + <dependency> + <groupId>com.github.xiaoymin</groupId> + <artifactId>knife4j-spring-boot-starter</artifactId> + </dependency> + + <!--验证码--> + <dependency> + <groupId>com.anji-plus</groupId> + <artifactId>spring-boot-starter-captcha</artifactId> + <version>1.2.8</version> + </dependency> + + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + </build> +</project> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/AopConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/AopConfig.java new file mode 100644 index 0000000..d20bc1d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/AopConfig.java @@ -0,0 +1,86 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import vip.xiaonuo.sys.core.aop.BusinessLogAop; +import vip.xiaonuo.sys.core.aop.DataScopeAop; +import vip.xiaonuo.sys.core.aop.PermissionAop; +import vip.xiaonuo.sys.core.aop.WrapperAop; + +/** + * 切面配置 + * + * @author xuyuxiang + * @date 2020/3/18 11:25 + */ +@Configuration +public class AopConfig { + + /** + * 日志切面 + * + * @author xuyuxiang + * @date 2020/3/20 14:10 + */ + @Bean + public BusinessLogAop businessLogAop() { + return new BusinessLogAop(); + } + + /** + * 权限切面 + * + * @author xuyuxiang + * @date 2020/3/23 17:36 + */ + @Bean + public PermissionAop permissionAop() { + return new PermissionAop(); + } + + /** + * 数据范围切面 + * + * @author xuyuxiang + * @date 2020/4/6 13:47 + */ + @Bean + public DataScopeAop dataScopeAop() { + return new DataScopeAop(); + } + + /** + * 结果包装的aop + * + * @author xuyuxiang + * @date 2020/7/24 22:18 + */ + @Bean + public WrapperAop wrapperAop() { + return new WrapperAop(); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/CacheConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/CacheConfig.java new file mode 100644 index 0000000..98f8098 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/CacheConfig.java @@ -0,0 +1,106 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import cn.hutool.cache.CacheUtil; +import cn.hutool.cache.impl.TimedCache; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.sys.core.cache.MappingCache; +import vip.xiaonuo.sys.core.cache.ResourceCache; +import vip.xiaonuo.sys.core.cache.UserCache; +import vip.xiaonuo.sys.core.redis.FastJson2JsonRedisSerializer; + +import java.util.Map; + +/** + * 缓存的配置,默认使用基于内存的缓存,如果分布式部署请更换为redis + * + * @author xuyuxiang + * @date 2020/7/9 11:43 + */ +@Configuration +public class CacheConfig { + + /** + * url资源的缓存,默认不过期 + * + * @author yubaoshan + * @date 2020/7/9 11:44 + */ + @Bean + public ResourceCache resourceCache() { + return new ResourceCache(); + } + + /** + * 登录用户的缓存,redis缓存 + * + * @author yubaoshan + * @date 2020/7/9 11:44 + */ + @Bean + public UserCache userCache(RedisTemplate<String, SysLoginUser> redisTemplate) { + return new UserCache(redisTemplate); + } + + /** + * redis缓存类 + * + * @author yubaoshan + * @date 2020/4/19 17:53 + */ + @Bean + public RedisTemplate<String, SysLoginUser> redisTemplate(RedisConnectionFactory factory) { + RedisTemplate<String, SysLoginUser> userRedisTemplate = new RedisTemplate<>(); + userRedisTemplate.setConnectionFactory(factory); + userRedisTemplate.setKeySerializer(new StringRedisSerializer()); + userRedisTemplate.setValueSerializer(new FastJson2JsonRedisSerializer<>(SysLoginUser.class)); + userRedisTemplate.afterPropertiesSet(); + return userRedisTemplate; + } + + /** + * mapping映射缓存 + * + * @author xuyuxiang + * @date 2020/7/24 13:55 + */ + @Bean + public MappingCache mappingCache() { + TimedCache<String, Map<String, Object>> timedCache = + CacheUtil.newTimedCache(2 * 60 * 1000); + + // 定时清理缓存,间隔1秒 + timedCache.schedulePrune(1000); + + return new MappingCache(timedCache); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/DataSourceConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/DataSourceConfig.java new file mode 100644 index 0000000..b4f2365 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/DataSourceConfig.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import cn.hutool.core.collection.CollectionUtil; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.support.http.StatViewServlet; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import vip.xiaonuo.core.pojo.druid.DruidProperties; + +import java.util.HashMap; + +/** + * Druid配置 + * + * @author yubaoshan + * @date 2017/5/20 21:58 + */ +@Configuration +public class DataSourceConfig { + + /** + * druid属性配置 + * + * @author xuyuxiang + * @date 2020/8/25 + */ + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DruidProperties druidProperties() { + return new DruidProperties(); + } + + /** + * druid数据库连接池 + * + * @author xuyuxiang + * @date 2020/8/25 + */ + @Bean(initMethod = "init") + public DruidDataSource dataSource(DruidProperties druidProperties) { + DruidDataSource dataSource = new DruidDataSource(); + druidProperties.config(dataSource); + return dataSource; + } + + /** + * druid监控,配置StatViewServlet + * + * @author xuyuxiang + * @date 2020/6/28 16:03 + */ + @Bean + public ServletRegistrationBean<StatViewServlet> druidServletRegistration() { + + // 设置servlet的参数 + HashMap<String, String> statViewServletParams = CollectionUtil.newHashMap(); + statViewServletParams.put("resetEnable", "true"); + ServletRegistrationBean<StatViewServlet> registration = new ServletRegistrationBean<>(new StatViewServlet()); + registration.addUrlMappings("/druid/*"); + registration.setInitParameters(statViewServletParams); + return registration; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/FileConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/FileConfig.java new file mode 100644 index 0000000..6515644 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/FileConfig.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import cn.hutool.core.util.ObjectUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.file.FileOperator; +import vip.xiaonuo.core.file.modular.local.LocalFileOperator; +import vip.xiaonuo.core.file.modular.local.prop.LocalFileProperties; + +/** + * 文件存储的配置 + * <p> + * 默认激活本地文件存储 + * + * @author yubaoshan + * @date 2020/6/6 22:27 + */ +@Configuration +public class FileConfig { + + /** + * 默认文件存储的位置 + */ + public static final String DEFAULT_BUCKET = "defaultBucket"; + + /** + * 本地文件操作客户端 + * + * @author yubaoshan + * @date 2020/6/9 21:39 + */ + @Bean + public FileOperator fileOperator() { + LocalFileProperties localFileProperties = new LocalFileProperties(); + String fileUploadPathForWindows = ConstantContextHolder.getDefaultFileUploadPathForWindows(); + if (ObjectUtil.isNotEmpty(fileUploadPathForWindows)) { + localFileProperties.setLocalFileSavePathWin(fileUploadPathForWindows); + } + + String fileUploadPathForLinux = ConstantContextHolder.getDefaultFileUploadPathForLinux(); + if (ObjectUtil.isNotEmpty(fileUploadPathForLinux)) { + localFileProperties.setLocalFileSavePathLinux(fileUploadPathForLinux); + } + return new LocalFileOperator(localFileProperties); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/MailSenderConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/MailSenderConfig.java new file mode 100644 index 0000000..b82b143 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/MailSenderConfig.java @@ -0,0 +1,59 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.extra.mail.MailAccount; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.email.MailSender; +import vip.xiaonuo.core.email.modular.SimpleMailSender; +import vip.xiaonuo.core.pojo.email.EmailConfigs; + +/** + * 邮件发送控制器 + * + * @author yubaoshan + * @date 2020/6/6 22:27 + */ +@Configuration +public class MailSenderConfig { + + /** + * 邮件发射器 + * + * @author yubaoshan + * @date 2020/6/9 23:13 + */ + @Bean + public MailSender mailSender() { + EmailConfigs emailConfigs = ConstantContextHolder.getEmailConfigs(); + MailAccount mailAccount = new MailAccount(); + BeanUtil.copyProperties(emailConfigs, mailAccount); + return new SimpleMailSender(mailAccount); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/MybatisConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/MybatisConfig.java new file mode 100644 index 0000000..b4519ae --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/MybatisConfig.java @@ -0,0 +1,95 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import vip.xiaonuo.sys.core.mybatis.dbid.SnowyDatabaseIdProvider; +import vip.xiaonuo.sys.core.mybatis.fieldfill.CustomMetaObjectHandler; +import vip.xiaonuo.sys.core.mybatis.sqlfilter.DemoProfileSqlInterceptor; + +/** + * mybatis扩展插件配置 + * + * @author xuyuxiang + * @date 2020/3/18 10:49 + */ +@Configuration +@MapperScan(basePackages = {"vip.xiaonuo.**.mapper"}) +public class MybatisConfig { + + /** + * mybatis-plus分页插件 + * + * @author xuyuxiang + * @date 2020/3/31 15:42 + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); + return interceptor; + } + + /** + * 演示环境的sql拦截器 + * <p> + * 演示环境只开放查询操作,其他都不允许 + * + * @author yubaoshan + * @date 2020/5/5 12:24 + */ + @Bean + public DemoProfileSqlInterceptor demoProfileSqlInterceptor() { + return new DemoProfileSqlInterceptor(); + } + + /** + * 自定义公共字段自动注入 + * + * @author xuyuxiang + * @date 2020/3/31 15:42 + */ + @Bean + public MetaObjectHandler metaObjectHandler() { + return new CustomMetaObjectHandler(); + } + + /** + * 数据库id选择器 + * + * @author yubaoshan + * @date 2020/6/20 21:23 + */ + @Bean + public SnowyDatabaseIdProvider snowyDatabaseIdProvider() { + return new SnowyDatabaseIdProvider(); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SmsSenderConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SmsSenderConfig.java new file mode 100644 index 0000000..f174710 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SmsSenderConfig.java @@ -0,0 +1,65 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import cn.hutool.core.bean.BeanUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.pojo.sms.AliyunSmsConfigs; +import vip.xiaonuo.core.sms.SmsSender; +import vip.xiaonuo.core.sms.modular.aliyun.AliyunSmsSender; +import vip.xiaonuo.core.sms.modular.aliyun.msign.impl.MapBasedMultiSignManager; +import vip.xiaonuo.core.sms.modular.aliyun.prop.AliyunSmsProperties; + +/** + * 短信发送配置,短信发送的配置属性都在数据库的sys_config表中 + * <p> + * 默认开启了阿里云的短信配置 + * + * @author yubaoshan + * @date 2020/6/6 22:27 + */ +@Configuration +public class SmsSenderConfig { + + /** + * 短信发送器(阿里云) + * + * @author yubaoshan + * @date 2020/6/6 22:30 + */ + @Bean + public SmsSender aliyunSmsSender() { + + // 从数据库配置读取阿里云配置 + AliyunSmsConfigs aliyunSmsConfigs = ConstantContextHolder.getAliyunSmsConfigs(); + AliyunSmsProperties aliyunSmsProperties = new AliyunSmsProperties(); + BeanUtil.copyProperties(aliyunSmsConfigs, aliyunSmsProperties); + + return new AliyunSmsSender(new MapBasedMultiSignManager(), aliyunSmsProperties); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SpringSecurityConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SpringSecurityConfig.java new file mode 100644 index 0000000..bded75b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SpringSecurityConfig.java @@ -0,0 +1,117 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import vip.xiaonuo.core.consts.SpringSecurityConstant; +import vip.xiaonuo.sys.core.filter.security.JwtAuthenticationTokenFilter; +import vip.xiaonuo.sys.core.filter.security.entrypoint.JwtAuthenticationEntryPoint; +import vip.xiaonuo.sys.modular.auth.service.impl.AuthServiceImpl; + +import javax.annotation.Resource; + +/** + * SpringSecurity配置 + * + * @author xuyuxiang + * @date 2020/3/18 10:54 + */ +@Configuration +public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { + + @Resource + private AuthServiceImpl authService; + + @Resource + private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; + + @Resource + private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + + /** + * 开启跨域访问拦截器 + * + * @author yubaoshan + * @date 2020/4/29 9:50 + */ + @Bean + public CorsFilter corsFilter() { + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsConfiguration.addAllowedOrigin("*"); + corsConfiguration.addAllowedHeader("*"); + corsConfiguration.addAllowedMethod("*"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", corsConfiguration); + return new CorsFilter(source); + } + + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception { + + //开启模拟请求,比如API POST测试工具的测试,不开启时,API POST为报403错误 + httpSecurity.csrf().disable(); + + //开启跨域访问 + httpSecurity.cors(); + + //不使用默认退出,自定义退出 + httpSecurity.logout().disable(); + + //未授权时访问须授权的资源端点 + httpSecurity.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint); + + //放开一些接口的权限校验 + for (String notAuthResource : SpringSecurityConstant.NONE_SECURITY_URL_PATTERNS) { + httpSecurity.authorizeRequests().antMatchers(notAuthResource).permitAll(); + } + + //其余的都需授权访问 + httpSecurity.authorizeRequests().anyRequest().authenticated(); + + //前置token过滤器 + httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); + + //用户详情service + httpSecurity.userDetailsService(authService); + + //全局不创建session + httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + //禁用页面缓存,返回的都是json + httpSecurity.headers() + .frameOptions().disable() + .cacheControl(); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SwaggerConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SwaggerConfig.java new file mode 100644 index 0000000..25c5863 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/SwaggerConfig.java @@ -0,0 +1,124 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import cn.hutool.core.collection.CollectionUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.ParameterBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.schema.ModelRef; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.Contact; +import springfox.documentation.service.Parameter; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; +import vip.xiaonuo.core.consts.CommonConstant; + +import java.util.List; + +/** + * swagger配置 + * + * @author xuyuxiang + * 加入分组功能(默认注释掉) + * <p> + * https://doc.xiaominfo.com/knife4j/changelog/2017-12-18-swagger-bootstrap-ui-1.7-issue.html + * </p> + * @author ldw4033#163.com + * @date 2021/4/9 10:42 + **/ +@Configuration +@EnableSwagger2WebMvc +public class SwaggerConfig { + + private List<Parameter> getParameters() { + Parameter parameter = new ParameterBuilder() + .name("Authorization") + .description("token令牌") + .modelRef(new ModelRef("string")) + .parameterType("header") + .required(false) + .build(); + + List<Parameter> parameters = CollectionUtil.newArrayList(); + parameters.add(parameter); + return parameters; + } + + @Bean + public Docket defaultApi() { + List<Parameter> parameters = getParameters(); + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(defaultApiInfo()) + .groupName("默认接口") + .select() + .apis(RequestHandlerSelectors.basePackage(CommonConstant.DEFAULT_PACKAGE_NAME)) + .paths(PathSelectors.any()) + .build() + .globalOperationParameters(parameters); + } + + private ApiInfo defaultApiInfo() { + return new ApiInfoBuilder() + .title("Snowy Doc") + .description("Snowy Doc文档") + .termsOfServiceUrl("https://www.xiaonuo.vip") + .contact(new Contact("xuyuxiang, yubaoshan, dongxiayu", "https://www.xiaonuo.vip", "")) + .version("1.0") + .build(); + } + + /** + * 想分组请放开注释 + */ + + // @Bean + // public Docket groupRestApi() { + // List<Parameter> parameters = getParameters(); + // return new Docket(DocumentationType.SWAGGER_2) + // .apiInfo(groupApiInfo()) + // .groupName("自定义") + // .select() + // //TODO 这里改为自己的包名 + // .apis(RequestHandlerSelectors.basePackage("com.example.XXX")) + // .paths(PathSelectors.any()) + // .build() + // .globalOperationParameters(parameters); + // } + // + // private ApiInfo groupApiInfo() { + // return new ApiInfoBuilder() + // .title("自定义") + // .description("自定义API") + // .termsOfServiceUrl("http://www.example.com/") + // .version("1.0") + // .build(); + // } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/WebMvcConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/WebMvcConfig.java new file mode 100644 index 0000000..023361e --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/config/WebMvcConfig.java @@ -0,0 +1,158 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.config; + +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import vip.xiaonuo.core.web.SnowyRequestResponseBodyMethodProcessor; +import vip.xiaonuo.sys.core.error.SnowyErrorAttributes; +import vip.xiaonuo.sys.core.filter.RequestNoFilter; +import vip.xiaonuo.sys.core.filter.xss.XssFilter; +import vip.xiaonuo.sys.core.validator.SnowyValidator; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * web配置 + * + * @author yubaoshan + * @date 2020/4/11 10:23 + */ +@Configuration +@Import({cn.hutool.extra.spring.SpringUtil.class}) +public class WebMvcConfig implements WebMvcConfigurer { + + /** + * 错误信息提示重写 + * + * @author yubaoshan + * @date 2020/4/14 22:27 + */ + @Bean + public SnowyErrorAttributes snowyErrorAttributes() { + return new SnowyErrorAttributes(); + } + + /** + * 静态资源映射 + * + * @author yubaoshan + * @date 2020/4/11 10:23 + */ + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + //swagger增强的静态资源映射 + registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/"); + registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); + //flowable设计器静态资源映射 + registry.addResourceHandler("/designer/**").addResourceLocations("classpath:/designer/"); + } + + /** + * xss过滤器 + * + * @author yubaoshan + * @date 2020/6/21 10:30 + */ + @Bean + public FilterRegistrationBean<XssFilter> xssFilterFilterRegistrationBean() { + FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>(new XssFilter()); + registration.addUrlPatterns("/*"); + return registration; + } + + /** + * 请求唯一编号生成器 + * + * @author yubaoshan + * @date 2020/6/21 10:30 + */ + @Bean + public FilterRegistrationBean<RequestNoFilter> requestNoFilterFilterRegistrationBean() { + FilterRegistrationBean<RequestNoFilter> registration = new FilterRegistrationBean<>(new RequestNoFilter()); + registration.addUrlPatterns("/*"); + return registration; + } + + /** + * json自定义序列化工具,long转string + * + * @author yubaoshan + * @date 2020/5/28 14:48 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + return jacksonObjectMapperBuilder -> + jacksonObjectMapperBuilder + .serializerByType(Long.class, ToStringSerializer.instance) + .serializerByType(Long.TYPE, ToStringSerializer.instance); + } + + /** + * 自定义的spring参数校验器,重写主要为了保存一些在自定义validator中读不到的属性 + * + * @author xuyuxiang + * @date 2020/8/12 20:18 + */ + @Bean + public SnowyValidator snowyValidator() { + return new SnowyValidator(); + } + + + /** + * 自定义的SnowyRequestResponseBodyMethodProcessor,放在所有resolvers之前 + * + * @author xuyuxiang + * @date 2020/8/21 21:09 + */ + @Configuration + public static class MethodArgumentResolver { + + @Resource + private RequestMappingHandlerAdapter adapter; + + @PostConstruct + public void injectSelfMethodArgumentResolver() { + List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(); + argumentResolvers.add(new SnowyRequestResponseBodyMethodProcessor(adapter.getMessageConverters())); + argumentResolvers.addAll(Objects.requireNonNull(adapter.getArgumentResolvers())); + adapter.setArgumentResolvers(argumentResolvers); + } + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/BusinessLogAop.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/BusinessLogAop.java new file mode 100644 index 0000000..1ec3145 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/BusinessLogAop.java @@ -0,0 +1,106 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.aop; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.Order; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.consts.AopSortConstant; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.sys.core.log.LogManager; + +import java.lang.reflect.Method; + +/** + * 业务日志aop切面 + * + * @author xuyuxiang + * @date 2020/3/20 11:47 + */ +@Aspect +@Order(AopSortConstant.BUSINESS_LOG_AOP) +public class BusinessLogAop { + + /** + * 日志切入点 + * + * @author xuyuxiang + * @date 2020/3/23 17:10 + */ + @Pointcut("@annotation(vip.xiaonuo.core.annotion.BusinessLog)") + private void getLogPointCut() { + } + + /** + * 操作成功返回结果记录日志 + * + * @author xuyuxiang + * @date 2020/3/20 11:51 + */ + @AfterReturning(pointcut = "getLogPointCut()", returning = "result") + public void doAfterReturning(JoinPoint joinPoint, Object result) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + BusinessLog businessLog = method.getAnnotation(BusinessLog.class); + SysLoginUser sysLoginUser = LoginContextHolder.me().getSysLoginUserWithoutException(); + String account = CommonConstant.UNKNOWN; + if(ObjectUtil.isNotNull(sysLoginUser)) { + account = sysLoginUser.getAccount(); + } + //异步记录日志 + LogManager.me().executeOperationLog( + businessLog, account, joinPoint, JSON.toJSONString(result)); + } + + /** + * 操作发生异常记录日志 + * + * @author xuyuxiang + * @date 2020/3/21 11:38 + */ + @AfterThrowing(pointcut = "getLogPointCut()", throwing = "exception") + public void doAfterThrowing(JoinPoint joinPoint, Exception exception) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + BusinessLog businessLog = method.getAnnotation(BusinessLog.class); + SysLoginUser sysLoginUser = LoginContextHolder.me().getSysLoginUserWithoutException(); + String account = CommonConstant.UNKNOWN; + if(ObjectUtil.isNotNull(sysLoginUser)) { + account = sysLoginUser.getAccount(); + } + //异步记录日志 + LogManager.me().executeExceptionLog( + businessLog, account, joinPoint, exception); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/DataScopeAop.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/DataScopeAop.java new file mode 100644 index 0000000..704f2d4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/DataScopeAop.java @@ -0,0 +1,82 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.aop; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.core.annotation.Order; +import vip.xiaonuo.core.consts.AopSortConstant; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import java.util.List; + +/** + * 数据权限切面 + * + * @author xuyuxiang + * @date 2020/3/28 17:16 + */ +@Aspect +@Order(AopSortConstant.DATA_SCOPE_AOP) +public class DataScopeAop { + + /** + * 数据范围切入点 + * + * @author xuyuxiang + * @date 2020/4/6 13:32 + */ + @Pointcut("@annotation(vip.xiaonuo.core.annotion.DataScope)") + private void getDataScopePointCut() { + } + + /** + * 执行数据范围过滤 + * + * @author xuyuxiang + * @date 2020/4/6 13:32 + */ + @Before("getDataScopePointCut()") + public void doDataScope(JoinPoint joinPoint) { + + //不是超级管理员时进行数据权限过滤 + if (!LoginContextHolder.me().isSuperAdmin()) { + Object[] args = joinPoint.getArgs(); + + //数据范围就是组织机构id集合 + List<Long> loginUserDataScopeIdList = LoginContextHolder.me().getLoginUserDataScopeIdList(); + BaseParam baseParam; + for (Object object : args) { + if (object instanceof BaseParam) { + baseParam = (BaseParam) object; + baseParam.setDataScope(loginUserDataScopeIdList); + } + } + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/PermissionAop.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/PermissionAop.java new file mode 100644 index 0000000..031f8ef --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/PermissionAop.java @@ -0,0 +1,138 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.aop; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.Log; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.Order; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.consts.AopSortConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.enums.LogicTypeEnum; +import vip.xiaonuo.core.exception.PermissionException; +import vip.xiaonuo.core.exception.enums.PermissionExceptionEnum; +import vip.xiaonuo.core.util.HttpServletUtil; +import vip.xiaonuo.sys.core.log.LogManager; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; + +/** + * 权限过滤Aop切面 + * + * @author xuyuxiang + * @date 2020/3/23 17:09 + */ +@Aspect +@Order(AopSortConstant.PERMISSION_AOP) +public class PermissionAop { + + private static final Log log = Log.get(); + + /** + * 权限切入点 + * + * @author xuyuxiang + * @date 2020/3/23 17:10 + */ + @Pointcut("@annotation(vip.xiaonuo.core.annotion.Permission)") + private void getPermissionPointCut() { + } + + /** + * 执行权限过滤 + * + * @author xuyuxiang + * @date 2020/3/23 17:14 + */ + @Before("getPermissionPointCut()") + public void doPermission(JoinPoint joinPoint) { + + // 如果是超级管理员,直接放过权限校验 + boolean isSuperAdmin = LoginContextHolder.me().isSuperAdmin(); + if (isSuperAdmin) { + return; + } + + // 如果不是超级管理员,则开始进行权限校验 + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + Permission permission = method.getAnnotation(Permission.class); + + // 当前方法需要的角色集合 + String[] requireRoles = permission.value(); + + // 逻辑类型 + LogicTypeEnum logicTypeEnum = permission.logicType(); + + // 首先校验当前用户有没有 当前请求requestUri的权限 + HttpServletRequest request = HttpServletUtil.getRequest(); + boolean hasUriPermission = LoginContextHolder.me().hasPermission(request.getRequestURI()); + if (!hasUriPermission) { + this.executeNoPermissionExceptionLog(joinPoint, new PermissionException(PermissionExceptionEnum.NO_PERMISSION)); + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION); + } + + // 如果当前接口需要特定的角色权限,则校验参数上的特殊角色当前用户有没 + if (requireRoles.length != 0) { + boolean hasSpecialRolePermission = true; + if (LogicTypeEnum.AND.equals(logicTypeEnum)) { + hasSpecialRolePermission = LoginContextHolder.me().hasAllRole(StrUtil.join(SymbolConstant.COMMA, (Object) requireRoles)); + } else if (LogicTypeEnum.OR.equals(logicTypeEnum)) { + hasSpecialRolePermission = LoginContextHolder.me().hasAnyRole(StrUtil.join(SymbolConstant.COMMA, (Object) requireRoles)); + } else { + log.error(">>> permission注解逻辑枚举错误"); + } + if (!hasSpecialRolePermission) { + this.executeNoPermissionExceptionLog(joinPoint, new PermissionException(PermissionExceptionEnum.NO_PERMISSION)); + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION); + } + } + } + + /** + * 记录无权限异常日志 + * + * @author xuyuxiang + * @date 2020/3/24 11:14 + */ + private void executeNoPermissionExceptionLog(JoinPoint joinPoint, Exception exception) { + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + Method method = methodSignature.getMethod(); + BusinessLog businessLog = method.getAnnotation(BusinessLog.class); + + //异步记录日志 + LogManager.me().executeExceptionLog( + businessLog, LoginContextHolder.me().getSysLoginUserAccount(), joinPoint, exception); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/WrapperAop.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/WrapperAop.java new file mode 100644 index 0000000..c0a56b7 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/aop/WrapperAop.java @@ -0,0 +1,241 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.aop; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.log.Log; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.core.annotation.Order; +import vip.xiaonuo.core.annotion.Wrapper; +import vip.xiaonuo.core.consts.AopSortConstant; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.WrapperExceptionEnum; +import vip.xiaonuo.core.pojo.base.wrapper.BaseWrapper; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.pojo.response.ResponseData; + +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +/** + * controller结果包装的aop + * + * @author xuyuxiang + * @date 2020/7/24 17:42 + */ +@Aspect +@Order(AopSortConstant.WRAPPER_AOP) +public class WrapperAop { + + private static final Log log = Log.get(); + + /** + * 切入点 + * + * @author xuyuxiang + * @date 2020/7/24 17:42 + */ + @Pointcut("@annotation(vip.xiaonuo.core.annotion.Wrapper)") + private void wrapperPointcut() { + } + + /** + * 执行具体的包装过程 + * + * @author xuyuxiang + * @date 2020/7/24 17:44 + */ + @Around("wrapperPointcut()") + public Object doWrapper(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { + + // 直接执行原有业务逻辑 + Object proceedResult = proceedingJoinPoint.proceed(); + + return processWrapping(proceedingJoinPoint, proceedResult); + } + + /** + * 具体包装过程 + * + * @author xuyuxiang + * @date 2020/7/24 17:53 + */ + @SuppressWarnings("all") + private Object processWrapping(ProceedingJoinPoint proceedingJoinPoint, Object originResult) throws IllegalAccessException, InstantiationException { + + // 获取@Wrapper注解 + MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature(); + Method method = methodSignature.getMethod(); + Wrapper wrapperAnnotation = method.getAnnotation(Wrapper.class); + + // 获取注解上的处理类 + Class<? extends BaseWrapper<?>>[] baseWrapperClasses = wrapperAnnotation.value(); + + // 如果注解上的为空直接返回 + if (ObjectUtil.isEmpty(baseWrapperClasses)) { + return originResult; + } + + // 获取原有返回结果,如果不是ResponseData则不进行处理(需要遵守这个约定) + if (!(originResult instanceof ResponseData)) { + log.warn(">>> 当前请求的返回结果不是ResponseData类型,直接返回原值!"); + return originResult; + } + + // 获取ResponseData中的值 + ResponseData responseData = (ResponseData) originResult; + Object beWrapped = responseData.getData(); + + // 如果是基本类型,不进行加工处理 + if (ObjectUtil.isBasicType(beWrapped)) { + throw new ServiceException(WrapperExceptionEnum.BASIC_TYPE_ERROR); + } + + // 如果是Page类型 + if (beWrapped instanceof Page) { + + // 获取Page原有对象 + Page page = (Page) beWrapped; + + // 将page中所有records都包装一遍 + ArrayList<Map<String, Object>> maps = new ArrayList<>(); + for (Object wrappedItem : page.getRecords()) { + maps.add(this.wrapPureObject(wrappedItem, baseWrapperClasses)); + } + + page.setRecords(maps); + responseData.setData(page); + } + + // 如果是PageResult类型 + else if (beWrapped instanceof PageResult) { + + // 获取PageResult原有对象 + PageResult pageResult = (PageResult) beWrapped; + + // 将PageResult中所有rows都包装一遍 + ArrayList<Map<String, Object>> maps = new ArrayList<>(); + for (Object wrappedItem : pageResult.getRows()) { + maps.add(this.wrapPureObject(wrappedItem, baseWrapperClasses)); + } + + pageResult.setRows(maps); + responseData.setData(pageResult); + } + + // 如果是List类型 + else if (beWrapped instanceof Collection) { + + // 获取原有的List + Collection collection = (Collection) beWrapped; + + // 将page中所有records都包装一遍 + ArrayList<Map<String, Object>> maps = new ArrayList<>(); + for (Object wrappedItem : collection) { + maps.add(this.wrapPureObject(wrappedItem, baseWrapperClasses)); + } + + responseData.setData(maps); + } + + // 如果是Array类型 + else if (ArrayUtil.isArray(beWrapped)) { + + // 获取原有的Array + Object[] objects = this.objToArray(beWrapped); + + // 将array中所有records都包装一遍 + ArrayList<Map<String, Object>> maps = new ArrayList<>(); + for (Object wrappedItem : objects) { + maps.add(this.wrapPureObject(wrappedItem, baseWrapperClasses)); + } + + responseData.setData(maps); + } + + // 如果是Object类型 + else { + responseData.setData(this.wrapPureObject(beWrapped, baseWrapperClasses)); + } + + + return responseData; + } + + /** + * 原始对象包装成一个map的过程 + * <p> + * 期间多次根据BaseWrapper接口方法执行包装过程 + * + * @author xuyuxiang + * @date 2020/7/24 21:40 + */ + @SuppressWarnings("all") + private Map<String, Object> wrapPureObject(Object originModel, Class<? extends BaseWrapper<?>>[] baseWrapperClasses) { + + // 首先将原始的对象转化为map + Map<String, Object> originMap = BeanUtil.beanToMap(originModel); + + // 经过多个包装类填充属性 + try { + for (Class<? extends BaseWrapper<?>> baseWrapperClass : baseWrapperClasses) { + BaseWrapper baseWrapper = baseWrapperClass.newInstance(); + Map<String, Object> incrementFieldsMap = baseWrapper.doWrap(originModel); + originMap.putAll(incrementFieldsMap); + } + } catch (Exception e) { + log.error(">>> 原始对象包装过程,字段转化异常:{}", e.getMessage()); + throw new ServiceException(WrapperExceptionEnum.TRANSFER_ERROR); + } + + return originMap; + } + + /** + * Object转为一个array,确保object为数组类型 + * + * @author xuyuxiang + * @date 2020/7/24 22:06 + */ + private Object[] objToArray(Object object) { + int length = Array.getLength(object); + Object[] result = new Object[length]; + for (int i = 0; i < result.length; i++) { + result[i] = Array.get(object, i); + } + return result; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/MappingCache.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/MappingCache.java new file mode 100644 index 0000000..bb4ead4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/MappingCache.java @@ -0,0 +1,65 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.cache; + +import cn.hutool.cache.impl.TimedCache; +import vip.xiaonuo.core.cache.base.AbstractMemoryCacheOperator; + +import java.util.Map; + +/** + * mapping是映射,指业务id和业务名称的映射,或字典code和字典值的映射 + * <p> + * mapping的含义: + * 指对接口响应字段转化(或完善)过程 + * <p> + * 为什么要映射: + * 一般在查询类的方法,响应结果如果需要返回详细的一些名称信息 + * 则需要通过left join关联对应明细表或字典表,通过id或者code查到对应的中文名称,这样做效率不高 + * <p> + * 结论: + * 利用缓存,将常用的字典或者业务id映射,保存起来 + * 一方面保证了查询速度,一方面简化了代码开发,不用写一些left join之类的sql + * + * @author xuyuxiang + * @date 2020/7/24 11:59 + */ +public class MappingCache extends AbstractMemoryCacheOperator<Map<String, Object>> { + + /** + * 缓存的前缀标识 + */ + public static final String MAPPING_CACHE_PREFIX = "MAPPINGS_"; + + public MappingCache(TimedCache<String, Map<String, Object>> timedCache) { + super(timedCache); + } + + @Override + public String getCommonKeyPrefix() { + return MAPPING_CACHE_PREFIX; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/OauthCache.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/OauthCache.java new file mode 100644 index 0000000..ba52162 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/OauthCache.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.cache; + +import cn.hutool.cache.impl.CacheObj; +import cn.hutool.cache.impl.TimedCache; +import me.zhyd.oauth.cache.AuthCacheConfig; +import me.zhyd.oauth.cache.AuthStateCache; +import org.springframework.stereotype.Component; + +import java.util.Iterator; + +/** + * Oauth登录的缓存 + * + * @author xuyuxiang + * @date 2020/7/28 17:42 + **/ +@Component +public class OauthCache implements AuthStateCache { + + private final TimedCache<String, String> timedCache = new TimedCache<>(AuthCacheConfig.timeout); + + @Override + public void cache(String key, String value) { + timedCache.put(key, value, AuthCacheConfig.timeout); + } + + @Override + public void cache(String key, String value, long timeoutSeconds) { + timedCache.put(key, value, AuthCacheConfig.timeout); + } + + @Override + public String get(String key) { + return timedCache.get(key); + } + + @Override + public boolean containsKey(String key) { + Iterator<CacheObj<String, String>> cacheObjIterator = timedCache.cacheObjIterator(); + while (cacheObjIterator.hasNext()) { + String temKey = cacheObjIterator.next().getKey(); + if (temKey.equals(key)) { + return true; + } + } + return false; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/ResourceCache.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/ResourceCache.java new file mode 100644 index 0000000..cb8df87 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/ResourceCache.java @@ -0,0 +1,63 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.cache; + +import cn.hutool.core.collection.CollectionUtil; + +import java.util.Set; + +/** + * 项目资源的缓存,存储了项目所有的访问url + * <p> + * 一般用在过滤器检测请求是否是项目没有的url + * + * @author yubaoshan + * @date 2020/7/9 11:03 + */ +public class ResourceCache { + + private final Set<String> resourceCaches = CollectionUtil.newHashSet(); + + /** + * 获取所有缓存资源 + * + * @author yubaoshan + * @date 2020/7/9 13:52 + */ + public Set<String> getAllResources() { + return resourceCaches; + } + + /** + * 直接缓存所有资源 + * + * @author yubaoshan + * @date 2020/7/9 13:52 + */ + public void putAllResources(Set<String> resources) { + resourceCaches.addAll(resources); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/UserCache.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/UserCache.java new file mode 100644 index 0000000..ba2e556 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/cache/UserCache.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.cache; + +import org.springframework.data.redis.core.RedisTemplate; +import vip.xiaonuo.core.cache.base.AbstractRedisCacheOperator; +import vip.xiaonuo.core.pojo.login.SysLoginUser; + +/** + * 登录用户的缓存,存储了当前登录的用户 + * <p> + * 一般用于在线用户的查看和过滤器检测用户是否登录 + * <p> + * key为用户的唯一id,value为LoginUser对象 + * + * @author yubaoshan + * @date 2020/7/9 11:02 + */ +public class UserCache extends AbstractRedisCacheOperator<SysLoginUser> { + + /** + * 登录用户缓存前缀 + */ + public static final String LOGIN_USER_CACHE_PREFIX = "LOGIN_USER_"; + + public UserCache(RedisTemplate<String, SysLoginUser> redisTemplate) { + super(redisTemplate); + } + + @Override + public String getCommonKeyPrefix() { + return LOGIN_USER_CACHE_PREFIX; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/consts/SysExpEnumConstant.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/consts/SysExpEnumConstant.java new file mode 100644 index 0000000..1d586e9 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/consts/SysExpEnumConstant.java @@ -0,0 +1,124 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.consts; + +/** + * 系统管理异常枚举编码构成常量 + * <p> + * 异常枚举编码由3部分组成,如下: + * <p> + * 模块编码(2位) + 分类编码(4位) + 具体项编码(至少1位) + * <p> + * 模块编码和分类编码在ExpEnumCodeConstant类中声明 + * + * @author yubaoshan + * @date 2020/6/19 20:46 + */ +public interface SysExpEnumConstant { + + /** + * 模块分类编码(2位) + * <p> + * snowy-system模块异常枚举编码 + */ + int SNOWY_SYS_MODULE_EXP_CODE = 20; + + /* 分类编码(4位) */ + /** + * 系统应用相关异常枚举 + */ + int SYS_APP_EXCEPTION_ENUM = 1100; + + /** + * 系统参数配置相关异常枚举 + */ + int SYS_CONFIG_EXCEPTION_ENUM = 1200; + + /** + * 系统字典值相关异常枚举 + */ + int SYS_DICT_DATA_EXCEPTION_ENUM = 1300; + + /** + * 系统字典类型相关异常枚举 + */ + int SYS_DICT_TYPE_EXCEPTION_ENUM = 1400; + + /** + * 文件信息表相关枚举 + */ + int SYS_FILE_INFO_EXCEPTION_ENUM = 1500; + + /** + * 系统菜单相关异常枚举 + */ + int SYS_MENU_EXCEPTION_ENUM = 1600; + + /** + * 系统组织机构相关异常枚举 + */ + int SYS_ORG_EXCEPTION_ENUM = 1700; + + /** + * 系统职位相关异常枚举 + */ + int SYS_POS_EXCEPTION_ENUM = 1800; + + /** + * 系统角色相关异常枚举 + */ + int SYS_ROLE_EXCEPTION_ENUM = 1900; + + /** + * 系统用户相关异常枚举 + */ + int SYS_USER_EXCEPTION_ENUM = 2000; + + /** + * 系统通知公告相关异常枚举 + */ + int SYS_NOTICE_EXCEPTION_ENUM = 2100; + + /** + * 定时任务相关 + */ + int TIMER_EXCEPTION_ENUM = 2200; + + /** + * 邮件相关 + */ + int EMAIL_EXCEPTION_ENUM = 2300; + + /** + * 短信相关 + */ + int SMS_EXCEPTION_ENUM = 2400; + + /** + * Oauth登录相关 + */ + int OAUTH_EXCEPTION_ENUM = 2500; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/context/SystemContextImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/context/SystemContextImpl.java new file mode 100644 index 0000000..1a058c3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/context/SystemContextImpl.java @@ -0,0 +1,218 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.context; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.Log; +import com.baomidou.mybatisplus.extension.toolkit.SqlRunner; +import org.springframework.stereotype.Component; +import vip.xiaonuo.core.context.system.SystemContext; +import vip.xiaonuo.core.pojo.base.validate.UniqueValidateParam; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.sys.modular.auth.service.AuthService; +import vip.xiaonuo.sys.modular.dict.service.SysDictDataService; +import vip.xiaonuo.sys.modular.role.entity.SysRole; +import vip.xiaonuo.sys.modular.role.param.SysRoleParam; +import vip.xiaonuo.sys.modular.role.service.SysRoleService; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.param.SysUserParam; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 系统相关上下文接口实现类 + * + * @author xuyuxiang + * @date 2020/5/6 14:59 + */ +@Component +public class SystemContextImpl implements SystemContext { + + Log log = Log.get(); + + @Resource + private AuthService authService; + + @Resource + private SysUserService sysUserService; + + @Resource + private SysRoleService sysRoleService; + + @Resource + private SysDictDataService sysDictDataService; + + @Override + public String getNameByUserId(Long userId) { + return sysUserService.getNameByUserId(userId); + } + + @Override + public String getNameByRoleId(Long roleId) { + return sysRoleService.getNameByRoleId(roleId); + } + + @Override + public SysLoginUser getLoginUserByToken(String token) { + return authService.getLoginUserByToken(token); + } + + @Override + public List<Dict> listUser(String account) { + SysUserParam sysUserParam = new SysUserParam(); + if (ObjectUtil.isNotEmpty(account)) { + sysUserParam.setAccount(account); + } + return sysUserService.list(sysUserParam); + } + + @Override + public List<Dict> listRole(String name) { + SysRoleParam sysRoleParam = new SysRoleParam(); + if (ObjectUtil.isNotEmpty(name)) { + sysRoleParam.setName(name); + } + return sysRoleService.list(sysRoleParam); + } + + @Override + public boolean isUser(Long userOrRoleId) { + SysUser sysUser = sysUserService.getById(userOrRoleId); + return !ObjectUtil.isNull(sysUser); + } + + @Override + public boolean isRole(Long userOrRoleId) { + SysRole sysRole = sysRoleService.getById(userOrRoleId); + return !ObjectUtil.isNull(sysRole); + } + + @Override + public List<String> getDictCodesByDictTypeCode(String... dictTypeCodes) { + return sysDictDataService.getDictCodesByDictTypeCode(dictTypeCodes); + } + + @Override + public boolean tableUniValueFlag(UniqueValidateParam uniqueValidateParam) { + int resultCount = 0; + + // 参数校验 + paramValidate(uniqueValidateParam); + + // 不排除当前记录,不排除逻辑删除的内容 + if (!uniqueValidateParam.getExcludeCurrentRecord() + && !uniqueValidateParam.getExcludeLogicDeleteItems()) { + resultCount = SqlRunner.db().selectCount( + "select count(*) from " + uniqueValidateParam.getTableName() + " where " + uniqueValidateParam.getColumnName() + " = {0}", + uniqueValidateParam.getValue()); + } + + // 不排除当前记录,排除逻辑删除的内容 + if (!uniqueValidateParam.getExcludeCurrentRecord() + && uniqueValidateParam.getExcludeLogicDeleteItems()) { + resultCount = SqlRunner.db().selectCount( + "select count(*) from " + uniqueValidateParam.getTableName() + + " where " + uniqueValidateParam.getColumnName() + " = {0} " + + " and " + + "(" + uniqueValidateParam.getLogicDeleteFieldName() + " is null || " + + uniqueValidateParam.getLogicDeleteFieldName() + " <> " + uniqueValidateParam.getLogicDeleteValue() + ")", + uniqueValidateParam.getValue()); + } + + // 排除当前记录,不排除逻辑删除的内容 + if (uniqueValidateParam.getExcludeCurrentRecord() + && !uniqueValidateParam.getExcludeLogicDeleteItems()) { + + // id判空 + paramIdValidate(uniqueValidateParam); + + resultCount = SqlRunner.db().selectCount( + "select count(*) from " + uniqueValidateParam.getTableName() + + " where " + uniqueValidateParam.getColumnName() + " = {0} " + + " and id <> {1}", + uniqueValidateParam.getValue(), uniqueValidateParam.getId()); + } + + // 排除当前记录,排除逻辑删除的内容 + if (uniqueValidateParam.getExcludeCurrentRecord() + && uniqueValidateParam.getExcludeLogicDeleteItems()) { + + // id判空 + paramIdValidate(uniqueValidateParam); + + resultCount = SqlRunner.db().selectCount( + "select count(*) from " + uniqueValidateParam.getTableName() + + " where " + uniqueValidateParam.getColumnName() + " = {0} " + + " and id <> {1} " + + " and " + + "(" + uniqueValidateParam.getLogicDeleteFieldName() + " is null || " + + uniqueValidateParam.getLogicDeleteFieldName() + " <> " + uniqueValidateParam.getLogicDeleteValue() + ")", + uniqueValidateParam.getValue(), uniqueValidateParam.getId()); + } + + // 如果大于0,代表不是唯一的当前校验的值 + return resultCount <= 0; + } + + @Override + public List<Long> getAllUserIdList() { + return sysUserService.getAllUserIdList(); + } + + /** + * 几个参数的为空校验 + * + * @author xuyuxiang + * @date 2020/8/17 22:00 + */ + private void paramValidate(UniqueValidateParam uniqueValidateParam) { + if (StrUtil.isBlank(uniqueValidateParam.getTableName())) { + throw new IllegalArgumentException("当前table字段值唯一性校验失败,tableName为空"); + } + if (StrUtil.isBlank(uniqueValidateParam.getColumnName())) { + throw new IllegalArgumentException("当前table字段值唯一性校验失败,columnName为空"); + } + if (StrUtil.isBlank(uniqueValidateParam.getValue())) { + throw new IllegalArgumentException("当前table字段值唯一性校验失败,字段值value为空"); + } + } + + /** + * id参数的为空校验 + * + * @author xuyuxiang + * @date 2020/8/17 22:00 + */ + private void paramIdValidate(UniqueValidateParam uniqueValidateParam) { + if (uniqueValidateParam.getId() == null) { + throw new IllegalArgumentException("当前table字段值唯一性校验失败,id为空"); + } + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/AdminTypeEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/AdminTypeEnum.java new file mode 100644 index 0000000..25e43e6 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/AdminTypeEnum.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 管理员类型枚举 + * + * @author xuyuxiang + * @date 2020/4/5 10:23 + */ +@Getter +public enum AdminTypeEnum { + + /** + * 超级管理员 + */ + SUPER_ADMIN(1, "超级管理员"), + + /** + * 非管理员 + */ + NONE(2, "非管理员"); + + private final Integer code; + + private final String message; + + AdminTypeEnum(int code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/DataScopeTypeEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/DataScopeTypeEnum.java new file mode 100644 index 0000000..df0b540 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/DataScopeTypeEnum.java @@ -0,0 +1,71 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 数据范围类型枚举 + * + * @author xuyuxiang + * @date 2020/5/28 11:02 + */ +@Getter +public enum DataScopeTypeEnum { + + /** + * 全部数据 + */ + ALL(1, "全部数据"), + + /** + * 本部门及以下数据 + */ + DEPT_WITH_CHILD(2, "本部门及以下数据"), + + /** + * 本部门数据 + */ + DEPT(3, "本部门数据"), + + /** + * 仅本人数据 + */ + SELF(4, "仅本人数据"), + + /** + * 仅本人数据 + */ + DEFINE(5, "自定义数据"); + + private final Integer code; + + private final String message; + + DataScopeTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/LogSuccessStatusEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/LogSuccessStatusEnum.java new file mode 100644 index 0000000..238175f --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/LogSuccessStatusEnum.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 日志成功状态 + * + * @author xuyuxiang + * @date 2020/3/12 15:53 + */ +@Getter +public enum LogSuccessStatusEnum { + + /** + * 失败 + */ + FAIL("N", "失败"), + + /** + * 成功 + */ + SUCCESS("Y", "成功"); + + private final String code; + + private final String message; + + LogSuccessStatusEnum(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuOpenTypeEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuOpenTypeEnum.java new file mode 100644 index 0000000..2639819 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuOpenTypeEnum.java @@ -0,0 +1,66 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 菜单类型枚举 + * + * @author xuyuxiang + * @date 2020/5/25 15:18 + */ +@Getter +public enum MenuOpenTypeEnum { + + /** + * 无 + */ + NONE(0, "无"), + + /** + * 组件 + */ + COMPONENT(1, "组件"), + + /** + * 内链 + */ + INNER(2, "内链"), + + /** + * 外链 + */ + OUTER(3, "外链"); + + private final Integer code; + + private final String message; + + MenuOpenTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuTypeEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuTypeEnum.java new file mode 100644 index 0000000..4ee1ba6 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuTypeEnum.java @@ -0,0 +1,61 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 菜单类型枚举 + * + * @author xuyuxiang + * @date 2020/5/25 15:18 + */ +@Getter +public enum MenuTypeEnum { + + /** + * 目录 + */ + DIR(0, "目录"), + + /** + * 菜单 + */ + MENU(1, "菜单"), + + /** + * 按钮 + */ + BTN(2, "按钮"); + + private final Integer code; + + private final String message; + + MenuTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuWeightEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuWeightEnum.java new file mode 100644 index 0000000..ff99b61 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/MenuWeightEnum.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 菜单权重枚举 + * + * @author xuyuxiang + * @date 2020/5/27 17:07 + */ +@Getter +public enum MenuWeightEnum { + + /** + * 系统权重 + */ + SUPER_ADMIN_WEIGHT(1, "系统权重"), + + /** + * 业务权重 + */ + DEFAULT_WEIGHT(2, "业务权重"); + + private final Integer code; + + private final String name; + + MenuWeightEnum(Integer code, String name) { + this.code = code; + this.name = name; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/NoticeStatusEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/NoticeStatusEnum.java new file mode 100644 index 0000000..ff443cd --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/NoticeStatusEnum.java @@ -0,0 +1,67 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 通知公告状态 + * + * @author xuyuxiang + * @date 2020/6/28 17:51 + */ +@Getter +public enum NoticeStatusEnum { + + /** + * 草稿 + */ + DRAFT(0, "草稿"), + + /** + * 发布 + */ + PUBLIC(1, "发布"), + + /** + * 撤回 + */ + CANCEL(2, "撤回"), + + /** + * 删除 + */ + DELETED(3, "删除"); + + private final Integer code; + + private final String message; + + NoticeStatusEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/NoticeUserStatusEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/NoticeUserStatusEnum.java new file mode 100644 index 0000000..444fe32 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/NoticeUserStatusEnum.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 通知公告用户状态 + * + * @author xuyuxiang + * @date 2020/6/29 11:02 + */ +@Getter +public enum NoticeUserStatusEnum { + + /** + * 未读 + */ + UNREAD(0, "未读"), + + /** + * 已读 + */ + READ(1, "已读"); + + private final Integer code; + + private final String message; + + NoticeUserStatusEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/OauthPlatformEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/OauthPlatformEnum.java new file mode 100644 index 0000000..6738bdb --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/OauthPlatformEnum.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * Oauth登录授权平台的枚举 + * + * @author xuyuxiang + * @date 2020/7/28 16:46 + **/ +@Getter +public enum OauthPlatformEnum { + + /** + * 码云 + */ + GITEE("gitee", "码云"), + + /** + * github + */ + GITHUB("github", "github"); + + private final String code; + + private final String message; + + OauthPlatformEnum(String code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/OauthSexEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/OauthSexEnum.java new file mode 100644 index 0000000..3287f87 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/OauthSexEnum.java @@ -0,0 +1,61 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * Oauth用户性别枚举 + * + * @author xuyuxiang + * @date 2020/7/29 10:32 + **/ +@Getter +public enum OauthSexEnum { + + /** + * 男 + */ + MAN("1", "男"), + + /** + * 女 + */ + WOMAN("0", "女"), + + /** + * 未知 + */ + NONE("-1", "未知"); + + private final String code; + + private final String message; + + OauthSexEnum(String code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/SexEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/SexEnum.java new file mode 100644 index 0000000..037c3ed --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/SexEnum.java @@ -0,0 +1,61 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 性别枚举 + * + * @author xuyuxiang + * @date 2020/5/28 10:51 + */ +@Getter +public enum SexEnum { + + /** + * 男 + */ + MAN(1, "男"), + + /** + * 女 + */ + WOMAN(2, "女"), + + /** + * 未知 + */ + NONE(3, "未知"); + + private final Integer code; + + private final String message; + + SexEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/VisLogTypeEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/VisLogTypeEnum.java new file mode 100644 index 0000000..0cf9e6b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/enums/VisLogTypeEnum.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.enums; + +import lombok.Getter; + +/** + * 访问日志类型枚举 + * + * @author xuyuxiang + * @date 2020/3/12 15:50 + */ +@Getter +public enum VisLogTypeEnum { + + /** + * 登录日志 + */ + LOGIN(1, "登录"), + + /** + * 退出日志 + */ + EXIT(2, "登出"); + + private final Integer code; + + private final String message; + + VisLogTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/error/GlobalExceptionHandler.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/error/GlobalExceptionHandler.java new file mode 100644 index 0000000..57db310 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/error/GlobalExceptionHandler.java @@ -0,0 +1,371 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.error; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.servlet.ServletUtil; +import cn.hutool.log.Log; +import org.apache.ibatis.exceptions.PersistenceException; +import org.mybatis.spring.MyBatisSystemException; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; +import org.springframework.web.HttpMediaTypeNotSupportedException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +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 org.springframework.web.servlet.NoHandlerFoundException; +import vip.xiaonuo.core.consts.AopSortConstant; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.requestno.RequestNoContext; +import vip.xiaonuo.core.exception.AuthException; +import vip.xiaonuo.core.exception.DemoException; +import vip.xiaonuo.core.exception.PermissionException; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.*; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.pojo.response.ErrorResponseData; +import vip.xiaonuo.core.sms.modular.aliyun.exp.AliyunSmsException; +import vip.xiaonuo.core.sms.modular.tencent.exp.TencentSmsException; +import vip.xiaonuo.core.util.HttpServletUtil; +import vip.xiaonuo.core.util.ResponseUtil; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * 全局异常处理器 + * + * @author xuyuxiang + * @date 2020/3/18 19:03 + */ +@Order(AopSortConstant.GLOBAL_EXP_HANDLER_AOP) +@ControllerAdvice +public class GlobalExceptionHandler { + + private static final Log log = Log.get(); + + /** + * 腾讯云短信发送异常 + * + * @author yubaoshan + * @date 2020/6/7 18:03 + */ + @ExceptionHandler(TencentSmsException.class) + @ResponseBody + public ErrorResponseData aliyunSmsException(TencentSmsException e) { + log.error(">>> 发送短信异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getErrorMessage()); + return renderJson(500, e.getErrorMessage()); + } + + /** + * 阿里云短信发送异常 + * + * @author yubaoshan + * @date 2020/6/7 18:03 + */ + @ExceptionHandler(AliyunSmsException.class) + @ResponseBody + public ErrorResponseData aliyunSmsException(AliyunSmsException e) { + log.error(">>> 发送短信异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getErrorMessage()); + return renderJson(500, e.getErrorMessage()); + } + + /** + * 请求参数缺失异常 + * + * @author yubaoshan + * @date 2020/6/7 18:03 + */ + @ExceptionHandler(MissingServletRequestParameterException.class) + @ResponseBody + public ErrorResponseData missParamException(MissingServletRequestParameterException e) { + log.error(">>> 请求参数异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + String parameterType = e.getParameterType(); + String parameterName = e.getParameterName(); + String message = StrUtil.format(">>> 缺少请求的参数{},类型为{}", parameterName, parameterType); + return renderJson(500, message); + } + + /** + * 拦截参数格式传递异常 + * + * @author xuyuxiang + * @date 2020/4/2 15:32 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + @ResponseBody + public ErrorResponseData httpMessageNotReadable(HttpMessageNotReadableException e) { + log.error(">>> 参数格式传递异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + return renderJson(RequestTypeExceptionEnum.REQUEST_JSON_ERROR); + } + + /** + * 拦截不支持媒体类型异常 + * + * @author xuyuxiang + * @date 2020/4/2 15:38 + */ + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + @ResponseBody + public ErrorResponseData httpMediaTypeNotSupport(HttpMediaTypeNotSupportedException e) { + log.error(">>> 参数格式传递异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + return renderJson(RequestTypeExceptionEnum.REQUEST_TYPE_IS_JSON); + } + + /** + * 拦截请求方法的异常 + * + * @author xuyuxiang + * @date 2020/3/18 19:14 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + @ResponseBody + public ErrorResponseData methodNotSupport(HttpServletRequest request) { + if (ServletUtil.isPostMethod(request)) { + log.error(">>> 请求方法异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), RequestMethodExceptionEnum.REQUEST_METHOD_IS_GET.getMessage()); + return renderJson(RequestMethodExceptionEnum.REQUEST_METHOD_IS_GET); + } + if (ServletUtil.isGetMethod(request)) { + log.error(">>> 请求方法异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), RequestMethodExceptionEnum.REQUEST_METHOD_IS_POST.getMessage()); + return renderJson(RequestMethodExceptionEnum.REQUEST_METHOD_IS_POST); + } + return null; + } + + /** + * 拦截资源找不到的运行时异常 + * + * @author xuyuxiang + * @date 2020/4/2 15:38 + */ + @ExceptionHandler(NoHandlerFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + @ResponseBody + public ErrorResponseData notFound(NoHandlerFoundException e) { + log.error(">>> 资源不存在异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage() +",请求地址为:" + HttpServletUtil.getRequest().getRequestURI()); + return renderJson(PermissionExceptionEnum.URL_NOT_EXIST.getCode(), PermissionExceptionEnum.URL_NOT_EXIST.getMessage() +",请求地址为:" + HttpServletUtil.getRequest().getRequestURI()); + } + + /** + * 拦截参数校验错误异常,JSON传参 + * + * @author xuyuxiang + * @date 2020/4/2 15:38 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseBody + public ErrorResponseData methodArgumentNotValidException(MethodArgumentNotValidException e) { + String argNotValidMessage = getArgNotValidMessage(e.getBindingResult()); + log.error(">>> 参数校验错误异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), argNotValidMessage); + return renderJson(ParamExceptionEnum.PARAM_ERROR.getCode(), argNotValidMessage); + } + + /** + * 拦截参数校验错误异常 + * + * @author xuyuxiang + * @date 2020/3/18 19:41 + */ + @ExceptionHandler(BindException.class) + @ResponseBody + public ErrorResponseData paramError(BindException e) { + String argNotValidMessage = getArgNotValidMessage(e.getBindingResult()); + log.error(">>> 参数校验错误异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), argNotValidMessage); + return renderJson(ParamExceptionEnum.PARAM_ERROR.getCode(), argNotValidMessage); + } + + /** + * 拦截认证失败异常 + * + * @author xuyuxiang + * @date 2020/3/18 19:41 + */ + @ExceptionHandler(AuthException.class) + @ResponseBody + public ErrorResponseData authFail(AuthException e) { + log.error(">>> 认证异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + return renderJson(e.getCode(), e.getErrorMessage()); + } + + /** + * 拦截权限异常 + * + * @author xuyuxiang + * @date 2020/3/18 19:41 + */ + @ExceptionHandler(PermissionException.class) + @ResponseBody + public ErrorResponseData noPermission(PermissionException e) { + log.error(">>> 权限异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage() +",请求地址为:" + HttpServletUtil.getRequest().getRequestURI()); + return renderJson(e.getCode(), e.getErrorMessage() + ",请求地址为:" + HttpServletUtil.getRequest().getRequestURI()); + } + + /** + * 拦截业务异常 + * + * @author xuyuxiang + * @date 2020/3/18 19:41 + */ + @ExceptionHandler(ServiceException.class) + @ResponseBody + public ErrorResponseData businessError(ServiceException e) { + log.error(">>> 业务异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + return renderJson(e.getCode(), e.getErrorMessage(), e); + } + + /** + * 拦截mybatis数据库操作的异常 + * <p> + * 用在demo模式,拦截DemoException + * + * @author yubaoshan + * @date 2020/5/5 15:19 + */ + @ExceptionHandler(MyBatisSystemException.class) + @ResponseBody + public ErrorResponseData persistenceException(MyBatisSystemException e) { + log.error(">>> mybatis操作出现异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + Throwable cause = e.getCause(); + if (cause instanceof PersistenceException) { + Throwable secondCause = cause.getCause(); + if (secondCause instanceof DemoException) { + DemoException demoException = (DemoException) secondCause; + return ResponseUtil.responseDataError(demoException.getCode(), demoException.getErrorMessage(), e.getStackTrace()[0].toString()); + } + } + return ResponseUtil.responseDataError(ServerExceptionEnum.SERVER_ERROR.getCode(), ServerExceptionEnum.SERVER_ERROR.getMessage(), e.getStackTrace()[0].toString()); + } + + /** + * 拦截未知的运行时异常 + * + * @author xuyuxiang + * @date 2020/3/18 19:41 + */ + @ExceptionHandler(Throwable.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public ErrorResponseData serverError(Throwable e) { + log.error(">>> 服务器运行异常,请求号为:{}", RequestNoContext.get()); + e.printStackTrace(); + return renderJson(e); + } + + /** + * 渲染异常json + * + * @author yubaoshan + * @date 2020/5/5 16:22 + */ + private ErrorResponseData renderJson(Integer code, String message) { + return renderJson(code, message, null); + } + + /** + * 渲染异常json + * + * @author yubaoshan + * @date 2020/5/5 16:22 + */ + private ErrorResponseData renderJson(AbstractBaseExceptionEnum baseExceptionEnum) { + return renderJson(baseExceptionEnum.getCode(), baseExceptionEnum.getMessage(), null); + } + + /** + * 渲染异常json + * + * @author yubaoshan + * @date 2020/5/5 16:22 + */ + private ErrorResponseData renderJson(Throwable throwable) { + return renderJson(((AbstractBaseExceptionEnum) ServerExceptionEnum.SERVER_ERROR).getCode(), + ((AbstractBaseExceptionEnum) ServerExceptionEnum.SERVER_ERROR).getMessage(), throwable); + } + + /** + * 渲染异常json + * <p> + * 根据异常枚举和Throwable异常响应,异常信息响应堆栈第一行 + * + * @author yubaoshan + * @date 2020/5/5 16:22 + */ + private ErrorResponseData renderJson(Integer code, String message, Throwable e) { + if (ObjectUtil.isNotNull(e)) { + + //获取所有堆栈信息 + StackTraceElement[] stackTraceElements = e.getStackTrace(); + + //默认的异常类全路径为第一条异常堆栈信息的 + String exceptionClassTotalName = stackTraceElements[0].toString(); + + //遍历所有堆栈信息,找到vip.xiaonuo开头的第一条异常信息 + for (StackTraceElement stackTraceElement : stackTraceElements) { + if (stackTraceElement.toString().contains(CommonConstant.DEFAULT_PACKAGE_NAME)) { + exceptionClassTotalName = stackTraceElement.toString(); + break; + } + } + return ResponseUtil.responseDataError(code, message, exceptionClassTotalName); + } else { + return ResponseUtil.responseDataError(code, message, null); + } + } + + /** + * 获取请求参数不正确的提示信息 + * <p> + * 多个信息,拼接成用逗号分隔的形式 + * + * @author yubaoshan + * @date 2020/5/5 16:50 + */ + private String getArgNotValidMessage(BindingResult bindingResult) { + if (bindingResult == null) { + return ""; + } + StringBuilder stringBuilder = new StringBuilder(); + + //多个错误用逗号分隔 + List<ObjectError> allErrorInfos = bindingResult.getAllErrors(); + for (ObjectError error : allErrorInfos) { + stringBuilder.append(SymbolConstant.COMMA).append(error.getDefaultMessage()); + } + + //最终把首部的逗号去掉 + return StrUtil.removePrefix(stringBuilder.toString(), SymbolConstant.COMMA); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/error/SnowyErrorAttributes.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/error/SnowyErrorAttributes.java new file mode 100644 index 0000000..f4d1058 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/error/SnowyErrorAttributes.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.error; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.http.HttpStatus; +import org.springframework.boot.web.error.ErrorAttributeOptions; +import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; +import org.springframework.web.context.request.WebRequest; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.PermissionExceptionEnum; +import vip.xiaonuo.core.exception.enums.ServerExceptionEnum; +import vip.xiaonuo.core.pojo.response.ErrorResponseData; + +import java.util.Map; + +/** + * 将系统管理未知错误异常,输出格式重写为我们熟悉的响应格式 + * + * @author yubaoshan + * @date 2020/4/14 22:21 + */ +public class SnowyErrorAttributes extends DefaultErrorAttributes { + + @Override + public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions attributeOptions) { + + // 1.先获取spring默认的返回内容 + Map<String, Object> defaultErrorAttributes = super.getErrorAttributes(webRequest, attributeOptions); + + // 2.如果返回的异常是ServiceException,则按ServiceException响应的内容进行返回 + Throwable throwable = this.getError(webRequest); + if (throwable instanceof ServiceException) { + ServiceException serviceException = (ServiceException) throwable; + return BeanUtil.beanToMap(new ErrorResponseData(serviceException.getCode(), serviceException.getErrorMessage())); + } + + // 3.如果返回的是404 http状态码 + Integer status = (Integer) defaultErrorAttributes.get("status"); + if (status.equals(HttpStatus.HTTP_NOT_FOUND)) { + return BeanUtil.beanToMap(new ErrorResponseData(PermissionExceptionEnum.URL_NOT_EXIST.getCode(), PermissionExceptionEnum.URL_NOT_EXIST.getMessage())); + } + + // 4.无法确定的返回服务器异常 + return BeanUtil.beanToMap(new ErrorResponseData(ServerExceptionEnum.SERVER_ERROR.getCode(), ServerExceptionEnum.SERVER_ERROR.getMessage())); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/RequestNoFilter.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/RequestNoFilter.java new file mode 100644 index 0000000..33e23f9 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/RequestNoFilter.java @@ -0,0 +1,67 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.filter; + + +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.context.requestno.RequestNoContext; + +import javax.servlet.*; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.UUID; + +/** + * 对请求生成唯一编码 + * + * @author yubaoshan + * @date 2020/6/21 10:04 + */ +public class RequestNoFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + try { + // 生成唯一请求号uuid + String requestNo = UUID.randomUUID().toString(); + + // 增加响应头的请求号 + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.addHeader(CommonConstant.REQUEST_NO_HEADER_NAME, requestNo); + + // 临时存储 + RequestNoContext.set(requestNo); + + // 放开请求 + chain.doFilter(request, response); + + } finally { + // 清除临时存储的唯一编号 + RequestNoContext.clear(); + } + + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/security/JwtAuthenticationTokenFilter.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/security/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..6d173c7 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/security/JwtAuthenticationTokenFilter.java @@ -0,0 +1,100 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.filter.security; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.Log; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import vip.xiaonuo.core.context.requestno.RequestNoContext; +import vip.xiaonuo.core.exception.AuthException; +import vip.xiaonuo.core.exception.enums.ServerExceptionEnum; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.core.util.ResponseUtil; +import vip.xiaonuo.sys.modular.auth.service.AuthService; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * 这个过滤器,在所有请求之前,也在spring security filters之前 + * <p>z + * 这个过滤器的作用是:接口在进业务之前,添加登录上下文(SecurityContext) + * <p> + * 因为现在没有用session了,只能token来校验当前的登录人的身份,所以在进业务之前要给当前登录人设置登录状态 + * + * @author yubaoshan,xuyuxiang + * @date 2020/4/11 13:02 + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { + + private static final Log log = Log.get(); + + @Autowired + private AuthService authService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException { + try { + doFilter(request, response, filterChain); + } catch (Exception e) { + log.error(">>> 服务器运行异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + ResponseUtil.responseExceptionError(response, ServerExceptionEnum.SERVER_ERROR.getCode(), + ServerExceptionEnum.SERVER_ERROR.getMessage(), e.getStackTrace()[0].toString()); + } + } + + private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException { + + // 1.如果当前请求带了token,判断token时效性,并获取当前登录用户信息 + SysLoginUser sysLoginUser = null; + try { + String token = authService.getTokenFromRequest(request); + if (StrUtil.isNotEmpty(token)) { + sysLoginUser = authService.getLoginUserByToken(token); + } + } catch (AuthException ae) { + //token过期或者token失效的情况,响应给前端 + ResponseUtil.responseExceptionError(response, ae.getCode(), ae.getErrorMessage(), ae.getStackTrace()[0].toString()); + return; + } + + // 2.如果当前登录用户不为空,就设置spring security上下文 + if (ObjectUtil.isNotNull(sysLoginUser)) { + authService.setSpringSecurityContextAuthentication(sysLoginUser); + } + + // 3.其他情况放开过滤 + filterChain.doFilter(request, response); + + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/security/entrypoint/JwtAuthenticationEntryPoint.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/security/entrypoint/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..1495443 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/security/entrypoint/JwtAuthenticationEntryPoint.java @@ -0,0 +1,99 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.filter.security.entrypoint; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.log.Log; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.AuthExceptionEnum; +import vip.xiaonuo.core.exception.enums.PermissionExceptionEnum; +import vip.xiaonuo.core.exception.enums.ServerExceptionEnum; +import vip.xiaonuo.core.util.ResponseUtil; +import vip.xiaonuo.sys.core.cache.ResourceCache; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; + +/** + * 未认证用户访问须授权资源端点 + * + * @author xuyuxiang + * @date 2020/3/18 11:52 + */ +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { + + private static final Log log = Log.get(); + + @Resource + private ResourceCache resourceCache; + + /** + * 访问未经授权的接口时执行此方法,未经授权的接口包含系统中存在和不存在的接口,分别处理 + * + * @author xuyuxiang + * @date 2020/3/18 19:15 + */ + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException { + + String requestUri = request.getRequestURI(); + + //1.检查redis中RESOURCE缓存是否为空,如果为空,直接抛出系统异常,缓存url作用详见ResourceCollectListener + Collection<String> urlCollections = resourceCache.getAllResources(); + if (ObjectUtil.isEmpty(urlCollections)) { + log.error(">>> 获取缓存的Resource Url为空,请检查缓存中是否被误删,requestUri={}", requestUri); + ResponseUtil.responseExceptionError(response, + ServerExceptionEnum.SERVER_ERROR.getCode(), + ServerExceptionEnum.SERVER_ERROR.getMessage(), + new ServiceException(ServerExceptionEnum.SERVER_ERROR).getStackTrace()[0].toString()); + return; + } + + //2.判断缓存的url中是否有当前请求的uri,没有的话响应给前端404 + if (!urlCollections.contains(requestUri)) { + log.error(">>> 当前请求的uri不存在,请检查请求地址是否正确或缓存中是否被误删,requestUri={}", requestUri); + ResponseUtil.responseExceptionError(response, + PermissionExceptionEnum.URL_NOT_EXIST.getCode(), + PermissionExceptionEnum.URL_NOT_EXIST.getMessage(), + new ServiceException(PermissionExceptionEnum.URL_NOT_EXIST).getStackTrace()[0].toString()); + return; + } + + //3.响应给前端无权限访问本接口(没有携带token) + log.error(">>> 没有权限访问该资源,requestUri={}", requestUri); + ResponseUtil.responseExceptionError(response, + AuthExceptionEnum.REQUEST_TOKEN_EMPTY.getCode(), + AuthExceptionEnum.REQUEST_TOKEN_EMPTY.getMessage(), + new ServiceException(AuthExceptionEnum.REQUEST_TOKEN_EMPTY).getStackTrace()[0].toString()); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/xss/XssFilter.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/xss/XssFilter.java new file mode 100644 index 0000000..f2e5d0b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/xss/XssFilter.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.filter.xss; + + +import vip.xiaonuo.core.context.constant.ConstantContextHolder; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.List; + +/** + * xss过滤器 + * + * @author yubaoshan + * @date 2020/6/21 10:04 + */ +public class XssFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + String servletPath = httpServletRequest.getServletPath(); + + // 获取不进行url过滤的接口 + List<String> unXssFilterUrl = ConstantContextHolder.getUnXssFilterUrl(); + if (unXssFilterUrl != null && unXssFilterUrl.contains(servletPath)) { + chain.doFilter(request, response); + } else { + chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response); + } + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/xss/XssHttpServletRequestWrapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/xss/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..a25c464 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/filter/xss/XssHttpServletRequestWrapper.java @@ -0,0 +1,91 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.filter.xss; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/** + * xss针对http请求的包装 + * + * @author yubaoshan + * @date 2020/6/21 10:29 + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { + + XssHttpServletRequestWrapper(HttpServletRequest servletRequest) { + super(servletRequest); + } + + @Override + public String[] getParameterValues(String parameter) { + String[] values = super.getParameterValues(parameter); + if (values == null) { + return null; + } + int count = values.length; + String[] encodedValues = new String[count]; + for (int i = 0; i < count; i++) { + encodedValues[i] = cleanXss(values[i]); + } + return encodedValues; + + } + + @Override + public String getParameter(String parameter) { + String value = super.getParameter(parameter); + if (value == null) { + return null; + } + return cleanXss(value); + } + + @Override + public String getHeader(String name) { + String value = super.getHeader(name); + if (value == null) { + return null; + } + return cleanXss(value); + } + + private String cleanXss(String value) { + + value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;"); + + value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;"); + + value = value.replaceAll("'", "& #39;"); + + value = value.replaceAll("eval\\((.*)\\)", ""); + + value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\""); + + return value; + + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/jwt/JwtPayLoad.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/jwt/JwtPayLoad.java new file mode 100644 index 0000000..5e6f624 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/jwt/JwtPayLoad.java @@ -0,0 +1,62 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.jwt; + +import cn.hutool.core.util.IdUtil; +import lombok.Data; + +/** + * JwtPayLoad部分 + * + * @author xuyuxiang + * @date 2020/3/12 17:41 + */ +@Data +public class JwtPayLoad { + + /** + * 用户id + */ + private Long userId; + + /** + * 账号 + */ + private String account; + + /** + * 唯一表示id, 用于缓存登录用户的唯一凭证 + */ + private String uuid; + + public JwtPayLoad() { + } + + public JwtPayLoad(Long userId, String account) { + this.userId = userId; + this.account = account; + this.uuid = IdUtil.fastUUID(); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/jwt/JwtTokenUtil.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/jwt/JwtTokenUtil.java new file mode 100644 index 0000000..a22f29a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/jwt/JwtTokenUtil.java @@ -0,0 +1,121 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.jwt; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import io.jsonwebtoken.*; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.util.CryptogramUtil; +import java.util.Date; + +/** + * JwtToken工具类 + * + * @author xuyuxiang + * @date 2020/3/12 17:39 + */ +public class JwtTokenUtil { + + /** + * 生成token + * + * @author xuyuxiang + * @date 2020/3/12 17:52 + */ + public static String generateToken(JwtPayLoad jwtPayLoad) { + + DateTime expirationDate = DateUtil.offsetSecond(new Date(), Convert.toInt(ConstantContextHolder.getTokenExpireSec())); + String jwtToken = Jwts.builder() + .setClaims(BeanUtil.beanToMap(jwtPayLoad)) + .setSubject(jwtPayLoad.getUserId().toString()) + .setIssuedAt(new Date()) + .setExpiration(expirationDate) + .signWith(SignatureAlgorithm.HS512, ConstantContextHolder.getJwtSecret()) + .compact(); + if (ConstantContextHolder.getCryptogramConfigs().getTokenEncDec()) { + // 加密token + return CryptogramUtil.doEncrypt(jwtToken); + } + return jwtToken; + } + + /** + * 根据token获取Claims + * + * @author xuyuxiang + * @date 2020/3/13 10:29 + */ + private static Claims getClaimsFromToken(String token) { + return Jwts.parser() + .setSigningKey(ConstantContextHolder.getJwtSecret()) + .parseClaimsJws(token) + .getBody(); + } + + /** + * 获取JwtPayLoad部分 + * + * @author xuyuxiang + * @date 2020/3/12 17:53 + */ + public static JwtPayLoad getJwtPayLoad(String token) { + Claims claims = getClaimsFromToken(token); + return BeanUtil.mapToBean(claims, JwtPayLoad.class, false); + } + + /** + * 校验token是否正确 + * + * @author xuyuxiang + * @date 2020/3/13 10:36 + */ + public static Boolean checkToken(String token) { + try { + getClaimsFromToken(token); + return true; + } catch (JwtException jwtException) { + return false; + } + } + + /** + * 校验token是否失效 + * + * @author xuyuxiang + * @date 2020/3/13 10:30 + */ + public static Boolean isTokenExpired(String token) { + try { + Claims claims = getClaimsFromToken(token); + final Date expiration = claims.getExpiration(); + return expiration.before(new Date()); + } catch (ExpiredJwtException expiredJwtException) { + return true; + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/ConstantsInitListener.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/ConstantsInitListener.java new file mode 100644 index 0000000..e6f61b9 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/ConstantsInitListener.java @@ -0,0 +1,121 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.listener; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.db.DbUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.handler.EntityListHandler; +import cn.hutool.db.sql.SqlExecutor; +import cn.hutool.log.Log; +import org.springframework.boot.context.event.ApplicationContextInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.context.constant.ConstantContext; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.sys.modular.consts.enums.SysConfigExceptionEnum; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.List; + +/** + * 初始化常量的监听器 + * <p> + * 当spring装配好配置后,就去数据库读constants + * + * @author yubaoshan + * @date 2020/6/6 23:39 + */ +public class ConstantsInitListener implements ApplicationListener<ApplicationContextInitializedEvent>, Ordered { + + private static final Log log = Log.get(); + + private static final String CONFIG_LIST_SQL = "select code,value from sys_config where status = ?"; + + private static final String CAPITAL_CODE = "CODE"; + + private static final String CODE = "code"; + + private static final String CAPITAL_VALUE = "VALUE"; + + private static final String VALUE = "value"; + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public void onApplicationEvent(ApplicationContextInitializedEvent applicationContextInitializedEvent) { + ConfigurableEnvironment environment = applicationContextInitializedEvent.getApplicationContext().getEnvironment(); + + // 获取数据库连接配置 + String dataSourceUrl = environment.getProperty("spring.datasource.url"); + String dataSourceUsername = environment.getProperty("spring.datasource.username"); + String dataSourcePassword = environment.getProperty("spring.datasource.password"); + + // 缓存中放入datasource链接,代码生成时候使用 + ConstantContext.putConstant(CommonConstant.DATABASE_URL_NAME, dataSourceUrl); + ConstantContext.putConstant(CommonConstant.DATABASE_DRIVER_NAME, environment.getProperty("spring.datasource.driver-class-name")); + ConstantContext.putConstant(CommonConstant.DATABASE_USER_NAME, dataSourceUsername); + + // 如果有为空的配置,终止执行 + if (ObjectUtil.hasEmpty(dataSourceUrl, dataSourceUsername)) { + throw new ServiceException(SysConfigExceptionEnum.DATA_SOURCE_NOT_EXIST); + } + + Connection conn = null; + try { + Class.forName(environment.getProperty("spring.datasource.driver-class-name")); + assert dataSourceUrl != null; + conn = DriverManager.getConnection(dataSourceUrl, dataSourceUsername, dataSourcePassword); + + // 获取sys_config表的数据 + List<Entity> entityList = SqlExecutor.query(conn, CONFIG_LIST_SQL, new EntityListHandler(), CommonStatusEnum.ENABLE.getCode()); + + // 将查询到的参数配置添加到缓存 + if (ObjectUtil.isNotEmpty(entityList)) { + entityList.forEach(sysConfig -> + ConstantContext.putConstant( + sysConfig.getStr(CODE) == null ? sysConfig.getStr(CAPITAL_CODE) : sysConfig.getStr(CODE), + sysConfig.getStr(VALUE) == null ? sysConfig.getStr(CAPITAL_VALUE) : sysConfig.getStr(VALUE) + ) + ); + } + } catch (SQLException | ClassNotFoundException e) { + log.error(">>> 读取数据库constants配置信息出错:"); + e.printStackTrace(); + throw new ServiceException(SysConfigExceptionEnum.DATA_SOURCE_NOT_EXIST); + } finally { + DbUtil.close(conn); + } + + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/RemoveRequestParamListener.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/RemoveRequestParamListener.java new file mode 100644 index 0000000..1856b65 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/RemoveRequestParamListener.java @@ -0,0 +1,44 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.listener; + +import org.springframework.context.ApplicationListener; +import org.springframework.web.context.support.ServletRequestHandledEvent; +import vip.xiaonuo.core.context.param.RequestParamContext; + +/** + * 用来清除临时缓存的@RequestBody的请求参数 + * + * @author xuyuxiang + * @date 2020/8/21 21:14 + */ +public class RemoveRequestParamListener implements ApplicationListener<ServletRequestHandledEvent> { + + @Override + public void onApplicationEvent(ServletRequestHandledEvent event) { + RequestParamContext.clear(); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/ResourceCollectListener.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/ResourceCollectListener.java new file mode 100644 index 0000000..2d66948 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/ResourceCollectListener.java @@ -0,0 +1,78 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.listener; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.log.Log; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import vip.xiaonuo.sys.core.cache.ResourceCache; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * 资源搜集器,将项目中所有接口(带@RequestMapping的)都搜集起来 + * <p> + * 搜集到的接口会被缓存,用于请求时判断请求的接口是否存在 + * + * @author xuyuxiang + * @date 2020/3/19 17:33 + */ +@Component +public class ResourceCollectListener implements CommandLineRunner { + + private static final Log log = Log.get(); + + @Resource + private ResourceCache resourceCache; + + @Override + public void run(String... args) { + + //1.获取所有后端接口 + Set<String> urlSet = CollectionUtil.newHashSet(); + Map<String, RequestMappingHandlerMapping> mappingMap = SpringUtil.getApplicationContext().getBeansOfType(RequestMappingHandlerMapping.class); + Collection<RequestMappingHandlerMapping> mappings = mappingMap.values(); + for (RequestMappingHandlerMapping mapping : mappings) { + Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods(); + map.keySet().forEach(requestMappingInfo -> { + Set<String> patterns = requestMappingInfo.getPatternsCondition().getPatterns(); + urlSet.addAll(patterns); + }); + } + + //2.汇总添加到缓存 + resourceCache.putAllResources(urlSet); + + log.info(">>> 缓存资源URL集合完成!资源数量:{}", urlSet.size()); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/TimerTaskRunListener.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/TimerTaskRunListener.java new file mode 100644 index 0000000..9ed0bad --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/listener/TimerTaskRunListener.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.listener; + +import cn.hutool.cron.CronUtil; +import cn.hutool.extra.spring.SpringUtil; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import vip.xiaonuo.sys.modular.timer.entity.SysTimers; +import vip.xiaonuo.sys.modular.timer.enums.TimerJobStatusEnum; +import vip.xiaonuo.sys.modular.timer.param.SysTimersParam; +import vip.xiaonuo.sys.modular.timer.service.SysTimersService; +import vip.xiaonuo.sys.modular.timer.service.TimerExeService; + +import java.util.List; + +/** + * 项目定时任务启动的listener + * + * @author yubaoshan + * @date 2020/7/1 15:30 + */ +public class TimerTaskRunListener implements ApplicationListener<ApplicationStartedEvent>, Ordered { + + @Override + public void onApplicationEvent(ApplicationStartedEvent event) { + + SysTimersService sysTimersService = SpringUtil.getBean(SysTimersService.class); + TimerExeService timerExeService = SpringUtil.getBean(TimerExeService.class); + + // 获取所有开启状态的任务 + SysTimersParam sysTimersParam = new SysTimersParam(); + sysTimersParam.setJobStatus(TimerJobStatusEnum.RUNNING.getCode()); + List<SysTimers> list = sysTimersService.list(sysTimersParam); + + // 添加定时任务到调度器 + for (SysTimers sysTimers : list) { + timerExeService.startTimer(String.valueOf(sysTimers.getId()), sysTimers.getCron(), sysTimers.getActionClass()); + } + + // 设置秒级别的启用 + CronUtil.setMatchSecond(true); + + // 启动定时器执行器 + CronUtil.start(); + } + + @Override + public int getOrder() { + return LOWEST_PRECEDENCE; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/LogManager.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/LogManager.java new file mode 100644 index 0000000..95e7efc --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/LogManager.java @@ -0,0 +1,178 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.log; + +import cn.hutool.core.util.ObjectUtil; +import org.aspectj.lang.JoinPoint; +import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.ServerExceptionEnum; +import vip.xiaonuo.core.util.HttpServletUtil; +import vip.xiaonuo.core.util.IpAddressUtil; +import vip.xiaonuo.core.util.UaUtil; +import vip.xiaonuo.sys.core.log.factory.LogFactory; +import vip.xiaonuo.sys.core.log.factory.LogTaskFactory; +import vip.xiaonuo.sys.modular.log.entity.SysOpLog; +import vip.xiaonuo.sys.modular.log.entity.SysVisLog; + +import javax.servlet.http.HttpServletRequest; +import java.util.TimerTask; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + + +/** + * 日志管理器 + * + * @author xuyuxiang + * @date 2020/3/12 14:13 + */ +public class LogManager { + + /** + * 异步操作记录日志的线程池 + */ + private static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(10, new ScheduledExecutorFactoryBean()); + + private LogManager() { + } + + private static final LogManager LOG_MANAGER = new LogManager(); + + public static LogManager me() { + return LOG_MANAGER; + } + + /** + * 异步执行日志的方法 + * + * @author xuyuxiang + * @date 2020/4/8 19:19 + */ + private void executeLog(TimerTask task) { + + //如果演示模式开启,则不记录日志 + if (ConstantContextHolder.getDemoEnvFlag()) { + return; + } + + //日志记录操作延时 + int operateDelayTime = 10; + EXECUTOR.schedule(task, operateDelayTime, TimeUnit.MILLISECONDS); + } + + /** + * 登录日志 + * + * @author xuyuxiang + * @date 2020/3/18 20:00 + */ + public void executeLoginLog(final String account, final String success, final String failMessage) { + SysVisLog sysVisLog = this.genBaseSysVisLog(); + TimerTask timerTask = LogTaskFactory.loginLog(sysVisLog, account, + success, + failMessage); + executeLog(timerTask); + } + + /** + * 登出日志 + * + * @author xuyuxiang + * @date 2020/3/18 20:01 + */ + public void executeExitLog(final String account) { + SysVisLog sysVisLog = this.genBaseSysVisLog(); + TimerTask timerTask = LogTaskFactory.exitLog(sysVisLog, account); + executeLog(timerTask); + } + + /** + * 操作日志 + * + * @author xuyuxiang + * @date 2020/3/18 20:01 + */ + public void executeOperationLog(BusinessLog businessLog, final String account, JoinPoint joinPoint, final String result) { + SysOpLog sysOpLog = this.genBaseSysOpLog(); + TimerTask timerTask = LogTaskFactory.operationLog(sysOpLog, account, businessLog, joinPoint, result); + executeLog(timerTask); + } + + /** + * 异常日志 + * + * @author xuyuxiang + * @date 2020/3/18 20:01 + */ + public void executeExceptionLog(BusinessLog businessLog, final String account, JoinPoint joinPoint, Exception exception) { + SysOpLog sysOpLog = this.genBaseSysOpLog(); + TimerTask timerTask = LogTaskFactory.exceptionLog(sysOpLog, account, businessLog, joinPoint, exception); + executeLog(timerTask); + } + + /** + * 构建基础访问日志 + * + * @author xuyuxiang + * @date 2020/3/19 14:44 + */ + private SysVisLog genBaseSysVisLog() { + HttpServletRequest request = HttpServletUtil.getRequest(); + if (ObjectUtil.isNotNull(request)) { + String ip = IpAddressUtil.getIp(request); + String address = IpAddressUtil.getAddress(request); + String browser = UaUtil.getBrowser(request); + String os = UaUtil.getOs(request); + return LogFactory.genBaseSysVisLog(ip, address, browser, os); + } else { + throw new ServiceException(ServerExceptionEnum.REQUEST_EMPTY); + } + } + + /** + * 构建基础操作日志 + * + * @author xuyuxiang + * @date 2020/3/19 14:44 + */ + private SysOpLog genBaseSysOpLog() { + HttpServletRequest request = HttpServletUtil.getRequest(); + if (ObjectUtil.isNotNull(request)) { + String ip = IpAddressUtil.getIp(request); + String address = IpAddressUtil.getAddress(request); + String browser = UaUtil.getBrowser(request); + String os = UaUtil.getOs(request); + String url = request.getRequestURI(); + String method = request.getMethod(); + return LogFactory.genBaseSysOpLog(ip, address, browser, os, url, method); + } else { + throw new ServiceException(ServerExceptionEnum.REQUEST_EMPTY); + } + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/factory/LogFactory.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/factory/LogFactory.java new file mode 100644 index 0000000..908ce44 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/factory/LogFactory.java @@ -0,0 +1,196 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.log.factory; + +import cn.hutool.core.date.DateTime; +import org.aspectj.lang.JoinPoint; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.util.CryptogramUtil; +import vip.xiaonuo.core.util.JoinPointUtil; +import vip.xiaonuo.sys.core.enums.LogSuccessStatusEnum; +import vip.xiaonuo.sys.core.enums.VisLogTypeEnum; +import vip.xiaonuo.sys.modular.log.entity.SysOpLog; +import vip.xiaonuo.sys.modular.log.entity.SysVisLog; + +import java.util.Arrays; + +/** + * 日志对象创建工厂 + * + * @author xuyuxiang + * @date 2020/3/12 14:31 + */ +public class LogFactory { + + /** + * 创建登录日志 + * + * @author xuyuxiang + * @date 2020/3/12 16:09 + */ + static void createSysLoginLog(SysVisLog sysVisLog, String account, String successCode, String failMessage) { + sysVisLog.setName(VisLogTypeEnum.LOGIN.getMessage()); + sysVisLog.setSuccess(successCode); + + sysVisLog.setVisType(VisLogTypeEnum.LOGIN.getCode()); + sysVisLog.setVisTime(DateTime.now()); + sysVisLog.setAccount(account); + + if (LogSuccessStatusEnum.SUCCESS.getCode().equals(successCode)) { + sysVisLog.setMessage(VisLogTypeEnum.LOGIN.getMessage() + LogSuccessStatusEnum.SUCCESS.getMessage()); + } + if (LogSuccessStatusEnum.FAIL.getCode().equals(successCode)) { + sysVisLog.setMessage(VisLogTypeEnum.LOGIN.getMessage() + + LogSuccessStatusEnum.FAIL.getMessage() + SymbolConstant.COLON + failMessage); + } + if (ConstantContextHolder.getCryptogramConfigs().getVisLogEnc()) { + SysVisLogSign(sysVisLog); + } + } + + /** + * 创建登出日志 + * + * @author xuyuxiang + * @date 2020/3/12 16:09 + */ + static void createSysExitLog(SysVisLog sysVisLog, String account) { + sysVisLog.setName(VisLogTypeEnum.EXIT.getMessage()); + sysVisLog.setSuccess(LogSuccessStatusEnum.SUCCESS.getCode()); + sysVisLog.setMessage(VisLogTypeEnum.EXIT.getMessage() + LogSuccessStatusEnum.SUCCESS.getMessage()); + sysVisLog.setVisType(VisLogTypeEnum.EXIT.getCode()); + sysVisLog.setVisTime(DateTime.now()); + sysVisLog.setAccount(account); + if (ConstantContextHolder.getCryptogramConfigs().getVisLogEnc()) { + SysVisLogSign(sysVisLog); + } + } + + /** + * 创建操作日志 + * + * @author xuyuxiang + * @date 2020/3/12 16:09 + */ + static void createSysOperationLog(SysOpLog sysOpLog, String account, BusinessLog businessLog, JoinPoint joinPoint, String result) { + fillCommonSysOpLog(sysOpLog, account, businessLog, joinPoint); + sysOpLog.setSuccess(LogSuccessStatusEnum.SUCCESS.getCode()); + sysOpLog.setResult(result); + sysOpLog.setMessage(LogSuccessStatusEnum.SUCCESS.getMessage()); + if (ConstantContextHolder.getCryptogramConfigs().getOpLogEnc()) { + SysOpLogSign(sysOpLog); + } + } + + /** + * 创建异常日志 + * + * @author xuyuxiang + * @date 2020/3/16 17:18 + */ + static void createSysExceptionLog(SysOpLog sysOpLog, String account, BusinessLog businessLog, JoinPoint joinPoint, Exception exception) { + fillCommonSysOpLog(sysOpLog, account, businessLog, joinPoint); + sysOpLog.setSuccess(LogSuccessStatusEnum.FAIL.getCode()); + sysOpLog.setMessage(Arrays.toString(exception.getStackTrace())); + if (ConstantContextHolder.getCryptogramConfigs().getOpLogEnc()) { + SysOpLogSign(sysOpLog); + } + } + + /** + * 生成通用操作日志字段 + * + * @author xuyuxiang + * @date 2020/3/16 17:20 + */ + private static void fillCommonSysOpLog(SysOpLog sysOpLog, String account, BusinessLog businessLog, JoinPoint joinPoint) { + String className = joinPoint.getTarget().getClass().getName(); + + String methodName = joinPoint.getSignature().getName(); + + String param = JoinPointUtil.getArgsJsonString(joinPoint); + + sysOpLog.setName(businessLog.title()); + sysOpLog.setOpType(businessLog.opType().ordinal()); + sysOpLog.setClassName(className); + sysOpLog.setMethodName(methodName); + sysOpLog.setParam(param); + sysOpLog.setOpTime(DateTime.now()); + sysOpLog.setAccount(account); + } + + /** + * 构建基础访问日志 + * + * @author xuyuxiang + * @date 2020/3/19 14:36 + */ + public static SysVisLog genBaseSysVisLog(String ip, String location, String browser, String os) { + SysVisLog sysVisLog = new SysVisLog(); + sysVisLog.setIp(ip); + sysVisLog.setLocation(location); + sysVisLog.setBrowser(browser); + sysVisLog.setOs(os); + return sysVisLog; + } + + /** + * 构建基础操作日志 + * + * @author xuyuxiang + * @date 2020/3/19 14:36 + */ + public static SysOpLog genBaseSysOpLog(String ip, String location, String browser, String os, String url, String method) { + SysOpLog sysOpLog = new SysOpLog(); + sysOpLog.setIp(ip); + sysOpLog.setLocation(location); + sysOpLog.setBrowser(browser); + sysOpLog.setOs(os); + sysOpLog.setUrl(url); + sysOpLog.setReqMethod(method); + return sysOpLog; + } + + /** + * 构建登录登出日志完整性保护(数据签名) + */ + private static SysVisLog SysVisLogSign (SysVisLog sysVisLog) { + String sysVisLogStr = sysVisLog.toString().replaceAll(" +",""); + sysVisLog.setSignValue(CryptogramUtil.doSignature(sysVisLogStr)); + return sysVisLog; + } + + /** + * 构建操作日志完整性保护(摘要) + */ + private static SysOpLog SysOpLogSign (SysOpLog sysOpLog) { + String sysVisLogStr = sysOpLog.toString(); + sysOpLog.setSignValue(CryptogramUtil.doHashValue(sysVisLogStr)); + return sysOpLog; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/factory/LogTaskFactory.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/factory/LogTaskFactory.java new file mode 100644 index 0000000..31dbcd3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/log/factory/LogTaskFactory.java @@ -0,0 +1,133 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.log.factory; + +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.log.Log; +import org.aspectj.lang.JoinPoint; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.context.requestno.RequestNoContext; +import vip.xiaonuo.sys.modular.log.entity.SysOpLog; +import vip.xiaonuo.sys.modular.log.entity.SysVisLog; +import vip.xiaonuo.sys.modular.log.service.SysOpLogService; +import vip.xiaonuo.sys.modular.log.service.SysVisLogService; + +import java.util.TimerTask; + + +/** + * 日志操作任务创建工厂 + * + * @author xuyuxiang + * @date 2020/3/12 14:18 + */ +public class LogTaskFactory { + + private static final Log log = Log.get(); + + private static final SysVisLogService sysVisLogService = SpringUtil.getBean(SysVisLogService.class); + + private static final SysOpLogService sysOpLogService = SpringUtil.getBean(SysOpLogService.class); + + /** + * 登录日志 + * + * @author xuyuxiang + * @date 2020/3/12 15:21 + */ + public static TimerTask loginLog(SysVisLog sysVisLog, final String account, String success, String failMessage) { + return new TimerTask() { + @Override + public void run() { + try { + LogFactory.createSysLoginLog(sysVisLog, account, success, failMessage); + sysVisLogService.save(sysVisLog); + } catch (Exception e) { + log.error(">>> 创建登录日志异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + } + } + }; + } + + /** + * 登出日志 + * + * @author xuyuxiang + * @date 2020/3/12 15:21 + */ + public static TimerTask exitLog(SysVisLog sysVisLog, String account) { + return new TimerTask() { + @Override + public void run() { + try { + LogFactory.createSysExitLog(sysVisLog, account); + sysVisLogService.save(sysVisLog); + } catch (Exception e) { + log.error(">>> 创建退出日志异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + } + } + }; + } + + /** + * 操作日志 + * + * @author xuyuxiang + * @date 2020/3/12 15:21 + */ + public static TimerTask operationLog(SysOpLog sysOpLog, String account, BusinessLog businessLog, JoinPoint joinPoint, String result) { + return new TimerTask() { + @Override + public void run() { + try { + LogFactory.createSysOperationLog(sysOpLog, account, businessLog, joinPoint, result); + sysOpLogService.save(sysOpLog); + } catch (Exception e) { + log.error(">>> 创建操作日志异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + } + } + }; + } + + /** + * 异常日志 + * + * @author xuyuxiang + * @date 2020/3/12 15:21 + */ + public static TimerTask exceptionLog(SysOpLog sysOpLog, String account, BusinessLog businessLog, JoinPoint joinPoint, Exception exception) { + return new TimerTask() { + @Override + public void run() { + try { + LogFactory.createSysExceptionLog(sysOpLog, account, businessLog, joinPoint, exception); + sysOpLogService.save(sysOpLog); + } catch (Exception e) { + log.error(">>> 创建异常日志异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + } + } + }; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/dbid/SnowyDatabaseIdProvider.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/dbid/SnowyDatabaseIdProvider.java new file mode 100644 index 0000000..da76f5e --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/dbid/SnowyDatabaseIdProvider.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.mybatis.dbid; + +import org.apache.ibatis.mapping.DatabaseIdProvider; +import vip.xiaonuo.core.enums.DbIdEnum; + +import javax.sql.DataSource; +import java.sql.SQLException; +import java.util.Properties; + +/** + * 数据库id选择器 + * + * @author yubaoshan + * @date 2019/3/30 22:26 + */ +public class SnowyDatabaseIdProvider implements DatabaseIdProvider { + + @Override + public void setProperties(Properties p) { + } + + @Override + public String getDatabaseId(DataSource dataSource) throws SQLException { + String url = dataSource.getConnection().getMetaData().getURL(); + + if (url.contains(DbIdEnum.ORACLE.getName())) { + return DbIdEnum.ORACLE.getCode(); + } else if (url.contains(DbIdEnum.PG_SQL.getName())) { + return DbIdEnum.PG_SQL.getCode(); + } else if (url.contains(DbIdEnum.MS_SQL.getName())) { + return DbIdEnum.MS_SQL.getCode(); + } else if (url.contains(DbIdEnum.DM_SQL.getName())) { + return DbIdEnum.DM_SQL.getCode(); + } else if (url.contains(DbIdEnum.KINGBASE_ES.getName())) { + return DbIdEnum.KINGBASE_ES.getCode(); + } else { + return DbIdEnum.MYSQL.getCode(); + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/fieldfill/CustomMetaObjectHandler.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/fieldfill/CustomMetaObjectHandler.java new file mode 100644 index 0000000..087b49d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/fieldfill/CustomMetaObjectHandler.java @@ -0,0 +1,102 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.mybatis.fieldfill; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.log.Log; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.ReflectionException; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.pojo.login.SysLoginUser; + +import java.util.Date; + +/** + * 自定义sql字段填充器,自动填充创建修改相关字段 + * + * @author xuyuxiang + * @date 2020/3/30 15:21 + */ +public class CustomMetaObjectHandler implements MetaObjectHandler { + + private static final Log log = Log.get(); + + private static final String CREATE_USER = "createUser"; + + private static final String CREATE_TIME = "createTime"; + + private static final String UPDATE_USER = "updateUser"; + + private static final String UPDATE_TIME = "updateTime"; + + @Override + public void insertFill(MetaObject metaObject) { + try { + //为空则设置createUser(BaseEntity) + Object createUser = metaObject.getValue(CREATE_USER); + if(ObjectUtil.isNull(createUser)) { + setFieldValByName(CREATE_USER, this.getUserUniqueId(), metaObject); + } + + //为空则设置createTime(BaseEntity) + Object createTime = metaObject.getValue(CREATE_TIME); + if(ObjectUtil.isNull(createTime)) { + setFieldValByName(CREATE_TIME, new Date(), metaObject); + } + } catch (ReflectionException e) { + log.warn(">>> CustomMetaObjectHandler处理过程中无相关字段,不做处理"); + } + } + + @Override + public void updateFill(MetaObject metaObject) { + try { + //设置updateUser(BaseEntity) + setFieldValByName(UPDATE_USER, this.getUserUniqueId(), metaObject); + //设置updateTime(BaseEntity) + setFieldValByName(UPDATE_TIME, new Date(), metaObject); + } catch (ReflectionException e) { + log.warn(">>> CustomMetaObjectHandler处理过程中无相关字段,不做处理"); + } + } + + /** + * 获取用户唯一id + */ + private Long getUserUniqueId() { + try { + SysLoginUser sysLoginUser = LoginContextHolder.me().getSysLoginUserWithoutException(); + if(ObjectUtil.isNotNull(sysLoginUser)) { + return sysLoginUser.getId(); + } else { + return -1L; + } + } catch (Exception e) { + //如果获取不到就返回-1 + return -1L; + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/sqlfilter/DemoProfileSqlInterceptor.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/sqlfilter/DemoProfileSqlInterceptor.java new file mode 100644 index 0000000..44111fa --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/mybatis/sqlfilter/DemoProfileSqlInterceptor.java @@ -0,0 +1,81 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.mybatis.sqlfilter; + +import com.baomidou.mybatisplus.core.toolkit.PluginUtils; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.springframework.util.AntPathMatcher; +import vip.xiaonuo.core.consts.SpringSecurityConstant; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.exception.DemoException; +import vip.xiaonuo.core.util.HttpServletUtil; + +import java.sql.Connection; + +/** + * 演示环境的sql过滤器,只放开select语句,其他语句都不放过 + * + * @author yubaoshan + * @date 2020/5/5 12:21 + */ +@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) +public class DemoProfileSqlInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + + // 演示环境没开,直接跳过此过滤器 + if (!ConstantContextHolder.getDemoEnvFlag()) { + return invocation.proceed(); + } + + StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget()); + MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler); + MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); + + if (SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) { + return invocation.proceed(); + } else { + + //放开不进行安全过滤的接口 + for (String notAuthResource : SpringSecurityConstant.NONE_SECURITY_URL_PATTERNS) { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + if (antPathMatcher.match(notAuthResource, HttpServletUtil.getRequest().getRequestURI())) { + return invocation.proceed(); + } + } + throw new DemoException(); + } + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/provider/CaptchaCacheServiceProvider.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/provider/CaptchaCacheServiceProvider.java new file mode 100644 index 0000000..3d08959 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/provider/CaptchaCacheServiceProvider.java @@ -0,0 +1,72 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.provider; + +import com.anji.captcha.service.CaptchaCacheService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.concurrent.TimeUnit; + +/** + * 对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis + * 如果应用是单点的,也没有使用redis,那默认使用内存。内存缓存只适合单节点部署的应用,否则验证码生产与验证在节点之间信息不同步,导致失败 + * + * @Author Jax + * @date 2021/1/21 16:27 + */ +public class CaptchaCacheServiceProvider implements CaptchaCacheService { + + private static final String REDIS = "redis"; + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + @Override + public void set(String key, String value, long expiresInSeconds) { + stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS); + } + + @Override + public boolean exists(String key) { + return stringRedisTemplate.hasKey(key); + } + + @Override + public void delete(String key) { + stringRedisTemplate.delete(key); + } + + @Override + public String get(String key) { + return stringRedisTemplate.opsForValue().get(key); + } + + @Override + public String type() { + return REDIS; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/redis/FastJson2JsonRedisSerializer.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/redis/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..e30f3c1 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/redis/FastJson2JsonRedisSerializer.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.redis; + +import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.parser.ParserConfig; +import com.alibaba.fastjson.serializer.SerializerFeature; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import java.nio.charset.Charset; + +/** + * redis序列化器 + * + * @author xuyuxiang + * @date 2020/3/30 18:30 + */ +public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> { + + private final Class<T> clazz; + + static { + ParserConfig.getGlobalInstance().setAutoTypeSupport(true); + } + + /** + * 构造函数 + * + * @author xuyuxiang + * @date 2020/4/8 19:12 + */ + public FastJson2JsonRedisSerializer(Class<T> clazz) { + super(); + this.clazz = clazz; + } + + /** + * 序列化 + * + * @author xuyuxiang + * @date 2020/4/8 19:12 + */ + @Override + public byte[] serialize(T t) throws SerializationException { + if (ObjectUtil.isEmpty(t)) { + return new byte[0]; + } + return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(Charset.defaultCharset()); + } + + /** + * 反序列化 + * + * @author xuyuxiang + * @date 2020/4/8 19:12 + */ + @Override + public T deserialize(byte[] bytes) throws SerializationException { + if (ObjectUtil.isEmpty(bytes)) { + return null; + } + String str = new String(bytes, Charset.defaultCharset()); + return (T) JSON.parseObject(str, clazz); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/scanner/ApiResourceScanner.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/scanner/ApiResourceScanner.java new file mode 100644 index 0000000..1535097 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/scanner/ApiResourceScanner.java @@ -0,0 +1,207 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.scanner; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +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.RestController; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.resources.ApiResourceContext; +import vip.xiaonuo.core.util.AopTargetUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Set; + +/** + * 资源扫描器 + * + * @author yubaoshan + * @date 2018/1/3 14:58 + */ +public class ApiResourceScanner implements BeanPostProcessor { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + + //如果controller是代理对象,则需要获取原始类的信息 + Object aopTarget = AopTargetUtil.getTarget(bean); + + if (aopTarget == null) { + aopTarget = bean; + } + Class<?> clazz = aopTarget.getClass(); + + //判断是不是控制器,不是控制器就略过 + boolean controllerFlag = getControllerFlag(clazz); + if (!controllerFlag) { + return bean; + } + + //扫描控制器的所有带ApiResource注解的方法 + Set<String> apiUrls = doScan(clazz); + + //将扫描到的注解存储到缓存 + persistApiResources(apiUrls); + + return bean; + } + + + /** + * 判断一个类是否是控制器 + * + * @author yubaoshan + * @date 2020/6/21 18:23 + */ + private boolean getControllerFlag(Class<?> clazz) { + Annotation[] annotations = clazz.getAnnotations(); + + for (Annotation annotation : annotations) { + if (RestController.class.equals(annotation.annotationType()) || Controller.class.equals(annotation.annotationType())) { + return true; + } + } + return false; + } + + /** + * 扫描整个类中包含的所有控制器 + * + * @author yubaoshan + * @date 2020/6/21 18:23 + */ + private Set<String> doScan(Class<?> clazz) { + + // 获取类头的@RequestMapping内容 + String primaryMappingUrl = getRequestMappingUrl(clazz); + + // 获取所有方法的RequestMapping的url + Set<String> apiResources = CollectionUtil.newHashSet(); + Method[] declaredMethods = clazz.getDeclaredMethods(); + if (declaredMethods.length > 0) { + for (Method declaredMethod : declaredMethods) { + String requestMappingUrl = getRequestMappingUrl(declaredMethod); + apiResources.add(primaryMappingUrl + requestMappingUrl); + } + } + return apiResources; + } + + /** + * 存储扫描到的api资源 + * + * @author yubaoshan + * @date 2020/6/21 17:43 + */ + private void persistApiResources(Set<String> apiResources) { + ApiResourceContext.addBatchUrls(apiResources); + } + + /** + * 获取@RequestMapping注解的url信息 + * + * @author yubaoshan + * @date 2020/6/21 17:43 + */ + private String getRequestMappingUrl(AnnotatedElement annotatedElement) { + + RequestMapping requestMapping = annotatedElement.getAnnotation(RequestMapping.class); + GetMapping getMapping = annotatedElement.getAnnotation(GetMapping.class); + PostMapping postMapping = annotatedElement.getAnnotation(PostMapping.class); + + // 分别判断三个注解中有没有value和path的值,优先级是 RequestMapping > GetMapping > PostMapping + if (requestMapping != null) { + String[] value = requestMapping.value(); + String[] path = requestMapping.path(); + if (value.length > 0) { + return getRequestMappingPath(value); + } else if (path.length > 0) { + return getRequestMappingPath(path); + } + } else if (getMapping != null) { + String[] value = getMapping.value(); + String[] path = getMapping.path(); + if (value.length > 0) { + return getRequestMappingPath(value); + } else if (path.length > 0) { + return getRequestMappingPath(path); + } + } else if (postMapping != null) { + String[] value = postMapping.value(); + String[] path = postMapping.path(); + if (value.length > 0) { + return getRequestMappingPath(value); + } else if (path.length > 0) { + return getRequestMappingPath(path); + } + } + + return ""; + } + + /** + * 获取数组第一个字符串 + * + * @author yubaoshan + * @date 2020/6/21 18:10 + */ + private String getRequestMappingPath(String[] strings) { + if (strings.length == 0) { + return ""; + } + String result = strings[0]; + + // 如果RequestMapping的值是“/”直接返回 + if (SymbolConstant.LEFT_DIVIDE.equals(result)) { + return result; + } + + // 添加路径首部的"/" + if (!result.startsWith(SymbolConstant.LEFT_DIVIDE)) { + result = SymbolConstant.LEFT_DIVIDE + result; + } + + // 则去掉尾部的"/" + if (result.endsWith(SymbolConstant.LEFT_DIVIDE)) { + result = StrUtil.removeSuffix(result, SymbolConstant.LEFT_DIVIDE); + } + + return result; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/validator/SnowyValidator.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/validator/SnowyValidator.java new file mode 100644 index 0000000..5e0af1c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/core/validator/SnowyValidator.java @@ -0,0 +1,118 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ + +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.core.validator; + +import cn.hutool.log.Log; +import org.springframework.validation.Errors; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.context.group.RequestGroupContext; +import vip.xiaonuo.core.context.group.RequestParamIdContext; + +import java.beans.PropertyDescriptor; + +/** + * 用于真正校验参数之前缓存一下group的class类型 + * <p> + * 因为ConstraintValidator的自定义校验中获取不到当前进行的group + * + * @author xuyuxiang + * @date 2020/8/12 20:07 + */ +public class SnowyValidator extends LocalValidatorFactoryBean { + + private static final Log log = Log.get(); + + @Override + public void validate(Object target, Errors errors, Object... validationHints) { + + try { + if (validationHints.length > 0) { + + // 如果是class类型,利用ThreadLocal缓存一下class类型 + if (validationHints[0] instanceof Class) { + + // 临时保存group的class值 + RequestGroupContext.set((Class<?>) validationHints[0]); + + // 临时保存字段为id的值 + RequestParamIdContext.set(getParamIdValue(target)); + } + } + super.validate(target, errors, validationHints); + } finally { + RequestGroupContext.clear(); + RequestParamIdContext.clear(); + } + } + + /** + * 获取参数中的id的值,如果没有id字段就返回null + * + * @author xuyuxiang + * @date 2020/8/12 21:24 + */ + private Long getParamIdValue(Object target) { + + try { + PropertyDescriptor prop = new PropertyDescriptor(CommonConstant.ID, target.getClass()); + Object paramId = prop.getReadMethod().invoke(target); + if (paramId != null) { + if (paramId instanceof Long) { + return (Long) paramId; + } + } + } catch (Exception e) { + log.error(">>> 获取参数的id值时候出错:{}", e.getMessage()); + } + + return null; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/IndexController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/IndexController.java new file mode 100644 index 0000000..95254c2 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/IndexController.java @@ -0,0 +1,50 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.consts.CommonConstant; + +/** + * 首页控制器 + * + * @author xuyuxiang + * @date 2020/3/18 11:20 + */ +@RestController +public class IndexController { + + /** + * 访问首页,提示语 + * + * @author xuyuxiang + * @date 2020/4/8 19:27 + */ + @RequestMapping("/") + public String index() { + return CommonConstant.INDEX_TIPS; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/controller/SysAppController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/controller/SysAppController.java new file mode 100644 index 0000000..ef4e43d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/controller/SysAppController.java @@ -0,0 +1,148 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.app.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.app.param.SysAppParam; +import vip.xiaonuo.sys.modular.app.service.SysAppService; + +import javax.annotation.Resource; + +/** + * 系统应用控制器 + * + * @author xuyuxiang + * @date 2020/3/20 21:25 + */ +@RestController +public class SysAppController { + + @Resource + private SysAppService sysAppService; + + /** + * 查询系统应用 + * + * @author xuyuxiang + * @date 2020/3/20 21:23 + */ + @Permission + @GetMapping("/sysApp/page") + @BusinessLog(title = "系统应用_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysAppParam sysAppParam) { + return new SuccessResponseData(sysAppService.page(sysAppParam)); + } + + /** + * 添加系统应用 + * + * @author xuyuxiang + * @date 2020/3/25 14:44 + */ + @Permission + @PostMapping("/sysApp/add") + @BusinessLog(title = "系统应用_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysAppParam.add.class) SysAppParam sysAppParam) { + sysAppService.add(sysAppParam); + return new SuccessResponseData(); + } + + /** + * 删除系统应用 + * + * @author xuyuxiang + * @date 2020/3/25 14:54 + */ + @Permission + @PostMapping("/sysApp/delete") + @BusinessLog(title = "系统应用_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysAppParam.delete.class) SysAppParam sysAppParam) { + sysAppService.delete(sysAppParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统应用 + * + * @author xuyuxiang + * @date 2020/3/25 14:54 + */ + @Permission + @PostMapping("/sysApp/edit") + @BusinessLog(title = "系统应用_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysAppParam.edit.class) SysAppParam sysAppParam) { + sysAppService.edit(sysAppParam); + return new SuccessResponseData(); + } + + /** + * 查看系统应用 + * + * @author xuyuxiang + * @date 2020/3/26 9:49 + */ + @Permission + @GetMapping("/sysApp/detail") + @BusinessLog(title = "系统应用_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysAppParam.detail.class) SysAppParam sysAppParam) { + return new SuccessResponseData(sysAppService.detail(sysAppParam)); + } + + /** + * 系统应用列表 + * + * @author xuyuxiang + * @date 2020/4/19 14:55 + */ + @Permission + @GetMapping("/sysApp/list") + @BusinessLog(title = "系统应用_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysAppParam sysAppParam) { + return new SuccessResponseData(sysAppService.list(sysAppParam)); + } + + /** + * 设为默认应用 + * + * @author xuyuxiang + * @date 2020/6/29 16:49 + */ + @Permission + @PostMapping("/sysApp/setAsDefault") + @BusinessLog(title = "系统应用_设为默认应用", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData setAsDefault(@RequestBody @Validated(SysAppParam.detail.class) SysAppParam sysAppParam) { + sysAppService.setAsDefault(sysAppParam); + return new SuccessResponseData(); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/entity/SysApp.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/entity/SysApp.java new file mode 100644 index 0000000..e0cf54d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/entity/SysApp.java @@ -0,0 +1,71 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.app.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 系统应用表 + * + * @author xuyuxiang + * @date 2020/3/11 12:14 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_app") +public class SysApp extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 是否默认激活(Y-是,N-否),只能有一个系统默认激活 + * 用户登录后默认展示此系统菜单 + */ + private String active; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/enums/SysAppExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/enums/SysAppExceptionEnum.java new file mode 100644 index 0000000..d5befd7 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/enums/SysAppExceptionEnum.java @@ -0,0 +1,85 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.app.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统应用相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/26 10:11 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_APP_EXCEPTION_ENUM) +public enum SysAppExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 应用不存在 + */ + APP_NOT_EXIST(1, "应用不存在"), + + /** + * 应用编码重复 + */ + APP_CODE_REPEAT(2, "应用编码重复,请检查code参数"), + + /** + * 应用名称重复 + */ + APP_NAME_REPEAT(3, "应用名称重复,请检查name参数"), + + /** + * 默认激活的系统只能有一个 + */ + APP_ACTIVE_REPEAT(4, "默认激活的系统只能有一个,请检查active参数"), + + /** + * 该应用下有菜单 + */ + APP_CANNOT_DELETE(5, "该应用下有菜单,无法删除"); + + private final Integer code; + + private final String message; + + SysAppExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/mapper/SysAppMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/mapper/SysAppMapper.java new file mode 100644 index 0000000..3a174d6 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/mapper/SysAppMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.app.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.app.entity.SysApp; + +/** + * 系统应用mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:17 + */ +public interface SysAppMapper extends BaseMapper<SysApp> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/mapper/mapping/SysAppMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/mapper/mapping/SysAppMapper.xml new file mode 100644 index 0000000..bf8ca4b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/mapper/mapping/SysAppMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.app.mapper.SysAppMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/param/SysAppParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/param/SysAppParam.java new file mode 100644 index 0000000..0d821a3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/param/SysAppParam.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.app.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; +import vip.xiaonuo.core.validation.flag.FlagValue; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统应用参数 + * + * @author xuyuxiang + * @date 2020/3/24 20:53 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysAppParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + private String code; + + /** + * 是否默认激活(Y-是,N-否),只能有一个系统默认激活 + * 用户登录后默认展示此系统菜单 + */ + @NotBlank(message = "是否默认激活不能为空,请检查active参数", groups = {add.class, edit.class}) + @FlagValue(message = "是否默认激活格式错误,正确格式应该Y或者N,请检查active参数", groups = {add.class, edit.class}) + private String active; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/service/SysAppService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/service/SysAppService.java new file mode 100644 index 0000000..3d39643 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/service/SysAppService.java @@ -0,0 +1,119 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.app.service; + +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.app.entity.SysApp; +import vip.xiaonuo.sys.modular.app.param.SysAppParam; + +import java.util.List; + +/** + * 系统应用service接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:14 + */ +public interface SysAppService extends IService<SysApp> { + + /** + * 获取用户应用相关信息 + * + * @param userId 用户id + * @param roleIdList 角色id集合 + * @return 用户拥有的应用信息,格式:[{"code":"system","name":"系统应用","active":true}] + * @author xuyuxiang + * @date 2020/3/13 16:25 + */ + List<Dict> getLoginApps(Long userId, List<Long> roleIdList); + + /** + * 查询系统应用 + * + * @param sysAppParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/3/24 20:55 + */ + PageResult<SysApp> page(SysAppParam sysAppParam); + + /** + * 添加系统应用 + * + * @param sysAppParam 添加参数 + * @author xuyuxiang + * @date 2020/3/25 14:57 + */ + void add(SysAppParam sysAppParam); + + /** + * 删除系统应用 + * + * @param sysAppParam 删除参数 + * @author xuyuxiang + * @date 2020/3/25 14:57 + */ + void delete(SysAppParam sysAppParam); + + /** + * 编辑系统应用 + * + * @param sysAppParam 编辑参数 + * @author xuyuxiang + * @date 2020/3/25 14:58 + */ + void edit(SysAppParam sysAppParam); + + /** + * 查看系统应用 + * + * @param sysAppParam 查看参数 + * @return 系统应用 + * @author xuyuxiang + * @date 2020/3/26 9:50 + */ + SysApp detail(SysAppParam sysAppParam); + + /** + * 系统应用列表 + * + * @param sysAppParam 查询参数 + * @return 系统应用列表 + * @author xuyuxiang + * @date 2020/4/19 14:56 + */ + List<SysApp> list(SysAppParam sysAppParam); + + /** + * 设为默认应用 + * + * @param sysAppParam 设为默认应用参数 + * @author xuyuxiang + * @date 2020/6/29 16:49 + */ + void setAsDefault(SysAppParam sysAppParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/service/impl/SysAppServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/service/impl/SysAppServiceImpl.java new file mode 100644 index 0000000..ea28ebd --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/app/service/impl/SysAppServiceImpl.java @@ -0,0 +1,265 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.app.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.enums.YesOrNotEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.core.enums.AdminTypeEnum; +import vip.xiaonuo.sys.modular.app.entity.SysApp; +import vip.xiaonuo.sys.modular.app.enums.SysAppExceptionEnum; +import vip.xiaonuo.sys.modular.app.mapper.SysAppMapper; +import vip.xiaonuo.sys.modular.app.param.SysAppParam; +import vip.xiaonuo.sys.modular.app.service.SysAppService; +import vip.xiaonuo.sys.modular.menu.service.SysMenuService; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.annotation.Resource; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * 系统应用service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 16:15 + */ +@Service +public class SysAppServiceImpl extends ServiceImpl<SysAppMapper, SysApp> implements SysAppService { + + @Resource + private SysUserService sysUserService; + + @Resource + private SysMenuService sysMenuService; + + @Override + public List<Dict> getLoginApps(Long userId, List<Long> roleIdList) { + List<Dict> userAppDictList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper<SysApp> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysApp::getStatus, CommonStatusEnum.ENABLE.getCode()); + + + SysUser sysUser = sysUserService.getById(userId); + Integer adminType = sysUser.getAdminType(); + + //如果不是超级管理员则有自己的菜单对应的应用编码 + if (!AdminTypeEnum.SUPER_ADMIN.getCode().equals(adminType)) { + //获取用户菜单对应的应用编码集合 + List<String> appCodeList = sysMenuService.getUserMenuAppCodeList(userId, roleIdList); + //当应用编码不为空时,则限制查询范围 + if (ObjectUtil.isNotEmpty(appCodeList)) { + queryWrapper.in(SysApp::getCode, appCodeList); + } else { + //没查到应用编码则直接返回 + return userAppDictList; + } + } + //定义是否有默认激活的应用标志 + AtomicBoolean hasDefaultActive = new AtomicBoolean(false); + //遍历 + this.list(queryWrapper).forEach(sysApp -> { + Dict dict = Dict.create(); + dict.put(CommonConstant.CODE, sysApp.getCode()); + dict.put(CommonConstant.NAME, sysApp.getName()); + //如果有默认激活的 + if (YesOrNotEnum.Y.getCode().equals(sysApp.getActive())) { + hasDefaultActive.set(true); + dict.put("active", true); + //将其放在第一个 + userAppDictList.add(0, dict); + } else { + dict.put("active", false); + userAppDictList.add(dict); + } + + }); + if (ObjectUtil.isNotEmpty(userAppDictList)) { + //如果默认激活的系统没有,则第一个为默认激活的系统 + if (!hasDefaultActive.get()) { + Dict dict = userAppDictList.get(0); + dict.put("active", true); + } + } + return userAppDictList; + } + + @Override + public PageResult<SysApp> page(SysAppParam sysAppParam) { + LambdaQueryWrapper<SysApp> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysAppParam)) { + //根据名称模糊查询 + if (ObjectUtil.isNotEmpty(sysAppParam.getName())) { + queryWrapper.like(SysApp::getName, sysAppParam.getName()); + } + //根据编码模糊查询 + if (ObjectUtil.isNotEmpty(sysAppParam.getCode())) { + queryWrapper.like(SysApp::getCode, sysAppParam.getCode()); + } + } + queryWrapper.eq(SysApp::getStatus, CommonStatusEnum.ENABLE.getCode()); + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public void add(SysAppParam sysAppParam) { + //校验参数,检查是否存在相同的名称和编码,以及默认激活的系统的数量是否合理 + checkParam(sysAppParam, false); + SysApp sysApp = new SysApp(); + BeanUtil.copyProperties(sysAppParam, sysApp); + sysApp.setStatus(CommonStatusEnum.ENABLE.getCode()); + this.save(sysApp); + } + + @Override + public void delete(SysAppParam sysAppParam) { + SysApp sysApp = this.querySysApp(sysAppParam); + String code = sysApp.getCode(); + //该应用下是否有状态为正常的菜单 + boolean hasMenu = sysMenuService.hasMenu(code); + //只要有,则不能删 + if (hasMenu) { + throw new ServiceException(SysAppExceptionEnum.APP_CANNOT_DELETE); + } + sysApp.setStatus(CommonStatusEnum.DELETED.getCode()); + this.updateById(sysApp); + } + + @Override + public void edit(SysAppParam sysAppParam) { + SysApp sysApp = this.querySysApp(sysAppParam); + //校验参数,检查是否存在相同的名称和编码,以及默认激活的系统的数量是否合理 + checkParam(sysAppParam, true); + BeanUtil.copyProperties(sysAppParam, sysApp); + //不能修改状态,用修改状态接口修改状态 + sysApp.setStatus(null); + this.updateById(sysApp); + } + + @Override + public SysApp detail(SysAppParam sysAppParam) { + return this.querySysApp(sysAppParam); + } + + @Override + public List<SysApp> list(SysAppParam sysAppParam) { + LambdaQueryWrapper<SysApp> appQueryWrapper = new LambdaQueryWrapper<>(); + appQueryWrapper.eq(SysApp::getStatus, CommonStatusEnum.ENABLE.getCode()); + return this.list(appQueryWrapper); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void setAsDefault(SysAppParam sysAppParam) { + SysApp currentApp = this.querySysApp(sysAppParam); + //所有已激活的改为未激活 + LambdaQueryWrapper<SysApp> appQueryWrapper = new LambdaQueryWrapper<>(); + appQueryWrapper.eq(SysApp::getStatus, CommonStatusEnum.ENABLE.getCode()) + .eq(SysApp::getActive, YesOrNotEnum.Y.getCode()); + this.list(appQueryWrapper).forEach(sysApp -> { + sysApp.setActive(YesOrNotEnum.N.getCode()); + this.updateById(sysApp); + }); + //当前的设置为已激活 + currentApp.setActive(YesOrNotEnum.Y.getCode()); + this.updateById(currentApp); + } + + /** + * 校验参数,检查是否存在相同的名称和编码,以及默认激活的系统的数量是否合理 + * + * @author xuyuxiang + * @date 2020/3/25 21:23 + */ + private void checkParam(SysAppParam sysAppParam, boolean isExcludeSelf) { + Long id = sysAppParam.getId(); + String name = sysAppParam.getName(); + String code = sysAppParam.getCode(); + String active = sysAppParam.getActive(); + + // 查询名称有无重复 + LambdaQueryWrapper<SysApp> appQueryWrapperByName = new LambdaQueryWrapper<>(); + appQueryWrapperByName.eq(SysApp::getName, name) + .ne(SysApp::getStatus, CommonStatusEnum.DELETED.getCode()); + + // 查询编码有无重复 + LambdaQueryWrapper<SysApp> appQueryWrapperByCode = new LambdaQueryWrapper<>(); + appQueryWrapperByCode.eq(SysApp::getCode, code) + .ne(SysApp::getStatus, CommonStatusEnum.DELETED.getCode()); + + // 查询激活状态有无已经有Y的,也就是激活的 + LambdaQueryWrapper<SysApp> appQueryWrapperByActive = new LambdaQueryWrapper<>(); + appQueryWrapperByActive.eq(SysApp::getActive, active) + .ne(SysApp::getStatus, CommonStatusEnum.DELETED.getCode()); + + if (isExcludeSelf) { + appQueryWrapperByName.ne(SysApp::getId, id); + appQueryWrapperByCode.ne(SysApp::getId, id); + appQueryWrapperByActive.ne(SysApp::getId, id); + } + int countByName = this.count(appQueryWrapperByName); + int countByCode = this.count(appQueryWrapperByCode); + int countByActive = this.count(appQueryWrapperByActive); + + if (countByName >= 1) { + throw new ServiceException(SysAppExceptionEnum.APP_NAME_REPEAT); + } + if (countByCode >= 1) { + throw new ServiceException(SysAppExceptionEnum.APP_CODE_REPEAT); + } + + // 只判断激活状态为Y时候数量是否大于1了 + if (countByActive >= 1 && YesOrNotEnum.Y.getCode().equals(sysAppParam.getActive())) { + throw new ServiceException(SysAppExceptionEnum.APP_ACTIVE_REPEAT); + } + } + + /** + * 获取系统应用 + * + * @author xuyuxiang + * @date 2020/3/26 9:56 + */ + private SysApp querySysApp(SysAppParam sysAppParam) { + SysApp sysApp = this.getById(sysAppParam.getId()); + if (ObjectUtil.isNull(sysApp)) { + throw new ServiceException(SysAppExceptionEnum.APP_NOT_EXIST); + } + return sysApp; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/controller/SysAreaController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/controller/SysAreaController.java new file mode 100644 index 0000000..d796b77 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/controller/SysAreaController.java @@ -0,0 +1,63 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.area.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.area.param.SysAreaParam; +import vip.xiaonuo.sys.modular.area.service.SysAreaService; + +import javax.annotation.Resource; + +/** + * 系统区域控制器 + * + * @author xuyuxiang + * @date 2020/3/31 20:49 + */ +@RestController +public class SysAreaController { + + @Resource + private SysAreaService sysAreaService; + + /** + * 系统区域列表(树) + * + * @author xuyuxiang + * @date 2020/3/20 21:23 + */ + @Permission + @GetMapping("/sysArea/list") + @BusinessLog(title = "系统区域_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysAreaParam sysAreaParam) { + return new SuccessResponseData(sysAreaService.list(sysAreaParam)); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/entity/SysArea.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/entity/SysArea.java new file mode 100644 index 0000000..4315bb6 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/entity/SysArea.java @@ -0,0 +1,125 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.area.entity; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import vip.xiaonuo.core.pojo.base.node.BaseTreeNode; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 系统区域表 + * + * @author xuyuxiang + * @date 2020/3/11 12:08 + */ +@Data +@TableName("sys_area") +public class SysArea implements BaseTreeNode { + + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 层级 + */ + private Integer levelCode; + + /** + * 父级行政代码 + */ + private String parentCode; + + /** + * 行政代码 + */ + private String areaCode; + + /** + * 邮政编码 + */ + private String zipCode; + + /** + * 区号 + */ + private String cityCode; + + /** + * 名称 + */ + private String name; + + /** + * 简称 + */ + private String shortName; + + /** + * 组合名 + */ + private String mergerName; + + /** + * 拼音 + */ + private String pinyin; + + /** + * 经度 + */ + private BigDecimal lng; + + /** + * 纬度 + */ + private BigDecimal lat; + + /** + * 子节点(表中不存在,用于构造树) + */ + @TableField(exist = false) + private List children = CollectionUtil.newArrayList(); + + @Override + public Long getPid() { + return Convert.toLong(parentCode); + } + + @Override + public void setChildren(List children) { + this.children = children; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/mapper/SysAreaMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/mapper/SysAreaMapper.java new file mode 100644 index 0000000..3afc908 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/mapper/SysAreaMapper.java @@ -0,0 +1,38 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.area.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.area.entity.SysArea; + +/** + * 系统区域mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:12 + */ +public interface SysAreaMapper extends BaseMapper<SysArea> { + +} \ No newline at end of file diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/mapper/mapping/SysAreaMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/mapper/mapping/SysAreaMapper.xml new file mode 100644 index 0000000..1d3f0b4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/mapper/mapping/SysAreaMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.area.mapper.SysAreaMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/param/SysAreaParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/param/SysAreaParam.java new file mode 100644 index 0000000..81b751f --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/param/SysAreaParam.java @@ -0,0 +1,102 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.area.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import java.math.BigDecimal; + +/** + * 系统字典值参数 + * + * @author xuyuxiang + * @date 2020/3/31 20:32 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysAreaParam extends BaseParam { + + /** + * 主键 + */ + private Long id; + + /** + * 层级 + */ + private Integer levelCode; + + /** + * 父级行政代码 + */ + private String parentCode; + + /** + * 行政代码 + */ + private String areaCode; + + /** + * 邮政编码 + */ + private String zipCode; + + /** + * 区号 + */ + private String cityCode; + + /** + * 名称 + */ + private String name; + + /** + * 简称 + */ + private String shortName; + + /** + * 组合名 + */ + private String mergerName; + + /** + * 拼音 + */ + private String pinyin; + + /** + * 经度 + */ + private BigDecimal lng; + + /** + * 纬度 + */ + private BigDecimal lat; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/service/SysAreaService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/service/SysAreaService.java new file mode 100644 index 0000000..feeb6a0 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/service/SysAreaService.java @@ -0,0 +1,50 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.area.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.sys.modular.area.entity.SysArea; +import vip.xiaonuo.sys.modular.area.param.SysAreaParam; + +import java.util.List; + +/** + * 系统字典值service接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:10 + */ +public interface SysAreaService extends IService<SysArea> { + + /** + * 系统区域列表(树表) + * + * @param sysAreaParam 查询参数 + * @return 区域树表列表 + * @author xuyuxiang + * @date 2020/3/26 10:19 + */ + List<SysArea> list(SysAreaParam sysAreaParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/service/impl/SysAreaServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/service/impl/SysAreaServiceImpl.java new file mode 100644 index 0000000..e51248b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/area/service/impl/SysAreaServiceImpl.java @@ -0,0 +1,59 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.area.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.sys.modular.area.entity.SysArea; +import vip.xiaonuo.sys.modular.area.mapper.SysAreaMapper; +import vip.xiaonuo.sys.modular.area.param.SysAreaParam; +import vip.xiaonuo.sys.modular.area.service.SysAreaService; + +import java.util.List; + +/** + * 系统区域service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 16:11 + */ +@Service +public class SysAreaServiceImpl extends ServiceImpl<SysAreaMapper, SysArea> implements SysAreaService { + + @Override + public List<SysArea> list(SysAreaParam sysAreaParam) { + LambdaQueryWrapper<SysArea> queryWrapper = new LambdaQueryWrapper<>(); + if(ObjectUtil.isNotNull(sysAreaParam)) { + if(ObjectUtil.isNotEmpty(sysAreaParam.getParentCode())) { + queryWrapper.eq(SysArea::getParentCode, sysAreaParam.getParentCode()); + } else { + queryWrapper.eq(SysArea::getParentCode, "0"); + } + } + return this.list(queryWrapper); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/context/LoginContextSpringSecurityImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/context/LoginContextSpringSecurityImpl.java new file mode 100644 index 0000000..f2d54dc --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/context/LoginContextSpringSecurityImpl.java @@ -0,0 +1,291 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.auth.context; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.login.LoginContext; +import vip.xiaonuo.core.exception.AuthException; +import vip.xiaonuo.core.exception.enums.AuthExceptionEnum; +import vip.xiaonuo.core.pojo.login.LoginEmpInfo; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.sys.core.enums.AdminTypeEnum; +import vip.xiaonuo.sys.modular.auth.service.AuthService; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 登录用户上下文实现类 + * + * @author xuyuxiang + * @date 2020/3/13 12:19 + */ +@Component +public class LoginContextSpringSecurityImpl implements LoginContext { + + @Resource + private AuthService authService; + + @Resource + private SysUserService sysUserService; + + private LoginContextSpringSecurityImpl() { + + } + + /** + * 获取当前登录用户 + * + * @author xuyuxiang + * @date 2020/3/13 14:42 + */ + @Override + public SysLoginUser getSysLoginUser() { + Authentication authentication = authService.getAuthentication(); + if (ObjectUtil.isEmpty(authentication) || authentication.getPrincipal() instanceof String) { + throw new AuthException(AuthExceptionEnum.NO_LOGIN_USER); + } else { + return (SysLoginUser) authentication.getPrincipal(); + } + } + + /** + * 获取当前登录用户,如未登录,则返回null,不抛异常 + * + * @author xuyuxiang + * @date 2020/3/13 14:42 + */ + @Override + public SysLoginUser getSysLoginUserWithoutException() { + Authentication authentication = authService.getAuthentication(); + if (ObjectUtil.isEmpty(authentication) || authentication.getPrincipal() instanceof String) { + return null; + } else { + return (SysLoginUser) authentication.getPrincipal(); + } + } + + /** + * 获取当前登录用户的id + * + * @author xuyuxiang + * @date 2020/3/18 19:26 + */ + @Override + public Long getSysLoginUserId() { + return this.getSysLoginUser().getId(); + } + + /** + * 判断用户是否登录 + * + * @author xuyuxiang + * @date 2020/3/18 19:23 + */ + @Override + public boolean hasLogin() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (ObjectUtil.isEmpty(authentication) || authentication.getPrincipal() instanceof String) { + return false; + } else { + return !(authentication instanceof AnonymousAuthenticationToken); + } + } + + /** + * 获取当前登录的用户账号 + * + * @author xuyuxiang + * @date 2020/3/23 8:49 + */ + @Override + public String getSysLoginUserAccount() { + return this.getSysLoginUser().getAccount(); + } + + /** + * 判断当前登录用户是否有某资源的访问权限 + * + * @author xuyuxiang + * @date 2020/3/23 8:49 + */ + @Override + public boolean hasPermission(String requestUri) { + String removePrefix = StrUtil.removePrefix(requestUri, SymbolConstant.LEFT_DIVIDE); + String requestPermission = removePrefix.replaceAll(SymbolConstant.LEFT_DIVIDE, SymbolConstant.COLON); + return this.getSysLoginUser().getPermissions().contains(requestPermission); + } + + /** + * 判断当前登录用户是否包含某个角色 + * + * @author xuyuxiang + * @date 2020/3/23 8:55 + */ + @Override + public boolean hasRole(String roleCode) { + List<String> roleCodeList = this.getLoginUserRoleCodeList(); + return roleCodeList.contains(roleCode); + } + + /** + * 判断当前登录用户是否包含任意一个角色 + * + * @author xuyuxiang + * @date 2020/3/23 8:55 + */ + @Override + public boolean hasAnyRole(String roleCodes) { + boolean flag = false; + List<String> loginUserRoleCodeList = this.getLoginUserRoleCodeList(); + String[] roleCodeArr = StrUtil.split(roleCodes, SymbolConstant.COMMA); + for (String roleCode : roleCodeArr) { + if (loginUserRoleCodeList.contains(roleCode)) { + flag = true; + break; + } + } + return flag; + } + + /** + * 管理员类型(0超级管理员 1非管理员) + * 判断当前登录用户是否是超级管理员 + * + * @author xuyuxiang + * @date 2020/3/23 17:51 + */ + @Override + public boolean isSuperAdmin() { + return this.isAdmin(AdminTypeEnum.SUPER_ADMIN.getCode()); + } + + /** + * 判断当前登录用户是否包含所有角色 + * + * @author xuyuxiang + * @date 2020/4/5 10:28 + */ + @Override + public boolean hasAllRole(String roleCodes) { + boolean flag = true; + List<String> loginUserRoleCodeList = this.getLoginUserRoleCodeList(); + String[] roleCodeArr = StrUtil.split(roleCodes, SymbolConstant.COMMA); + for (String roleCode : roleCodeArr) { + if (!loginUserRoleCodeList.contains(roleCode)) { + flag = false; + break; + } + } + return flag; + } + + /** + * 判断当前登录用户是否是指定类型的管理员 + * + * @author xuyuxiang + * @date 2020/4/5 11:43 + */ + private boolean isAdmin(Integer adminTypeCode) { + Integer adminType = this.getSysLoginUser().getAdminType(); + boolean flag = false; + if (adminType.equals(adminTypeCode)) { + flag = true; + } + return flag; + } + + /** + * 当前登录用户的数据范围(组织机构id集合) + * + * @author xuyuxiang + * @date 2020/4/5 17:20 + */ + @Override + public List<Long> getLoginUserDataScopeIdList() { + return this.getSysLoginUser().getDataScopes(); + } + + /** + * 获取当前登录用户的组织机构id集合 + * + * @author xuyuxiang + * @date 2020/4/5 18:32 + */ + @Override + public Long getSysLoginUserOrgId() { + LoginEmpInfo loginEmpInfo = this.getSysLoginUser().getLoginEmpInfo(); + if (ObjectUtil.isNotNull(loginEmpInfo)) { + if (ObjectUtil.isNotEmpty(loginEmpInfo.getOrgId())) { + return loginEmpInfo.getOrgId(); + } + } + return null; + } + + /** + * 获取当前登录用户的角色id集合 + * + * @author xuyuxiang + * @date 2020/4/20 16:04 + */ + @Override + public List<String> getLoginUserRoleIds() { + List<String> resultList = CollectionUtil.newArrayList(); + this.getSysLoginUser().getRoles().forEach(dict -> resultList.add(dict.getStr(CommonConstant.ID))); + return resultList; + } + + @Override + public SysLoginUser getSysLoginUserUpToDate() { + SysLoginUser sysLoginUser = this.getSysLoginUser(); + Long loginUserId = sysLoginUser.getId(); + SysUser sysUser = sysUserService.getById(loginUserId); + //构造SysLoginUser + return authService.genSysLoginUser(sysUser); + } + + /** + * 获取当前用户的角色编码集合 + * + * @author xuyuxiang + * @date 2020/3/23 8:58 + */ + private List<String> getLoginUserRoleCodeList() { + List<String> roleCodeList = CollectionUtil.newArrayList(); + this.getSysLoginUser().getRoles().forEach(dict -> roleCodeList.add(dict.getStr(CommonConstant.CODE))); + return roleCodeList; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/controller/SysLoginController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/controller/SysLoginController.java new file mode 100644 index 0000000..f23626c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/controller/SysLoginController.java @@ -0,0 +1,146 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.auth.controller; + +import cn.hutool.core.lang.Dict; +import com.anji.captcha.model.vo.CaptchaVO; +import com.anji.captcha.service.CaptchaService; +import org.springframework.context.annotation.Lazy; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.exception.AuthException; +import vip.xiaonuo.core.exception.enums.AuthExceptionEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.auth.service.AuthService; + +import javax.annotation.Resource; + +/** + * 登录控制器 + * + * @author xuyuxiang + * @date 2020/3/11 12:20 + */ +@RestController +public class SysLoginController { + + @Resource + private AuthService authService; + + @Lazy + @Resource + private CaptchaService captchaService; + + /** + * 获取是否开启租户的标识 + * + * @author xuyuxiang + * @date 2020/9/4 + */ + @GetMapping("/getTenantOpen") + public ResponseData getTenantOpen() { + return new SuccessResponseData(ConstantContextHolder.getTenantOpenFlag()); + } + + /** + * 账号密码登录 + * + * @author xuyuxiang + * @date 2020/3/11 15:52 + */ + @PostMapping("/login") + public ResponseData login(@RequestBody Dict dict) { + String account = dict.getStr("account"); + String password = dict.getStr("password"); + String tenantCode = dict.getStr("tenantCode"); + + //检测是否开启验证码 + if (ConstantContextHolder.getCaptchaOpenFlag()) { + verificationCode(dict.getStr("code")); + } + + //如果系统开启了多租户开关,则添加租户的临时缓存 + if (ConstantContextHolder.getTenantOpenFlag()) { + authService.cacheTenantInfo(tenantCode); + } + + String token = authService.login(account, password); + return new SuccessResponseData(token); + } + + /** + * 退出登录 + * + * @author xuyuxiang + * @date 2020/3/16 15:02 + */ + @GetMapping("/logout") + public void logout() { + authService.logout(); + } + + /** + * 获取当前登录用户信息 + * + * @author xuyuxiang + * @date 2020/3/23 17:57 + */ + @GetMapping("/getLoginUser") + public ResponseData getLoginUser() { + return new SuccessResponseData(LoginContextHolder.me().getSysLoginUserUpToDate()); + } + + /** + * 获取验证码开关 + * + * @author Jax + * @date 2021/1/21 15:27 + */ + @GetMapping("/getCaptchaOpen") + public ResponseData getCaptchaOpen() { + return new SuccessResponseData(ConstantContextHolder.getCaptchaOpenFlag()); + } + + /** + * 校验验证码 + * + * @author Jax + * @date 2021/1/21 15:27 + */ + private boolean verificationCode(String code) { + CaptchaVO vo = new CaptchaVO(); + vo.setCaptchaVerification(code); + if (!captchaService.verification(vo).isSuccess()) { + throw new AuthException(AuthExceptionEnum.CONSTANT_EMPTY_ERROR); + } + return true; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/factory/LoginUserFactory.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/factory/LoginUserFactory.java new file mode 100644 index 0000000..eb29a52 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/factory/LoginUserFactory.java @@ -0,0 +1,154 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.auth.factory; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.ServerExceptionEnum; +import vip.xiaonuo.core.pojo.login.LoginEmpInfo; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.core.pojo.node.LoginMenuTreeNode; +import vip.xiaonuo.core.tenant.consts.TenantConstants; +import vip.xiaonuo.core.tenant.context.TenantCodeHolder; +import vip.xiaonuo.core.tenant.context.TenantDbNameHolder; +import vip.xiaonuo.core.util.HttpServletUtil; +import vip.xiaonuo.core.util.IpAddressUtil; +import vip.xiaonuo.core.util.UaUtil; +import vip.xiaonuo.sys.modular.app.service.SysAppService; +import vip.xiaonuo.sys.modular.emp.service.SysEmpService; +import vip.xiaonuo.sys.modular.menu.entity.SysMenu; +import vip.xiaonuo.sys.modular.menu.service.SysMenuService; +import vip.xiaonuo.sys.modular.role.service.SysRoleMenuService; +import vip.xiaonuo.sys.modular.role.service.SysRoleService; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 登录用户工厂类 + * + * @author xuyuxiang + * @date 2020/3/13 14:58 + */ +public class LoginUserFactory { + + private static final SysUserService sysUserService = SpringUtil.getBean(SysUserService.class); + + private static final SysEmpService sysEmpService = SpringUtil.getBean(SysEmpService.class); + + private static final SysAppService sysAppService = SpringUtil.getBean(SysAppService.class); + + private static final SysMenuService sysMenuService = SpringUtil.getBean(SysMenuService.class); + + private static final SysRoleService sysRoleService = SpringUtil.getBean(SysRoleService.class); + + private static final SysRoleMenuService sysRoleMenuService = SpringUtil.getBean(SysRoleMenuService.class); + + /** + * 填充登录用户相关信息 + * + * @author xuyuxiang yubaoshan + * @date 2020/3/13 15:01 + */ + public static void fillLoginUserInfo(SysLoginUser sysLoginUser) { + HttpServletRequest request = HttpServletUtil.getRequest(); + if (ObjectUtil.isNotNull(request)) { + sysLoginUser.setLastLoginIp(IpAddressUtil.getIp(request)); + sysLoginUser.setLastLoginTime(DateTime.now().toString()); + sysLoginUser.setLastLoginAddress(IpAddressUtil.getAddress(request)); + sysLoginUser.setLastLoginBrowser(UaUtil.getBrowser(request)); + sysLoginUser.setLastLoginOs(UaUtil.getOs(request)); + Long userId = sysLoginUser.getId(); + + // 员工信息 + LoginEmpInfo loginEmpInfo = sysEmpService.getLoginEmpInfo(userId); + sysLoginUser.setLoginEmpInfo(loginEmpInfo); + + // 角色信息 + List<Dict> roles = sysRoleService.getLoginRoles(userId); + sysLoginUser.setRoles(roles); + + // 获取角色id集合 + List<Long> roleIdList = roles.stream().map(dict -> Convert.toLong(dict.get(CommonConstant.ID))).collect(Collectors.toList()); + + // 获取菜单id集合 + List<Long> menuIdList = sysRoleMenuService.getRoleMenuIdList(roleIdList); + + // 权限信息 + List<String> permissions = sysMenuService.getLoginPermissions(userId, menuIdList); + sysLoginUser.setPermissions(permissions); + + // 数据范围信息 + List<Long> dataScopes = sysUserService.getUserDataScopeIdList(userId, loginEmpInfo.getOrgId()); + sysLoginUser.setDataScopes(dataScopes); + + // 具备应用信息(多系统,默认激活一个,可根据系统切换菜单),返回的结果中第一个为激活的系统 + List<Dict> apps = sysAppService.getLoginApps(userId, roleIdList); + sysLoginUser.setApps(apps); + + // 如果根本没有应用信息,则没有菜单信息 + if (ObjectUtil.isEmpty(apps)) { + sysLoginUser.setMenus(CollectionUtil.newArrayList()); + } else { + //AntDesign菜单信息,根据人获取,用于登录后展示菜单树,默认获取默认激活的系统的菜单 + String defaultActiveAppCode = apps.get(0).getStr(CommonConstant.CODE); + List<SysMenu> loginMenus = sysMenuService.getLoginMenus(userId, defaultActiveAppCode, menuIdList); + Map<String, List<SysMenu>> collect = loginMenus.stream().collect(Collectors.groupingBy(SysMenu::getApplication)); + List<SysMenu> tempList = collect.get(defaultActiveAppCode); + List<LoginMenuTreeNode> loginMenuTreeNodes = sysMenuService.convertSysMenuToLoginMenu(tempList); + sysLoginUser.setMenus(loginMenuTreeNodes); + } + + //如果开启了多租户功能,则设置当前登录用户的租户标识 + if (ConstantContextHolder.getTenantOpenFlag()) { + String tenantCode = TenantCodeHolder.get(); + String dataBaseName = TenantDbNameHolder.get(); + if (StrUtil.isNotBlank(tenantCode) && StrUtil.isNotBlank(dataBaseName)) { + Dict tenantInfo = Dict.create(); + tenantInfo.set(TenantConstants.TENANT_CODE, tenantCode); + tenantInfo.set(TenantConstants.TENANT_DB_NAME, dataBaseName); + sysLoginUser.setTenants(tenantInfo); + } + //注意,这里remove不代表所有情况,在aop remove + TenantCodeHolder.remove(); + TenantDbNameHolder.remove(); + } + + } else { + throw new ServiceException(ServerExceptionEnum.REQUEST_EMPTY); + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/service/AuthService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/service/AuthService.java new file mode 100644 index 0000000..aea366c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/service/AuthService.java @@ -0,0 +1,144 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.auth.service; + +import org.springframework.security.core.Authentication; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.sys.modular.user.entity.SysUser; + +import javax.servlet.http.HttpServletRequest; + +/** + * 认证相关service + * + * @author xuyuxiang + * @date 2020/3/11 14:14 + */ +public interface AuthService { + + /** + * 账号密码登录 + * + * @param account 账号 + * @param password 密码 + * @return token + * @author xuyuxiang + * @date 2020/3/11 15:57 + */ + String login(String account, String password); + + /** + * 根据已有用户信息登录 + * + * @param sysUser 用户信息 + * @return token + * @author xuyuxiang + * @date 2020/7/29 10:12 + **/ + String doLogin(SysUser sysUser); + + /** + * 从request获取token + * + * @param request request + * @return token + * @author xuyuxiang + * @date 2020/3/13 11:41 + */ + String getTokenFromRequest(HttpServletRequest request); + + /** + * 根据token获取登录用户信息 + * + * @param token token + * @return 当前登陆的用户信息 + * @author xuyuxiang + * @date 2020/3/13 11:59 + */ + SysLoginUser getLoginUserByToken(String token); + + /** + * 退出登录 + * + * @author xuyuxiang + * @date 2020/3/16 15:03 + */ + void logout(); + + /** + * 设置SpringSecurityContext上下文,方便获取用户 + * + * @param sysLoginUser 当前登录用户信息 + * @author xuyuxiang + * @date 2020/3/19 19:59 + */ + void setSpringSecurityContextAuthentication(SysLoginUser sysLoginUser); + + /** + * 获取SpringSecurityContext中认证信息 + * + * @return 认证信息 + * @author xuyuxiang + * @date 2020/3/19 20:02 + */ + Authentication getAuthentication(); + + /** + * 校验token是否正确 + * + * @param token token + * @author xuyuxiang + * @date 2020/5/28 9:57 + */ + void checkToken(String token); + + /** + * 临时缓存租户信息 + * + * @param tenantCode 多租户编码 + * @author xuyuxiang + * @date 2020/9/3 21:22 + */ + void cacheTenantInfo(String tenantCode); + + /** + * 根据系统用户构造用户登陆信息 + * + * @param sysUser 系统用户 + * @return 用户信息 + * @author xuyuxiang + * @date 2020/9/20 15:21 + **/ + SysLoginUser genSysLoginUser(SysUser sysUser); + + /** + * 新增用户的数据授权范围 + * + * @author yubaoshan + * @date 2021/7/20 14:50 + */ + void refreshUserDataScope(Long orgId); + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/service/impl/AuthServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/service/impl/AuthServiceImpl.java new file mode 100644 index 0000000..626c6cd --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/auth/service/impl/AuthServiceImpl.java @@ -0,0 +1,382 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.auth.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.spring.SpringUtil; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.dbs.CurrentDataSourceContext; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.AuthException; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.AuthExceptionEnum; +import vip.xiaonuo.core.exception.enums.ServerExceptionEnum; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.core.tenant.context.TenantCodeHolder; +import vip.xiaonuo.core.tenant.context.TenantDbNameHolder; +import vip.xiaonuo.core.tenant.entity.TenantInfo; +import vip.xiaonuo.core.tenant.exception.TenantException; +import vip.xiaonuo.core.tenant.exception.enums.TenantExceptionEnum; +import vip.xiaonuo.core.tenant.service.TenantInfoService; +import vip.xiaonuo.core.util.CryptogramUtil; +import vip.xiaonuo.core.util.HttpServletUtil; +import vip.xiaonuo.core.util.IpAddressUtil; +import vip.xiaonuo.sys.core.cache.UserCache; +import vip.xiaonuo.sys.core.enums.LogSuccessStatusEnum; +import vip.xiaonuo.sys.core.jwt.JwtPayLoad; +import vip.xiaonuo.sys.core.jwt.JwtTokenUtil; +import vip.xiaonuo.sys.core.log.LogManager; +import vip.xiaonuo.sys.modular.auth.factory.LoginUserFactory; +import vip.xiaonuo.sys.modular.auth.service.AuthService; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * 认证相关service实现类 + * + * @author xuyuxiang + * @date 2020/3/11 16:58 + */ +@Service +public class AuthServiceImpl implements AuthService, UserDetailsService { + + @Resource + private SysUserService sysUserService; + + @Resource + private UserCache userCache; + + @Override + public String login(String account, String password) { + + if (ObjectUtil.hasEmpty(account, password)) { + LogManager.me().executeLoginLog(account, LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_PWD_EMPTY.getMessage()); + throw new AuthException(AuthExceptionEnum.ACCOUNT_PWD_EMPTY); + } + + SysUser sysUser = sysUserService.getUserByCount(account); + + //用户不存在,账号或密码错误 + if (ObjectUtil.isEmpty(sysUser)) { + LogManager.me().executeLoginLog(account, LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_PWD_ERROR.getMessage()); + throw new AuthException(AuthExceptionEnum.ACCOUNT_PWD_ERROR); + } + + String passwordHashValue = sysUser.getPwdHashValue(); + + // 解密密码(这里是前端传来的,如果不需要可将其干掉,这里不做开关) + password = CryptogramUtil.doSm2Decrypt(password); + + //验证账号密码是否正确 + if (ObjectUtil.isEmpty(passwordHashValue) || !passwordHashValue.equals(CryptogramUtil.doHashValue(password))) { + LogManager.me().executeLoginLog(sysUser.getAccount(), LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_PWD_ERROR.getMessage()); + throw new AuthException(AuthExceptionEnum.ACCOUNT_PWD_ERROR); + } + + return doLogin(sysUser); + } + + @Override + public String doLogin(SysUser sysUser) { + + Integer sysUserStatus = sysUser.getStatus(); + + //验证账号是否被冻结 + if (CommonStatusEnum.DISABLE.getCode().equals(sysUserStatus)) { + LogManager.me().executeLoginLog(sysUser.getAccount(), LogSuccessStatusEnum.FAIL.getCode(), AuthExceptionEnum.ACCOUNT_FREEZE_ERROR.getMessage()); + throw new AuthException(AuthExceptionEnum.ACCOUNT_FREEZE_ERROR); + } + + //构造SysLoginUser + SysLoginUser sysLoginUser = this.genSysLoginUser(sysUser); + + //构造jwtPayLoad + JwtPayLoad jwtPayLoad = new JwtPayLoad(sysUser.getId(), sysUser.getAccount()); + + //生成token + String token = JwtTokenUtil.generateToken(jwtPayLoad); + + //缓存token与登录用户信息对应, 默认2个小时 + this.cacheLoginUser(jwtPayLoad, sysLoginUser); + + //设置最后登录ip和时间 + sysUser.setLastLoginIp(IpAddressUtil.getIp(HttpServletUtil.getRequest())); + sysUser.setLastLoginTime(DateTime.now()); + + //更新用户登录信息 + sysUserService.updateById(sysUser); + + //登录成功,记录登录日志 + LogManager.me().executeLoginLog(sysUser.getAccount(), LogSuccessStatusEnum.SUCCESS.getCode(), null); + + //登录成功,设置SpringSecurityContext上下文,方便获取用户 + this.setSpringSecurityContextAuthentication(sysLoginUser); + + //如果开启限制单用户登陆,则踢掉原来的用户 + Boolean enableSingleLogin = ConstantContextHolder.getEnableSingleLogin(); + if (enableSingleLogin) { + + //获取所有的登陆用户 + Map<String, SysLoginUser> allLoginUsers = userCache.getAllKeyValues(); + for (Map.Entry<String, SysLoginUser> loginedUserEntry : allLoginUsers.entrySet()) { + + String loginedUserKey = loginedUserEntry.getKey(); + SysLoginUser loginedUser = loginedUserEntry.getValue(); + + //如果账号名称相同,并且redis缓存key和刚刚生成的用户的uuid不一样,则清除以前登录的 + if (loginedUser.getName().equals(sysUser.getName()) + && !loginedUserKey.equals(jwtPayLoad.getUuid())) { + this.clearUser(loginedUserKey, loginedUser.getAccount()); + } + } + } + + //返回token + return token; + } + + @Override + public String getTokenFromRequest(HttpServletRequest request) { + String authToken = request.getHeader(CommonConstant.AUTHORIZATION); + if (ObjectUtil.isEmpty(authToken) || CommonConstant.UNDEFINED.equals(authToken)) { + return null; + } else { + //token不是以Bearer打头,则响应回格式不正确 + if (!authToken.startsWith(CommonConstant.TOKEN_TYPE_BEARER)) { + throw new AuthException(AuthExceptionEnum.NOT_VALID_TOKEN_TYPE); + } + try { + authToken = authToken.substring(CommonConstant.TOKEN_TYPE_BEARER.length() + 1); + } catch (StringIndexOutOfBoundsException e) { + throw new AuthException(AuthExceptionEnum.NOT_VALID_TOKEN_TYPE); + } + // 判断是否开启了加密 + if (ConstantContextHolder.getCryptogramConfigs().getTokenEncDec()) { + // 解密token + authToken = CryptogramUtil.doDecrypt(authToken); + } + } + + return authToken; + } + + @Override + public SysLoginUser getLoginUserByToken(String token) { + + //校验token,错误则抛异常 + this.checkToken(token); + + //根据token获取JwtPayLoad部分 + JwtPayLoad jwtPayLoad = JwtTokenUtil.getJwtPayLoad(token); + + //从redis缓存中获取登录用户 + Object cacheObject = userCache.get(jwtPayLoad.getUuid()); + + //用户不存在则表示登录已过期 + if (ObjectUtil.isEmpty(cacheObject)) { + throw new AuthException(AuthExceptionEnum.LOGIN_EXPIRED); + } + + //转换成登录用户 + SysLoginUser sysLoginUser = (SysLoginUser) cacheObject; + + //用户存在, 无痛刷新缓存,在登录过期前活动的用户自动刷新缓存时间 + this.cacheLoginUser(jwtPayLoad, sysLoginUser); + + //返回用户 + return sysLoginUser; + } + + @Override + public void logout() { + + HttpServletRequest request = HttpServletUtil.getRequest(); + + if (ObjectUtil.isNotNull(request)) { + + //获取token + String token = this.getTokenFromRequest(request); + + //如果token为空直接返回 + if (ObjectUtil.isEmpty(token)) { + return; + } + + //校验token,错误则抛异常,待确定 + this.checkToken(token); + + //根据token获取JwtPayLoad部分 + JwtPayLoad jwtPayLoad = JwtTokenUtil.getJwtPayLoad(token); + + //获取缓存的key + String loginUserCacheKey = jwtPayLoad.getUuid(); + this.clearUser(loginUserCacheKey, jwtPayLoad.getAccount()); + + } else { + throw new ServiceException(ServerExceptionEnum.REQUEST_EMPTY); + } + } + + @Override + public void setSpringSecurityContextAuthentication(SysLoginUser sysLoginUser) { + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = + new UsernamePasswordAuthenticationToken( + sysLoginUser, + null, + sysLoginUser.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + } + + @Override + public Authentication getAuthentication() { + return SecurityContextHolder.getContext().getAuthentication(); + } + + @Override + public void checkToken(String token) { + //校验token是否正确 + Boolean tokenCorrect = JwtTokenUtil.checkToken(token); + if (!tokenCorrect) { + throw new AuthException(AuthExceptionEnum.REQUEST_TOKEN_ERROR); + } + + //校验token是否失效 + Boolean tokenExpired = JwtTokenUtil.isTokenExpired(token); + if (tokenExpired) { + throw new AuthException(AuthExceptionEnum.LOGIN_EXPIRED); + } + } + + @Override + public void cacheTenantInfo(String tenantCode) { + if (StrUtil.isBlank(tenantCode)) { + return; + } + + // 从spring容器中获取service,如果没开多租户功能,没引入相关包,这里会报错 + TenantInfoService tenantInfoService = null; + try { + tenantInfoService = SpringUtil.getBean(TenantInfoService.class); + } catch (Exception e) { + throw new TenantException(TenantExceptionEnum.TENANT_MODULE_NOT_ENABLE_ERROR); + } + + // 获取租户信息 + TenantInfo tenantInfo = tenantInfoService.getByCode(tenantCode); + if (tenantInfo != null) { + String dbName = tenantInfo.getDbName(); + + // 租户编码的临时存放 + TenantCodeHolder.put(tenantCode); + + // 租户的数据库名称临时缓存 + TenantDbNameHolder.put(dbName); + + // 数据源信息临时缓存 + CurrentDataSourceContext.setDataSourceType(dbName); + } else { + throw new TenantException(TenantExceptionEnum.CNAT_FIND_TENANT_ERROR); + } + } + + @Override + public SysLoginUser loadUserByUsername(String account) throws UsernameNotFoundException { + SysLoginUser sysLoginUser = new SysLoginUser(); + SysUser user = sysUserService.getUserByCount(account); + BeanUtil.copyProperties(user, sysLoginUser); + return sysLoginUser; + } + + /** + * 根据key清空登陆信息 + * + * @author xuyuxiang + * @date 2020/6/19 12:28 + */ + private void clearUser(String loginUserKey, String account) { + //获取缓存的用户 + Object cacheObject = userCache.get(loginUserKey); + + //如果缓存的用户存在,清除会话,否则表示该会话信息已失效,不执行任何操作 + if (ObjectUtil.isNotEmpty(cacheObject)) { + //清除登录会话 + userCache.remove(loginUserKey); + //创建退出登录日志 + LogManager.me().executeExitLog(account); + } + } + + /** + * 构造登录用户信息 + * + * @author xuyuxiang + * @date 2020/3/12 17:32 + */ + @Override + public SysLoginUser genSysLoginUser(SysUser sysUser) { + SysLoginUser sysLoginUser = new SysLoginUser(); + BeanUtil.copyProperties(sysUser, sysLoginUser); + LoginUserFactory.fillLoginUserInfo(sysLoginUser); + return sysLoginUser; + } + + /** + * 缓存token与登录用户信息对应, 默认2个小时 + * + * @author xuyuxiang + * @date 2020/3/13 14:51 + */ + private void cacheLoginUser(JwtPayLoad jwtPayLoad, SysLoginUser sysLoginUser) { + String redisLoginUserKey = jwtPayLoad.getUuid(); + userCache.put(redisLoginUserKey, sysLoginUser, Convert.toLong(ConstantContextHolder.getSessionTokenExpireSec())); + } + + @Override + public void refreshUserDataScope(Long orgId) { + // request获取到token + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String token = this.getTokenFromRequest(request); + SysLoginUser sysLoginUser = this.getLoginUserByToken(token); + sysLoginUser.getDataScopes().add(orgId); + this.cacheLoginUser(JwtTokenUtil.getJwtPayLoad(token), sysLoginUser); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/controller/SysConfigController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/controller/SysConfigController.java new file mode 100644 index 0000000..0f705e4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/controller/SysConfigController.java @@ -0,0 +1,138 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.consts.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.consts.param.SysConfigParam; +import vip.xiaonuo.sys.modular.consts.service.SysConfigService; + +import javax.annotation.Resource; + + +/** + * 参数配置控制器 + * + * @author yubaoshan + * @date 2020/4/13 22:46 + */ +@RestController +public class SysConfigController { + + @Resource + private SysConfigService sysConfigService; + + /** + * 分页查询配置列表 + * + * @author xuyuxiang + * @date 2020/4/14 11:10 + */ + @Permission + @GetMapping("/sysConfig/page") + @BusinessLog(title = "系统参数配置_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysConfigParam sysConfigParam) { + return new SuccessResponseData(sysConfigService.page(sysConfigParam)); + } + + /** + * 系统参数配置列表 + * + * @author xuyuxiang + * @date 2020/4/14 11:10 + */ + @Permission + @GetMapping("/sysConfig/list") + @BusinessLog(title = "系统参数配置_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysConfigParam sysConfigParam) { + return new SuccessResponseData(sysConfigService.list(sysConfigParam)); + } + + /** + * 查看系统参数配置 + * + * @author xuyuxiang + * @date 2020/4/14 11:12 + */ + @Permission + @GetMapping("/sysConfig/detail") + @BusinessLog(title = "系统参数配置_详情", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysConfigParam.detail.class) SysConfigParam sysConfigParam) { + return new SuccessResponseData(sysConfigService.detail(sysConfigParam)); + } + + /** + * 添加系统参数配置 + * + * @author xuyuxiang + * @date 2020/4/14 11:11 + */ + @Permission + @PostMapping("/sysConfig/add") + @BusinessLog(title = "系统参数配置_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysConfigParam.add.class) SysConfigParam sysConfigParam) { + sysConfigService.add(sysConfigParam); + return new SuccessResponseData(); + } + + /** + * 删除系统参数配置 + * + * @author xuyuxiang + * @date 2020/4/14 11:11 + */ + @Permission + @PostMapping("/sysConfig/delete") + @BusinessLog(title = "系统参数配置_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysConfigParam.delete.class) SysConfigParam sysConfigParam) { + sysConfigService.delete(sysConfigParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统参数配置 + * + * @author xuyuxiang + * @date 2020/4/14 11:11 + */ + @Permission + @PostMapping("/sysConfig/edit") + @BusinessLog(title = "系统参数配置_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysConfigParam.edit.class) SysConfigParam sysConfigParam) { + sysConfigService.edit(sysConfigParam); + return new SuccessResponseData(); + } + +} + + diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/entity/SysConfig.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/entity/SysConfig.java new file mode 100644 index 0000000..194d207 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/entity/SysConfig.java @@ -0,0 +1,87 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.consts.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * <p> + * 参数配置 + * </p> + * + * @author yubaoshan + * @date 2019/6/20 13:44 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_config") +public class SysConfig extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 属性值 + */ + private String value; + + /** + * 是否是系统参数(Y-是,N-否) + */ + private String sysFlag; + + /** + * 备注 + */ + @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + private String remark; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + private Integer status; + + /** + * 常量所属分类的编码,来自于“常量的分类”字典 + */ + private String groupCode; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/enums/SysConfigExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/enums/SysConfigExceptionEnum.java new file mode 100644 index 0000000..800a73b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/enums/SysConfigExceptionEnum.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.consts.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统参数配置相关异常枚举 + * + * @author xuyuxiang + * @date 2020/4/14 11:24 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_CONFIG_EXCEPTION_ENUM) +public enum SysConfigExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 数据库连接配置不存在 + */ + DATA_SOURCE_NOT_EXIST(1, "数据库连接配置不存在,请检查spring.datasource配置用户名密码是否正确"), + + /** + * 系统参数配置不存在 + */ + CONFIG_NOT_EXIST(2, "系统参数配置不存在"), + + /** + * 系统参数配置编码重复 + */ + CONFIG_CODE_REPEAT(3, "系统参数配置编码重复,请检查code参数"), + + /** + * 系统参数配置名称重复 + */ + CONFIG_NAME_REPEAT(4, "系统参数配置名称重复,请检查name参数"), + + /** + * 不能删除系统参数 + */ + CONFIG_SYS_CAN_NOT_DELETE(5, "系统参数配置不能删除"), + + /** + * 常量分类在字典中未找到 + */ + NOT_EXIST_DICT_TYPE(6, "字典类型中未找到常量分类,请检查字典类型表"); + + private final Integer code; + + private final String message; + + SysConfigExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/mapper/SysConfigMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/mapper/SysConfigMapper.java new file mode 100644 index 0000000..1c27cd4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/mapper/SysConfigMapper.java @@ -0,0 +1,39 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.consts.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.consts.entity.SysConfig; + +/** + * 系统参数配置 Mapper 接口 + * + * @author yubaoshan + * @date 2019/6/20 13:44 + */ +public interface SysConfigMapper extends BaseMapper<SysConfig> { + + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/mapper/mapping/SysConfigMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/mapper/mapping/SysConfigMapper.xml new file mode 100644 index 0000000..a73479f --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/mapper/mapping/SysConfigMapper.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.consts.mapper.SysConfigMapper"> + + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/param/SysConfigParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/param/SysConfigParam.java new file mode 100644 index 0000000..b0bff6f --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/param/SysConfigParam.java @@ -0,0 +1,86 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.consts.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; +import vip.xiaonuo.core.validation.flag.FlagValue; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统参数配置参数 + * + * @author xuyuxiang + * @date 2020/4/14 10:18 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysConfigParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + private String code; + + /** + * 值 + */ + @NotBlank(message = "值不能为空,请检查value参数", groups = {add.class, edit.class}) + private String value; + + /** + * 是否是系统参数(Y-是,N-否) + */ + @NotBlank(message = "是否是系统参数不能为空,请检查sysFlag参数", groups = {add.class, edit.class}) + @FlagValue(message = "是否是系统参数格式错误,正确格式应该Y或者N,请检查sysFlag参数", groups = {add.class, edit.class}) + private String sysFlag; + + /** + * 备注 + */ + private String remark; + + /** + * 常量所属分类的编码,来自于“常量的分类”字典 + */ + @NotBlank(message = "值不能为空,请检查value参数", groups = {add.class, edit.class}) + private String groupCode; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/service/SysConfigService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/service/SysConfigService.java new file mode 100644 index 0000000..8cb1b00 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/service/SysConfigService.java @@ -0,0 +1,99 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.consts.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.consts.entity.SysConfig; +import vip.xiaonuo.sys.modular.consts.param.SysConfigParam; + +import java.util.List; + +/** + * 系统参数配置service接口 + * + * @author xuyuxiang + * @date 2020/4/14 11:14 + */ +public interface SysConfigService extends IService<SysConfig> { + + /** + * 查询系统参数配置 + * + * @param sysConfigParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/4/14 11:14 + */ + PageResult<SysConfig> page(SysConfigParam sysConfigParam); + + /** + * 查询系统参数配置 + * + * @param sysConfigParam 查询参数 + * @return 系统参数配置列表 + * @author xuyuxiang + * @date 2020/4/14 11:14 + */ + List<SysConfig> list(SysConfigParam sysConfigParam); + + /** + * 查看系统参数配置 + * + * @param sysConfigParam 查看参数 + * @return 系统参数配置 + * @author xuyuxiang + * @date 2020/4/14 11:15 + */ + SysConfig detail(SysConfigParam sysConfigParam); + + /** + * 添加系统参数配置 + * + * @param sysConfigParam 添加参数 + * @author xuyuxiang + * @date 2020/4/14 11:14 + */ + void add(SysConfigParam sysConfigParam); + + /** + * 删除系统参数配置 + * + * @param sysConfigParam 删除参数 + * @author xuyuxiang + * @date 2020/4/14 11:15 + */ + void delete(SysConfigParam sysConfigParam); + + /** + * 编辑系统参数配置 + * + * @param sysConfigParam 编辑参数 + * @author xuyuxiang + * @date 2020/4/14 11:15 + */ + void edit(SysConfigParam sysConfigParam); + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/service/impl/SysConfigServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..76093a1 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/consts/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,210 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.consts.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.core.context.constant.ConstantContext; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.enums.YesOrNotEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.consts.entity.SysConfig; +import vip.xiaonuo.sys.modular.consts.enums.SysConfigExceptionEnum; +import vip.xiaonuo.sys.modular.consts.mapper.SysConfigMapper; +import vip.xiaonuo.sys.modular.consts.param.SysConfigParam; +import vip.xiaonuo.sys.modular.consts.service.SysConfigService; + +import java.util.List; + + +/** + * 系统参数配置service接口实现类 + * + * @author xuyuxiang + * @date 2020/4/14 11:16 + */ +@Service +public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig> implements SysConfigService { + + @Override + public PageResult<SysConfig> page(SysConfigParam sysConfigParam) { + + //构造查询条件 + LambdaQueryWrapper<SysConfig> queryWrapper = new LambdaQueryWrapper<>(); + + if (ObjectUtil.isNotNull(sysConfigParam)) { + //如果名称不为空,则带上名称搜素搜条件 + if (ObjectUtil.isNotEmpty(sysConfigParam.getName())) { + queryWrapper.like(SysConfig::getName, sysConfigParam.getName()); + } + //如果常量编码不为空,则带上常量编码搜素搜条件 + if (ObjectUtil.isNotEmpty(sysConfigParam.getCode())) { + queryWrapper.like(SysConfig::getCode, sysConfigParam.getCode()); + } + //如果分类编码不为空,则带上分类编码 + if (ObjectUtil.isNotEmpty(sysConfigParam.getGroupCode())) { + queryWrapper.eq(SysConfig::getGroupCode, sysConfigParam.getGroupCode()); + } + } + + //查询未删除的 + queryWrapper.ne(SysConfig::getStatus, CommonStatusEnum.DELETED.getCode()); + + //按类型升序排列,同类型的排在一起 + queryWrapper.orderByDesc(SysConfig::getGroupCode); + + //查询分页结果 + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<SysConfig> list(SysConfigParam sysConfigParam) { + + //构造条件 + LambdaQueryWrapper<SysConfig> wrapper = new LambdaQueryWrapper<>(); + + //查询未删除的 + wrapper.ne(SysConfig::getStatus, CommonStatusEnum.DELETED.getCode()); + + return this.list(wrapper); + } + + @Override + public SysConfig detail(SysConfigParam sysConfigParam) { + return this.querySysConfig(sysConfigParam); + } + + @Override + public void add(SysConfigParam sysConfigParam) { + + //1.校验参数,是否有重复的名称和编码,不排除当前记录 + checkRepeatParam(sysConfigParam, false); + + //2.构造实体 + SysConfig sysConfig = new SysConfig(); + BeanUtil.copyProperties(sysConfigParam, sysConfig); + sysConfig.setStatus(CommonStatusEnum.ENABLE.getCode()); + + //3.保存到库中 + this.save(sysConfig); + + //4.添加对应context + ConstantContext.putConstant(sysConfigParam.getCode(), sysConfigParam.getValue()); + } + + @Override + public void delete(SysConfigParam sysConfigParam) { + + //1.根据id获取常量 + SysConfig sysConfig = this.querySysConfig(sysConfigParam); + + //2.不能删除系统参数 + if (YesOrNotEnum.Y.getCode().equals(sysConfig.getSysFlag())) { + throw new ServiceException(SysConfigExceptionEnum.CONFIG_SYS_CAN_NOT_DELETE); + } + + //3.设置状态为已删除 + sysConfig.setStatus(CommonStatusEnum.DELETED.getCode()); + this.updateById(sysConfig); + + //4.删除对应context + ConstantContext.deleteConstant(sysConfigParam.getCode()); + } + + @Override + public void edit(SysConfigParam sysConfigParam) { + + //1.根据id获取常量信息 + SysConfig sysConfig = this.querySysConfig(sysConfigParam); + + //2.校验参数,是否有重复的名称和编码,排除本条记录 + checkRepeatParam(sysConfigParam, true); + + //请求参数转化为实体 + BeanUtil.copyProperties(sysConfigParam, sysConfig); + //不能修改状态,用修改状态接口修改状态 + sysConfig.setStatus(null); + + //3.更新记录 + this.updateById(sysConfig); + + //4.更新对应常量context + ConstantContext.putConstant(sysConfigParam.getCode(), sysConfigParam.getValue()); + } + + /** + * 校验参数,是否有重复的名称和编码 + * + * @author xuyuxiang + * @date 2020/4/14 11:18 + */ + private void checkRepeatParam(SysConfigParam sysConfigParam, boolean isExcludeSelf) { + Long id = sysConfigParam.getId(); + String name = sysConfigParam.getName(); + String code = sysConfigParam.getCode(); + + //构建带name和code的查询条件 + LambdaQueryWrapper<SysConfig> queryWrapperByName = new LambdaQueryWrapper<>(); + queryWrapperByName.eq(SysConfig::getName, name); + + LambdaQueryWrapper<SysConfig> queryWrapperByCode = new LambdaQueryWrapper<>(); + queryWrapperByCode.eq(SysConfig::getCode, code); + + //如果排除自己,则增加查询条件主键id不等于本条id + if (isExcludeSelf) { + queryWrapperByName.ne(SysConfig::getId, id); + queryWrapperByCode.ne(SysConfig::getId, id); + } + int countByName = this.count(queryWrapperByName); + int countByCode = this.count(queryWrapperByCode); + + //如果存在重复的记录,抛出异常,直接返回前端 + if (countByName >= 1) { + throw new ServiceException(SysConfigExceptionEnum.CONFIG_NAME_REPEAT); + } + if (countByCode >= 1) { + throw new ServiceException(SysConfigExceptionEnum.CONFIG_CODE_REPEAT); + } + } + + /** + * 获取系统参数配置 + * + * @author xuyuxiang + * @date 2020/4/14 11:19 + */ + private SysConfig querySysConfig(SysConfigParam sysConfigParam) { + SysConfig sysConfig = this.getById(sysConfigParam.getId()); + if (ObjectUtil.isEmpty(sysConfig)) { + throw new ServiceException(SysConfigExceptionEnum.CONFIG_NOT_EXIST); + } + return sysConfig; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/controller/SysDictDataController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/controller/SysDictDataController.java new file mode 100644 index 0000000..c2a7df2 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/controller/SysDictDataController.java @@ -0,0 +1,149 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.dict.param.SysDictDataParam; +import vip.xiaonuo.sys.modular.dict.service.SysDictDataService; + +import javax.annotation.Resource; + +/** + * 系统字典值控制器 + * + * @author xuyuxiang + * @date 2020/3/31 20:49 + */ +@RestController +public class SysDictDataController { + + @Resource + private SysDictDataService sysDictDataService; + + /** + * 查询系统字典值 + * + * @author xuyuxiang + * @date 2020/3/31 20:50 + */ + @Permission + @GetMapping("/sysDictData/page") + @BusinessLog(title = "系统字典值_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysDictDataParam sysDictDataParam) { + return new SuccessResponseData(sysDictDataService.page(sysDictDataParam)); + } + + /** + * 某个字典类型下所有的字典 + * + * @author xuyuxiang + * @date 2020/3/31 21:03 + */ + @Permission + @GetMapping("/sysDictData/list") + @BusinessLog(title = "系统字典值_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(@Validated(SysDictDataParam.list.class) SysDictDataParam sysDictDataParam) { + return new SuccessResponseData(sysDictDataService.list(sysDictDataParam)); + } + + /** + * 查看系统字典值 + * + * @author xuyuxiang + * @date 2020/3/31 20:51 + */ + @Permission + @GetMapping("/sysDictData/detail") + @BusinessLog(title = "系统字典值_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysDictDataParam.detail.class) SysDictDataParam sysDictDataParam) { + return new SuccessResponseData(sysDictDataService.detail(sysDictDataParam)); + } + + /** + * 添加系统字典值 + * + * @author xuyuxiang + * @date 2020/3/31 20:50 + */ + @Permission + @PostMapping("/sysDictData/add") + @BusinessLog(title = "系统字典值_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysDictDataParam.add.class) SysDictDataParam sysDictDataParam) { + sysDictDataService.add(sysDictDataParam); + return new SuccessResponseData(); + } + + /** + * 删除系统字典值 + * + * @author xuyuxiang + * @date 2020/3/31 20:50 + */ + @Permission + @PostMapping("/sysDictData/delete") + @BusinessLog(title = "系统字典值_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysDictDataParam.delete.class) SysDictDataParam sysDictDataParam) { + sysDictDataService.delete(sysDictDataParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统字典值 + * + * @author xuyuxiang + * @date 2020/3/31 20:51 + */ + @Permission + @PostMapping("/sysDictData/edit") + @BusinessLog(title = "系统字典值_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysDictDataParam.edit.class) SysDictDataParam sysDictDataParam) { + sysDictDataService.edit(sysDictDataParam); + return new SuccessResponseData(); + } + + /** + * 修改状态 + * + * @author yubaoshan + * @date 2020/5/1 9:43 + */ + @Permission + @PostMapping("/sysDictData/changeStatus") + @BusinessLog(title = "系统字典值_修改状态", opType = LogAnnotionOpTypeEnum.CHANGE_STATUS) + public ResponseData changeStatus(@RequestBody @Validated(SysDictDataParam.changeStatus.class) SysDictDataParam sysDictDataParam) { + sysDictDataService.changeStatus(sysDictDataParam); + return new SuccessResponseData(); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/controller/SysDictTypeController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/controller/SysDictTypeController.java new file mode 100644 index 0000000..f543d73 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/controller/SysDictTypeController.java @@ -0,0 +1,172 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.dict.param.SysDictTypeParam; +import vip.xiaonuo.sys.modular.dict.service.SysDictTypeService; + +import javax.annotation.Resource; + +/** + * 系统字典类型控制器 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:49 + */ +@RestController +public class SysDictTypeController { + + @Resource + private SysDictTypeService sysDictTypeService; + + /** + * 分页查询系统字典类型 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:30 + */ + @Permission + @GetMapping("/sysDictType/page") + @BusinessLog(title = "系统字典类型_分页查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysDictTypeParam sysDictTypeParam) { + return new SuccessResponseData(sysDictTypeService.page(sysDictTypeParam)); + } + + /** + * 获取字典类型列表 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 21:03 + */ + @Permission + @GetMapping("/sysDictType/list") + @BusinessLog(title = "系统字典类型_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysDictTypeParam sysDictTypeParam) { + return new SuccessResponseData(sysDictTypeService.list(sysDictTypeParam)); + } + + /** + * 获取字典类型下所有字典,举例,返回格式为:[{code:"M",value:"男"},{code:"F",value:"女"}] + * + * @author xuyuxiang,xuyuxiang yubaoshan + * @date 2020/3/31 21:18 + */ + @GetMapping("/sysDictType/dropDown") + @BusinessLog(title = "系统字典类型_下拉", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData dropDown(@Validated(SysDictTypeParam.dropDown.class) SysDictTypeParam sysDictTypeParam) { + return new SuccessResponseData(sysDictTypeService.dropDown(sysDictTypeParam)); + } + + /** + * 查看系统字典类型 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:31 + */ + @Permission + @GetMapping("/sysDictType/detail") + @BusinessLog(title = "系统字典类型_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysDictTypeParam.detail.class) SysDictTypeParam sysDictTypeParam) { + return new SuccessResponseData(sysDictTypeService.detail(sysDictTypeParam)); + } + + /** + * 添加系统字典类型 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:30 + */ + @Permission + @PostMapping("/sysDictType/add") + @BusinessLog(title = "系统字典类型_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysDictTypeParam.add.class) SysDictTypeParam sysDictTypeParam) { + sysDictTypeService.add(sysDictTypeParam); + return new SuccessResponseData(); + } + + /** + * 删除系统字典类型 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:30 + */ + @Permission + @PostMapping("/sysDictType/delete") + @BusinessLog(title = "系统字典类型_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysDictTypeParam.delete.class) SysDictTypeParam sysDictTypeParam) { + sysDictTypeService.delete(sysDictTypeParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统字典类型 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:31 + */ + @Permission + @PostMapping("/sysDictType/edit") + @BusinessLog(title = "系统字典类型_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysDictTypeParam.edit.class) SysDictTypeParam sysDictTypeParam) { + sysDictTypeService.edit(sysDictTypeParam); + return new SuccessResponseData(); + } + + /** + * 修改状态 + * + * @author yubaoshan + * @date 2020/4/30 22:20 + */ + @Permission + @PostMapping("/sysDictType/changeStatus") + @BusinessLog(title = "系统字典类型_修改状态", opType = LogAnnotionOpTypeEnum.CHANGE_STATUS) + public ResponseData changeStatus(@RequestBody @Validated(SysDictTypeParam.changeStatus.class) SysDictTypeParam sysDictTypeParam) { + sysDictTypeService.changeStatus(sysDictTypeParam); + return new SuccessResponseData(); + } + + /** + * 系统字典类型与字典值构造的树 + * + * @author yubaoshan + * @date 2020/4/30 22:20 + */ + @GetMapping("/sysDictType/tree") + @BusinessLog(title = "系统字典类型_树", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData tree() { + return new SuccessResponseData(sysDictTypeService.tree()); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/entity/SysDictData.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/entity/SysDictData.java new file mode 100644 index 0000000..24265f0 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/entity/SysDictData.java @@ -0,0 +1,79 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 系统字典值表 + * + * @author xuyuxiang + * @date 2020/3/11 12:08 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_dict_data") +public class SysDictData extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 字典类型id + */ + private Long typeId; + + /** + * 值 + */ + private String value; + + /** + * 编码 + */ + private String code; + + /** + * 排序 + */ + private Integer sort; + + /** + * 备注 + */ + @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + private String remark; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/entity/SysDictType.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/entity/SysDictType.java new file mode 100644 index 0000000..8d14bb4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/entity/SysDictType.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 系统字典类型表 + * + * @author xuyuxiang + * @date 2020/3/11 12:08 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_dict_type") +public class SysDictType extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 排序 + */ + private Integer sort; + + /** + * 备注 + */ + @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + private String remark; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + private Integer status; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/enums/SysDictDataExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/enums/SysDictDataExceptionEnum.java new file mode 100644 index 0000000..44b796b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/enums/SysDictDataExceptionEnum.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统字典值相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/31 20:47 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_DICT_DATA_EXCEPTION_ENUM) +public enum SysDictDataExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 字典值不存在 + */ + DICT_DATA_NOT_EXIST(1, "字典值不存在"), + + /** + * 字典值编码重复 + */ + DICT_DATA_CODE_REPEAT(2, "字典值编码重复,请检查code参数"); + + private final Integer code; + + private final String message; + + SysDictDataExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/enums/SysDictTypeExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/enums/SysDictTypeExceptionEnum.java new file mode 100644 index 0000000..83db876 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/enums/SysDictTypeExceptionEnum.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统字典类型相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/31 20:44 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_DICT_TYPE_EXCEPTION_ENUM) +public enum SysDictTypeExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 字典类型不存在 + */ + DICT_TYPE_NOT_EXIST(1, "字典类型不存在"), + + /** + * 字典类型编码重复 + */ + DICT_TYPE_CODE_REPEAT(2, "字典类型编码重复,请检查code参数"), + + /** + * 字典类型名称重复 + */ + DICT_TYPE_NAME_REPEAT(3, "字典类型名称重复,请检查name参数"); + + private final Integer code; + + private final String message; + + SysDictTypeExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/SysDictDataMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/SysDictDataMapper.java new file mode 100644 index 0000000..9ce5b77 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/SysDictDataMapper.java @@ -0,0 +1,50 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.dict.entity.SysDictData; + +import java.util.List; + +/** + * 系统字典值mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:12 + */ +public interface SysDictDataMapper extends BaseMapper<SysDictData> { + + /** + * 通过字典类型code获取字典编码值列表 + * + * @param dictTypeCodes 字典类型编码集合 + * @return 字典编码值列表 + * @author xuyuxiang + * @date 2020/8/9 14:27 + */ + List<String> getDictCodesByDictTypeCode(String[] dictTypeCodes); + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/SysDictTypeMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..2e289f4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/SysDictTypeMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.dict.entity.SysDictType; + +/** + * 系统字典类型mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:11 + */ +public interface SysDictTypeMapper extends BaseMapper<SysDictType> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/mapping/SysDictDataMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/mapping/SysDictDataMapper.xml new file mode 100644 index 0000000..e65e890 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/mapping/SysDictDataMapper.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.dict.mapper.SysDictDataMapper"> + + <select id="getDictCodesByDictTypeCode" resultType="java.lang.String"> + SELECT + dict.`code` + FROM + sys_dict_data dict + INNER JOIN sys_dict_type type ON dict.type_id = type.id + where type.code in + <foreach collection="array" index="index" item="i" open="(" separator="," close=")"> + #{i} + </foreach> + </select> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/mapping/SysDictTypeMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/mapping/SysDictTypeMapper.xml new file mode 100644 index 0000000..84eaf6c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/mapper/mapping/SysDictTypeMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.dict.mapper.SysDictTypeMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/param/SysDictDataParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/param/SysDictDataParam.java new file mode 100644 index 0000000..b4a648c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/param/SysDictDataParam.java @@ -0,0 +1,84 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统字典值参数 + * + * @author xuyuxiang + * @date 2020/3/31 20:32 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysDictDataParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, changeStatus.class}) + private Long id; + + /** + * 字典类型id + */ + @NotNull(message = "字典类型typeId不能为空,请检查typeId参数", groups = {list.class, add.class, edit.class}) + private Long typeId; + + /** + * 值 + */ + @NotBlank(message = "值不能为空,请检查value参数", groups = {add.class, edit.class}) + private String value; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {dropDown.class, add.class, edit.class}) + private String code; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空,请检查sort参数", groups = {add.class, edit.class}) + private Integer sort; + + /** + * 备注 + */ + private String remark; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + @NotNull(message = "状态不能为空,请检查status参数", groups = changeStatus.class) + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/param/SysDictTypeParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/param/SysDictTypeParam.java new file mode 100644 index 0000000..b1f8722 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/param/SysDictTypeParam.java @@ -0,0 +1,78 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统字典类型参数 + * + * @author xuyuxiang + * @date 2020/3/31 20:32 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysDictTypeParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, changeStatus.class}) + private Long id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class, dropDown.class,}) + private String code; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空,请检查sort参数", groups = {add.class, edit.class}) + private Integer sort; + + /** + * 备注 + */ + private String remark; + + /** + * 状态 + */ + @NotNull(message = "状态不能为空,请检查status参数", groups = {changeStatus.class}) + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/result/SysDictTreeNode.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/result/SysDictTreeNode.java new file mode 100644 index 0000000..5a4916d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/result/SysDictTreeNode.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.result; + +import lombok.Data; +import vip.xiaonuo.core.pojo.base.node.BaseTreeNode; + +import java.util.List; + +/** + * 系统字典树 + * + * @author xuyuxiang + * @date 2020/3/11 12:08 + */ +@Data +public class SysDictTreeNode implements BaseTreeNode { + + /** + * id + */ + private Long id; + + /** + * 父id + */ + private Long pid; + + /** + * 编码-对应字典值的编码 + */ + private String code; + + /** + * 名称-对应字典值的value + */ + private String name; + + /** + * 子节点集合 + */ + private List<SysDictTreeNode> children; + + @Override + public Long getId() { + return this.id; + } + + @Override + public Long getPid() { + return this.pid; + } + + @Override + public void setChildren(List children) { + this.children = children; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/SysDictDataService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/SysDictDataService.java new file mode 100644 index 0000000..3d8b296 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/SysDictDataService.java @@ -0,0 +1,137 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.service; + +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.dict.entity.SysDictData; +import vip.xiaonuo.sys.modular.dict.param.SysDictDataParam; + +import java.util.List; + +/** + * 系统字典值service接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:10 + */ +public interface SysDictDataService extends IService<SysDictData> { + + /** + * 查询系统字典值 + * + * @param sysDictDataParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/3/31 20:53 + */ + PageResult<SysDictData> page(SysDictDataParam sysDictDataParam); + + /** + * 系统字典值列表 + * + * @param sysDictDataParam 查询参数 + * @return 系统字典值列表 + * @author xuyuxiang + * @date 2020/3/31 21:07 + */ + List<SysDictData> list(SysDictDataParam sysDictDataParam); + + /** + * 添加系统字典值 + * + * @param sysDictDataParam 添加参数 + * @author xuyuxiang + * @date 2020/3/31 20:53 + */ + void add(SysDictDataParam sysDictDataParam); + + /** + * 删除系统字典值 + * + * @param sysDictDataParam 删除参数 + * @author xuyuxiang + * @date 2020/3/31 20:54 + */ + void delete(SysDictDataParam sysDictDataParam); + + /** + * 编辑系统字典值 + * + * @param sysDictDataParam 编辑参数 + * @author xuyuxiang + * @date 2020/3/31 20:54 + */ + void edit(SysDictDataParam sysDictDataParam); + + /** + * 查看系统字典值 + * + * @param sysDictDataParam 查看参数 + * @return 系统字典值 + * @author xuyuxiang + * @date 2020/3/31 20:54 + */ + SysDictData detail(SysDictDataParam sysDictDataParam); + + /** + * 根据typeId下拉 + * + * @param dictTypeId 字典类型id + * @return 增强版hashMap,格式:[{"code:":"1", "value":"正常"}] + * @author xuyuxiang yubaoshan + * @date 2020/3/31 21:27 + */ + List<Dict> getDictDataListByDictTypeId(Long dictTypeId); + + /** + * 根据typeId删除 + * + * @param dictTypeId 字典类型id + * @author xuyuxiang + * @date 2020/4/1 10:27 + */ + void deleteByTypeId(Long dictTypeId); + + /** + * 修改状态 + * + * @param sysDictDataParam 修改参数 + * @author yubaoshan + * @date 2020/5/1 9:44 + */ + void changeStatus(SysDictDataParam sysDictDataParam); + + /** + * 根据字典类型获取字典的code值 + * + * @param dictTypeCodes 字典类型编码集合 + * @return 字典编码值列表 + * @author xuyuxiang + * @date 2020/8/9 14:18 + */ + List<String> getDictCodesByDictTypeCode(String... dictTypeCodes); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/SysDictTypeService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/SysDictTypeService.java new file mode 100644 index 0000000..a577cb5 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/SysDictTypeService.java @@ -0,0 +1,128 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.service; + +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.dict.entity.SysDictType; +import vip.xiaonuo.sys.modular.dict.param.SysDictTypeParam; +import vip.xiaonuo.sys.modular.dict.result.SysDictTreeNode; + +import java.util.List; + +/** + * 系统字典类型service接口 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/13 16:10 + */ +public interface SysDictTypeService extends IService<SysDictType> { + + /** + * 查询系统字典类型 + * + * @param sysDictTypeParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:34 + */ + PageResult<SysDictType> page(SysDictTypeParam sysDictTypeParam); + + /** + * 获取字典类型列表 + * + * @param sysDictTypeParam 查询参数 + * @return 系统字典类型列表 + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 21:07 + */ + List<SysDictType> list(SysDictTypeParam sysDictTypeParam); + + /** + * 系统字典类型下拉 + * + * @param sysDictTypeParam 下拉参数 + * @return 增强版hashMap,格式:[{"code:":"1", "value":"正常"}] + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 21:23 + */ + List<Dict> dropDown(SysDictTypeParam sysDictTypeParam); + + /** + * 添加系统字典类型 + * + * @param sysDictTypeParam 添加参数 + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:35 + */ + void add(SysDictTypeParam sysDictTypeParam); + + /** + * 删除系统字典类型 + * + * @param sysDictTypeParam 删除参数 + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:35 + */ + void delete(SysDictTypeParam sysDictTypeParam); + + /** + * 编辑系统字典类型 + * + * @param sysDictTypeParam 编辑参数 + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:35 + */ + void edit(SysDictTypeParam sysDictTypeParam); + + /** + * 查看系统字典类型 + * + * @param sysDictTypeParam 查看参数 + * @return 系统字典类型 + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:35 + */ + SysDictType detail(SysDictTypeParam sysDictTypeParam); + + /** + * 修改状态(字典 0正常 1停用 2删除) + * + * @param sysDictTypeParam 修改参数 + * @author yubaoshan + * @date 2020/4/30 22:30 + */ + void changeStatus(SysDictTypeParam sysDictTypeParam); + + /** + * 系统字典类型与字典值构造的树 + * + * @return 树 + * @author xuyuxiang + * @date 2020/4/30 22:30 + */ + List<SysDictTreeNode> tree(); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/impl/SysDictDataServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 0000000..6d8fbd2 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,256 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.StatusExceptionEnum; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.dict.entity.SysDictData; +import vip.xiaonuo.sys.modular.dict.enums.SysDictDataExceptionEnum; +import vip.xiaonuo.sys.modular.dict.mapper.SysDictDataMapper; +import vip.xiaonuo.sys.modular.dict.param.SysDictDataParam; +import vip.xiaonuo.sys.modular.dict.service.SysDictDataService; + +import java.util.List; + +/** + * 系统字典值service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 16:11 + */ +@Service +public class SysDictDataServiceImpl extends ServiceImpl<SysDictDataMapper, SysDictData> implements SysDictDataService { + + @Override + public PageResult<SysDictData> page(SysDictDataParam sysDictDataParam) { + + //构造条件 + LambdaQueryWrapper<SysDictData> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysDictDataParam)) { + //根据字典类型查询 + if (ObjectUtil.isNotEmpty(sysDictDataParam.getTypeId())) { + queryWrapper.eq(SysDictData::getTypeId, sysDictDataParam.getTypeId()); + } + //根据字典值的编码模糊查询 + if (ObjectUtil.isNotEmpty(sysDictDataParam.getCode())) { + queryWrapper.like(SysDictData::getCode, sysDictDataParam.getCode()); + } + //根据字典值的内容模糊查询 + if (ObjectUtil.isNotEmpty(sysDictDataParam.getValue())) { + queryWrapper.like(SysDictData::getValue, sysDictDataParam.getValue()); + } + } + //查询未删除的 + queryWrapper.ne(SysDictData::getStatus, CommonStatusEnum.DELETED.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysDictData::getSort); + //返回分页查询结果 + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<SysDictData> list(SysDictDataParam sysDictDataParam) { + //构造条件,查询某个字典类型下的 + LambdaQueryWrapper<SysDictData> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysDictDataParam)) { + if (ObjectUtil.isNotEmpty(sysDictDataParam.getTypeId())) { + queryWrapper.eq(SysDictData::getTypeId, sysDictDataParam.getTypeId()); + } + } + //查询未删除的 + queryWrapper.ne(SysDictData::getStatus, CommonStatusEnum.DELETED.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysDictData::getSort); + return this.list(queryWrapper); + } + + @Override + public void add(SysDictDataParam sysDictDataParam) { + + //校验参数,检查是否存在重复的编码,不排除当前添加的这条记录 + checkParam(sysDictDataParam, false); + + //将dto转为实体 + SysDictData sysDictData = new SysDictData(); + BeanUtil.copyProperties(sysDictDataParam, sysDictData); + + //设置状态为启用 + sysDictData.setStatus(CommonStatusEnum.ENABLE.getCode()); + + this.save(sysDictData); + } + + @Override + public void delete(SysDictDataParam sysDictDataParam) { + + //根据id查询实体 + SysDictData sysDictData = this.querySysDictData(sysDictDataParam); + + //逻辑删除,修改状态 + sysDictData.setStatus(CommonStatusEnum.DELETED.getCode()); + + //更新实体 + this.updateById(sysDictData); + } + + @Override + public void edit(SysDictDataParam sysDictDataParam) { + + //根据id查询实体 + SysDictData sysDictData = this.querySysDictData(sysDictDataParam); + + //校验参数,检查是否存在重复的编码或者名称,排除当前编辑的这条记录 + checkParam(sysDictDataParam, true); + + //请求参数转化为实体 + BeanUtil.copyProperties(sysDictDataParam, sysDictData); + + //不能修改状态,用修改状态接口修改状态 + sysDictData.setStatus(null); + + this.updateById(sysDictData); + } + + @Override + public SysDictData detail(SysDictDataParam sysDictDataParam) { + return this.querySysDictData(sysDictDataParam); + } + + @Override + public List<Dict> getDictDataListByDictTypeId(Long dictTypeId) { + + //构造查询条件 + LambdaQueryWrapper<SysDictData> queryWrapper = new LambdaQueryWrapper<SysDictData>(); + queryWrapper.eq(SysDictData::getTypeId, dictTypeId).ne(SysDictData::getStatus, CommonStatusEnum.DELETED.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysDictData::getSort); + //查询dictTypeId下所有的字典项 + List<SysDictData> results = this.list(queryWrapper); + + //抽取code和value封装到map返回 + List<Dict> dictList = CollectionUtil.newArrayList(); + results.forEach(sysDictData -> { + Dict dict = Dict.create(); + dict.put(CommonConstant.CODE, sysDictData.getCode()); + dict.put(CommonConstant.VALUE, sysDictData.getValue()); + dictList.add(dict); + }); + + return dictList; + } + + @Override + public void deleteByTypeId(Long typeId) { + //将所有typeId为某值的记录全部置为delete状态 + LambdaUpdateWrapper<SysDictData> queryWrapper = new LambdaUpdateWrapper<>(); + queryWrapper.eq(SysDictData::getTypeId, typeId) + .set(SysDictData::getStatus, CommonStatusEnum.DELETED.getCode()); + this.update(queryWrapper); + } + + @Override + public void changeStatus(SysDictDataParam sysDictDataParam) { + //根据id查询实体 + SysDictData sysDictData = this.querySysDictData(sysDictDataParam); + Long id = sysDictData.getId(); + + Integer status = sysDictDataParam.getStatus(); + + //校验状态在不在枚举值里 + CommonStatusEnum.validateStatus(status); + + //更新枚举,更新只能更新未删除状态的 + LambdaUpdateWrapper<SysDictData> updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SysDictData::getId, id) + .and(i -> i.ne(SysDictData::getStatus, CommonStatusEnum.DELETED.getCode())) + .set(SysDictData::getStatus, status); + boolean update = this.update(updateWrapper); + if (!update) { + throw new ServiceException(StatusExceptionEnum.UPDATE_STATUS_ERROR); + } + } + + @Override + public List<String> getDictCodesByDictTypeCode(String... dictTypeCodes) { + return this.baseMapper.getDictCodesByDictTypeCode(dictTypeCodes); + } + + /** + * 校验参数,校验是否存在相同的编码 + * + * @author xuyuxiang + * @date 2020/3/31 20:56 + */ + private void checkParam(SysDictDataParam sysDictDataParam, boolean isExcludeSelf) { + Long id = sysDictDataParam.getId(); + Long typeId = sysDictDataParam.getTypeId(); + String code = sysDictDataParam.getCode(); + + //构建带code的查询条件 + LambdaQueryWrapper<SysDictData> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysDictData::getTypeId, typeId) + .eq(SysDictData::getCode, code) + .ne(SysDictData::getStatus, CommonStatusEnum.DELETED.getCode()); + + //如果排除自己,则增加查询条件主键id不等于本条id + if (isExcludeSelf) { + queryWrapper.ne(SysDictData::getId, id); + } + + //查询重复记录的数量 + int countByCode = this.count(queryWrapper); + + //如果存在重复的记录,抛出异常,直接返回前端 + if (countByCode >= 1) { + throw new ServiceException(SysDictDataExceptionEnum.DICT_DATA_CODE_REPEAT); + } + } + + /** + * 获取系统字典值 + * + * @author xuyuxiang + * @date 2020/3/31 20:56 + */ + private SysDictData querySysDictData(SysDictDataParam sysDictDataParam) { + SysDictData sysDictData = this.getById(sysDictDataParam.getId()); + if (ObjectUtil.isNull(sysDictData)) { + throw new ServiceException(SysDictDataExceptionEnum.DICT_DATA_NOT_EXIST); + } + return sysDictData; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/impl/SysDictTypeServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 0000000..5ef0217 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/dict/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,266 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.dict.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.StatusExceptionEnum; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.factory.TreeBuildFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.dict.entity.SysDictData; +import vip.xiaonuo.sys.modular.dict.entity.SysDictType; +import vip.xiaonuo.sys.modular.dict.enums.SysDictTypeExceptionEnum; +import vip.xiaonuo.sys.modular.dict.mapper.SysDictTypeMapper; +import vip.xiaonuo.sys.modular.dict.param.SysDictTypeParam; +import vip.xiaonuo.sys.modular.dict.result.SysDictTreeNode; +import vip.xiaonuo.sys.modular.dict.service.SysDictDataService; +import vip.xiaonuo.sys.modular.dict.service.SysDictTypeService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 系统字典类型service接口实现类 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/13 16:11 + */ +@Service +public class SysDictTypeServiceImpl extends ServiceImpl<SysDictTypeMapper, SysDictType> implements SysDictTypeService { + + @Resource + private SysDictDataService sysDictDataService; + + @Override + public PageResult<SysDictType> page(SysDictTypeParam sysDictTypeParam) { + + //构造条件 + LambdaQueryWrapper<SysDictType> queryWrapper = new LambdaQueryWrapper<>(); + + if (ObjectUtil.isNotNull(sysDictTypeParam)) { + //根据字典类型名称模糊查询 + if (ObjectUtil.isNotEmpty(sysDictTypeParam.getName())) { + queryWrapper.like(SysDictType::getName, sysDictTypeParam.getName()); + } + + //根据字典类型编码模糊查询 + if (ObjectUtil.isNotEmpty(sysDictTypeParam.getCode())) { + queryWrapper.like(SysDictType::getCode, sysDictTypeParam.getCode()); + } + } + + //查询未删除的 + queryWrapper.ne(SysDictType::getStatus, CommonStatusEnum.DELETED.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysDictType::getSort); + //查询分页结果 + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<SysDictType> list(SysDictTypeParam sysDictTypeParam) { + + //构造条件 + LambdaQueryWrapper<SysDictType> queryWrapper = new LambdaQueryWrapper<>(); + + //查询未删除的 + queryWrapper.ne(SysDictType::getStatus, CommonStatusEnum.DELETED.getCode()); + + return this.list(queryWrapper); + } + + @Override + public List<Dict> dropDown(SysDictTypeParam sysDictTypeParam) { + LambdaQueryWrapper<SysDictType> queryWrapper = new LambdaQueryWrapper<SysDictType>() + .eq(SysDictType::getCode, sysDictTypeParam.getCode()); + + SysDictType sysDictType = this.getOne(queryWrapper); + if (ObjectUtil.isNull(sysDictType)) { + throw new ServiceException(SysDictTypeExceptionEnum.DICT_TYPE_NOT_EXIST); + } + return sysDictDataService.getDictDataListByDictTypeId(sysDictType.getId()); + } + + @Override + public void add(SysDictTypeParam sysDictTypeParam) { + + //校验参数,检查是否存在重复的编码或者名称,不排除当前添加的这条记录 + checkParam(sysDictTypeParam, false); + + //将dto转为实体 + SysDictType sysDictType = new SysDictType(); + BeanUtil.copyProperties(sysDictTypeParam, sysDictType); + + //设置状态为启用 + sysDictType.setStatus(CommonStatusEnum.ENABLE.getCode()); + + this.save(sysDictType); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(SysDictTypeParam sysDictTypeParam) { + + //根据id查询实体 + SysDictType sysDictType = this.querySysDictType(sysDictTypeParam); + + //逻辑删除,修改状态 + sysDictType.setStatus(CommonStatusEnum.DELETED.getCode()); + + //更新实体 + this.updateById(sysDictType); + + //级联删除字典值 + sysDictDataService.deleteByTypeId(sysDictType.getId()); + } + + @Override + public void edit(SysDictTypeParam sysDictTypeParam) { + + //根据id查询实体 + SysDictType sysDictType = this.querySysDictType(sysDictTypeParam); + + //校验参数,检查是否存在重复的编码或者名称,排除当前编辑的这条记录 + checkParam(sysDictTypeParam, true); + + //请求参数转化为实体 + BeanUtil.copyProperties(sysDictTypeParam, sysDictType); + + //不能修改状态,用修改状态接口修改状态 + sysDictType.setStatus(null); + + this.updateById(sysDictType); + } + + @Override + public SysDictType detail(SysDictTypeParam sysDictTypeParam) { + return this.querySysDictType(sysDictTypeParam); + } + + @Override + public void changeStatus(SysDictTypeParam sysDictTypeParam) { + Long id = sysDictTypeParam.getId(); + Integer status = sysDictTypeParam.getStatus(); + + //校验状态在不在枚举值里 + CommonStatusEnum.validateStatus(status); + + //更新枚举,更新只能更新未删除状态的 + LambdaUpdateWrapper<SysDictType> updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SysDictType::getId, id) + .and(i -> i.ne(SysDictType::getStatus, CommonStatusEnum.DELETED.getCode())) + .set(SysDictType::getStatus, status); + boolean update = this.update(updateWrapper); + if (!update) { + throw new ServiceException(StatusExceptionEnum.UPDATE_STATUS_ERROR); + } + } + + @Override + public List<SysDictTreeNode> tree() { + List<SysDictTreeNode> resultList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysDictType> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.ne(SysDictType::getStatus, CommonStatusEnum.DELETED.getCode()); + this.list(queryWrapper).forEach(sysDictType -> { + SysDictTreeNode sysDictTreeNode = new SysDictTreeNode(); + BeanUtil.copyProperties(sysDictType, sysDictTreeNode); + sysDictTreeNode.setPid(0L); + resultList.add(sysDictTreeNode); + }); + sysDictDataService.list(new LambdaQueryWrapper<SysDictData>().ne(SysDictData::getStatus, CommonStatusEnum.DELETED.getCode())) + .forEach(sysDictData -> { + SysDictTreeNode sysDictTreeNode = new SysDictTreeNode(); + sysDictTreeNode.setId(sysDictData.getId()); + sysDictTreeNode.setPid(sysDictData.getTypeId()); + sysDictTreeNode.setCode(sysDictData.getCode()); + sysDictTreeNode.setName(sysDictData.getValue()); + resultList.add(sysDictTreeNode); + }); + return new TreeBuildFactory<SysDictTreeNode>().doTreeBuild(resultList); + } + + /** + * 校验参数,检查是否存在重复的编码或者名称 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/25 21:23 + */ + private void checkParam(SysDictTypeParam sysDictTypeParam, boolean isExcludeSelf) { + Long id = sysDictTypeParam.getId(); + String name = sysDictTypeParam.getName(); + String code = sysDictTypeParam.getCode(); + + //构建带name和code的查询条件 + LambdaQueryWrapper<SysDictType> queryWrapperByName = new LambdaQueryWrapper<>(); + queryWrapperByName.eq(SysDictType::getName, name) + .ne(SysDictType::getStatus, CommonStatusEnum.DELETED.getCode()); + + LambdaQueryWrapper<SysDictType> queryWrapperByCode = new LambdaQueryWrapper<>(); + queryWrapperByCode.eq(SysDictType::getCode, code) + .ne(SysDictType::getStatus, CommonStatusEnum.DELETED.getCode()); + + //如果排除自己,则增加查询条件主键id不等于本条id + if (isExcludeSelf) { + queryWrapperByName.ne(SysDictType::getId, id); + queryWrapperByCode.ne(SysDictType::getId, id); + } + + //查询重复记录的数量 + int countByName = this.count(queryWrapperByName); + int countByCode = this.count(queryWrapperByCode); + + //如果存在重复的记录,抛出异常,直接返回前端 + if (countByName >= 1) { + throw new ServiceException(SysDictTypeExceptionEnum.DICT_TYPE_NAME_REPEAT); + } + if (countByCode >= 1) { + throw new ServiceException(SysDictTypeExceptionEnum.DICT_TYPE_CODE_REPEAT); + } + } + + /** + * 获取系统字典类型 + * + * @author xuyuxiang,xuyuxiang + * @date 2020/3/31 20:38 + */ + private SysDictType querySysDictType(SysDictTypeParam sysDictTypeParam) { + SysDictType sysDictType = this.getById(sysDictTypeParam.getId()); + if (ObjectUtil.isNull(sysDictType)) { + throw new ServiceException(SysDictTypeExceptionEnum.DICT_TYPE_NOT_EXIST); + } + return sysDictType; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/email/controler/EmailController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/email/controler/EmailController.java new file mode 100644 index 0000000..a50d817 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/email/controler/EmailController.java @@ -0,0 +1,122 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.email.controler; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.extra.mail.MailException; +import cn.hutool.log.Log; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.context.requestno.RequestNoContext; +import vip.xiaonuo.core.email.MailSender; +import vip.xiaonuo.core.email.modular.model.SendMailParam; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.email.enums.SysEmailExceptionEnum; + +import javax.annotation.Resource; + +/** + * 邮件发送控制器 + * + * @author yubaoshan + * @date 2020/6/9 23:02 + */ +@RestController +public class EmailController { + + private static final Log log = Log.get(); + + @Resource + private MailSender mailSender; + + /** + * 发送邮件 + * + * @author yubaoshan, xuyuxiang + * @date 2020/6/9 23:02 + */ + @PostMapping("/email/sendEmail") + @BusinessLog(title = "发送邮件", opType = LogAnnotionOpTypeEnum.OTHER) + public ResponseData sendEmail(@RequestBody SendMailParam sendMailParam) { + String to = sendMailParam.getTo(); + if (ObjectUtil.isEmpty(to)) { + throw new ServiceException(SysEmailExceptionEnum.EMAIL_TO_EMPTY); + } + + String title = sendMailParam.getTitle(); + if (ObjectUtil.isEmpty(title)) { + throw new ServiceException(SysEmailExceptionEnum.EMAIL_TITLE_EMPTY); + } + + String content = sendMailParam.getContent(); + if (ObjectUtil.isEmpty(content)) { + throw new ServiceException(SysEmailExceptionEnum.EMAIL_CONTENT_EMPTY); + } + try { + mailSender.sendMail(sendMailParam); + } catch (MailException e) { + log.error(">>> 邮件发送异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + throw new ServiceException(SysEmailExceptionEnum.EMAIL_SEND_ERROR); + } + return new SuccessResponseData(); + } + + /** + * 发送邮件(html) + * + * @author yubaoshan, xuyuxiang + * @date 2020/6/9 23:02 + */ + @PostMapping("/email/sendEmailHtml") + @BusinessLog(title = "发送邮件", opType = LogAnnotionOpTypeEnum.OTHER) + public ResponseData sendEmailHtml(@RequestBody SendMailParam sendMailParam) { + String to = sendMailParam.getTo(); + if (ObjectUtil.isEmpty(to)) { + throw new ServiceException(SysEmailExceptionEnum.EMAIL_TO_EMPTY); + } + + String title = sendMailParam.getTitle(); + if (ObjectUtil.isEmpty(title)) { + throw new ServiceException(SysEmailExceptionEnum.EMAIL_TITLE_EMPTY); + } + + String content = sendMailParam.getContent(); + if (ObjectUtil.isEmpty(content)) { + throw new ServiceException(SysEmailExceptionEnum.EMAIL_CONTENT_EMPTY); + } + try { + mailSender.sendMailHtml(sendMailParam); + } catch (MailException e) { + log.error(">>> 邮件发送异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + throw new ServiceException(SysEmailExceptionEnum.EMAIL_SEND_ERROR); + } + return new SuccessResponseData(); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/email/enums/SysEmailExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/email/enums/SysEmailExceptionEnum.java new file mode 100644 index 0000000..58b7e68 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/email/enums/SysEmailExceptionEnum.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.email.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统应用相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/26 10:11 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.EMAIL_EXCEPTION_ENUM) +public enum SysEmailExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 收件人为空 + */ + EMAIL_TO_EMPTY(1, "收件人为空,请检查to参数"), + + /** + * 标题为空 + */ + EMAIL_TITLE_EMPTY(2, "标题为空,请检查title参数"), + + /** + * 内容为空 + */ + EMAIL_CONTENT_EMPTY(3, "内容为空,请检查content参数"), + + /** + * 邮件发送失败 + */ + EMAIL_SEND_ERROR(4, "邮件发送失败,请检查发送参数"); + + private final Integer code; + + private final String message; + + SysEmailExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmp.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmp.java new file mode 100644 index 0000000..68a8a19 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmp.java @@ -0,0 +1,62 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 员工表 + * + * @author xuyuxiang + * @date 2020/3/11 11:20 + */ +@Data +@TableName("sys_emp") +public class SysEmp { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 工号 + */ + private String jobNum; + + /** + * 所属机构id + */ + private Long orgId; + + /** + * 所属机构名称 + */ + private String orgName; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmpExtOrgPos.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmpExtOrgPos.java new file mode 100644 index 0000000..06a2b59 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmpExtOrgPos.java @@ -0,0 +1,62 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 员工附属机构职位表 + * + * @author xuyuxiang + * @date 2020/3/11 11:47 + */ +@Data +@TableName("sys_emp_ext_org_pos") +public class SysEmpExtOrgPos { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 员工id + */ + private Long empId; + + /** + * 机构id + */ + private Long orgId; + + /** + * 职位id + */ + private Long posId; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmpPos.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmpPos.java new file mode 100644 index 0000000..3012e4b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/entity/SysEmpPos.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 员工职位关联表 + * + * @author xuyuxiang + * @date 2020/3/11 11:47 + */ +@Data +@TableName("sys_emp_pos") +public class SysEmpPos { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 员工id + */ + private Long empId; + + /** + * 职位id + */ + private Long posId; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpExtOrgPosMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpExtOrgPosMapper.java new file mode 100644 index 0000000..4745669 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpExtOrgPosMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.emp.entity.SysEmpExtOrgPos; + +/** + * 员工附属机构mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:13 + */ +public interface SysEmpExtOrgPosMapper extends BaseMapper<SysEmpExtOrgPos> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpMapper.java new file mode 100644 index 0000000..4ce9b7b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.emp.entity.SysEmp; + +/** + * 员工mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:13 + */ +public interface SysEmpMapper extends BaseMapper<SysEmp> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpPosMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpPosMapper.java new file mode 100644 index 0000000..2e87097 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/SysEmpPosMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.emp.entity.SysEmpPos; + +/** + * 员工职位mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:12 + */ +public interface SysEmpPosMapper extends BaseMapper<SysEmpPos> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpExtOrgPosMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpExtOrgPosMapper.xml new file mode 100644 index 0000000..9d1f98d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpExtOrgPosMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.emp.mapper.SysEmpExtOrgPosMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpMapper.xml new file mode 100644 index 0000000..2f90a38 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.emp.mapper.SysEmpMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpPosMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpPosMapper.xml new file mode 100644 index 0000000..662cb27 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/mapper/mapping/SysEmpPosMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.emp.mapper.SysEmpPosMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/param/SysEmpParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/param/SysEmpParam.java new file mode 100644 index 0000000..0cbfcef --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/param/SysEmpParam.java @@ -0,0 +1,79 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.param; + +import cn.hutool.core.lang.Dict; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 员工参数 + * + * @author xuyuxiang + * @date 2020/4/1 19:28 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysEmpParam extends BaseParam { + + /** + * 主键 + */ + private Long id; + + /** + * 工号 + */ + private String jobNum; + + /** + * 所属机构id + */ + @NotNull(message = "所属机构id不能为空,请检查sysEmpParam.orgId参数", groups = {add.class, edit.class}) + private Long orgId; + + /** + * 所属机构名称 + */ + @NotBlank(message = "所属机构名称不能为空,请检查sysEmpParam.orgName参数", groups = {add.class, edit.class}) + private String orgName; + + /** + * 附属机构id和附属职位id集合 + */ + @NotNull(message = "附属信息不能为空,请检查sysEmpParam.extIds参数", groups = {add.class, edit.class}) + private List<Dict> extIds; + + /** + * 职位id集合 + */ + @NotNull(message = "所属职位不能为空,请检查sysEmpParam.posIdList参数", groups = {add.class, edit.class}) + private List<Long> posIdList; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/result/SysEmpInfo.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/result/SysEmpInfo.java new file mode 100644 index 0000000..1d3bfad --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/result/SysEmpInfo.java @@ -0,0 +1,69 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.result; + +import cn.hutool.core.lang.Dict; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 用户员工信息 + * + * @author xuyuxiang + * @date 2020/4/2 20:22 + */ +@Data +public class SysEmpInfo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 工号 + */ + private String jobNum; + + /** + * 所属机构id + */ + private Long orgId; + + /** + * 所属机构名称 + */ + private String orgName; + + /** + * 附属机构与职位信息 + */ + private List<Dict> extOrgPos; + + /** + * 职位信息 + */ + private List<Dict> positions; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpExtOrgPosService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpExtOrgPosService.java new file mode 100644 index 0000000..be525e4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpExtOrgPosService.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.service; + +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.sys.modular.emp.entity.SysEmpExtOrgPos; + +import java.util.List; + +/** + * 员工附属机构service接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:10 + */ +public interface SysEmpExtOrgPosService extends IService<SysEmpExtOrgPos> { + + /** + * 保存或编辑附属机构相关信息 + * + * @param empId 员工id(用户id) + * @param extIdList 附属机构职位信息集合,格式:[{"orgId":1234, "posId":5678}] + * @author xuyuxiang + * @date 2020/4/2 8:59 + */ + void addOrEdit(Long empId, List<Dict> extIdList); + + /** + * 获取附属机构和职位信息 + * + * @param empId 员工id(用户id) + * @param isFillId 是否需要返回id信息 + * @return 增强版hashMap,格式:[{"orgId":123, "orgCode":"yfb", "orgName":"研发部", "posId":456, "posCode":"zjl", "posName":"总经理"}] + * @author xuyuxiang + * @date 2020/4/2 20:07 + */ + List<Dict> getEmpExtOrgPosDictList(Long empId, boolean isFillId); + + /** + * 根据机构id判断该附属机构下是否有员工 + * + * @param orgId 机构id + * @return 该附属机构下是否有员工,true是,false否 + * @author xuyuxiang + * @date 2020/6/23 10:30 + */ + boolean hasExtOrgEmp(Long orgId); + + /** + * 根据职位id判断该附属职位下是否有员工 + * + * @param posId 职位id + * @return 该附属职位下是否有员工,true是,false否 + * @author xuyuxiang + * @date 2020/6/23 10:38 + */ + boolean hasExtPosEmp(Long posId); + + /** + * 根据员工id删除对应的员工-附属信息 + * + * @param empId 员工id(用户id) + * @author xuyuxiang + * @date 2020/6/28 14:57 + */ + void deleteEmpExtInfoByUserId(Long empId); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpPosService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpPosService.java new file mode 100644 index 0000000..a0fdc6d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpPosService.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.service; + +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.sys.modular.emp.entity.SysEmpPos; + +import java.util.List; + +/** + * 员工职位service接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:10 + */ +public interface SysEmpPosService extends IService<SysEmpPos> { + + /** + * 保存职位相关信息 + * + * @param empId 员工id(用户id) + * @param posIdList 职位id集合 + * @author xuyuxiang + * @date 2020/4/2 9:00 + */ + void addOrEdit(Long empId, List<Long> posIdList); + + /** + * 获取所属职位信息 + * + * @param empId 员工id(用户id) + * @param isFillId 是否需要返回id信息 + * @return 增强版hashMap,格式:[{"posId":456, "posCode":"zjl", "posName":"总经理"}] + * @author xuyuxiang + * @date 2020/4/2 20:07 + */ + List<Dict> getEmpPosDictList(Long empId, boolean isFillId); + + /** + * 根据职位id判断该职位下是否有员工 + * + * @param posId 职位id + * @return 该职位下是否有员工,true是,false否 + * @author xuyuxiang + * @date 2020/6/23 10:40 + */ + boolean hasPosEmp(Long posId); + + /** + * 根据员工id删除对用的员工-职位信息 + * + * @param empId 员工id(用户id) + * @author xuyuxiang + * @date 2020/6/28 14:58 + */ + void deleteEmpPosInfoByUserId(Long empId); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpService.java new file mode 100644 index 0000000..cf35801 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/SysEmpService.java @@ -0,0 +1,98 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.login.LoginEmpInfo; +import vip.xiaonuo.sys.modular.emp.entity.SysEmp; +import vip.xiaonuo.sys.modular.emp.param.SysEmpParam; +import vip.xiaonuo.sys.modular.emp.result.SysEmpInfo; + +/** + * 员工service接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:08 + */ +public interface SysEmpService extends IService<SysEmp> { + + /** + * 获取登录用户员工相关信息 + * + * @param empId 员工id(用户id) + * @return 登录用户员工相关信息 + * @author xuyuxiang + * @date 2020/3/13 15:23 + */ + LoginEmpInfo getLoginEmpInfo(Long empId); + + /** + * 获取用户员工相关信息 + * + * @param empId 员工id(用户id) + * @return 用户员工相关信息 + * @author xuyuxiang + * @date 2020/4/2 20:33 + */ + SysEmpInfo getSysEmpInfo(Long empId); + + /** + * 增加或编辑员工相关信息 + * + * @param sysEmpParam 增加或编辑参数 + * @author xuyuxiang + * @date 2020/4/2 8:44 + */ + void addOrUpdate(SysEmpParam sysEmpParam); + + /** + * 修改员工相关机构信息 + * + * @param orgId 机构id + * @param orgName 机构名称 + * @author xuyuxiang + * @date 2020/6/23 9:57 + */ + void updateEmpOrgInfo(Long orgId, String orgName); + + /** + * 根据机构id判断该机构下是否有员工 + * + * @param orgId 机构id + * @return 该机构下是否有员工,true是,false否 + * @author xuyuxiang + * @date 2020/6/23 10:30 + */ + boolean hasOrgEmp(Long orgId); + + /** + * 根据员工id删除对应的员工表信息 + * + * @param empId 员工id(用户id) + * @author xuyuxiang + * @date 2020/6/28 14:51 + */ + void deleteEmpInfoByUserId(Long empId); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpExtOrgPosPosServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpExtOrgPosPosServiceImpl.java new file mode 100644 index 0000000..3f5d503 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpExtOrgPosPosServiceImpl.java @@ -0,0 +1,157 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.sys.modular.emp.entity.SysEmpExtOrgPos; +import vip.xiaonuo.sys.modular.emp.mapper.SysEmpExtOrgPosMapper; +import vip.xiaonuo.sys.modular.emp.service.SysEmpExtOrgPosService; +import vip.xiaonuo.sys.modular.org.entity.SysOrg; +import vip.xiaonuo.sys.modular.org.service.SysOrgService; +import vip.xiaonuo.sys.modular.pos.entity.SysPos; +import vip.xiaonuo.sys.modular.pos.service.SysPosService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 员工附属机构service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 15:11 + */ +@Service +public class SysEmpExtOrgPosPosServiceImpl extends ServiceImpl<SysEmpExtOrgPosMapper, SysEmpExtOrgPos> implements SysEmpExtOrgPosService { + + @Resource + private SysOrgService sysOrgService; + + @Resource + private SysPosService sysPosService; + + /** + * 附属机构id参数键 + */ + private static final String EXT_ORG_ID_DICT_KEY = "orgId"; + + /** + * 附属机构编码参数键 + */ + private static final String EXT_ORG_CODE_DICT_KEY = "orgCode"; + + /** + * 附属机构名称参数键 + */ + private static final String EXT_ORG_NAME_DICT_KEY = "orgName"; + + /** + * 附属职位id参数键 + */ + private static final String EXT_POS_ID_DICT_KEY = "posId"; + + /** + * 附属职位编码参数键 + */ + private static final String EXT_POS_CODE_DICT_KEY = "posCode"; + + /** + * 附属职位名称参数键 + */ + private static final String EXT_POS_NAME_DICT_KEY = "posName"; + + @Override + public void addOrEdit(Long empId, List<Dict> extIdList) { + LambdaQueryWrapper<SysEmpExtOrgPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpExtOrgPos::getEmpId, empId); + //删除附属信息 + this.remove(queryWrapper); + //保存附属信息 + extIdList.forEach(dict -> { + Long orgId = dict.getLong(EXT_ORG_ID_DICT_KEY); + Long posId = dict.getLong(EXT_POS_ID_DICT_KEY); + SysEmpExtOrgPos sysEmpExtOrgPos = new SysEmpExtOrgPos(); + sysEmpExtOrgPos.setEmpId(empId); + sysEmpExtOrgPos.setOrgId(orgId); + sysEmpExtOrgPos.setPosId(posId); + this.save(sysEmpExtOrgPos); + }); + } + + @Override + public List<Dict> getEmpExtOrgPosDictList(Long empId, boolean isFillId) { + List<Dict> dictList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper<SysEmpExtOrgPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpExtOrgPos::getEmpId, empId); + + this.list(queryWrapper).forEach(sysEmpExtOrgPos -> { + Dict dict = Dict.create(); + Long orgId = sysEmpExtOrgPos.getOrgId(); + SysOrg sysOrg = sysOrgService.getById(orgId); + dict.put(EXT_ORG_CODE_DICT_KEY, sysOrg.getCode()); + dict.put(EXT_ORG_NAME_DICT_KEY, sysOrg.getName()); + + Long posId = sysEmpExtOrgPos.getPosId(); + SysPos sysPos = sysPosService.getById(posId); + dict.put(EXT_POS_CODE_DICT_KEY, sysPos.getCode()); + dict.put(EXT_POS_NAME_DICT_KEY, sysPos.getName()); + + if (isFillId) { + dict.put(EXT_ORG_ID_DICT_KEY, orgId); + dict.put(EXT_POS_ID_DICT_KEY, posId); + } + dictList.add(dict); + }); + + return dictList; + } + + @Override + public boolean hasExtOrgEmp(Long orgId) { + LambdaQueryWrapper<SysEmpExtOrgPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpExtOrgPos::getOrgId, orgId); + List<SysEmpExtOrgPos> list = this.list(queryWrapper); + return list.size() != 0; + } + + @Override + public boolean hasExtPosEmp(Long posId) { + LambdaQueryWrapper<SysEmpExtOrgPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpExtOrgPos::getPosId, posId); + List<SysEmpExtOrgPos> list = this.list(queryWrapper); + return list.size() != 0; + } + + @Override + public void deleteEmpExtInfoByUserId(Long empId) { + LambdaQueryWrapper<SysEmpExtOrgPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpExtOrgPos::getEmpId, empId); + this.remove(queryWrapper); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpPosServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpPosServiceImpl.java new file mode 100644 index 0000000..1b45813 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpPosServiceImpl.java @@ -0,0 +1,121 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.sys.modular.emp.entity.SysEmpPos; +import vip.xiaonuo.sys.modular.emp.mapper.SysEmpPosMapper; +import vip.xiaonuo.sys.modular.emp.service.SysEmpPosService; +import vip.xiaonuo.sys.modular.pos.entity.SysPos; +import vip.xiaonuo.sys.modular.pos.service.SysPosService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 员工职位service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 15:11 + */ +@Service +public class SysEmpPosServiceImpl extends ServiceImpl<SysEmpPosMapper, SysEmpPos> implements SysEmpPosService { + + @Resource + private SysPosService sysPosService; + + /** + * 职位id参数键 + */ + private static final String POS_ID_DICT_KEY = "posId"; + + /** + * 职位编码参数键 + */ + private static final String POS_CODE_DICT_KEY = "posCode"; + + /** + * 职位名称参数键 + */ + private static final String POS_NAME_DICT_KEY = "posName"; + + @Override + public void addOrEdit(Long empId, List<Long> posIdList) { + LambdaQueryWrapper<SysEmpPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpPos::getEmpId, empId); + + //删除职位信息 + this.remove(queryWrapper); + + //保存职位信息 + posIdList.forEach(posId -> { + SysEmpPos sysEmpPos = new SysEmpPos(); + sysEmpPos.setEmpId(empId); + sysEmpPos.setPosId(Convert.toLong(posId)); + this.save(sysEmpPos); + }); + } + + @Override + public List<Dict> getEmpPosDictList(Long empId, boolean isFillId) { + List<Dict> dictList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper<SysEmpPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpPos::getEmpId, empId); + + this.list(queryWrapper).forEach(sysEmpPos -> { + Dict dict = Dict.create(); + Long posId = sysEmpPos.getPosId(); + SysPos sysPos = sysPosService.getById(posId); + if (isFillId) { + dict.put(POS_ID_DICT_KEY, posId); + } + dict.put(POS_CODE_DICT_KEY, sysPos.getCode()); + dict.put(POS_NAME_DICT_KEY, sysPos.getName()); + dictList.add(dict); + }); + return dictList; + } + + @Override + public boolean hasPosEmp(Long posId) { + LambdaQueryWrapper<SysEmpPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpPos::getPosId, posId); + List<SysEmpPos> list = this.list(queryWrapper); + return list.size() != 0; + } + + @Override + public void deleteEmpPosInfoByUserId(Long empId) { + LambdaQueryWrapper<SysEmpPos> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmpPos::getEmpId, empId); + this.remove(queryWrapper); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpServiceImpl.java new file mode 100644 index 0000000..ce0e581 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/emp/service/impl/SysEmpServiceImpl.java @@ -0,0 +1,164 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.emp.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.pojo.login.LoginEmpInfo; +import vip.xiaonuo.sys.modular.emp.entity.SysEmp; +import vip.xiaonuo.sys.modular.emp.mapper.SysEmpMapper; +import vip.xiaonuo.sys.modular.emp.param.SysEmpParam; +import vip.xiaonuo.sys.modular.emp.result.SysEmpInfo; +import vip.xiaonuo.sys.modular.emp.service.SysEmpExtOrgPosService; +import vip.xiaonuo.sys.modular.emp.service.SysEmpPosService; +import vip.xiaonuo.sys.modular.emp.service.SysEmpService; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 员工service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 15:10 + */ +@Service +public class SysEmpServiceImpl extends ServiceImpl<SysEmpMapper, SysEmp> implements SysEmpService { + + @Resource + private SysEmpExtOrgPosService sysEmpExtOrgPosService; + + @Resource + private SysEmpPosService sysEmpPosService; + + @Resource + private SysUserService sysUserService; + + @Override + public LoginEmpInfo getLoginEmpInfo(Long empId) { + + LoginEmpInfo loginEmpInfo = new LoginEmpInfo(); + //获取员工信息 + SysEmp sysEmp = this.getById(empId); + if (ObjectUtil.isNotNull(sysEmp)) { + BeanUtil.copyProperties(sysEmp, loginEmpInfo); + //获取附属机构和职位信息 + List<Dict> empExtOrgPosDictList = sysEmpExtOrgPosService.getEmpExtOrgPosDictList(sysEmp.getId(), false); + loginEmpInfo.setExtOrgPos(empExtOrgPosDictList); + + //获取所属职位信息 + List<Dict> empEmpPosDictList = sysEmpPosService.getEmpPosDictList(sysEmp.getId(), false); + loginEmpInfo.setPositions(empEmpPosDictList); + } else { + loginEmpInfo.setExtOrgPos(CollectionUtil.newArrayList()); + loginEmpInfo.setPositions(CollectionUtil.newArrayList()); + } + return loginEmpInfo; + } + + @Override + public SysEmpInfo getSysEmpInfo(Long empId) { + SysEmpInfo sysEmpInfo = new SysEmpInfo(); + //获取员工信息 + SysEmp sysEmp = this.getById(empId); + if (ObjectUtil.isNotNull(sysEmp)) { + BeanUtil.copyProperties(sysEmp, sysEmpInfo); + //获取附属机构和职位信息 + List<Dict> empExtOrgPosDictList = sysEmpExtOrgPosService.getEmpExtOrgPosDictList(sysEmp.getId(), true); + sysEmpInfo.setExtOrgPos(empExtOrgPosDictList); + + //获取所属职位信息 + List<Dict> empEmpPosDictList = sysEmpPosService.getEmpPosDictList(sysEmp.getId(), true); + sysEmpInfo.setPositions(empEmpPosDictList); + } else { + sysEmpInfo.setExtOrgPos(CollectionUtil.newArrayList()); + sysEmpInfo.setPositions(CollectionUtil.newArrayList()); + } + return sysEmpInfo; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void addOrUpdate(SysEmpParam sysEmpParam) { + Long empId = sysEmpParam.getId(); + SysEmp sysEmp = this.getById(empId); + if (ObjectUtil.isNull(sysEmp)) { + sysEmp = new SysEmp(); + } + BeanUtil.copyProperties(sysEmpParam, sysEmp); + this.saveOrUpdate(sysEmp); + //编辑附属机构职位相关信息 + List<Dict> extIdList = sysEmpParam.getExtIds(); + sysEmpExtOrgPosService.addOrEdit(empId, extIdList); + //编辑职位相关信息 + List<Long> posIdList = sysEmpParam.getPosIdList(); + sysEmpPosService.addOrEdit(empId, posIdList); + } + + @Override + public void updateEmpOrgInfo(Long orgId, String orgName) { + LambdaQueryWrapper<SysEmp> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmp::getOrgId, orgId); + this.list(queryWrapper).forEach(sysEmp -> { + sysEmp.setOrgName(orgName); + this.updateById(sysEmp); + }); + } + + @Override + public boolean hasOrgEmp(Long orgId) { + LambdaQueryWrapper<SysEmp> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysEmp::getOrgId, orgId); + List<SysEmp> list = this.list(queryWrapper); + if(ObjectUtil.isEmpty(list)) { + return false; + } else { + Set<Long> collect = list.stream().map(SysEmp::getId).collect(Collectors.toSet()); + return sysUserService.hasAllDeletedUser(collect); + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void deleteEmpInfoByUserId(Long empId) { + //删除员工信息 + this.removeById(empId); + + //级联删除对应的员工-附属信息 + sysEmpExtOrgPosService.deleteEmpExtInfoByUserId(empId); + + //级联删除对用的员工-职位信息 + sysEmpPosService.deleteEmpPosInfoByUserId(empId); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/controller/SysFileInfoController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/controller/SysFileInfoController.java new file mode 100644 index 0000000..3fc593f --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/controller/SysFileInfoController.java @@ -0,0 +1,180 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.controller; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONUtil; +import org.springframework.ui.Model; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.file.param.SysFileInfoParam; +import vip.xiaonuo.sys.modular.file.result.SysOnlineFileInfoResult; +import vip.xiaonuo.sys.modular.file.service.SysFileInfoService; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; + +/** + * 文件信息表 控制器 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +@RestController +public class SysFileInfoController { + + @Resource + private SysFileInfoService sysFileInfoService; + + /** + * onlyoffice资源文件路径 + */ + public static final String ONLY_OFFICE_APP_JS_SUFFIX = "/web-apps/apps/api/documents/api.js"; + + /** + * 在线文档配置 + * + * @author xuyuxiang + * @date 2020/11/17 16:40 + */ + @GetMapping("/sysFileInfo/getOnlineFileConfig") + public ResponseData getOnlineFileConfig(SysFileInfoParam sysFileInfoParam) { + //生成在线文档的model + SysOnlineFileInfoResult sysOnlineFileInfoResult = sysFileInfoService.onlineAddOrUpdate(sysFileInfoParam); + Dict dict = Dict.create(); + dict.put("docServiceApiUrl", ConstantContextHolder.getOnlyOfficeUrl() + ONLY_OFFICE_APP_JS_SUFFIX); + dict.put("sysOnlineFileInfoResult", sysOnlineFileInfoResult); + return new SuccessResponseData(dict); + } + + /** + * 上传文件 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + @PostMapping("/sysFileInfo/upload") + @BusinessLog(title = "文件信息表_上传文件", opType = LogAnnotionOpTypeEnum.OTHER) + public ResponseData upload(@RequestPart("file") MultipartFile file) { + Long fileId = sysFileInfoService.uploadFile(file); + return new SuccessResponseData(fileId); + } + + /** + * 下载文件 + * + * @author yubaoshan, xuyuxiang + * @date 2020/6/9 21:53 + */ + @GetMapping("/sysFileInfo/download") + @BusinessLog(title = "文件信息表_下载文件", opType = LogAnnotionOpTypeEnum.OTHER) + public void download(@Validated(SysFileInfoParam.detail.class) SysFileInfoParam sysFileInfoParam, HttpServletResponse response) { + sysFileInfoService.download(sysFileInfoParam, response); + } + + /** + * 文件预览 + * + * @author yubaoshan, xuyuxiang + * @date 2020/6/9 22:07 + */ + @GetMapping("/sysFileInfo/preview") + public void preview(@Validated(SysFileInfoParam.detail.class) SysFileInfoParam sysFileInfoParam, HttpServletResponse response) { + sysFileInfoService.preview(sysFileInfoParam, response); + } + + /** + * 分页查询文件信息表 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + @Permission + @GetMapping("/sysFileInfo/page") + @BusinessLog(title = "文件信息表_分页查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysFileInfoParam sysFileInfoParam) { + return new SuccessResponseData(sysFileInfoService.page(sysFileInfoParam)); + } + + /** + * 获取全部文件信息表 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + @Permission + @GetMapping("/sysFileInfo/list") + @BusinessLog(title = "文件信息表_查询所有", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysFileInfoParam sysFileInfoParam) { + return new SuccessResponseData(sysFileInfoService.list(sysFileInfoParam)); + } + + /** + * 查看详情文件信息表 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + @Permission + @GetMapping("/sysFileInfo/detail") + @BusinessLog(title = "文件信息表_查看详情", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysFileInfoParam.detail.class) SysFileInfoParam sysFileInfoParam) { + return new SuccessResponseData(sysFileInfoService.detail(sysFileInfoParam)); + } + + /** + * 删除文件信息表 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + @Permission + @PostMapping("/sysFileInfo/delete") + @BusinessLog(title = "文件信息表_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysFileInfoParam.delete.class) SysFileInfoParam sysFileInfoParam) { + sysFileInfoService.delete(sysFileInfoParam); + return new SuccessResponseData(); + } + + /** + * 在线文档编辑回调 + * + * @author xuyuxiang + * @date 2021/3/25 16:06 + */ + @ResponseBody + @PostMapping("/sysFileInfo/track") + public void track() { + sysFileInfoService.track(); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/entity/SysFileInfo.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/entity/SysFileInfo.java new file mode 100644 index 0000000..5e33a5f --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/entity/SysFileInfo.java @@ -0,0 +1,93 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * <p> + * 文件信息表 + * </p> + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_file_info") +public class SysFileInfo extends BaseEntity { + + /** + * 主键id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地) + */ + private Integer fileLocation; + + /** + * 文件仓库 + */ + private String fileBucket; + + /** + * 文件名称(上传时候的文件名) + */ + private String fileOriginName; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 文件大小kb + */ + private Long fileSizeKb; + + /** + * 文件大小信息,计算后的 + */ + private String fileSizeInfo; + + /** + * 存储到bucket的名称(文件唯一标识id) + */ + private String fileObjectName; + + /** + * 存储路径 + */ + private String filePath; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/enums/SysFileInfoExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/enums/SysFileInfoExceptionEnum.java new file mode 100644 index 0000000..78f110e --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/enums/SysFileInfoExceptionEnum.java @@ -0,0 +1,110 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 文件信息表相关枚举 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_FILE_INFO_EXCEPTION_ENUM) +public enum SysFileInfoExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 该条记录不存在 + */ + NOT_EXISTED(1, "您查询的该条记录不存在"), + + /** + * 获取文件流错误 + */ + FILE_STREAM_ERROR(2, "获取文件流错误"), + + /** + * 文件不存在 + */ + NOT_EXISTED_FILE(3, "文件不存在"), + + /** + * 获取上传文件异常 + */ + ERROR_FILE(4, "获取上传文件异常"), + + /** + * 下载文件错误 + */ + DOWNLOAD_FILE_ERROR(5, "下载文件错误"), + + /** + * 预览文件异常 + */ + PREVIEW_ERROR_NOT_SUPPORT(6, "预览文件异常,您预览的文件类型不支持或文件出现错误"), + + /** + * 预览文件异常 + */ + PREVIEW_ERROR_LIBREOFFICE(7, "预览文件异常,请检查LibreOffice是否启动"), + + /** + * 文件操作客户端初始化异常 + */ + CLIENT_INIT_ERROR(8, "文件操作客户端初始化异常"), + + /** + * 在线文档暂时只支持本地文件 + */ + ONLINE_EDIT_SUPPORT_LOCAL_ONLY(9, "在线文档暂时只支持本地文件"), + + /** + * 在线文档参数错误 + */ + ONLINE_EDIT_PARAM_ERROR(10, "在线文档参数错误"); + + private final Integer code; + + private final String message; + + SysFileInfoExceptionEnum(int code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/enums/SysFileLocationEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/enums/SysFileLocationEnum.java new file mode 100644 index 0000000..f37075a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/enums/SysFileLocationEnum.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.enums; + +import lombok.Getter; + +/** + * 文件存储位置 + * + * @author yubaoshan + * @date 2020/6/7 22:24 + */ +@Getter +public enum SysFileLocationEnum { + + /** + * 阿里云 + */ + ALIYUN(1), + + /** + * 腾讯云 + */ + TENCENT(2), + + /** + * minio服务器 + */ + MINIO(3), + + /** + * 本地 + */ + LOCAL(4); + + private final Integer code; + + SysFileLocationEnum(int code) { + this.code = code; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/mapper/SysFileInfoMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/mapper/SysFileInfoMapper.java new file mode 100644 index 0000000..1dce14f --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/mapper/SysFileInfoMapper.java @@ -0,0 +1,40 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.file.entity.SysFileInfo; + +/** + * <p> + * 文件信息表 Mapper 接口 + * </p> + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +public interface SysFileInfoMapper extends BaseMapper<SysFileInfo> { + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/mapper/mapping/SysFileInfoMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/mapper/mapping/SysFileInfoMapper.xml new file mode 100644 index 0000000..15efc75 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/mapper/mapping/SysFileInfoMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.file.mapper.SysFileInfoMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/param/SysFileInfoParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/param/SysFileInfoParam.java new file mode 100644 index 0000000..ea7f938 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/param/SysFileInfoParam.java @@ -0,0 +1,103 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * <p> + * 文件信息表 + * </p> + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysFileInfoParam extends BaseParam { + + /** + * 主键id + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {delete.class, detail.class}) + private Long id; + + /** + * 文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地) + */ + private Integer fileLocation; + + /** + * 文件仓库 + */ + private String fileBucket; + + /** + * 文件名称(上传时候的文件名) + */ + private String fileOriginName; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 文件大小kb + */ + private Long fileSizeKb; + + /** + * 存储到bucket的名称(文件唯一标识id) + */ + @NotEmpty(message = "存储到bucket的名称不能为空,请检查fileObjectName参数", groups = {trace.class}) + private String fileObjectName; + + /** + * 存储路径 + */ + private String filePath; + + /** + * 文档创建时是否创建相同文件内容的模板文件 + */ + private Boolean sample = false; + + /** + * 模式:编辑edit 查看view + */ + private String mode; + + /** + * 类型:桌面desktop 手机mobile + */ + private String type; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/result/SysFileInfoResult.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/result/SysFileInfoResult.java new file mode 100644 index 0000000..4172268 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/result/SysFileInfoResult.java @@ -0,0 +1,83 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.result; + +import lombok.Data; + +/** + * 文件信息结果集 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +@Data +public class SysFileInfoResult { + + /** + * 主键id + */ + private Long id; + + /** + * 文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地) + */ + private Integer fileLocation; + + /** + * 文件仓库 + */ + private String fileBucket; + + /** + * 文件名称(上传时候的文件名) + */ + private String fileOriginName; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 文件大小kb + */ + private Long fileSizeKb; + + /** + * 存储到bucket的名称(文件唯一标识id) + */ + private String fileObjectName; + + /** + * 存储路径 + */ + private String filePath; + + /** + * 文件的字节 + */ + private byte[] fileBytes; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/result/SysOnlineFileInfoResult.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/result/SysOnlineFileInfoResult.java new file mode 100644 index 0000000..ef06040 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/result/SysOnlineFileInfoResult.java @@ -0,0 +1,187 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy-layui +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy-layui +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.result; + +import cn.hutool.core.io.FileUtil; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import lombok.Data; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.sys.modular.file.util.OnlineDocumentUtil; + +import java.io.File; +import java.util.HashMap; + +/** + * 在线文件信息结果集 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +@Data +public class SysOnlineFileInfoResult { + + public String fileId; + public String[] history; + public String type = "desktop"; + public String mode = "edit"; + public String documentType; + public Document document; + public EditorConfig editorConfig; + public String token; + + public static class Document { + public String title; + public String url; + public String fileType; + public String key; + public Permissions permissions; + } + + public static class Permissions { + public Boolean comment; + public Boolean download; + public Boolean edit; + public Boolean fillForms; + public Boolean modifyFilter; + public Boolean modifyContentControl; + public Boolean review; + + public Permissions(String mode, String type, Boolean canEdit) { + comment = !mode.equals("view") && !mode.equals("fillForms") && !mode.equals("embedded") && !mode.equals("blockcontent"); + download = true; + edit = canEdit && (mode.equals("edit") || mode.equals("filter") || mode.equals("blockcontent")); + fillForms = !mode.equals("view") && !mode.equals("comment") && !mode.equals("embedded") && !mode.equals("blockcontent"); + modifyFilter = !mode.equals("filter"); + modifyContentControl = !mode.equals("blockcontent"); + review = mode.equals("edit") || mode.equals("review"); + } + } + + public static class EditorConfig { + public HashMap<String, Object> actionLink = null; + public String mode = "edit"; + public String callbackUrl; + public String lang = "en"; + public Integer forcesavetype = 1; + public User user; + public Customization customization; + public Embedded embedded; + + public EditorConfig(String actionData) { + if (actionData != null) { + Gson gson = new Gson(); + actionLink = gson.fromJson(actionData, new TypeToken<HashMap<String, Object>>() { }.getType()); + } + user = new User(); + customization = new Customization(); + } + + public void InitDesktop(String url) { + embedded = new Embedded(); + embedded.saveUrl = url; + embedded.embedUrl = url; + embedded.shareUrl = url; + embedded.toolbarDocked = "top"; + } + + public static class User { + public String id = "-1"; + public String name = CommonConstant.UNKNOWN; + } + + public static class Customization { + public GoBack goback; + public Boolean forcesave = true; + public Customization() + { + goback = new GoBack(); + } + + public class GoBack { + public String url; + } + } + + public static class Embedded { + public String saveUrl; + public String embedUrl; + public String shareUrl; + public String toolbarDocked; + } + } + + public SysOnlineFileInfoResult(String fileId, String fileOriginName, String userId, String userName) { + if (fileOriginName == null) fileOriginName = ""; + fileOriginName = fileOriginName.trim(); + documentType = OnlineDocumentUtil.getFileType(fileOriginName).toLowerCase(); + this.fileId = fileId; + document = new Document(); + document.title = fileOriginName; + document.url = OnlineDocumentUtil.getFileUri(fileId, fileOriginName); + document.fileType = FileUtil.getSuffix(fileOriginName); + document.key = GenerateRevisionId(fileOriginName + SymbolConstant.PERIOD + new File(OnlineDocumentUtil.getStoragePath(fileId + SymbolConstant.PERIOD + FileUtil.getSuffix(fileOriginName))).lastModified()); + + editorConfig = new EditorConfig(null); + editorConfig.callbackUrl = OnlineDocumentUtil.getCallback(fileId, document.fileType); + editorConfig.lang = "zh"; + + if (userId != null) editorConfig.user.id = userId; + if (userName != null) editorConfig.user.name = userName; + + editorConfig.customization.goback.url = ""; + + changeType(mode, type); + } + + public void changeType(String _mode, String _type) { + if (_mode != null) mode = _mode; + if (_type != null) type = _type; + + Boolean canEdit = OnlineDocumentUtil.getEditedSuffix().contains(SymbolConstant.PERIOD + FileUtil.getSuffix(document.title)); + + editorConfig.mode = canEdit && !mode.equals("view") ? "edit" : "view"; + + document.permissions = new Permissions(mode, type, canEdit); + + if (type.equals("embedded")) InitDesktop(); + } + + public static String GenerateRevisionId(String expectedKey) { + if (expectedKey.length() > 20) + expectedKey = Integer.toString(expectedKey.hashCode()); + + String key = expectedKey.replace("[^0-9-.a-zA-Z_=]", "_"); + + return key.substring(0, Math.min(key.length(), 20)); + } + + public void InitDesktop() { + editorConfig.InitDesktop(document.url); + } + + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/service/SysFileInfoService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/service/SysFileInfoService.java new file mode 100644 index 0000000..addf73d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/service/SysFileInfoService.java @@ -0,0 +1,168 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.web.multipart.MultipartFile; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.file.entity.SysFileInfo; +import vip.xiaonuo.sys.modular.file.param.SysFileInfoParam; +import vip.xiaonuo.sys.modular.file.result.SysFileInfoResult; +import vip.xiaonuo.sys.modular.file.result.SysOnlineFileInfoResult; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件信息表 服务类 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +public interface SysFileInfoService extends IService<SysFileInfo> { + + /** + * 分页查询文件信息表 + * + * @param sysFileInfoParam 查询参数 + * @return 查询分页结果 + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + PageResult<SysFileInfo> page(SysFileInfoParam sysFileInfoParam); + + /** + * 查询所有文件信息表 + * + * @param sysFileInfoParam 查询参数 + * @return 文件信息列表 + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + List<SysFileInfo> list(SysFileInfoParam sysFileInfoParam); + + /** + * 添加文件信息表 + * + * @param sysFileInfoParam 添加参数 + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + void add(SysFileInfoParam sysFileInfoParam); + + /** + * 删除文件信息表 + * + * @param sysFileInfoParam 删除参数 + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + void delete(SysFileInfoParam sysFileInfoParam); + + /** + * 编辑文件信息表 + * + * @param sysFileInfoParam 编辑参数 + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + void edit(SysFileInfoParam sysFileInfoParam); + + /** + * 查看详情文件信息表 + * + * @param sysFileInfoParam 查看参数 + * @return 文件信息 + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + SysFileInfo detail(SysFileInfoParam sysFileInfoParam); + + /** + * 上传文件,返回文件的唯一标识 + * + * @param file 要上传的文件 + * @return 文件id + * @author yubaoshan + * @date 2020/6/9 21:21 + */ + Long uploadFile(MultipartFile file); + + /** + * 获取文件信息结果集 + * + * @param fileId 文件id + * @return 文件信息结果集 + * @author yubaoshan + * @date 2020/6/9 21:56 + */ + SysFileInfoResult getFileInfoResult(Long fileId); + + /** + * 判断文件是否存在 + * + * @param field 文件id + * @author xuyuxiang + * @date 2020/6/28 15:55 + */ + void assertFile(Long field); + + /** + * 文件预览 + * + * @param sysFileInfoParam 文件预览参数 + * @param response 响应结果 + * @author xuyuxiang + * @date 2020/7/7 11:23 + */ + void preview(SysFileInfoParam sysFileInfoParam, HttpServletResponse response); + + /** + * 文件下载 + * + * @param sysFileInfoParam 文件下载参数 + * @param response 响应结果 + * @author xuyuxiang + * @date 2020/7/7 12:09 + */ + void download(SysFileInfoParam sysFileInfoParam, HttpServletResponse response); + + /** + * 新增或编辑在线文档 + * + * @param sysFileInfoParam 新增或编辑参数 + * @author xuyuxiang + * @date 2021/3/24 10:02 + */ + SysOnlineFileInfoResult onlineAddOrUpdate(SysFileInfoParam sysFileInfoParam); + + /** + * 在线文档编辑回调 + * + * @author xuyuxiang + * @date 2021/3/25 15:48 + */ + void track(); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/service/impl/SysFileInfoServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/service/impl/SysFileInfoServiceImpl.java new file mode 100644 index 0000000..8497ef2 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/service/impl/SysFileInfoServiceImpl.java @@ -0,0 +1,540 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import cn.hutool.log.Log; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.context.requestno.RequestNoContext; +import vip.xiaonuo.core.exception.LibreOfficeException; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.file.FileOperator; +import vip.xiaonuo.core.file.modular.local.LocalFileOperator; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.HttpServletUtil; +import vip.xiaonuo.core.util.LibreOfficeUtil; +import vip.xiaonuo.sys.modular.file.entity.SysFileInfo; +import vip.xiaonuo.sys.modular.file.enums.SysFileLocationEnum; +import vip.xiaonuo.sys.modular.file.enums.SysFileInfoExceptionEnum; +import vip.xiaonuo.sys.modular.file.mapper.SysFileInfoMapper; +import vip.xiaonuo.sys.modular.file.param.SysFileInfoParam; +import vip.xiaonuo.sys.modular.file.result.SysFileInfoResult; +import vip.xiaonuo.sys.modular.file.result.SysOnlineFileInfoResult; +import vip.xiaonuo.sys.modular.file.service.SysFileInfoService; +import vip.xiaonuo.sys.modular.file.util.DownloadUtil; +import vip.xiaonuo.sys.modular.file.util.OnlineDocumentUtil; + +import javax.annotation.Resource; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +import static vip.xiaonuo.sys.config.FileConfig.DEFAULT_BUCKET; + +/** + * 文件信息表 服务实现类 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ +@Service +public class SysFileInfoServiceImpl extends ServiceImpl<SysFileInfoMapper, SysFileInfo> implements SysFileInfoService { + + private static final Log log = Log.get(); + + @Resource + private FileOperator fileOperator; + + @Override + public SysOnlineFileInfoResult onlineAddOrUpdate(SysFileInfoParam sysFileInfoParam) { + if(fileOperator instanceof LocalFileOperator) { + //文件后缀 + String fileSuffix = sysFileInfoParam.getFileSuffix(); + //文件名称 + String fileOriginName = sysFileInfoParam.getFileOriginName(); + //文件id + Long id = sysFileInfoParam.getId(); + //参数错误 + if(ObjectUtil.isAllEmpty(fileSuffix, fileOriginName, id)) { + throw new ServiceException(SysFileInfoExceptionEnum.ONLINE_EDIT_PARAM_ERROR); + } + //获取登录用户 + SysLoginUser sysLoginUser = LoginContextHolder.me().getSysLoginUser(); + SysFileInfo sysFileInfo; + SysOnlineFileInfoResult sysOnlineFileInfoResult; + //文件id不为空则表示编辑 + if(ObjectUtil.isNotEmpty(id)) { + sysFileInfo = this.getById(id); + sysOnlineFileInfoResult= new SysOnlineFileInfoResult(Convert.toStr(sysFileInfo.getId()), sysFileInfo.getFileOriginName(), Convert.toStr(sysLoginUser.getId()), sysLoginUser.getName()); + } else { + //否则表示新增 + Boolean sample = sysFileInfoParam.getSample(); + sysFileInfo = createDemo(fileSuffix, fileOriginName, sample, Convert.toStr(sysLoginUser.getId()), sysLoginUser.getName()); + sysOnlineFileInfoResult= new SysOnlineFileInfoResult(Convert.toStr(sysFileInfo.getId()), fileOriginName + SymbolConstant.PERIOD + fileSuffix, Convert.toStr(sysLoginUser.getId()), sysLoginUser.getName()); + + } + //设置history + sysOnlineFileInfoResult.history = OnlineDocumentUtil.getHistory(sysOnlineFileInfoResult); + if(ObjectUtil.isAllNotEmpty(sysFileInfoParam.getMode(), sysFileInfoParam.getType())) { + sysOnlineFileInfoResult.changeType(sysFileInfoParam.getMode(), sysFileInfoParam.getType()); + } + return sysOnlineFileInfoResult; + } else { + //暂时只支持本地文件 + throw new ServiceException(SysFileInfoExceptionEnum.ONLINE_EDIT_SUPPORT_LOCAL_ONLY); + } + } + + @Override + public PageResult<SysFileInfo> page(SysFileInfoParam sysFileInfoParam) { + + // 构造条件 + LambdaQueryWrapper<SysFileInfo> queryWrapper = new LambdaQueryWrapper<>(); + + // 拼接查询条件-文件存储位置(1:阿里云,2:腾讯云,3:minio,4:本地) + if (ObjectUtil.isNotNull(sysFileInfoParam)) { + if (ObjectUtil.isNotEmpty(sysFileInfoParam.getFileLocation())) { + queryWrapper.like(SysFileInfo::getFileLocation, sysFileInfoParam.getFileLocation()); + } + + // 拼接查询条件-文件仓库 + if (ObjectUtil.isNotEmpty(sysFileInfoParam.getFileBucket())) { + queryWrapper.like(SysFileInfo::getFileBucket, sysFileInfoParam.getFileBucket()); + } + + // 拼接查询条件-文件名称(上传时候的文件名) + if (ObjectUtil.isNotEmpty(sysFileInfoParam.getFileOriginName())) { + queryWrapper.like(SysFileInfo::getFileOriginName, sysFileInfoParam.getFileOriginName()); + } + + // 根据后缀查询 + if(ObjectUtil.isNotEmpty(sysFileInfoParam.getFileSuffix())) { + if(sysFileInfoParam.getFileSuffix().contains(SymbolConstant.COMMA)) { + queryWrapper.in(SysFileInfo::getFileSuffix, Arrays.asList(sysFileInfoParam.getFileSuffix().split(SymbolConstant.COMMA))); + } else { + queryWrapper.eq(SysFileInfo::getFileSuffix, sysFileInfoParam.getFileSuffix()); + } + } + } + + // 查询分页结果 + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<SysFileInfo> list(SysFileInfoParam sysFileInfoParam) { + + // 构造条件 + LambdaQueryWrapper<SysFileInfo> queryWrapper = new LambdaQueryWrapper<>(); + + return this.list(queryWrapper); + } + + @Override + public void add(SysFileInfoParam sysFileInfoParam) { + + // 将dto转为实体 + SysFileInfo sysFileInfo = new SysFileInfo(); + BeanUtil.copyProperties(sysFileInfoParam, sysFileInfo); + + this.save(sysFileInfo); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(SysFileInfoParam sysFileInfoParam) { + + // 查询文件的信息 + SysFileInfo sysFileInfo = this.getById(sysFileInfoParam.getId()); + + // 删除文件记录 + this.removeById(sysFileInfoParam.getId()); + + // 删除具体文件 + this.fileOperator.deleteFile(sysFileInfo.getFileBucket(), sysFileInfo.getFileObjectName()); + } + + @Override + public void edit(SysFileInfoParam sysFileInfoParam) { + + // 根据id查询实体 + SysFileInfo sysFileInfo = this.querySysFileInfo(sysFileInfoParam); + + // 请求参数转化为实体 + BeanUtil.copyProperties(sysFileInfoParam, sysFileInfo); + + this.updateById(sysFileInfo); + } + + @Override + public SysFileInfo detail(SysFileInfoParam sysFileInfoParam) { + return this.querySysFileInfo(sysFileInfoParam); + } + + @Override + public Long uploadFile(MultipartFile file) { + + // 生成文件的唯一id + Long fileId = IdWorker.getId(); + + // 获取文件原始名称 + String originalFilename = file.getOriginalFilename(); + + // 获取文件后缀 + String fileSuffix = null; + + if (ObjectUtil.isNotEmpty(originalFilename)) { + fileSuffix = StrUtil.subAfter(originalFilename, SymbolConstant.PERIOD, true); + } + // 生成文件的最终名称 + String finalName = fileId + SymbolConstant.PERIOD + fileSuffix; + + // 存储文件 + byte[] bytes; + try { + bytes = file.getBytes(); + fileOperator.storageFile(DEFAULT_BUCKET, finalName, bytes); + } catch (IOException e) { + throw new ServiceException(SysFileInfoExceptionEnum.ERROR_FILE); + } + + // 计算文件大小kb + long fileSizeKb = Convert.toLong(NumberUtil.div(new BigDecimal(file.getSize()), BigDecimal.valueOf(1024)) + .setScale(0, BigDecimal.ROUND_HALF_UP)); + + //计算文件大小信息 + String fileSizeInfo = FileUtil.readableFileSize(file.getSize()); + + // 存储文件信息 + SysFileInfo sysFileInfo = new SysFileInfo(); + sysFileInfo.setId(fileId); + sysFileInfo.setFileLocation(SysFileLocationEnum.LOCAL.getCode()); + sysFileInfo.setFileBucket(DEFAULT_BUCKET); + sysFileInfo.setFileObjectName(finalName); + sysFileInfo.setFileOriginName(originalFilename); + sysFileInfo.setFileSuffix(fileSuffix); + sysFileInfo.setFileSizeKb(fileSizeKb); + sysFileInfo.setFileSizeInfo(fileSizeInfo); + this.save(sysFileInfo); + + // 返回文件id + return fileId; + } + + @Override + public SysFileInfoResult getFileInfoResult(Long fileId) { + byte[] fileBytes; + // 获取文件名 + SysFileInfo sysFileInfo = this.getById(fileId); + if (sysFileInfo == null) { + throw new ServiceException(SysFileInfoExceptionEnum.NOT_EXISTED_FILE); + } + try { + // 返回文件字节码 + fileBytes = fileOperator.getFileBytes(DEFAULT_BUCKET, sysFileInfo.getFileObjectName()); + } catch (Exception e) { + log.error(">>> 获取文件流异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + throw new ServiceException(SysFileInfoExceptionEnum.FILE_STREAM_ERROR); + } + + SysFileInfoResult sysFileInfoResult = new SysFileInfoResult(); + BeanUtil.copyProperties(sysFileInfo, sysFileInfoResult); + sysFileInfoResult.setFileBytes(fileBytes); + + return sysFileInfoResult; + } + + @Override + public void assertFile(Long field) { + SysFileInfo sysFileInfo = this.getById(field); + if (ObjectUtil.isEmpty(sysFileInfo)) { + throw new ServiceException(SysFileInfoExceptionEnum.NOT_EXISTED); + } + } + + @Override + public void preview(SysFileInfoParam sysFileInfoParam, HttpServletResponse response) { + + byte[] fileBytes; + //根据文件id获取文件信息结果集 + SysFileInfoResult sysFileInfoResult = this.getFileInfoResult(sysFileInfoParam.getId()); + //获取文件后缀 + String fileSuffix = sysFileInfoResult.getFileSuffix().toLowerCase(); + //获取文件字节码 + fileBytes = sysFileInfoResult.getFileBytes(); + //如果是图片类型,则直接输出 + if (LibreOfficeUtil.isPic(fileSuffix)) { + try { + //设置contentType + response.setContentType(MediaType.IMAGE_JPEG_VALUE); + //获取outputStream + ServletOutputStream outputStream = response.getOutputStream(); + //输出 + IoUtil.write(outputStream, true, fileBytes); + } catch (IOException e) { + throw new ServiceException(SysFileInfoExceptionEnum.PREVIEW_ERROR_NOT_SUPPORT); + } + + } else if (LibreOfficeUtil.isDoc(fileSuffix)) { + try { + //如果是文档类型,则使用libreoffice转换为pdf或html + InputStream inputStream = IoUtil.toStream(fileBytes); + + //获取目标contentType(word和ppt和text转成pdf,excel转成html) + String targetContentType = LibreOfficeUtil.getTargetContentTypeBySuffix(fileSuffix); + + //设置contentType + response.setContentType(targetContentType); + + //获取outputStream + ServletOutputStream outputStream = response.getOutputStream(); + + //转换 + LibreOfficeUtil.convertToPdf(inputStream, outputStream, fileSuffix); + + //输出 + IoUtil.write(outputStream, true, fileBytes); + } catch (IOException e) { + log.error(">>> 预览文件异常", e.getMessage()); + throw new ServiceException(SysFileInfoExceptionEnum.PREVIEW_ERROR_NOT_SUPPORT); + + } catch (LibreOfficeException e) { + log.error(">>> 初始化LibreOffice失败", e.getMessage()); + throw new ServiceException(SysFileInfoExceptionEnum.PREVIEW_ERROR_LIBREOFFICE); + } + + } else { + //否则不支持预览(暂时) + throw new ServiceException(SysFileInfoExceptionEnum.PREVIEW_ERROR_NOT_SUPPORT); + } + } + + @Override + public void download(SysFileInfoParam sysFileInfoParam, HttpServletResponse response) { + // 获取文件信息结果集 + SysFileInfoResult sysFileInfoResult = this.getFileInfoResult(sysFileInfoParam.getId()); + String fileName = sysFileInfoResult.getFileOriginName(); + byte[] fileBytes = sysFileInfoResult.getFileBytes(); + DownloadUtil.download(fileName, fileBytes, response); + } + + /** + * 获取文件信息表 + * + * @author yubaoshan + * @date 2020/6/7 22:15 + */ + private SysFileInfo querySysFileInfo(SysFileInfoParam sysFileInfoParam) { + SysFileInfo sysFileInfo = this.getById(sysFileInfoParam.getId()); + if (ObjectUtil.isEmpty(sysFileInfo)) { + throw new ServiceException(SysFileInfoExceptionEnum.NOT_EXISTED); + } + return sysFileInfo; + } + + @Override + public void track() { + HttpServletRequest request = HttpServletUtil.getRequest(); + HttpServletResponse response = HttpServletUtil.getResponse(); + String fileObjectName = request.getParameter("fileObjectName"); + String id = request.getParameter("id"); + String storagePath = OnlineDocumentUtil.getStoragePath(id + SymbolConstant.PERIOD + FileUtil.getSuffix(fileObjectName)); + String body = ""; + Scanner scanner; + + try { + scanner = new Scanner(request.getInputStream()); + scanner.useDelimiter("\\A"); + body = scanner.hasNext() ? scanner.next() : ""; + scanner.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + JSONObject jsonObj; + + if (body.isEmpty()) { + log.error(">>> 读取文件request输入流为空"); + return; + } + + try { + jsonObj = JSONObject.parseObject(body); + } catch (Exception ex) { + log.error(">>> 文件信息body格式化错误"); + return; + } + + int status = (int) jsonObj.get("status"); + String downloadUri = (String) jsonObj.get("url"); + String changesUri = (String) jsonObj.get("changesurl"); + String key = (String) jsonObj.get("key"); + + int saved = 0; + if (status == 2 || status == 3) { + //MustSave, Corrupted + try { + String histDir = OnlineDocumentUtil.getHistoryDir(OnlineDocumentUtil.getStoragePath(id)); + String versionDir = OnlineDocumentUtil.getVersionDir(histDir, OnlineDocumentUtil.getFileVersion(histDir) + 1); + File ver = new File(versionDir); + File toSave = new File(storagePath); + if (!ver.exists()) ver.mkdirs(); + toSave.renameTo(new File(versionDir + File.separator + "prev" + SymbolConstant.PERIOD + FileUtil.getSuffix(fileObjectName))); + DownloadUtil.downloadToFile(downloadUri, toSave); + DownloadUtil.downloadToFile(changesUri, new File(versionDir + File.separator + "diff.zip")); + + String history = (String) jsonObj.get("changeshistory"); + if (history == null && jsonObj.containsKey("history")) { + history = ((JSONObject) jsonObj.get("history")).toJSONString(); + } + if (history != null && !history.isEmpty()) { + FileWriter fw = new FileWriter(new File(versionDir + File.separator + "changes.json")); + fw.write(history); + fw.close(); + } + + FileWriter fw = new FileWriter(new File(versionDir + File.separator + "key.txt")); + fw.write(key); + fw.close(); + } catch (Exception ex) { + saved = 1; + } + } + try { + response.getWriter().write("{\"error\":" + saved + "}"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * 创建模板文件 + * + * @param fileSuffix 文件后缀 + * @param originalFilename 文件原始名称 + * @param sample 是否创建相同文件内容的模板文件 + * @param userId 用户id + * @param userName 用户名称 + * @author xuyuxiang + * @date 2021/3/24 11:01 + */ + public SysFileInfo createDemo(String fileSuffix, String originalFilename, Boolean sample, String userId, String userName) { + // 文件名称拼接 + originalFilename = originalFilename + SymbolConstant.PERIOD + fileSuffix; + // 模板名称 + String demoName = (sample ? "sample." : "new.") + fileSuffix; + InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("assets/" + demoName); + // 生成文件的唯一id + Long fileId = IdWorker.getId(); + // 生成文件的最终名称 + String finalName = fileId + SymbolConstant.PERIOD + fileSuffix; + // 读取流 + byte[] bytes = IoUtil.readBytes(stream); + // 将该模板文件存到存储桶 + fileOperator.storageFile(DEFAULT_BUCKET, finalName, bytes); + // 创建元数据信息 + createMeta(Convert.toStr(fileId), userId, userName); + // 计算文件大小kb + long fileSizeKb = Convert.toLong(NumberUtil.div(new BigDecimal(bytes.length), BigDecimal.valueOf(1024)) + .setScale(0, BigDecimal.ROUND_HALF_UP)); + // 计算文件大小信息 + String fileSizeInfo = FileUtil.readableFileSize(bytes.length); + // 存储文件信息 + SysFileInfo sysFileInfo = new SysFileInfo(); + sysFileInfo.setId(fileId); + sysFileInfo.setFileLocation(SysFileLocationEnum.LOCAL.getCode()); + sysFileInfo.setFileBucket(DEFAULT_BUCKET); + sysFileInfo.setFileObjectName(finalName); + sysFileInfo.setFileOriginName(originalFilename); + sysFileInfo.setFileSuffix(fileSuffix); + sysFileInfo.setFileSizeKb(fileSizeKb); + sysFileInfo.setFileSizeInfo(fileSizeInfo); + // 将新创建的文件保存到数据库 + this.save(sysFileInfo); + return sysFileInfo; + } + + /** + * 创建元数据信息 + * + * @param fileId 文件id + * @param userId 用户id + * @param userName 用户名称 + * @author xuyuxiang + * @date 2021/3/24 11:19 + */ + public void createMeta(String fileId, String userId, String userName) { + // 仅限本地文件 + Object localClient = fileOperator.getClient(); + if(ObjectUtil.isNull(localClient)) { + throw new ServiceException(SysFileInfoExceptionEnum.CLIENT_INIT_ERROR); + } + Dict localClientDict = (Dict) localClient; + // 拼接获取文档历史路径 + String histDir = localClientDict.getStr("currentSavePath") + File.separator + DEFAULT_BUCKET + File.separator + fileId + "-hist"; + if(!FileUtil.exist(histDir)) { + // 历史路径不存在则创建 + File dir = new File(histDir); + dir.mkdir(); + } + Dict dict = new Dict(); + dict.put("created", DateUtil.now()); + dict.put("id", (userId == null || userId.isEmpty()) ? -1 : userId); + dict.put("name", (userName == null || userName.isEmpty()) ? CommonConstant.UNKNOWN : userName); + File metaFile = new File(histDir + File.separator + "createdInfo.json"); + FileUtil.writeString(JSONUtil.toJsonStr(dict), metaFile, Charset.defaultCharset()); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/util/DownloadUtil.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/util/DownloadUtil.java new file mode 100644 index 0000000..7b66e58 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/util/DownloadUtil.java @@ -0,0 +1,120 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.util; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.log.Log; +import vip.xiaonuo.core.context.requestno.RequestNoContext; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.sys.modular.file.enums.SysFileInfoExceptionEnum; + +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLEncoder; + +/** + * 文件下载工具类 + * + * @author xuyuxiang + * @date 2020/8/5 21:45 + */ +public class DownloadUtil { + + private static final Log log = Log.get(); + + public static void download(String fileName, byte[] fileBytes, HttpServletResponse response) { + try { + response.reset(); + response.setHeader("Content-Disposition", "attachment; filename=\"" + URLUtil.encode(fileName) + "\""); + response.addHeader("Content-Length", "" + fileBytes.length); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setContentType("application/octet-stream;charset=UTF-8"); + IoUtil.write(response.getOutputStream(), true, fileBytes); + } catch (IOException e) { + log.error(">>> 下载文件异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + throw new ServiceException(SysFileInfoExceptionEnum.DOWNLOAD_FILE_ERROR); + } + } + + /** + * 下载文件 + * + * @param file 要下载的文件 + * @param response 响应 + * @author xuyuxiang + * @date 2020/8/5 21:46 + */ + public static void download(File file, HttpServletResponse response) { + // 获取文件字节 + byte[] fileBytes = FileUtil.readBytes(file); + //获取文件名称 + String fileName; + try { + fileName = URLEncoder.encode(file.getName(), CharsetUtil.UTF_8); + } catch (UnsupportedEncodingException e) { + log.error(">>> 下载文件异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + throw new ServiceException(SysFileInfoExceptionEnum.DOWNLOAD_FILE_ERROR); + } + //下载文件 + download(fileName, fileBytes, response); + } + + /** + * 将url的文件下载到目标文件 + * + * @param url 下载url + * @param file 目标文件 + * @author xuyuxiang + * @date 2021/3/25 16:51 + */ + public static void downloadToFile(String url, File file) { + if (url == null || url.isEmpty()) throw new ServiceException(SysFileInfoExceptionEnum.DOWNLOAD_FILE_ERROR); + + if (file == null) throw new ServiceException(SysFileInfoExceptionEnum.NOT_EXISTED_FILE); + + try { + URL uri = new URL(url); + java.net.HttpURLConnection connection = (java.net.HttpURLConnection) uri.openConnection(); + InputStream stream = connection.getInputStream(); + + if (stream == null) { + throw new ServiceException(SysFileInfoExceptionEnum.FILE_STREAM_ERROR); + } + FileUtil.writeFromStream(stream, file); + connection.disconnect(); + } catch (Exception e) { + log.error(">>> 下载文件异常,请求号为:{},具体信息为:{}", RequestNoContext.get(), e.getMessage()); + throw new ServiceException(SysFileInfoExceptionEnum.DOWNLOAD_FILE_ERROR); + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/util/OnlineDocumentUtil.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/util/OnlineDocumentUtil.java new file mode 100644 index 0000000..2e01882 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/file/util/OnlineDocumentUtil.java @@ -0,0 +1,246 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy-layui +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy-layui +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.file.util; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.google.gson.Gson; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.file.FileOperator; +import vip.xiaonuo.core.file.modular.local.LocalFileOperator; +import vip.xiaonuo.sys.config.FileConfig; +import vip.xiaonuo.sys.modular.file.result.SysOnlineFileInfoResult; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.*; + +/** + * 在线文档相关工具类 + * + * @author xuyuxiang + * @date 2021/3/24 16:12 + */ +public class OnlineDocumentUtil { + + private static final String VIEWED_SUFFIX = ".pdf|.djvu|.xps"; + private static final String EDITED_SUFFIX = ".docx|.xlsx|.csv|.pptx|.txt"; + private static final String CONVERT_SUFFIX = ".docm|.dotx|.dotm|.dot|.doc|.odt|.fodt|.ott|.xlsm|.xltx|.xltm|.xlt|.xls|.ods|.fods|.ots|.pptm|.ppt|.ppsx|.ppsm|.pps|.potx|.potm|.pot|.odp|.fodp|.otp|.rtf|.mht|.html|.htm|.epub"; + + public static final List<String> DOCUMENT_SUFFIX = Arrays.asList(".doc", ".docx", ".docm", ".dot", ".dotx", ".dotm", ".odt", ".fodt", ".ott", ".rtf", ".txt", ".html", ".htm", + ".mht", ".pdf", ".djvu", ".fb2", ".epub", ".xps"); + + public static final List<String> SPREADSHEET_SUFFIX = Arrays.asList(".xls", ".xlsx", ".xlsm", ".xlt", ".xltx", ".xltm", ".ods", ".fods", ".ots", ".csv"); + + public static final List<String> Presentation_SUFFIX = Arrays.asList(".pps", ".ppsx", ".ppsm",".ppt", ".pptx", ".pptm", ".pot", ".potx", ".potm", ".odp", ".fodp", ".otp"); + + + public static List<String> getFileSuffix() { + List<String> res = new ArrayList<>(); + res.addAll(getViewedSuffix()); + res.addAll(getEditedSuffix()); + res.addAll(getConvertSuffix()); + return res; + } + + public static List<String> getViewedSuffix() { + return Arrays.asList(VIEWED_SUFFIX.split("\\|")); + } + + public static List<String> getEditedSuffix() { + return Arrays.asList(EDITED_SUFFIX.split("\\|")); + } + + public static List<String> getConvertSuffix() { + return Arrays.asList(CONVERT_SUFFIX.split("\\|")); + } + + public static String getFileType(String fileOriginName) { + String suffix = SymbolConstant.PERIOD + FileUtil.getSuffix(fileOriginName); + if (DOCUMENT_SUFFIX.contains(suffix)) return "Text"; + if (SPREADSHEET_SUFFIX.contains(suffix)) return "Spreadsheet"; + if (Presentation_SUFFIX.contains(suffix)) return "Presentation"; + return "Text"; + } + + public static String getFileUri(String fileId, String fileName) { + try { + String filePath = "sysFileInfo/download?id="+ fileId +"&name=" + URLEncoder.encode(fileName, java.nio.charset.StandardCharsets.UTF_8.toString()).replace("+", "%20"); + + return filePath; + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + public static String getCallback(String fileId, String fileSuffix) { + return "sysFileInfo/track?fileObjectName=" + fileId + SymbolConstant.PERIOD + fileSuffix + "&id=" + fileId; + } + + public static String getStoragePath(String fileIdOrObjectName) { + String directory = FilesRootPath(); + return directory + File.separator + FileConfig.DEFAULT_BUCKET + File.separator + fileIdOrObjectName; + } + + public static String FilesRootPath() { + LocalFileOperator localFileOperator = (LocalFileOperator) SpringUtil.getBean(FileOperator.class); + Dict localClientDict = (Dict) localFileOperator.getClient(); + String currentSavePath = localClientDict.getStr("currentSavePath"); + File file = new File(currentSavePath); + + if (!file.exists()) { + file.mkdirs(); + } + return currentSavePath; + } + + public static String[] getHistory(SysOnlineFileInfoResult sysOnlineFileInfoResult) { + String histDir = OnlineDocumentUtil.getHistoryDir(OnlineDocumentUtil.getStoragePath(sysOnlineFileInfoResult.getFileId())); + if (getFileVersion(histDir) > 0) { + Integer curVer = getFileVersion(histDir); + + Set<Object> hist = new HashSet<Object>(); + Map<String, Object> histData = new HashMap<String, Object>(); + + for (Integer i = 0; i <= curVer; i++) { + Map<String, Object> obj = new HashMap<String, Object>(); + Map<String, Object> dataObj = new HashMap<String, Object>(); + String verDir = getVersionDir(histDir, i + 1); + + try { + String key = null; + + key = i == curVer ? sysOnlineFileInfoResult.document.key : readFileToEnd(new File(verDir + File.separator + "key.txt")); + + obj.put("key", key); + obj.put("version", i); + + if (i == 0) { + String createdInfo = readFileToEnd(new File(histDir + File.separator + "createdInfo.json")); + JSONObject json = (JSONObject) JSONUtil.parse(createdInfo); + + obj.put("created", json.get("created")); + Map<String, Object> user = new HashMap<String, Object>(); + user.put("id", json.get("id")); + user.put("name", json.get("name")); + obj.put("user", user); + } + + dataObj.put("key", key); + dataObj.put("url", i == curVer ? sysOnlineFileInfoResult.document.url : getStoragePath(verDir + File.separator + "prev" + FileUtil.getSuffix(sysOnlineFileInfoResult.getDocument().title))); + dataObj.put("version", i); + + if (i > 0) { + JSONObject changes = (JSONObject) JSONUtil.parse(readFileToEnd(new File(getVersionDir(histDir, i) + File.separator + "changes.json"))); + JSONObject change = (JSONObject) ((JSONArray) changes.get("changes")).get(0); + + obj.put("changes", changes.get("changes")); + obj.put("serverVersion", changes.get("serverVersion")); + obj.put("created", change.get("created")); + obj.put("user", change.get("user")); + + Map<String, Object> prev = (Map<String, Object>) histData.get(Integer.toString(i - 1)); + Map<String, Object> prevInfo = new HashMap<String, Object>(); + prevInfo.put("key", prev.get("key")); + prevInfo.put("url", prev.get("url")); + dataObj.put("previous", prevInfo); + dataObj.put("changesUrl", getStoragePath(getVersionDir(histDir, i) + File.separator + "diff.zip")); + } + + hist.add(obj); + histData.put(Integer.toString(i), dataObj); + + } catch (Exception ex) { + } + } + + Map<String, Object> histObj = new HashMap<String, Object>(); + histObj.put("currentVersion", curVer); + histObj.put("history", hist); + + Gson gson = new Gson(); + return new String[]{gson.toJson(histObj), gson.toJson(histData)}; + } + return new String[]{"", ""}; + } + + public static String getHistoryDir(String storagePath) { + return storagePath += "-hist"; + } + + public static String getVersionDir(String histPath, Integer version) { + return histPath + File.separator + Integer.toString(version); + } + + public static Integer getFileVersion(String historyPath) { + File dir = new File(historyPath); + + if (!dir.exists()) return 0; + + File[] dirs = dir.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + }); + + return dirs.length; + } + + private static String readFileToEnd(File file) { + String output = ""; + try { + try(FileInputStream is = new FileInputStream(file)) + { + Scanner scanner = new Scanner(is); + scanner.useDelimiter("\\A"); + while (scanner.hasNext()) { + output += scanner.next(); + } + scanner.close(); + } + } catch (Exception e) { } + return output; + } + + public static void deleteFileHistory(String fileId) { + // 判断文件存在不存在 + String histDir = OnlineDocumentUtil.getHistoryDir(OnlineDocumentUtil.getStoragePath(fileId)); + File hisDirFile = FileUtil.file(histDir); + if (!FileUtil.exist(hisDirFile)) { + return; + } + + // 删除文件 + FileUtil.del(hisDirFile); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/controller/SysLogController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/controller/SysLogController.java new file mode 100644 index 0000000..2d0eb8d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/controller/SysLogController.java @@ -0,0 +1,135 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.log.param.SysOpLogParam; +import vip.xiaonuo.sys.modular.log.param.SysVisLogParam; +import vip.xiaonuo.sys.modular.log.service.SysOpLogService; +import vip.xiaonuo.sys.modular.log.service.SysVisLogService; + +import javax.annotation.Resource; + +/** + * 系统日志控制器 + * + * @author xuyuxiang + * @date 2020/3/19 21:14 + */ +@RestController +public class SysLogController { + + @Resource + private SysVisLogService sysVisLogService; + + @Resource + private SysOpLogService sysOpLogService; + + /** + * 查询访问日志 + * + * @author xuyuxiang + * @date 2020/3/20 18:52 + */ + @Permission + @GetMapping("/sysVisLog/page") + public ResponseData visLogPage(SysVisLogParam visLogParam) { + return new SuccessResponseData(sysVisLogService.page(visLogParam)); + } + + /** + * 查询操作日志 + * + * @author xuyuxiang + * @date 2020/3/20 18:52 + */ + @Permission + @GetMapping("/sysOpLog/page") + public ResponseData opLogPage(SysOpLogParam sysOpLogParam) { + return new SuccessResponseData(sysOpLogService.page(sysOpLogParam)); + } + + /** + * 清空访问日志 + * + * @author xuyuxiang + * @date 2020/3/23 16:28 + */ + @Permission + @PostMapping("/sysVisLog/delete") + @BusinessLog(title = "访问日志_清空", opType = LogAnnotionOpTypeEnum.CLEAN) + public ResponseData visLogDelete() { + sysVisLogService.delete(); + return new SuccessResponseData(); + } + + /** + * 清空操作日志 + * + * @author xuyuxiang + * @date 2020/3/23 16:28 + */ + @Permission + @PostMapping("/sysOpLog/delete") + @BusinessLog(title = "操作日志_清空", opType = LogAnnotionOpTypeEnum.CLEAN) + public ResponseData opLogDelete() { + sysOpLogService.delete(); + return new SuccessResponseData(); + } + + /** + * 导出系统操作日志 + * + * @author yubaoshan + * @date 2020/5/30 17:55 + */ + @Permission + @GetMapping("/sysOpLog/export") + @BusinessLog(title = "操作日志_导出", opType = LogAnnotionOpTypeEnum.EXPORT) + public void export(SysOpLogParam sysOpLogParam) { + sysOpLogService.export(sysOpLogParam); + } + + /** + * 导出系统访问日志 + * + * @author yubaoshan + * @date 2020/5/30 17:55 + */ + @Permission + @GetMapping("/sysVisLog/export") + @BusinessLog(title = "访问日志_导出", opType = LogAnnotionOpTypeEnum.EXPORT) + public void export(SysVisLogParam sysVisLogParam) { + sysVisLogService.export(sysVisLogParam); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/entity/SysOpLog.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/entity/SysOpLog.java new file mode 100644 index 0000000..427c8c0 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/entity/SysOpLog.java @@ -0,0 +1,164 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.entity; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * 系统操作日志表 + * + * @author xuyuxiang + * @date 2020/3/11 11:56 + */ +@Data +@TableName("sys_op_log") +public class SysOpLog { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + @Excel(name = "名称", width = 20) + private String name; + + /** + * 操作类型(见LogAnnotionOpTypeEnum) + */ + @Excel(name = "操作类型", width = 20) + private Integer opType; + + /** + * 是否执行成功(Y-是,N-否) + */ + @Excel(name = "是否执行成功", replace = {"是_Y", "否_N"}, width = 20) + private String success; + + /** + * 具体消息 + */ + @Excel(name = "具体消息", width = 20) + private String message; + + /** + * ip + */ + @Excel(name = "ip", width = 20) + private String ip; + + /** + * 地址 + */ + @Excel(name = "地址", width = 20) + private String location; + + /** + * 浏览器 + */ + @Excel(name = "浏览器", width = 40) + private String browser; + + /** + * 操作系统 + */ + @Excel(name = "操作系统", width = 20) + private String os; + + /** + * 请求地址 + */ + @Excel(name = "请求地址", width = 40) + private String url; + + /** + * 类名称 + */ + @Excel(name = "类名称", width = 20) + private String className; + + /** + * 方法名称 + */ + @Excel(name = "方法名称", width = 20) + private String methodName; + + /** + * 请求方式(GET POST PUT DELETE) + */ + @Excel(name = "请求方式", width = 20) + private String reqMethod; + + /** + * 请求参数 + */ + @Excel(name = "请求参数", width = 40) + private String param; + + /** + * 返回结果 + */ + @Excel(name = "返回结果", width = 20) + private String result; + + /** + * 操作时间 + */ + @Excel(name = "操作时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd HH:mm:ss", width = 20) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date opTime; + + /** + * 操作人 + */ + private String account; + + /** + * 签名数据(ID除外) + */ + private String signValue; + + /** + * 重写tostring方法 并去除所有空格 + */ + @Override + public String toString () { + String toStr = name + opType + success + message + ip + location + browser + + os + url + className + methodName + reqMethod + param + result + + opTime + account; + return toStr.replaceAll(" +",""); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/entity/SysVisLog.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/entity/SysVisLog.java new file mode 100644 index 0000000..13c7823 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/entity/SysVisLog.java @@ -0,0 +1,126 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.entity; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; + +/** + * 系统访问日志表 + * + * @author xuyuxiang + * @date 2020/3/11 11:56 + */ +@Data +@TableName("sys_vis_log") +public class SysVisLog { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + @Excel(name = "名称", width = 20) + private String name; + + /** + * 是否执行成功(Y-是,N-否) + */ + @Excel(name = "是否执行成功", replace = {"是_Y", "否_N"}, width = 20) + private String success; + + /** + * 具体消息 + */ + @Excel(name = "具体消息", width = 20) + private String message; + + /** + * ip + */ + @Excel(name = "ip", width = 20) + private String ip; + + /** + * 地址 + */ + @Excel(name = "地址", width = 20) + private String location; + + /** + * 浏览器 + */ + @Excel(name = "浏览器", width = 20) + private String browser; + + /** + * 操作系统 + */ + @Excel(name = "操作系统", width = 20) + private String os; + + /** + * 访问类型(字典 1登入 2登出) + */ + @Excel(name = "访问类型", replace = {"登入_1", "登出_2"}, width = 20) + private Integer visType; + + /** + * 访问时间 + */ + @Excel(name = "操作时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd HH:mm:ss", width = 20) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date visTime; + + /** + * 访问人 + */ + private String account; + + /** + * 签名数据(ID除外) + */ + private String signValue; + + /** + * 重写tostring方法 + */ + @Override + public String toString(){ + String toStr = name + success + message + ip + location + browser + + os + visType + visTime + account; + return toStr.replaceAll(" +",""); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/SysOpLogMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/SysOpLogMapper.java new file mode 100644 index 0000000..21ec42a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/SysOpLogMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.log.entity.SysOpLog; + +/** + * 系统访问日志mapper + * + * @author xuyuxiang + * @date 2020/3/12 14:24 + */ +public interface SysOpLogMapper extends BaseMapper<SysOpLog> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/SysVisLogMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/SysVisLogMapper.java new file mode 100644 index 0000000..acf9f4a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/SysVisLogMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.log.entity.SysVisLog; + +/** + * 系统访问日志mapper + * + * @author xuyuxiang + * @date 2020/3/12 14:25 + */ +public interface SysVisLogMapper extends BaseMapper<SysVisLog> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/mapping/SysOpLogMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/mapping/SysOpLogMapper.xml new file mode 100644 index 0000000..0b4dce3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/mapping/SysOpLogMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.log.mapper.SysOpLogMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/mapping/SysVisLogMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/mapping/SysVisLogMapper.xml new file mode 100644 index 0000000..40ba705 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/mapper/mapping/SysVisLogMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.log.mapper.SysVisLogMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/param/SysOpLogParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/param/SysOpLogParam.java new file mode 100644 index 0000000..f295526 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/param/SysOpLogParam.java @@ -0,0 +1,127 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import java.util.Date; + +/** + * 操作日志参数 + * + * @author xuyuxiang + * @date 2020/3/26 9:16 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysOpLogParam extends BaseParam { + + /** + * 主键 + */ + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 操作类型(0其他 1增加 2删除 3编辑 ...见BasePram的参数校验类型) + */ + private Integer opType; + + /** + * 是否执行成功(Y-是,N-否) + */ + private String success; + + /** + * 具体消息 + */ + private String message; + + /** + * ip + */ + private String ip; + + /** + * 地址 + */ + private String location; + + /** + * 浏览器 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 请求地址 + */ + private String url; + + /** + * 类名称 + */ + private String className; + + /** + * 方法名称 + */ + private String methodName; + + /** + * 请求方式(GET POST PUT DELETE) + */ + private String reqMethod; + + /** + * 请求参数 + */ + private String param; + + /** + * 返回结果 + */ + private String result; + + /** + * 操作时间 + */ + private Date opTime; + + /** + * 操作人 + */ + private String account; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/param/SysVisLogParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/param/SysVisLogParam.java new file mode 100644 index 0000000..2088b87 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/param/SysVisLogParam.java @@ -0,0 +1,97 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import java.util.Date; + +/** + * 访问日志参数 + * + * @author xuyuxiang + * @date 2020/3/26 9:15 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysVisLogParam extends BaseParam { + + /** + * 主键 + */ + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 是否执行成功(Y-是,N-否) + */ + private String success; + + /** + * 具体消息 + */ + private String message; + + /** + * ip + */ + private String ip; + + /** + * 地址 + */ + private String location; + + /** + * 浏览器 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 访问类型(字典 1登入 2登出) + */ + private Integer visType; + + /** + * 访问时间 + */ + private Date visTime; + + /** + * 访问人 + */ + private String account; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/SysOpLogService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/SysOpLogService.java new file mode 100644 index 0000000..b432d93 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/SysOpLogService.java @@ -0,0 +1,65 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.log.entity.SysOpLog; +import vip.xiaonuo.sys.modular.log.param.SysOpLogParam; + +/** + * 系统操作日志service接口 + * + * @author xuyuxiang + * @date 2020/3/12 14:21 + */ +public interface SysOpLogService extends IService<SysOpLog> { + + /** + * 查询系统操作日志 + * + * @param sysOpLogParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/3/30 10:32 + */ + PageResult<SysOpLog> page(SysOpLogParam sysOpLogParam); + + /** + * 清空系统操作日志 + * + * @author xuyuxiang + * @date 2020/6/1 11:05 + */ + void delete(); + + /** + * 导出系统操作日志 + * + * @author yubaoshan + * @date 2021/5/30 17:58 + */ + void export(SysOpLogParam sysOpLogParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/SysVisLogService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/SysVisLogService.java new file mode 100644 index 0000000..941ce73 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/SysVisLogService.java @@ -0,0 +1,65 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.log.entity.SysVisLog; +import vip.xiaonuo.sys.modular.log.param.SysVisLogParam; + +/** + * 系统访问日志service接口 + * + * @author xuyuxiang + * @date 2020/3/12 14:20 + */ +public interface SysVisLogService extends IService<SysVisLog> { + + /** + * 查询系统访问日志 + * + * @param sysVisLogParam 查询参数 + * @return 查询结果集合 + * @author xuyuxiang + * @date 2020/3/24 20:55 + */ + PageResult<SysVisLog> page(SysVisLogParam sysVisLogParam); + + /** + * 清空系统访问日志 + * + * @author xuyuxiang + * @date 2020/6/1 11:04 + */ + void delete(); + + /** + * 导出系统访问日志 + * + * @author yubaoshan + * @date 2021/5/30 17:58 + */ + void export(SysVisLogParam sysVisLogParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/impl/SysOpLogServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/impl/SysOpLogServiceImpl.java new file mode 100644 index 0000000..adee104 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/impl/SysOpLogServiceImpl.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.PoiUtil; +import vip.xiaonuo.sys.modular.log.entity.SysOpLog; +import vip.xiaonuo.sys.modular.log.mapper.SysOpLogMapper; +import vip.xiaonuo.sys.modular.log.param.SysOpLogParam; +import vip.xiaonuo.sys.modular.log.service.SysOpLogService; + +import java.util.List; + +/** + * 系统操作日志service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/12 14:22 + */ +@Service +public class SysOpLogServiceImpl extends ServiceImpl<SysOpLogMapper, SysOpLog> implements SysOpLogService { + + @Override + public PageResult<SysOpLog> page(SysOpLogParam sysOpLogParam) { + LambdaQueryWrapper<SysOpLog> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysOpLogParam)) { + //根据名称模糊查询 + if (ObjectUtil.isNotEmpty(sysOpLogParam.getName())) { + queryWrapper.like(SysOpLog::getName, sysOpLogParam.getName()); + } + //根据操作类型查询 + if (ObjectUtil.isNotEmpty(sysOpLogParam.getOpType())) { + queryWrapper.eq(SysOpLog::getOpType, sysOpLogParam.getOpType()); + } + //根据是否成功查询 + if (ObjectUtil.isNotEmpty(sysOpLogParam.getSuccess())) { + queryWrapper.eq(SysOpLog::getSuccess, sysOpLogParam.getSuccess()); + } + // 根据时间范围查询 + if (ObjectUtil.isAllNotEmpty(sysOpLogParam.getSearchBeginTime(), sysOpLogParam.getSearchEndTime())) { + queryWrapper.apply("date_format (op_time,'%Y-%m-%d') >= date_format('" + sysOpLogParam.getSearchBeginTime() + "','%Y-%m-%d')") + .apply("date_format (op_time,'%Y-%m-%d') <= date_format('" + sysOpLogParam.getSearchEndTime() + "','%Y-%m-%d')"); + } + } + //根据操作时间倒序排列 + queryWrapper.orderByDesc(SysOpLog::getOpTime); + Page<SysOpLog> page = this.page(PageFactory.defaultPage(), queryWrapper); + return new PageResult<>(page); + } + + @Override + public void delete() { + this.remove(new QueryWrapper<>()); + } + + @Override + public void export(SysOpLogParam sysOpLogParam) { + List<SysOpLog> list = this.list(); + PoiUtil.exportExcelWithStream("SysOpLog.xls", SysOpLog.class, list); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/impl/SysVisLogServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/impl/SysVisLogServiceImpl.java new file mode 100644 index 0000000..7fee419 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/log/service/impl/SysVisLogServiceImpl.java @@ -0,0 +1,88 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.log.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.PoiUtil; +import vip.xiaonuo.sys.modular.log.entity.SysVisLog; +import vip.xiaonuo.sys.modular.log.mapper.SysVisLogMapper; +import vip.xiaonuo.sys.modular.log.param.SysVisLogParam; +import vip.xiaonuo.sys.modular.log.service.SysVisLogService; + +import java.util.List; + +/** + * 系统访问日志service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/12 14:23 + */ +@Service +public class SysVisLogServiceImpl extends ServiceImpl<SysVisLogMapper, SysVisLog> implements SysVisLogService { + + @Override + public PageResult<SysVisLog> page(SysVisLogParam sysVisLogParam) { + LambdaQueryWrapper<SysVisLog> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysVisLogParam)) { + //根据名称模糊查询 + if (ObjectUtil.isNotEmpty(sysVisLogParam.getName())) { + queryWrapper.like(SysVisLog::getName, sysVisLogParam.getName()); + } + //跟据访问类型(字典 1登入 2登出)查询 + if (ObjectUtil.isNotEmpty(sysVisLogParam.getVisType())) { + queryWrapper.eq(SysVisLog::getVisType, sysVisLogParam.getVisType()); + } + //根据是否成功查询 + if (ObjectUtil.isNotEmpty(sysVisLogParam.getSuccess())) { + queryWrapper.eq(SysVisLog::getSuccess, sysVisLogParam.getSuccess()); + } + // 根据时间范围查询 + if (ObjectUtil.isAllNotEmpty(sysVisLogParam.getSearchBeginTime(), sysVisLogParam.getSearchEndTime())) { + queryWrapper.apply("date_format (vis_time,'%Y-%m-%d') >= date_format('" + sysVisLogParam.getSearchBeginTime() + "','%Y-%m-%d')") + .apply("date_format (vis_time,'%Y-%m-%d') <= date_format('" + sysVisLogParam.getSearchEndTime() + "','%Y-%m-%d')"); + } + } + //根据访问时间倒序排列 + queryWrapper.orderByDesc(SysVisLog::getVisTime); + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public void delete() { + this.remove(new QueryWrapper<>()); + } + + @Override + public void export(SysVisLogParam sysVisLogParam) { + List<SysVisLog> list = this.list(); + PoiUtil.exportExcelWithStream("SysVisLog.xls", SysVisLog.class, list); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/controller/SysMenuController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/controller/SysMenuController.java new file mode 100644 index 0000000..618f5f9 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/controller/SysMenuController.java @@ -0,0 +1,177 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.menu.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.node.LoginMenuTreeNode; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.menu.entity.SysMenu; +import vip.xiaonuo.sys.modular.menu.param.SysMenuParam; +import vip.xiaonuo.sys.modular.menu.service.SysMenuService; +import vip.xiaonuo.sys.modular.role.service.SysRoleMenuService; +import vip.xiaonuo.sys.modular.user.service.SysUserRoleService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 系统菜单控制器 + * + * @author xuyuxiang + * @date 2020/3/20 18:54 + */ +@RestController +public class SysMenuController { + + @Resource + private SysMenuService sysMenuService; + + @Resource + private SysRoleMenuService sysRoleMenuService; + + @Resource + private SysUserRoleService sysUserRoleService; + + /** + * 系统菜单列表(树) + * + * @author xuyuxiang + * @date 2020/3/20 21:23 + */ + @Permission + @GetMapping("/sysMenu/list") + @BusinessLog(title = "系统菜单_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysMenuParam sysMenuParam) { + return new SuccessResponseData(sysMenuService.list(sysMenuParam)); + } + + /** + * 添加系统菜单 + * + * @author xuyuxiang + * @date 2020/3/27 8:57 + */ + @Permission + @PostMapping("/sysMenu/add") + @BusinessLog(title = "系统菜单_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysMenuParam.add.class) SysMenuParam sysMenuParam) { + sysMenuService.add(sysMenuParam); + return new SuccessResponseData(); + } + + /** + * 删除系统菜单 + * + * @author xuyuxiang + * @date 2020/3/27 8:58 + */ + @Permission + @PostMapping("/sysMenu/delete") + @BusinessLog(title = "系统菜单_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysMenuParam.delete.class) SysMenuParam sysMenuParam) { + sysMenuService.delete(sysMenuParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统菜单 + * + * @author xuyuxiang + * @date 2020/3/27 8:59 + */ + @Permission + @PostMapping("/sysMenu/edit") + @BusinessLog(title = "系统菜单_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysMenuParam.edit.class) SysMenuParam sysMenuParam) { + sysMenuService.edit(sysMenuParam); + return new SuccessResponseData(); + } + + /** + * 查看系统菜单 + * + * @author xuyuxiang + * @date 2020/3/27 9:01 + */ + @Permission + @PostMapping("/sysMenu/detail") + @BusinessLog(title = "系统菜单_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysMenuParam.detail.class) SysMenuParam sysMenuParam) { + return new SuccessResponseData(sysMenuService.detail(sysMenuParam)); + } + + /** + * 获取系统菜单树,用于新增,编辑时选择上级节点 + * + * @author xuyuxiang + * @date 2020/3/27 15:55 + */ + @Permission + @GetMapping("/sysMenu/tree") + @BusinessLog(title = "系统菜单_树", opType = LogAnnotionOpTypeEnum.TREE) + public ResponseData tree(SysMenuParam sysMenuParam) { + return new SuccessResponseData(sysMenuService.tree(sysMenuParam)); + } + + /** + * 获取系统菜单树,用于给角色授权时选择 + * + * @author xuyuxiang + * @date 2020/4/5 15:00 + */ + @Permission + @GetMapping("/sysMenu/treeForGrant") + @BusinessLog(title = "系统菜单_授权树", opType = LogAnnotionOpTypeEnum.TREE) + public ResponseData treeForGrant(SysMenuParam sysMenuParam) { + return new SuccessResponseData(sysMenuService.treeForGrant(sysMenuParam)); + } + + /** + * 根据系统切换菜单 + * + * @author xuyuxiang + * @date 2020/4/19 15:50 + */ + @PostMapping("/sysMenu/change") + @BusinessLog(title = "系统菜单_切换", opType = LogAnnotionOpTypeEnum.TREE) + public ResponseData change(@RequestBody @Validated(SysMenuParam.change.class) SysMenuParam sysMenuParam) { + Long sysLoginUserId = LoginContextHolder.me().getSysLoginUserId(); + List<Long> userRoleIdList = sysUserRoleService.getUserRoleIdList(sysLoginUserId); + List<Long> menuIdList = sysRoleMenuService.getRoleMenuIdList(userRoleIdList); + //转换成登录菜单 + List<SysMenu> sysMenuList = sysMenuService.getLoginMenus(sysLoginUserId, sysMenuParam.getApplication(), menuIdList); + List<LoginMenuTreeNode> menuTreeNodeList = sysMenuService.convertSysMenuToLoginMenu(sysMenuList); + return new SuccessResponseData(menuTreeNodeList); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/entity/SysMenu.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/entity/SysMenu.java new file mode 100644 index 0000000..1a9a973 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/entity/SysMenu.java @@ -0,0 +1,153 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.menu.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; +import vip.xiaonuo.core.pojo.base.node.BaseTreeNode; + +import java.util.List; + +/** + * 系统菜单表 + * + * @author xuyuxiang + * @date 2020/3/11 11:20 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_menu") +public class SysMenu extends BaseEntity implements BaseTreeNode { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 父id + */ + private Long pid; + + /** + * 父ids + */ + private String pids; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 菜单类型(字典 0目录 1菜单 2按钮) + */ + private Integer type; + + /** + * 图标 + */ + private String icon; + + /** + * 路由地址 + */ + private String router; + + /** + * 组件地址 + */ + private String component; + + /** + * 权限标识 + */ + private String permission; + + /** + * 应用分类(应用编码) + */ + private String application; + + /** + * 打开方式(字典 0无 1组件 2内链 3外链) + */ + private Integer openType; + + /** + * 是否可见(Y-是,N-否) + */ + private String visible; + + /** + * 内链地址 + */ + private String link; + + /** + * 重定向地址 + */ + private String redirect; + + /** + * 权重(字典 1系统权重 2业务权重) + */ + private Integer weight; + + /** + * 排序 + */ + private Integer sort; + + /** + * 备注 + */ + @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + private String remark; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + private Integer status; + + /** + * 子节点(表中不存在,用于构造树) + */ + @TableField(exist = false) + private List children; + + @Override + public void setChildren(List children) { + this.children = children; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/enums/SysMenuExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/enums/SysMenuExceptionEnum.java new file mode 100644 index 0000000..2fd2583 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/enums/SysMenuExceptionEnum.java @@ -0,0 +1,120 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.menu.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统菜单相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/26 10:12 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_MENU_EXCEPTION_ENUM) +public enum SysMenuExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 菜单不存在 + */ + MENU_NOT_EXIST(1, "菜单不存在"), + + /** + * 菜单编码重复 + */ + MENU_CODE_REPEAT(2, "菜单编码重复,请检查code参数"), + + /** + * 菜单名称重复 + */ + MENU_NAME_REPEAT(3, "菜单名称重复,请检查name参数"), + + /** + * 路由地址为空 + */ + MENU_ROUTER_EMPTY(4, "路由地址为空,请检查router参数"), + + /** + * 组件地址为空 + */ + MENU_COMPONENT_EMPTY(5, "组件地址为空,请检查component参数"), + + /** + * 打开方式为空 + */ + MENU_OPEN_TYPE_EMPTY(6, "打开方式为空,请检查openType参数"), + + /** + * 权限标识格式为空 + */ + MENU_PERMISSION_EMPTY(7, "权限标识为空,请检查permission参数"), + + /** + * 权限标识格式错误 + */ + MENU_PERMISSION_ERROR(8, "权限标识格式错误,请检查permission参数"), + + /** + * 权限不存在 + */ + MENU_PERMISSION_NOT_EXIST(9, "权限不存在,请检查permission参数"), + + /** + * 不能移动根节点 + */ + CANT_MOVE_APP(10, "父节点不是根节点不能移动应用"), + + /** + * 父级菜单不能为当前节点,请重新选择父级菜单 + */ + PID_CANT_EQ_ID(11, "父级菜单不能为当前节点,请重新选择父级菜单"), + + /** + * 父节点不能为本节点的子节点,请重新选择父节点 + */ + PID_CANT_EQ_CHILD_ID(6, "父节点不能为本节点的子节点,请重新选择父节点"); + + private final Integer code; + + private final String message; + + SysMenuExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/mapper/SysMenuMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/mapper/SysMenuMapper.java new file mode 100644 index 0000000..f083ed7 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/mapper/SysMenuMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.menu.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.menu.entity.SysMenu; + +/** + * 系统菜单mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:05 + */ +public interface SysMenuMapper extends BaseMapper<SysMenu> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/mapper/mapping/SysMenuMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/mapper/mapping/SysMenuMapper.xml new file mode 100644 index 0000000..cfb6613 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/mapper/mapping/SysMenuMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.menu.mapper.SysMenuMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/node/MenuBaseTreeNode.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/node/MenuBaseTreeNode.java new file mode 100644 index 0000000..149b13d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/node/MenuBaseTreeNode.java @@ -0,0 +1,86 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.menu.node; + +import lombok.Data; +import vip.xiaonuo.core.pojo.base.node.BaseTreeNode; + +import java.util.List; + +/** + * 菜单树节点 + * + * @author xuyuxiang + * @date 2020/4/5 12:03 + */ +@Data +public class MenuBaseTreeNode implements BaseTreeNode { + + /** + * 主键 + */ + private Long id; + + /** + * 父id + */ + private Long parentId; + + /** + * 名称 + */ + private String title; + + /** + * 值 + */ + private String value; + + /** + * 排序,越小优先级越高 + */ + private Integer weight; + + /** + * 子节点 + */ + private List children; + + /** + * 父id别名 + */ + @Override + public Long getPid() { + return this.parentId; + } + + /** + * 子节点 + */ + @Override + public void setChildren(List children) { + this.children = children; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/param/SysMenuParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/param/SysMenuParam.java new file mode 100644 index 0000000..a889a24 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/param/SysMenuParam.java @@ -0,0 +1,148 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.menu.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; +import vip.xiaonuo.core.validation.flag.FlagValue; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统菜单参数 + * + * @author xuyuxiang + * @date 2020/3/26 20:42 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysMenuParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 父id + */ + @NotNull(message = "pid不能为空,请检查pid参数", groups = {add.class, edit.class}) + private Long pid; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + private String code; + + /** + * 菜单类型(字典 0目录 1菜单 2按钮) + */ + @NotNull(message = "菜单类型不能为空,请检查type参数", groups = {add.class, edit.class}) + @Min(value = 0, message = "菜单类型格式错误,请检查type参数", groups = {add.class, edit.class}) + @Max(value = 2, message = "菜单类型格式错误,请检查type参数", groups = {add.class, edit.class}) + private Integer type; + + /** + * 图标 + */ + private String icon; + + /** + * 路由地址 + */ + private String router; + + /** + * 组件地址 + */ + private String component; + + /** + * 权限标识 + */ + private String permission; + + /** + * 应用分类(应用编码) + */ + @NotBlank(message = "应用分类不能为空,请检查application参数", groups = {add.class, edit.class, change.class}) + private String application; + + /** + * 打开方式(字典 0无 1组件 2内链 3外链) + */ + @NotNull(message = "打开方式不能为空,请检查openType参数", groups = {add.class, edit.class}) + @Min(value = 0, message = "打开方式格式错误,请检查openType参数", groups = {add.class, edit.class}) + @Max(value = 3, message = "打开方式格式错误,请检查openType参数", groups = {add.class, edit.class}) + private Integer openType; + + /** + * 是否可见(Y-是,N-否) + */ + @NotBlank(message = "是否可见不能为空,请检查visible参数", groups = {add.class, edit.class}) + @FlagValue(message = "是否可见格式错误,正确格式应该Y或者N,请检查visible参数", groups = {add.class, edit.class}) + private String visible; + + /** + * 内链地址 + */ + private String link; + + /** + * 重定向地址 + */ + private String redirect; + + /** + * 权重(字典 1系统权重 2业务权重) + */ + @NotNull(message = "权重不能为空,请检查weight参数", groups = {add.class, edit.class}) + @Min(value = 0, message = "权重格式错误,请检查weight参数", groups = {add.class, edit.class}) + @Max(value = 2, message = "权重格式错误,请检查weight参数", groups = {add.class, edit.class}) + private Integer weight; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空,请检查sort参数", groups = {add.class, edit.class}) + private Integer sort; + + /** + * 备注 + */ + private String remark; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/service/SysMenuService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/service/SysMenuService.java new file mode 100644 index 0000000..94c48fb --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/service/SysMenuService.java @@ -0,0 +1,163 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.menu.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.node.LoginMenuTreeNode; +import vip.xiaonuo.sys.modular.menu.entity.SysMenu; +import vip.xiaonuo.sys.modular.menu.node.MenuBaseTreeNode; +import vip.xiaonuo.sys.modular.menu.param.SysMenuParam; + +import java.util.List; + +/** + * 系统菜单service接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:05 + */ +public interface SysMenuService extends IService<SysMenu> { + + /** + * 获取用户权限相关信息 + * + * @param userId 用户id + * @param menuIdList 菜单id集合 + * @return 权限集合 + * @author xuyuxiang + * @date 2020/3/13 16:26 + */ + List<String> getLoginPermissions(Long userId, List<Long> menuIdList); + + /** + * 获取用户AntDesign菜单相关信息,前端使用 + * + * @param userId 用户id + * @param appCode 应用编码 + * @param menuIdList 菜单id集合 + * @return AntDesign菜单信息结果集 + * @author yubaoshan + * @date 2020/4/17 17:48 + */ + List<SysMenu> getLoginMenus(Long userId, String appCode, List<Long> menuIdList); + + /** + * 获取用户菜单所属的应用编码集合 + * + * @param userId 用户id + * @param roleIdList 角色id集合 + * @return 用户菜单所属的应用编码集合 + * @author xuyuxiang + * @date 2020/3/21 11:01 + */ + List<String> getUserMenuAppCodeList(Long userId, List<Long> roleIdList); + + /** + * 系统菜单列表(树表) + * + * @param sysMenuParam 查询参数 + * @return 菜单树表列表 + * @author xuyuxiang + * @date 2020/3/26 10:19 + */ + List<SysMenu> list(SysMenuParam sysMenuParam); + + /** + * 添加系统菜单 + * + * @param sysMenuParam 添加参数 + * @author xuyuxiang + * @date 2020/3/27 9:03 + */ + void add(SysMenuParam sysMenuParam); + + /** + * 删除系统菜单 + * + * @param sysMenuParam 删除参数 + * @author xuyuxiang + * @date 2020/3/27 9:03 + */ + void delete(SysMenuParam sysMenuParam); + + /** + * 编辑系统菜单 + * + * @param sysMenuParam 编辑参数 + * @author xuyuxiang + * @date 2020/3/27 9:03 + */ + void edit(SysMenuParam sysMenuParam); + + /** + * 查看系统菜单 + * + * @param sysMenuParam 查看参数 + * @return 系统菜单 + * @author xuyuxiang + * @date 2020/3/27 9:03 + */ + SysMenu detail(SysMenuParam sysMenuParam); + + /** + * 获取系统菜单树,用于新增,编辑时选择上级节点 + * + * @param sysMenuParam 查询参数 + * @return 菜单树列表 + * @author xuyuxiang + * @date 2020/3/27 15:56 + */ + List<MenuBaseTreeNode> tree(SysMenuParam sysMenuParam); + + /** + * 获取系统菜单树,用于给角色授权时选择 + * + * @param sysMenuParam 查询参数 + * @return 菜单树列表 + * @author xuyuxiang + * @date 2020/4/5 15:01 + */ + List<MenuBaseTreeNode> treeForGrant(SysMenuParam sysMenuParam); + + /** + * 根据应用编码判断该机构下是否有状态为正常的菜单 + * + * @param appCode 应用编码 + * @return 该应用下是否有正常菜单,true是,false否 + * @author xuyuxiang + * @date 2020/6/28 12:14 + */ + boolean hasMenu(String appCode); + + /** + * 将SysMenu格式菜单转换为LoginMenuTreeNode菜单 + * + * @author xuyuxiang + * @date 2021/6/29 13:43 + * @param sysMenuList 原始菜单集合 + * @return LoginMenuTreeNode菜单集合 + */ + List<LoginMenuTreeNode> convertSysMenuToLoginMenu(List<SysMenu> sysMenuList); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/service/impl/SysMenuServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..984e059 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/menu/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,556 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.menu.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.enums.YesOrNotEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.TreeBuildFactory; +import vip.xiaonuo.core.pojo.node.LoginMenuTreeNode; +import vip.xiaonuo.sys.core.cache.ResourceCache; +import vip.xiaonuo.sys.core.enums.AdminTypeEnum; +import vip.xiaonuo.sys.core.enums.MenuOpenTypeEnum; +import vip.xiaonuo.sys.core.enums.MenuTypeEnum; +import vip.xiaonuo.sys.core.enums.MenuWeightEnum; +import vip.xiaonuo.sys.modular.menu.entity.SysMenu; +import vip.xiaonuo.sys.modular.menu.enums.SysMenuExceptionEnum; +import vip.xiaonuo.sys.modular.menu.mapper.SysMenuMapper; +import vip.xiaonuo.sys.modular.menu.node.MenuBaseTreeNode; +import vip.xiaonuo.sys.modular.menu.param.SysMenuParam; +import vip.xiaonuo.sys.modular.menu.service.SysMenuService; +import vip.xiaonuo.sys.modular.role.service.SysRoleMenuService; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.service.SysUserRoleService; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 系统菜单service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 16:05 + */ +@Service +@SuppressWarnings("unchecked") +public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService { + + @Resource + private SysUserService sysUserService; + + @Resource + private SysUserRoleService sysUserRoleService; + + @Resource + private SysRoleMenuService sysRoleMenuService; + + @Resource + private ResourceCache resourceCache; + + @Override + public List<String> getLoginPermissions(Long userId, List<Long> menuIdList) { + Set<String> permissions = CollectionUtil.newHashSet(); + if (ObjectUtil.isNotEmpty(menuIdList)) { + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysMenu::getId, menuIdList).ne(SysMenu::getType, MenuTypeEnum.DIR.getCode()) + .eq(SysMenu::getStatus, CommonStatusEnum.ENABLE.getCode()); + + this.list(queryWrapper).forEach(sysMenu -> { + if(MenuTypeEnum.BTN.getCode().equals(sysMenu.getType())) { + permissions.add(sysMenu.getPermission()); + } else { + String removePrefix = StrUtil.removePrefix(sysMenu.getRouter(), SymbolConstant.LEFT_DIVIDE); + String permission = removePrefix.replaceAll(SymbolConstant.LEFT_DIVIDE, SymbolConstant.COLON); + permissions.add(permission); + } + }); + } + return CollectionUtil.newArrayList(permissions); + } + + @Override + public List<SysMenu> getLoginMenus(Long userId, String appCode, List<Long> menuIdList) { + //如果是超级管理员则展示所有系统权重菜单,不能展示业务权重菜单 + SysUser sysUser = sysUserService.getById(userId); + Integer adminType = sysUser.getAdminType(); + + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + + if (AdminTypeEnum.SUPER_ADMIN.getCode().equals(adminType)) { + + //查询权重不为业务权重的且类型不是按钮的 + queryWrapper.eq(SysMenu::getStatus, CommonStatusEnum.ENABLE.getCode()) + .eq(SysMenu::getApplication, appCode) + .notIn(SysMenu::getType, MenuTypeEnum.BTN.getCode()) + .notIn(SysMenu::getWeight, MenuWeightEnum.DEFAULT_WEIGHT.getCode()) + .orderByAsc(SysMenu::getSort); + } else { + + //非超级管理员则获取自己角色所拥有的菜单集合 + if (ObjectUtil.isNotEmpty(menuIdList)) { + queryWrapper.in(SysMenu::getId, menuIdList) + .eq(SysMenu::getStatus, CommonStatusEnum.ENABLE.getCode()) + .eq(SysMenu::getApplication, appCode) + .notIn(SysMenu::getType, MenuTypeEnum.BTN.getCode()) + .orderByAsc(SysMenu::getSort); + } else { + //如果角色的菜单为空,则查不到菜单 + return CollectionUtil.newArrayList(); + } + } + //查询列表 + return this.list(queryWrapper); + } + + /** + * 将SysMenu格式菜单转换为LoginMenuTreeNode菜单 + * + * @author xuyuxiang + * @date 2020/4/17 17:53 + */ + @Override + public List<LoginMenuTreeNode> convertSysMenuToLoginMenu(List<SysMenu> sysMenuList) { + List<LoginMenuTreeNode> antDesignMenuTreeNodeList = CollectionUtil.newArrayList(); + sysMenuList.forEach(sysMenu -> { + LoginMenuTreeNode loginMenuTreeNode = new LoginMenuTreeNode(); + loginMenuTreeNode.setComponent(sysMenu.getComponent()); + loginMenuTreeNode.setId(sysMenu.getId()); + loginMenuTreeNode.setName(sysMenu.getCode()); + loginMenuTreeNode.setPath(sysMenu.getRouter()); + loginMenuTreeNode.setPid(sysMenu.getPid()); + LoginMenuTreeNode.Meta mateItem = new LoginMenuTreeNode().new Meta(); + mateItem.setIcon(sysMenu.getIcon()); + mateItem.setTitle(sysMenu.getName()); + mateItem.setLink(sysMenu.getLink()); + //是否可见 + mateItem.setShow(!YesOrNotEnum.N.getCode().equals(sysMenu.getVisible())); + //设置的首页,默认打开此链接 + loginMenuTreeNode.setRedirect(sysMenu.getRedirect()); + //是否是外链 + if (MenuOpenTypeEnum.OUTER.getCode().equals(sysMenu.getOpenType())) { + //打开外链 + mateItem.setTarget("_blank"); + loginMenuTreeNode.setPath(sysMenu.getLink()); + loginMenuTreeNode.setRedirect(sysMenu.getLink()); + } + loginMenuTreeNode.setMeta(mateItem); + antDesignMenuTreeNodeList.add(loginMenuTreeNode); + }); + return antDesignMenuTreeNodeList; + } + + @Override + public List<String> getUserMenuAppCodeList(Long userId, List<Long> roleIdList) { + Set<String> appCodeSet = CollectionUtil.newHashSet(); + + if (ObjectUtil.isNotEmpty(roleIdList)) { + List<Long> menuIdList = sysRoleMenuService.getRoleMenuIdList(roleIdList); + + if (ObjectUtil.isNotEmpty(menuIdList)) { + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysMenu::getId, menuIdList) + .eq(SysMenu::getStatus, CommonStatusEnum.ENABLE.getCode()); + appCodeSet = this.list(queryWrapper).stream().map(SysMenu::getApplication).collect(Collectors.toSet()); + } + } + + return CollectionUtil.newArrayList(appCodeSet); + } + + @Override + public List<SysMenu> list(SysMenuParam sysMenuParam) { + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysMenuParam)) { + //根据所属应用查询 + if (ObjectUtil.isNotEmpty(sysMenuParam.getApplication())) { + queryWrapper.eq(SysMenu::getApplication, sysMenuParam.getApplication()); + } + //根据菜单名称模糊查询 + if (ObjectUtil.isNotEmpty(sysMenuParam.getName())) { + queryWrapper.like(SysMenu::getName, sysMenuParam.getName()); + } + } + queryWrapper.eq(SysMenu::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysMenu::getSort); + List<SysMenu> sysMenuList = this.list(queryWrapper); + //将结果集处理成树 + return new TreeBuildFactory<SysMenu>().doTreeBuild(sysMenuList); + } + + @Override + public void add(SysMenuParam sysMenuParam) { + // 校验参数 + checkParam(sysMenuParam, false); + + SysMenu sysMenu = new SysMenu(); + BeanUtil.copyProperties(sysMenuParam, sysMenu); + + // 设置新的pid + String newPids = createNewPids(sysMenuParam.getPid()); + sysMenu.setPids(newPids); + + // 设置启用状态 + sysMenu.setStatus(CommonStatusEnum.ENABLE.getCode()); + + this.save(sysMenu); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(SysMenuParam sysMenuParam) { + Long id = sysMenuParam.getId(); + //级联删除子节点 + List<Long> childIdList = this.getChildIdListById(id); + childIdList.add(id); + LambdaUpdateWrapper<SysMenu> updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.in(SysMenu::getId, childIdList) + .set(SysMenu::getStatus, CommonStatusEnum.DELETED.getCode()); + this.update(updateWrapper); + //级联删除该菜单及子菜单对应的角色-菜单表信息 + sysRoleMenuService.deleteRoleMenuListByMenuIdList(childIdList); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void edit(SysMenuParam sysMenuParam) { + + // 校验参数 + checkParam(sysMenuParam, true); + + // 获取修改的菜单的旧数据(库中的) + SysMenu oldMenu = this.querySysMenu(sysMenuParam); + + // 本菜单旧的pids + Long oldPid = oldMenu.getPid(); + String oldPids = oldMenu.getPids(); + + // 生成新的pid和pids + Long newPid = sysMenuParam.getPid(); + String newPids = this.createNewPids(sysMenuParam.getPid()); + + // 是否更新子应用的标识 + boolean updateSubAppsFlag = false; + + // 是否更新子节点的pids的标识 + boolean updateSubPidsFlag = false; + + // 如果应用有变化 + if (!sysMenuParam.getApplication().equals(oldMenu.getApplication())) { + // 父节点不是根节点不能移动应用 + if (!oldPid.equals(0L)) { + throw new ServiceException(SysMenuExceptionEnum.CANT_MOVE_APP); + } + updateSubAppsFlag = true; + } + + // 父节点有变化 + if (!newPid.equals(oldPid)) { + updateSubPidsFlag = true; + } + + // 开始更新所有子节点的配置 + if (updateSubAppsFlag || updateSubPidsFlag) { + + // 查找所有叶子节点,包含子节点的子节点 + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(SysMenu::getPids, oldMenu.getId()); + List<SysMenu> list = this.list(queryWrapper); + + // 更新所有子节点的应用为当前菜单的应用 + if (ObjectUtil.isNotEmpty(list)) { + + // 更新所有子节点的application + if (updateSubAppsFlag) { + list.forEach(child -> child.setApplication(sysMenuParam.getApplication())); + } + // 更新所有子节点的pids + if (updateSubPidsFlag) { + list.forEach(child -> { + // 子节点pids组成 = 当前菜单新pids + 当前菜单id + 子节点自己的pids后缀 + String oldParentCodesPrefix = oldPids + SymbolConstant.LEFT_SQUARE_BRACKETS + oldMenu.getId() + + SymbolConstant.RIGHT_SQUARE_BRACKETS + SymbolConstant.COMMA; + String oldParentCodesSuffix = child.getPids().substring(oldParentCodesPrefix.length()); + String menuParentCodes = newPids + SymbolConstant.LEFT_SQUARE_BRACKETS + oldMenu.getId() + + SymbolConstant.RIGHT_SQUARE_BRACKETS + SymbolConstant.COMMA + oldParentCodesSuffix; + child.setPids(menuParentCodes); + }); + } + + this.updateBatchById(list); + } + } + + // 拷贝参数到实体中 + BeanUtil.copyProperties(sysMenuParam, oldMenu); + + // 设置新的pids + oldMenu.setPids(newPids); + //不能修改状态,用修改状态接口修改状态 + oldMenu.setStatus(null); + this.updateById(oldMenu); + } + + @Override + public SysMenu detail(SysMenuParam sysMenuParam) { + return this.querySysMenu(sysMenuParam); + } + + @Override + public List<MenuBaseTreeNode> tree(SysMenuParam sysMenuParam) { + List<MenuBaseTreeNode> menuTreeNodeList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + + if (ObjectUtil.isNotNull(sysMenuParam)) { + if (ObjectUtil.isNotEmpty(sysMenuParam.getApplication())) { + queryWrapper.eq(SysMenu::getApplication, sysMenuParam.getApplication()); + } + } + queryWrapper.eq(SysMenu::getStatus, CommonStatusEnum.ENABLE.getCode()) + .in(SysMenu::getType, CollectionUtil.newArrayList(MenuTypeEnum.DIR.getCode(), MenuTypeEnum.MENU.getCode())); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysMenu::getSort); + this.list(queryWrapper).forEach(sysMenu -> { + MenuBaseTreeNode menuTreeNode = new MenuBaseTreeNode(); + menuTreeNode.setId(sysMenu.getId()); + menuTreeNode.setParentId(sysMenu.getPid()); + menuTreeNode.setValue(String.valueOf(sysMenu.getId())); + menuTreeNode.setTitle(sysMenu.getName()); + menuTreeNode.setWeight(sysMenu.getSort()); + menuTreeNodeList.add(menuTreeNode); + }); + return new TreeBuildFactory<MenuBaseTreeNode>().doTreeBuild(menuTreeNodeList); + } + + @Override + public List<MenuBaseTreeNode> treeForGrant(SysMenuParam sysMenuParam) { + List<MenuBaseTreeNode> menuTreeNodeList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + //根据应用查询 + if (ObjectUtil.isNotNull(sysMenuParam)) { + if (ObjectUtil.isNotEmpty(sysMenuParam.getApplication())) { + queryWrapper.eq(SysMenu::getApplication, sysMenuParam.getApplication()); + } + } + //如果是超级管理员给角色授权菜单时可选择所有菜单 + if (LoginContextHolder.me().isSuperAdmin()) { + queryWrapper.eq(SysMenu::getStatus, CommonStatusEnum.ENABLE.getCode()); + } else { + //非超级管理员则获取自己拥有的菜单,分配给人员,防止越级授权 + Long userId = LoginContextHolder.me().getSysLoginUserId(); + List<Long> roleIdList = sysUserRoleService.getUserRoleIdList(userId); + if (ObjectUtil.isNotEmpty(roleIdList)) { + List<Long> menuIdList = sysRoleMenuService.getRoleMenuIdList(roleIdList); + if (ObjectUtil.isNotEmpty(menuIdList)) { + queryWrapper.in(SysMenu::getId, menuIdList) + .eq(SysMenu::getStatus, CommonStatusEnum.ENABLE.getCode()); + } else { + //如果角色的菜单为空,则查不到菜单 + return CollectionUtil.newArrayList(); + } + } else { + //如果角色为空,则根本没菜单 + return CollectionUtil.newArrayList(); + } + } + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysMenu::getSort); + this.list(queryWrapper).forEach(sysMenu -> { + MenuBaseTreeNode menuTreeNode = new MenuBaseTreeNode(); + menuTreeNode.setId(sysMenu.getId()); + menuTreeNode.setParentId(sysMenu.getPid()); + menuTreeNode.setValue(String.valueOf(sysMenu.getId())); + menuTreeNode.setTitle(sysMenu.getName()); + menuTreeNode.setWeight(sysMenu.getSort()); + menuTreeNodeList.add(menuTreeNode); + }); + return new TreeBuildFactory<MenuBaseTreeNode>().doTreeBuild(menuTreeNodeList); + } + + @Override + public boolean hasMenu(String appCode) { + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysMenu::getApplication, appCode) + .ne(SysMenu::getStatus, CommonStatusEnum.DELETED.getCode()); + return this.list(queryWrapper).size() != 0; + } + + /** + * 校验参数 + * + * @author xuyuxiang + * @date 2020/3/27 9:15 + */ + private void checkParam(SysMenuParam sysMenuParam, boolean isExcludeSelf) { + //菜单类型(字典 0目录 1菜单 2按钮) + Integer type = sysMenuParam.getType(); + + String router = sysMenuParam.getRouter(); + + String permission = sysMenuParam.getPermission(); + + Integer openType = sysMenuParam.getOpenType(); + + if (MenuTypeEnum.DIR.getCode().equals(type)) { + if (ObjectUtil.isEmpty(router)) { + throw new ServiceException(SysMenuExceptionEnum.MENU_ROUTER_EMPTY); + } + } + + if (MenuTypeEnum.MENU.getCode().equals(type)) { + if (ObjectUtil.isEmpty(router)) { + throw new ServiceException(SysMenuExceptionEnum.MENU_ROUTER_EMPTY); + } + if (ObjectUtil.isEmpty(openType)) { + throw new ServiceException(SysMenuExceptionEnum.MENU_OPEN_TYPE_EMPTY); + } + } + + if (MenuTypeEnum.BTN.getCode().equals(type)) { + if (ObjectUtil.isEmpty(permission)) { + throw new ServiceException(SysMenuExceptionEnum.MENU_PERMISSION_EMPTY); + } else { + Set<String> urlSet = resourceCache.getAllResources(); + + if (!permission.contains(SymbolConstant.COLON)) { + throw new ServiceException(SysMenuExceptionEnum.MENU_PERMISSION_ERROR); + } + permission = SymbolConstant.COLON + permission; + if (!urlSet.contains(permission.replaceAll(SymbolConstant.COLON, SymbolConstant.LEFT_DIVIDE))) { + throw new ServiceException(SysMenuExceptionEnum.MENU_PERMISSION_NOT_EXIST); + } + } + } + + // 如果是编辑菜单时候,pid和id不能一致,一致会导致无限递归 + if (isExcludeSelf) { + if (sysMenuParam.getId().equals(sysMenuParam.getPid())) { + throw new ServiceException(SysMenuExceptionEnum.PID_CANT_EQ_ID); + } + + // 如果是编辑,父id不能为自己的子节点 + List<Long> childIdListById = this.getChildIdListById(sysMenuParam.getId()); + if(ObjectUtil.isNotEmpty(childIdListById)) { + if(childIdListById.contains(sysMenuParam.getPid())) { + throw new ServiceException(SysMenuExceptionEnum.PID_CANT_EQ_CHILD_ID); + } + } + } + + Long id = sysMenuParam.getId(); + String name = sysMenuParam.getName(); + String code = sysMenuParam.getCode(); + + LambdaQueryWrapper<SysMenu> queryWrapperByName = new LambdaQueryWrapper<>(); + queryWrapperByName.eq(SysMenu::getName, name) + .ne(SysMenu::getStatus, CommonStatusEnum.DELETED.getCode()); + + LambdaQueryWrapper<SysMenu> queryWrapperByCode = new LambdaQueryWrapper<>(); + queryWrapperByCode.eq(SysMenu::getCode, code) + .ne(SysMenu::getStatus, CommonStatusEnum.DELETED.getCode()); + + if (isExcludeSelf) { + queryWrapperByName.ne(SysMenu::getId, id); + queryWrapperByCode.ne(SysMenu::getId, id); + } + int countByName = this.count(queryWrapperByName); + int countByCode = this.count(queryWrapperByCode); + + if (countByName >= 1) { + throw new ServiceException(SysMenuExceptionEnum.MENU_NAME_REPEAT); + } + if (countByCode >= 1) { + throw new ServiceException(SysMenuExceptionEnum.MENU_CODE_REPEAT); + } + } + + /** + * 获取系统菜单 + * + * @author xuyuxiang + * @date 2020/3/27 9:13 + */ + private SysMenu querySysMenu(SysMenuParam sysMenuParam) { + SysMenu sysMenu = this.getById(sysMenuParam.getId()); + if (ObjectUtil.isNull(sysMenu)) { + throw new ServiceException(SysMenuExceptionEnum.MENU_NOT_EXIST); + } + return sysMenu; + } + + /** + * 创建pids的值 + * <p> + * 如果pid是0顶级节点,pids就是 [0], + * <p> + * 如果pid不是顶级节点,pids就是 pid菜单的pids + [pid] + , + * + * @author xuyuxiang + * @date 2020/3/26 11:28 + */ + private String createNewPids(Long pid) { + if (pid.equals(0L)) { + return SymbolConstant.LEFT_SQUARE_BRACKETS + 0 + SymbolConstant.RIGHT_SQUARE_BRACKETS + + SymbolConstant.COMMA; + } else { + //获取父菜单 + SysMenu parentMenu = this.getById(pid); + return parentMenu.getPids() + + SymbolConstant.LEFT_SQUARE_BRACKETS + pid + SymbolConstant.RIGHT_SQUARE_BRACKETS + + SymbolConstant.COMMA; + } + } + + /** + * 根据节点id获取所有子节点id集合 + * + * @author xuyuxiang + * @date 2020/3/26 11:31 + */ + private List<Long> getChildIdListById(Long id) { + List<Long> childIdList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(SysMenu::getPids, SymbolConstant.LEFT_SQUARE_BRACKETS + id + + SymbolConstant.RIGHT_SQUARE_BRACKETS); + this.list(queryWrapper).forEach(sysMenu -> childIdList.add(sysMenu.getId())); + return childIdList; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/controller/SysMachineController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/controller/SysMachineController.java new file mode 100644 index 0000000..de5e25f --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/controller/SysMachineController.java @@ -0,0 +1,60 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.monitor.service.SysMachineService; + +import javax.annotation.Resource; + +/** + * 系统属性监控控制器 + * + * @author xuyuxiang + * @date 2020/6/5 14:36 + */ +@RestController +public class SysMachineController { + + @Resource + private SysMachineService sysMachineService; + + /** + * 系统属性监控 + * + * @author xuyuxiang + * @date 2020/6/5 14:38 + */ + @GetMapping("/sysMachine/query") + @BusinessLog(title = "系统属性监控_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData query() { + return new SuccessResponseData(sysMachineService.query()); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/controller/SysOnlineUserController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/controller/SysOnlineUserController.java new file mode 100644 index 0000000..5c57554 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/controller/SysOnlineUserController.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.monitor.param.SysOnlineUserParam; +import vip.xiaonuo.sys.modular.monitor.service.SysOnlineUserService; + +import javax.annotation.Resource; + +/** + * 在线用户控制器 + * + * @author xuyuxiang + * @date 2020/4/7 16:57 + */ +@RestController +public class SysOnlineUserController { + + @Resource + private SysOnlineUserService sysOnlineUserService; + + /** + * 在线用户列表 + * + * @author xuyuxiang + * @date 2020/4/7 16:58 + */ + @Permission + @GetMapping("/sysOnlineUser/list") + @BusinessLog(title = "系统在线用户_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysOnlineUserParam sysOnlineUserParam) { + return new SuccessResponseData(sysOnlineUserService.list(sysOnlineUserParam)); + } + + /** + * 在线用户强退 + * + * @author xuyuxiang + * @date 2020/4/7 17:36 + */ + @Permission + @PostMapping("/sysOnlineUser/forceExist") + @BusinessLog(title = "系统在线用户_强退", opType = LogAnnotionOpTypeEnum.FORCE) + public ResponseData forceExist(@RequestBody @Validated(SysOnlineUserParam.force.class) SysOnlineUserParam sysOnlineUserParam) { + sysOnlineUserService.forceExist(sysOnlineUserParam); + return new SuccessResponseData(); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/param/SysOnlineUserParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/param/SysOnlineUserParam.java new file mode 100644 index 0000000..2052bd5 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/param/SysOnlineUserParam.java @@ -0,0 +1,83 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotEmpty; + +/** + * 系统在线用户参数 + * + * @author xuyuxiang + * @date 2020/4/7 17:38 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysOnlineUserParam extends BaseParam { + + /** + * 会话id + */ + @NotEmpty(message = "sessionId不能为空,请检查sessionId参数", groups = {force.class}) + private String sessionId; + + /** + * 账号 + */ + private String account; + + /** + * 昵称 + */ + private String nickName; + + /** + * 最后登陆IP + */ + private String lastLoginIp; + + /** + * 最后登陆时间 + */ + private String lastLoginTime; + + /** + * 最后登陆地址 + */ + private String lastLoginAddress; + + /** + * 最后登陆所用浏览器 + */ + private String lastLoginBrowser; + + /** + * 最后登陆所用系统 + */ + private String lastLoginOs; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/result/SysMachineResult.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/result/SysMachineResult.java new file mode 100644 index 0000000..4e5a059 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/result/SysMachineResult.java @@ -0,0 +1,172 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.result; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 系统属性结果集 + * + * @author xuyuxiang + * @date 2020/6/5 15:02 + */ +@Data +public class SysMachineResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 系统信息 + */ + private SysOsInfo sysOsInfo; + + /** + * Java信息 + */ + private SysJavaInfo sysJavaInfo; + + /** + * JVM内存信息 + */ + private SysJvmMemInfo sysJvmMemInfo; + + /** + * 系统信息内部类 + * + * @author xuyuxiang + * @date 2020/6/5 15:19 + */ + @NoArgsConstructor + @Data + public static class SysOsInfo { + + /** + * 系统名称 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + + /** + * 系统版本 + */ + private String osVersion; + + /** + * 主机名称 + */ + private String osHostName; + + /** + * 主机ip地址 + */ + private String osHostAddress; + + } + + /** + * JVM信息内部类 + * + * @author xuyuxiang + * @date 2020/6/5 15:19 + */ + @NoArgsConstructor + @Data + public static class SysJavaInfo { + + /** + * 虚拟机名称 + */ + private String jvmName; + + /** + * 虚拟机版本 + */ + private String jvmVersion; + + /** + * 虚拟机供应商 + */ + private String jvmVendor; + + /** + * java名称 + */ + private String javaName; + + /** + * java版本 + */ + private String javaVersion; + + } + + /** + * JVM内存信息 + * + * @author xuyuxiang + * @date 2020/6/5 15:19 + */ + @NoArgsConstructor + @Data + public static class SysJvmMemInfo { + + /** + * 最大内存 + */ + private String jvmMaxMemory; + + /** + * 可用内存 + */ + private String jvmUsableMemory; + + /** + * 总内存 + */ + private String jvmTotalMemory; + + /** + * 已使用内存 + */ + private String jvmUsedMemory; + + /** + * 空余内存 + */ + private String jvmFreeMemory; + + /** + * 使用率 + */ + private String jvmMemoryUsedRate; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/result/SysOnlineUserResult.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/result/SysOnlineUserResult.java new file mode 100644 index 0000000..91098c1 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/result/SysOnlineUserResult.java @@ -0,0 +1,81 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.result; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 系统在线用户结果集 + * + * @author xuyuxiang + * @date 2020/4/7 17:07 + */ +@Data +public class SysOnlineUserResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 会话id + */ + private String sessionId; + + /** + * 账号 + */ + private String account; + + /** + * 昵称 + */ + private String nickName; + + /** + * 最后登陆IP + */ + private String lastLoginIp; + + /** + * 最后登陆时间 + */ + private String lastLoginTime; + + /** + * 最后登陆地址 + */ + private String lastLoginAddress; + + /** + * 最后登陆所用浏览器 + */ + private String lastLoginBrowser; + + /** + * 最后登陆所用系统 + */ + private String lastLoginOs; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/SysMachineService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/SysMachineService.java new file mode 100644 index 0000000..8dd86c4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/SysMachineService.java @@ -0,0 +1,45 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.service; + +import vip.xiaonuo.sys.modular.monitor.result.SysMachineResult; + +/** + * 系统属性监控service接口 + * + * @author xuyuxiang + * @date 2020/6/5 14:39 + */ +public interface SysMachineService { + + /** + * 系统属性监控 + * + * @return 系统属性结果集 + * @author xuyuxiang + * @date 2020/6/5 14:45 + */ + SysMachineResult query(); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/SysOnlineUserService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/SysOnlineUserService.java new file mode 100644 index 0000000..b5ca63d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/SysOnlineUserService.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.service; + +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.monitor.param.SysOnlineUserParam; +import vip.xiaonuo.sys.modular.monitor.result.SysOnlineUserResult; + +/** + * 系统在线用户service接口 + * + * @author xuyuxiang + * @date 2020/4/7 17:06 + */ +public interface SysOnlineUserService { + + /** + * 系统在线用户列表 + * + * @param sysOnlineUserParam 查询参数 + * @return 在线用户列表 + * @author xuyuxiang + * @date 2020/4/7 17:09 + */ + PageResult<SysOnlineUserResult> list(SysOnlineUserParam sysOnlineUserParam); + + /** + * 系统在线用户强退 + * + * @param sysOnlineUserParam 操作参数 + * @author xuyuxiang + * @date 2020/4/7 20:20 + */ + void forceExist(SysOnlineUserParam sysOnlineUserParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/impl/SysMachineServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/impl/SysMachineServiceImpl.java new file mode 100644 index 0000000..a14738a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/impl/SysMachineServiceImpl.java @@ -0,0 +1,89 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.service.impl; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.system.*; +import org.springframework.stereotype.Service; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.sys.modular.monitor.result.SysMachineResult; +import vip.xiaonuo.sys.modular.monitor.service.SysMachineService; + +import java.math.BigDecimal; +import java.text.DecimalFormat; + +/** + * 系统属性监控service接口实现类 + * + * @author xuyuxiang + * @date 2020/6/5 14:40 + */ +@Service +public class SysMachineServiceImpl implements SysMachineService { + + @Override + public SysMachineResult query() { + JvmInfo jvmInfo = SystemUtil.getJvmInfo(); + JavaRuntimeInfo javaRuntimeInfo = SystemUtil.getJavaRuntimeInfo(); + OsInfo osInfo = SystemUtil.getOsInfo(); + HostInfo hostInfo = SystemUtil.getHostInfo(); + RuntimeInfo runtimeInfo = SystemUtil.getRuntimeInfo(); + //系统属性结果集 + SysMachineResult sysMachineResult = new SysMachineResult(); + + //系统信息 + SysMachineResult.SysOsInfo sysOsInfo = new SysMachineResult.SysOsInfo(); + sysOsInfo.setOsName(osInfo.getName()); + sysOsInfo.setOsArch(osInfo.getArch()); + sysOsInfo.setOsVersion(osInfo.getVersion()); + sysOsInfo.setOsHostName(hostInfo.getName()); + sysOsInfo.setOsHostAddress(hostInfo.getAddress()); + sysMachineResult.setSysOsInfo(sysOsInfo); + + //Java信息 + SysMachineResult.SysJavaInfo sysJavaInfo = new SysMachineResult.SysJavaInfo(); + sysJavaInfo.setJvmName(jvmInfo.getName()); + sysJavaInfo.setJvmVersion(jvmInfo.getVersion()); + sysJavaInfo.setJvmVendor(jvmInfo.getVendor()); + sysJavaInfo.setJavaName(javaRuntimeInfo.getName()); + sysJavaInfo.setJavaVersion(javaRuntimeInfo.getVersion()); + sysMachineResult.setSysJavaInfo(sysJavaInfo); + + //jvm内存信息 + SysMachineResult.SysJvmMemInfo sysJvmMemInfo = new SysMachineResult.SysJvmMemInfo(); + sysJvmMemInfo.setJvmMaxMemory(FileUtil.readableFileSize(runtimeInfo.getMaxMemory())); + sysJvmMemInfo.setJvmUsableMemory(FileUtil.readableFileSize(runtimeInfo.getUsableMemory())); + sysJvmMemInfo.setJvmTotalMemory(FileUtil.readableFileSize(runtimeInfo.getTotalMemory())); + sysJvmMemInfo.setJvmFreeMemory(FileUtil.readableFileSize(runtimeInfo.getFreeMemory())); + BigDecimal usedMemory = NumberUtil.sub(new BigDecimal(runtimeInfo.getTotalMemory()), new BigDecimal(runtimeInfo.getFreeMemory())); + sysJvmMemInfo.setJvmUsedMemory(FileUtil.readableFileSize(usedMemory.longValue())); + BigDecimal rate = NumberUtil.div(usedMemory, runtimeInfo.getTotalMemory()); + String usedRate = new DecimalFormat("#.00").format(NumberUtil.mul(rate, 100)) + SymbolConstant.PERCENT; + sysJvmMemInfo.setJvmMemoryUsedRate(usedRate); + sysMachineResult.setSysJvmMemInfo(sysJvmMemInfo); + return sysMachineResult; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/impl/SysOnlineUserServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/impl/SysOnlineUserServiceImpl.java new file mode 100644 index 0000000..5c875cb --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/monitor/service/impl/SysOnlineUserServiceImpl.java @@ -0,0 +1,109 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.monitor.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.springframework.stereotype.Service; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.exception.DemoException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.PageUtil; +import vip.xiaonuo.sys.core.cache.UserCache; +import vip.xiaonuo.sys.core.log.LogManager; +import vip.xiaonuo.sys.modular.monitor.param.SysOnlineUserParam; +import vip.xiaonuo.sys.modular.monitor.result.SysOnlineUserResult; +import vip.xiaonuo.sys.modular.monitor.service.SysOnlineUserService; + +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 系统组织机构service接口实现类 + * + * @author xuyuxiang + * @date 2020/4/7 17:06 + */ +@Service +public class SysOnlineUserServiceImpl implements SysOnlineUserService { + + @Resource + private UserCache userCache; + + @Override + public PageResult<SysOnlineUserResult> list(SysOnlineUserParam sysOnlineUserParam) { + List<SysOnlineUserResult> tempList = CollectionUtil.newArrayList(); + // 获取缓存中的所有用户 + Map<String, SysLoginUser> allKeyValues = userCache.getAllKeyValues(); + for (Map.Entry<String, SysLoginUser> sysLoginUserEntry : allKeyValues.entrySet()) { + SysOnlineUserResult sysOnlineUserResult = new SysOnlineUserResult(); + sysOnlineUserResult.setSessionId(sysLoginUserEntry.getKey()); + BeanUtil.copyProperties(sysLoginUserEntry.getValue(), sysOnlineUserResult); + tempList.add(sysOnlineUserResult); + } + List<SysOnlineUserResult> listAll = tempList.stream() + .sorted(Comparator.comparing(SysOnlineUserResult::getLastLoginTime, Comparator.reverseOrder())) + .collect(Collectors.toList()); + Page<SysOnlineUserResult> page = PageFactory.defaultPage(); + page.setTotal(tempList.size()); + List<SysOnlineUserResult> resultList = PageUtil.page(page, listAll); + return new PageResult<>(page, resultList); + } + + @Override + public void forceExist(SysOnlineUserParam sysOnlineUserParam) { + Boolean demoEnvFlag = ConstantContextHolder.getDemoEnvFlag(); + if (demoEnvFlag) { + throw new DemoException(); + } + + //获取缓存的key + String redisLoginUserKey = sysOnlineUserParam.getSessionId(); + + //获取缓存的用户 + SysLoginUser sysLoginUser = userCache.get(redisLoginUserKey); + + //如果缓存的用户存在,清除会话,否则表示该会话信息已失效,不执行任何操作 + if (ObjectUtil.isNotNull(sysLoginUser)) { + + //清除登录会话 + userCache.remove(redisLoginUserKey); + + //获取登录用户的账户信息 + String account = LoginContextHolder.me().getSysLoginUserAccount(); + + //创建退出登录日志 + LogManager.me().executeExitLog(account); + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/controller/SysNoticeController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/controller/SysNoticeController.java new file mode 100644 index 0000000..3a04c94 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/controller/SysNoticeController.java @@ -0,0 +1,148 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.notice.param.SysNoticeParam; +import vip.xiaonuo.sys.modular.notice.service.SysNoticeService; + +import javax.annotation.Resource; + +/** + * 系统通知公告控制器 + * + * @author xuyuxiang + * @date 2020/6/28 17:18 + */ +@RestController +public class SysNoticeController { + + @Resource + private SysNoticeService sysNoticeService; + + /** + * 查询系统通知公告 + * + * @author xuyuxiang + * @date 2020/6/28 17:24 + */ + @Permission + @GetMapping("/sysNotice/page") + @BusinessLog(title = "系统通知公告_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysNoticeParam sysNoticeParam) { + return new SuccessResponseData(sysNoticeService.page(sysNoticeParam)); + } + + /** + * 查询我收到的系统通知公告 + * + * @author xuyuxiang + * @date 2020/6/29 11:59 + */ + @Permission + @GetMapping("/sysNotice/received") + @BusinessLog(title = "系统通知公告_已收", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData received(SysNoticeParam sysNoticeParam) { + return new SuccessResponseData(sysNoticeService.received(sysNoticeParam)); + } + + /** + * 添加系统通知公告 + * + * @author xuyuxiang + * @date 2020/6/28 17:24 + */ + @Permission + @PostMapping("/sysNotice/add") + @BusinessLog(title = "系统通知公告_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysNoticeParam.add.class) SysNoticeParam sysNoticeParam) { + sysNoticeService.add(sysNoticeParam); + return new SuccessResponseData(); + } + + /** + * 删除系统通知公告 + * + * @author xuyuxiang + * @date 2020/6/28 17:25 + */ + @Permission + @PostMapping("/sysNotice/delete") + @BusinessLog(title = "系统通知公告_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysNoticeParam.delete.class) SysNoticeParam sysNoticeParam) { + sysNoticeService.delete(sysNoticeParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统通知公告 + * + * @author xuyuxiang + * @date 2020/6/28 17:25 + */ + @Permission + @PostMapping("/sysNotice/edit") + @BusinessLog(title = "系统通知公告_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysNoticeParam.edit.class) SysNoticeParam sysNoticeParam) { + sysNoticeService.edit(sysNoticeParam); + return new SuccessResponseData(); + } + + /** + * 查看系统通知公告 + * + * @author xuyuxiang + * @date 2020/6/28 17:25 + */ + @Permission + @GetMapping("/sysNotice/detail") + @BusinessLog(title = "系统通知公告_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysNoticeParam.detail.class) SysNoticeParam sysNoticeParam) { + return new SuccessResponseData(sysNoticeService.detail(sysNoticeParam)); + } + + /** + * 修改状态 + * + * @author xuyuxiang + * @date 2020/6/29 9:44 + */ + @Permission + @PostMapping("/sysNotice/changeStatus") + @BusinessLog(title = "系统通知公告_修改状态", opType = LogAnnotionOpTypeEnum.CHANGE_STATUS) + public ResponseData changeStatus(@RequestBody @Validated(SysNoticeParam.changeStatus.class) SysNoticeParam sysNoticeParam) { + sysNoticeService.changeStatus(sysNoticeParam); + return new SuccessResponseData(); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/entity/SysNotice.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/entity/SysNotice.java new file mode 100644 index 0000000..8e56866 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/entity/SysNotice.java @@ -0,0 +1,102 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +import java.util.Date; + +/** + * 系统通知公告表 + * + * @author xuyuxiang + * @date 2020/6/28 17:12 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_notice") +public class SysNotice extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 类型(字典 1通知 2公告) + */ + private Integer type; + + /** + * 发布人id + */ + private Long publicUserId; + + /** + * 发布人姓名 + */ + private String publicUserName; + + /** + * 发布机构id + */ + private Long publicOrgId; + + /** + * 发布机构名称 + */ + private String publicOrgName; + + /** + * 发布时间 + */ + private Date publicTime; + + /** + * 撤回时间 + */ + private Date cancelTime; + + /** + * 状态(字典 0草稿 1发布 2撤回 3删除) + */ + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/entity/SysNoticeUser.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/entity/SysNoticeUser.java new file mode 100644 index 0000000..1629e18 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/entity/SysNoticeUser.java @@ -0,0 +1,69 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * 系统通知公告用户表 + * + * @author xuyuxiang + * @date 2020/6/29 10:45 + */ +@Data +@TableName("sys_notice_user") +public class SysNoticeUser { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 通知公告id + */ + private Long noticeId; + + /** + * 用户id + */ + private Long userId; + + /** + * 状态(字典 0未读 1已读) + */ + private Integer status; + + /** + * 阅读时间 + */ + private Date readTime; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/enums/SysNoticeExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/enums/SysNoticeExceptionEnum.java new file mode 100644 index 0000000..19bbb98 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/enums/SysNoticeExceptionEnum.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统应用相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/26 10:11 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_NOTICE_EXCEPTION_ENUM) +public enum SysNoticeExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 通知公告不存在 + */ + NOTICE_NOT_EXIST(1, "通知公告不存在"), + + /** + * 编辑失败 + */ + NOTICE_CANNOT_EDIT(2, "编辑失败,通知公告非草稿状态时无法编辑"), + + /** + * 状态格式错误 + */ + NOTICE_STATUS_ERROR(3, "状态格式错误,请检查status参数"), + + /** + * 删除失败 + */ + NOTICE_CANNOT_DELETE(4, "删除失败,通知公告已发布或已删除"); + + private final Integer code; + + private final String message; + + SysNoticeExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/SysNoticeMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/SysNoticeMapper.java new file mode 100644 index 0000000..f3383de --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/SysNoticeMapper.java @@ -0,0 +1,52 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import vip.xiaonuo.sys.modular.notice.entity.SysNotice; +import vip.xiaonuo.sys.modular.notice.result.SysNoticeReceiveResult; + +/** + * 系统通知公告mapper接口 + * + * @author xuyuxiang + * @date 2020/6/28 17:18 + */ +public interface SysNoticeMapper extends BaseMapper<SysNotice> { + + /** + * 查询当前登陆用户已收公告 + * + * @param page 分页参数 + * @param queryWrapper 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/6/29 14:32 + */ + Page<SysNoticeReceiveResult> page(@Param("page") Page page, @Param("ew") QueryWrapper queryWrapper); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/SysNoticeUserMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/SysNoticeUserMapper.java new file mode 100644 index 0000000..970b2ce --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/SysNoticeUserMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.notice.entity.SysNoticeUser; + +/** + * 系统通知公告用户mapper接口 + * + * @author xuyuxiang + * @date 2020/6/29 10:52 + */ +public interface SysNoticeUserMapper extends BaseMapper<SysNoticeUser> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/mapping/SysNoticeMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/mapping/SysNoticeMapper.xml new file mode 100644 index 0000000..999bcea --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/mapping/SysNoticeMapper.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.notice.mapper.SysNoticeMapper"> + + <resultMap id="sysNoticeReceiveResult" type="vip.xiaonuo.sys.modular.notice.result.SysNoticeReceiveResult"> + <id column="id" property="id" /> + <result column="title" property="title" /> + <result column="content" property="content" /> + <result column="type" property="type" /> + <result column="public_user_id" property="publicUserId" /> + <result column="public_user_name" property="publicUserName" /> + <result column="public_org_id" property="publicOrgId" /> + <result column="public_org_name" property="publicOrgName" /> + <result column="public_time" property="publicTime" /> + <result column="cancel_time" property="cancelTime" /> + <result column="read_status" property="readStatus" /> + <result column="read_time" property="readTime" /> + </resultMap> + + <select id="page" resultMap="sysNoticeReceiveResult"> + select + sys_notice.*, + sys_notice_user.status as read_status, + sys_notice_user.read_time as read_time + from + sys_notice + left join sys_notice_user on sys_notice.id = sys_notice_user.notice_id + ${ew.customSqlSegment} + </select> +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/mapping/SysNoticeUserMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/mapping/SysNoticeUserMapper.xml new file mode 100644 index 0000000..d0cefc9 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/mapper/mapping/SysNoticeUserMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.notice.mapper.SysNoticeUserMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/param/SysNoticeParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/param/SysNoticeParam.java new file mode 100644 index 0000000..a0f4139 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/param/SysNoticeParam.java @@ -0,0 +1,84 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 系统通知公告参数 + * + * @author xuyuxiang + * @date 2020/6/28 17:19 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysNoticeParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, changeStatus.class}) + private Long id; + + /** + * 标题 + */ + @NotBlank(message = "标题不能为空,请检查title参数", groups = {add.class, edit.class}) + private String title; + + /** + * 内容 + */ + @NotBlank(message = "内容不能为空,请检查content参数", groups = {add.class, edit.class}) + private String content; + + /** + * 类型(字典 1通知 2公告) + */ + @NotNull(message = "类型不能为空,请检查type参数", groups = {add.class, edit.class}) + @Min(value = 1, message = "类型格式错误,请检查type参数", groups = {add.class, edit.class}) + @Max(value = 2, message = "类型格式错误,请检查type参数", groups = {add.class, edit.class}) + private Integer type; + + /** + * 状态(字典 0草稿 1发布 2撤回 3删除) + */ + @NotNull(message = "状态不能为空,请检查status参数", groups = {add.class, edit.class, changeStatus.class}) + private Integer status; + + /** + * 通知到的人 + */ + @NotNull(message = "通知到的人不能为空,请检查noticeUserIdList参数", groups = {add.class, edit.class}) + private List<Long> noticeUserIdList; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/result/SysNoticeDetailResult.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/result/SysNoticeDetailResult.java new file mode 100644 index 0000000..0bfd009 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/result/SysNoticeDetailResult.java @@ -0,0 +1,109 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.result; + +import cn.hutool.core.lang.Dict; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * 系统通知公告详情结果集 + * + * @author xuyuxiang + * @date 2020/6/29 11:46 + */ +@Data +public class SysNoticeDetailResult { + + /** + * 主键 + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 类型(字典 1通知 2公告) + */ + private Integer type; + + /** + * 发布人id + */ + private Long publicUserId; + + /** + * 发布人姓名 + */ + private String publicUserName; + + /** + * 发布机构id + */ + private Long publicOrgId; + + /** + * 发布机构名称 + */ + private String publicOrgName; + + /** + * 发布时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date publicTime; + + /** + * 撤回时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date cancelTime; + + /** + * 状态(字典 0草稿 1发布 2撤回 3删除) + */ + private Integer status; + + /** + * 通知到的用户id集合 + */ + private List<Long> noticeUserIdList; + + /** + * 通知到的用户阅读信息集合 + */ + private List<Dict> noticeUserReadInfoList; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/result/SysNoticeReceiveResult.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/result/SysNoticeReceiveResult.java new file mode 100644 index 0000000..b233eb6 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/result/SysNoticeReceiveResult.java @@ -0,0 +1,104 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.result; + +import lombok.Data; + +import java.util.Date; + +/** + * 已收系统通知公告结果集 + * + * @author xuyuxiang + * @date 2020/6/29 12:20 + */ +@Data +public class SysNoticeReceiveResult { + + /** + * 主键 + */ + private Long id; + + /** + * 标题 + */ + private String title; + + /** + * 内容 + */ + private String content; + + /** + * 类型(字典 1通知 2公告) + */ + private Integer type; + + /** + * 发布人id + */ + private Long publicUserId; + + /** + * 发布人姓名 + */ + private String publicUserName; + + /** + * 发布机构id + */ + private Long publicOrgId; + + /** + * 发布机构名称 + */ + private String publicOrgName; + + /** + * 发布时间 + */ + private Date publicTime; + + /** + * 撤回时间 + */ + private Date cancelTime; + + /** + * 状态(字典 0草稿 1发布 2撤回 3删除) + */ + private Integer status; + + /** + * 阅读状态(字典 0未读 1已读) + */ + private Integer readStatus; + + /** + * 阅读时间 + */ + private Date readTime; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/SysNoticeService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/SysNoticeService.java new file mode 100644 index 0000000..fd9047b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/SysNoticeService.java @@ -0,0 +1,107 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.notice.entity.SysNotice; +import vip.xiaonuo.sys.modular.notice.param.SysNoticeParam; +import vip.xiaonuo.sys.modular.notice.result.SysNoticeDetailResult; +import vip.xiaonuo.sys.modular.notice.result.SysNoticeReceiveResult; + +/** + * 系统通知公告service接口 + * + * @author xuyuxiang + * @date 2020/6/28 17:16 + */ +public interface SysNoticeService extends IService<SysNotice> { + + /** + * 查询系统通知公告 + * + * @param sysNoticeParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/6/28 17:16 + */ + PageResult<SysNotice> page(SysNoticeParam sysNoticeParam); + + /** + * 添加系统通知公告 + * + * @param sysNoticeParam 添加参数 + * @author xuyuxiang + * @date 2020/6/28 17:21 + */ + void add(SysNoticeParam sysNoticeParam); + + /** + * 删除系统通知公告 + * + * @param sysNoticeParam 删除参数 + * @author xuyuxiang + * @date 2020/6/28 17:22 + */ + void delete(SysNoticeParam sysNoticeParam); + + /** + * 编辑系统通知公告 + * + * @param sysNoticeParam 编辑参数 + * @author xuyuxiang + * @date 2020/6/28 17:22 + */ + void edit(SysNoticeParam sysNoticeParam); + + /** + * 查看系统通知公告 + * + * @param sysNoticeParam 查看参数 + * @return 通知公告详情结果 + * @author xuyuxiang + * @date 2020/6/28 17:22 + */ + SysNoticeDetailResult detail(SysNoticeParam sysNoticeParam); + + /** + * 修改状态 + * + * @param sysNoticeParam 修改参数 + * @author xuyuxiang + * @date 2020/6/29 9:45 + */ + void changeStatus(SysNoticeParam sysNoticeParam); + + /** + * 查询当前登陆用户已收通知公告 + * + * @param sysNoticeParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/6/29 12:01 + */ + PageResult<SysNoticeReceiveResult> received(SysNoticeParam sysNoticeParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/SysNoticeUserService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/SysNoticeUserService.java new file mode 100644 index 0000000..6e9512a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/SysNoticeUserService.java @@ -0,0 +1,82 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.sys.modular.notice.entity.SysNoticeUser; + +import java.util.List; + +/** + * 系统通知公告用户service接口 + * + * @author xuyuxiang + * @date 2020/6/29 10:51 + */ +public interface SysNoticeUserService extends IService<SysNoticeUser> { + + /** + * 添加 + * + * @param noticeId 通知公告id + * @param noticeUserIdList 通知到的用户id集合 + * @param noticeUserStatus 阅读状态 + * @author xuyuxiang + * @date 2020/6/29 11:06 + */ + void add(Long noticeId, List<Long> noticeUserIdList, Integer noticeUserStatus); + + /** + * 编辑 + * + * @param noticeId 通知公告id + * @param noticeUserIdList 通知到的用户id集合 + * @param noticeUserStatus 阅读状态 + * @author xuyuxiang + * @date 2020/6/29 11:40 + */ + void edit(Long noticeId, List<Long> noticeUserIdList, Integer noticeUserStatus); + + /** + * 根据通知公告id查询通知人员信息集合 + * + * @param noticeId 通知公告id + * @return 通知用户列表 + * @author xuyuxiang + * @date 2020/6/29 11:50 + */ + List<SysNoticeUser> getSysNoticeUserListByNoticeId(Long noticeId); + + /** + * 设为已读 + * + * @param noticeId 通知公告id + * @param userId 用户id + * @param status 阅读状态 + * @author xuyuxiang + * @date 2020/6/29 12:05 + */ + void read(Long noticeId, Long userId, Integer status); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/impl/SysNoticeServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 0000000..58d15b0 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,257 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.login.SysLoginUser; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.core.enums.NoticeStatusEnum; +import vip.xiaonuo.sys.core.enums.NoticeUserStatusEnum; +import vip.xiaonuo.sys.modular.notice.entity.SysNotice; +import vip.xiaonuo.sys.modular.notice.entity.SysNoticeUser; +import vip.xiaonuo.sys.modular.notice.enums.SysNoticeExceptionEnum; +import vip.xiaonuo.sys.modular.notice.mapper.SysNoticeMapper; +import vip.xiaonuo.sys.modular.notice.param.SysNoticeParam; +import vip.xiaonuo.sys.modular.notice.result.SysNoticeDetailResult; +import vip.xiaonuo.sys.modular.notice.result.SysNoticeReceiveResult; +import vip.xiaonuo.sys.modular.notice.service.SysNoticeService; +import vip.xiaonuo.sys.modular.notice.service.SysNoticeUserService; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +/** + * 系统通知公告service接口实现类 + * + * @author xuyuxiang + * @date 2020/6/28 17:20 + */ +@Service +public class SysNoticeServiceImpl extends ServiceImpl<SysNoticeMapper, SysNotice> implements SysNoticeService { + + @Resource + private SysNoticeUserService sysNoticeUserService; + + @Resource + private SysUserService sysUserService; + + @Override + public PageResult<SysNotice> page(SysNoticeParam sysNoticeParam) { + LambdaQueryWrapper<SysNotice> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysNoticeParam)) { + //根据标题或内容模糊查询 + if (ObjectUtil.isNotEmpty(sysNoticeParam.getSearchValue())) { + queryWrapper.and(q -> q.like(SysNotice::getTitle, sysNoticeParam.getSearchValue()) + .or().like(SysNotice::getContent, sysNoticeParam.getSearchValue())); + } + //根据类型查询 + if (ObjectUtil.isNotEmpty(sysNoticeParam.getType())) { + queryWrapper.eq(SysNotice::getType, sysNoticeParam.getType()); + } + } + //查询未删除的 + queryWrapper.ne(SysNotice::getStatus, NoticeStatusEnum.DELETED.getCode()); + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void add(SysNoticeParam sysNoticeParam) { + //校验参数,检查状态是否正确 + checkParam(sysNoticeParam, true); + SysNotice sysNotice = new SysNotice(); + BeanUtil.copyProperties(sysNoticeParam, sysNotice); + SysLoginUser sysLoginUser = LoginContextHolder.me().getSysLoginUser(); + sysNotice.setPublicUserId(sysLoginUser.getId()); + sysNotice.setPublicUserName(sysLoginUser.getName()); + sysNotice.setPublicOrgId(sysLoginUser.getLoginEmpInfo().getOrgId()); + sysNotice.setPublicOrgName(sysLoginUser.getLoginEmpInfo().getOrgName()); + //如果是发布,则设置发布时间 + if (NoticeStatusEnum.PUBLIC.getCode().equals(sysNotice.getStatus())) { + sysNotice.setPublicTime(new Date()); + } + this.save(sysNotice); + //通知到的人 + List<Long> noticeUserIdList = sysNoticeParam.getNoticeUserIdList(); + Integer noticeUserStatus = NoticeUserStatusEnum.UNREAD.getCode(); + sysNoticeUserService.add(sysNotice.getId(), noticeUserIdList, noticeUserStatus); + } + + @Override + public void delete(SysNoticeParam sysNoticeParam) { + SysNotice sysNotice = this.querySysNotice(sysNoticeParam); + Integer status = sysNotice.getStatus(); + if (!NoticeStatusEnum.DRAFT.getCode().equals(status) && !NoticeStatusEnum.CANCEL.getCode().equals(status)) { + throw new ServiceException(SysNoticeExceptionEnum.NOTICE_CANNOT_DELETE); + } + sysNotice.setStatus(NoticeStatusEnum.DELETED.getCode()); + this.updateById(sysNotice); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(SysNoticeParam sysNoticeParam) { + SysNotice sysNotice = this.querySysNotice(sysNoticeParam); + //校验参数,检查状态是否正确 + checkParam(sysNoticeParam, true); + //非草稿状态 + Integer status = sysNotice.getStatus(); + if (!NoticeStatusEnum.DRAFT.getCode().equals(status)) { + throw new ServiceException(SysNoticeExceptionEnum.NOTICE_CANNOT_EDIT); + } + BeanUtil.copyProperties(sysNoticeParam, sysNotice); + //如果是发布,则设置发布时间 + if (NoticeStatusEnum.PUBLIC.getCode().equals(sysNotice.getStatus())) { + sysNotice.setPublicTime(new Date()); + } + this.updateById(sysNotice); + //通知到的人 + List<Long> noticeUserIdList = sysNoticeParam.getNoticeUserIdList(); + Integer noticeUserStatus = NoticeUserStatusEnum.UNREAD.getCode(); + sysNoticeUserService.edit(sysNotice.getId(), noticeUserIdList, noticeUserStatus); + } + + @Override + public SysNoticeDetailResult detail(SysNoticeParam sysNoticeParam) { + SysNotice sysNotice = this.querySysNotice(sysNoticeParam); + Long id = sysNotice.getId(); + //获取通知到的用户 + List<SysNoticeUser> sysNoticeUserList = sysNoticeUserService.getSysNoticeUserListByNoticeId(id); + List<Long> noticeUserIdList = CollectionUtil.newArrayList(); + List<Dict> noticeUserReadInfoList = CollectionUtil.newArrayList(); + SysNoticeDetailResult sysNoticeResult = new SysNoticeDetailResult(); + BeanUtil.copyProperties(sysNotice, sysNoticeResult); + if (ObjectUtil.isNotEmpty(sysNoticeUserList)) { + sysNoticeUserList.forEach(sysNoticeUser -> { + //遍历通知到的用户,并构造 + noticeUserIdList.add(sysNoticeUser.getUserId()); + Dict dict = Dict.create(); + dict.put("userId", sysNoticeUser.getUserId()); + dict.put("userName", sysUserService.getNameByUserId(sysNoticeUser.getUserId())); + dict.put("readStatus", sysNoticeUser.getStatus()); + dict.put("readTime", sysNoticeUser.getReadTime()); + noticeUserReadInfoList.add(dict); + }); + } + sysNoticeResult.setNoticeUserIdList(noticeUserIdList); + sysNoticeResult.setNoticeUserReadInfoList(noticeUserReadInfoList); + //如果该条通知公告为已发布,则将当前用户的该条通知公告设置为已读 + if (sysNotice.getStatus().equals(NoticeStatusEnum.PUBLIC.getCode())) { + sysNoticeUserService.read(sysNotice.getId(), + LoginContextHolder.me().getSysLoginUserId(), NoticeUserStatusEnum.READ.getCode()); + } + return sysNoticeResult; + } + + @Override + public void changeStatus(SysNoticeParam sysNoticeParam) { + SysNotice sysNotice = this.querySysNotice(sysNoticeParam); + //校验参数,检查状态是否正确 + checkParam(sysNoticeParam, false); + sysNotice.setStatus(sysNoticeParam.getStatus()); + //如果是撤回,则设置撤回时间 + if (NoticeStatusEnum.CANCEL.getCode().equals(sysNotice.getStatus())) { + sysNotice.setCancelTime(new Date()); + } else if (NoticeStatusEnum.PUBLIC.getCode().equals(sysNotice.getStatus())) { + //如果是发布,则设置发布时间 + sysNotice.setPublicTime(new Date()); + } + this.updateById(sysNotice); + } + + @Override + public PageResult<SysNoticeReceiveResult> received(SysNoticeParam sysNoticeParam) { + QueryWrapper<SysNoticeReceiveResult> queryWrapper = new QueryWrapper<>(); + //查询当前用户的 + queryWrapper.eq("sys_notice_user.user_id", LoginContextHolder.me().getSysLoginUserId()); + if (ObjectUtil.isNotNull(sysNoticeParam)) { + //根据标题或内容模糊查询 + if (ObjectUtil.isNotEmpty(sysNoticeParam.getSearchValue())) { + queryWrapper.and(q -> q.like("sys_notice.title", sysNoticeParam.getSearchValue()) + .or().like("sys_notice.content", sysNoticeParam.getSearchValue())); + } + //根据类型查询 + if (ObjectUtil.isNotEmpty(sysNoticeParam.getType())) { + queryWrapper.eq("sys_notice.type", sysNoticeParam.getType()); + } + } + //查询未删除的 + queryWrapper.ne("sys_notice.status", NoticeStatusEnum.DELETED.getCode()); + return new PageResult<>(this.baseMapper.page(PageFactory.defaultPage(), queryWrapper)); + } + + /** + * 校验参数,判断状态是否正确 + * + * @author xuyuxiang + * @date 2020/6/29 10:06 + */ + private void checkParam(SysNoticeParam sysNoticeParam, boolean isAddOrEdit) { + //保存或编辑时,传递的状态参数应为草稿,或发布 + Integer status = sysNoticeParam.getStatus(); + if (isAddOrEdit) { + if (!NoticeStatusEnum.DRAFT.getCode().equals(status) && + !NoticeStatusEnum.PUBLIC.getCode().equals(status)) { + throw new ServiceException(SysNoticeExceptionEnum.NOTICE_STATUS_ERROR); + } + } else { + //修改状态时,传递的状态参数应为撤回或删除或发布 + if (!NoticeStatusEnum.CANCEL.getCode().equals(status) && + !NoticeStatusEnum.DELETED.getCode().equals(status) && + !NoticeStatusEnum.PUBLIC.getCode().equals(status)) { + throw new ServiceException(SysNoticeExceptionEnum.NOTICE_STATUS_ERROR); + } + } + + } + + /** + * 获取系统通知公告 + * + * @author xuyuxiang + * @date 2020/6/29 9:58 + */ + private SysNotice querySysNotice(SysNoticeParam sysNoticeParam) { + SysNotice sysNotice = this.getById(sysNoticeParam.getId()); + if (ObjectUtil.isNull(sysNotice)) { + throw new ServiceException(SysNoticeExceptionEnum.NOTICE_NOT_EXIST); + } + return sysNotice; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/impl/SysNoticeUserServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/impl/SysNoticeUserServiceImpl.java new file mode 100644 index 0000000..4843485 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/notice/service/impl/SysNoticeUserServiceImpl.java @@ -0,0 +1,89 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.notice.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.sys.modular.notice.entity.SysNoticeUser; +import vip.xiaonuo.sys.modular.notice.mapper.SysNoticeUserMapper; +import vip.xiaonuo.sys.modular.notice.service.SysNoticeUserService; + +import java.util.Date; +import java.util.List; + +/** + * 系统通知公告用户service接口实现类 + * + * @author xuyuxiang + * @date 2020/6/29 10:53 + */ +@Service +public class SysNoticeUserServiceImpl extends ServiceImpl<SysNoticeUserMapper, SysNoticeUser> implements SysNoticeUserService { + + @Override + public void add(Long noticeId, List<Long> noticeUserIdList, Integer noticeUserStatus) { + noticeUserIdList.forEach(userId -> { + SysNoticeUser sysNoticeUser = new SysNoticeUser(); + sysNoticeUser.setNoticeId(noticeId); + sysNoticeUser.setUserId(userId); + sysNoticeUser.setStatus(noticeUserStatus); + this.save(sysNoticeUser); + }); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(Long noticeId, List<Long> noticeUserIdList, Integer noticeUserStatus) { + LambdaQueryWrapper<SysNoticeUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysNoticeUser::getNoticeId, noticeId); + //先删除 + this.remove(queryWrapper); + //再增加 + this.add(noticeId, noticeUserIdList, noticeUserStatus); + } + + @Override + public List<SysNoticeUser> getSysNoticeUserListByNoticeId(Long noticeId) { + LambdaQueryWrapper<SysNoticeUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysNoticeUser::getNoticeId, noticeId); + return this.list(queryWrapper); + } + + @Override + public void read(Long noticeId, Long userId, Integer status) { + LambdaQueryWrapper<SysNoticeUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysNoticeUser::getNoticeId, noticeId) + .eq(SysNoticeUser::getUserId, userId); + SysNoticeUser sysNoticeUser = this.getOne(queryWrapper); + if (ObjectUtil.isNotNull(sysNoticeUser)) { + sysNoticeUser.setStatus(status); + sysNoticeUser.setReadTime(new Date()); + this.updateById(sysNoticeUser); + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/controller/SysOauthController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/controller/SysOauthController.java new file mode 100644 index 0000000..fbcd505 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/controller/SysOauthController.java @@ -0,0 +1,77 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.oauth.controller; + +import me.zhyd.oauth.model.AuthCallback; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.sys.modular.oauth.service.SysOauthService; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Oauth登录控制器 + * + * @author xuyuxiang + * @date 2020/7/28 16:38 + **/ +@RestController +public class SysOauthController { + + @Resource + private SysOauthService sysOauthService; + + /** + * oauth登录 + * + * @author xuyuxiang + * @date 2020/7/29 12:18 + **/ + @GetMapping("/oauth/render/{source}") + public void renderAuth(@PathVariable("source") String source, HttpServletResponse response) throws IOException { + String authorizeUrl = sysOauthService.getAuthorizeUrl(source); + response.sendRedirect(authorizeUrl); + } + + /** + * oauth平台中配置的授权回调地址 + * + * @author xuyuxiang + * @date 2020/7/29 12:19 + **/ + @GetMapping("/oauth/callback/{source}") + public void callback(@PathVariable("source") String source, AuthCallback callback, HttpServletRequest request, HttpServletResponse response) throws IOException { + String token = sysOauthService.callback(source, callback, request); + String webUrl = ConstantContextHolder.getWebUrl(); + response.sendRedirect(webUrl + SymbolConstant.QUESTION_MARK + CommonConstant.TOKEN_NAME + SymbolConstant.EQUAL + token); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/entity/SysOauthUser.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/entity/SysOauthUser.java new file mode 100644 index 0000000..c3c506b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/entity/SysOauthUser.java @@ -0,0 +1,105 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.oauth.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * Oauth登录用户表 + * + * @author xuyuxiang + * @date 2020/7/28 17:04 + **/ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_oauth_user") +public class SysOauthUser extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 第三方平台的用户唯一id + */ + private String uuid; + + /** + * 用户授权的token + */ + private String accessToken; + + /** + * 昵称 + */ + private String nickName; + + /** + * 头像 + */ + private String avatar; + + /** + * 用户网址 + */ + private String blog; + + /** + * 所在公司 + */ + private String company; + + /** + * 位置 + */ + private String location; + + /** + * 邮箱 + */ + private String email; + + /** + * 性别 + */ + private String gender; + + /** + * 用户来源 + */ + private String source; + + /** + * 用户备注(各平台中的用户个人介绍) + */ + private String remark; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/enums/SysOauthExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/enums/SysOauthExceptionEnum.java new file mode 100644 index 0000000..af2f139 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/enums/SysOauthExceptionEnum.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.oauth.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统角色相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/28 14:47 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.OAUTH_EXCEPTION_ENUM) +public enum SysOauthExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * Oauth登录开关未开启 + */ + OAUTH_DISABLED(1, "Oauth登录开关未开启,无法使用Oauth登录"), + + /** + * 不支持该平台Oauth登录 + */ + OAUTH_NOT_SUPPORT(2, "不支持该平台Oauth登录"); + + private final Integer code; + + private final String message; + + SysOauthExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/mapper/SysOauthMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/mapper/SysOauthMapper.java new file mode 100644 index 0000000..1a8103e --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/mapper/SysOauthMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.oauth.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.oauth.entity.SysOauthUser; + +/** + * Oauth登录相关mapper接口 + * + * @author xuyuxiang + * @date 2020/7/28 17:11 + **/ +public interface SysOauthMapper extends BaseMapper<SysOauthUser> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/mapper/mapping/SysOauthMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/mapper/mapping/SysOauthMapper.xml new file mode 100644 index 0000000..b720812 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/mapper/mapping/SysOauthMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.oauth.entity.SysOauthUser"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/service/SysOauthService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/service/SysOauthService.java new file mode 100644 index 0000000..bc3c84b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/service/SysOauthService.java @@ -0,0 +1,62 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.oauth.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import me.zhyd.oauth.model.AuthCallback; +import vip.xiaonuo.sys.modular.oauth.entity.SysOauthUser; + +import javax.servlet.http.HttpServletRequest; + +/** + * Oauth登录相关service接口 + * + * @author xuyuxiang + * @date 2020/7/28 17:06 + **/ +public interface SysOauthService extends IService<SysOauthUser> { + + /** + * 根据授权平台来源获取授权地址 + * + * @param source 授权平台来源 + * @return 授权地址 + * @author xuyuxiang + * @date 2020/7/28 17:26 + **/ + String getAuthorizeUrl(String source); + + /** + * 授权后回调方法 + * + * @param source 授权来源平台 + * @param callback 授权平台返回的用户信息 + * @param request request请求 + * @return 登录成功的token + * @author xuyuxiang + * @date 2020/7/29 9:48 + **/ + String callback(String source, AuthCallback callback, HttpServletRequest request); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/service/impl/SysOauthServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/service/impl/SysOauthServiceImpl.java new file mode 100644 index 0000000..937a190 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/oauth/service/impl/SysOauthServiceImpl.java @@ -0,0 +1,206 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.oauth.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthGiteeRequest; +import me.zhyd.oauth.request.AuthGithubRequest; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.utils.AuthStateUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.pojo.oauth.OauthConfigs; +import vip.xiaonuo.sys.core.cache.OauthCache; +import vip.xiaonuo.sys.core.enums.OauthPlatformEnum; +import vip.xiaonuo.sys.modular.auth.service.AuthService; +import vip.xiaonuo.sys.modular.oauth.entity.SysOauthUser; +import vip.xiaonuo.sys.modular.oauth.enums.SysOauthExceptionEnum; +import vip.xiaonuo.sys.modular.oauth.mapper.SysOauthMapper; +import vip.xiaonuo.sys.modular.oauth.service.SysOauthService; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.service.SysUserService; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + + +/** + * Oauth登录相关service接口实现类 + * + * @author xuyuxiang + * @date 2020/7/28 17:07 + **/ +@Service +public class SysOauthServiceImpl extends ServiceImpl<SysOauthMapper, SysOauthUser> implements SysOauthService { + + @Resource + private OauthCache oauthCache; + + @Resource + private AuthService authService; + + @Resource + private SysUserService sysUserService; + + + @Override + public String getAuthorizeUrl(String source) { + Boolean enableOauthLogin = ConstantContextHolder.getEnableOauthLogin(); + if (!enableOauthLogin) { + throw new ServiceException(SysOauthExceptionEnum.OAUTH_DISABLED); + } + AuthRequest authRequest = this.getAuthRequest(source); + return authRequest.authorize(AuthStateUtils.createState()); + } + + @SuppressWarnings("all") + @Override + public String callback(String source, AuthCallback callback, HttpServletRequest request) { + AuthRequest authRequest = this.getAuthRequest(source); + AuthResponse<AuthUser> response = authRequest.login(callback); + if (response.ok()) { + AuthUser authUser = response.getData(); + return doLogin(authUser); + } else { + throw new ServiceException(response.getCode(), response.getMsg()); + } + } + + /** + * 根据用户授权信息进行登录 + * + * @param authUser 用户授权信息 + * @return token + * @author xuyuxiang + * @date 2020/7/29 9:54 + **/ + @Transactional(rollbackFor = Exception.class) + public String doLogin(AuthUser authUser) { + //获取uuid + String uuid = authUser.getUuid(); + LambdaQueryWrapper<SysOauthUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysOauthUser::getUuid, uuid); + SysOauthUser oauthUser = this.getOne(queryWrapper); + //从没授权登录过 + if (ObjectUtil.isNull(oauthUser)) { + //将授权的用户信息保存到sys_oauth_user表和sys_user表 + this.saveByAuthUser(authUser); + //再获取oauthUser用户 + oauthUser = this.getOne(queryWrapper); + } + //获取用户账户信息进行登录 + Long userId = oauthUser.getId(); + SysUser sysUser = sysUserService.getUserById(userId); + return authService.doLogin(sysUser); + } + + /** + * 将授权的用户信息保存到sys_oauth_user表和sys_user表 + * + * @param authUser 用户授权信息 + * @return void + * @author xuyuxiang + * @date 2020/7/29 10:16 + **/ + @Transactional(rollbackFor = Exception.class) + public void saveByAuthUser(AuthUser authUser) { + //生成用户id + long userId = IdWorker.getId(); + //创建oauthUser对象 + SysOauthUser oauthUser = new SysOauthUser(); + oauthUser.setId(userId); + this.fillOauthUserInfo(oauthUser, authUser); + //创建sysUser对象 + SysUser sysUser = new SysUser(); + sysUser.setId(userId); + //将授权的用户信息保存到user表 + sysUserService.saveAuthUserToUser(authUser, sysUser); + this.save(oauthUser); + } + + /** + * 根据具体的授权来源,获取授权请求 + * + * @param source 授权平台来源 + * @return 授权请求 + * @author xuyuxiang + * @date 2020/7/28 17:28 + **/ + private AuthRequest getAuthRequest(String source) { + AuthRequest authRequest; + if (source.toLowerCase().equals(OauthPlatformEnum.GITEE.getCode())) { + OauthConfigs giteeOauthConfigs = ConstantContextHolder.getGiteeOauthConfigs(); + authRequest = new AuthGiteeRequest(AuthConfig.builder() + .clientId(giteeOauthConfigs.getClientId()) + .clientSecret(giteeOauthConfigs.getClientSecret()) + .redirectUri(giteeOauthConfigs.getRedirectUri()) + .build(), oauthCache); + } else if (source.toLowerCase().equals(OauthPlatformEnum.GITHUB.getCode())) { + OauthConfigs githubOauthConfigs = ConstantContextHolder.getGithubOauthConfigs(); + authRequest = new AuthGithubRequest(AuthConfig.builder() + .clientId(githubOauthConfigs.getClientId()) + .clientSecret(githubOauthConfigs.getClientSecret()) + .redirectUri(githubOauthConfigs.getRedirectUri()) + .build(), oauthCache); + } else { + throw new ServiceException(SysOauthExceptionEnum.OAUTH_NOT_SUPPORT); + } + return authRequest; + } + + /** + * 将授权用户信息填充到oauthUser + * + * @param oauthUser 系统授权用户信息 + * @param authUser 平台授权用户信息 + * @return void + * @author xuyuxiang + * @date 2020/7/29 10:42 + **/ + private void fillOauthUserInfo(SysOauthUser oauthUser, AuthUser authUser) { + oauthUser.setUuid(authUser.getUuid()); + oauthUser.setAccessToken(authUser.getToken().getAccessToken()); + oauthUser.setNickName(authUser.getNickname()); + oauthUser.setAvatar(authUser.getAvatar()); + oauthUser.setBlog(authUser.getBlog()); + oauthUser.setCompany(authUser.getCompany()); + oauthUser.setLocation(authUser.getLocation()); + oauthUser.setEmail(authUser.getEmail()); + oauthUser.setSource(authUser.getSource()); + oauthUser.setRemark(authUser.getRemark()); + if (ObjectUtil.isNotNull(authUser.getGender())) { + oauthUser.setGender(authUser.getGender().getDesc()); + } + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/controller/SysOrgController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/controller/SysOrgController.java new file mode 100644 index 0000000..f87c196 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/controller/SysOrgController.java @@ -0,0 +1,169 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.org.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.DataScope; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.base.param.BaseParam; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.org.param.SysOrgParam; +import vip.xiaonuo.sys.modular.org.service.SysOrgService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 系统组织机构控制器 + * + * @author xuyuxiang + * @date 2020/3/20 19:47 + */ +@RestController +public class SysOrgController { + + @Resource + private SysOrgService sysOrgService; + + /** + * 查询系统机构 + * + * @author xuyuxiang + * @date 2020/5/11 15:49 + */ + @Permission + @DataScope + @GetMapping("/sysOrg/page") + @BusinessLog(title = "系统机构_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysOrgParam sysOrgParam) { + return new SuccessResponseData(sysOrgService.page(sysOrgParam)); + } + + /** + * 系统组织机构列表 + * + * @author xuyuxiang + * @date 2020/3/26 10:20 + */ + @Permission + @DataScope + @GetMapping("/sysOrg/list") + @BusinessLog(title = "系统组织机构_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysOrgParam sysOrgParam) { + return new SuccessResponseData(sysOrgService.list(sysOrgParam)); + } + + /** + * 添加系统组织机构 + * + * @author xuyuxiang + * @date 2020/3/25 14:44 + */ + @Permission + @DataScope + @PostMapping("/sysOrg/add") + @BusinessLog(title = "系统组织机构_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(BaseParam.add.class) SysOrgParam sysOrgParam) { + sysOrgService.add(sysOrgParam); + return new SuccessResponseData(); + } + + /** + * 删除系统组织机构 + * + * @author xuyuxiang + * @date 2020/3/25 14:54 + */ + @Permission + @DataScope + @PostMapping("/sysOrg/delete") + @BusinessLog(title = "系统组织机构_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(BaseParam.delete.class) List<SysOrgParam> sysOrgParamList) { + sysOrgService.delete(sysOrgParamList); + return new SuccessResponseData(); + } + + /** + * 编辑系统组织机构 + * + * @author xuyuxiang + * @date 2020/3/25 14:54 + */ + @Permission + @DataScope + @PostMapping("/sysOrg/edit") + @BusinessLog(title = "系统组织机构_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(BaseParam.edit.class) SysOrgParam sysOrgParam) { + sysOrgService.edit(sysOrgParam); + return new SuccessResponseData(); + } + + /** + * 查看系统组织机构 + * + * @author xuyuxiang + * @date 2020/3/26 9:49 + */ + @Permission + @GetMapping("/sysOrg/detail") + @BusinessLog(title = "系统组织机构_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(BaseParam.detail.class) SysOrgParam sysOrgParam) { + return new SuccessResponseData(sysOrgService.detail(sysOrgParam)); + } + + /** + * 获取组织机构树 + * + * @author xuyuxiang + * @date 2020/3/26 11:55 + */ + @Permission + @DataScope + @GetMapping("/sysOrg/tree") + @BusinessLog(title = "系统组织机构_树", opType = LogAnnotionOpTypeEnum.TREE) + public ResponseData tree(SysOrgParam sysOrgParam) { + return new SuccessResponseData(sysOrgService.tree(sysOrgParam)); + } + + /** + * 导出系统组织机构 + * + * @author yubaoshan + * @date 2021/5/30 12:48 + */ + @Permission + @GetMapping("/sysOrg/export") + @BusinessLog(title = "系统组织机构_导出", opType = LogAnnotionOpTypeEnum.EXPORT) + public void export(SysOrgParam sysOrgParam) { + sysOrgService.export(sysOrgParam); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/entity/SysOrg.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/entity/SysOrg.java new file mode 100644 index 0000000..26e03d3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/entity/SysOrg.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.org.entity; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 系统组织机构表 + * + * @author xuyuxiang + * @date 2020/3/11 11:20 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_org") +public class SysOrg extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 父id + */ + private Long pid; + + /** + * 父ids + */ + private String pids; + + /** + * 名称 + */ + @Excel(name = "名称", width = 20) + private String name; + + /** + * 编码 + */ + @Excel(name = "编码", width = 20) + private String code; + + /** + * 排序 + */ + @Excel(name = "排序", width = 20) + private Integer sort; + + /** + * 备注 + */ + @Excel(name = "备注", width = 20) + @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + private String remark; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + @Excel(name = "状态", replace = {"正常_0", "停用_1", "删除_2"}, width = 20) + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/enums/SysOrgExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/enums/SysOrgExceptionEnum.java new file mode 100644 index 0000000..3e27a59 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/enums/SysOrgExceptionEnum.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.org.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统组织机构相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/26 10:12 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_ORG_EXCEPTION_ENUM) +public enum SysOrgExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 组织机构不存在 + */ + ORG_NOT_EXIST(1, "组织机构不存在"), + + /** + * 组织机构编码重复 + */ + ORG_CODE_REPEAT(2, "组织机构编码重复,请检查code参数"), + + /** + * 组织机构名称重复 + */ + ORG_NAME_REPEAT(3, "组织机构名称重复,请检查name参数"), + + /** + * 该机构下有员工 + */ + ORG_CANNOT_DELETE(4, "该机构或子机构下有员工,无法删除"), + + /** + * 父节点不能和本节点一致,请重新选择父节点 + */ + ID_CANT_EQ_PID(5, "父节点不能和本节点一致,请重新选择父节点"), + + /** + * 父节点不能为本节点的子节点,请重新选择父节点 + */ + PID_CANT_EQ_CHILD_ID(6, "父节点不能为本节点的子节点,请重新选择父节点"); + + private final Integer code; + + private final String message; + + SysOrgExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/mapper/SysOrgMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/mapper/SysOrgMapper.java new file mode 100644 index 0000000..5dc02d1 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/mapper/SysOrgMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.org.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.org.entity.SysOrg; + +/** + * 系统组织机构mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:03 + */ +public interface SysOrgMapper extends BaseMapper<SysOrg> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/mapper/mapping/SysOrgMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/mapper/mapping/SysOrgMapper.xml new file mode 100644 index 0000000..6a4cf1b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/mapper/mapping/SysOrgMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.org.mapper.SysOrgMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/param/SysOrgParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/param/SysOrgParam.java new file mode 100644 index 0000000..3cd68d8 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/param/SysOrgParam.java @@ -0,0 +1,78 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.org.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统组织机构参数 + * + * @author xuyuxiang + * @date 2020/3/26 10:10 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysOrgParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 父id + */ + @NotNull(message = "pid不能为空,请检查pid参数", groups = {add.class, edit.class}) + private Long pid; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + private String code; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空,请检查sort参数", groups = {add.class, edit.class}) + private Integer sort; + + /** + * 备注 + */ + private String remark; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/service/SysOrgService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/service/SysOrgService.java new file mode 100644 index 0000000..ef6b4f8 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/service/SysOrgService.java @@ -0,0 +1,128 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.org.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.node.AntdBaseTreeNode; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.org.entity.SysOrg; +import vip.xiaonuo.sys.modular.org.param.SysOrgParam; + +import java.util.List; + +/** + * 系统组织机构service接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:02 + */ +public interface SysOrgService extends IService<SysOrg> { + + /** + * 查询系统机构 + * + * @param sysOrgParam 查询参数 + * @return 查询分页结果 + * @author yubaoshan + * @date 2020/5/11 15:49 + */ + PageResult<SysOrg> page(SysOrgParam sysOrgParam); + + /** + * 系统组织机构列表 + * + * @param sysOrgParam 查询参数 + * @return 组织机构列表 + * @author xuyuxiang + * @date 2020/3/26 10:19 + */ + List<SysOrg> list(SysOrgParam sysOrgParam); + + /** + * 添加系统组织机构 + * + * @param sysOrgParam 添加参数 + * @author xuyuxiang + * @date 2020/3/25 14:57 + */ + void add(SysOrgParam sysOrgParam); + + /** + * 删除系统组织机构 + * + * @param sysOrgParamList 删除参数集合 + * @author xuyuxiang + * @date 2020/3/25 14:57 + */ + void delete(List<SysOrgParam> sysOrgParamList); + + /** + * 编辑系统组织机构 + * + * @param sysOrgParam 编辑参数 + * @author xuyuxiang + * @date 2020/3/25 14:58 + */ + void edit(SysOrgParam sysOrgParam); + + /** + * 查看系统组织机构 + * + * @param sysOrgParam 查看参数 + * @return 组织机构 + * @author xuyuxiang + * @date 2020/3/26 9:50 + */ + SysOrg detail(SysOrgParam sysOrgParam); + + /** + * 获取系统组织机构树 + * + * @param sysOrgParam 查询参数 + * @return 系统组织机构树 + * @author xuyuxiang yubaoshan + * @date 2020/3/26 11:56 + */ + List<AntdBaseTreeNode> tree(SysOrgParam sysOrgParam); + + /** + * 根据数据范围类型获取当前登录用户的数据范围id集合 + * + * @param dataScopeType 数据范围类型(1全部数据 2本部门及以下数据 3本部门数据 4仅本人数据) + * @param orgId 组织机构id + * @return 数据范围id集合 + * @author xuyuxiang + * @date 2020/4/5 18:29 + */ + List<Long> getDataScopeListByDataScopeType(Integer dataScopeType, Long orgId); + + /** + * 导出机构数据 + * + * @author yubaoshan + * @date 2021/5/30 12:48 + */ + void export(SysOrgParam sysOrgParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/service/impl/SysOrgServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/service/impl/SysOrgServiceImpl.java new file mode 100644 index 0000000..9bf7b35 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/org/service/impl/SysOrgServiceImpl.java @@ -0,0 +1,546 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.org.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.PermissionException; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.PermissionExceptionEnum; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.factory.TreeBuildFactory; +import vip.xiaonuo.core.pojo.node.AntdBaseTreeNode; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.PoiUtil; +import vip.xiaonuo.sys.core.enums.DataScopeTypeEnum; +import vip.xiaonuo.sys.modular.auth.service.AuthService; +import vip.xiaonuo.sys.modular.emp.service.SysEmpExtOrgPosService; +import vip.xiaonuo.sys.modular.emp.service.SysEmpService; +import vip.xiaonuo.sys.modular.org.entity.SysOrg; +import vip.xiaonuo.sys.modular.org.enums.SysOrgExceptionEnum; +import vip.xiaonuo.sys.modular.org.mapper.SysOrgMapper; +import vip.xiaonuo.sys.modular.org.param.SysOrgParam; +import vip.xiaonuo.sys.modular.org.service.SysOrgService; +import vip.xiaonuo.sys.modular.role.service.SysRoleDataScopeService; +import vip.xiaonuo.sys.modular.user.service.SysUserDataScopeService; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Set; + +/** + * 系统组织机构service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 16:02 + */ +@Service +public class SysOrgServiceImpl extends ServiceImpl<SysOrgMapper, SysOrg> implements SysOrgService { + + @Resource + private SysEmpService sysEmpService; + + @Resource + private SysEmpExtOrgPosService sysEmpExtOrgPosService; + + @Resource + private SysRoleDataScopeService sysRoleDataScopeService; + + @Resource + private SysUserDataScopeService sysUserDataScopeService; + + @Resource + private AuthService authService; + + @Override + public PageResult<SysOrg> page(SysOrgParam sysOrgParam) { + LambdaQueryWrapper<SysOrg> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysOrgParam)) { + + // 根据机构名称模糊查询 + if (ObjectUtil.isNotEmpty(sysOrgParam.getName())) { + queryWrapper.like(SysOrg::getName, sysOrgParam.getName()); + } + + // 根据机构id查询 + if (ObjectUtil.isNotEmpty(sysOrgParam.getId())) { + queryWrapper.eq(SysOrg::getId, sysOrgParam.getId()); + } + + // 根据父机构id查询 + if (ObjectUtil.isNotEmpty(sysOrgParam.getPid())) { + queryWrapper + .eq(SysOrg::getId, sysOrgParam.getPid()) + .or() + .like(SysOrg::getPids, sysOrgParam.getPid()); + } + } + + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + + // 如果是超级管理员则获取所有组织机构,否则只获取其数据范围的机构数据 + if (!superAdmin) { + List<Long> dataScope = sysOrgParam.getDataScope(); + if (ObjectUtil.isEmpty(dataScope)) { + return new PageResult<>(new Page<>()); + } else { + Set<Long> dataScopeSet = CollectionUtil.newHashSet(dataScope); + dataScope.forEach(orgId -> { + //此处获取所有的上级节点,放入set,用于构造完整树 + List<Long> parentAndChildIdListWithSelf = this.getParentIdListById(orgId); + dataScopeSet.addAll(parentAndChildIdListWithSelf); + }); + queryWrapper.in(SysOrg::getId, dataScopeSet); + } + } + + // 查询启用状态的 + queryWrapper.eq(SysOrg::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysOrg::getSort); + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<SysOrg> list(SysOrgParam sysOrgParam) { + LambdaQueryWrapper<SysOrg> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysOrgParam)) { + //根据父机构id查询 + if (ObjectUtil.isNotEmpty(sysOrgParam.getPid())) { + queryWrapper.eq(SysOrg::getPid, sysOrgParam.getPid()); + } + } + //如果是超级管理员则获取所有组织机构,否则只获取其数据范围的机构数据 + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + if (!superAdmin) { + List<Long> dataScope = sysOrgParam.getDataScope(); + if (ObjectUtil.isEmpty(dataScope)) { + return CollectionUtil.newArrayList(); + } else { + Set<Long> dataScopeSet = CollectionUtil.newHashSet(dataScope); + dataScope.forEach(orgId -> { + //此处获取所有的上级节点,放入set,用于构造完整树 + List<Long> parentAndChildIdListWithSelf = this.getParentIdListById(orgId); + dataScopeSet.addAll(parentAndChildIdListWithSelf); + }); + queryWrapper.in(SysOrg::getId, dataScopeSet); + } + } + queryWrapper.eq(SysOrg::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysOrg::getSort); + return this.list(queryWrapper); + } + + @Override + public void add(SysOrgParam sysOrgParam) { + //校验参数,检查是否存在相同的名称和编码 + checkParam(sysOrgParam, false); + //获取父id + Long pid = sysOrgParam.getPid(); + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + //如果登录用户不是超级管理员 + if (!superAdmin) { + //如果新增的机构父id不是0,则进行数据权限校验 + if (!pid.equals(0L)) { + List<Long> dataScope = sysOrgParam.getDataScope(); + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } else if (!dataScope.contains(pid)) { + //所添加的组织机构的父机构不在自己的数据范围内 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } else { + //如果新增的机构父id是0,则根本没权限,只有超级管理员能添加父id为0的节点 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + + SysOrg sysOrg = new SysOrg(); + BeanUtil.copyProperties(sysOrgParam, sysOrg); + this.fillPids(sysOrg); + sysOrg.setStatus(CommonStatusEnum.ENABLE.getCode()); + this.save(sysOrg); + this.authService.refreshUserDataScope(sysOrg.getId()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(List<SysOrgParam> sysOrgParamList) { + sysOrgParamList.forEach(sysOrgParam -> { + SysOrg sysOrg = this.querySysOrg(sysOrgParam); + Long id = sysOrg.getId(); + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + + // 级联删除子节点 + List<Long> childIdList = this.getChildIdListById(id); + childIdList.add(id); + + childIdList.forEach(item ->{ + if (!superAdmin) { + List<Long> dataScope = LoginContextHolder.me().getLoginUserDataScopeIdList(); + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } else if (!dataScope.contains(item)) { + //所操作的数据不在自己的数据范围内 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + // 该机构下有员工,则不能删 + boolean hasOrgEmp = sysEmpService.hasOrgEmp(item); + if (hasOrgEmp) { + throw new ServiceException(SysOrgExceptionEnum.ORG_CANNOT_DELETE); + } + + // 该附属机构下若有员工,则不能删除 + boolean hasExtOrgEmp = sysEmpExtOrgPosService.hasExtOrgEmp(item); + if (hasExtOrgEmp) { + throw new ServiceException(SysOrgExceptionEnum.ORG_CANNOT_DELETE); + } + }); + + LambdaUpdateWrapper<SysOrg> updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.in(SysOrg::getId, childIdList) + .set(SysOrg::getStatus, CommonStatusEnum.DELETED.getCode()); + this.update(updateWrapper); + + // 级联删除该机构及子机构对应的角色-数据范围关联信息 + sysRoleDataScopeService.deleteRoleDataScopeListByOrgIdList(childIdList); + + // 级联删除该机构子机构对应的用户-数据范围关联信息 + sysUserDataScopeService.deleteUserDataScopeListByOrgIdList(childIdList); + }); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(SysOrgParam sysOrgParam) { + + SysOrg sysOrg = this.querySysOrg(sysOrgParam); + Long id = sysOrg.getId(); + + // 检测此人数据范围能不能操作这个公司 + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + if (!superAdmin) { + List<Long> dataScope = sysOrgParam.getDataScope(); + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + //数据范围中不包含本公司 + else if (!dataScope.contains(id)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + + //校验参数,检查是否存在相同的名称和编码 + checkParam(sysOrgParam, true); + + //如果名称有变化,则修改对应员工的机构相关信息 + if (!sysOrg.getName().equals(sysOrgParam.getName())) { + sysEmpService.updateEmpOrgInfo(sysOrg.getId(), sysOrg.getName()); + } + + BeanUtil.copyProperties(sysOrgParam, sysOrg); + this.fillPids(sysOrg); + + //不能修改状态,用修改状态接口修改状态 + sysOrg.setStatus(null); + this.updateById(sysOrg); + //将所有子的父id进行更新 + List<Long> childIdListById = this.getChildIdListById(sysOrg.getId()); + childIdListById.forEach(subChildId -> { + SysOrg child = this.getById(subChildId); + SysOrgParam childParam = new SysOrgParam(); + BeanUtil.copyProperties(child, childParam); + this.edit(childParam); + }); + } + + @Override + public SysOrg detail(SysOrgParam sysOrgParam) { + return this.querySysOrg(sysOrgParam); + } + + @Override + public List<AntdBaseTreeNode> tree(SysOrgParam sysOrgParam) { + List<AntdBaseTreeNode> treeNodeList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysOrg> queryWrapper = new LambdaQueryWrapper<>(); + + // 如果是超级管理员则获取所有组织机构,否则只获取其数据范围的机构数据 + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + if (!superAdmin) { + List<Long> dataScope = sysOrgParam.getDataScope(); + if (ObjectUtil.isEmpty(dataScope)) { + return treeNodeList; + } else { + Set<Long> dataScopeSet = CollectionUtil.newHashSet(dataScope); + dataScope.forEach(orgId -> { + //此处获取所有的上级节点,放入set,用于构造完整树 + List<Long> parentAndChildIdListWithSelf = this.getParentIdListById(orgId); + dataScopeSet.addAll(parentAndChildIdListWithSelf); + }); + queryWrapper.in(SysOrg::getId, dataScopeSet); + } + } + + // 只查询未删除的 + queryWrapper.eq(SysOrg::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysOrg::getSort); + this.list(queryWrapper).forEach(sysOrg -> { + AntdBaseTreeNode orgTreeNode = new AntdBaseTreeNode(); + orgTreeNode.setId(sysOrg.getId()); + orgTreeNode.setParentId(sysOrg.getPid()); + orgTreeNode.setTitle(sysOrg.getName()); + orgTreeNode.setValue(String.valueOf(sysOrg.getId())); + orgTreeNode.setWeight(sysOrg.getSort()); + treeNodeList.add(orgTreeNode); + }); + + return new TreeBuildFactory<AntdBaseTreeNode>().doTreeBuild(treeNodeList); + } + + @Override + public List<Long> getDataScopeListByDataScopeType(Integer dataScopeType, Long orgId) { + List<Long> resultList = CollectionUtil.newArrayList(); + + if (ObjectUtil.isEmpty(orgId)) { + return CollectionUtil.newArrayList(); + } + + // 如果是范围类型是全部数据,则获取当前系统所有的组织架构id + if (DataScopeTypeEnum.ALL.getCode().equals(dataScopeType)) { + resultList = this.getOrgIdAll(); + } + // 如果范围类型是本部门及以下部门,则查询本节点和子节点集合,包含本节点 + else if (DataScopeTypeEnum.DEPT_WITH_CHILD.getCode().equals(dataScopeType)) { + resultList = this.getChildIdListWithSelfById(orgId); + } + // 如果数据范围是本部门,不含子节点,则直接返回本部门 + else if (DataScopeTypeEnum.DEPT.getCode().equals(dataScopeType)) { + resultList.add(orgId); + } + + return resultList; + } + + /** + * 根据条件获取组织机构id集合 + * + * @author xuyuxiang + * @date 2020/4/5 18:35 + */ + private List<Long> getOrgIdAll() { + List<Long> resultList = CollectionUtil.newArrayList(); + + LambdaQueryWrapper<SysOrg> queryWrapper = new LambdaQueryWrapper<>(); + + queryWrapper.eq(SysOrg::getStatus, CommonStatusEnum.ENABLE.getCode()); + + this.list(queryWrapper).forEach(sysOrg -> resultList.add(sysOrg.getId())); + return resultList; + } + + /** + * 校验参数,检查是否存在相同的名称和编码 + * + * @author xuyuxiang + * @date 2020/3/25 21:23 + */ + private void checkParam(SysOrgParam sysOrgParam, boolean isExcludeSelf) { + Long id = sysOrgParam.getId(); + String name = sysOrgParam.getName(); + String code = sysOrgParam.getCode(); + Long pid = sysOrgParam.getPid(); + + //如果父id不是根节点 + if (!pid.equals(0L)) { + SysOrg pOrg = this.getById(pid); + if (ObjectUtil.isNull(pOrg)) { + //父机构不存在 + throw new ServiceException(SysOrgExceptionEnum.ORG_NOT_EXIST); + } + } + + // 如果是编辑,父id和自己的id不能一致 + if (isExcludeSelf) { + if (sysOrgParam.getId().equals(sysOrgParam.getPid())) { + throw new ServiceException(SysOrgExceptionEnum.ID_CANT_EQ_PID); + } + + // 如果是编辑,父id不能为自己的子节点 + List<Long> childIdListById = this.getChildIdListById(sysOrgParam.getId()); + if(ObjectUtil.isNotEmpty(childIdListById)) { + if(childIdListById.contains(sysOrgParam.getPid())) { + throw new ServiceException(SysOrgExceptionEnum.PID_CANT_EQ_CHILD_ID); + } + } + } + + LambdaQueryWrapper<SysOrg> queryWrapperByName = new LambdaQueryWrapper<>(); + queryWrapperByName.eq(SysOrg::getName, name) + .ne(SysOrg::getStatus, CommonStatusEnum.DELETED.getCode()); + + LambdaQueryWrapper<SysOrg> queryWrapperByCode = new LambdaQueryWrapper<>(); + queryWrapperByCode.eq(SysOrg::getCode, code) + .ne(SysOrg::getStatus, CommonStatusEnum.DELETED.getCode()); + + if (isExcludeSelf) { + queryWrapperByName.ne(SysOrg::getId, id); + queryWrapperByCode.ne(SysOrg::getId, id); + } + int countByName = this.count(queryWrapperByName); + int countByCode = this.count(queryWrapperByCode); + + if (countByName >= 1) { + throw new ServiceException(SysOrgExceptionEnum.ORG_NAME_REPEAT); + } + if (countByCode >= 1) { + throw new ServiceException(SysOrgExceptionEnum.ORG_CODE_REPEAT); + } + } + + /** + * 获取系统组织机构 + * + * @author xuyuxiang + * @date 2020/3/26 9:56 + */ + private SysOrg querySysOrg(SysOrgParam sysOrgParam) { + SysOrg sysOrg = this.getById(sysOrgParam.getId()); + if (ObjectUtil.isNull(sysOrg)) { + throw new ServiceException(SysOrgExceptionEnum.ORG_NOT_EXIST); + } + return sysOrg; + } + + /** + * 填充父ids + * + * @author xuyuxiang + * @date 2020/3/26 11:28 + */ + private void fillPids(SysOrg sysOrg) { + if (sysOrg.getPid().equals(0L)) { + sysOrg.setPids(SymbolConstant.LEFT_SQUARE_BRACKETS + + 0 + + SymbolConstant.RIGHT_SQUARE_BRACKETS + + SymbolConstant.COMMA); + } else { + //获取父组织机构 + SysOrg pSysOrg = this.getById(sysOrg.getPid()); + sysOrg.setPids(pSysOrg.getPids() + + SymbolConstant.LEFT_SQUARE_BRACKETS + pSysOrg.getId() + + SymbolConstant.RIGHT_SQUARE_BRACKETS + + SymbolConstant.COMMA); + } + } + + /** + * 根据节点id获取所有子节点id集合 + * + * @author xuyuxiang + * @date 2020/3/26 11:31 + */ + private List<Long> getChildIdListById(Long id) { + List<Long> childIdList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysOrg> queryWrapper = new LambdaQueryWrapper<>(); + + queryWrapper.like(SysOrg::getPids, SymbolConstant.LEFT_SQUARE_BRACKETS + id + + SymbolConstant.RIGHT_SQUARE_BRACKETS); + + this.list(queryWrapper).forEach(sysOrg -> childIdList.add(sysOrg.getId())); + + return childIdList; + } + + /** + * 根据节点id获取所有父节点id集合,不包含自己 + * + * @author xuyuxiang + * @date 2020/4/6 14:53 + */ + private List<Long> getParentIdListById(Long id) { + List<Long> resultList = CollectionUtil.newArrayList(); + SysOrg sysOrg = this.getById(id); + String pids = sysOrg.getPids(); + String pidsWithRightSymbol = StrUtil.removeAll(pids, SymbolConstant.LEFT_SQUARE_BRACKETS); + String pidsNormal = StrUtil.removeAll(pidsWithRightSymbol, SymbolConstant.RIGHT_SQUARE_BRACKETS); + String[] pidsNormalArr = pidsNormal.split(SymbolConstant.COMMA); + for (String pid : pidsNormalArr) { + resultList.add(Convert.toLong(pid)); + } + return resultList; + } + + /** + * 根据节点id获取所有子节点id集合,包含自己 + * + * @author xuyuxiang + * @date 2020/4/6 14:54 + */ + private List<Long> getChildIdListWithSelfById(Long id) { + List<Long> childIdListById = this.getChildIdListById(id); + List<Long> resultList = CollectionUtil.newArrayList(childIdListById); + resultList.add(id); + return resultList; + } + + /** + * 根据节点id获取父节点和子节点id集合,包含自己 + * + * @author xuyuxiang + * @date 2020/4/7 16:50 + */ + private List<Long> getParentAndChildIdListWithSelfById(Long id) { + Set<Long> resultSet = CollectionUtil.newHashSet(); + List<Long> parentIdListById = this.getParentIdListById(id); + List<Long> childIdListById = this.getChildIdListWithSelfById(id); + resultSet.addAll(parentIdListById); + resultSet.addAll(childIdListById); + return CollectionUtil.newArrayList(resultSet); + } + + @Override + public void export(SysOrgParam sysOrgParam) { + List<SysOrg> list = this.list(sysOrgParam); + PoiUtil.exportExcelWithStream("SysOrg.xls", SysOrg.class, list); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/controller/SysPosController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/controller/SysPosController.java new file mode 100644 index 0000000..044689c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/controller/SysPosController.java @@ -0,0 +1,148 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.pos.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.pos.param.SysPosParam; +import vip.xiaonuo.sys.modular.pos.service.SysPosService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 系统职位控制器 + * + * @author xuyuxiang + * @date 2020/3/20 19:44 + */ +@RestController +public class SysPosController { + + @Resource + private SysPosService sysPosService; + + /** + * 查询系统职位 + * + * @author xuyuxiang + * @date 2020/3/26 10:20 + */ + @Permission + @GetMapping("/sysPos/page") + @BusinessLog(title = "系统职位_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysPosParam sysPosParam) { + return new SuccessResponseData(sysPosService.page(sysPosParam)); + } + + /** + * 系统职位列表 + * + * @author yubaoshan + * @date 2020/6/21 23:38 + */ + @Permission + @GetMapping("/sysPos/list") + @BusinessLog(title = "系统职位_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysPosParam sysPosParam) { + return new SuccessResponseData(sysPosService.list(sysPosParam)); + } + + /** + * 添加系统职位 + * + * @author xuyuxiang + * @date 2020/3/26 19:03 + */ + @Permission + @PostMapping("/sysPos/add") + @BusinessLog(title = "系统职位_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysPosParam.add.class) SysPosParam sysPosParam) { + sysPosService.add(sysPosParam); + return new SuccessResponseData(); + } + + /** + * 删除系统职位 + * + * @author xuyuxiang + * @date 2020/3/25 14:54 + */ + @Permission + @PostMapping("/sysPos/delete") + @BusinessLog(title = "系统职位_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysPosParam.delete.class) List<SysPosParam> sysPosParamList) { + sysPosService.delete(sysPosParamList); + return new SuccessResponseData(); + } + + /** + * 编辑系统职位 + * + * @author xuyuxiang + * @date 2020/3/25 14:54 + */ + @Permission + @PostMapping("/sysPos/edit") + @BusinessLog(title = "系统职位_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysPosParam.edit.class) SysPosParam sysPosParam) { + sysPosService.edit(sysPosParam); + return new SuccessResponseData(); + } + + /** + * 查看系统职位 + * + * @author xuyuxiang + * @date 2020/3/26 9:49 + */ + @Permission + @GetMapping("/sysPos/detail") + @BusinessLog(title = "系统职位_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysPosParam.detail.class) SysPosParam sysPosParam) { + return new SuccessResponseData(sysPosService.detail(sysPosParam)); + } + + /** + * 导出系统用户 + * + * @author yubaoshan + * @date 2020/6/30 16:07 + */ + @Permission + @GetMapping("/sysPos/export") + @BusinessLog(title = "系统职位_导出", opType = LogAnnotionOpTypeEnum.EXPORT) + public void export(SysPosParam sysPosParam) { + sysPosService.export(sysPosParam); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/entity/SysPos.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/entity/SysPos.java new file mode 100644 index 0000000..c1ea075 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/entity/SysPos.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.pos.entity; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 系统职位表 + * + * @author xuyuxiang + * @date 2020/3/11 11:20 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_pos") +public class SysPos extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + @Excel(name = "名称", width = 20) + private String name; + + /** + * 编码 + */ + @Excel(name = "编码", width = 20) + private String code; + + /** + * 排序 + */ + @Excel(name = "排序", width = 20) + private Integer sort; + + /** + * 备注 + */ + @Excel(name = "备注", width = 20) + @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + private String remark; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + @Excel(name = "状态", replace = {"正常_0", "停用_1", "删除_2"}, width = 20) + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/enums/SysPosExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/enums/SysPosExceptionEnum.java new file mode 100644 index 0000000..f587c57 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/enums/SysPosExceptionEnum.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.pos.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统职位相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/26 10:12 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_POS_EXCEPTION_ENUM) +public enum SysPosExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 职位不存在 + */ + POS_NOT_EXIST(1, "职位不存在"), + + /** + * 职位编码重复 + */ + POS_CODE_REPEAT(2, "职位编码重复,请检查code参数"), + + /** + * 职位名称重复 + */ + POS_NAME_REPEAT(3, "职位名称重复,请检查name参数"), + + /** + * 该职位下有员工 + */ + POS_CANNOT_DELETE(4, "该职位下有员工,无法删除"); + + private final Integer code; + + private final String message; + + SysPosExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/mapper/SysPosMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/mapper/SysPosMapper.java new file mode 100644 index 0000000..33111f0 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/mapper/SysPosMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.pos.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.pos.entity.SysPos; + +/** + * 系统职位mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 16:00 + */ +public interface SysPosMapper extends BaseMapper<SysPos> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/mapper/mapping/SysPosMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/mapper/mapping/SysPosMapper.xml new file mode 100644 index 0000000..7f3f2e8 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/mapper/mapping/SysPosMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.pos.mapper.SysPosMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/param/SysPosParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/param/SysPosParam.java new file mode 100644 index 0000000..9a5bdc6 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/param/SysPosParam.java @@ -0,0 +1,77 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.pos.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 系统职位参数 + * + * @author xuyuxiang + * @date 2020/3/26 19:02 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysPosParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, export.class}) + private Long id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + private String code; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空,请检查sort参数", groups = {add.class, edit.class}) + private Integer sort; + + /** + * 备注 + */ + private String remark; + + /** + * 状态 + */ + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/service/SysPosService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/service/SysPosService.java new file mode 100644 index 0000000..6262bba --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/service/SysPosService.java @@ -0,0 +1,105 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.pos.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.pos.entity.SysPos; +import vip.xiaonuo.sys.modular.pos.param.SysPosParam; + +import java.util.List; + +/** + * 系统职位service接口 + * + * @author xuyuxiang yubaoshan + * @date 2020/3/13 16:00 + */ +public interface SysPosService extends IService<SysPos> { + + /** + * 查询系统职位 + * + * @param sysPosParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/3/26 10:19 + */ + PageResult<SysPos> page(SysPosParam sysPosParam); + + /** + * 系统职位列表 + * + * @param sysPosParam 查询参数 + * @return 职位列表 + * @author yubaoshan + * @date 2020/6/21 23:44 + */ + List<SysPos> list(SysPosParam sysPosParam); + + /** + * 添加系统职位 + * + * @param sysPosParam 添加参数 + * @author xuyuxiang + * @date 2020/3/25 14:57 + */ + void add(SysPosParam sysPosParam); + + /** + * 删除系统职位 + * + * @param sysPosParamList 删除参数集合 + * @author xuyuxiang + * @date 2020/3/25 14:57 + */ + void delete(List<SysPosParam> sysPosParamList); + + /** + * 编辑系统职位 + * + * @param sysPosParam 编辑参数 + * @author xuyuxiang + * @date 2020/3/25 14:58 + */ + void edit(SysPosParam sysPosParam); + + /** + * 查看系统职位 + * + * @param sysPosParam 查看参数 + * @return 系统职位 + * @author xuyuxiang + * @date 2020/3/26 9:50 + */ + SysPos detail(SysPosParam sysPosParam); + + /** + * 导出系统职位 + * @author yubaoshan + * @date 2021/5/29 16:12 + */ + void export(SysPosParam sysPosParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/service/impl/SysPosServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/service/impl/SysPosServiceImpl.java new file mode 100644 index 0000000..86827c3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/pos/service/impl/SysPosServiceImpl.java @@ -0,0 +1,202 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.pos.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.PoiUtil; +import vip.xiaonuo.sys.modular.emp.service.SysEmpExtOrgPosService; +import vip.xiaonuo.sys.modular.emp.service.SysEmpPosService; +import vip.xiaonuo.sys.modular.pos.entity.SysPos; +import vip.xiaonuo.sys.modular.pos.enums.SysPosExceptionEnum; +import vip.xiaonuo.sys.modular.pos.mapper.SysPosMapper; +import vip.xiaonuo.sys.modular.pos.param.SysPosParam; +import vip.xiaonuo.sys.modular.pos.service.SysPosService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 系统职位service接口实现类 + * + * @author xuyuxiang yubaoshan + * @date 2020/3/13 16:01 + */ +@Service +public class SysPosServiceImpl extends ServiceImpl<SysPosMapper, SysPos> implements SysPosService { + + @Resource + private SysEmpPosService sysEmpPosService; + + @Resource + private SysEmpExtOrgPosService sysEmpExtOrgPosService; + + @Override + public PageResult<SysPos> page(SysPosParam sysPosParam) { + LambdaQueryWrapper<SysPos> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysPosParam)) { + //根据职位名称模糊查询 + if (ObjectUtil.isNotEmpty(sysPosParam.getName())) { + queryWrapper.like(SysPos::getName, sysPosParam.getName()); + } + //根据职位编码模糊查询 + if (ObjectUtil.isNotEmpty(sysPosParam.getCode())) { + queryWrapper.like(SysPos::getCode, sysPosParam.getCode()); + } + } + queryWrapper.eq(SysPos::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysPos::getSort); + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<SysPos> list(SysPosParam sysPosParam) { + LambdaQueryWrapper<SysPos> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysPosParam)) { + //根据职位编码模糊查询 + if (ObjectUtil.isNotEmpty(sysPosParam.getCode())) { + queryWrapper.eq(SysPos::getCode, sysPosParam.getCode()); + } + } + queryWrapper.eq(SysPos::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysPos::getSort); + return this.list(queryWrapper); + } + + @Override + public void add(SysPosParam sysPosParam) { + //校验参数,检查是否存在相同的名称和编码 + checkParam(sysPosParam, false); + SysPos sysPos = new SysPos(); + BeanUtil.copyProperties(sysPosParam, sysPos); + sysPos.setStatus(CommonStatusEnum.ENABLE.getCode()); + this.save(sysPos); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(List<SysPosParam> sysPosParamList) { + sysPosParamList.forEach(sysPosParam -> { + SysPos sysPos = this.querySysPos(sysPosParam); + Long id = sysPos.getId(); + //该职位下是否有员工 + boolean hasPosEmp = sysEmpPosService.hasPosEmp(id); + //只要还有,则不能删 + if (hasPosEmp) { + throw new ServiceException(SysPosExceptionEnum.POS_CANNOT_DELETE); + } + //该附属职位下是否有员工 + boolean hasExtPosEmp = sysEmpExtOrgPosService.hasExtPosEmp(id); + //只要还有,则不能删 + if (hasExtPosEmp) { + throw new ServiceException(SysPosExceptionEnum.POS_CANNOT_DELETE); + } + sysPos.setStatus(CommonStatusEnum.DELETED.getCode()); + this.updateById(sysPos); + }); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(SysPosParam sysPosParam) { + SysPos sysPos = this.querySysPos(sysPosParam); + //校验参数,检查是否存在相同的名称和编码 + checkParam(sysPosParam, true); + BeanUtil.copyProperties(sysPosParam, sysPos); + //不能修改状态,用修改状态接口修改状态 + sysPos.setStatus(null); + this.updateById(sysPos); + } + + @Override + public SysPos detail(SysPosParam sysPosParam) { + return this.querySysPos(sysPosParam); + } + + /** + * 校验参数,检查是否存在相同的名称和编码 + * + * @author xuyuxiang + * @date 2020/3/25 21:23 + */ + private void checkParam(SysPosParam sysPosParam, boolean isExcludeSelf) { + Long id = sysPosParam.getId(); + String name = sysPosParam.getName(); + String code = sysPosParam.getCode(); + + LambdaQueryWrapper<SysPos> queryWrapperByName = new LambdaQueryWrapper<>(); + queryWrapperByName.eq(SysPos::getName, name) + .ne(SysPos::getStatus, CommonStatusEnum.DELETED.getCode()); + + LambdaQueryWrapper<SysPos> queryWrapperByCode = new LambdaQueryWrapper<>(); + queryWrapperByCode.eq(SysPos::getCode, code) + .ne(SysPos::getStatus, CommonStatusEnum.DELETED.getCode()); + + if (isExcludeSelf) { + queryWrapperByName.ne(SysPos::getId, id); + queryWrapperByCode.ne(SysPos::getId, id); + } + + int countByName = this.count(queryWrapperByName); + int countByCode = this.count(queryWrapperByCode); + + if (countByName >= 1) { + throw new ServiceException(SysPosExceptionEnum.POS_NAME_REPEAT); + } + if (countByCode >= 1) { + throw new ServiceException(SysPosExceptionEnum.POS_CODE_REPEAT); + } + } + + /** + * 获取系统职位 + * + * @author xuyuxiang + * @date 2020/3/26 9:56 + */ + private SysPos querySysPos(SysPosParam sysPosParam) { + SysPos sysPos = this.getById(sysPosParam.getId()); + if (ObjectUtil.isNull(sysPos)) { + throw new ServiceException(SysPosExceptionEnum.POS_NOT_EXIST); + } + return sysPos; + } + + @Override + public void export(SysPosParam sysPosParam) { + List<SysPos> list = this.list(sysPosParam); + PoiUtil.exportExcelWithStream("SnowyPos.xls", SysPos.class, list); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/controller/SysRoleController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/controller/SysRoleController.java new file mode 100644 index 0000000..1639f3c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/controller/SysRoleController.java @@ -0,0 +1,191 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.DataScope; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.role.param.SysRoleParam; +import vip.xiaonuo.sys.modular.role.service.SysRoleService; + +import javax.annotation.Resource; + +/** + * 系统角色控制器 + * + * @author xuyuxiang + * @date 2020/3/20 19:42 + */ +@RestController +public class SysRoleController { + + @Resource + private SysRoleService sysRoleService; + + /** + * 查询系统角色 + * + * @author xuyuxiang + * @date 2020/3/28 14:45 + */ + @Permission + @GetMapping("/sysRole/page") + @BusinessLog(title = "系统角色_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysRoleParam sysRoleParam) { + return new SuccessResponseData(sysRoleService.page(sysRoleParam)); + } + + /** + * 系统角色下拉(用于授权角色时选择) + * + * @author xuyuxiang + * @date 2020/4/5 16:45 + */ + @Permission + @GetMapping("/sysRole/dropDown") + @BusinessLog(title = "系统角色_下拉", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData dropDown() { + return new SuccessResponseData(sysRoleService.dropDown()); + } + + /** + * 添加系统角色 + * + * @author xuyuxiang + * @date 2020/3/28 14:45 + */ + @Permission + @PostMapping("/sysRole/add") + @BusinessLog(title = "系统角色_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysRoleParam.add.class) SysRoleParam sysRoleParam) { + sysRoleService.add(sysRoleParam); + return new SuccessResponseData(); + } + + /** + * 删除系统角色 + * + * @author xuyuxiang + * @date 2020/3/28 14:45 + */ + @Permission + @PostMapping("/sysRole/delete") + @BusinessLog(title = "系统角色_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysRoleParam.delete.class) SysRoleParam sysRoleParam) { + sysRoleService.delete(sysRoleParam); + return new SuccessResponseData(); + } + + /** + * 编辑系统角色 + * + * @author xuyuxiang + * @date 2020/3/28 14:46 + */ + @Permission + @PostMapping("/sysRole/edit") + @BusinessLog(title = "系统角色_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysRoleParam.edit.class) SysRoleParam sysRoleParam) { + sysRoleService.edit(sysRoleParam); + return new SuccessResponseData(); + } + + /** + * 查看系统角色 + * + * @author xuyuxiang + * @date 2020/3/28 14:46 + */ + @Permission + @GetMapping("/sysRole/detail") + @BusinessLog(title = "系统角色_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysRoleParam.detail.class) SysRoleParam sysRoleParam) { + return new SuccessResponseData(sysRoleService.detail(sysRoleParam)); + } + + /** + * 授权菜单 + * + * @author xuyuxiang + * @date 2020/3/28 16:05 + */ + @Permission + @PostMapping("/sysRole/grantMenu") + @BusinessLog(title = "系统角色_授权菜单", opType = LogAnnotionOpTypeEnum.GRANT) + public ResponseData grantMenu(@RequestBody @Validated(SysRoleParam.grantMenu.class) SysRoleParam sysRoleParam) { + sysRoleService.grantMenu(sysRoleParam); + return new SuccessResponseData(); + } + + /** + * 授权数据 + * + * @author xuyuxiang + * @date 2020/3/28 16:05 + */ + @Permission + @DataScope + @PostMapping("/sysRole/grantData") + @BusinessLog(title = "系统角色_授权数据", opType = LogAnnotionOpTypeEnum.GRANT) + public ResponseData grantData(@RequestBody @Validated(SysRoleParam.grantData.class) SysRoleParam sysRoleParam) { + sysRoleService.grantData(sysRoleParam); + return new SuccessResponseData(); + } + + /** + * 拥有菜单 + * + * @author xuyuxiang + * @date 2020/3/28 14:46 + */ + @Permission + @GetMapping("/sysRole/ownMenu") + @BusinessLog(title = "系统角色_拥有菜单", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData ownMenu(@Validated(SysRoleParam.detail.class) SysRoleParam sysRoleParam) { + return new SuccessResponseData(sysRoleService.ownMenu(sysRoleParam)); + } + + /** + * 拥有数据 + * + * @author xuyuxiang + * @date 2020/3/28 14:46 + */ + @Permission + @GetMapping("/sysRole/ownData") + @BusinessLog(title = "系统角色_拥有数据", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData ownData(@Validated(SysRoleParam.detail.class) SysRoleParam sysRoleParam) { + return new SuccessResponseData(sysRoleService.ownData(sysRoleParam)); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRole.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRole.java new file mode 100644 index 0000000..9cc5dbc --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRole.java @@ -0,0 +1,79 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 系统角色表 + * + * @author xuyuxiang + * @date 2020/3/11 11:20 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_role") +public class SysRole extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 名称 + */ + private String name; + + /** + * 编码 + */ + private String code; + + /** + * 排序 + */ + private Integer sort; + + /** + * 数据范围类型(字典 1全部数据 2本部门及以下数据 3本部门数据 4仅本人数据 5自定义数据) + */ + private Integer dataScopeType; + + /** + * 备注 + */ + @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + private String remark; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRoleDataScope.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRoleDataScope.java new file mode 100644 index 0000000..046d1b2 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRoleDataScope.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 系统角色数据范围表 + * + * @author xuyuxiang + * @date 2020/3/11 11:47 + */ +@Data +@TableName("sys_role_data_scope") +public class SysRoleDataScope { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 角色id + */ + private Long roleId; + + /** + * 机构id + */ + private Long orgId; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRoleMenu.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRoleMenu.java new file mode 100644 index 0000000..f55aff3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/entity/SysRoleMenu.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 系统角色菜单表 + * + * @author xuyuxiang + * @date 2020/3/11 11:47 + */ +@Data +@TableName("sys_role_menu") +public class SysRoleMenu { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 角色id + */ + private Long roleId; + + /** + * 菜单id + */ + private Long menuId; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/enums/SysRoleExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/enums/SysRoleExceptionEnum.java new file mode 100644 index 0000000..050b79d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/enums/SysRoleExceptionEnum.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统角色相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/28 14:47 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_ROLE_EXCEPTION_ENUM) +public enum SysRoleExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 角色不存在 + */ + ROLE_NOT_EXIST(1, "角色不存在"), + + /** + * 角色编码重复 + */ + ROLE_CODE_REPEAT(2, "角色编码重复,请检查code参数"), + + /** + * 角色名称重复 + */ + ROLE_NAME_REPEAT(3, "角色名称重复,请检查name参数"); + + private final Integer code; + + private final String message; + + SysRoleExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleDataScopeMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleDataScopeMapper.java new file mode 100644 index 0000000..d1696b9 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleDataScopeMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.role.entity.SysRoleDataScope; + +/** + * 系统角色范围mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:53 + */ +public interface SysRoleDataScopeMapper extends BaseMapper<SysRoleDataScope> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleMapper.java new file mode 100644 index 0000000..79cda89 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.role.entity.SysRole; + +/** + * 系统角色mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:51 + */ +public interface SysRoleMapper extends BaseMapper<SysRole> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleMenuMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..412ca90 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/SysRoleMenuMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.role.entity.SysRoleMenu; + +/** + * 系统角色菜单mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:52 + */ +public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenu> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleDataScopeMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleDataScopeMapper.xml new file mode 100644 index 0000000..7049efd --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleDataScopeMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.role.mapper.SysRoleDataScopeMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleMapper.xml new file mode 100644 index 0000000..3de88ff --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.role.mapper.SysRoleMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleMenuMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleMenuMapper.xml new file mode 100644 index 0000000..60413cc --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/mapper/mapping/SysRoleMenuMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.role.mapper.SysRoleMenuMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/param/SysRoleParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/param/SysRoleParam.java new file mode 100644 index 0000000..202ddc9 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/param/SysRoleParam.java @@ -0,0 +1,93 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.*; +import java.util.List; + +/** + * 系统角色参数 + * + * @author xuyuxiang + * @date 2020/3/26 19:02 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysRoleParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class, grantMenu.class, grantData.class}) + private Long id; + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 编码 + */ + @NotBlank(message = "编码不能为空,请检查code参数", groups = {add.class, edit.class}) + private String code; + + /** + * 排序 + */ + @NotNull(message = "排序不能为空,请检查sort参数", groups = {add.class, edit.class}) + private Integer sort; + + /** + * 数据范围类型(字典 1全部数据 2本部门及以下数据 3本部门数据 4仅本人数据 5自定义数据) + */ + @Null(message = "数据范围类型应该为空, 请移除dataScopeType参数", groups = {add.class, edit.class}) + @NotNull(message = "数据范围类型不能为空,请检查dataScopeType参数", groups = {grantData.class}) + @Min(value = 0, message = "数据范围类型格式错误,请检查dataScopeType参数", groups = {grantData.class}) + @Max(value = 5, message = "数据范围类型格式错误,请检查dataScopeType参数", groups = {grantData.class}) + private Integer dataScopeType; + + /** + * 备注 + */ + private String remark; + + /** + * 授权菜单 + */ + @NotNull(message = "授权菜单不能为空,请检查grantMenuIdList参数", groups = {grantMenu.class}) + private List<Long> grantMenuIdList; + + /** + * 授权数据 + */ + @NotNull(message = "授权数据不能为空,请检查grantOrgIdList参数", groups = {grantData.class}) + private List<Long> grantOrgIdList; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleDataScopeService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleDataScopeService.java new file mode 100644 index 0000000..07a9d1c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleDataScopeService.java @@ -0,0 +1,77 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.sys.modular.role.entity.SysRoleDataScope; +import vip.xiaonuo.sys.modular.role.param.SysRoleParam; + +import java.util.List; + +/** + * 系统角色数据范围service接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:51 + */ +public interface SysRoleDataScopeService extends IService<SysRoleDataScope> { + + /** + * 授权数据 + * + * @param sysRoleParam 授权参数 + * @author xuyuxiang + * @date 2020/3/28 16:36 + */ + void grantDataScope(SysRoleParam sysRoleParam); + + /** + * 根据角色id获取角色数据范围集合 + * + * @param roleIdList 角色id集合 + * @return 数据范围id集合 + * @author xuyuxiang + * @date 2020/4/5 17:44 + */ + List<Long> getRoleDataScopeIdList(List<Long> roleIdList); + + /** + * 根据机构id集合删除对应的角色-数据范围关联信息 + * + * @param orgIdList 机构id集合 + * @author xuyuxiang + * @date 2020/6/28 14:14 + */ + void deleteRoleDataScopeListByOrgIdList(List<Long> orgIdList); + + /** + * 根据角色id删除对应的角色-数据范围关联信息 + * + * @param roleId 角色id + * @author xuyuxiang + * @date 2020/6/28 14:47 + */ + void deleteRoleDataScopeListByRoleId(Long roleId); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleMenuService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleMenuService.java new file mode 100644 index 0000000..77056e7 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleMenuService.java @@ -0,0 +1,77 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.sys.modular.role.entity.SysRoleMenu; +import vip.xiaonuo.sys.modular.role.param.SysRoleParam; + +import java.util.List; + +/** + * 系统角色菜单service接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:50 + */ +public interface SysRoleMenuService extends IService<SysRoleMenu> { + + /** + * 获取角色的菜单id集合 + * + * @param roleIdList 角色id集合 + * @return 菜单id集合 + * @author xuyuxiang + * @date 2020/3/21 10:17 + */ + List<Long> getRoleMenuIdList(List<Long> roleIdList); + + /** + * 授权菜单 + * + * @param sysRoleParam 授权参数 + * @author xuyuxiang + * @date 2020/3/28 16:36 + */ + void grantMenu(SysRoleParam sysRoleParam); + + /** + * 根据菜单id集合删除对应的角色-菜单表信息 + * + * @param menuIdList 菜单id集合 + * @author xuyuxiang + * @date 2020/6/28 14:19 + */ + void deleteRoleMenuListByMenuIdList(List<Long> menuIdList); + + /** + * 根据角色id删除对应的角色-菜单表关联信息 + * + * @param roleId 角色id + * @author xuyuxiang + * @date 2020/6/28 14:43 + */ + void deleteRoleMenuListByRoleId(Long roleId); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleService.java new file mode 100644 index 0000000..e8b7453 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/SysRoleService.java @@ -0,0 +1,177 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.service; + +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.role.entity.SysRole; +import vip.xiaonuo.sys.modular.role.param.SysRoleParam; + +import java.util.List; + +/** + * 系统角色service接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:47 + */ +public interface SysRoleService extends IService<SysRole> { + + /** + * 获取用户角色相关信息 + * + * @param userId 用户id + * @return 增强版hashMap,格式:[{"id":456, "code":"zjl", "name":"总经理"}] + * @author xuyuxiang + * @date 2020/3/13 16:26 + */ + List<Dict> getLoginRoles(Long userId); + + /** + * 查询系统角色 + * + * @param sysRoleParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/3/28 14:53 + */ + PageResult<SysRole> page(SysRoleParam sysRoleParam); + + /** + * 根据角色名模糊搜索系统角色列表 + * + * @param sysRoleParam 查询参数 + * @return 增强版hashMap,格式:[{"id":456, "name":"总经理(zjl)"}] + * @author xuyuxiang + * @date 2020/4/14 9:21 + */ + List<Dict> list(SysRoleParam sysRoleParam); + + /** + * 系统角色下拉(用于授权角色时选择) + * + * @return 增强版hashMap,格式:[{"id":456, "code":"zjl", "name":"总经理"}] + * @author xuyuxiang + * @date 2020/4/5 16:47 + */ + List<Dict> dropDown(); + + /** + * 添加系统角色 + * + * @param sysRoleParam 添加参数 + * @author xuyuxiang + * @date 2020/3/28 14:54 + */ + void add(SysRoleParam sysRoleParam); + + /** + * 删除系统角色 + * + * @param sysRoleParam 删除参数 + * @author xuyuxiang + * @date 2020/3/28 14:54 + */ + void delete(SysRoleParam sysRoleParam); + + /** + * 编辑系统角色 + * + * @param sysRoleParam 编辑参数 + * @author xuyuxiang + * @date 2020/3/28 14:54 + */ + void edit(SysRoleParam sysRoleParam); + + /** + * 查看系统角色 + * + * @param sysRoleParam 查看参数 + * @return 系统角色 + * @author xuyuxiang + * @date 2020/3/26 9:50 + */ + SysRole detail(SysRoleParam sysRoleParam); + + /** + * 授权菜单 + * + * @param sysRoleParam 授权参数 + * @author xuyuxiang + * @date 2020/3/28 16:19 + */ + void grantMenu(SysRoleParam sysRoleParam); + + /** + * 授权数据 + * + * @param sysRoleParam 授权参数 + * @author xuyuxiang + * @date 2020/3/28 16:20 + */ + void grantData(SysRoleParam sysRoleParam); + + /** + * 根据角色id集合获取数据范围id集合 + * + * @param roleIdList 角色id集合 + * @param orgId 机构id + * @return 数据范围id集合 + * @author xuyuxiang + * @date 2020/4/5 17:41 + */ + List<Long> getUserDataScopeIdList(List<Long> roleIdList, Long orgId); + + /** + * 根据角色id获取角色名称 + * + * @param roleId 角色id + * @return 角色名称 + * @author xuyuxiang + * @date 2020/5/22 16:15 + */ + String getNameByRoleId(Long roleId); + + /** + * 查询角色拥有菜单 + * + * @param sysRoleParam 查询参数 + * @return 菜单id集合 + * @author xuyuxiang + * @date 2020/5/29 14:03 + */ + List<Long> ownMenu(SysRoleParam sysRoleParam); + + /** + * 查询角色拥有数据 + * + * @param sysRoleParam 查询参数 + * @return 数据范围id集合 + * @author xuyuxiang + * @date 2020/5/29 14:04 + */ + List<Long> ownData(SysRoleParam sysRoleParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleDataScopeServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleDataScopeServiceImpl.java new file mode 100644 index 0000000..8146694 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleDataScopeServiceImpl.java @@ -0,0 +1,88 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.sys.modular.role.entity.SysRoleDataScope; +import vip.xiaonuo.sys.modular.role.mapper.SysRoleDataScopeMapper; +import vip.xiaonuo.sys.modular.role.param.SysRoleParam; +import vip.xiaonuo.sys.modular.role.service.SysRoleDataScopeService; + +import java.util.List; + +/** + * 系统角色数据范围service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 15:55 + */ +@Service +public class SysRoleDataScopeServiceImpl extends ServiceImpl<SysRoleDataScopeMapper, SysRoleDataScope> implements SysRoleDataScopeService { + + @Override + public void grantDataScope(SysRoleParam sysRoleParam) { + Long roleId = sysRoleParam.getId(); + LambdaQueryWrapper<SysRoleDataScope> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysRoleDataScope::getRoleId, roleId); + //删除所拥有数据 + this.remove(queryWrapper); + //授权数据 + sysRoleParam.getGrantOrgIdList().forEach(orgId -> { + SysRoleDataScope sysRoleDataScope = new SysRoleDataScope(); + sysRoleDataScope.setRoleId(roleId); + sysRoleDataScope.setOrgId(orgId); + this.save(sysRoleDataScope); + }); + } + + @Override + public List<Long> getRoleDataScopeIdList(List<Long> roleIdList) { + List<Long> resultList = CollectionUtil.newArrayList(); + if (ObjectUtil.isNotEmpty(roleIdList)) { + LambdaQueryWrapper<SysRoleDataScope> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleDataScope::getRoleId, roleIdList); + this.list(queryWrapper).forEach(sysRoleDataScope -> resultList.add(sysRoleDataScope.getOrgId())); + } + return resultList; + } + + @Override + public void deleteRoleDataScopeListByOrgIdList(List<Long> orgIdList) { + LambdaQueryWrapper<SysRoleDataScope> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleDataScope::getOrgId, orgIdList); + this.remove(queryWrapper); + } + + @Override + public void deleteRoleDataScopeListByRoleId(Long roleId) { + LambdaQueryWrapper<SysRoleDataScope> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysRoleDataScope::getRoleId, roleId); + this.remove(queryWrapper); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleMenuServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleMenuServiceImpl.java new file mode 100644 index 0000000..3374c83 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleMenuServiceImpl.java @@ -0,0 +1,88 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.sys.modular.role.entity.SysRoleMenu; +import vip.xiaonuo.sys.modular.role.mapper.SysRoleMenuMapper; +import vip.xiaonuo.sys.modular.role.param.SysRoleParam; +import vip.xiaonuo.sys.modular.role.service.SysRoleMenuService; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 系统角色菜单service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 15:55 + */ +@Service +public class SysRoleMenuServiceImpl extends ServiceImpl<SysRoleMenuMapper, SysRoleMenu> implements SysRoleMenuService { + + @Override + public List<Long> getRoleMenuIdList(List<Long> roleIdList) { + if(ObjectUtil.isNotEmpty(roleIdList)) { + LambdaQueryWrapper<SysRoleMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleMenu::getRoleId, roleIdList); + return this.list(queryWrapper).stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList()); + } + return CollectionUtil.newArrayList(); + } + + @Override + public void grantMenu(SysRoleParam sysRoleParam) { + Long roleId = sysRoleParam.getId(); + //删除所拥有菜单 + LambdaQueryWrapper<SysRoleMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysRoleMenu::getRoleId, roleId); + this.remove(queryWrapper); + //授权菜单 + sysRoleParam.getGrantMenuIdList().forEach(menuId -> { + SysRoleMenu sysRoleMenu = new SysRoleMenu(); + sysRoleMenu.setRoleId(roleId); + sysRoleMenu.setMenuId(menuId); + this.save(sysRoleMenu); + }); + } + + @Override + public void deleteRoleMenuListByMenuIdList(List<Long> menuIdList) { + LambdaQueryWrapper<SysRoleMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRoleMenu::getMenuId, menuIdList); + this.remove(queryWrapper); + } + + @Override + public void deleteRoleMenuListByRoleId(Long roleId) { + LambdaQueryWrapper<SysRoleMenu> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysRoleMenu::getRoleId, roleId); + this.remove(queryWrapper); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..d41d3ac --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/role/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,358 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.role.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.PermissionException; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.PermissionExceptionEnum; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.core.enums.DataScopeTypeEnum; +import vip.xiaonuo.sys.modular.org.service.SysOrgService; +import vip.xiaonuo.sys.modular.role.entity.SysRole; +import vip.xiaonuo.sys.modular.role.enums.SysRoleExceptionEnum; +import vip.xiaonuo.sys.modular.role.mapper.SysRoleMapper; +import vip.xiaonuo.sys.modular.role.param.SysRoleParam; +import vip.xiaonuo.sys.modular.role.service.SysRoleDataScopeService; +import vip.xiaonuo.sys.modular.role.service.SysRoleMenuService; +import vip.xiaonuo.sys.modular.role.service.SysRoleService; +import vip.xiaonuo.sys.modular.user.service.SysUserRoleService; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 系统角色service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 15:55 + */ +@Service +public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService { + + @Resource + private SysUserRoleService sysUserRoleService; + + @Resource + private SysRoleMenuService sysRoleMenuService; + + @Resource + private SysRoleDataScopeService sysRoleDataScopeService; + + @Resource + private SysOrgService sysOrgService; + + @Override + public List<Dict> getLoginRoles(Long userId) { + List<Dict> dictList = CollectionUtil.newArrayList(); + //获取用户角色id集合 + List<Long> roleIdList = sysUserRoleService.getUserRoleIdList(userId); + if (ObjectUtil.isNotEmpty(roleIdList)) { + LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysRole::getId, roleIdList).eq(SysRole::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据角色id集合查询并返回结果 + dictList = this.list(queryWrapper).stream().map(sysRole -> { + Dict dict = Dict.create(); + dict.put(CommonConstant.ID, sysRole.getId()); + dict.put(CommonConstant.CODE, sysRole.getCode()); + dict.put(CommonConstant.NAME, sysRole.getName()); + return dict; + }).collect(Collectors.toList()); + } + return dictList; + } + + @Override + public PageResult<SysRole> page(SysRoleParam sysRoleParam) { + LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysRoleParam)) { + //根据名称模糊查询 + if (ObjectUtil.isNotEmpty(sysRoleParam.getName())) { + queryWrapper.like(SysRole::getName, sysRoleParam.getName()); + } + //根据编码模糊查询 + if (ObjectUtil.isNotEmpty(sysRoleParam.getCode())) { + queryWrapper.like(SysRole::getCode, sysRoleParam.getCode()); + } + } + + queryWrapper.eq(SysRole::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysRole::getSort); + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<Dict> list(SysRoleParam sysRoleParam) { + List<Dict> dictList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysRoleParam)) { + //根据角色名称或编码模糊查询 + if (ObjectUtil.isNotEmpty(sysRoleParam.getName())) { + queryWrapper.and(i -> i.like(SysRole::getName, sysRoleParam.getName()) + .or().like(SysRole::getCode, sysRoleParam.getName())); + } + } + //只查询正常状态 + queryWrapper.eq(SysRole::getStatus, CommonStatusEnum.ENABLE.getCode()); + //根据排序升序排列,序号越小越在前 + queryWrapper.orderByAsc(SysRole::getSort); + this.list(queryWrapper).forEach(sysRole -> { + Dict dict = Dict.create(); + dict.put(CommonConstant.ID, sysRole.getId()); + dict.put(CommonConstant.NAME, sysRole.getName() + SymbolConstant.LEFT_SQUARE_BRACKETS + + sysRole.getCode() + SymbolConstant.RIGHT_SQUARE_BRACKETS); + dictList.add(dict); + }); + return dictList; + } + + @Override + public List<Dict> dropDown() { + List<Dict> dictList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>(); + //如果当前登录用户不是超级管理员,则查询自己拥有的 + if (!LoginContextHolder.me().isSuperAdmin()) { + + //查询自己拥有的 + List<String> loginUserRoleIds = LoginContextHolder.me().getLoginUserRoleIds(); + if (ObjectUtil.isEmpty(loginUserRoleIds)) { + return dictList; + } + queryWrapper.in(SysRole::getId, loginUserRoleIds); + } + //只查询正常状态 + queryWrapper.eq(SysRole::getStatus, CommonStatusEnum.ENABLE.getCode()); + this.list(queryWrapper) + .forEach(sysRole -> { + Dict dict = Dict.create(); + dict.put(CommonConstant.ID, sysRole.getId()); + dict.put(CommonConstant.CODE, sysRole.getCode()); + dict.put(CommonConstant.NAME, sysRole.getName()); + dictList.add(dict); + }); + return dictList; + } + + @Override + public void add(SysRoleParam sysRoleParam) { + //校验参数,检查是否存在相同的名称和编码 + checkParam(sysRoleParam, false); + SysRole sysRole = new SysRole(); + BeanUtil.copyProperties(sysRoleParam, sysRole); + sysRole.setStatus(CommonStatusEnum.ENABLE.getCode()); + this.save(sysRole); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(SysRoleParam sysRoleParam) { + SysRole sysRole = this.querySysRole(sysRoleParam); + sysRole.setStatus(CommonStatusEnum.DELETED.getCode()); + this.updateById(sysRole); + Long id = sysRole.getId(); + //级联删除该角色对应的角色-数据范围关联信息 + sysRoleDataScopeService.deleteRoleDataScopeListByRoleId(id); + + //级联删除该角色对应的用户-角色表关联信息 + sysUserRoleService.deleteUserRoleListByRoleId(id); + + //级联删除该角色对应的角色-菜单表关联信息 + sysRoleMenuService.deleteRoleMenuListByRoleId(id); + } + + @Override + public void edit(SysRoleParam sysRoleParam) { + SysRole sysRole = this.querySysRole(sysRoleParam); + //校验参数,检查是否存在相同的名称和编码 + checkParam(sysRoleParam, true); + BeanUtil.copyProperties(sysRoleParam, sysRole); + //不能修改状态,用修改状态接口修改状态 + sysRole.setStatus(null); + this.updateById(sysRole); + } + + @Override + public SysRole detail(SysRoleParam sysRoleParam) { + return this.querySysRole(sysRoleParam); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void grantMenu(SysRoleParam sysRoleParam) { + this.querySysRole(sysRoleParam); + sysRoleMenuService.grantMenu(sysRoleParam); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void grantData(SysRoleParam sysRoleParam) { + SysRole sysRole = this.querySysRole(sysRoleParam); + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + //如果登录用户不是超级管理员,则进行数据权限校验 + if (!superAdmin) { + Integer dataScopeType = sysRoleParam.getDataScopeType(); + //如果授权的角色的数据范围类型为全部,则没权限,只有超级管理员有 + if (DataScopeTypeEnum.ALL.getCode().equals(dataScopeType)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + //如果授权的角色数据范围类型为自定义,则要判断授权的数据范围是否在自己的数据范围内 + if (DataScopeTypeEnum.DEFINE.getCode().equals(dataScopeType)) { + List<Long> dataScope = sysRoleParam.getDataScope(); + //要授权的数据范围列表 + List<Long> grantOrgIdList = sysRoleParam.getGrantOrgIdList(); + if (ObjectUtil.isNotEmpty(grantOrgIdList)) { + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } else if (!dataScope.containsAll(grantOrgIdList)) { + //所要授权的数据不在自己的数据范围内 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + } + } + sysRole.setDataScopeType(sysRoleParam.getDataScopeType()); + this.updateById(sysRole); + sysRoleDataScopeService.grantDataScope(sysRoleParam); + } + + @Override + public List<Long> getUserDataScopeIdList(List<Long> roleIdList, Long orgId) { + Set<Long> resultList = CollectionUtil.newHashSet(); + + //定义角色中最大数据范围的类型,目前系统按最大范围策略来,如果你同时拥有ALL和SELF的权限,系统最后按ALL返回 + Integer strongerDataScopeType = DataScopeTypeEnum.SELF.getCode(); + + //获取用户自定义数据范围的角色集合 + List<Long> customDataScopeRoleIdList = CollectionUtil.newArrayList(); + if (ObjectUtil.isNotEmpty(roleIdList)) { + List<SysRole> sysRoleList = this.listByIds(roleIdList); + for (SysRole sysRole : sysRoleList) { + if (DataScopeTypeEnum.DEFINE.getCode().equals(sysRole.getDataScopeType())) { + customDataScopeRoleIdList.add(sysRole.getId()); + } else { + if (sysRole.getDataScopeType() <= strongerDataScopeType) { + strongerDataScopeType = sysRole.getDataScopeType(); + } + } + } + } + + //自定义数据范围的角色对应的数据范围 + List<Long> roleDataScopeIdList = sysRoleDataScopeService.getRoleDataScopeIdList(customDataScopeRoleIdList); + + //角色中拥有最大数据范围类型的数据范围 + List<Long> dataScopeIdList = sysOrgService.getDataScopeListByDataScopeType(strongerDataScopeType, orgId); + + resultList.addAll(dataScopeIdList); + resultList.addAll(roleDataScopeIdList); + return CollectionUtil.newArrayList(resultList); + } + + @Override + public String getNameByRoleId(Long roleId) { + SysRole sysRole = this.getById(roleId); + if (ObjectUtil.isEmpty(sysRole)) { + throw new ServiceException(SysRoleExceptionEnum.ROLE_NOT_EXIST); + } + return sysRole.getName(); + } + + @Override + public List<Long> ownMenu(SysRoleParam sysRoleParam) { + SysRole sysRole = this.querySysRole(sysRoleParam); + return sysRoleMenuService.getRoleMenuIdList(CollectionUtil.newArrayList(sysRole.getId())); + } + + @Override + public List<Long> ownData(SysRoleParam sysRoleParam) { + SysRole sysRole = this.querySysRole(sysRoleParam); + return sysRoleDataScopeService.getRoleDataScopeIdList(CollectionUtil.newArrayList(sysRole.getId())); + } + + /** + * 校验参数,检查是否存在相同的名称和编码 + * + * @author xuyuxiang + * @date 2020/3/28 14:59 + */ + private void checkParam(SysRoleParam sysRoleParam, boolean isExcludeSelf) { + Long id = sysRoleParam.getId(); + String name = sysRoleParam.getName(); + String code = sysRoleParam.getCode(); + + LambdaQueryWrapper<SysRole> queryWrapperByName = new LambdaQueryWrapper<>(); + queryWrapperByName.eq(SysRole::getName, name) + .ne(SysRole::getStatus, CommonStatusEnum.DELETED.getCode()); + + LambdaQueryWrapper<SysRole> queryWrapperByCode = new LambdaQueryWrapper<>(); + queryWrapperByCode.eq(SysRole::getCode, code) + .ne(SysRole::getStatus, CommonStatusEnum.DELETED.getCode()); + + //是否排除自己,如果排除自己则不查询自己的id + if (isExcludeSelf) { + queryWrapperByName.ne(SysRole::getId, id); + queryWrapperByCode.ne(SysRole::getId, id); + } + int countByName = this.count(queryWrapperByName); + int countByCode = this.count(queryWrapperByCode); + + if (countByName >= 1) { + throw new ServiceException(SysRoleExceptionEnum.ROLE_NAME_REPEAT); + } + if (countByCode >= 1) { + throw new ServiceException(SysRoleExceptionEnum.ROLE_CODE_REPEAT); + } + } + + /** + * 获取系统角色 + * + * @author xuyuxiang + * @date 2020/3/28 14:59 + */ + private SysRole querySysRole(SysRoleParam sysRoleParam) { + SysRole sysRole = this.getById(sysRoleParam.getId()); + if (ObjectUtil.isNull(sysRole)) { + throw new ServiceException(SysRoleExceptionEnum.ROLE_NOT_EXIST); + } + return sysRole; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/controller/SmsSenderController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/controller/SmsSenderController.java new file mode 100644 index 0000000..32b99e8 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/controller/SmsSenderController.java @@ -0,0 +1,108 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.controller; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.RandomUtil; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.sms.enums.SmsVerifyEnum; +import vip.xiaonuo.sys.modular.sms.param.SysSmsInfoParam; +import vip.xiaonuo.sys.modular.sms.param.SysSmsSendParam; +import vip.xiaonuo.sys.modular.sms.param.SysSmsVerifyParam; +import vip.xiaonuo.sys.modular.sms.service.SmsSenderService; +import vip.xiaonuo.sys.modular.sms.service.SysSmsInfoService; + +import javax.annotation.Resource; +import java.util.HashMap; + +/** + * 短信发送控制器 + * + * @author yubaoshan + * @date 2020/6/7 16:07 + */ +@RestController +@RequestMapping +public class SmsSenderController { + + @Resource + private SmsSenderService smsSenderService; + + @Resource + private SysSmsInfoService sysSmsInfoService; + + /** + * 发送记录查询 + * + * @author xuyuxiang + * @date 2020/7/2 12:03 + */ + @Permission + @GetMapping("/sms/page") + @BusinessLog(title = "短信发送记录查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysSmsInfoParam sysSmsInfoParam) { + return new SuccessResponseData(sysSmsInfoService.page(sysSmsInfoParam)); + } + + /** + * 发送验证码短信 + * + * @author yubaoshan + * @date 2020/6/7 16:07 + */ + @Permission + @PostMapping("/sms/sendLoginMessage") + @BusinessLog(title = "发送验证码短信") + public ResponseData sendLoginMessage(@RequestBody @Validated SysSmsSendParam sysSmsSendParam) { + + // 设置模板中的参数 + HashMap<String, Object> paramMap = CollectionUtil.newHashMap(); + paramMap.put("code", RandomUtil.randomNumbers(6)); + sysSmsSendParam.setParams(paramMap); + + return new SuccessResponseData(smsSenderService.sendShortMessage(sysSmsSendParam)); + } + + /** + * 验证短信验证码 + * + * @author yubaoshan + * @date 2020/6/7 16:07 + */ + @Permission + @PostMapping("/sms/validateMessage") + @BusinessLog(title = "验证短信验证码") + public ResponseData validateMessage(@RequestBody @Validated SysSmsVerifyParam sysSmsVerifyParam) { + SmsVerifyEnum smsVerifyEnum = smsSenderService.verifyShortMessage(sysSmsVerifyParam); + return new SuccessResponseData(smsVerifyEnum); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/entity/SysSms.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/entity/SysSms.java new file mode 100644 index 0000000..6391640 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/entity/SysSms.java @@ -0,0 +1,92 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.entity; + + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +import java.io.Serializable; +import java.util.Date; + +/** + * 系统短信表 + * + * @author yubaoshan + * @date 2018/7/5 13:44 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_sms") +public class SysSms extends BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 手机号 + */ + private String phoneNumbers; + + /** + * 短信验证码 + */ + private String validateCode; + + /** + * 短信模板ID + */ + private String templateCode; + + /** + * 回执id,可根据该id查询具体的发送状态 + */ + private String bizId; + + /** + * 发送状态(字典 0 未发送,1 发送成功,2 发送失败,3 失效) + */ + private Integer status; + + /** + * 来源(字典 1 app, 2 pc, 3 其他) + */ + private Integer source; + + /** + * 失效时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date invalidTime; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendExceptionEnum.java new file mode 100644 index 0000000..4c7d920 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendExceptionEnum.java @@ -0,0 +1,75 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 短信发送相关异常枚举 + * + * @author xuyuxiang + * @date 2020/7/7 11:30 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SMS_EXCEPTION_ENUM) +public enum SmsSendExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 手机号码不能为空 + */ + PHONE_NUMBER_EMPTY(1, "手机号码不能为空,请检查phoneNumbers参数"), + + /** + * 验证码不能为空 + */ + VALIDATE_CODE_EMPTY(2, "验证码不能为空,请检查validateCode参数"), + + /** + * 模板号不能为空 + */ + TEMPLATE_CODE_EMPTY(3, "模板号不能为空,请检查templateCode参数"); + + private final Integer code; + + private final String message; + + SmsSendExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendSourceEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendSourceEnum.java new file mode 100644 index 0000000..44bb8f8 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendSourceEnum.java @@ -0,0 +1,58 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.enums; + +import lombok.Getter; + +/** + * 短信发送业务枚举 + * + * @author yubaoshan + * @date 2018/5/6 12:30 + */ +@Getter +public enum SmsSendSourceEnum { + + /** + * APP + */ + APP(1), + + /** + * PC + */ + PC(2), + + /** + * OTHER + */ + OTHER(3); + + private final Integer code; + + SmsSendSourceEnum(Integer code) { + this.code = code; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendStatusEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendStatusEnum.java new file mode 100644 index 0000000..857929e --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsSendStatusEnum.java @@ -0,0 +1,66 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.enums; + +import lombok.Getter; + +/** + * 短信发送状态枚举 + * + * @author yubaoshan + * @date 2018/5/6 12:30 + */ +@Getter +public enum SmsSendStatusEnum { + + /** + * 未发送 + */ + WAITING(0, "未发送"), + + /** + * 发送成功 + */ + SUCCESS(1, "发送成功"), + + /** + * 发送失败 + */ + FAILED(2, "发送失败"), + + /** + * 失效 + */ + INVALID(3, "失效"); + + private final Integer code; + + private final String message; + + SmsSendStatusEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsTypeEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsTypeEnum.java new file mode 100644 index 0000000..048c9e5 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsTypeEnum.java @@ -0,0 +1,56 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.enums; + +import lombok.Getter; + +/** + * 短信类型枚举 + * + * @author yubaoshan + * @date 2018/5/6 12:30 + */ +@Getter +public enum SmsTypeEnum { + + /** + * 验证类短信 + */ + SMS(1, "验证类短信"), + + /** + * 纯发送短信 + */ + MESSAGE(2, "纯发送短信"); + + private final Integer code; + + private final String message; + + SmsTypeEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsVerifyEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsVerifyEnum.java new file mode 100644 index 0000000..993b9b3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/enums/SmsVerifyEnum.java @@ -0,0 +1,67 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.enums; + +import lombok.Getter; + +/** + * 短信验证相关美剧 + * + * @author yubaoshan + * @date 2018/5/6 12:30 + */ +@Getter +public enum SmsVerifyEnum { + + /** + * 验证成功 + */ + SUCCESS(10, "验证成功"), + + /** + * 验证码错误 + */ + ERROR(20, "验证码错误"), + + /** + * 验证码超时 + */ + EXPIRED(30, "验证码超时"), + + /** + * 超过验证次数 + */ + TIMES_UP(40, "超过验证次数"); + + private final Integer code; + + private final String message; + + SmsVerifyEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/mapper/SysSmsMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/mapper/SysSmsMapper.java new file mode 100644 index 0000000..8f0484b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/mapper/SysSmsMapper.java @@ -0,0 +1,38 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.sms.entity.SysSms; + +/** + * 系统短信mapper接口 + * + * @author yubaoshan + * @date 2018/7/5 13:44 + */ +public interface SysSmsMapper extends BaseMapper<SysSms> { + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/mapper/mapping/SysSmsMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/mapper/mapping/SysSmsMapper.xml new file mode 100644 index 0000000..04f7c22 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/mapper/mapping/SysSmsMapper.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.sms.mapper.SysSmsMapper"> + + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsInfoParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsInfoParam.java new file mode 100644 index 0000000..84e7e40 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsInfoParam.java @@ -0,0 +1,83 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.param; + + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import java.util.Date; + +/** + * 系统短信表 + * + * @author yubaoshan + * @date 2018/7/5 13:44 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysSmsInfoParam extends BaseParam { + + /** + * 主键 + */ + private Long id; + + /** + * 手机号 + */ + private String phoneNumbers; + + /** + * 短信验证码 + */ + private String validateCode; + + /** + * 短信模板ID + */ + private String templateCode; + + /** + * 回执id,可根据该id查询具体的发送状态 + */ + private String bizId; + + /** + * 发送状态(字典 0 未发送,1 发送成功,2 发送失败,3 失效) + */ + private Integer status; + + /** + * 来源(字典 1 app, 2 pc, 3 其他) + */ + private Integer source; + + /** + * 失效时间 + */ + private Date invalidTime; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsSendParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsSendParam.java new file mode 100644 index 0000000..83bd351 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsSendParam.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.param; + +import lombok.Data; +import vip.xiaonuo.sys.modular.sms.enums.SmsSendSourceEnum; +import vip.xiaonuo.sys.modular.sms.enums.SmsTypeEnum; + +import javax.validation.constraints.NotBlank; +import java.util.Map; + +/** + * 发送短信的参数 + * + * @author yubaoshan + * @date 2018/7/5 21:19 + */ +@Data +public class SysSmsSendParam { + + /** + * 手机号 + */ + @NotBlank(message = "手机号码为空,请检查phoneNumbers参数") + private String phoneNumbers; + + /** + * 模板号 + */ + @NotBlank(message = "模板号为空,请检查templateCode参数") + private String templateCode; + + /** + * 模板中的参数 + */ + private Map<String, Object> params; + + /** + * 发送源 + */ + private SmsSendSourceEnum smsSendSourceEnum = SmsSendSourceEnum.PC; + + /** + * 消息类型,1验证码,2消息,默认不传为验证码 + */ + private SmsTypeEnum smsTypeEnum = SmsTypeEnum.SMS; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsVerifyParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsVerifyParam.java new file mode 100644 index 0000000..713a6b1 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/param/SysSmsVerifyParam.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.param; + +import lombok.Data; +import vip.xiaonuo.sys.modular.sms.enums.SmsSendSourceEnum; + +import javax.validation.constraints.NotBlank; + +/** + * 验证短信的参数 + * + * @author yubaoshan + * @date 2018/7/5 21:19 + */ +@Data +public class SysSmsVerifyParam { + + /** + * 手机号 + */ + @NotBlank(message = "手机号不能为空") + private String phoneNumbers; + + /** + * 验证码 + */ + @NotBlank(message = "验证码不能为空") + private String code; + + /** + * 模板号 + */ + @NotBlank(message = "模板号不能为空") + private String templateCode; + + /** + * 来源 + */ + private SmsSendSourceEnum smsSendSourceEnum = SmsSendSourceEnum.PC; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/SmsSenderService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/SmsSenderService.java new file mode 100644 index 0000000..b13ecd8 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/SmsSenderService.java @@ -0,0 +1,70 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.service; + +import vip.xiaonuo.sys.modular.sms.enums.SmsSendStatusEnum; +import vip.xiaonuo.sys.modular.sms.enums.SmsVerifyEnum; +import vip.xiaonuo.sys.modular.sms.param.SysSmsSendParam; +import vip.xiaonuo.sys.modular.sms.param.SysSmsVerifyParam; + +/** + * 短信通知接口 + * + * @author yubaoshan + * @date 2018/7/5 21:05 + */ +public interface SmsSenderService { + + /** + * 发送短信 + * + * @param sysSmsSendParam 短信发送参数 + * @return 是否成功 + * @author yubaoshan + * @date 2018/7/6 16:32 + */ + boolean sendShortMessage(SysSmsSendParam sysSmsSendParam); + + /** + * 验证短信 + * + * @param sysSmsVerifyParam 短信验证参数 + * @return 校验结果 + * @author yubaoshan + * @date 2018/7/6 16:30 + */ + SmsVerifyEnum verifyShortMessage(SysSmsVerifyParam sysSmsVerifyParam); + + /** + * 查看短信发送状态 0=未发送,1=发送成功,2=发送失败 + * + * @param smsId 短信发送记录id + * @return 发送状态 + * @author yubaoshan + * @date 2018/7/6 16:32 + */ + SmsSendStatusEnum getMessageSendStatus(Integer smsId); + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/SysSmsInfoService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/SysSmsInfoService.java new file mode 100644 index 0000000..a2ecb6c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/SysSmsInfoService.java @@ -0,0 +1,84 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.sms.entity.SysSms; +import vip.xiaonuo.sys.modular.sms.enums.SmsSendStatusEnum; +import vip.xiaonuo.sys.modular.sms.enums.SmsVerifyEnum; +import vip.xiaonuo.sys.modular.sms.param.SysSmsInfoParam; +import vip.xiaonuo.sys.modular.sms.param.SysSmsSendParam; +import vip.xiaonuo.sys.modular.sms.param.SysSmsVerifyParam; + +/** + * 系统短信service接口 + * + * @author yubaoshan + * @date 2018/7/5 13:44 + */ +public interface SysSmsInfoService extends IService<SysSms> { + + /** + * 存储短信验证信息 + * + * @param sysSmsSendParam 发送参数 + * @param validateCode 验证码 + * @return 短信记录id + * @author yubaoshan + * @date 2018/7/6 16:47 + */ + Long saveSmsInfo(SysSmsSendParam sysSmsSendParam, String validateCode); + + /** + * 更新短息发送状态 + * + * @param smsId 短信记录id + * @param smsSendStatusEnum 发送状态枚举 + * @author yubaoshan + * @date 2018/7/6 17:12 + */ + void updateSmsInfo(Long smsId, SmsSendStatusEnum smsSendStatusEnum); + + /** + * 校验验证码是否正确 + * + * @param sysSmsVerifyParam 短信校验参数 + * @return 短信校验结果枚举 + * @author yubaoshan + * @date 2018/7/6 17:16 + */ + SmsVerifyEnum validateSmsInfo(SysSmsVerifyParam sysSmsVerifyParam); + + /** + * 短信发送记录查询 + * + * @param sysSmsInfoParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/7/2 12:08 + */ + PageResult<SysSms> page(SysSmsInfoParam sysSmsInfoParam); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/impl/SmsSenderServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/impl/SmsSenderServiceImpl.java new file mode 100644 index 0000000..44fa91c --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/impl/SmsSenderServiceImpl.java @@ -0,0 +1,122 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.log.Log; +import com.alibaba.fastjson.JSON; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.sms.SmsSender; +import vip.xiaonuo.sys.modular.sms.enums.SmsSendStatusEnum; +import vip.xiaonuo.sys.modular.sms.enums.SmsTypeEnum; +import vip.xiaonuo.sys.modular.sms.enums.SmsVerifyEnum; +import vip.xiaonuo.sys.modular.sms.param.SysSmsSendParam; +import vip.xiaonuo.sys.modular.sms.param.SysSmsVerifyParam; +import vip.xiaonuo.sys.modular.sms.service.SmsSenderService; +import vip.xiaonuo.sys.modular.sms.service.SysSmsInfoService; + +import javax.annotation.Resource; +import java.util.Map; + + +/** + * 短信发送接口实现类 + * + * @author yubaoshan + * @date 2018/7/5 21:05 + */ +@Component +public class SmsSenderServiceImpl implements SmsSenderService { + + private static final Log log = Log.get(); + + @Resource + private SmsSender smsSender; + + @Resource + private SysSmsInfoService sysSmsInfoService; + + @Transactional(rollbackFor = Exception.class) + @Override + public boolean sendShortMessage(SysSmsSendParam sysSmsSendParam) { + + Map<String, Object> params = sysSmsSendParam.getParams(); + log.info(">>> 发送短信Provider接口,参数为:" + JSON.toJSONString(sysSmsSendParam)); + + //如果是纯消息发送,直接发送 + if (SmsTypeEnum.MESSAGE.equals(sysSmsSendParam.getSmsTypeEnum())) { + smsSender.sendSms(sysSmsSendParam.getPhoneNumbers(), sysSmsSendParam.getTemplateCode(), params); + log.info(">>> 发送短信Provider接口--message,params的map具体为:" + JSON.toJSONString(params)); + } else { + + //如果是验证类消息发送,需要存储到数据库验证码信息 + String validateCode; + + //如果传的参数中没有code,就初始化一个code放到参数map里 + if (params != null && params.get(CommonConstant.CODE) != null) { + validateCode = params.get(CommonConstant.CODE).toString(); + } else { + validateCode = RandomUtil.randomNumbers(6); + if (params == null) { + params = CollectionUtil.newHashMap(); + } + params.put(CommonConstant.CODE, validateCode); + } + + log.info(">>> 发送短信Provider接口,params的map具体为:" + JSON.toJSONString(params)); + log.info(">>> 发送短信Provider接口,验证码为:" + validateCode); + + //存储短信到数据库 + Long smsId = sysSmsInfoService.saveSmsInfo(sysSmsSendParam, validateCode); + + log.info(">>> 开始发送短信:发送的电话号码= " + sysSmsSendParam.getPhoneNumbers() + ",发送的模板号=" + sysSmsSendParam.getTemplateCode() + ",发送的参数是:" + JSON.toJSONString(params)); + + //发送短信 + smsSender.sendSms(sysSmsSendParam.getPhoneNumbers(), sysSmsSendParam.getTemplateCode(), params); + + //更新短信发送状态 + sysSmsInfoService.updateSmsInfo(smsId, SmsSendStatusEnum.SUCCESS); + } + + return true; + } + + @Override + public SmsVerifyEnum verifyShortMessage(SysSmsVerifyParam sysSmsVerifyParam) { + log.info(">>> 验证短信Provider接口,参数为:" + JSON.toJSONString(sysSmsVerifyParam)); + SmsVerifyEnum smsVerifyEnum = sysSmsInfoService.validateSmsInfo(sysSmsVerifyParam); + log.info(">>> 验证短信Provider接口,响应结果为:" + JSON.toJSONString(smsVerifyEnum)); + return smsVerifyEnum; + } + + @Override + public SmsSendStatusEnum getMessageSendStatus(Integer smsId) { + return null; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/impl/SysSmsInfoServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/impl/SysSmsInfoServiceImpl.java new file mode 100644 index 0000000..655be46 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/sms/service/impl/SysSmsInfoServiceImpl.java @@ -0,0 +1,193 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.sms.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.log.Log; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.pojo.sms.AliyunSmsConfigs; +import vip.xiaonuo.sys.modular.sms.entity.SysSms; +import vip.xiaonuo.sys.modular.sms.enums.SmsSendExceptionEnum; +import vip.xiaonuo.sys.modular.sms.enums.SmsSendStatusEnum; +import vip.xiaonuo.sys.modular.sms.enums.SmsVerifyEnum; +import vip.xiaonuo.sys.modular.sms.mapper.SysSmsMapper; +import vip.xiaonuo.sys.modular.sms.param.SysSmsInfoParam; +import vip.xiaonuo.sys.modular.sms.param.SysSmsSendParam; +import vip.xiaonuo.sys.modular.sms.param.SysSmsVerifyParam; +import vip.xiaonuo.sys.modular.sms.service.SysSmsInfoService; + +import java.util.Date; +import java.util.List; + +/** + * 系统短信接口实现类 + * + * @author yubaoshan + * @date 2018/7/5 13:44 + */ + +@Service +public class SysSmsInfoServiceImpl extends ServiceImpl<SysSmsMapper, SysSms> implements SysSmsInfoService { + + private static final Log log = Log.get(); + + @Override + public Long saveSmsInfo(SysSmsSendParam sysSmsSendParam, String validateCode) { + + if (ObjectUtil.isEmpty(sysSmsSendParam.getPhoneNumbers())) { + throw new ServiceException(SmsSendExceptionEnum.PHONE_NUMBER_EMPTY); + } + if (ObjectUtil.isEmpty(validateCode)) { + throw new ServiceException(SmsSendExceptionEnum.VALIDATE_CODE_EMPTY); + } + if (ObjectUtil.isEmpty(sysSmsSendParam.getTemplateCode())) { + throw new ServiceException(SmsSendExceptionEnum.TEMPLATE_CODE_EMPTY); + } + + //当前时间 + Date nowDate = new Date(); + + //短信失效时间 + AliyunSmsConfigs aliyunSmsProperties = ConstantContextHolder.getAliyunSmsConfigs(); + long invalidateTime = nowDate.getTime() + aliyunSmsProperties.getInvalidateMinutes() * 60 * 1000; + Date invalidate = new Date(invalidateTime); + + SysSms sysSms = new SysSms(); + sysSms.setCreateTime(nowDate); + sysSms.setInvalidTime(invalidate); + sysSms.setPhoneNumbers(sysSmsSendParam.getPhoneNumbers()); + sysSms.setStatus(SmsSendStatusEnum.WAITING.getCode()); + sysSms.setSource(sysSmsSendParam.getSmsSendSourceEnum().getCode()); + sysSms.setTemplateCode(sysSmsSendParam.getTemplateCode()); + sysSms.setValidateCode(validateCode); + + this.save(sysSms); + + log.info(">>> 发送短信,存储短信到数据库,数据为:" + JSON.toJSONString(sysSms)); + + return sysSms.getId(); + } + + @Override + public void updateSmsInfo(Long smsId, SmsSendStatusEnum smsSendStatusEnum) { + SysSms sysSms = this.getById(smsId); + sysSms.setStatus(smsSendStatusEnum.getCode()); + this.updateById(sysSms); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public SmsVerifyEnum validateSmsInfo(SysSmsVerifyParam sysSmsVerifyParam) { + + if (ObjectUtil.isEmpty(sysSmsVerifyParam.getPhoneNumbers())) { + throw new ServiceException(SmsSendExceptionEnum.PHONE_NUMBER_EMPTY); + } + if (ObjectUtil.isEmpty(sysSmsVerifyParam)) { + throw new ServiceException(SmsSendExceptionEnum.VALIDATE_CODE_EMPTY); + } + if (ObjectUtil.isEmpty(sysSmsVerifyParam.getTemplateCode())) { + throw new ServiceException(SmsSendExceptionEnum.TEMPLATE_CODE_EMPTY); + } + + //查询有没有这条记录 + LambdaQueryWrapper<SysSms> smsQueryWrapper = new LambdaQueryWrapper<>(); + + smsQueryWrapper.eq(SysSms::getPhoneNumbers, sysSmsVerifyParam.getPhoneNumbers()) + .and(f -> f.eq(SysSms::getSource, sysSmsVerifyParam.getSmsSendSourceEnum().getCode())) + .and(f -> f.eq(SysSms::getTemplateCode, sysSmsVerifyParam.getTemplateCode())); + + smsQueryWrapper.orderByDesc(SysSms::getCreateTime); + + List<SysSms> sysSmsList = this.list(smsQueryWrapper); + + log.info(">>> 验证短信Provider接口,查询到sms记录:" + JSON.toJSONString(sysSmsList)); + + //如果找不到记录,提示验证失败 + if (ObjectUtil.isEmpty(sysSmsList)) { + log.info(">>> 验证短信Provider接口,找不到验证码记录,响应验证失败!"); + return SmsVerifyEnum.ERROR; + } else { + + //获取最近发送的第一条 + SysSms sysSms = sysSmsList.get(0); + + //先判断状态是不是失效的状态 + if (SmsSendStatusEnum.INVALID.getCode().equals(sysSms.getStatus())) { + log.info(">>> 验证短信Provider接口,短信状态是失效,响应验证失败!"); + return SmsVerifyEnum.ERROR; + } + + //如果验证码和传过来的不一致 + if (!sysSmsVerifyParam.getCode().equals(sysSms.getValidateCode())) { + log.info(">>> 验证短信Provider接口,验证手机号和验证码不一致,响应验证失败!"); + return SmsVerifyEnum.ERROR; + } + + //判断是否超时 + Date invalidTime = sysSms.getInvalidTime(); + if (ObjectUtil.isEmpty(invalidTime) || new Date().after(invalidTime)) { + log.info(">>> 验证短信Provider接口,验证码超时,响应验证失败!"); + return SmsVerifyEnum.EXPIRED; + } + + //验证成功把短信设置成失效 + sysSms.setStatus(SmsSendStatusEnum.INVALID.getCode()); + this.updateById(sysSms); + + log.info(">>> 验证短信Provider接口,验证码验证成功!"); + return SmsVerifyEnum.SUCCESS; + } + } + + @Override + public PageResult<SysSms> page(SysSmsInfoParam sysSmsInfoParam) { + LambdaQueryWrapper<SysSms> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysSmsInfoParam)) { + //根据手机号模糊查询 + if (ObjectUtil.isNotEmpty(sysSmsInfoParam.getPhoneNumbers())) { + queryWrapper.like(SysSms::getPhoneNumbers, sysSmsInfoParam.getPhoneNumbers()); + } + //根据发送状态查询(字典 0 未发送,1 发送成功,2 发送失败,3 失效) + if (ObjectUtil.isNotEmpty(sysSmsInfoParam.getStatus())) { + queryWrapper.eq(SysSms::getStatus, sysSmsInfoParam.getStatus()); + } + //根据来源查询(字典 1 app, 2 pc, 3 其他) + if (ObjectUtil.isNotEmpty(sysSmsInfoParam.getSource())) { + queryWrapper.eq(SysSms::getSource, sysSmsInfoParam.getSource()); + } + } + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/controller/SysTimersController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/controller/SysTimersController.java new file mode 100644 index 0000000..71abb49 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/controller/SysTimersController.java @@ -0,0 +1,168 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.timer.param.SysTimersParam; +import vip.xiaonuo.sys.modular.timer.service.SysTimersService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 定时任务 控制器 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ +@RestController +public class SysTimersController { + + @Resource + private SysTimersService sysTimersService; + + /** + * 分页查询定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + @GetMapping("/sysTimers/page") + @BusinessLog(title = "定时任务_分页查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(SysTimersParam sysTimersParam) { + return new SuccessResponseData(sysTimersService.page(sysTimersParam)); + } + + /** + * 获取全部定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + @GetMapping("/sysTimers/list") + @BusinessLog(title = "定时任务_查询所有", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(SysTimersParam sysTimersParam) { + return new SuccessResponseData(sysTimersService.list(sysTimersParam)); + } + + /** + * 查看详情定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + @GetMapping("/sysTimers/detail") + @BusinessLog(title = "定时任务_查看详情", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(SysTimersParam.detail.class) SysTimersParam sysTimersParam) { + return new SuccessResponseData(sysTimersService.detail(sysTimersParam)); + } + + /** + * 添加定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + @PostMapping("/sysTimers/add") + @BusinessLog(title = "定时任务_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysTimersParam.add.class) SysTimersParam sysTimersParam) { + sysTimersService.add(sysTimersParam); + return new SuccessResponseData(); + } + + /** + * 删除定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + @PostMapping("/sysTimers/delete") + @BusinessLog(title = "定时任务_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysTimersParam.delete.class) SysTimersParam sysTimersParam) { + sysTimersService.delete(sysTimersParam); + return new SuccessResponseData(); + } + + /** + * 编辑定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + @PostMapping("/sysTimers/edit") + @BusinessLog(title = "定时任务_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysTimersParam.edit.class) SysTimersParam sysTimersParam) { + sysTimersService.edit(sysTimersParam); + return new SuccessResponseData(); + } + + /** + * 获取系统的所有任务列表 + * + * @author yubaoshan + * @date 2020/7/1 14:34 + */ + @PostMapping("/sysTimers/getActionClasses") + @BusinessLog(title = "定时任务_任务列表", opType = LogAnnotionOpTypeEnum.OTHER) + public ResponseData getActionClasses() { + List<String> actionClasses = sysTimersService.getActionClasses(); + return new SuccessResponseData(actionClasses); + } + + /** + * 启动定时任务 + * + * @author yubaoshan + * @date 2020/7/1 14:34 + */ + @PostMapping("/sysTimers/start") + @BusinessLog(title = "定时任务_启动", opType = LogAnnotionOpTypeEnum.OTHER) + public ResponseData start(@RequestBody @Validated(SysTimersParam.start.class) SysTimersParam sysTimersParam) { + sysTimersService.start(sysTimersParam); + return new SuccessResponseData(); + } + + /** + * 停止定时任务 + * + * @author yubaoshan + * @date 2020/7/1 14:34 + */ + @PostMapping("/sysTimers/stop") + @BusinessLog(title = "定时任务_关闭", opType = LogAnnotionOpTypeEnum.OTHER) + public ResponseData stop(@RequestBody @Validated(SysTimersParam.stop.class) SysTimersParam sysTimersParam) { + sysTimersService.stop(sysTimersParam); + return new SuccessResponseData(); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/entity/SysTimers.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/entity/SysTimers.java new file mode 100644 index 0000000..50e11db --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/entity/SysTimers.java @@ -0,0 +1,77 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +/** + * 定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("sys_timers") +public class SysTimers extends BaseEntity { + + + /** + * 定时器id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 任务名称 + */ + private String timerName; + + /** + * 执行任务的class的类名(实现了TimerTaskRunner接口的类的全称) + */ + private String actionClass; + + /** + * 定时任务表达式 + */ + private String cron; + + /** + * 状态(字典 1运行 2停止) + */ + private Integer jobStatus; + + /** + * 备注信息 + */ + private String remark; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/enums/TimerJobStatusEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/enums/TimerJobStatusEnum.java new file mode 100644 index 0000000..8fa9c33 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/enums/TimerJobStatusEnum.java @@ -0,0 +1,54 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.enums; + +import lombok.Getter; + +/** + * 定时任务的状态 + * + * @author yubaoshan + * @date 2020/6/30 20:44 + */ +@Getter +public enum TimerJobStatusEnum { + + /** + * 启动状态 + */ + RUNNING(1), + + /** + * 停止状态 + */ + STOP(2); + + private final Integer code; + + TimerJobStatusEnum(int code) { + this.code = code; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/enums/exp/SysTimersExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/enums/exp/SysTimersExceptionEnum.java new file mode 100644 index 0000000..e6a4c54 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/enums/exp/SysTimersExceptionEnum.java @@ -0,0 +1,74 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.enums.exp; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 定时任务相关枚举 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.TIMER_EXCEPTION_ENUM) +public enum SysTimersExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 该条记录不存在 + */ + NOT_EXISTED(1, "您查询的该条记录不存在"), + + /** + * 定时任务执行类不存在 + */ + TIMER_NOT_EXISTED(2, "定时任务执行类不存在"), + + /** + * 检查定时任务启动时候的参数是否传了 + */ + EXE_EMPTY_PARAM(3, "请检查定时器的id,定时器cron表达式,定时任务是否为空!"); + + private final Integer code; + + private final String message; + + SysTimersExceptionEnum(int code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/mapper/SysTimersMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/mapper/SysTimersMapper.java new file mode 100644 index 0000000..7a15199 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/mapper/SysTimersMapper.java @@ -0,0 +1,40 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.timer.entity.SysTimers; + +/** + * <p> + * 定时任务 Mapper 接口 + * </p> + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ +public interface SysTimersMapper extends BaseMapper<SysTimers> { + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/mapper/mapping/SysTimersMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/mapper/mapping/SysTimersMapper.xml new file mode 100644 index 0000000..6b70cc4 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/mapper/mapping/SysTimersMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.timer.mapper.SysTimersMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/param/SysTimersParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/param/SysTimersParam.java new file mode 100644 index 0000000..36dfa7d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/param/SysTimersParam.java @@ -0,0 +1,78 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysTimersParam extends BaseParam { + + /** + * 定时器id + */ + @NotNull(message = "主键id不能为空,请检查id字段", groups = {edit.class, detail.class, delete.class, start.class, stop.class}) + private Long id; + + /** + * 任务名称 + */ + @NotBlank(message = "任务名称不能为空,请检查timerName字段", groups = {add.class, edit.class}) + private String timerName; + + /** + * 执行任务的class的类名(实现了TimerTaskRunner接口的类的全称) + */ + @NotBlank(message = "任务的class的类名不能为空,请检查actionClass字段", groups = {add.class, edit.class}) + private String actionClass; + + /** + * 定时任务表达式 + */ + @NotBlank(message = "定时任务表达式不能为空,请检查cron字段", groups = {add.class, edit.class}) + private String cron; + + /** + * 状态(字典 1运行 2停止) + */ + private Integer jobStatus; + + /** + * 备注信息 + */ + private String remark; + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/SysTimersService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/SysTimersService.java new file mode 100644 index 0000000..61327b8 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/SysTimersService.java @@ -0,0 +1,126 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.timer.entity.SysTimers; +import vip.xiaonuo.sys.modular.timer.param.SysTimersParam; + +import java.util.List; + +/** + * 定时任务 服务类 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ +public interface SysTimersService extends IService<SysTimers> { + + /** + * 分页查询定时任务 + * + * @param sysTimersParam 查询参数 + * @return 查询分页结果 + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + PageResult<SysTimers> page(SysTimersParam sysTimersParam); + + /** + * 查询所有定时任务 + * + * @param sysTimersParam 查询参数 + * @return 定时任务列表 + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + List<SysTimers> list(SysTimersParam sysTimersParam); + + /** + * 添加定时任务 + * + * @param sysTimersParam 添加参数 + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + void add(SysTimersParam sysTimersParam); + + /** + * 删除定时任务 + * + * @param sysTimersParam 删除参数 + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + void delete(SysTimersParam sysTimersParam); + + /** + * 编辑定时任务 + * + * @param sysTimersParam 编辑参数 + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + void edit(SysTimersParam sysTimersParam); + + /** + * 查看详情定时任务 + * + * @param sysTimersParam 查看参数 + * @return 定时任务 + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + SysTimers detail(SysTimersParam sysTimersParam); + + /** + * 启动任务 + * + * @param sysTimersParam 启动参数 + * @author yubaoshan + * @date 2020/7/1 14:36 + */ + void start(SysTimersParam sysTimersParam); + + /** + * 停止任务 + * + * @param sysTimersParam 停止参数 + * @author yubaoshan + * @date 2020/7/1 14:36 + */ + void stop(SysTimersParam sysTimersParam); + + /** + * 获取所有可执行的任务列表 + * + * @return TimerTaskRunner的所有子类名称集合 + * @author yubaoshan + * @date 2020/7/1 14:36 + */ + List<String> getActionClasses(); + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/TimerExeService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/TimerExeService.java new file mode 100644 index 0000000..d46b751 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/TimerExeService.java @@ -0,0 +1,61 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.service; + +/** + * 本接口用来,屏蔽定时任务的多样性 + * <p> + * 目前用hutool,不排除以后用别的 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ +public interface TimerExeService { + + /** + * 启动一个定时器 + * <p> + * 定时任务表达式书写规范:0/2 * * * * * + * <p> + * 六位数,分别是:秒 分 小时 日 月 年 + * + * @param taskId 任务id + * @param cron cron表达式 + * @param className 类的全名,必须是TimerTaskRunner的子类 + * @author yubaoshan + * @date 2020/7/1 13:51 + */ + void startTimer(String taskId, String cron, String className); + + /** + * 停止一个定时器 + * + * @param taskId 定时任务Id + * @author yubaoshan + * @date 2020/7/1 14:08 + */ + void stopTimer(String taskId); + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/impl/HutoolTimerExeServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/impl/HutoolTimerExeServiceImpl.java new file mode 100644 index 0000000..45df382 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/impl/HutoolTimerExeServiceImpl.java @@ -0,0 +1,82 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.cron.CronUtil; +import cn.hutool.cron.task.Task; +import cn.hutool.extra.spring.SpringUtil; +import cn.hutool.log.Log; +import org.springframework.stereotype.Service; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.timer.TimerTaskRunner; +import vip.xiaonuo.sys.modular.timer.enums.exp.SysTimersExceptionEnum; +import vip.xiaonuo.sys.modular.timer.service.TimerExeService; + +/** + * hutool方式的定时任务执行 + * + * @author yubaoshan + * @date 2020/7/1 13:48 + */ +@Service +public class HutoolTimerExeServiceImpl implements TimerExeService { + + private static final Log log = Log.get(); + + @Override + public void startTimer(String taskId, String cron, String className) { + + if (ObjectUtil.hasEmpty(taskId, cron, className)) { + throw new ServiceException(SysTimersExceptionEnum.EXE_EMPTY_PARAM); + } + + // 预加载类看是否存在此定时任务类 + try { + Class.forName(className); + } catch (ClassNotFoundException e) { + throw new ServiceException(SysTimersExceptionEnum.TIMER_NOT_EXISTED); + } + + // 定义hutool的任务 + Task task = () -> { + try { + TimerTaskRunner timerTaskRunner = (TimerTaskRunner) SpringUtil.getBean(Class.forName(className)); + timerTaskRunner.action(); + } catch (ClassNotFoundException e) { + log.error(">>> 任务执行异常:{}", e.getMessage()); + } + }; + + // 开始执行任务 + CronUtil.schedule(taskId, cron, task); + } + + @Override + public void stopTimer(String taskId) { + CronUtil.remove(taskId); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/impl/SysTimersServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/impl/SysTimersServiceImpl.java new file mode 100644 index 0000000..5d8188a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/service/impl/SysTimersServiceImpl.java @@ -0,0 +1,216 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.cron.CronUtil; +import cn.hutool.extra.spring.SpringUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.timer.TimerTaskRunner; +import vip.xiaonuo.sys.modular.timer.entity.SysTimers; +import vip.xiaonuo.sys.modular.timer.enums.TimerJobStatusEnum; +import vip.xiaonuo.sys.modular.timer.enums.exp.SysTimersExceptionEnum; +import vip.xiaonuo.sys.modular.timer.mapper.SysTimersMapper; +import vip.xiaonuo.sys.modular.timer.param.SysTimersParam; +import vip.xiaonuo.sys.modular.timer.service.SysTimersService; +import vip.xiaonuo.sys.modular.timer.service.TimerExeService; + +import javax.annotation.Resource; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 定时任务 服务实现类 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ +@Service +public class SysTimersServiceImpl extends ServiceImpl<SysTimersMapper, SysTimers> implements SysTimersService { + + @Resource + private TimerExeService timerExeService; + + @Override + public PageResult<SysTimers> page(SysTimersParam sysTimersParam) { + + // 构造条件 + LambdaQueryWrapper<SysTimers> queryWrapper = new LambdaQueryWrapper<>(); + + if (ObjectUtil.isNotNull(sysTimersParam)) { + + // 拼接查询条件-任务名称 + if (ObjectUtil.isNotEmpty(sysTimersParam.getTimerName())) { + queryWrapper.like(SysTimers::getTimerName, sysTimersParam.getTimerName()); + } + + // 拼接查询条件-状态(字典 1运行 2停止) + if (ObjectUtil.isNotEmpty(sysTimersParam.getJobStatus())) { + queryWrapper.eq(SysTimers::getJobStatus, sysTimersParam.getJobStatus()); + } + } + + // 查询分页结果 + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<SysTimers> list(SysTimersParam sysTimersParam) { + + // 构造条件 + LambdaQueryWrapper<SysTimers> queryWrapper = new LambdaQueryWrapper<>(); + + if (ObjectUtil.isNotNull(sysTimersParam)) { + + // 拼接查询条件-任务名称 + if (ObjectUtil.isNotEmpty(sysTimersParam.getTimerName())) { + queryWrapper.like(SysTimers::getTimerName, sysTimersParam.getTimerName()); + } + + // 拼接查询条件-状态(字典 1运行 2停止) + if (ObjectUtil.isNotEmpty(sysTimersParam.getJobStatus())) { + queryWrapper.eq(SysTimers::getJobStatus, sysTimersParam.getJobStatus()); + } + } + + return this.list(queryWrapper); + } + + @Override + public void add(SysTimersParam sysTimersParam) { + + // 将dto转为实体 + SysTimers sysTimers = new SysTimers(); + BeanUtil.copyProperties(sysTimersParam, sysTimers); + + // 设置为停止状态,点击启动时启动任务 + sysTimers.setJobStatus(TimerJobStatusEnum.STOP.getCode()); + + this.save(sysTimers); + } + + @Override + public void delete(SysTimersParam sysTimersParam) { + + // 先停止id为参数id的定时器 + CronUtil.remove(String.valueOf(sysTimersParam.getId())); + + this.removeById(sysTimersParam.getId()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(SysTimersParam sysTimersParam) { + + // 更新库中记录 + SysTimers oldTimer = this.querySysTimers(sysTimersParam); + // 查看被编辑的任务的状态 + Integer jobStatus = oldTimer.getJobStatus(); + BeanUtil.copyProperties(sysTimersParam, oldTimer); + oldTimer.setJobStatus(jobStatus); + this.updateById(oldTimer); + + // 如果任务正在运行,则停掉这个任务,重新运行任务 + if (jobStatus.equals(TimerJobStatusEnum.RUNNING.getCode())) { + CronUtil.remove(String.valueOf(oldTimer.getId())); + timerExeService.startTimer( + String.valueOf(sysTimersParam.getId()), + sysTimersParam.getCron(), + sysTimersParam.getActionClass()); + } + } + + @Override + public SysTimers detail(SysTimersParam sysTimersParam) { + return this.querySysTimers(sysTimersParam); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void start(SysTimersParam sysTimersParam) { + + // 更新库中的状态 + LambdaUpdateWrapper<SysTimers> wrapper = new LambdaUpdateWrapper<>(); + wrapper.set(SysTimers::getJobStatus, TimerJobStatusEnum.RUNNING.getCode()) + .eq(SysTimers::getId, sysTimersParam.getId()); + this.update(wrapper); + + // 添加定时任务调度 + SysTimers sysTimers = this.querySysTimers(sysTimersParam); + timerExeService.startTimer(String.valueOf(sysTimers.getId()), sysTimers.getCron(), sysTimers.getActionClass()); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void stop(SysTimersParam sysTimersParam) { + + // 更新库中的状态 + LambdaUpdateWrapper<SysTimers> wrapper = new LambdaUpdateWrapper<>(); + wrapper.set(SysTimers::getJobStatus, TimerJobStatusEnum.STOP.getCode()) + .eq(SysTimers::getId, sysTimersParam.getId()); + this.update(wrapper); + + // 关闭定时任务调度 + SysTimers sysTimers = this.querySysTimers(sysTimersParam); + timerExeService.stopTimer(String.valueOf(sysTimers.getId())); + } + + @Override + public List<String> getActionClasses() { + Map<String, TimerTaskRunner> timerTaskRunnerMap = SpringUtil.getBeansOfType(TimerTaskRunner.class); + if (ObjectUtil.isNotEmpty(timerTaskRunnerMap)) { + Collection<TimerTaskRunner> values = timerTaskRunnerMap.values(); + return values.stream().map(i -> i.getClass().getName()).collect(Collectors.toList()); + } else { + return CollectionUtil.newArrayList(); + } + } + + /** + * 获取定时任务 + * + * @author yubaoshan + * @date 2020/6/30 18:26 + */ + private SysTimers querySysTimers(SysTimersParam sysTimersParam) { + SysTimers sysTimers = this.getById(sysTimersParam.getId()); + if (ObjectUtil.isEmpty(sysTimers)) { + throw new ServiceException(SysTimersExceptionEnum.NOT_EXISTED); + } + return sysTimers; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/tasks/RefreshConstantsTaskRunner.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/tasks/RefreshConstantsTaskRunner.java new file mode 100644 index 0000000..d90659d --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/tasks/RefreshConstantsTaskRunner.java @@ -0,0 +1,71 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.tasks; + +import cn.hutool.log.Log; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Component; +import vip.xiaonuo.core.context.constant.ConstantContext; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.timer.TimerTaskRunner; +import vip.xiaonuo.sys.modular.consts.entity.SysConfig; +import vip.xiaonuo.sys.modular.consts.service.SysConfigService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 定时器:用来根据sys_config表中的配置,刷新ConstantContextHolder中的缓存 + * <p> + * 防止由于直接改动数据库,而调用缓存常量时,产生数据不一致问题 + * + * @author xuyuxiang + * @date 2020/7/30 16:41 + */ +@Component +public class RefreshConstantsTaskRunner implements TimerTaskRunner { + + private static final Log log = Log.get(); + + @Resource + private SysConfigService sysConfigService; + + @Override + public void action() { + + // 查询库中的所有配置 + LambdaQueryWrapper<SysConfig> sysConfigLambdaQueryWrapper = new LambdaQueryWrapper<>(); + + sysConfigLambdaQueryWrapper.eq(SysConfig::getStatus, CommonStatusEnum.ENABLE.getCode()); + sysConfigLambdaQueryWrapper.select(SysConfig::getCode, SysConfig::getValue); + + List<SysConfig> list = sysConfigService.list(sysConfigLambdaQueryWrapper); + + // 所有配置添加到缓存中,覆盖已有配置 + list.forEach(sysConfig -> ConstantContext.putConstant(sysConfig.getCode(), sysConfig.getValue())); + + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/tasks/SystemOutTaskRunner.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/tasks/SystemOutTaskRunner.java new file mode 100644 index 0000000..8c1d8be --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/timer/tasks/SystemOutTaskRunner.java @@ -0,0 +1,44 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.timer.tasks; + +import org.springframework.stereotype.Component; +import vip.xiaonuo.core.timer.TimerTaskRunner; + +/** + * 这是一个定时任务的示例程序 + * + * @author yubaoshan + * @date 2020/6/30 22:09 + */ +@Component +public class SystemOutTaskRunner implements TimerTaskRunner { + + @Override + public void action() { + System.out.println("一直往南方开!一直往南方开!"); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/controller/SysUserController.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/controller/SysUserController.java new file mode 100644 index 0000000..6345716 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/controller/SysUserController.java @@ -0,0 +1,281 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.controller; + +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.DataScope; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.annotion.Wrapper; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.sys.modular.user.param.SysUserParam; +import vip.xiaonuo.sys.modular.user.service.SysUserService; +import vip.xiaonuo.sys.modular.user.wrapper.SysUserWrapper; +import javax.annotation.Resource; +import java.util.List; + +/** + * 系统用户控制器 + * + * @author xuyuxiang + * @date 2020/3/19 21:14 + */ +@RestController +public class SysUserController { + + @Resource + private SysUserService sysUserService; + + /** + * 查询系统用户 + * + * @author xuyuxiang + * @date 2020/3/20 21:00 + */ + @Permission + @DataScope + @GetMapping("/sysUser/page") + @BusinessLog(title = "系统用户_查询", opType = LogAnnotionOpTypeEnum.QUERY) + @Wrapper(SysUserWrapper.class) + public ResponseData page(SysUserParam sysUserParam) { + return new SuccessResponseData(sysUserService.page(sysUserParam)); + } + + /** + * 增加系统用户 + * + * @author xuyuxiang + * @date 2020/3/23 16:28 + */ + @Permission + @DataScope + @PostMapping("/sysUser/add") + @BusinessLog(title = "系统用户_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(SysUserParam.add.class) SysUserParam sysUserParam) { + sysUserService.add(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 删除系统用户 + * + * @author xuyuxiang yubaoshan + * @date 2020/3/23 16:28 + */ + @Permission + @DataScope + @PostMapping("/sysUser/delete") + @BusinessLog(title = "系统用户_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(SysUserParam.delete.class) List<SysUserParam> sysUserParamList) { + sysUserService.delete(sysUserParamList); + return new SuccessResponseData(); + } + + /** + * 编辑系统用户 + * + * @author xuyuxiang + * @date 2020/3/23 16:28 + */ + @Permission + @DataScope + @PostMapping("/sysUser/edit") + @BusinessLog(title = "系统用户_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(SysUserParam.edit.class) SysUserParam sysUserParam) { + sysUserService.edit(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 查看系统用户 + * + * @author xuyuxiang + * @date 2020/3/23 16:28 + */ + @Permission + @GetMapping("/sysUser/detail") + @BusinessLog(title = "系统用户_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + @Wrapper(SysUserWrapper.class) + public ResponseData detail(@Validated(SysUserParam.detail.class) SysUserParam sysUserParam) { + return new SuccessResponseData(sysUserService.detail(sysUserParam)); + } + + /** + * 修改状态 + * + * @author xuyuxiang + * @date 2020/5/25 14:32 + */ + @Permission + @PostMapping("/sysUser/changeStatus") + @BusinessLog(title = "系统用户_修改状态", opType = LogAnnotionOpTypeEnum.CHANGE_STATUS) + public ResponseData changeStatus(@RequestBody @Validated(SysUserParam.changeStatus.class) SysUserParam sysUserParam) { + sysUserService.changeStatus(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 授权角色 + * + * @author xuyuxiang + * @date 2020/3/28 16:05 + */ + @Permission + @DataScope + @PostMapping("/sysUser/grantRole") + @BusinessLog(title = "系统用户_授权角色", opType = LogAnnotionOpTypeEnum.GRANT) + public ResponseData grantRole(@RequestBody @Validated(SysUserParam.grantRole.class) SysUserParam sysUserParam) { + sysUserService.grantRole(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 授权数据 + * + * @author xuyuxiang + * @date 2020/3/28 16:05 + */ + @Permission + @DataScope + @PostMapping("/sysUser/grantData") + @BusinessLog(title = "系统用户_授权数据", opType = LogAnnotionOpTypeEnum.GRANT) + public ResponseData grantData(@RequestBody @Validated(SysUserParam.grantData.class) SysUserParam sysUserParam) { + sysUserService.grantData(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 更新信息 + * + * @author xuyuxiang + * @date 2020/4/1 14:27 + */ + @PostMapping("/sysUser/updateInfo") + @BusinessLog(title = "系统用户_更新信息", opType = LogAnnotionOpTypeEnum.UPDATE) + public ResponseData updateInfo(@RequestBody @Validated(SysUserParam.updateInfo.class) SysUserParam sysUserParam) { + sysUserService.updateInfo(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 修改密码 + * + * @author xuyuxiang + * @date 2020/4/1 14:42 + */ + @PostMapping("/sysUser/updatePwd") + @BusinessLog(title = "系统用户_修改密码", opType = LogAnnotionOpTypeEnum.UPDATE) + public ResponseData updatePwd(@RequestBody @Validated(SysUserParam.updatePwd.class) SysUserParam sysUserParam) { + sysUserService.updatePwd(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 拥有角色 + * + * @author xuyuxiang + * @date 2020/3/28 14:46 + */ + @Permission + @GetMapping("/sysUser/ownRole") + @BusinessLog(title = "系统用户_拥有角色", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData ownRole(@Validated(SysUserParam.detail.class) SysUserParam sysUserParam) { + return new SuccessResponseData(sysUserService.ownRole(sysUserParam)); + } + + /** + * 拥有数据 + * + * @author xuyuxiang + * @date 2020/3/28 14:46 + */ + @Permission + @GetMapping("/sysUser/ownData") + @BusinessLog(title = "系统用户_拥有数据", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData ownData(@Validated(SysUserParam.detail.class) SysUserParam sysUserParam) { + return new SuccessResponseData(sysUserService.ownData(sysUserParam)); + } + + /** + * 重置密码 + * + * @author xuyuxiang + * @date 2020/4/1 14:42 + */ + @Permission + @PostMapping("/sysUser/resetPwd") + @BusinessLog(title = "系统用户_重置密码", opType = LogAnnotionOpTypeEnum.UPDATE) + public ResponseData resetPwd(@RequestBody @Validated(SysUserParam.resetPwd.class) SysUserParam sysUserParam) { + sysUserService.resetPwd(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 修改头像 + * + * @author xuyuxiang + * @date 2020/6/28 15:19 + */ + @PostMapping("/sysUser/updateAvatar") + @BusinessLog(title = "系统用户_修改头像", opType = LogAnnotionOpTypeEnum.UPDATE) + public ResponseData updateAvatar(@RequestBody @Validated(SysUserParam.updateAvatar.class) SysUserParam sysUserParam) { + sysUserService.updateAvatar(sysUserParam); + return new SuccessResponseData(); + } + + /** + * 导出系统用户 + * + * @author xuyuxiang + * @date 2020/6/30 16:07 + */ + @Permission + @GetMapping("/sysUser/export") + @BusinessLog(title = "系统用户_导出", opType = LogAnnotionOpTypeEnum.EXPORT) + public void export(SysUserParam sysUserParam) { + sysUserService.export(sysUserParam); + } + + + /** + * 用户选择器 + * + * @author xuyuxiang + * @date 2020/7/3 13:17 + */ + @Permission + @GetMapping("/sysUser/selector") + @BusinessLog(title = "系统用户_选择器", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData selector(SysUserParam sysUserParam) { + return new SuccessResponseData(sysUserService.selector(sysUserParam)); + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUser.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUser.java new file mode 100644 index 0000000..5515624 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUser.java @@ -0,0 +1,136 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.entity; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.entity.BaseEntity; + +import java.util.Date; + +/** + * 系统用户表 + * + * @author xuyuxiang + * @date 2020/3/5 12:25 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@TableName("sys_user") +public class SysUser extends BaseEntity { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 账号 + */ + @Excel(name = "账号", width = 20) + private String account; + + /** + * 密码哈希值 + */ + private String pwdHashValue; + + /** + * 昵称 + */ + @Excel(name = "昵称", width = 20) + private String nickName; + + /** + * 姓名 + */ + @Excel(name = "姓名", width = 20) + private String name; + + /** + * 头像 + */ + private Long avatar; + + /** + * 生日 + */ + @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED) + @Excel(name = "生日", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd", width = 20) + private Date birthday; + + /** + * 性别(字典 1男 2女 3未知) + */ + @Excel(name = "性别", replace = {"男_1", "女_2"}, width = 20) + private Integer sex; + + /** + * 邮箱 + */ + @Excel(name = "邮箱", width = 30) + private String email; + + /** + * 手机 + */ + @Excel(name = "手机", width = 30) + private String phone; + + /** + * 电话 + */ + @Excel(name = "电话", width = 30) + private String tel; + + /** + * 最后登陆IP + */ + @Excel(name = "最后登陆IP", width = 30) + private String lastLoginIp; + + /** + * 最后登陆时间 + */ + @Excel(name = "最后登陆时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd HH:mm:ss", width = 30) + private Date lastLoginTime; + + /** + * 管理员类型(1超级管理员 2非管理员) + */ + @Excel(name = "管理员类型", replace = {"超级管理员_1", "非管理员_2"}, width = 20) + private Integer adminType; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + @Excel(name = "状态", replace = {"正常_0", "停用_1", "删除_2"}, width = 20) + private Integer status; + + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUserDataScope.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUserDataScope.java new file mode 100644 index 0000000..230af19 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUserDataScope.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 系统用户数据范围表 + * + * @author xuyuxiang + * @date 2020/3/11 11:47 + */ +@Data +@TableName("sys_user_data_scope") +public class SysUserDataScope { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 机构id + */ + private Long orgId; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUserRole.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUserRole.java new file mode 100644 index 0000000..2e7b849 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/entity/SysUserRole.java @@ -0,0 +1,57 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 系统用户角色表 + * + * @author xuyuxiang + * @date 2020/3/11 11:47 + */ +@Data +@TableName("sys_user_role") +public class SysUserRole { + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 用户id + */ + private Long userId; + + /** + * 角色id + */ + private Long roleId; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/enums/SysUserExceptionEnum.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/enums/SysUserExceptionEnum.java new file mode 100644 index 0000000..3cb015a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/enums/SysUserExceptionEnum.java @@ -0,0 +1,90 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * 系统用户相关异常枚举 + * + * @author xuyuxiang + * @date 2020/3/23 9:32 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE, kind = SysExpEnumConstant.SYS_USER_EXCEPTION_ENUM) +public enum SysUserExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 用户不存在 + */ + USER_NOT_EXIST(1, "用户不存在"), + + /** + * 账号已存在 + */ + USER_ACCOUNT_REPEAT(2, "账号已存在,请检查account参数"), + + /** + * 原密码错误 + */ + USER_PWD_ERROR(3, "原密码错误,完整性校验失败,请检查password参数"), + + /** + * 新密码与原密码相同 + */ + USER_PWD_REPEAT(4, "新密码与原密码相同,请检查newPassword参数"), + + /** + * 不能删除超级管理员 + */ + USER_CAN_NOT_DELETE_ADMIN(5, "不能删除超级管理员"), + + /** + * 不能修改超级管理员状态 + */ + USER_CAN_NOT_UPDATE_ADMIN(6, "不能修改超级管理员状态"); + + private final Integer code; + + private final String message; + + SysUserExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/factory/SysUserFactory.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/factory/SysUserFactory.java new file mode 100644 index 0000000..7be8f6b --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/factory/SysUserFactory.java @@ -0,0 +1,80 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.factory; + +import cn.hutool.core.util.ObjectUtil; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.util.CryptogramUtil; +import vip.xiaonuo.sys.core.enums.AdminTypeEnum; +import vip.xiaonuo.sys.core.enums.SexEnum; +import vip.xiaonuo.sys.modular.user.entity.SysUser; + +/** + * 填充用户附加信息工厂 + * + * @author xuyuxiang + * @date 2020/3/23 16:40 + */ +public class SysUserFactory { + + /** + * 管理员类型(1超级管理员 2非管理员) + * 新增普通用户时填充相关信息 + * + * @author xuyuxiang + * @date 2020/3/23 16:41 + */ + public static void fillAddCommonUserInfo(SysUser sysUser) { + fillBaseUserInfo(sysUser); + sysUser.setAdminType(AdminTypeEnum.NONE.getCode()); + } + + /** + * 填充用户基本字段 + * + * @author xuyuxiang + * @date 2020/3/23 16:50 + */ + public static void fillBaseUserInfo(SysUser sysUser) { + //密码为空则设置密码(密码都为哈希值哦) + if (ObjectUtil.isEmpty(sysUser.getPwdHashValue())) { + //没有密码则设置默认密码 + String password = ConstantContextHolder.getDefaultPassWord(); + //设置密码为Sm3的哈希值,这里代表保护密码的完整性 + sysUser.setPwdHashValue(CryptogramUtil.doHashValue(password)); + } + + if (ObjectUtil.isEmpty(sysUser.getAvatar())) { + sysUser.setAvatar(null); + } + + if (ObjectUtil.isEmpty(sysUser.getSex())) { + sysUser.setSex(SexEnum.NONE.getCode()); + } + + sysUser.setStatus(CommonStatusEnum.ENABLE.getCode()); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserDataScopeMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserDataScopeMapper.java new file mode 100644 index 0000000..deca304 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserDataScopeMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.user.entity.SysUserDataScope; + +/** + * 系统用户数据范围mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:46 + */ +public interface SysUserDataScopeMapper extends BaseMapper<SysUserDataScope> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserMapper.java new file mode 100644 index 0000000..2cc1634 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserMapper.java @@ -0,0 +1,52 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.result.SysUserResult; + +/** + * 系统用户mapper接口 + * + * @author xuyuxiang + * @date 2020/3/12 9:48 + */ +public interface SysUserMapper extends BaseMapper<SysUser> { + + /** + * 获取用户分页列表 + * + * @param page 分页参数 + * @param queryWrapper 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/4/7 21:14 + */ + Page<SysUserResult> page(@Param("page") Page page, @Param("ew") QueryWrapper queryWrapper); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserRoleMapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..58f1759 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/SysUserRoleMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.sys.modular.user.entity.SysUserRole; + +/** + * 系统用户角色mapper接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:46 + */ +public interface SysUserRoleMapper extends BaseMapper<SysUserRole> { +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserDataScopeMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserDataScopeMapper.xml new file mode 100644 index 0000000..61d1b24 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserDataScopeMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.user.mapper.SysUserDataScopeMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserMapper.xml new file mode 100644 index 0000000..95000f3 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserMapper.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.user.mapper.SysUserMapper"> + + <resultMap id="sysUserResult" type="vip.xiaonuo.sys.modular.user.result.SysUserResult"> + <id column="id" property="id" /> + <result column="account" property="account" /> + <result column="nick_name" property="nickName" /> + <result column="name" property="name" /> + <result column="avatar" property="avatar" /> + <result column="birthday" property="birthday" /> + <result column="sex" property="sex" /> + <result column="email" property="email" /> + <result column="phone" property="phone" /> + <result column="tel" property="tel" /> + <result column="status" property="status" /> + <association property="sysEmpInfo" javaType="vip.xiaonuo.sys.modular.emp.result.SysEmpInfo"> + <result column="job_num" property="jobNum" /> + <result column="org_id" property="orgId" /> + <result column="org_name" property="orgName" /> + </association> + </resultMap> + + <!--获取用户分页列表--> + <select id="page" resultMap="sysUserResult"> + select sys_user.*, + sys_emp.job_num, + sys_emp.org_id, + sys_emp.org_name + from sys_user left join sys_emp on sys_user.id = sys_emp.id left join sys_org on sys_emp.org_id = sys_org.id + ${ew.customSqlSegment} + </select> +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserRoleMapper.xml b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserRoleMapper.xml new file mode 100644 index 0000000..7d4c6c8 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/mapper/mapping/SysUserRoleMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.sys.modular.user.mapper.SysUserRoleMapper"> + +</mapper> diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/param/SysUserParam.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/param/SysUserParam.java new file mode 100644 index 0000000..6a78750 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/param/SysUserParam.java @@ -0,0 +1,146 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.param; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import vip.xiaonuo.core.pojo.base.param.BaseParam; +import vip.xiaonuo.core.validation.date.DateValue; +import vip.xiaonuo.sys.modular.emp.param.SysEmpParam; + +import javax.validation.Valid; +import javax.validation.constraints.*; +import java.util.List; + +/** + * 系统用户参数 + * + * @author xuyuxiang + * @date 2020/3/23 9:21 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class SysUserParam extends BaseParam { + + /** + * 主键 + */ + @NotNull(message = "id不能为空,请检查id参数", + groups = {edit.class, delete.class, detail.class, start.class, + stop.class, grantRole.class, grantData.class, updateInfo.class, + updatePwd.class, resetPwd.class, changeStatus.class, updateAvatar.class}) + private Long id; + + /** + * 账号 + */ + @NotBlank(message = "账号不能为空,请检查account参数", groups = {add.class, edit.class}) + private String account; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空,请检查password参数", groups = {updatePwd.class}) + private String password; + + /** + * 新密码 + */ + @NotBlank(message = "新密码不能为空,请检查newPassword参数", groups = {updatePwd.class}) + private String newPassword; + + /** + * 昵称 + */ + private String nickName; + + /** + * 姓名 + */ + @NotBlank(message = "姓名不能为空,请检查name参数", groups = {add.class, edit.class}) + private String name; + + /** + * 头像 + */ + @NotNull(message = "头像不能为空,请检查avatar参数", groups = {updateAvatar.class}) + private Long avatar; + + /** + * 生日 + */ + @DateValue(message = "生日格式不正确,请检查birthday参数", groups = {add.class, edit.class, updateInfo.class}) + private String birthday; + + /** + * 性别(字典 1男 2女) + */ + @NotNull(message = "性别不能为空,请检查sex参数", groups = {updateInfo.class}) + @Min(value = 1, message = "性别格式错误,请检查sex参数", groups = {updateInfo.class}) + @Max(value = 2, message = "性别格式错误,请检查sex参数", groups = {updateInfo.class}) + private Integer sex; + + /** + * 邮箱 + */ + @Email(message = "邮箱格式错误,请检查email参数", groups = {updateInfo.class}) + private String email; + + /** + * 手机 + */ + @NotNull(message = "手机号码不能为空,请检查phone参数", groups = {add.class, edit.class, updateInfo.class}) + @Size(min = 11, max = 11, message = "手机号码格式错误,请检查phone参数", groups = {add.class, edit.class, updateInfo.class}) + private String phone; + + /** + * 电话 + */ + private String tel; + + /** + * 授权角色 + */ + @NotNull(message = "授权角色不能为空,请检查grantRoleIdList参数", groups = {grantRole.class}) + private List<Long> grantRoleIdList; + + /** + * 授权数据 + */ + @NotNull(message = "授权数据不能为空,请检查grantOrgIdList参数", groups = {grantData.class}) + private List<Long> grantOrgIdList; + + /*==============员工相关信息==========*/ + + @NotNull(message = "员工信息不能为空,请检查sysEmpParam参数", groups = {add.class, edit.class}) + @Valid + private SysEmpParam sysEmpParam; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + @NotNull(message = "状态不能为空,请检查status参数", groups = changeStatus.class) + private Integer status; +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/result/SysUserResult.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/result/SysUserResult.java new file mode 100644 index 0000000..48139ed --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/result/SysUserResult.java @@ -0,0 +1,109 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.result; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import vip.xiaonuo.core.pojo.base.wrapper.BaseWrapper; +import vip.xiaonuo.sys.modular.emp.result.SysEmpInfo; + +import java.util.Date; +import java.util.Map; + +/** + * 系统用户结果 + * + * @author xuyuxiang + * @date 2020/4/2 9:19 + */ +@Data +public class SysUserResult implements BaseWrapper { + + /** + * 主键 + */ + private Long id; + + /** + * 账号 + */ + private String account; + + /** + * 昵称 + */ + private String nickName; + + /** + * 姓名 + */ + private String name; + + /** + * 头像 + */ + private Long avatar; + + /** + * 生日 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + private Date birthday; + + /** + * 性别(字典 1男 2女 3未知) + */ + private Integer sex; + + /** + * 邮箱 + */ + private String email; + + /** + * 手机 + */ + private String phone; + + /** + * 电话 + */ + private String tel; + + /** + * 用户员工信息 + */ + private SysEmpInfo sysEmpInfo; + + /** + * 状态(字典 0正常 1停用 2删除) + */ + private Integer status; + + @Override + public Map<String, Object> doWrap(Object beWrappedModel) { + return null; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserDataScopeService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserDataScopeService.java new file mode 100644 index 0000000..15cbba7 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserDataScopeService.java @@ -0,0 +1,77 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.sys.modular.user.entity.SysUserDataScope; +import vip.xiaonuo.sys.modular.user.param.SysUserParam; + +import java.util.List; + +/** + * 系统用户数据范围service接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:45 + */ +public interface SysUserDataScopeService extends IService<SysUserDataScope> { + + /** + * 授权数据 + * + * @param sysUserParam 授权参数 + * @author xuyuxiang + * @date 2020/3/28 16:57 + */ + void grantData(SysUserParam sysUserParam); + + /** + * 获取用户的数据范围id集合 + * + * @param uerId 用户id + * @return 数据范围id集合 + * @author xuyuxiang + * @date 2020/4/5 17:27 + */ + List<Long> getUserDataScopeIdList(Long uerId); + + /** + * 根据机构id集合删除对应的用户-数据范围关联信息 + * + * @param orgIdList 机构id集合 + * @author xuyuxiang + * @date 2020/6/28 14:15 + */ + void deleteUserDataScopeListByOrgIdList(List<Long> orgIdList); + + /** + * 根据用户id删除对应的用户-数据范围关联信息 + * + * @param userId 用户id + * @author xuyuxiang + * @date 2020/6/28 14:52 + */ + void deleteUserDataScopeListByUserId(Long userId); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserRoleService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserRoleService.java new file mode 100644 index 0000000..35b1585 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserRoleService.java @@ -0,0 +1,88 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.sys.modular.user.entity.SysUserRole; +import vip.xiaonuo.sys.modular.user.param.SysUserParam; + +import java.util.List; + +/** + * 系统用户角色service接口 + * + * @author xuyuxiang + * @date 2020/3/13 15:44 + */ +public interface SysUserRoleService extends IService<SysUserRole> { + + /** + * 获取用户的角色id集合 + * + * @param userId 用户id + * @return 角色id集合 + * @author xuyuxiang + * @date 2020/3/20 22:29 + */ + List<Long> getUserRoleIdList(Long userId); + + /** + * 授权角色 + * + * @param sysUserParam 授权参数 + * @author xuyuxiang + * @date 2020/3/28 16:57 + */ + void grantRole(SysUserParam sysUserParam); + + /** + * 获取用户所有角色的数据范围(组织机构id集合) + * + * @param userId 用户id + * @param orgId 组织机构id + * @return 数据范围id集合(组织机构id集合) + * @author xuyuxiang + * @date 2020/4/5 17:31 + */ + List<Long> getUserRoleDataScopeIdList(Long userId, Long orgId); + + /** + * 根据角色id删除对应的用户-角色表关联信息 + * + * @param roleId 角色id + * @author xuyuxiang + * @date 2020/6/28 14:22 + */ + void deleteUserRoleListByRoleId(Long roleId); + + /** + * 根据用户id删除对应的用户-角色表关联信息 + * + * @param userId 用户id + * @author xuyuxiang + * @date 2020/6/28 14:52 + */ + void deleteUserRoleListByUserId(Long userId); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserService.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserService.java new file mode 100644 index 0000000..2ecdead --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/SysUserService.java @@ -0,0 +1,275 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.service; + +import cn.hutool.core.lang.Dict; +import com.baomidou.mybatisplus.extension.service.IService; +import me.zhyd.oauth.model.AuthUser; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.param.SysUserParam; +import vip.xiaonuo.sys.modular.user.result.SysUserResult; + +import java.util.List; +import java.util.Set; + +/** + * 系统用户service接口 + * + * @author xuyuxiang + * @date 2020/3/11 17:49 + */ +public interface SysUserService extends IService<SysUser> { + + /** + * 根据账号获取用户 + * + * @param account 账号 + * @return 用户 + * @author xuyuxiang + * @date 2020/3/11 17:51 + */ + SysUser getUserByCount(String account); + + /** + * 查询系统用户 + * + * @param sysUserParam 查询参数 + * @return 查询分页结果 + * @author xuyuxiang + * @date 2020/3/23 9:23 + */ + PageResult<SysUserResult> page(SysUserParam sysUserParam); + + /** + * 根据用户账号模糊搜索系统用户列表 + * + * @param sysUserParam 查询参数 + * @return 增强版hashMap 格式:[{"id:":123, "firstName":"张三"}] + * @author xuyuxiang + * @date 2020/4/14 9:21 + */ + List<Dict> list(SysUserParam sysUserParam); + + /** + * 增加系统用户 + * + * @param sysUserParam 添加参数 + * @author xuyuxiang + * @date 2020/3/23 9:26 + */ + void add(SysUserParam sysUserParam); + + /** + * 删除系统用户 + * + * @param sysUserParamList 删除集合 + * @author xuyuxiang yubaosahn + * @date 2020/3/23 9:26 + */ + void delete(List<SysUserParam> sysUserParamList); + + /** + * 编辑系统用户 + * + * @param sysUserParam 编辑参数 + * @author xuyuxiang + * @date 2020/3/23 9:26 + */ + void edit(SysUserParam sysUserParam); + + /** + * 查看系统用户 + * + * @param sysUserParam 查看参数 + * @return 用户结果集 + * @author xuyuxiang + * @date 2020/3/26 9:52 + */ + SysUserResult detail(SysUserParam sysUserParam); + + /** + * 修改状态 + * + * @param sysUserParam 修改参数 + * @author xuyuxiang + * @date 2020/5/25 14:34 + */ + void changeStatus(SysUserParam sysUserParam); + + /** + * 授权角色 + * + * @param sysUserParam 授权参数 + * @author xuyuxiang + * @date 2020/3/28 16:54 + */ + void grantRole(SysUserParam sysUserParam); + + /** + * 授权数据 + * + * @param sysUserParam 授权参数 + * @author xuyuxiang + * @date 2020/3/28 16:54 + */ + void grantData(SysUserParam sysUserParam); + + /** + * 更新信息 + * + * @param sysUserParam 更新参数 + * @author xuyuxiang + * @date 2020/4/1 14:43 + */ + void updateInfo(SysUserParam sysUserParam); + + /** + * 修改密码 + * + * @param sysUserParam 修改密码参数 + * @author xuyuxiang + * @date 2020/4/1 14:44 + */ + void updatePwd(SysUserParam sysUserParam); + + /** + * 获取用户的数据范围(组织机构id集合) + * + * @param userId 用户id + * @param orgId 组织机构id + * @return 数据范围id集合(组织机构id集合) + * @author xuyuxiang + * @date 2020/4/5 17:23 + */ + List<Long> getUserDataScopeIdList(Long userId, Long orgId); + + /** + * 根据用户id获取姓名 + * + * @param userId 用户id + * @return 用户姓名 + * @author xuyuxiang + * @date 2020/5/6 15:02 + */ + String getNameByUserId(Long userId); + + /** + * 拥有角色 + * + * @param sysUserParam 查询参数 + * @return 角色id集合 + * @author xuyuxiang + * @date 2020/5/29 14:10 + */ + List<Long> ownRole(SysUserParam sysUserParam); + + /** + * 拥有数据 + * + * @param sysUserParam 查询参数 + * @return 数据范围id集合 + * @author xuyuxiang + * @date 2020/5/29 14:10 + */ + List<Long> ownData(SysUserParam sysUserParam); + + /** + * 重置密码 + * + * @param sysUserParam 重置参数 + * @author xuyuxiang + * @date 2020/5/29 14:57 + */ + void resetPwd(SysUserParam sysUserParam); + + /** + * 修改头像 + * + * @param sysUserParam 修改头像参数 + * @author xuyuxiang + * @date 2020/6/28 15:21 + */ + void updateAvatar(SysUserParam sysUserParam); + + /** + * 导出用户 + * + * @param sysUserParam 导出参数 + * @author xuyuxiang + * @date 2020/6/30 16:08 + */ + void export(SysUserParam sysUserParam); + + /** + * 用户选择器 + * + * @param sysUserParam 查询参数 + * @return 用户列表集合,格式[{"id":123,"name":"张三"},{"id":456,"name":"李四"}] + * @author xuyuxiang + * @date 2020/7/3 13:17 + */ + List<Dict> selector(SysUserParam sysUserParam); + + /** + * 根据用户id获取用户 + * + * @param userId 用户id + * @return 用户实体 + * @author xuyuxiang + * @date 2020/7/29 10:07 + **/ + SysUser getUserById(Long userId); + + /** + * 将授权的用户信息保存到用户表 + * + * @param authUser 授权的用户信息 + * @param sysUser 用户表信息 + * @return void + * @author xuyuxiang + * @date 2020/7/29 10:26 + **/ + void saveAuthUserToUser(AuthUser authUser, SysUser sysUser); + + /** + * 获取用户id集合 + * + * @return 用户id集合 + * @author xuyuxiang + * @date 2020/9/11 17:54 + **/ + List<Long> getAllUserIdList(); + + /** + * 判断集合内用户是否均已删除 + * + * @author xuyuxiang + * @date 2021/9/3 13:26 + * @param userIdSet 用户id集合 + * @return boolean + **/ + boolean hasAllDeletedUser(Set<Long> userIdSet); +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserDataScopeServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserDataScopeServiceImpl.java new file mode 100644 index 0000000..465dfcc --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserDataScopeServiceImpl.java @@ -0,0 +1,86 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.sys.modular.user.entity.SysUserDataScope; +import vip.xiaonuo.sys.modular.user.mapper.SysUserDataScopeMapper; +import vip.xiaonuo.sys.modular.user.param.SysUserParam; +import vip.xiaonuo.sys.modular.user.service.SysUserDataScopeService; + +import java.util.List; + +/** + * 系统用户数据范围service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 15:49 + */ +@Service +public class SysUserDataScopeServiceImpl extends ServiceImpl<SysUserDataScopeMapper, SysUserDataScope> + implements SysUserDataScopeService { + + @Override + public void grantData(SysUserParam sysUserParam) { + Long userId = sysUserParam.getId(); + //删除所拥有数据 + LambdaQueryWrapper<SysUserDataScope> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserDataScope::getUserId, userId); + this.remove(queryWrapper); + //授权数据 + sysUserParam.getGrantOrgIdList().forEach(orgId -> { + SysUserDataScope sysUserDataScope = new SysUserDataScope(); + sysUserDataScope.setUserId(userId); + sysUserDataScope.setOrgId(orgId); + this.save(sysUserDataScope); + }); + } + + @Override + public List<Long> getUserDataScopeIdList(Long uerId) { + List<Long> userDataScopeIdList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysUserDataScope> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserDataScope::getUserId, uerId); + this.list(queryWrapper).forEach(sysUserDataScope -> userDataScopeIdList.add(sysUserDataScope.getOrgId())); + return userDataScopeIdList; + } + + @Override + public void deleteUserDataScopeListByOrgIdList(List<Long> orgIdList) { + LambdaQueryWrapper<SysUserDataScope> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(SysUserDataScope::getOrgId, orgIdList); + this.remove(queryWrapper); + } + + @Override + public void deleteUserDataScopeListByUserId(Long userId) { + LambdaQueryWrapper<SysUserDataScope> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserDataScope::getUserId, userId); + this.remove(queryWrapper); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserRoleServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserRoleServiceImpl.java new file mode 100644 index 0000000..8298f22 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserRoleServiceImpl.java @@ -0,0 +1,106 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import vip.xiaonuo.sys.modular.role.service.SysRoleService; +import vip.xiaonuo.sys.modular.user.entity.SysUserRole; +import vip.xiaonuo.sys.modular.user.mapper.SysUserRoleMapper; +import vip.xiaonuo.sys.modular.user.param.SysUserParam; +import vip.xiaonuo.sys.modular.user.service.SysUserRoleService; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 系统用户角色service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/13 15:48 + */ +@Service +public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUserRole> implements SysUserRoleService { + + @Resource + private SysRoleService sysRoleService; + + @Override + public List<Long> getUserRoleIdList(Long userId) { + LambdaQueryWrapper<SysUserRole> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getUserId, userId); + return this.list(queryWrapper).stream().map(SysUserRole::getRoleId).collect(Collectors.toList()); + } + + @Override + public void grantRole(SysUserParam sysUserParam) { + Long userId = sysUserParam.getId(); + //删除所拥有角色 + LambdaQueryWrapper<SysUserRole> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getUserId, userId); + this.remove(queryWrapper); + //授权角色 + sysUserParam.getGrantRoleIdList().forEach(roleId -> { + SysUserRole sysUserRole = new SysUserRole(); + sysUserRole.setUserId(userId); + sysUserRole.setRoleId(roleId); + this.save(sysUserRole); + }); + } + + @Override + public List<Long> getUserRoleDataScopeIdList(Long userId, Long orgId) { + List<Long> roleIdList = CollectionUtil.newArrayList(); + + // 获取用户所有角色 + LambdaQueryWrapper<SysUserRole> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getUserId, userId); + this.list(queryWrapper).forEach(sysUserRole -> roleIdList.add(sysUserRole.getRoleId())); + + // 获取这些角色对应的数据范围 + if (ObjectUtil.isNotEmpty(roleIdList)) { + return sysRoleService.getUserDataScopeIdList(roleIdList, orgId); + } + return CollectionUtil.newArrayList(); + } + + @Override + public void deleteUserRoleListByRoleId(Long roleId) { + LambdaQueryWrapper<SysUserRole> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getRoleId, roleId); + this.remove(queryWrapper); + } + + @Override + public void deleteUserRoleListByUserId(Long userId) { + LambdaQueryWrapper<SysUserRole> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUserRole::getUserId, userId); + this.remove(queryWrapper); + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserServiceImpl.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..8b5d884 --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/service/impl/SysUserServiceImpl.java @@ -0,0 +1,572 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.sys.modular.user.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import me.zhyd.oauth.enums.AuthUserGender; +import me.zhyd.oauth.model.AuthUser; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.consts.SymbolConstant; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.context.login.LoginContextHolder; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.PermissionException; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.exception.enums.PermissionExceptionEnum; +import vip.xiaonuo.core.exception.enums.StatusExceptionEnum; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.CryptogramUtil; +import vip.xiaonuo.core.util.PoiUtil; +import vip.xiaonuo.sys.core.enums.AdminTypeEnum; +import vip.xiaonuo.sys.core.enums.OauthSexEnum; +import vip.xiaonuo.sys.core.enums.SexEnum; +import vip.xiaonuo.sys.modular.emp.param.SysEmpParam; +import vip.xiaonuo.sys.modular.emp.result.SysEmpInfo; +import vip.xiaonuo.sys.modular.emp.service.SysEmpService; +import vip.xiaonuo.sys.modular.file.service.SysFileInfoService; +import vip.xiaonuo.sys.modular.user.entity.SysUser; +import vip.xiaonuo.sys.modular.user.enums.SysUserExceptionEnum; +import vip.xiaonuo.sys.modular.user.factory.SysUserFactory; +import vip.xiaonuo.sys.modular.user.mapper.SysUserMapper; +import vip.xiaonuo.sys.modular.user.param.SysUserParam; +import vip.xiaonuo.sys.modular.user.result.SysUserResult; +import vip.xiaonuo.sys.modular.user.service.SysUserDataScopeService; +import vip.xiaonuo.sys.modular.user.service.SysUserRoleService; +import vip.xiaonuo.sys.modular.user.service.SysUserService; +import javax.annotation.Resource; +import java.util.*; + +/** + * 系统用户service接口实现类 + * + * @author xuyuxiang + * @date 2020/3/11 17:49 + */ +@Service +public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService { + + @Resource + private SysEmpService sysEmpService; + + @Resource + private SysUserRoleService sysUserRoleService; + + @Resource + private SysUserDataScopeService sysUserDataScopeService; + + @Resource + private SysFileInfoService sysFileInfoService; + + @Override + public SysUser getUserByCount(String account) { + LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUser::getAccount, account); + queryWrapper.ne(SysUser::getStatus, CommonStatusEnum.DELETED.getCode()); + return this.getOne(queryWrapper); + } + + @Override + public PageResult<SysUserResult> page(SysUserParam sysUserParam) { + QueryWrapper<SysUserResult> queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(sysUserParam)) { + //根据关键字模糊查询(姓名,账号,手机号) + if (ObjectUtil.isNotEmpty(sysUserParam.getSearchValue())) { + queryWrapper.and(q -> q.like("sys_user.account", sysUserParam.getSearchValue()) + .or().like("sys_user.name", sysUserParam.getSearchValue())); + } + //根据员工所属机构查询 + if (ObjectUtil.isNotEmpty(sysUserParam.getSysEmpParam())) { + SysEmpParam sysEmpParam = sysUserParam.getSysEmpParam(); + if (ObjectUtil.isNotEmpty(sysEmpParam.getOrgId())) { + //查询属于该机构的,或该机构下级所有的人员 + queryWrapper.and(q -> q.eq("sys_emp.org_id", sysEmpParam.getOrgId()) + .or().like("sys_org.pids", sysEmpParam.getOrgId())); + } + } + //根据状态查询(0正常 1停用),删除的不会展示在列表 + if (ObjectUtil.isNotEmpty(sysUserParam.getSearchStatus())) { + queryWrapper.eq("sys_user.status", sysUserParam.getSearchStatus()); + } + } + //查询非删除状态,排除超级管理员 + queryWrapper.ne("sys_user.status", CommonStatusEnum.DELETED.getCode()) + .ne("sys_user.admin_type", AdminTypeEnum.SUPER_ADMIN.getCode()); + + //如果是超级管理员则获取所有用户,否则只获取其数据范围的用户 + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + if (!superAdmin) { + List<Long> dataScope = sysUserParam.getDataScope(); + if (ObjectUtil.isEmpty(dataScope)) { + return new PageResult<>(new Page<>()); + } else { + Set<Long> dataScopeSet = CollectionUtil.newHashSet(dataScope); + queryWrapper.in("sys_emp.org_id", dataScopeSet); + } + } + + return new PageResult<>(this.baseMapper.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<Dict> list(SysUserParam sysUserParam) { + List<Dict> dictList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysUserParam)) { + //根据账号,姓名模糊查询 + if (ObjectUtil.isNotEmpty(sysUserParam.getAccount())) { + queryWrapper.and(i -> i.like(SysUser::getAccount, sysUserParam.getAccount()) + .or().like(SysUser::getName, sysUserParam.getAccount())); + } + } + //查询正常状态,排除超级管理员 + queryWrapper.eq(SysUser::getStatus, CommonStatusEnum.ENABLE.getCode()) + .ne(SysUser::getAdminType, AdminTypeEnum.SUPER_ADMIN.getCode()); + this.list(queryWrapper).forEach(sysUser -> { + Dict dict = Dict.create(); + dict.put("id", sysUser.getId().toString()); + dict.put("firstName", sysUser.getName() + SymbolConstant.LEFT_SQUARE_BRACKETS + + sysUser.getAccount() + SymbolConstant.RIGHT_SQUARE_BRACKETS); + dictList.add(dict); + }); + return dictList; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void add(SysUserParam sysUserParam) { + checkParam(sysUserParam, false); + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + //如果登录用户不是超级管理员,则进行数据权限校验 + if (!superAdmin) { + List<Long> dataScope = sysUserParam.getDataScope(); + //获取添加的用户的所属机构 + Long orgId = sysUserParam.getSysEmpParam().getOrgId(); + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } else if (!dataScope.contains(orgId)) { + //所添加的用户的所属机构不在自己的数据范围内 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + SysUser sysUser = new SysUser(); + BeanUtil.copyProperties(sysUserParam, sysUser); + SysUserFactory.fillAddCommonUserInfo(sysUser); + if(ObjectUtil.isNotEmpty(sysUserParam.getPassword())) { + sysUser.setPwdHashValue(CryptogramUtil.doHashValue(sysUserParam.getPassword())); + } + // 对铭感字段(手机号进行加密保护) + if (ConstantContextHolder.getCryptogramConfigs().getFieldEncDec()) { + sysUser.setPhone(CryptogramUtil.doEncrypt(sysUserParam.getPhone())); + } + this.save(sysUser); + Long sysUserId = sysUser.getId(); + //增加员工信息 + SysEmpParam sysEmpParam = sysUserParam.getSysEmpParam(); + sysEmpParam.setId(sysUserId); + sysEmpService.addOrUpdate(sysEmpParam); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(List<SysUserParam> sysUserParamList) { + sysUserParamList.forEach(sysUserParam -> { + SysUser sysUser = this.querySysUser(sysUserParam); + //不能删除超级管理员 + if (AdminTypeEnum.SUPER_ADMIN.getCode().equals(sysUser.getAdminType())) { + throw new ServiceException(SysUserExceptionEnum.USER_CAN_NOT_DELETE_ADMIN); + } + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + //如果登录用户不是超级管理员,则进行数据权限校验 + if (!superAdmin) { + List<Long> dataScope = sysUserParam.getDataScope(); + //获取要删除的用户的所属机构 + SysEmpInfo sysEmpInfo = sysEmpService.getSysEmpInfo(sysUser.getId()); + Long orgId = sysEmpInfo.getOrgId(); + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } else if (!dataScope.contains(orgId)) { + //所要删除的用户的所属机构不在自己的数据范围内 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + sysUser.setStatus(CommonStatusEnum.DELETED.getCode()); + this.updateById(sysUser); + Long id = sysUser.getId(); + //删除该用户对应的员工表信息 + sysEmpService.deleteEmpInfoByUserId(id); + + //删除该用户对应的用户-角色表关联信息 + sysUserRoleService.deleteUserRoleListByUserId(id); + + //删除该用户对应的用户-数据范围表关联信息 + sysUserDataScopeService.deleteUserDataScopeListByUserId(id); + }); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + checkParam(sysUserParam, true); + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + //如果登录用户不是超级管理员,则进行数据权限校验 + if (!superAdmin) { + List<Long> dataScope = sysUserParam.getDataScope(); + //获取要编辑的用户的所属机构 + Long orgId = sysUserParam.getSysEmpParam().getOrgId(); + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } else if (!dataScope.contains(orgId)) { + //所要编辑的用户的所属机构不在自己的数据范围内 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + + BeanUtil.copyProperties(sysUserParam, sysUser); + //不能修改状态,用修改状态接口修改状态 + sysUser.setStatus(null); + //设置密码 + SysUserFactory.fillBaseUserInfo(sysUser); + if(ObjectUtil.isNotEmpty(sysUserParam.getPassword())) { + // 将其哈希掉,存进去 + sysUser.setPwdHashValue(CryptogramUtil.doHashValue(sysUserParam.getPassword())); + } + + // 对铭感字段(手机号进行加密保护) + if (ConstantContextHolder.getCryptogramConfigs().getFieldEncDec()) { + sysUser.setPhone(CryptogramUtil.doEncrypt(sysUserParam.getPhone())); + } + + this.updateById(sysUser); + Long sysUserId = sysUser.getId(); + //编辑员工信息 + SysEmpParam sysEmpParam = sysUserParam.getSysEmpParam(); + BeanUtil.copyProperties(sysUserParam, sysEmpParam); + sysEmpParam.setId(sysUserId); + sysEmpService.addOrUpdate(sysEmpParam); + } + + @Override + public SysUserResult detail(SysUserParam sysUserParam) { + SysUserResult sysUserResult = new SysUserResult(); + //获取系统用户 + SysUser sysUser = this.querySysUser(sysUserParam); + BeanUtil.copyProperties(sysUser, sysUserResult); + //获取对应员工信息 + SysEmpInfo sysEmpInfo = sysEmpService.getSysEmpInfo(sysUser.getId()); + //设置员工信息 + sysUserResult.setSysEmpInfo(sysEmpInfo); + return sysUserResult; + } + + @Override + public void changeStatus(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + //不能修改超级管理员状态 + if (AdminTypeEnum.SUPER_ADMIN.getCode().equals(sysUser.getAdminType())) { + throw new ServiceException(SysUserExceptionEnum.USER_CAN_NOT_UPDATE_ADMIN); + } + + Long id = sysUser.getId(); + + Integer status = sysUserParam.getStatus(); + //校验状态在不在枚举值里 + CommonStatusEnum.validateStatus(status); + + //更新枚举,更新只能更新未删除状态的 + LambdaUpdateWrapper<SysUser> updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(SysUser::getId, id) + .and(i -> i.ne(SysUser::getStatus, CommonStatusEnum.DELETED.getCode())) + .set(SysUser::getStatus, status); + boolean update = this.update(updateWrapper); + if (!update) { + throw new ServiceException(StatusExceptionEnum.UPDATE_STATUS_ERROR); + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void grantRole(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + //如果登录用户不是超级管理员,则进行数据权限校验 + if (!superAdmin) { + List<Long> dataScope = sysUserParam.getDataScope(); + //获取要授权角色的用户的所属机构 + SysEmpInfo sysEmpInfo = sysEmpService.getSysEmpInfo(sysUser.getId()); + Long orgId = sysEmpInfo.getOrgId(); + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } else if (!dataScope.contains(orgId)) { + //所要授权角色的用户的所属机构不在自己的数据范围内 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + sysUserRoleService.grantRole(sysUserParam); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void grantData(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + boolean superAdmin = LoginContextHolder.me().isSuperAdmin(); + //如果登录用户不是超级管理员,则进行数据权限校验 + if (!superAdmin) { + List<Long> dataScope = sysUserParam.getDataScope(); + //获取要授权数据的用户的所属机构 + SysEmpInfo sysEmpInfo = sysEmpService.getSysEmpInfo(sysUser.getId()); + Long orgId = sysEmpInfo.getOrgId(); + //数据范围为空 + if (ObjectUtil.isEmpty(dataScope)) { + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } else if (!dataScope.contains(orgId)) { + //所要授权数据的用户的所属机构不在自己的数据范围内 + throw new PermissionException(PermissionExceptionEnum.NO_PERMISSION_OPERATE); + } + } + sysUserDataScopeService.grantData(sysUserParam); + } + + @Override + public void updateInfo(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + BeanUtil.copyProperties(sysUserParam, sysUser); + this.updateById(sysUser); + } + + @Override + public void updatePwd(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + //新密码与原密码相同 + if (sysUserParam.getNewPassword().equals(sysUserParam.getPassword())) { + throw new ServiceException(SysUserExceptionEnum.USER_PWD_REPEAT); + } + //原密码错误 + if (!sysUser.getPwdHashValue().equals(CryptogramUtil.doHashValue(sysUserParam.getPassword()))) { + throw new ServiceException(SysUserExceptionEnum.USER_PWD_ERROR); + } + sysUser.setPwdHashValue(CryptogramUtil.doHashValue(sysUserParam.getNewPassword())); + this.updateById(sysUser); + } + + @Override + public List<Long> getUserDataScopeIdList(Long userId, Long orgId) { + Set<Long> userDataScopeIdSet = CollectionUtil.newHashSet(); + if (ObjectUtil.isAllNotEmpty(userId, orgId)) { + + //获取该用户对应的数据范围集合 + List<Long> userDataScopeIdListForUser = sysUserDataScopeService.getUserDataScopeIdList(userId); + + //获取该用户的角色对应的数据范围集合 + List<Long> userDataScopeIdListForRole = sysUserRoleService.getUserRoleDataScopeIdList(userId, orgId); + + userDataScopeIdSet.addAll(userDataScopeIdListForUser); + userDataScopeIdSet.addAll(userDataScopeIdListForRole); + } + return CollectionUtil.newArrayList(userDataScopeIdSet); + } + + @Override + public String getNameByUserId(Long userId) { + SysUser sysUser = this.getById(userId); + if (ObjectUtil.isNull(sysUser)) { + throw new ServiceException(SysUserExceptionEnum.USER_NOT_EXIST); + } + return sysUser.getName(); + } + + @Override + public List<Long> ownRole(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + return sysUserRoleService.getUserRoleIdList(sysUser.getId()); + } + + @Override + public List<Long> ownData(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + return sysUserDataScopeService.getUserDataScopeIdList(sysUser.getId()); + } + + @Override + public void resetPwd(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + String password = ConstantContextHolder.getDefaultPassWord(); + sysUser.setPwdHashValue(CryptogramUtil.doHashValue(password)); + this.updateById(sysUser); + } + + @Override + public void updateAvatar(SysUserParam sysUserParam) { + SysUser sysUser = this.querySysUser(sysUserParam); + Long avatar = sysUserParam.getAvatar(); + sysFileInfoService.assertFile(avatar); + sysUser.setAvatar(avatar); + this.updateById(sysUser); + } + + @Override + public void export(SysUserParam sysUserParam) { + // 只导出正常的 + LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUser::getStatus, CommonStatusEnum.ENABLE.getCode()); + // 其他的条件正常来说导出也只能是自己权限范围内看到的用户,改天我们再优化 + List<SysUser> list = this.list(queryWrapper); + PoiUtil.exportExcelWithStream("SonwyUsers.xls", SysUser.class, list); + } + + @Override + public List<Dict> selector(SysUserParam sysUserParam) { + List<Dict> dictList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); + if (ObjectUtil.isNotNull(sysUserParam)) { + //根据姓名模糊查询 + if (ObjectUtil.isNotEmpty(sysUserParam.getName())) { + queryWrapper.like(SysUser::getName, sysUserParam.getName()); + } + } + //查询非删除状态,排除超级管理员 + queryWrapper.ne(SysUser::getStatus, CommonStatusEnum.DELETED.getCode()) + .ne(SysUser::getAdminType, AdminTypeEnum.SUPER_ADMIN.getCode()); + this.list(queryWrapper).forEach(sysUser -> { + Dict dict = Dict.create(); + dict.put(CommonConstant.ID, sysUser.getId()); + dict.put(CommonConstant.NAME, sysUser.getName()); + dictList.add(dict); + }); + return dictList; + } + + @Override + public SysUser getUserById(Long userId) { + SysUser sysUser = this.getById(userId); + if (ObjectUtil.isNull(sysUser)) { + throw new ServiceException(SysUserExceptionEnum.USER_NOT_EXIST); + } + return sysUser; + } + + @Override + public void saveAuthUserToUser(AuthUser authUser, SysUser sysUser) { + SysUserFactory.fillAddCommonUserInfo(sysUser); + //获取oauth用户的账号 + String username = authUser.getUsername(); + //判断账号是否在用户表中有重复的 + LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUser::getAccount, username); + SysUser existUser = this.getOne(queryWrapper); + if (ObjectUtil.isNotNull(existUser)) { + //如果该账号已经存在了,则将oauth用户的账号后缀拼接随机数 + username = username + RandomUtil.randomString(5); + } + sysUser.setAccount(username); + sysUser.setName(authUser.getNickname()); + sysUser.setNickName(authUser.getNickname()); + sysUser.setEmail(authUser.getEmail()); + if (ObjectUtil.isNotNull(authUser.getGender())) { + AuthUserGender gender = authUser.getGender(); + //性别转换 + if (OauthSexEnum.MAN.getCode().equals(gender.getCode())) { + sysUser.setSex(SexEnum.MAN.getCode()); + } else if (OauthSexEnum.WOMAN.getCode().equals(gender.getCode())) { + sysUser.setSex(SexEnum.WOMAN.getCode()); + } else { + sysUser.setSex(SexEnum.NONE.getCode()); + } + } + this.save(sysUser); + } + + @Override + public List<Long> getAllUserIdList() { + List<Long> resultList = CollectionUtil.newArrayList(); + LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.ne(SysUser::getAdminType, AdminTypeEnum.SUPER_ADMIN); + this.list(queryWrapper).forEach(sysUser -> { + resultList.add(sysUser.getId()); + }); + return resultList; + } + + @Override + public boolean hasAllDeletedUser(Set<Long> userIdSet) { + //查询id在此集合内,且状态为删除的用户,判断其数量是否大于等于集合数量,大于是为了容错 + LambdaQueryWrapper<SysUser> lambdaQueryWrapper = new LambdaQueryWrapper<SysUser>() + .eq(SysUser::getStatus, CommonStatusEnum.DELETED).in(SysUser::getId, userIdSet); + return this.count(lambdaQueryWrapper) >= userIdSet.size(); + } + + /** + * 校验参数,检查是否存在相同的账号 + * + * @author xuyuxiang + * @date 2020/3/27 16:04 + */ + private void checkParam(SysUserParam sysUserParam, boolean isExcludeSelf) { + Long id = sysUserParam.getId(); + String account = sysUserParam.getAccount(); + LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SysUser::getAccount, account) + .ne(SysUser::getStatus, CommonStatusEnum.DELETED.getCode()); + //是否排除自己,如果是则查询条件排除自己id + if (isExcludeSelf) { + queryWrapper.ne(SysUser::getId, id); + } + int countByAccount = this.count(queryWrapper); + //大于等于1个则表示重复 + if (countByAccount >= 1) { + throw new ServiceException(SysUserExceptionEnum.USER_ACCOUNT_REPEAT); + } + } + + /** + * 获取系统用户 + * + * @author xuyuxiang + * @date 2020/3/26 9:54 + */ + private SysUser querySysUser(SysUserParam sysUserParam) { + SysUser sysUser = this.getById(sysUserParam.getId()); + if (ObjectUtil.isNull(sysUser)) { + throw new ServiceException(SysUserExceptionEnum.USER_NOT_EXIST); + } + return sysUser; + } +} diff --git a/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/wrapper/SysUserWrapper.java b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/wrapper/SysUserWrapper.java new file mode 100644 index 0000000..734ea7a --- /dev/null +++ b/snowy-base/snowy-system/src/main/java/vip/xiaonuo/sys/modular/user/wrapper/SysUserWrapper.java @@ -0,0 +1,36 @@ +package vip.xiaonuo.sys.modular.user.wrapper; + +import cn.hutool.core.util.ObjectUtil; +import vip.xiaonuo.core.context.constant.ConstantContextHolder; +import vip.xiaonuo.core.pojo.base.wrapper.BaseWrapper; +import vip.xiaonuo.core.util.CryptogramUtil; +import vip.xiaonuo.sys.modular.user.result.SysUserResult; + +import java.util.HashMap; +import java.util.Map; + + +/** + * 用户管理的包装类 + * + * @author yubaoshan + */ +public class SysUserWrapper implements BaseWrapper<SysUserResult> { + + @Override + public Map<String, Object> doWrap(SysUserResult sysUserResult) { + Map<String, Object> map = new HashMap<>(); + + // 是否开启用户表字段加解密,如果是被加密的,返回列表时需要脱敏 + if (ConstantContextHolder.getCryptogramConfigs().getFieldEncDec()) { + if (ObjectUtil.isNotEmpty(sysUserResult.getPhone())) { + map.put("phone", CryptogramUtil.doDecrypt(sysUserResult.getPhone())); + } + } + + // 下面这里其他的字段,需要做翻译脱敏的,下面处理即可 + + return map; + } + +} diff --git a/snowy-base/snowy-system/src/main/resources/META-INF/spring.factories b/snowy-base/snowy-system/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..e654c66 --- /dev/null +++ b/snowy-base/snowy-system/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.context.ApplicationListener=\ +vip.xiaonuo.sys.core.listener.ConstantsInitListener,\ +vip.xiaonuo.sys.core.listener.TimerTaskRunListener,\ +vip.xiaonuo.sys.core.listener.RemoveRequestParamListener diff --git a/snowy-main/README.md b/snowy-main/README.md new file mode 100644 index 0000000..baeced1 --- /dev/null +++ b/snowy-main/README.md @@ -0,0 +1,3 @@ +## 本模块为项目的启动模块,运行SnowyApplication启动程序 + +## 在此模块下开发业务即可 diff --git a/snowy-main/pom.xml b/snowy-main/pom.xml new file mode 100644 index 0000000..edcbd78 --- /dev/null +++ b/snowy-main/pom.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy</artifactId> + <version>1.6.0</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>snowy-main</artifactId> + + <packaging>jar</packaging> + + <dependencies> + + <!-- 系统模块 --> + <dependency> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy-system</artifactId> + <version>1.6.0</version> + </dependency> + + <!-- 代码生成模块 --> + <dependency> + <groupId>vip.xiaonuo</groupId> + <artifactId>snowy-gen</artifactId> + <version>1.6.0</version> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <finalName>snowy</finalName> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + <configuration> + <fork>true</fork> + </configuration> + </plugin> + </plugins> + </build> + +</project> diff --git a/snowy-main/src/main/docker/docker-assembly.xml b/snowy-main/src/main/docker/docker-assembly.xml new file mode 100644 index 0000000..b77e47f --- /dev/null +++ b/snowy-main/src/main/docker/docker-assembly.xml @@ -0,0 +1,10 @@ +<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> + <files> + <file> + <source>target/${project.artifactId}.jar</source> + <destName>${project.artifactId}.jar</destName> + </file> + </files> +</assembly> \ No newline at end of file diff --git a/snowy-main/src/main/java/vip/xiaonuo/SnowyApplication.java b/snowy-main/src/main/java/vip/xiaonuo/SnowyApplication.java new file mode 100644 index 0000000..e215376 --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/SnowyApplication.java @@ -0,0 +1,23 @@ +package vip.xiaonuo; + +import cn.hutool.log.Log; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * SpringBoot方式启动类 + * + * @author yubaoshan + * @date 2017/5/21 12:06 + */ +@SpringBootApplication +public class SnowyApplication { + + private static final Log log = Log.get(); + + public static void main(String[] args) { + SpringApplication.run(SnowyApplication.class, args); + log.info(">>> " + SnowyApplication.class.getSimpleName() + " is success!"); + } + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/SnowyServletInitializer.java b/snowy-main/src/main/java/vip/xiaonuo/SnowyServletInitializer.java new file mode 100644 index 0000000..12e54fd --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/SnowyServletInitializer.java @@ -0,0 +1,19 @@ +package vip.xiaonuo; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * Snowy Web程序启动类 + * + * @author xuyuxiang + * @date 2017-05-21 9:43 + */ +public class SnowyServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(SnowyApplication.class); + } + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/controller/BlogArticleController.java b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/controller/BlogArticleController.java new file mode 100644 index 0000000..773878f --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/controller/BlogArticleController.java @@ -0,0 +1,148 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.blogarticle.controller; + +import vip.xiaonuo.core.annotion.BusinessLog; +import vip.xiaonuo.core.annotion.Permission; +import vip.xiaonuo.core.enums.LogAnnotionOpTypeEnum; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.modular.blogarticle.param.BlogArticleParam; +import vip.xiaonuo.modular.blogarticle.service.BlogArticleService; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import javax.annotation.Resource; +import java.util.List; + +/** + * blog文章主体控制器 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +@RestController +public class BlogArticleController { + + @Resource + private BlogArticleService blogArticleService; + + /** + * 查询blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + @Permission + @GetMapping("/blogArticle/page") + @BusinessLog(title = "blog文章主体_查询", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData page(BlogArticleParam blogArticleParam) { + return new SuccessResponseData(blogArticleService.page(blogArticleParam)); + } + + /** + * 添加blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + @Permission + @PostMapping("/blogArticle/add") + @BusinessLog(title = "blog文章主体_增加", opType = LogAnnotionOpTypeEnum.ADD) + public ResponseData add(@RequestBody @Validated(BlogArticleParam.add.class) BlogArticleParam blogArticleParam) { + blogArticleService.add(blogArticleParam); + return new SuccessResponseData(); + } + + /** + * 删除blog文章主体,可批量删除 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + @Permission + @PostMapping("/blogArticle/delete") + @BusinessLog(title = "blog文章主体_删除", opType = LogAnnotionOpTypeEnum.DELETE) + public ResponseData delete(@RequestBody @Validated(BlogArticleParam.delete.class) List<BlogArticleParam> blogArticleParamList) { + blogArticleService.delete(blogArticleParamList); + return new SuccessResponseData(); + } + + /** + * 编辑blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + @Permission + @PostMapping("/blogArticle/edit") + @BusinessLog(title = "blog文章主体_编辑", opType = LogAnnotionOpTypeEnum.EDIT) + public ResponseData edit(@RequestBody @Validated(BlogArticleParam.edit.class) BlogArticleParam blogArticleParam) { + blogArticleService.edit(blogArticleParam); + return new SuccessResponseData(); + } + + /** + * 查看blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + @Permission + @GetMapping("/blogArticle/detail") + @BusinessLog(title = "blog文章主体_查看", opType = LogAnnotionOpTypeEnum.DETAIL) + public ResponseData detail(@Validated(BlogArticleParam.detail.class) BlogArticleParam blogArticleParam) { + return new SuccessResponseData(blogArticleService.detail(blogArticleParam)); + } + + /** + * blog文章主体列表 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + @Permission + @GetMapping("/blogArticle/list") + @BusinessLog(title = "blog文章主体_列表", opType = LogAnnotionOpTypeEnum.QUERY) + public ResponseData list(BlogArticleParam blogArticleParam) { + return new SuccessResponseData(blogArticleService.list(blogArticleParam)); + } + + /** + * 导出系统用户 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + @Permission + @GetMapping("/blogArticle/export") + @BusinessLog(title = "blog文章主体_导出", opType = LogAnnotionOpTypeEnum.EXPORT) + public void export(BlogArticleParam blogArticleParam) { + blogArticleService.export(blogArticleParam); + } + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/entity/BlogArticle.java b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/entity/BlogArticle.java new file mode 100644 index 0000000..6c06354 --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/entity/BlogArticle.java @@ -0,0 +1,165 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.blogarticle.entity; + +import cn.afterturn.easypoi.excel.annotation.Excel; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.util.Date; + +/** + * blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +@Data +@TableName("blog_article") +public class BlogArticle{ + + /** + * 主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 文章标题 + */ + @Excel(name = "文章标题") + private String title; + + /** + * 文章文件id + */ + @Excel(name = "文章文件id") + private Long articleFileId; + + /** + * 文件类型 1:markdown 2:html + */ + @Excel(name = "文件类型 1:markdown 2:html") + private Integer articleFileType; + + /** + * 文章分类id 0:没有分类 + */ + @Excel(name = "文章分类id 0:没有分类") + private Long articleTypeId; + + /** + * 文章引言 + */ + @Excel(name = "文章引言") + private String introduce; + + /** + * 封面文件地址(id) + */ + @Excel(name = "封面文件地址(id)") + private Long coverFileId; + + /** + * 上次编辑时间 + */ + @Excel(name = "上次编辑时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd", width = 20) + private Date lastEditorDate; + + /** + * 发布时间 + */ + @Excel(name = "发布时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd", width = 20) + private Date publishDate; + + /** + * 是否置顶 0:否 1:是 + */ + @Excel(name = "是否置顶 0:否 1:是") + private Integer isTop; + + /** + * 置顶值(越小越靠前) + */ + @Excel(name = "置顶值(越小越靠前)") + private Integer topValue; + + /** + * 公开状态 1:公开 2:私密 3:密码授权 + */ + @Excel(name = "公开状态 1:公开 2:私密 3:密码授权") + private Integer authStatus; + + /** + * 授权密码(在密码授权状态时) + */ + @Excel(name = "授权密码(在密码授权状态时)") + private String authPassword; + + /** + * 编辑状态 0:草稿 1:发布 + */ + @Excel(name = "编辑状态 0:草稿 1:发布") + private Integer editorStatus; + + /** + * 归档年份(以初次发布时间为准) + */ + @Excel(name = "归档年份(以初次发布时间为准)") + private Integer separateYear; + + /** + * 归档月份 + */ + @Excel(name = "归档月份") + private Integer separateMonth; + + /** + * 归档日 + */ + @Excel(name = "归档日") + private Integer separateDay; + + /** + * 是否启用 0:否 1:是 + */ + @Excel(name = "是否启用 0:否 1:是") + private Integer isEnable; + + /** + * 更新时间 + */ + @Excel(name = "更新时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd", width = 20) + private Date updateDate; + + /** + * 创建时间 + */ + @Excel(name = "创建时间", databaseFormat = "yyyy-MM-dd HH:mm:ss", format = "yyyy-MM-dd", width = 20) + private Date createDate; + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/enums/BlogArticleExceptionEnum.java b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/enums/BlogArticleExceptionEnum.java new file mode 100644 index 0000000..07a01d8 --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/enums/BlogArticleExceptionEnum.java @@ -0,0 +1,64 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.blogarticle.enums; + +import vip.xiaonuo.core.annotion.ExpEnumType; +import vip.xiaonuo.core.exception.enums.abs.AbstractBaseExceptionEnum; +import vip.xiaonuo.core.factory.ExpEnumCodeFactory; +import vip.xiaonuo.sys.core.consts.SysExpEnumConstant; + +/** + * blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +@ExpEnumType(module = SysExpEnumConstant.SNOWY_SYS_MODULE_EXP_CODE) +public enum BlogArticleExceptionEnum implements AbstractBaseExceptionEnum { + + /** + * 数据不存在 + */ + NOT_EXIST(1, "此数据不存在"); + + private final Integer code; + + private final String message; + BlogArticleExceptionEnum(Integer code, String message) { + this.code = code; + this.message = message; + } + + @Override + public Integer getCode() { + return ExpEnumCodeFactory.getExpEnumCode(this.getClass(), code); + } + + @Override + public String getMessage() { + return message; + } + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/mapper/BlogArticleMapper.java b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/mapper/BlogArticleMapper.java new file mode 100644 index 0000000..60dcf8e --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/mapper/BlogArticleMapper.java @@ -0,0 +1,37 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.blogarticle.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import vip.xiaonuo.modular.blogarticle.entity.BlogArticle; + +/** + * blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +public interface BlogArticleMapper extends BaseMapper<BlogArticle> { +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/mapper/mapping/BlogArticleMapper.xml b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/mapper/mapping/BlogArticleMapper.xml new file mode 100644 index 0000000..ad494aa --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/mapper/mapping/BlogArticleMapper.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> +<mapper namespace="vip.xiaonuo.modular.blogarticle.mapper.BlogArticleMapper"> + +</mapper> diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/param/BlogArticleParam.java b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/param/BlogArticleParam.java new file mode 100644 index 0000000..27b37d7 --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/param/BlogArticleParam.java @@ -0,0 +1,169 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.blogarticle.param; + +import com.baomidou.mybatisplus.core.toolkit.AES; +import lombok.Data; +import vip.xiaonuo.core.pojo.base.param.BaseParam; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** +* blog文章主体参数类 + * + * @author inleft + * @date 2022-01-22 16:53:06 +*/ +@Data +public class BlogArticleParam extends BaseParam { + public static void main(String[] args) { + String data = "Root+redis"; + String randomKey = "inleftKey1234567"; + + String result = AES.encrypt(data, randomKey); + System.out.println(result); + } + /** + * 主键 + */ + @NotNull(message = "主键不能为空,请检查id参数", groups = {edit.class, delete.class, detail.class}) + private Long id; + + /** + * 文章标题 + */ + @NotBlank(message = "文章标题不能为空,请检查title参数", groups = {add.class, edit.class}) + private String title; + + /** + * 文章文件id + */ + @NotNull(message = "文章文件id不能为空,请检查articleFileId参数", groups = {add.class, edit.class}) + private Long articleFileId; + + /** + * 文件类型 1:markdown 2:html + */ + @NotNull(message = "文件类型 1:markdown 2:html不能为空,请检查articleFileType参数", groups = {add.class, edit.class}) + private Integer articleFileType; + + /** + * 文章分类id 0:没有分类 + */ + @NotNull(message = "文章分类id 0:没有分类不能为空,请检查articleTypeId参数", groups = {add.class, edit.class}) + private Long articleTypeId; + + /** + * 文章引言 + */ + @NotBlank(message = "文章引言不能为空,请检查introduce参数", groups = {add.class, edit.class}) + private String introduce; + + /** + * 封面文件地址(id) + */ + @NotNull(message = "封面文件地址(id)不能为空,请检查coverFileId参数", groups = {add.class, edit.class}) + private Long coverFileId; + + /** + * 上次编辑时间 + */ + @NotNull(message = "上次编辑时间不能为空,请检查lastEditorDate参数", groups = {add.class, edit.class}) + private String lastEditorDate; + + /** + * 发布时间 + */ + @NotNull(message = "发布时间不能为空,请检查publishDate参数", groups = {add.class, edit.class}) + private String publishDate; + + /** + * 是否置顶 0:否 1:是 + */ + @NotNull(message = "是否置顶 0:否 1:是不能为空,请检查isTop参数", groups = {add.class, edit.class}) + private Integer isTop; + + /** + * 置顶值(越小越靠前) + */ + @NotNull(message = "置顶值(越小越靠前)不能为空,请检查topValue参数", groups = {add.class, edit.class}) + private Integer topValue; + + /** + * 公开状态 1:公开 2:私密 3:密码授权 + */ + @NotNull(message = "公开状态 1:公开 2:私密 3:密码授权不能为空,请检查authStatus参数", groups = {add.class, edit.class}) + private Integer authStatus; + + /** + * 授权密码(在密码授权状态时) + */ + @NotBlank(message = "授权密码(在密码授权状态时)不能为空,请检查authPassword参数", groups = {add.class, edit.class}) + private String authPassword; + + /** + * 编辑状态 0:草稿 1:发布 + */ + @NotNull(message = "编辑状态 0:草稿 1:发布不能为空,请检查editorStatus参数", groups = {add.class, edit.class}) + private Integer editorStatus; + + /** + * 归档年份(以初次发布时间为准) + */ + @NotNull(message = "归档年份(以初次发布时间为准)不能为空,请检查separateYear参数", groups = {add.class, edit.class}) + private Integer separateYear; + + /** + * 归档月份 + */ + @NotNull(message = "归档月份不能为空,请检查separateMonth参数", groups = {add.class, edit.class}) + private Integer separateMonth; + + /** + * 归档日 + */ + @NotNull(message = "归档日不能为空,请检查separateDay参数", groups = {add.class, edit.class}) + private Integer separateDay; + + /** + * 是否启用 0:否 1:是 + */ + @NotNull(message = "是否启用 0:否 1:是不能为空,请检查isEnable参数", groups = {add.class, edit.class}) + private Integer isEnable; + + /** + * 更新时间 + */ + @NotNull(message = "更新时间不能为空,请检查updateDate参数", groups = {add.class, edit.class}) + private String updateDate; + + /** + * 创建时间 + */ + @NotNull(message = "创建时间不能为空,请检查createDate参数", groups = {add.class, edit.class}) + private String createDate; + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/service/BlogArticleService.java b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/service/BlogArticleService.java new file mode 100644 index 0000000..7619919 --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/service/BlogArticleService.java @@ -0,0 +1,97 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.blogarticle.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.modular.blogarticle.entity.BlogArticle; +import vip.xiaonuo.modular.blogarticle.param.BlogArticleParam; +import java.util.List; + +/** + * blog文章主体service接口 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +public interface BlogArticleService extends IService<BlogArticle> { + + /** + * 查询blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + PageResult<BlogArticle> page(BlogArticleParam blogArticleParam); + + /** + * blog文章主体列表 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + List<BlogArticle> list(BlogArticleParam blogArticleParam); + + /** + * 添加blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + void add(BlogArticleParam blogArticleParam); + + /** + * 删除blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + void delete(List<BlogArticleParam> blogArticleParamList); + + /** + * 编辑blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + void edit(BlogArticleParam blogArticleParam); + + /** + * 查看blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + BlogArticle detail(BlogArticleParam blogArticleParam); + + /** + * 导出blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + void export(BlogArticleParam blogArticleParam); + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/service/impl/BlogArticleServiceImpl.java b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/service/impl/BlogArticleServiceImpl.java new file mode 100644 index 0000000..d55be1c --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/blogarticle/service/impl/BlogArticleServiceImpl.java @@ -0,0 +1,196 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.blogarticle.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import vip.xiaonuo.core.consts.CommonConstant; +import vip.xiaonuo.core.enums.CommonStatusEnum; +import vip.xiaonuo.core.exception.ServiceException; +import vip.xiaonuo.core.factory.PageFactory; +import vip.xiaonuo.core.pojo.page.PageResult; +import vip.xiaonuo.core.util.PoiUtil; +import vip.xiaonuo.modular.blogarticle.entity.BlogArticle; +import vip.xiaonuo.modular.blogarticle.enums.BlogArticleExceptionEnum; +import vip.xiaonuo.modular.blogarticle.mapper.BlogArticleMapper; +import vip.xiaonuo.modular.blogarticle.param.BlogArticleParam; +import vip.xiaonuo.modular.blogarticle.service.BlogArticleService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import javax.annotation.Resource; +import java.util.List; + +/** + * blog文章主体service接口实现类 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ +@Service +public class BlogArticleServiceImpl extends ServiceImpl<BlogArticleMapper, BlogArticle> implements BlogArticleService { + + @Override + public PageResult<BlogArticle> page(BlogArticleParam blogArticleParam) { + QueryWrapper<BlogArticle> queryWrapper = new QueryWrapper<>(); + if (ObjectUtil.isNotNull(blogArticleParam)) { + + // 根据文章标题 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getTitle())) { + queryWrapper.lambda().eq(BlogArticle::getTitle, blogArticleParam.getTitle()); + } + // 根据文章文件id 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getArticleFileId())) { + queryWrapper.lambda().eq(BlogArticle::getArticleFileId, blogArticleParam.getArticleFileId()); + } + // 根据文件类型 1:markdown 2:html 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getArticleFileType())) { + queryWrapper.lambda().eq(BlogArticle::getArticleFileType, blogArticleParam.getArticleFileType()); + } + // 根据文章分类id 0:没有分类 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getArticleTypeId())) { + queryWrapper.lambda().eq(BlogArticle::getArticleTypeId, blogArticleParam.getArticleTypeId()); + } + // 根据文章引言 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getIntroduce())) { + queryWrapper.lambda().eq(BlogArticle::getIntroduce, blogArticleParam.getIntroduce()); + } + // 根据封面文件地址(id) 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getCoverFileId())) { + queryWrapper.lambda().eq(BlogArticle::getCoverFileId, blogArticleParam.getCoverFileId()); + } + // 根据上次编辑时间 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getLastEditorDate())) { + queryWrapper.lambda().eq(BlogArticle::getLastEditorDate, blogArticleParam.getLastEditorDate()); + } + // 根据发布时间 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getPublishDate())) { + queryWrapper.lambda().eq(BlogArticle::getPublishDate, blogArticleParam.getPublishDate()); + } + // 根据是否置顶 0:否 1:是 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getIsTop())) { + queryWrapper.lambda().eq(BlogArticle::getIsTop, blogArticleParam.getIsTop()); + } + // 根据置顶值(越小越靠前) 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getTopValue())) { + queryWrapper.lambda().eq(BlogArticle::getTopValue, blogArticleParam.getTopValue()); + } + // 根据公开状态 1:公开 2:私密 3:密码授权 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getAuthStatus())) { + queryWrapper.lambda().eq(BlogArticle::getAuthStatus, blogArticleParam.getAuthStatus()); + } + // 根据授权密码(在密码授权状态时) 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getAuthPassword())) { + queryWrapper.lambda().eq(BlogArticle::getAuthPassword, blogArticleParam.getAuthPassword()); + } + // 根据编辑状态 0:草稿 1:发布 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getEditorStatus())) { + queryWrapper.lambda().eq(BlogArticle::getEditorStatus, blogArticleParam.getEditorStatus()); + } + // 根据归档年份(以初次发布时间为准) 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getSeparateYear())) { + queryWrapper.lambda().eq(BlogArticle::getSeparateYear, blogArticleParam.getSeparateYear()); + } + // 根据归档月份 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getSeparateMonth())) { + queryWrapper.lambda().eq(BlogArticle::getSeparateMonth, blogArticleParam.getSeparateMonth()); + } + // 根据归档日 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getSeparateDay())) { + queryWrapper.lambda().eq(BlogArticle::getSeparateDay, blogArticleParam.getSeparateDay()); + } + // 根据是否启用 0:否 1:是 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getIsEnable())) { + queryWrapper.lambda().eq(BlogArticle::getIsEnable, blogArticleParam.getIsEnable()); + } + // 根据更新时间 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getUpdateDate())) { + queryWrapper.lambda().eq(BlogArticle::getUpdateDate, blogArticleParam.getUpdateDate()); + } + // 根据创建时间 查询 + if (ObjectUtil.isNotEmpty(blogArticleParam.getCreateDate())) { + queryWrapper.lambda().eq(BlogArticle::getCreateDate, blogArticleParam.getCreateDate()); + } + } + return new PageResult<>(this.page(PageFactory.defaultPage(), queryWrapper)); + } + + @Override + public List<BlogArticle> list(BlogArticleParam blogArticleParam) { + return this.list(); + } + + @Override + public void add(BlogArticleParam blogArticleParam) { + BlogArticle blogArticle = new BlogArticle(); + BeanUtil.copyProperties(blogArticleParam, blogArticle); + this.save(blogArticle); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(List<BlogArticleParam> blogArticleParamList) { + blogArticleParamList.forEach(blogArticleParam -> { + this.removeById(blogArticleParam.getId()); + }); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void edit(BlogArticleParam blogArticleParam) { + BlogArticle blogArticle = this.queryBlogArticle(blogArticleParam); + BeanUtil.copyProperties(blogArticleParam, blogArticle); + this.updateById(blogArticle); + } + + @Override + public BlogArticle detail(BlogArticleParam blogArticleParam) { + return this.queryBlogArticle(blogArticleParam); + } + + /** + * 获取blog文章主体 + * + * @author inleft + * @date 2022-01-22 16:53:06 + */ + private BlogArticle queryBlogArticle(BlogArticleParam blogArticleParam) { + BlogArticle blogArticle = this.getById(blogArticleParam.getId()); + if (ObjectUtil.isNull(blogArticle)) { + throw new ServiceException(BlogArticleExceptionEnum.NOT_EXIST); + } + return blogArticle; + } + + @Override + public void export(BlogArticleParam blogArticleParam) { + List<BlogArticle> list = this.list(blogArticleParam); + PoiUtil.exportExcelWithStream("SnowyBlogArticle.xls", BlogArticle.class, list); + } + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/controller/DatasourceExampleController.java b/snowy-main/src/main/java/vip/xiaonuo/modular/controller/DatasourceExampleController.java new file mode 100644 index 0000000..75d80b2 --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/controller/DatasourceExampleController.java @@ -0,0 +1,76 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import vip.xiaonuo.core.pojo.response.ResponseData; +import vip.xiaonuo.core.pojo.response.SuccessResponseData; +import vip.xiaonuo.modular.service.DatasourceExampleService; + +import javax.annotation.Resource; + +/** + * 一个示例接口 + * + * @author yubaoshan + * @date 2020/4/9 18:09 + */ +@RestController +@RequestMapping("/example") +public class DatasourceExampleController { + + @Resource + private DatasourceExampleService datasourceService; + + @GetMapping("/niceDay") + public ResponseData niceDay() { + return new SuccessResponseData("nice day"); + } + + @GetMapping("/masterDatasource") + public ResponseData masterDatasource() { + return new SuccessResponseData(datasourceService.masterDatasource()); + } + + @GetMapping("/backupDatasource") + public ResponseData backupDatasource() { + return new SuccessResponseData(datasourceService.backupDatasource()); + } + + @GetMapping("/datasourceTransactionNone") + public ResponseData datasourceTransactionNone() { + datasourceService.datasourceTransactionNone(); + return new SuccessResponseData(); + } + + @GetMapping("/datasourceTransaction") + public ResponseData datasourceTransaction() { + datasourceService.datasourceTransaction(); + return new SuccessResponseData(); + } + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/model/AbModel.java b/snowy-main/src/main/java/vip/xiaonuo/modular/model/AbModel.java new file mode 100644 index 0000000..772f934 --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/model/AbModel.java @@ -0,0 +1,45 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.model; + +/** + * 一个示例model + * + * @author yubaoshan + * @date 2020/4/9 18:10 + */ +public class AbModel { + + /** + * 字段a + */ + private String a; + + /** + * 字段b + */ + private String b; + +} diff --git a/snowy-main/src/main/java/vip/xiaonuo/modular/service/DatasourceExampleService.java b/snowy-main/src/main/java/vip/xiaonuo/modular/service/DatasourceExampleService.java new file mode 100644 index 0000000..0d592c8 --- /dev/null +++ b/snowy-main/src/main/java/vip/xiaonuo/modular/service/DatasourceExampleService.java @@ -0,0 +1,89 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.modular.service; + +import cn.hutool.core.util.RandomUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import vip.xiaonuo.sys.modular.app.param.SysAppParam; +import vip.xiaonuo.sys.modular.app.service.SysAppService; +import vip.xiaonuo.sys.modular.log.entity.SysVisLog; +import vip.xiaonuo.sys.modular.log.service.SysVisLogService; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 一个service实现 + * + * @author yubaoshan + * @date 2020/4/9 18:11 + */ +@Service +public class DatasourceExampleService { + + @Resource + private SysVisLogService sysVisLogService; + + @Resource + private SysAppService sysAppService; + + public List<SysVisLog> masterDatasource() { + return sysVisLogService.list(); + } + + public List<SysVisLog> backupDatasource() { + return sysVisLogService.list(); + } + + public void datasourceTransactionNone() { + + SysAppParam sysAppParam = new SysAppParam(); + sysAppParam.setName(RandomUtil.randomNumbers(5)); + sysAppParam.setCode(RandomUtil.randomNumbers(5)); + sysAppParam.setActive("N"); + + sysAppService.add(sysAppParam); + + //抛异常测试 + int i = 1 / 0; + } + + //@DataSource(name = "master") + @Transactional(rollbackFor = Exception.class) + public void datasourceTransaction() { + + SysAppParam sysAppParam = new SysAppParam(); + sysAppParam.setName(RandomUtil.randomNumbers(5)); + sysAppParam.setCode(RandomUtil.randomNumbers(5)); + sysAppParam.setActive("N"); + + sysAppService.add(sysAppParam); + + //抛异常测试 + int i = 1 / 0; + } + +} diff --git a/snowy-main/src/main/resources/application-dev.yml b/snowy-main/src/main/resources/application-dev.yml new file mode 100644 index 0000000..1c13fda --- /dev/null +++ b/snowy-main/src/main/resources/application-dev.yml @@ -0,0 +1,56 @@ +# Mysql数据库 +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/snowy-pub?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT&nullCatalogMeansCurrent=true + username: root + password: 123456 + redis: + host: localhost + port: 6379 + password: + +# Oracle数据库 +#spring: +# datasource: +# driver-class-name: oracle.jdbc.OracleDriver +# url: jdbc:oracle:thin:@localhost:1521:xe +# username: SNOWY-PUB-ORACLE +# password: 123456 + +# SQLServer配置 +#spring: +# datasource: +# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver +# url: jdbc:sqlserver://localhost:1433;DatabaseName=snowy-pub-mssql +# username: sa +# password: 123456 + +# PostgreSQL配置 +#spring: +# datasource: +# driverClassName: org.postgresql.Driver +# url: jdbc:postgresql://127.0.0.1:5432/snowy-pub-postgresql +# username: postgres +# password: 123456 + +# 达梦数据库 +#spring: +# datasource: +# driver-class-name: dm.jdbc.driver.DmDriver +# url: jdbc:dm://localhost:5236/snowy-pub-dm +# username: SNOWY +# password: 123456789 +# #达梦数据库兼容问题,不需要在sql语句前加模式名的解决方法: +# #https://blog.csdn.net/myth8860/article/details/100557705 + +# 人大金仓数据库 +#spring: +# datasource: +# driver-class-name: com.kingbase8.Driver +# url: jdbc:kingbase8://localhost:54321/snowy-pub-kingbase +# username: SYSTEM +# password: 123456 +# #人大金仓数据库兼容问题,不需要加在sql语句中加public的解决方法: +# #在根目录data下的kingbase.conf文档里面找到search_path = '"$user",PUBLIC,sys_catalog'进行替换放开 +# #重启数据库即可完全兼容,注意 sql中不能出现mysql中的关键字的单引号 diff --git a/snowy-main/src/main/resources/application-local.yml b/snowy-main/src/main/resources/application-local.yml new file mode 100644 index 0000000..a56b002 --- /dev/null +++ b/snowy-main/src/main/resources/application-local.yml @@ -0,0 +1,59 @@ +# Mysql数据库 +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: mpw:pOR3O+UltK0HTNqvX/MeDsJDOCUeDoAnP5TsPqREtj384CDziueYbj8Z0bha3LWM1LdIKYTQuyS9cEaTceRHtJQIZymWMwsKEV++JDZgqBVnx9B5ztb3dGk3IhB678/E/rH1lJ8Jpzw21EkgPcie3A6ELU62rlJUycpcnK95oBDXKYZeMclnNdMVH5PVP70z5cWc5Ai741/1a/doxqo92IFnBMMyJG+r4B7KXBf3/LMm6xpvUm6e4xYPzpoy4hhNFp4TfNBWcsO0BEMH1GJaMw== + username: mpw:Zr3vDuEdTm3NPpydFgU2eA== + password: mpw:KWYe3LgV6I2rToYRn3k35g== +# url: jdbc:mysql://localhost:3306/blog?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT&nullCatalogMeansCurrent=true +# username: root +# password: root + redis: + host: mpw:74taZBJwkaUlVuljmdqsRg== + port: 6379 + password: mpw:xMwjz92TmxCDCJYvcfwIZA== + +# Oracle数据库 +#spring: +# datasource: +# driver-class-name: oracle.jdbc.OracleDriver +# url: jdbc:oracle:thin:@localhost:1521:xe +# username: SNOWY-PUB-ORACLE +# password: 123456 + +# SQLServer配置 +#spring: +# datasource: +# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver +# url: jdbc:sqlserver://localhost:1433;DatabaseName=snowy-pub-mssql +# username: sa +# password: 123456 + +# PostgreSQL配置 +#spring: +# datasource: +# driverClassName: org.postgresql.Driver +# url: jdbc:postgresql://127.0.0.1:5432/snowy-pub-postgresql +# username: postgres +# password: 123456 + +# 达梦数据库 +#spring: +# datasource: +# driver-class-name: dm.jdbc.driver.DmDriver +# url: jdbc:dm://localhost:5236/snowy-pub-dm +# username: SNOWY +# password: 123456789 +# #达梦数据库兼容问题,不需要在sql语句前加模式名的解决方法: +# #https://blog.csdn.net/myth8860/article/details/100557705 + +# 人大金仓数据库 +#spring: +# datasource: +# driver-class-name: com.kingbase8.Driver +# url: jdbc:kingbase8://localhost:54321/snowy-pub-kingbase +# username: SYSTEM +# password: 123456 +# #人大金仓数据库兼容问题,不需要加在sql语句中加public的解决方法: +# #在根目录data下的kingbase.conf文档里面找到search_path = '"$user",PUBLIC,sys_catalog'进行替换放开 +# #重启数据库即可完全兼容,注意 sql中不能出现mysql中的关键字的单引号 diff --git a/snowy-main/src/main/resources/application-prod.yml b/snowy-main/src/main/resources/application-prod.yml new file mode 100644 index 0000000..ea82253 --- /dev/null +++ b/snowy-main/src/main/resources/application-prod.yml @@ -0,0 +1,56 @@ +JwtAuthenticationTokenFilter# Mysql数据库 +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/snowy-pub?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT&nullCatalogMeansCurrent=true + username: root + password: 123456 + redis: + host: localhost + port: 6379 + password: + +# Oracle数据库 +#spring: +# datasource: +# driver-class-name: oracle.jdbc.OracleDriver +# url: jdbc:oracle:thin:@localhost:1521:xe +# username: SNOWY-PUB-ORACLE +# password: 123456 + +# SQLServer配置 +#spring: +# datasource: +# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver +# url: jdbc:sqlserver://localhost:1433;DatabaseName=snowy-pub-mssql +# username: sa +# password: 123456 + +# PostgreSQL配置 +#spring: +# datasource: +# driverClassName: org.postgresql.Driver +# url: jdbc:postgresql://127.0.0.1:5432/snowy-pub-postgresql +# username: postgres +# password: 123456 + +# 达梦数据库 +#spring: +# datasource: +# driver-class-name: dm.jdbc.driver.DmDriver +# url: jdbc:dm://localhost:5236/snowy-pub-dm +# username: SNOWY +# password: 123456789 +# #达梦数据库兼容问题,不需要在sql语句前加模式名的解决方法: +# #https://blog.csdn.net/myth8860/article/details/100557705 + +# 人大金仓数据库 +#spring: +# datasource: +# driver-class-name: com.kingbase8.Driver +# url: jdbc:kingbase8://localhost:54321/snowy-pub-kingbase +# username: SYSTEM +# password: 123456 +# #人大金仓数据库兼容问题,不需要加在sql语句中加public的解决方法: +# #在根目录data下的kingbase.conf文档里面找到search_path = '"$user",PUBLIC,sys_catalog'进行替换放开 +# #重启数据库即可完全兼容,注意 sql中不能出现mysql中的关键字的单引号 diff --git a/snowy-main/src/main/resources/application.yml b/snowy-main/src/main/resources/application.yml new file mode 100644 index 0000000..8378d9e --- /dev/null +++ b/snowy-main/src/main/resources/application.yml @@ -0,0 +1,76 @@ +#服务配置 +server: + port: 82 + max-http-header-size: 10240 + +#spring相关配置 +spring: + profiles: + active: @spring.active@ + servlet: + multipart: + max-request-size: 100MB + max-file-size: 100MB + jackson: + time-zone: GMT+8 + date-format: yyyy-MM-dd HH:mm:ss.SSS + locale: zh_CN + serialization: + # 格式化输出 + indent_output: false + +#mybaits相关配置 +mybatis-plus: + mapper-locations: classpath*:vip/xiaonuo/**/mapping/*.xml, classpath:/META-INF/modeler-mybatis-mappings/*.xml + configuration: + map-underscore-to-camel-case: true + cache-enabled: true + lazy-loading-enabled: true + multiple-result-sets-enabled: true + log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl + global-config: + banner: false + db-config: + id-type: assign_id + table-underline: true + enable-sql-runner: true + configuration-properties: + prefix: + #如果数据库为postgresql,则需要配置为blobType: BINARY + blobType: BLOB + #如果数据库为oracle或mssql,则需要配置为boolValue: 1 + boolValue: true + +#libreoffice文档在线预览配置 +# CentOS 下安装 libreoffice: +# 安装:yum -y install libreoffice +# Linux 中文字体乱码解决: +# 1、上传 C:\Windows\Fonts 下的字体到 /usr/share/fonts/windows 目录 +# 2、执行命令: chmod 644 /usr/share/fonts/windows/* && fc-cache -fv +jodconverter: + local: + #暂时关闭预览,启动时会有点慢 + enabled: false + #设置libreoffice主目录 linux地址如:/usr/lib64/libreoffice + office-home: C:\Program Files\LibreOffice + #开启多个libreoffice进程,每个端口对应一个进程 + port-numbers: 8100 + #libreoffice进程重启前的最大进程数 + max-tasks-per-process: 100 + +#验证码相关配置 去除日志打印 +logging: + level: + com.anji: off +#验证码相关配置 +aj: + captcha: + cache-type: local #分布式部署需要 自己实现CaptchaCacheService 使用redis需要配置redis相关配置 + type: default #验证码类型 clickword 为点选 blockPuzzle 为滑块验证码 default 两种都实例化 + font-type: 宋体 + req-frequency-limit-enable: true #接口请求次数一分钟限制是否开启 true|false + req-get-lock-limit: 2 # 验证失败2次,get接口锁定 + req-get-lock-seconds: 10 # 验证失败后,锁定时间间隔,s + req-get-minute-limit: 30 # get接口一分钟内请求数限制 + req-check-minute-limit: 60 # check接口一分钟内请求数限制 + req-verify-minute-limit: 60 # verify接口一分钟内请求数限制 diff --git a/snowy-main/src/main/resources/banner.txt b/snowy-main/src/main/resources/banner.txt new file mode 100644 index 0000000..14932ab --- /dev/null +++ b/snowy-main/src/main/resources/banner.txt @@ -0,0 +1,9 @@ + + _____ + / ___/____ ____ _ ____ __ + \__ \/ __ \/ __ \ | /| / / / / / + ___/ / / / / /_/ / |/ |/ / /_/ / +/____/_/ /_/\____/|__/|__/\__, / + /____/ + + :: Spring Boot :: (v2.3.1.RELEASE) \ No newline at end of file diff --git a/snowy-main/src/main/resources/logback-spring.xml b/snowy-main/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..0994459 --- /dev/null +++ b/snowy-main/src/main/resources/logback-spring.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + + <!--日志格式应用spring boot默认的格式,也可以自己更改--> + <include resource="org/springframework/boot/logging/logback/defaults.xml"/> + + <!--定义日志存放的位置,默认存放在项目启动的相对路径的目录--> + <springProperty scope="context" name="LOG_PATH" source="log.path" defaultValue="app-log"/> + + <!-- ****************************************************************************************** --> + <!-- ****************************** 本地开发只在控制台打印日志 ************************************ --> + <!-- ****************************************************************************************** --> + <springProfile name="local"> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>${CONSOLE_LOG_PATTERN}</pattern> + <charset>utf-8</charset> + </encoder> + </appender> + + <!--默认所有的包以info--> + <root level="info"> + <appender-ref ref="STDOUT"/> + </root> + + <!--各个服务的包在本地执行的时候,打开debug模式--> + <logger name="vip.xiaonuo" level="info" additivity="false"> + <appender-ref ref="STDOUT"/> + </logger> + </springProfile> + + <!-- ********************************************************************************************** --> + <!-- **** 放到服务器上不管在什么环境都只在文件记录日志,控制台(catalina.out)打印logback捕获不到的日志 **** --> + <!-- ********************************************************************************************** --> + <springProfile name="!local"> + + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>${CONSOLE_LOG_PATTERN}</pattern> + <charset>utf-8</charset> + </encoder> + </appender> + + <!-- 日志记录器,日期滚动记录 --> + <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> + + <!-- 正在记录的日志文件的路径及文件名 --> + <file>${LOG_PATH}/log_error.log</file> + + <!-- 日志记录器的滚动策略,按日期,按大小记录 --> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + + <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 --> + <fileNamePattern>${LOG_PATH}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> + + <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始, + 命名日志文件,例如log-error-2013-12-21.0.log --> + <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> + <maxFileSize>10MB</maxFileSize> + </timeBasedFileNamingAndTriggeringPolicy> + </rollingPolicy> + + <!-- 追加方式记录日志 --> + <append>true</append> + + <!-- 日志文件的格式 --> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern>${FILE_LOG_PATTERN}</pattern> + <charset>utf-8</charset> + </encoder> + + <!-- 此日志文件只记录error级别的 --> + <filter class="ch.qos.logback.classic.filter.LevelFilter"> + <level>error</level> + <onMatch>ACCEPT</onMatch> + <onMismatch>DENY</onMismatch> + </filter> + </appender> + + <!-- 日志记录器,日期滚动记录 --> + <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender"> + + <!-- 正在记录的日志文件的路径及文件名 --> + <file>${LOG_PATH}/log_total.log</file> + + <!-- 日志记录器的滚动策略,按日期,按大小记录 --> + <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> + + <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 --> + <fileNamePattern>${LOG_PATH}/total/log-total-%d{yyyy-MM-dd}.%i.log</fileNamePattern> + + <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始, + 命名日志文件,例如log-error-2013-12-21.0.log --> + <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> + <maxFileSize>10MB</maxFileSize> + </timeBasedFileNamingAndTriggeringPolicy> + </rollingPolicy> + + <!-- 追加方式记录日志 --> + <append>true</append> + + <!-- 日志文件的格式 --> + <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> + <pattern>${FILE_LOG_PATTERN}</pattern> + <charset>utf-8</charset> + </encoder> + </appender> + + <!--记录到文件时,记录两类一类是error日志,一个是所有日志--> + <root level="info"> + <appender-ref ref="STDOUT"/> + <appender-ref ref="FILE_ERROR"/> + <appender-ref ref="FILE_ALL"/> + </root> + + </springProfile> + +</configuration> + + diff --git a/snowy-main/src/test/java/vip/xiaonuo/core/BaseJunit.java b/snowy-main/src/test/java/vip/xiaonuo/core/BaseJunit.java new file mode 100644 index 0000000..4c50044 --- /dev/null +++ b/snowy-main/src/test/java/vip/xiaonuo/core/BaseJunit.java @@ -0,0 +1,67 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import vip.xiaonuo.SnowyApplication; + +import javax.annotation.Resource; + + +/** + * 基础测试类 + * + * @author yubaoshan + * @date 2017/5/21 16:10 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = SnowyApplication.class) +@WebAppConfiguration +//@Transactional(rollbackFor = Exception.class) //打开的话测试之后数据可自动回滚 +public class BaseJunit { + + @Resource + private WebApplicationContext webApplicationContext; + + private MockMvc mockMvc; + + @Before + public void setupMockMvc() { + mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); + } + + @Before + public void initDatabase() { + } + + +} diff --git a/snowy-main/src/test/java/vip/xiaonuo/core/Test.java b/snowy-main/src/test/java/vip/xiaonuo/core/Test.java new file mode 100644 index 0000000..67b670e --- /dev/null +++ b/snowy-main/src/test/java/vip/xiaonuo/core/Test.java @@ -0,0 +1,39 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core; + + +/** + * 测试类 + * + * @author xuyuxiang + * @date 2020/3/16 11:25 + */ +public class Test extends BaseJunit { + + @org.junit.Test + public void test() { + } +} diff --git a/snowy-main/src/test/java/vip/xiaonuo/core/Test2.java b/snowy-main/src/test/java/vip/xiaonuo/core/Test2.java new file mode 100644 index 0000000..3f0c29a --- /dev/null +++ b/snowy-main/src/test/java/vip/xiaonuo/core/Test2.java @@ -0,0 +1,41 @@ +/* +Copyright [2020] [https://www.xiaonuo.vip] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: + +1.请不要删除和修改根目录下的LICENSE文件。 +2.请不要删除和修改Snowy源码头部的版权声明。 +3.请保留源码和相关描述文件的项目出处,作者声明等。 +4.分发源码时候,请注明软件出处 https://gitee.com/xiaonuobase/snowy +5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/xiaonuobase/snowy +6.若您的项目无法满足以上几点,可申请商业授权,获取Snowy商业授权许可,请在官网购买授权,地址为 https://www.xiaonuo.vip + */ +package vip.xiaonuo.core; +import cn.hutool.core.date.DateUtil; +import vip.xiaonuo.core.util.PastTimeFormatUtil; + +/** + * 纯test + * + * @author xuyuxiang + * @date 2020/5/2014:29 + */ +public class Test2 { + + public static void main(String[] args) { + String s = PastTimeFormatUtil.formatPastTime(DateUtil.parseDateTime("2020-08-05 19:24:33")); + System.out.println(s); + } +} diff --git a/snowy-main/src/test/sql/test.sql b/snowy-main/src/test/sql/test.sql new file mode 100644 index 0000000..3d75660 --- /dev/null +++ b/snowy-main/src/test/sql/test.sql @@ -0,0 +1,19 @@ +DROP DATABASE IF EXISTS snowy_test; +CREATE DATABASE IF NOT EXISTS snowy_test DEFAULT CHARSET utf8 COLLATE utf8_general_ci; + +use snowy_test; + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for test +-- ---------------------------- +DROP TABLE IF EXISTS `test`; +CREATE TABLE `test` ( + `aaa` int(11) NOT NULL AUTO_INCREMENT, + `bbb` varchar(255) DEFAULT NULL, + PRIMARY KEY (`aaa`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; + +SET FOREIGN_KEY_CHECKS = 1; -- Gitblit v1.9.1