Repository: oddfar/campus-imaotai Branch: master Commit: ff23e422111f Files: 448 Total size: 1.3 MB Directory structure: gitextract_lhpb8umu/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yaml │ │ └── feature_request.md │ └── workflows/ │ ├── api.yml │ ├── release.yml │ └── web.yml ├── .gitignore ├── .run/ │ └── campus-server.run.xml ├── LICENSE ├── README.md ├── campus-admin/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── oddfar/ │ └── campus/ │ └── admin/ │ ├── config/ │ │ ├── CaptchaConfig.java │ │ └── SwaggerConfig.java │ └── controller/ │ ├── monitor/ │ │ ├── SysLogininforController.java │ │ └── SysOperlogController.java │ └── system/ │ ├── CaptchaController.java │ ├── SysApiResourceController.java │ ├── SysConfigController.java │ ├── SysDictDataController.java │ ├── SysDictTypeController.java │ ├── SysIndexController.java │ ├── SysLoginController.java │ ├── SysMenuController.java │ ├── SysProfileController.java │ ├── SysRegisterController.java │ ├── SysRoleController.java │ └── SysUserController.java ├── campus-common/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── oddfar/ │ └── campus/ │ └── common/ │ ├── annotation/ │ │ ├── Anonymous.java │ │ ├── ApiResource.java │ │ ├── Log.java │ │ ├── RepeatSubmit.java │ │ └── Sensitive.java │ ├── config/ │ │ ├── CampusConfig.java │ │ ├── FastJson2JsonRedisSerializer.java │ │ ├── RedisConfig.java │ │ └── SensitiveSerializer.java │ ├── constant/ │ │ ├── CacheConstants.java │ │ ├── Constants.java │ │ ├── HttpStatus.java │ │ └── UserConstants.java │ ├── core/ │ │ ├── BaseMapperX.java │ │ ├── LambdaQueryWrapperX.java │ │ ├── RedisCache.java │ │ ├── page/ │ │ │ ├── PageQuery.java │ │ │ └── TableDataInfo.java │ │ └── text/ │ │ ├── CharsetKit.java │ │ └── Convert.java │ ├── domain/ │ │ ├── BaseEntity.java │ │ ├── PageResult.java │ │ ├── R.java │ │ ├── TreeSelect.java │ │ ├── entity/ │ │ │ ├── SysConfigEntity.java │ │ │ ├── SysDictDataEntity.java │ │ │ ├── SysDictTypeEntity.java │ │ │ ├── SysLoginLogEntity.java │ │ │ ├── SysMenuEntity.java │ │ │ ├── SysOperLogEntity.java │ │ │ ├── SysResourceEntity.java │ │ │ ├── SysRoleEntity.java │ │ │ ├── SysRoleMenuEntity.java │ │ │ ├── SysRoleResourceEntity.java │ │ │ ├── SysUserEntity.java │ │ │ └── SysUserRoleEntity.java │ │ ├── model/ │ │ │ ├── LoginBody.java │ │ │ ├── LoginUser.java │ │ │ ├── LoginUserToken.java │ │ │ ├── RegisterBody.java │ │ │ ├── SysRoleAuth.java │ │ │ └── SysRoleAuthList.java │ │ └── vo/ │ │ └── RouterVo.java │ ├── enums/ │ │ ├── BizCodeEnum.java │ │ ├── BusinessStatus.java │ │ ├── ResBizTypeEnum.java │ │ ├── SensitiveStrategy.java │ │ └── UserStatusEnum.java │ ├── exception/ │ │ ├── ServiceException.java │ │ ├── UtilException.java │ │ ├── base/ │ │ │ └── BaseException.java │ │ ├── file/ │ │ │ ├── FileException.java │ │ │ ├── FileNameLengthLimitExceededException.java │ │ │ ├── FileSizeLimitExceededException.java │ │ │ └── InvalidExtensionException.java │ │ └── user/ │ │ ├── CaptchaException.java │ │ ├── CaptchaExpireException.java │ │ ├── UserException.java │ │ ├── UserPasswordNotMatchException.java │ │ └── UserPasswordRetryLimitExceedException.java │ ├── filter/ │ │ ├── PropertyPreExcludeFilter.java │ │ └── RepeatedlyRequestWrapper.java │ ├── utils/ │ │ ├── DateUtils.java │ │ ├── DictUtils.java │ │ ├── LogUtils.java │ │ ├── MessageUtils.java │ │ ├── MetaVo.java │ │ ├── SecurityUtils.java │ │ ├── ServletUtils.java │ │ ├── SpringUtils.java │ │ ├── StringUtils.java │ │ ├── Threads.java │ │ ├── http/ │ │ │ ├── HttpHelper.java │ │ │ └── HttpUtils.java │ │ ├── ip/ │ │ │ ├── AddressUtils.java │ │ │ └── IpUtils.java │ │ ├── pol/ │ │ │ └── ExcelHandlerAdapter.java │ │ ├── reflect/ │ │ │ └── ReflectUtils.java │ │ ├── spring/ │ │ │ └── AopTargetUtils.java │ │ ├── sql/ │ │ │ └── SqlUtil.java │ │ ├── uuid/ │ │ │ ├── IdUtils.java │ │ │ └── Seq.java │ │ └── web/ │ │ └── WebFrameworkUtils.java │ └── validator/ │ ├── Xss.java │ └── XssValidator.java ├── campus-framework/ │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── oddfar/ │ │ └── campus/ │ │ └── framework/ │ │ ├── api/ │ │ │ ├── file/ │ │ │ │ ├── FileOperatorApi.java │ │ │ │ ├── FileUploadUtils.java │ │ │ │ ├── LocalFileOperator.java │ │ │ │ ├── MimeTypeUtils.java │ │ │ │ └── ZyFileAutoConfiguration.java │ │ │ ├── mail/ │ │ │ │ ├── Impl/ │ │ │ │ │ └── MailServiceImpl.java │ │ │ │ ├── MailConfigRead.java │ │ │ │ ├── MailSendApi.java │ │ │ │ ├── MailSendContext.java │ │ │ │ └── ZyMailAutoConfig.java │ │ │ ├── resource/ │ │ │ │ ├── ApiResourceAutoConfig.java │ │ │ │ ├── ResourceCollectorApi.java │ │ │ │ └── impl/ │ │ │ │ └── DefaultResourceCollector.java │ │ │ └── sysconfig/ │ │ │ ├── ConfigContext.java │ │ │ ├── ConfigExpander.java │ │ │ └── ZyConfigAutoConfiguration.java │ │ ├── aspectj/ │ │ │ └── LogAspect.java │ │ ├── config/ │ │ │ ├── ApplicationConfig.java │ │ │ ├── AsyncConfig.java │ │ │ ├── JacksonConfig.java │ │ │ ├── KaptchaTextCreator.java │ │ │ ├── MyWebMvcConfig.java │ │ │ ├── MybatisPlusConfig.java │ │ │ ├── SecurityConfig.java │ │ │ └── ThreadPoolConfig.java │ │ ├── expander/ │ │ │ ├── SysConfigExpander.java │ │ │ └── SysFileConfigExpander.java │ │ ├── handler/ │ │ │ ├── BigNumberSerializer.java │ │ │ └── MyDBFieldHandler.java │ │ ├── interceptor/ │ │ │ ├── RepeatSubmitInterceptor.java │ │ │ └── impl/ │ │ │ └── SameUrlDataInterceptor.java │ │ ├── listener/ │ │ │ ├── ApiResourceScanner.java │ │ │ ├── ReadyEventListener.java │ │ │ └── ResourceReportListener.java │ │ ├── manager/ │ │ │ ├── AsyncFactory.java │ │ │ └── AsyncManager.java │ │ ├── mapper/ │ │ │ ├── SysConfigMapper.java │ │ │ ├── SysDictDataMapper.java │ │ │ ├── SysDictTypeMapper.java │ │ │ ├── SysLoginLogMapper.java │ │ │ ├── SysMenuMapper.java │ │ │ ├── SysOperLogMapper.java │ │ │ ├── SysResourceMapper.java │ │ │ ├── SysRoleMapper.java │ │ │ ├── SysRoleMenuMapper.java │ │ │ ├── SysRoleResourceMapper.java │ │ │ ├── SysUserMapper.java │ │ │ └── SysUserRoleMapper.java │ │ ├── security/ │ │ │ ├── context/ │ │ │ │ ├── AuthenticationContextHolder.java │ │ │ │ └── PermissionContextHolder.java │ │ │ ├── filter/ │ │ │ │ └── JwtAuthenticationTokenFilter.java │ │ │ ├── handle/ │ │ │ │ ├── AuthenticationEntryPointImpl.java │ │ │ │ └── LogoutSuccessHandlerImpl.java │ │ │ └── properties/ │ │ │ └── PermitAllUrlProperties.java │ │ ├── service/ │ │ │ ├── SysConfigService.java │ │ │ ├── SysDictDataService.java │ │ │ ├── SysDictTypeService.java │ │ │ ├── SysLoginLogService.java │ │ │ ├── SysMenuService.java │ │ │ ├── SysOperLogService.java │ │ │ ├── SysResourceService.java │ │ │ ├── SysRoleService.java │ │ │ ├── SysUserService.java │ │ │ └── impl/ │ │ │ ├── SysConfigServiceImpl.java │ │ │ ├── SysDictDataServiceImpl.java │ │ │ ├── SysDictTypeServiceImpl.java │ │ │ ├── SysLoginLogServiceImpl.java │ │ │ ├── SysMenuServiceImpl.java │ │ │ ├── SysOperLogServiceImpl.java │ │ │ ├── SysResourceServiceImpl.java │ │ │ ├── SysRoleServiceImpl.java │ │ │ └── SysUserServiceImpl.java │ │ └── web/ │ │ ├── exception/ │ │ │ └── GlobalExceptionHandler.java │ │ └── service/ │ │ ├── PermissionService.java │ │ ├── SysLoginService.java │ │ ├── SysPasswordService.java │ │ ├── SysPermissionService.java │ │ ├── SysRegisterService.java │ │ ├── TokenService.java │ │ └── UserDetailsServiceImpl.java │ └── resources/ │ └── mapper/ │ ├── SysConfigMapper.xml │ ├── SysDictDataMapper.xml │ ├── SysDictTypeMapper.xml │ ├── SysLoginLogMapper.xml │ ├── SysMenuMapper.xml │ ├── SysOperLogMapper.xml │ ├── SysResourceMapper.xml │ ├── SysRoleMapper.xml │ ├── SysRoleMenuMapper.xml │ ├── SysRoleResourceMapper.xml │ ├── SysUserMapper.xml │ └── SysUserRoleMapper.xml ├── campus-modular/ │ ├── Dockerfile │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── oddfar/ │ │ └── campus/ │ │ ├── CampusApplication.java │ │ └── business/ │ │ ├── api/ │ │ │ └── PushPlusApi.java │ │ ├── controller/ │ │ │ ├── IItemController.java │ │ │ ├── ILogController.java │ │ │ ├── IShopController.java │ │ │ ├── IUserController.java │ │ │ └── TestController.java │ │ ├── domain/ │ │ │ ├── IMTCacheConstants.java │ │ │ ├── IMTItemInfo.java │ │ │ ├── IUserRequest.java │ │ │ └── MapPoint.java │ │ ├── entity/ │ │ │ ├── IItem.java │ │ │ ├── ILog.java │ │ │ ├── IShop.java │ │ │ └── IUser.java │ │ ├── mapper/ │ │ │ ├── IItemMapper.java │ │ │ ├── ILogMapper.java │ │ │ ├── IShopMapper.java │ │ │ └── IUserMapper.java │ │ ├── service/ │ │ │ ├── IMTLogFactory.java │ │ │ ├── IMTLogService.java │ │ │ ├── IMTService.java │ │ │ ├── IShopService.java │ │ │ ├── IUserService.java │ │ │ └── impl/ │ │ │ ├── IMTLogServiceImpl.java │ │ │ ├── IMTServiceImpl.java │ │ │ ├── IShopServiceImpl.java │ │ │ └── IUserServiceImpl.java │ │ └── task/ │ │ └── CampusIMTTask.java │ └── resources/ │ ├── application-dev.yml │ ├── application-prod.yml │ ├── application.yml │ ├── i18n/ │ │ └── messages.properties │ ├── logback.xml │ ├── mapper/ │ │ ├── IItemMapper.xml │ │ ├── ILogMapper.xml │ │ ├── IShopMapper.xml │ │ └── IUserMapper.xml │ └── spy.properties ├── doc/ │ ├── docker/ │ │ ├── docker-compose.yml │ │ ├── nginx/ │ │ │ └── conf/ │ │ │ └── nginx.conf │ │ ├── redis/ │ │ │ ├── conf/ │ │ │ │ └── redis.conf │ │ │ └── data/ │ │ │ └── README.md │ │ └── server/ │ │ └── conf/ │ │ └── README.md │ └── sql/ │ └── campus_imaotai-1.0.5.sql ├── pom.xml └── vue_campus_admin/ ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── Dockerfile ├── README.md ├── babel.config.js ├── bin/ │ ├── build.bat │ ├── package.bat │ └── run-web.bat ├── package.json ├── public/ │ ├── html/ │ │ └── ie.html │ ├── index.html │ └── robots.txt ├── src/ │ ├── App.vue │ ├── api/ │ │ ├── imt/ │ │ │ ├── item.js │ │ │ ├── log.js │ │ │ ├── shop.js │ │ │ └── user.js │ │ ├── login.js │ │ ├── menu.js │ │ ├── monitor/ │ │ │ ├── cache.js │ │ │ ├── job.js │ │ │ ├── jobLog.js │ │ │ ├── logininfor.js │ │ │ ├── online.js │ │ │ ├── operlog.js │ │ │ └── server.js │ │ ├── system/ │ │ │ ├── config.js │ │ │ ├── dept.js │ │ │ ├── dict/ │ │ │ │ ├── data.js │ │ │ │ └── type.js │ │ │ ├── index.js │ │ │ ├── menu.js │ │ │ ├── notice.js │ │ │ ├── post.js │ │ │ ├── resource.js │ │ │ ├── role.js │ │ │ └── user.js │ │ └── tool/ │ │ └── gen.js │ ├── assets/ │ │ ├── icons/ │ │ │ ├── index.js │ │ │ └── svgo.yml │ │ └── styles/ │ │ ├── btn.scss │ │ ├── element-ui.scss │ │ ├── element-variables.scss │ │ ├── index.scss │ │ ├── mixin.scss │ │ ├── ruoyi.scss │ │ ├── sidebar.scss │ │ ├── transition.scss │ │ └── variables.scss │ ├── components/ │ │ ├── Breadcrumb/ │ │ │ └── index.vue │ │ ├── Crontab/ │ │ │ ├── day.vue │ │ │ ├── hour.vue │ │ │ ├── index.vue │ │ │ ├── min.vue │ │ │ ├── month.vue │ │ │ ├── result.vue │ │ │ ├── second.vue │ │ │ ├── week.vue │ │ │ └── year.vue │ │ ├── DictData/ │ │ │ └── index.js │ │ ├── DictTag/ │ │ │ └── index.vue │ │ ├── Editor/ │ │ │ └── index.vue │ │ ├── FileUpload/ │ │ │ └── index.vue │ │ ├── Hamburger/ │ │ │ └── index.vue │ │ ├── HeaderSearch/ │ │ │ └── index.vue │ │ ├── IconSelect/ │ │ │ ├── index.vue │ │ │ └── requireIcons.js │ │ ├── ImagePreview/ │ │ │ └── index.vue │ │ ├── ImageUpload/ │ │ │ └── index.vue │ │ ├── Pagination/ │ │ │ └── index.vue │ │ ├── PanThumb/ │ │ │ └── index.vue │ │ ├── ParentView/ │ │ │ └── index.vue │ │ ├── RightPanel/ │ │ │ └── index.vue │ │ ├── RightToolbar/ │ │ │ └── index.vue │ │ ├── RuoYi/ │ │ │ ├── Doc/ │ │ │ │ └── index.vue │ │ │ └── Git/ │ │ │ └── index.vue │ │ ├── Screenfull/ │ │ │ └── index.vue │ │ ├── SizeSelect/ │ │ │ └── index.vue │ │ ├── SvgIcon/ │ │ │ └── index.vue │ │ ├── ThemePicker/ │ │ │ └── index.vue │ │ ├── TopNav/ │ │ │ └── index.vue │ │ └── iFrame/ │ │ └── index.vue │ ├── directive/ │ │ ├── dialog/ │ │ │ ├── drag.js │ │ │ ├── dragHeight.js │ │ │ └── dragWidth.js │ │ ├── index.js │ │ ├── module/ │ │ │ └── clipboard.js │ │ └── permission/ │ │ ├── hasPermi.js │ │ └── hasRole.js │ ├── layout/ │ │ ├── components/ │ │ │ ├── AppMain.vue │ │ │ ├── IframeToggle/ │ │ │ │ └── index.vue │ │ │ ├── InnerLink/ │ │ │ │ └── index.vue │ │ │ ├── Navbar.vue │ │ │ ├── Settings/ │ │ │ │ └── index.vue │ │ │ ├── Sidebar/ │ │ │ │ ├── FixiOSBug.js │ │ │ │ ├── Item.vue │ │ │ │ ├── Link.vue │ │ │ │ ├── Logo.vue │ │ │ │ ├── SidebarItem.vue │ │ │ │ └── index.vue │ │ │ ├── TagsView/ │ │ │ │ ├── ScrollPane.vue │ │ │ │ └── index.vue │ │ │ └── index.js │ │ ├── index.vue │ │ └── mixin/ │ │ └── ResizeHandler.js │ ├── main.js │ ├── permission.js │ ├── plugins/ │ │ ├── auth.js │ │ ├── cache.js │ │ ├── download.js │ │ ├── index.js │ │ ├── modal.js │ │ └── tab.js │ ├── router/ │ │ └── index.js │ ├── settings.js │ ├── store/ │ │ ├── getters.js │ │ ├── index.js │ │ └── modules/ │ │ ├── app.js │ │ ├── dict.js │ │ ├── permission.js │ │ ├── settings.js │ │ ├── tagsView.js │ │ └── user.js │ ├── utils/ │ │ ├── auth.js │ │ ├── crypto.js │ │ ├── dict/ │ │ │ ├── Dict.js │ │ │ ├── DictConverter.js │ │ │ ├── DictData.js │ │ │ ├── DictMeta.js │ │ │ ├── DictOptions.js │ │ │ └── index.js │ │ ├── errorCode.js │ │ ├── generator/ │ │ │ ├── config.js │ │ │ ├── css.js │ │ │ ├── drawingDefault.js │ │ │ ├── html.js │ │ │ ├── icon.json │ │ │ ├── js.js │ │ │ └── render.js │ │ ├── index.js │ │ ├── jsencrypt.js │ │ ├── permission.js │ │ ├── request.js │ │ ├── ruoyi.js │ │ ├── scroll-to.js │ │ └── validate.js │ └── views/ │ ├── components/ │ │ └── icons/ │ │ ├── element-icons.js │ │ ├── index.vue │ │ └── svg-icons.js │ ├── dashboard/ │ │ ├── BarChart.vue │ │ ├── LineChart.vue │ │ ├── PanelGroup.vue │ │ ├── PieChart.vue │ │ ├── RaddarChart.vue │ │ └── mixins/ │ │ └── resize.js │ ├── error/ │ │ ├── 401.vue │ │ └── 404.vue │ ├── imt/ │ │ ├── item/ │ │ │ └── index.vue │ │ ├── log/ │ │ │ └── index.vue │ │ ├── shop/ │ │ │ └── index.vue │ │ └── user/ │ │ └── index.vue │ ├── index.vue │ ├── index_v1.vue │ ├── login.vue │ ├── monitor/ │ │ ├── job/ │ │ │ ├── index.vue │ │ │ └── log.vue │ │ ├── logininfor/ │ │ │ └── index.vue │ │ ├── online/ │ │ │ └── index.vue │ │ └── operlog/ │ │ └── index.vue │ ├── redirect.vue │ ├── register.vue │ ├── system/ │ │ ├── config/ │ │ │ └── index.vue │ │ ├── dict/ │ │ │ ├── data.vue │ │ │ └── index.vue │ │ ├── menu/ │ │ │ └── index.vue │ │ ├── notice/ │ │ │ └── index.vue │ │ ├── role/ │ │ │ ├── authUser.vue │ │ │ ├── index.vue │ │ │ └── selectUser.vue │ │ └── user/ │ │ ├── authRole.vue │ │ ├── index.vue │ │ └── profile/ │ │ ├── index.vue │ │ ├── resetPwd.vue │ │ ├── userAvatar.vue │ │ └── userInfo.vue │ └── tool/ │ ├── gen/ │ │ ├── basicInfoForm.vue │ │ ├── editTable.vue │ │ ├── genInfoForm.vue │ │ ├── importTable.vue │ │ └── index.vue │ └── swagger/ │ └── index.vue └── vue.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yaml ================================================ name: 报告bug description: 创建一个报告,请详细描述,来帮助我们改进 title: '[bug]: ' labels: [ "bug" ] body: # User's README and agreement - type: markdown attributes: value: | ## 感谢您愿意填写错误回报! ## 以下是一些注意事项,请务必阅读让我们能够更容易处理 ### ❗ | 查看[文档](https://oddfar.github.io/campus-doc/campus-imaotai)里是否有解决方案 ### ❗ | 确定没有相同问题的 ISSUE 已被提出. ### ❗ | 如果是部署问题,在[讨论区](https://github.com/oddfar/campus-imaotai/discussions/categories/show-and-tell)里搜索是否存在相似的部署方案 ## 如果您不知道如何有效、精准地表述,我们建议您先阅读《提问的智慧》 链接: [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md) --- - type: checkboxes id: terms attributes: label: 请确保您已阅读以上注意事项,并勾选下方的确认框。 options: - label: "我已确认我已升级到最新版本(最新代码)测试过,问题依旧存在。" required: true - label: "我已经在 [Issue Tracker](https://github.com/oddfar/campus-imaotai/issues) 中找过我要提出的问题,没有找到相同问题的ISSUE。" required: true - label: "我理解并认可上述内容,并理解项目维护者精力有限,不遵循规则的 issue 可能会被无视或直接关闭" required: true # User's data - type: markdown attributes: value: | ## 环境信息 请根据实际使用环境修改以下信息。 # Env | Version - type: input id: env-campus-version attributes: label: campus框架版本 - type: input id: env-campus-imaotai-version attributes: label: 本项目 campus-imaotai 版本 validations: required: true # Env | VM Version - type: dropdown id: env-vm-ver attributes: label: 运行环境 description: 选择运行 campus-imaotai 的系统版本 options: - Windows (64) - Windows (32/x84) - MacOS - Linux - Ubuntu - CentOS - ArchLinux - UNIX (Android) - 其它(请在下方说明) validations: required: true # Env | VM Arch - type: dropdown id: env-vm-arch attributes: label: 运行架构 description: (可选) 选择运行 campus-imaotai 的系统架构 options: - AMD64 - x86 - ARM [32] (别名:AArch32 / ARMv7) - ARM [64] (别名:AArch64 / ARMv8) - 其它 # Input | Reproduce - type: textarea id: problem-description attributes: label: 问题描述 description: | 1. 使用的什么功能,遇到什么问题 2. 返回的实际结果是什么 validations: required: true # Input | Expected result - type: textarea id: expected attributes: label: 期望的结果是什么? validations: required: true # Optional | Reproduce code - type: textarea id: reproduce-code attributes: label: 简单的复现代码/链接(可选) render: Java # Optional | Logging - type: textarea id: logging attributes: label: 日志记录(可选) description: | 截图或者日志记录都可以,比如 Docker 容器的日志。 Docker中查看容器的日志,可以使用 `docker logs container_id` 命令。 `container_id` 为目标容器的ID或名称 # Optional | Extra description - type: textarea id: extra-desc attributes: label: 补充说明(可选) ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: ⭐ 功能请求 —— Feature Request about: 使用简练详细的语言描述希望加入的新功能 title: '[Feature] 请填写标题' labels: enhancement assignees: '' --- ## 例行检查 + [ ] 我已确认目前没有类似 issue + [ ] 我已确认我已升级到最新版本 + [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈 + [ ] 我理解并认可上述内容,并理解项目维护者精力有限,不遵循规则的 issue 可能会被无视或直接关闭 ## 功能请求 ## 解决方案和应用场景 ================================================ FILE: .github/workflows/api.yml ================================================ name: api on: # 手动构建 workflow_dispatch: push: branches: [ "master","dev" ] tags: [ 'v*','V*' ] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} IMAGE_SUBNAME: api WORKING_DIR: campus-modular jobs: java: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write steps: - name: Checkout 🛎️ uses: actions/checkout@v3 - name: Set up JDK 8 uses: actions/setup-java@v3 with: java-version: '8' distribution: 'temurin' cache: maven - name: Build with Maven working-directory: ./ run: mvn -B package -P prod --file pom.xml - name: Login to GHCR if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ env.IMAGE_SUBNAME }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Build and push uses: docker/build-push-action@v5 with: context: ${{ env.WORKING_DIR }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le ================================================ FILE: .github/workflows/release.yml ================================================ name: release on: # 手动构建 workflow_dispatch: inputs: version_name: description: '版本号' required: false type: string env: API_WORKING_DIR: campus-modular WEB_WORKING_DIR: vue_campus_admin jobs: release: runs-on: ubuntu-latest defaults: run: working-directory: ./ permissions: write-all strategy: matrix: node-version: [16.x] steps: - name: Checkout 🛎️ uses: actions/checkout@v3 #--------------------构建jar-------------------- - name: Set up JDK 8 uses: actions/setup-java@v3 with: java-version: '8' distribution: 'temurin' cache: maven - name: Build with Maven working-directory: ./ run: mvn -B package -P prod --file pom.xml # 设置 Maven pom 版本环境变量 - name: Set Release version env variable run: | if [ ${{ github.event.inputs.version_name }} != "" ]; then echo "RELEASE_VERSION=${{ github.event.inputs.version_name }}" >> $GITHUB_ENV else # 获取maven项目里的版本号 echo "RELEASE_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV fi - name: Remane jar working-directory: ${{ env.API_WORKING_DIR }} run: | cd target # rename 's/(.*)\.jar/$1_${{ env.RELEASE_VERSION }}.jar/' *.jar for file in *.jar; do mv "$file" "${file%.jar}_${{ env.RELEASE_VERSION }}.jar"; done #--------------------构建前端-------------------- - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - name: Cache ☕ id: cache uses: actions/cache@v3 with: path: vue_campus_admin/node_modules key: ${{runner.os}}-npm-caches-${{ hashfiles('package-lock.json') }} - name: Install 🔧 working-directory: ${{ env.WEB_WORKING_DIR }} if: steps.cache.outputs.cache-hit != 'true' run: npm install - name: Build 🔧 working-directory: ${{ env.WEB_WORKING_DIR }} run: | npm run build:prod - name: Tar dist file working-directory: ${{ env.WEB_WORKING_DIR }} run: | tar -czvf dist_${{ env.RELEASE_VERSION }}.tar.gz dist # 上传文件并发布 Release - name: Create GitHub release uses: marvinpinto/action-automatic-releases@latest with: repo_token: "${{ secrets.GITHUB_TOKEN }}" automatic_release_tag: "v${{ env.RELEASE_VERSION }}" prerelease: false title: "v${{ env.RELEASE_VERSION }}" files: | ${{ env.API_WORKING_DIR }}/target/*.jar ${{ env.WEB_WORKING_DIR }}/dist_*.tar.gz ================================================ FILE: .github/workflows/web.yml ================================================ name: web on: # 手动构建 workflow_dispatch: push: branches: [ "master","dev" ] tags: [ 'v*','V*' ] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} IMAGE_SUBNAME: web WORKING_DIR: vue_campus_admin jobs: node: runs-on: ubuntu-latest defaults: run: working-directory: ${{ env.WORKING_DIR }} permissions: contents: read packages: write id-token: write strategy: matrix: node-version: [16.x] steps: - name: Checkout 🛎️ uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} - name: Cache ☕ id: cache uses: actions/cache@v3 with: path: vue_campus_admin/node_modules key: ${{runner.os}}-npm-caches-${{ hashfiles('package-lock.json') }} - name: Install 🔧 if: steps.cache.outputs.cache-hit != 'true' run: npm install - name: Build 🔧 run: | npm run build:prod - name: Login to GHCR if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-${{ env.IMAGE_SUBNAME }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: CP Docker conf run: | cp -r ../doc doc - name: Build and push uses: docker/build-push-action@v5 with: context: ${{ env.WORKING_DIR }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} platforms: linux/386,linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le,linux/s390x ================================================ FILE: .gitignore ================================================ HELP.md *.log # Build Tools target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ !**/src/main/**/build/ !**/src/test/**/build/ ### VS Code ### .vscode/ ~/ ### mac ### .DS_Store .flattened-pom.xml ================================================ FILE: .run/campus-server.run.xml ================================================ ================================================ FILE: LICENSE ================================================ 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: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) 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 (d) 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 APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================

logo

i茅台app自动预约,每日自动预约,支持docker一键部署

Campus-imaotai

[笔记仓库](https://github.com/oddfar/notes) | [我的博客](https://oddfar.com) ## 项目介绍 i茅台app,每日自动预约茅台 - [x] 平台注册账号 - [x] 添加多个用户 - [x] 自动预约 - [x] 类型选择(本市出货量最大的门店,或位置附近门店) - [x] 自动旅行 - [x] 首次旅行分享 - [x] 获取申购耐力值 - [x] 自定义时间/随机时间预约或旅行 - [x] 申购结果消息推送 此项目使用 **Campus** 进行编写: ## 文档 - 文档:https://oddfar.github.io/campus-doc/campus-imaotai - 视频:https://www.bilibili.com/video/BV1dj411H7oT ## 演示图 | i茅台预约 | | | ------------------------------------------------------------ | ------------------------------------------------------------ | | ![image-20230707144241399](https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/image-20230707144241399.png) | ![image-20230707144404638](https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/image-20230707144404638.png) | | | | | ![image-20230707144703842](https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/image-20230707144703842.png) | ![image-20230707145525709](https://gcore.jsdelivr.net/gh/oddfar/campus-imaotai/.github/image-20230707145525709.png) | ## 贡献代码 若您有好的想法,发现一些 **BUG** 并修复了,欢迎提交 **Pull Request** 参与开源贡献 发起 pull request 请求,提交到 master 分支,等待作者合并 **感谢为这个项目贡献代码的朋友** ## star 趋势图 ![Stargazers over time](https://starchart.cc/oddfar/campus-imaotai.svg) ## 友情链接 - 本项目其他版 C#: - 葫芦娃项目 yize8888-maotai:https://github.com/yize8888/maotai ## 声明 - 本项目涉及的数据由使用的个人或组织自行填写,本项目不对数据内容负责,包括但不限于数据的真实性、准确性、合法性。使用本项目所造成的一切后果,与本项目的所有贡献者无关,由使用的个人或组织完全承担。 - 本项目中涉及的第三方硬件、软件等,与本项目没有任何直接或间接的关系。本项目仅对部署和使用过程进行客观描述,不代表支持使用任何第三方硬件、软件。使用任何第三方硬件、软件,所造成的一切后果由使用的个人或组织承担,与本项目无关。 - 本项目中所有内容只供学习和研究使用,不得将本项目中任何内容用于违反国家/地区/组织等的法律法规或相关规定的其他用途。 - 所有基于本项目源代码,进行的任何修改,为其他个人或组织的自发行为,与本项目没有任何直接或间接的关系,所造成的一切后果亦与本项目无关。 - 所有直接或间接使用本项目的个人和组织,应24小时内完成学习和研究,并及时删除本项目中的所有内容。如对本项目的功能有需求,应自行开发相关功能。 - 本项目保留随时对免责声明进行补充或更改的权利,直接或间接使用本项目内容的个人或组织,视为接受本项目的特别声明。 ## 鸣谢 > [IntelliJ IDEA](https://zh.wikipedia.org/zh-hans/IntelliJ_IDEA) 是一个在各个方面都最大程度地提高开发人员的生产力的 IDE,适用于 JVM 平台语言。 特别感谢 [JetBrains](https://www.jetbrains.com/?from=campus) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=campus) 等 IDE 的授权 [](https://www.jetbrains.com/?from=campus) [](https://api.gitsponsors.com/api/badge/link?p=B5hGpnclTmBJ3UZkEvdPGMFIr6mP8gfAX5zs5E7fvFS73izvYiiomLvUJSqDJ1PcIBTj9m7RAfJ+YSorEaTfDOXnqL5OsV3x5taooWf6/7j78cFmcDrgKyQYgKj41O17r5xkL3Nfi2mfpMBe5pGLpg==) ================================================ FILE: campus-admin/pom.xml ================================================ campus com.oddfar.campus ${revision} 4.0.0 campus-admin 8 8 com.oddfar.campus campus-framework io.springfox springfox-boot-starter io.swagger swagger-models 1.6.2 ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/config/CaptchaConfig.java ================================================ package com.oddfar.campus.admin.config; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; import static com.google.code.kaptcha.Constants.*; /** * 验证码配置 * * @author ruoyi */ @Configuration public class CaptchaConfig { @Bean(name = "captchaProducer") public DefaultKaptcha getKaptchaBean() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } @Bean(name = "captchaProducerMath") public DefaultKaptcha getKaptchaBeanMath() { DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); Properties properties = new Properties(); // 是否有边框 默认为true 我们可以自己设置yes,no properties.setProperty(KAPTCHA_BORDER, "yes"); // 边框颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); // 验证码文本字符颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); // 验证码图片宽度 默认为200 properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); // 验证码图片高度 默认为50 properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); // 验证码文本字符大小 默认为40 properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); // KAPTCHA_SESSION_KEY properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); // 验证码文本生成器 properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.oddfar.campus.framework.config.KaptchaTextCreator"); // 验证码文本字符间距 默认为2 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); // 验证码文本字符长度 默认为5 properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); // 验证码噪点颜色 默认为Color.BLACK properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); // 干扰实现类 properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); Config config = new Config(properties); defaultKaptcha.setConfig(config); return defaultKaptcha; } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/config/SwaggerConfig.java ================================================ package com.oddfar.campus.admin.config; import io.swagger.models.auth.In; import io.swagger.v3.oas.annotations.Operation; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.*; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.service.contexts.SecurityContext; import springfox.documentation.spring.web.plugins.Docket; import java.util.ArrayList; import java.util.List; /** * Swagger2的接口配置 * * @author ruoyi */ @Configuration public class SwaggerConfig { /** * 是否开启swagger */ @Value("${swagger.enabled}") private boolean enabled; /** * 版本情况 */ @Value("${campus.version}") private String version; /** * 创建API */ @Bean public Docket createRestApi() { return new Docket(DocumentationType.OAS_30) // 是否启用Swagger .enable(enabled) // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) .apiInfo(apiInfo()) // 设置哪些接口暴露给Swagger展示 .select() // 扫描所有有注解的api,用这种方式更灵活 .apis(RequestHandlerSelectors.withMethodAnnotation(Operation.class)) // 扫描指定包中的swagger注解 // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger")) // 扫描所有 .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()) .build() /* 设置安全模式,swagger可以设置访问token */ .securitySchemes(securitySchemes()) .securityContexts(securityContexts()); } /** * 安全模式,这里指定token通过Authorization头请求头传递 */ private List securitySchemes() { List apiKeyList = new ArrayList(); apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue())); return apiKeyList; } /** * 安全上下文 */ private List securityContexts() { List securityContexts = new ArrayList<>(); securityContexts.add( SecurityContext.builder() .securityReferences(defaultAuth()) .operationSelector(o -> o.requestMappingPattern().matches("/.*")) .build()); return securityContexts; } /** * 默认的安全上引用 */ private List defaultAuth() { AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; List securityReferences = new ArrayList<>(); securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); return securityReferences; } /** * 添加摘要信息 */ private ApiInfo apiInfo() { // 用ApiInfoBuilder进行定制 return new ApiInfoBuilder() // 设置标题 .title("标题:Campus_接口文档") // 描述 .description("描述:Campus的接口文档列表") // 作者信息 .contact(new Contact("oddfar", null, null)) // 版本 .version("版本号:" + version) .build(); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/monitor/SysLogininforController.java ================================================ package com.oddfar.campus.admin.controller.monitor; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.annotation.Log; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysLoginLogEntity; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.framework.service.SysLoginLogService; import com.oddfar.campus.framework.web.service.SysPasswordService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; /** * 系统访问记录 * * @author ruoyi */ @RestController @RequestMapping("/monitor/logininfor") @Log(openLog = false) @ApiResource(name = "登录日志管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysLogininforController { @Autowired private SysLoginLogService logininforService; @Autowired private SysPasswordService passwordService; @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") @GetMapping(value = "/list",name = "登录日志-分类列表") public R list(SysLoginLogEntity logininfor) { PageResult page = logininforService.selectLogininforPage(logininfor); return R.ok().put(page); } @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") @DeleteMapping(value = "/{infoIds}",name = "登录日志-删除") public R remove(@PathVariable Long[] infoIds) { return R.ok(logininforService.deleteLogininforByIds(infoIds)); } @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") @DeleteMapping(value = "/clean",name = "登录日志-清空") public R clean() { logininforService.cleanLogininfor(); return R.ok(); } @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')") @GetMapping(value = "/unlock/{userName}",name = "登录日志-解锁") public R unlock(@PathVariable("userName") String userName) { passwordService.clearLoginRecordCache(userName); return R.ok(); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/monitor/SysOperlogController.java ================================================ package com.oddfar.campus.admin.controller.monitor; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.annotation.Log; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysOperLogEntity; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.framework.service.SysOperLogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; /** * 操作日志记录 */ @RestController @RequestMapping("/monitor/operlog") @Log(openLog = false) @ApiResource(name = "操作日志管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysOperlogController { @Autowired private SysOperLogService operLogService; @PreAuthorize("@ss.hasPermi('monitor:operlog:list')") @GetMapping(value = "/list", name = "操作日志-分页") public R list(SysOperLogEntity operLog) { PageResult page = operLogService.selectOperLogPage(operLog); return R.ok().put(page); } @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") @DeleteMapping(value = "/{operIds}", name = "操作日志-删除") public R remove(@PathVariable Long[] operIds) { return R.ok(operLogService.deleteOperLogByIds(operIds)); } @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") @DeleteMapping(value = "/clean", name = "操作日志-清空") public R clean() { operLogService.cleanOperLog(); return R.ok(); } /** * @author jamin * 接下来要改造的是,将日志系统升级为elk 2025-03-21 */ } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/CaptchaController.java ================================================ package com.oddfar.campus.admin.controller.system; import cn.hutool.core.codec.Base64; import com.google.code.kaptcha.Producer; import com.oddfar.campus.common.annotation.Log; import com.oddfar.campus.common.constant.CacheConstants; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.utils.uuid.IdUtils; import com.oddfar.campus.framework.api.sysconfig.ConfigExpander; import com.oddfar.campus.framework.service.SysConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.FastByteArrayOutputStream; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.concurrent.TimeUnit; /** * 验证码操作处理 * * @author ruoyi */ @RestController @Log(openLog = false) public class CaptchaController { @Resource(name = "captchaProducer") private Producer captchaProducer; @Resource(name = "captchaProducerMath") private Producer captchaProducerMath; @Autowired private RedisCache redisCache; @Autowired private SysConfigService configService; /** * 生成验证码 */ @GetMapping(value = "/captchaImage", name = "生产验证码") public R getCode(HttpServletResponse response) throws IOException { R ajax = R.ok(); boolean captchaEnabled = configService.selectCaptchaEnabled(); ajax.put("captchaEnabled", captchaEnabled); if (!captchaEnabled) { return ajax; } // 保存验证码信息 String uuid = IdUtils.simpleUUID(); String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; String capStr = null, code = null; BufferedImage image = null; String captchaType = ConfigExpander.getLoginCaptchaType(); if ("math".equals(captchaType)) { String capText = captchaProducerMath.createText(); capStr = capText.substring(0, capText.lastIndexOf("@")); code = capText.substring(capText.lastIndexOf("@") + 1); image = captchaProducerMath.createImage(capStr); } else if ("char".equals(captchaType)) { capStr = code = captchaProducer.createText(); image = captchaProducer.createImage(capStr); } redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); // 转换流信息写出 FastByteArrayOutputStream os = new FastByteArrayOutputStream(); try { ImageIO.write(image, "jpg", os); } catch (IOException e) { return R.error(e.getMessage()); } ajax.put("uuid", uuid); ajax.put("img", Base64.encode(os.toByteArray())); return ajax; } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysApiResourceController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.framework.service.SysResourceService; import com.oddfar.campus.framework.web.service.SysPermissionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import static com.oddfar.campus.common.utils.SecurityUtils.getUserId; @RestController @RequestMapping("/system/resource") @ApiResource(name = "资源管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysApiResourceController { @Autowired private SysResourceService resourceService; @Autowired private SysPermissionService permissionService; /** * 加载对应角色资源列表树 */ @GetMapping(value = "/roleApiTreeselect/{roleId}", name = "资源管理-加载对应角色资源列表树") public R roleMenuTreeSelect(@PathVariable("roleId") Long roleId) { List resources = resourceService.selectApiResourceList(getUserId()); R ajax = R.ok(); ajax.put("checkedKeys", resourceService.selectResourceListByRoleId(roleId)); ajax.put("resources", resourceService.buildResourceTreeSelect(resources)); return ajax; } /** * 修改对应角色api资源 */ @PutMapping(value = "/roleApi", name = "修改对应角色api资源") public R editRoleResource(Long roleId, Long[] resourceIds) { resourceService.editRoleResource(roleId, resourceIds); //更新redis缓存权限数据 permissionService.resetLoginUserRoleCache(roleId); return R.ok(); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysConfigController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysConfigEntity; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.framework.service.SysConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; /** * 配置管理 */ @RestController @RequestMapping("/system/config") @ApiResource(name = "参数配置管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysConfigController { @Autowired private SysConfigService configService; @GetMapping(value = "page", name = "参数配置管理-分页") @PreAuthorize("@ss.hasPermi('system:config:list')") public R page(SysConfigEntity sysConfigEntity) { PageResult page = configService.page(sysConfigEntity); return R.ok().put(page); } @GetMapping(value = "{id}", name = "参数配置管理-查询id信息") @PreAuthorize("@ss.hasPermi('system:config:query')") public R getInfo(@PathVariable("id") Long id) { SysConfigEntity entity = configService.selectConfigById(id); return R.ok().put(entity); } /** * 根据参数键名查询参数值 */ @GetMapping(value = "/configKey/{configKey:.+}") public R getConfigKey(@PathVariable String configKey) { return R.ok(configService.selectConfigByKey(configKey)); } @PostMapping(name = "参数配置管理-新增") @PreAuthorize("@ss.hasPermi('system:config:add')") public R add(@Validated @RequestBody SysConfigEntity config) { if (!configService.checkConfigKeyUnique(config)) { return R.error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); } return R.ok(configService.insertConfig(config)); } @PutMapping(name = "参数配置管理-修改") @PreAuthorize("@ss.hasPermi('system:config:edit')") public R edit(@Validated @RequestBody SysConfigEntity config) { if (!configService.checkConfigKeyUnique(config)) { return R.error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); } return R.ok(configService.updateConfig(config)); } @DeleteMapping(value = "/{configIds}", name = "参数配置管理-删除") @PreAuthorize("@ss.hasPermi('system:config:remove')") public R remove(@PathVariable Long[] configIds) { configService.deleteConfigByIds(configIds); return R.ok(); } /** * 刷新参数缓存 */ @PreAuthorize("@ss.hasPermi('system:config:remove')") @DeleteMapping(value = "/refreshCache", name = "参数配置管理-刷新缓存") public R refreshCache() { configService.resetConfigCache(); return R.ok(); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysDictDataController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.Anonymous; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysDictDataEntity; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.framework.service.SysDictDataService; import com.oddfar.campus.framework.service.SysDictTypeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; @RestController @RequestMapping("/system/dict/data") @ApiResource(name = "字典数据管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysDictDataController { @Autowired private SysDictDataService dictDataService; @Autowired private SysDictTypeService dictTypeService; @PreAuthorize("@ss.hasPermi('system:dict:list')") @GetMapping(value = "/list", name = "字典数据管理-分页") public R page(SysDictDataEntity dictData) { PageResult page = dictDataService.page(dictData); return R.ok().put(page); } /** * 根据字典类型查询字典数据信息 */ @GetMapping(value = "/type/{dictType}", name = "字典数据管理-根据字典类型查询字典数据信息") @Anonymous public R dictType(@PathVariable String dictType) { List data = dictTypeService.selectDictDataByType(dictType); if (StringUtils.isEmpty(data)) { data = new ArrayList(); } return R.ok().put(data); } /** * 查询字典数据详细 */ @PreAuthorize("@ss.hasPermi('system:dict:query')") @GetMapping(value = "/{dictCode}", name = "字典数据管理-查询") public R getInfo(@PathVariable Long dictCode) { return R.ok(dictDataService.selectDictDataById(dictCode)); } /** * 新增字典类型 */ @PreAuthorize("@ss.hasPermi('system:dict:add')") @PostMapping(name = "字典数据管理-新增") public R add(@Validated @RequestBody SysDictDataEntity dict) { return R.ok(dictDataService.insertDictData(dict)); } /** * 修改保存字典类型 */ @PreAuthorize("@ss.hasPermi('system:dict:edit')") @PutMapping(name = "字典数据管理-修改") public R edit(@Validated @RequestBody SysDictDataEntity dict) { return R.ok(dictDataService.updateDictData(dict)); } /** * 删除字典类型 */ @PreAuthorize("@ss.hasPermi('system:dict:remove')") @DeleteMapping(value = "/{dictCodes}", name = "字典数据管理-删除") public R remove(@PathVariable Long[] dictCodes) { dictDataService.deleteDictDataByIds(dictCodes); return R.ok(); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysDictTypeController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysDictTypeEntity; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.framework.service.SysDictTypeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/system/dict/type") @ApiResource(name = "字典类型管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysDictTypeController { @Autowired private SysDictTypeService dictTypeService; @PreAuthorize("@ss.hasPermi('system:dict:list')") @GetMapping(value = "/list", name = "字典类型管理-分页") public R list(SysDictTypeEntity sysDictTypeEntity) { PageResult page = dictTypeService.page(sysDictTypeEntity); return R.ok().put(page); } /** * 查询字典类型详细 */ @PreAuthorize("@ss.hasPermi('system:dict:query')") @GetMapping(value = "/{dictId}", name = "字典类型管理-查询") public R getInfo(@PathVariable Long dictId) { return R.ok(dictTypeService.selectDictTypeById(dictId)); } /** * 新增字典类型 */ @PreAuthorize("@ss.hasPermi('system:dict:add')") @PostMapping(name = "字典类型管理-新增") public R add(@Validated @RequestBody SysDictTypeEntity dict) { if (!dictTypeService.checkDictTypeUnique(dict)) { return R.error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); } return R.ok(dictTypeService.insertDictType(dict)); } /** * 修改字典类型 */ @PreAuthorize("@ss.hasPermi('system:dict:edit')") @PutMapping(name = "字典类型管理-修改") public R edit(@Validated @RequestBody SysDictTypeEntity dict) { if (!dictTypeService.checkDictTypeUnique(dict)) { return R.error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); } return R.ok(dictTypeService.updateDictType(dict)); } /** * 删除字典类型 */ @PreAuthorize("@ss.hasPermi('system:dict:remove')") @DeleteMapping(value = "/{dictIds}", name = "字典类型管理-删除") public R remove(@PathVariable Long[] dictIds) { dictTypeService.deleteDictTypeByIds(dictIds); return R.ok(); } /** * 刷新字典缓存 */ @PreAuthorize("@ss.hasPermi('system:dict:remove')") @DeleteMapping(value = "/refreshCache", name = "字典类型管理-刷新") public R refreshCache() { dictTypeService.resetDictCache(); return R.ok(); } /** * 获取字典选择框列表 */ @GetMapping(value = "/optionselect", name = "字典类型管理-获取字典选择框列表") public R optionselect() { List dictTypes = dictTypeService.selectDictTypeAll(); return R.ok(dictTypes); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysIndexController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.config.CampusConfig; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; /** * 首页 * * @author oddfar */ @RestController public class SysIndexController { /** * 系统基础配置 */ @Autowired private CampusConfig campusConfig; /** * 访问首页,提示语 */ @RequestMapping("/") public String index() { return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", campusConfig.getName(), campusConfig.getVersion()); } @Value("${campus.version}") private String version; @Value("${campus.frameworkVersion}") private String frameworkVersion; /** * 版本情况 */ @RequestMapping("/version") public R version() { HashMap map = new HashMap<>(); map.put("version", version); map.put("frameworkVersion", frameworkVersion); return R.ok(map); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysLoginController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysMenuEntity; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.LoginBody; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.framework.service.SysMenuService; import com.oddfar.campus.framework.web.service.SysLoginService; import com.oddfar.campus.framework.web.service.SysPermissionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Set; @RestController @RequestMapping @ApiResource(name = "登录路由", resBizType = ResBizTypeEnum.SYSTEM) public class SysLoginController { @Autowired private SysMenuService menuService; @Autowired private SysLoginService loginService; @Autowired private SysPermissionService permissionService; /** * 登录方法 * * @param loginBody 登录信息 * @return 结果 */ @PostMapping(value = "/login", name = "登录方法") public R login(@RequestBody LoginBody loginBody) { R r = R.ok(); // 生成令牌 String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid()); r.put(Constants.TOKEN, token); return r; } /** * 获取用户信息 * * @return 用户信息 */ @GetMapping(value = "getInfo", name = "获取用户信息") public R getInfo() { SysUserEntity user = SecurityUtils.getLoginUser().getUser(); // 角色集合 Set roles = permissionService.getRolePermission(user); // 权限集合 Set permissions = permissionService.getMenuPermission(user); R ajax = R.ok(); ajax.put("user", user); ajax.put("roles", roles); ajax.put("permissions", permissions); return ajax; } /** * 获取路由信息 * * @return 路由信息 */ @GetMapping(value = "getRouters", name = "获取路由信息") public R getRouters() { Long userId = SecurityUtils.getUserId(); List menus = menuService.selectMenuTreeByUserId(userId); return R.ok(menuService.buildMenus(menus)); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysMenuController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.constant.UserConstants; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysMenuEntity; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.service.SysMenuService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; import static com.oddfar.campus.common.utils.SecurityUtils.getUserId; @RestController @RequestMapping("/system/menu") @ApiResource(name = "菜单管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysMenuController { @Autowired private SysMenuService menuService; /** * 获取菜单列表 */ @PreAuthorize("@ss.hasPermi('system:menu:list')") @GetMapping(value = "/list", name = "菜单管理-分页") public R list(SysMenuEntity menu) { List menus = menuService.selectMenuList(menu, getUserId()); return R.ok(menus); } /** * 根据菜单编号获取详细信息 */ @PreAuthorize("@ss.hasPermi('system:menu:query')") @GetMapping(value = "/{menuId}", name = "菜单管理-查询") public R getInfo(@PathVariable Long menuId) { return R.ok(menuService.selectMenuById(menuId)); } /** * 获取菜单下拉树列表 */ @GetMapping(value = "/treeselect", name = "菜单管理-获取菜单下拉树列表") public R treeSelect(SysMenuEntity menu) { List menus = menuService.selectMenuList(menu, getUserId()); return R.ok(menuService.buildMenuTreeSelect(menus)); } /** * 加载对应角色菜单列表树 */ @GetMapping(value = "/roleMenuTreeselect/{roleId}", name = "菜单管理-加载对应角色菜单列表树") public R roleMenuTreeselect(@PathVariable("roleId") Long roleId) { List menus = menuService.selectMenuList(getUserId()); R ajax = R.ok(); ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); ajax.put("menus", menuService.buildMenuTreeSelect(menus)); return ajax; } /** * 新增菜单 */ @PreAuthorize("@ss.hasPermi('system:menu:add')") @PostMapping(name = "菜单管理-新增") public R add(@Validated @RequestBody SysMenuEntity menu) { if (!menuService.checkMenuNameUnique(menu)) { return R.error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { return R.error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); } return R.ok(menuService.insertMenu(menu)); } /** * 修改菜单 */ @PreAuthorize("@ss.hasPermi('system:menu:edit')") @PutMapping(name = "菜单管理-修改") public R edit(@Validated @RequestBody SysMenuEntity menu) { if (!menuService.checkMenuNameUnique(menu)) { return R.error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { return R.error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); } else if (menu.getMenuId().equals(menu.getParentId())) { return R.error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); } return R.ok(menuService.updateMenu(menu)); } /** * 删除菜单 */ @PreAuthorize("@ss.hasPermi('system:menu:remove')") @DeleteMapping(value = "/{menuId}", name = "菜单管理-删除") public R remove(@PathVariable("menuId") Long menuId) { if (menuService.hasChildByMenuId(menuId)) { return R.error("存在子菜单,不允许删除"); } if (menuService.checkMenuExistRole(menuId)) { return R.error("菜单已分配,不允许删除"); } return R.ok(menuService.deleteMenuById(menuId)); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysProfileController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.api.file.FileUploadUtils; import com.oddfar.campus.framework.api.file.MimeTypeUtils; import com.oddfar.campus.framework.api.sysconfig.ConfigExpander; import com.oddfar.campus.framework.service.SysUserService; import com.oddfar.campus.framework.web.service.TokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import static com.oddfar.campus.common.utils.SecurityUtils.getLoginUser; /** * 个人信息 业务处理 */ @RestController @RequestMapping("/system/user/profile") @ApiResource(name = "个人信息管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysProfileController { @Autowired private SysUserService userService; @Autowired private TokenService tokenService; /** * 个人信息 */ @GetMapping(name = "个人信息管理-查询") public R profile() { LoginUser loginUser = getLoginUser(); SysUserEntity user = loginUser.getUser(); R ajax = R.ok(user); ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); return ajax; } /** * 修改用户 */ @PutMapping(name = "个人信息管理-修改") public R updateProfile(@RequestBody SysUserEntity user) { LoginUser loginUser = getLoginUser(); SysUserEntity sysUser = loginUser.getUser(); user.setUserName(sysUser.getUserName()); if (StringUtils.isNotEmpty(user.getPhonenumber()) && !(userService.checkPhoneUnique(user))) { return R.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); } if (StringUtils.isNotEmpty(user.getEmail()) && !(userService.checkEmailUnique(user))) { return R.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); } user.setUserId(sysUser.getUserId()); user.setPassword(null); user.setAvatar(null); if (userService.updateUserProfile(user) > 0) { // 更新缓存用户信息 sysUser.setNickName(user.getNickName()); sysUser.setPhonenumber(user.getPhonenumber()); sysUser.setEmail(user.getEmail()); sysUser.setSex(user.getSex()); tokenService.setLoginUser(loginUser); return R.ok(); } return R.error("修改个人信息异常,请联系管理员"); } /** * 重置密码 */ @PutMapping(value = "/updatePwd", name = "个人信息管理-重置密码") public R updatePwd(String oldPassword, String newPassword) { SysUserEntity user = userService.selectUserById(SecurityUtils.getUserId()); // LoginUser loginUser = getLoginUser(); String userName = user.getUserName(); String password = user.getPassword(); if (!SecurityUtils.matchesPassword(oldPassword, password)) { return R.error("修改密码失败,旧密码错误"); } if (SecurityUtils.matchesPassword(newPassword, password)) { return R.error("新密码不能与旧密码相同"); } if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) { // // 更新缓存用户密码 // loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword)); // tokenService.setLoginUser(loginUser); return R.ok(); } return R.error("修改密码异常,请联系管理员"); } /** * 头像上传 */ @PostMapping(value = "/avatar", name = "个人信息管理-头像上次") public R avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception { if (!file.isEmpty()) { LoginUser loginUser = getLoginUser(); String avatar = FileUploadUtils.upload(ConfigExpander.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) { R ajax = R.ok(); ajax.put("imgUrl", avatar); // 更新缓存用户头像 loginUser.getUser().setAvatar(avatar); tokenService.setLoginUser(loginUser); return ajax; } } return R.error("上传图片异常,请联系管理员"); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysRegisterController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.Anonymous; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.RegisterBody; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.service.SysConfigService; import com.oddfar.campus.framework.service.SysUserService; import com.oddfar.campus.framework.web.service.SysRegisterService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; /** * 注册验证 */ @RestController public class SysRegisterController { @Autowired private SysRegisterService registerService; @Autowired private SysConfigService configService; @Autowired private SysUserService userService; @PostMapping("/register") public R register(@RequestBody RegisterBody user) { if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) { return R.error("当前系统没有开启注册功能!"); } String msg = registerService.register(user); return StringUtils.isEmpty(msg) ? R.ok() : R.error(msg); } @Anonymous @GetMapping("/userNameUnique") public R userNameUnique(String userName) { SysUserEntity userEntity = new SysUserEntity(); userEntity.setUserName(userName); return R.ok(userService.checkUserNameUnique(userEntity)); } @Anonymous @GetMapping("/emailUnique") public R emailUnique(String email) { SysUserEntity userEntity = new SysUserEntity(); userEntity.setEmail(email); return R.ok(userService.checkEmailUnique(userEntity)); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysRoleController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.entity.SysUserRoleEntity; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.service.SysRoleService; import com.oddfar.campus.framework.service.SysUserService; import com.oddfar.campus.framework.web.service.SysPermissionService; import com.oddfar.campus.framework.web.service.TokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.Arrays; @RestController @RequestMapping("/system/role") @ApiResource(name = "角色管理" , resBizType = ResBizTypeEnum.SYSTEM) public class SysRoleController { @Autowired private SysRoleService roleService; @Autowired private SysUserService userService; @Autowired private SysPermissionService permissionService; @Autowired private TokenService tokenService; @PreAuthorize("@ss.hasPermi('system:role:list')") @GetMapping("/list") public R list(SysRoleEntity role) { PageResult list = roleService.page(role); return R.ok().put(list); } /** * 根据角色编号获取详细信息 */ @PreAuthorize("@ss.hasPermi('system:role:query')") @GetMapping(value = "/{roleId}") public R getInfo(@PathVariable Long roleId) { return R.ok(roleService.selectRoleById(roleId)); } /** * 新增角色 */ @PreAuthorize("@ss.hasPermi('system:role:add')") @PostMapping public R add(@Validated @RequestBody SysRoleEntity role) { if (!roleService.checkRoleNameUnique(role)) { return R.error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); } else if (!roleService.checkRoleKeyUnique(role)) { return R.error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); } return R.ok(roleService.insertRole(role)); } /** * 修改保存角色 */ @PreAuthorize("@ss.hasPermi('system:role:edit')") @PutMapping public R edit(@Validated @RequestBody SysRoleEntity role) { roleService.checkRoleAllowed(role); if (!roleService.checkRoleNameUnique(role)) { return R.error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); } else if (!roleService.checkRoleKeyUnique(role)) { return R.error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); } if (roleService.updateRole(role) > 0) { // 更新缓存用户权限 LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) { loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); tokenService.setLoginUser(loginUser); } permissionService.resetLoginUserRoleCache(role.getRoleId()); return R.ok(); } return R.error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); } /** * 状态修改 */ @PreAuthorize("@ss.hasPermi('system:role:edit')") @PutMapping("/changeStatus") public R changeStatus(@RequestBody SysRoleEntity role) { roleService.checkRoleAllowed(role); roleService.updateRoleStatus(role); //更新redis缓存权限数据 permissionService.resetLoginUserRoleCache(role.getRoleId()); return R.ok(); } /** * 删除角色 */ @PreAuthorize("@ss.hasPermi('system:role:remove')") @DeleteMapping("/{roleIds}") public R remove(@PathVariable Long[] roleIds) { roleService.deleteRoleByIds(roleIds); //更新redis缓存权限数据 Arrays.stream(roleIds).forEach(id -> permissionService.resetLoginUserRoleCache(id)); return R.ok(); } /** * 查询已分配用户角色列表 */ @PreAuthorize("@ss.hasPermi('system:role:list')") @GetMapping("/authUser/allocatedList") public R allocatedList(SysUserEntity user) { Page page = userService.selectAllocatedList(user); return R.ok().put(page); } /** * 查询未分配用户角色列表 */ @PreAuthorize("@ss.hasPermi('system:role:list')") @GetMapping("/authUser/unallocatedList") public R unallocatedList(SysUserEntity user) { Page page = userService.selectUnallocatedList(user); return R.ok().put(page); } /** * 取消授权用户 */ @PreAuthorize("@ss.hasPermi('system:role:edit')") @PutMapping("/authUser/cancel") public R cancelAuthUser(@RequestBody SysUserRoleEntity userRole) { int i = roleService.deleteAuthUser(userRole); //更新redis缓存权限数据 permissionService.resetLoginUserRoleCache(userRole.getRoleId()); return R.ok(i); } /** * 批量取消授权用户 */ @PreAuthorize("@ss.hasPermi('system:role:edit')") @PutMapping("/authUser/cancelAll") public R cancelAuthUserAll(Long roleId, Long[] userIds) { int i = roleService.deleteAuthUsers(roleId, userIds); //更新redis缓存权限数据 permissionService.resetLoginUserRoleCache(roleId); return R.ok(i); } /** * 批量选择用户授权 */ @PreAuthorize("@ss.hasPermi('system:role:edit')") @PutMapping("/authUser/selectAll") public R selectAuthUserAll(Long roleId, Long[] userIds) { return R.ok(roleService.insertAuthUsers(roleId, userIds)); } } ================================================ FILE: campus-admin/src/main/java/com/oddfar/campus/admin/controller/system/SysUserController.java ================================================ package com.oddfar.campus.admin.controller.system; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.enums.ResBizTypeEnum; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.service.SysRoleService; import com.oddfar.campus.framework.service.SysUserService; import com.oddfar.campus.framework.web.service.SysPermissionService; import org.apache.commons.lang3.ArrayUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; /** * 用户管理 * * @author oddfar */ @RestController @RequestMapping("/system/user") @ApiResource(name = "用户管理", resBizType = ResBizTypeEnum.SYSTEM) public class SysUserController { @Autowired private SysUserService userService; @Autowired private SysRoleService roleService; @Autowired private SysPermissionService permissionService; /** * 分页 */ @GetMapping("list") @PreAuthorize("@ss.hasPermi('system:user:list')") public R page(SysUserEntity sysUserEntity) { PageResult page = userService.page(sysUserEntity); return R.ok().put(page); } /** * 信息 */ @GetMapping({"{userId}", "/"}) @PreAuthorize("@ss.hasPermi('system:user:query')") public R getInfo(@PathVariable(value = "userId", required = false) Long userId) { R res = R.ok(); List roles = roleService.selectRoleAll(); res.put("roles", SysUserEntity.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); if (StringUtils.isNotNull(userId)) { SysUserEntity sysUser = userService.selectUserById(userId); res.put("data", sysUser); res.put("roleIds", sysUser.getRoles().stream().map(SysRoleEntity::getRoleId).filter(Objects::nonNull).collect(Collectors.toList())); } return res; } /** * 新增用户 */ @PostMapping @PreAuthorize("@ss.hasPermi('system:user:add')") public R add(@Validated @RequestBody SysUserEntity sysUserEntity) { userService.insertUser(sysUserEntity); return R.ok(); } /** * 修改 */ @PutMapping @PreAuthorize("@ss.hasPermi('system:user:edit')") public R update(@Validated @RequestBody SysUserEntity user) { userService.checkUserAllowed(user); if (!(userService.checkUserNameUnique(user))) { return R.error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !(userService.checkPhoneUnique(user))) { return R.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); } else if (StringUtils.isNotEmpty(user.getEmail()) && !(userService.checkEmailUnique(user))) { return R.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); } user.setPassword(null); return R.ok(userService.updateUser(user)); } /** * 删除 */ @DeleteMapping("/{userIds}") @PreAuthorize("@ss.hasPermi('system:user:remove')") public R remove(@PathVariable Long[] userIds) { if (ArrayUtils.contains(userIds, SecurityUtils.getUserId())) { return R.error("当前用户不能删除"); } return R.ok(userService.deleteUserByIds(userIds)); } /** * 根据用户编号获取授权角色 */ @PreAuthorize("@ss.hasPermi('system:user:query')") @GetMapping("/authRole/{userId}") public R authRole(@PathVariable("userId") Long userId) { R res = R.ok(); SysUserEntity user = userService.selectUserById(userId); List roles = roleService.selectRolesByUserId(userId); res.put("user", user); res.put("roles", SysUserEntity.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); return res; } /** * 用户授权角色 */ @PreAuthorize("@ss.hasPermi('system:user:edit')") @PutMapping("/authRole") public R insertAuthRole(Long userId, Long[] roleIds) { if (!SysUserEntity.isAdmin(userId)) { userService.insertUserAuth(userId, roleIds); return R.ok(); } else { return R.error("不可操作超级管理员"); } } /** * 重置密码 */ @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") @PutMapping("/resetPwd") public R resetPwd(@RequestBody SysUserEntity user) { userService.checkUserAllowed(user); user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); return R.ok(userService.resetPwd(user)); } /** * 状态修改 */ @PreAuthorize("@ss.hasPermi('system:user:edit')") @PutMapping("/changeStatus") public R changeStatus(@RequestBody SysUserEntity user) { userService.checkUserAllowed(user); userService.updateUserStatus(user); permissionService.resetUserRoleAuthCache(user.getUserId()); return R.ok(); } } ================================================ FILE: campus-common/pom.xml ================================================ campus com.oddfar.campus ${revision} 4.0.0 campus-common 8 8 io.jsonwebtoken jjwt org.apache.commons commons-lang3 commons-fileupload commons-fileupload org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-validation org.apache.poi poi-ooxml org.projectlombok lombok com.baomidou mybatis-plus-boot-starter p6spy p6spy com.baomidou dynamic-datasource-spring-boot-starter com.alibaba.fastjson2 fastjson2 jakarta.validation jakarta.validation-api provided cn.hutool hutool-all org.mapstruct mapstruct eu.bitwalker UserAgentUtils javax.servlet javax.servlet-api ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/annotation/Anonymous.java ================================================ package com.oddfar.campus.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 匿名访问不鉴权注解 * * @author ruoyi */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Anonymous { } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/annotation/ApiResource.java ================================================ package com.oddfar.campus.common.annotation; import com.oddfar.campus.common.enums.ResBizTypeEnum; import java.lang.annotation.*; /** * 资源标识 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiResource { /** * 资源编码唯一标识 * 可不填写此注解属性,默认生成的编码标识为: 控制器类名称 + 分隔符 + 方法名称 */ String code() default ""; /** * 应用编码 * 用于区分不同业务 */ String appCode() default ""; /** * 资源名称(必填项) */ String name() default ""; /** * 资源的类型,系统类还是业务类资源 */ ResBizTypeEnum resBizType() default ResBizTypeEnum.BUSINESS; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/annotation/Log.java ================================================ package com.oddfar.campus.common.annotation; import java.lang.annotation.*; /** * 用来标记在控制器类或方法上,进行判断是否需要对接口进行日志记录 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** * 是否进行日志记录,默认开启 */ boolean openLog() default true; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/annotation/RepeatSubmit.java ================================================ package com.oddfar.campus.common.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义注解防止表单重复提交 * * @author ruoyi */ @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RepeatSubmit { /** * 间隔时间(ms),小于此时间视为重复提交 */ public int interval() default 5000; /** * 提示消息 */ public String message() default "不允许重复提交,请稍候再试"; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/annotation/Sensitive.java ================================================ package com.oddfar.campus.common.annotation; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.oddfar.campus.common.config.SensitiveSerializer; import com.oddfar.campus.common.enums.SensitiveStrategy; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 数据脱敏注解 * * @author zhujie */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JacksonAnnotationsInside @JsonSerialize(using = SensitiveSerializer.class) public @interface Sensitive { SensitiveStrategy strategy(); } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/config/CampusConfig.java ================================================ package com.oddfar.campus.common.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 首页 * * @author oddfar */ @Data @Component @ConfigurationProperties(prefix = "campus") public class CampusConfig { /** * 项目名称 */ private String name; /** * 版本 */ private String version; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/config/FastJson2JsonRedisSerializer.java ================================================ package com.oddfar.campus.common.config; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONReader; import com.alibaba.fastjson2.JSONWriter; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import java.nio.charset.Charset; /** * Redis使用FastJson序列化 * * @author ruoyi */ public class FastJson2JsonRedisSerializer implements RedisSerializer { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class clazz; public FastJson2JsonRedisSerializer(Class clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/config/RedisConfig.java ================================================ package com.oddfar.campus.common.config; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; 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.core.script.DefaultRedisScript; import org.springframework.data.redis.serializer.StringRedisSerializer; /** * redis配置 * * @author ruoyi */ @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean @SuppressWarnings(value = {"unchecked", "rawtypes"}) public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } @Bean public DefaultRedisScript limitScript() { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptText(limitScriptText()); redisScript.setResultType(Long.class); return redisScript; } /** * 限流脚本 */ private String limitScriptText() { return "local key = KEYS[1]\n" + "local count = tonumber(ARGV[1])\n" + "local time = tonumber(ARGV[2])\n" + "local current = redis.call('get', key);\n" + "if current and tonumber(current) > count then\n" + " return tonumber(current);\n" + "end\n" + "current = redis.call('incr', key)\n" + "if tonumber(current) == 1 then\n" + " redis.call('expire', key, time)\n" + "end\n" + "return tonumber(current);"; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/config/SensitiveSerializer.java ================================================ package com.oddfar.campus.common.config; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.oddfar.campus.common.annotation.Sensitive; import com.oddfar.campus.common.enums.SensitiveStrategy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import java.io.IOException; import java.util.Objects; /** * 数据脱敏json序列化工具 * * @author Yjoioooo */ @Slf4j public class SensitiveSerializer extends JsonSerializer implements ContextualSerializer { private SensitiveStrategy strategy; @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { try { gen.writeString(strategy.desensitizer().apply(value)); } catch (BeansException e) { log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage()); gen.writeString(value); } } @Override public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { Sensitive annotation = property.getAnnotation(Sensitive.class); if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) { this.strategy = annotation.strategy(); return this; } return prov.findValueSerializer(property.getType(), property); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/constant/CacheConstants.java ================================================ package com.oddfar.campus.common.constant; /** * 缓存的key 常量 */ public class CacheConstants { /** * 登录用户 redis token key */ public static final String LOGIN_TOKEN_KEY = "login_tokens:"; /** * 登录用户 redis userId key */ public static final String LOGIN_USER_KEY = "login_user:"; /** * 验证码 redis key */ public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; /** * 参数管理 cache key */ public static final String SYS_CONFIG_KEY = "sys_config:"; /** * 字典管理 cache key */ public static final String SYS_DICT_KEY = "sys_dict:"; /** * 防重提交 redis key */ public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; /** * 限流 redis key */ public static final String RATE_LIMIT_KEY = "rate_limit:"; /** * 登录账户密码错误次数 redis key */ public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/constant/Constants.java ================================================ package com.oddfar.campus.common.constant; /** * 通用常量信息 * * @author ruoyi */ public class Constants { /** * UTF-8 字符集 */ public static final String UTF8 = "UTF-8"; /** * GBK 字符集 */ public static final String GBK = "GBK"; /** * www主域 */ public static final String WWW = "www."; /** * http请求 */ public static final String HTTP = "http://"; /** * https请求 */ public static final String HTTPS = "https://"; /** * 通用成功标识 */ public static final String SUCCESS = "0"; /** * 通用失败标识 */ public static final String FAIL = "1"; /** * 是 */ public static final String YES = "Y"; /** * 否 */ public static final String NO = "N"; /** * 登录成功 */ public static final String LOGIN_SUCCESS = "Success"; /** * 注销 */ public static final String LOGOUT = "Logout"; /** * 注册 */ public static final String REGISTER = "Register"; /** * 登录失败 */ public static final String LOGIN_FAIL = "Error"; /** * 验证码有效期(分钟) */ public static final Integer CAPTCHA_EXPIRATION = 2; /** * 令牌 */ public static final String TOKEN = "token"; /** * 用户id */ public static final String USER_ID = "userId"; /** * 令牌前缀 */ public static final String TOKEN_PREFIX = "Bearer "; /** * 令牌前缀 */ public static final String LOGIN_USER_KEY = "login_user_key"; /** * 用户ID */ public static final String JWT_USERID = "userid"; /** * 用户名称 */ public static final String JWT_USERNAME = "sub"; /** * 用户头像 */ public static final String JWT_AVATAR = "avatar"; /** * 创建时间 */ public static final String JWT_CREATED = "created"; /** * 用户权限 */ public static final String JWT_AUTHORITIES = "authorities"; /** * 资源映射路径 前缀 */ public static final String RESOURCE_PREFIX = "/profile"; /** * RMI 远程方法调用 */ public static final String LOOKUP_RMI = "rmi:"; /** * LDAP 远程方法调用 */ public static final String LOOKUP_LDAP = "ldap:"; /** * LDAPS 远程方法调用 */ public static final String LOOKUP_LDAPS = "ldaps:"; /** * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) */ public static final String[] JOB_WHITELIST_STR = {"com.ruoyi"}; /** * 定时任务违规的字符 */ public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", "org.springframework", "org.apache", "com.ruoyi.common.utils.file"}; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/constant/HttpStatus.java ================================================ package com.oddfar.campus.common.constant; /** * 返回状态码 * * @author ruoyi */ public class HttpStatus { /** * 操作成功 */ public static final int SUCCESS = 200; /** * 对象创建成功 */ public static final int CREATED = 201; /** * 请求已经被接受 */ public static final int ACCEPTED = 202; /** * 操作已经执行成功,但是没有返回数据 */ public static final int NO_CONTENT = 204; /** * 资源已被移除 */ public static final int MOVED_PERM = 301; /** * 重定向 */ public static final int SEE_OTHER = 303; /** * 资源没有被修改 */ public static final int NOT_MODIFIED = 304; /** * 参数列表错误(缺少,格式不匹配) */ public static final int BAD_REQUEST = 400; /** * 未授权 */ public static final int UNAUTHORIZED = 401; /** * 访问受限,授权过期 */ public static final int FORBIDDEN = 403; /** * 资源,服务未找到 */ public static final int NOT_FOUND = 404; /** * 不允许的http方法 */ public static final int BAD_METHOD = 405; /** * 资源冲突,或者资源被锁 */ public static final int CONFLICT = 409; /** * 不支持的数据,媒体类型 */ public static final int UNSUPPORTED_TYPE = 415; /** * 系统内部错误 */ public static final int ERROR = 500; /** * 接口未实现 */ public static final int NOT_IMPLEMENTED = 501; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/constant/UserConstants.java ================================================ package com.oddfar.campus.common.constant; /** * 用户常量信息 * * @author ruoyi */ public class UserConstants { /** * 平台内系统用户的唯一标志 */ public static final String SYS_USER = "SYS_USER"; /** * 正常状态 */ public static final String NORMAL = "0"; /** * 异常状态 */ public static final String EXCEPTION = "1"; /** * 用户封禁状态 */ public static final String USER_DISABLE = "1"; /** * 角色封禁状态 */ public static final String ROLE_DISABLE = "1"; /** * 字典正常状态 */ public static final String DICT_NORMAL = "0"; /** * 是否为系统默认(是) */ public static final String YES = "Y"; /** * 是否菜单外链(是) */ public static final String YES_FRAME = "0"; /** * 是否菜单外链(否) */ public static final String NO_FRAME = "1"; /** * 菜单类型(目录) */ public static final String TYPE_DIR = "M"; /** * 菜单类型(菜单) */ public static final String TYPE_MENU = "C"; /** * 菜单类型(按钮) */ public static final String TYPE_BUTTON = "F"; /** * Layout组件标识 */ public final static String LAYOUT = "Layout"; /** * ParentView组件标识 */ public final static String PARENT_VIEW = "ParentView"; /** * InnerLink组件标识 */ public final static String INNER_LINK = "InnerLink"; /** * 校验返回结果码 */ public final static String UNIQUE = "0"; public final static String NOT_UNIQUE = "1"; /** * 用户名长度限制 */ public static final int USERNAME_MIN_LENGTH = 2; public static final int USERNAME_MAX_LENGTH = 20; /** * 密码长度限制 */ public static final int PASSWORD_MIN_LENGTH = 5; public static final int PASSWORD_MAX_LENGTH = 20; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/core/BaseMapperX.java ================================================ package com.oddfar.campus.common.core; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.oddfar.campus.common.core.page.PageQuery; import com.oddfar.campus.common.domain.PageResult; import org.apache.ibatis.annotations.Param; import java.util.Collection; import java.util.List; /** * 在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力 */ public interface BaseMapperX extends BaseMapper { default PageResult selectPage(@Param("ew") Wrapper queryWrapper) { PageQuery pageQuery = new PageQuery(); Page page = pageQuery.buildPage(); selectPage(page, queryWrapper); // 转换返回 return new PageResult(page.getRecords(), page.getTotal()); } default T selectOne(String field, Object value) { return selectOne(new QueryWrapper().eq(field, value)); } default T selectOne(SFunction field, Object value) { return selectOne(new LambdaQueryWrapper().eq(field, value)); } default T selectOne(String field1, Object value1, String field2, Object value2) { return selectOne(new QueryWrapper().eq(field1, value1).eq(field2, value2)); } default T selectOne(SFunction field1, Object value1, SFunction field2, Object value2) { return selectOne(new LambdaQueryWrapper().eq(field1, value1).eq(field2, value2)); } default Long selectCount() { return selectCount(new QueryWrapper()); } default Long selectCount(String field, Object value) { return selectCount(new QueryWrapper().eq(field, value)); } default Long selectCount(SFunction field, Object value) { return selectCount(new LambdaQueryWrapper().eq(field, value)); } default List selectList() { return selectList(new QueryWrapper<>()); } default List selectList(String field, Object value) { return selectList(new QueryWrapper().eq(field, value)); } default List selectList(SFunction field, Object value) { return selectList(new LambdaQueryWrapper().eq(field, value)); } default List selectList(String field, Collection values) { return selectList(new QueryWrapper().in(field, values)); } default List selectList(SFunction field, Collection values) { return selectList(new LambdaQueryWrapper().in(field, values)); } /** * 逐条插入,适合少量数据插入,或者对性能要求不高的场景 *

* 如果大量,请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法 * * @param entities 实体们 */ default void insertBatch(Collection entities) { entities.forEach(this::insert); } default void updateBatch(T update) { update(update, new QueryWrapper<>()); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/core/LambdaQueryWrapperX.java ================================================ package com.oddfar.campus.common.core; import cn.hutool.core.util.ArrayUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import org.springframework.util.StringUtils; import java.util.Collection; import java.util.Map; /** * 拓展 MyBatis Plus QueryWrapper 类,主要增加如下功能: *

* 1. 拼接条件的方法,增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。 * * @param 数据类型 */ public class LambdaQueryWrapperX extends LambdaQueryWrapper { public LambdaQueryWrapperX likeIfPresent(SFunction column, String val) { if (StringUtils.hasText(val)) { return (LambdaQueryWrapperX) super.like(column, val); } return this; } public LambdaQueryWrapperX inIfPresent(SFunction column, Collection values) { if (!CollectionUtils.isEmpty(values)) { return (LambdaQueryWrapperX) super.in(column, values); } return this; } public LambdaQueryWrapperX inIfPresent(SFunction column, Object... values) { if (!ArrayUtil.isEmpty(values)) { return (LambdaQueryWrapperX) super.in(column, values); } return this; } public LambdaQueryWrapperX eqIfPresent(SFunction column, Object val) { if (val != null) { return (LambdaQueryWrapperX) super.eq(column, val); } return this; } public LambdaQueryWrapperX neIfPresent(SFunction column, Object val) { if (val != null) { return (LambdaQueryWrapperX) super.ne(column, val); } return this; } public LambdaQueryWrapperX gtIfPresent(SFunction column, Object val) { if (val != null) { return (LambdaQueryWrapperX) super.gt(column, val); } return this; } public LambdaQueryWrapperX geIfPresent(SFunction column, Object val) { if (val != null) { return (LambdaQueryWrapperX) super.ge(column, val); } return this; } public LambdaQueryWrapperX ltIfPresent(SFunction column, Object val) { if (val != null) { return (LambdaQueryWrapperX) super.lt(column, val); } return this; } public LambdaQueryWrapperX leIfPresent(SFunction column, Object val) { if (val != null) { return (LambdaQueryWrapperX) super.le(column, val); } return this; } public LambdaQueryWrapperX betweenIfPresent(SFunction column, Object val1, Object val2) { if (val1 != null && val2 != null) { return (LambdaQueryWrapperX) super.between(column, val1, val2); } if (val1 != null) { return (LambdaQueryWrapperX) ge(column, val1); } if (val2 != null) { return (LambdaQueryWrapperX) le(column, val2); } return this; } public LambdaQueryWrapperX betweenIfPresent(SFunction column, Object[] values) { Object val1 = ArrayUtil.get(values, 0); Object val2 = ArrayUtil.get(values, 1); return betweenIfPresent(column, val1, val2); } public LambdaQueryWrapperX betweenIfPresent(SFunction column, Map values) { String val1 = (String) values.get("beginTime"); String val2 = (String) values.get("endTime"); return betweenIfPresent(column, val1, val2); } // ========== 重写父类方法,方便链式调用 ========== @Override public LambdaQueryWrapperX eq(boolean condition, SFunction column, Object val) { super.eq(condition, column, val); return this; } @Override public LambdaQueryWrapperX eq(SFunction column, Object val) { super.eq(column, val); return this; } @Override public LambdaQueryWrapperX orderByDesc(SFunction column) { super.orderByDesc(true, column); return this; } @Override public LambdaQueryWrapperX last(String lastSql) { super.last(lastSql); return this; } @Override public LambdaQueryWrapperX in(SFunction column, Collection coll) { super.in(column, coll); return this; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/core/RedisCache.java ================================================ package com.oddfar.campus.common.core; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.TimeUnit; /** * spring redis 工具类 * * @author ruoyi **/ @SuppressWarnings(value = {"unchecked", "rawtypes"}) @Component public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 */ public void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @param unit 时间单位 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 获取有效时间 * * @param key Redis键 * @return 有效时间 */ public long getExpire(final String key) { return redisTemplate.getExpire(key); } /** * 判断 key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public Boolean hasKey(String key) { return redisTemplate.hasKey(key); } /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public T getCacheObject(final String key) { ValueOperations operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection 多个对象 * @return */ public boolean deleteObject(final Collection collection) { return redisTemplate.delete(collection) > 0; } /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public long setCacheList(final String key, final List dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 重新set缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public long reSetCacheList(final String key, final List dataList) { this.deleteObject(key); Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public List getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public BoundSetOperations setCacheSet(final String key, final Set dataSet) { BoundSetOperations setOperation = redisTemplate.boundSetOps(key); Iterator it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得缓存的set * * @param key * @return */ public Set getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * 缓存Map * * @param key * @param dataMap */ public void setCacheMap(final String key, final Map dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 获得缓存的Map * * @param key * @return */ public Map getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 获取Hash中的数据 * * @param key Redis键 * @param hKey Hash键 * @return Hash中的对象 */ public T getCacheMapValue(final String key, final String hKey) { HashOperations opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 获取多个Hash中的数据 * * @param key Redis键 * @param hKeys Hash键集合 * @return Hash对象集合 */ public List getMultiCacheMapValue(final String key, final Collection hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 删除Hash中的某条数据 * * @param key Redis键 * @param hKey Hash键 * @return 是否成功 */ public boolean deleteCacheMapValue(final String key, final String hKey) { return redisTemplate.opsForHash().delete(key, hKey) > 0; } /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection keys(final String pattern) { return redisTemplate.keys(pattern); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/core/page/PageQuery.java ================================================ package com.oddfar.campus.common.core.page; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.oddfar.campus.common.core.text.Convert; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.sql.SqlUtil; import lombok.Data; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * 分页查询实体类 * * @author oddfar * @date 2023/11/26 */ @Data public class PageQuery implements Serializable { private static final long serialVersionUID = 1L; /** * 当前记录起始索引 */ private static final String PAGE_NUM = "pageNum"; /** * 每页显示记录数 */ private static final String PAGE_SIZE = "pageSize"; /** * 分页大小 */ private Integer pageSize; /** * 当前页数 */ private Integer pageNum; /** * 排序列 */ private String orderByColumn; /** * 排序的方向desc或者asc */ private String isAsc; /** * 当前记录起始索引 默认值 */ public static final int DEFAULT_PAGE_NUM = 1; /** * 每页显示记录数 默认值 10 */ public static final int DEFAULT_PAGE_SIZE = 10; public final static String COMMA = ","; public Page build() { Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM); Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE); if (pageNum <= 0) { pageNum = DEFAULT_PAGE_NUM; } Page page = new Page<>(pageNum, pageSize); List orderItems = buildOrderItem(); if (CollUtil.isNotEmpty(orderItems)) { page.addOrder(orderItems); } return page; } public Page buildPage() { Integer pageNum = Convert.toInt(ServletUtils.getParameter(PAGE_NUM), DEFAULT_PAGE_NUM); Integer pageSize = Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), DEFAULT_PAGE_SIZE); if (pageNum <= 0) { pageNum = DEFAULT_PAGE_NUM; } Page page = new Page<>(pageNum, pageSize); List orderItems = buildOrderItem(); if (CollUtil.isNotEmpty(orderItems)) { page.addOrder(orderItems); } return page; } /** * 构建排序 *

* 支持的用法如下: * {isAsc:"asc",orderByColumn:"id"} order by id asc * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc */ private List buildOrderItem() { if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) { return null; } String orderBy = SqlUtil.escapeOrderBySql(orderByColumn); orderBy = StringUtils.toUnderScoreCase(orderBy); // 兼容前端排序类型 isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"}); String[] orderByArr = orderBy.split(COMMA); String[] isAscArr = isAsc.split(COMMA); if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) { throw new ServiceException("排序参数有误"); } List list = new ArrayList<>(); // 每个字段各自排序 for (int i = 0; i < orderByArr.length; i++) { String orderByStr = orderByArr[i]; String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i]; if ("asc".equals(isAscStr)) { list.add(OrderItem.asc(orderByStr)); } else if ("desc".equals(isAscStr)) { list.add(OrderItem.desc(orderByStr)); } else { throw new ServiceException("排序参数有误"); } } return list; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/core/page/TableDataInfo.java ================================================ package com.oddfar.campus.common.core.page; import java.io.Serializable; import java.util.List; /** * 表格分页数据对象 * * @author ruoyi */ public class TableDataInfo implements Serializable { private static final long serialVersionUID = 1L; /** * 总记录数 */ private long total; /** * 列表数据 */ private List rows; /** * 消息状态码 */ private int code; /** * 消息内容 */ private String msg; /** * 表格数据对象 */ public TableDataInfo() { } /** * 分页 * * @param list 列表数据 * @param total 总记录数 */ public TableDataInfo(List list, int total) { this.rows = list; this.total = total; } public long getTotal() { return total; } public void setTotal(long total) { this.total = total; } public List getRows() { return rows; } public void setRows(List rows) { this.rows = rows; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/core/text/CharsetKit.java ================================================ package com.oddfar.campus.common.core.text; import com.oddfar.campus.common.utils.StringUtils; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; /** * 字符集工具类 * * @author ruoyi */ public class CharsetKit { /** ISO-8859-1 */ public static final String ISO_8859_1 = "ISO-8859-1"; /** UTF-8 */ public static final String UTF_8 = "UTF-8"; /** GBK */ public static final String GBK = "GBK"; /** ISO-8859-1 */ public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); /** UTF-8 */ public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); /** GBK */ public static final Charset CHARSET_GBK = Charset.forName(GBK); /** * 转换为Charset对象 * * @param charset 字符集,为空则返回默认字符集 * @return Charset */ public static Charset charset(String charset) { return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); } /** * 转换字符串的字符集编码 * * @param source 字符串 * @param srcCharset 源字符集,默认ISO-8859-1 * @param destCharset 目标字符集,默认UTF-8 * @return 转换后的字符集 */ public static String convert(String source, String srcCharset, String destCharset) { return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); } /** * 转换字符串的字符集编码 * * @param source 字符串 * @param srcCharset 源字符集,默认ISO-8859-1 * @param destCharset 目标字符集,默认UTF-8 * @return 转换后的字符集 */ public static String convert(String source, Charset srcCharset, Charset destCharset) { if (null == srcCharset) { srcCharset = StandardCharsets.ISO_8859_1; } if (null == destCharset) { destCharset = StandardCharsets.UTF_8; } if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) { return source; } return new String(source.getBytes(srcCharset), destCharset); } /** * @return 系统字符集编码 */ public static String systemCharset() { return Charset.defaultCharset().name(); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/core/text/Convert.java ================================================ package com.oddfar.campus.common.core.text; import com.oddfar.campus.common.utils.StringUtils; import org.apache.commons.lang3.ArrayUtils; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.text.NumberFormat; import java.util.Set; /** * 类型转换器 * * @author ruoyi */ public class Convert { /** * 转换为字符串
* 如果给定的值为null,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static String toStr(Object value, String defaultValue) { if (null == value) { return defaultValue; } if (value instanceof String) { return (String) value; } return value.toString(); } /** * 转换为字符串
* 如果给定的值为null,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static String toStr(Object value) { return toStr(value, null); } /** * 转换为字符
* 如果给定的值为null,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Character toChar(Object value, Character defaultValue) { if (null == value) { return defaultValue; } if (value instanceof Character) { return (Character) value; } final String valueStr = toStr(value, null); return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); } /** * 转换为字符
* 如果给定的值为null,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Character toChar(Object value) { return toChar(value, null); } /** * 转换为byte
* 如果给定的值为null,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Byte toByte(Object value, Byte defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Byte) { return (Byte) value; } if (value instanceof Number) { return ((Number) value).byteValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Byte.parseByte(valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为byte
* 如果给定的值为null,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Byte toByte(Object value) { return toByte(value, null); } /** * 转换为Short
* 如果给定的值为null,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Short toShort(Object value, Short defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Short) { return (Short) value; } if (value instanceof Number) { return ((Number) value).shortValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Short.parseShort(valueStr.trim()); } catch (Exception e) { return defaultValue; } } /** * 转换为Short
* 如果给定的值为null,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Short toShort(Object value) { return toShort(value, null); } /** * 转换为Number
* 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Number toNumber(Object value, Number defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Number) { return (Number) value; } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return NumberFormat.getInstance().parse(valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为Number
* 如果给定的值为空,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Number toNumber(Object value) { return toNumber(value, null); } /** * 转换为int
* 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Integer toInt(Object value, Integer defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Integer) { return (Integer) value; } if (value instanceof Number) { return ((Number) value).intValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Integer.parseInt(valueStr.trim()); } catch (Exception e) { return defaultValue; } } /** * 转换为int
* 如果给定的值为null,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Integer toInt(Object value) { return toInt(value, null); } /** * 转换为Integer数组
* * @param str 被转换的值 * @return 结果 */ public static Integer[] toIntArray(String str) { return toIntArray(",", str); } /** * 转换为Long数组
* * @param str 被转换的值 * @return 结果 */ public static Long[] toLongArray(String str) { return toLongArray(",", str); } /** * 转换为Integer数组
* * @param split 分隔符 * @param split 被转换的值 * @return 结果 */ public static Integer[] toIntArray(String split, String str) { if (StringUtils.isEmpty(str)) { return new Integer[]{}; } String[] arr = str.split(split); final Integer[] ints = new Integer[arr.length]; for (int i = 0; i < arr.length; i++) { final Integer v = toInt(arr[i], 0); ints[i] = v; } return ints; } /** * 转换为Long数组
* * @param split 分隔符 * @param str 被转换的值 * @return 结果 */ public static Long[] toLongArray(String split, String str) { if (StringUtils.isEmpty(str)) { return new Long[]{}; } String[] arr = str.split(split); final Long[] longs = new Long[arr.length]; for (int i = 0; i < arr.length; i++) { final Long v = toLong(arr[i], null); longs[i] = v; } return longs; } /** * 转换为String数组
* * @param str 被转换的值 * @return 结果 */ public static String[] toStrArray(String str) { return toStrArray(",", str); } /** * 转换为String数组
* * @param split 分隔符 * @param split 被转换的值 * @return 结果 */ public static String[] toStrArray(String split, String str) { return str.split(split); } /** * 转换为long
* 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Long toLong(Object value, Long defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Long) { return (Long) value; } if (value instanceof Number) { return ((Number) value).longValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { // 支持科学计数法 return new BigDecimal(valueStr.trim()).longValue(); } catch (Exception e) { return defaultValue; } } /** * 转换为long
* 如果给定的值为null,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Long toLong(Object value) { return toLong(value, null); } /** * 转换为double
* 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Double toDouble(Object value, Double defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Double) { return (Double) value; } if (value instanceof Number) { return ((Number) value).doubleValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { // 支持科学计数法 return new BigDecimal(valueStr.trim()).doubleValue(); } catch (Exception e) { return defaultValue; } } /** * 转换为double
* 如果给定的值为空,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Double toDouble(Object value) { return toDouble(value, null); } /** * 转换为Float
* 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Float toFloat(Object value, Float defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Float) { return (Float) value; } if (value instanceof Number) { return ((Number) value).floatValue(); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Float.parseFloat(valueStr.trim()); } catch (Exception e) { return defaultValue; } } /** * 转换为Float
* 如果给定的值为空,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Float toFloat(Object value) { return toFloat(value, null); } /** * 转换为boolean
* String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static Boolean toBool(Object value, Boolean defaultValue) { if (value == null) { return defaultValue; } if (value instanceof Boolean) { return (Boolean) value; } String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } valueStr = valueStr.trim().toLowerCase(); switch (valueStr) { case "true": case "yes": case "ok": case "1": return true; case "false": case "no": case "0": return false; default: return defaultValue; } } /** * 转换为boolean
* 如果给定的值为空,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static Boolean toBool(Object value) { return toBool(value, null); } /** * 转换为Enum对象
* 如果给定的值为空,或者转换失败,返回默认值
* * @param clazz Enum的Class * @param value 值 * @param defaultValue 默认值 * @return Enum */ public static > E toEnum(Class clazz, Object value, E defaultValue) { if (value == null) { return defaultValue; } if (clazz.isAssignableFrom(value.getClass())) { @SuppressWarnings("unchecked") E myE = (E) value; return myE; } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return Enum.valueOf(clazz, valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为Enum对象
* 如果给定的值为空,或者转换失败,返回默认值null
* * @param clazz Enum的Class * @param value 值 * @return Enum */ public static > E toEnum(Class clazz, Object value) { return toEnum(clazz, value, null); } /** * 转换为BigInteger
* 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static BigInteger toBigInteger(Object value, BigInteger defaultValue) { if (value == null) { return defaultValue; } if (value instanceof BigInteger) { return (BigInteger) value; } if (value instanceof Long) { return BigInteger.valueOf((Long) value); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return new BigInteger(valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为BigInteger
* 如果给定的值为空,或者转换失败,返回默认值null
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static BigInteger toBigInteger(Object value) { return toBigInteger(value, null); } /** * 转换为BigDecimal
* 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @param defaultValue 转换错误时的默认值 * @return 结果 */ public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) { if (value == null) { return defaultValue; } if (value instanceof BigDecimal) { return (BigDecimal) value; } if (value instanceof Long) { return new BigDecimal((Long) value); } if (value instanceof Double) { return new BigDecimal((Double) value); } if (value instanceof Integer) { return new BigDecimal((Integer) value); } final String valueStr = toStr(value, null); if (StringUtils.isEmpty(valueStr)) { return defaultValue; } try { return new BigDecimal(valueStr); } catch (Exception e) { return defaultValue; } } /** * 转换为BigDecimal
* 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * * @param value 被转换的值 * @return 结果 */ public static BigDecimal toBigDecimal(Object value) { return toBigDecimal(value, null); } /** * 将对象转为字符串
* 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 * * @param obj 对象 * @return 字符串 */ public static String utf8Str(Object obj) { return str(obj, CharsetKit.CHARSET_UTF_8); } /** * 将对象转为字符串
* 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 * * @param obj 对象 * @param charsetName 字符集 * @return 字符串 */ public static String str(Object obj, String charsetName) { return str(obj, Charset.forName(charsetName)); } /** * 将对象转为字符串
* 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 * * @param obj 对象 * @param charset 字符集 * @return 字符串 */ public static String str(Object obj, Charset charset) { if (null == obj) { return null; } if (obj instanceof String) { return (String) obj; } else if (obj instanceof byte[]) { return str((byte[]) obj, charset); } else if (obj instanceof Byte[]) { byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj); return str(bytes, charset); } else if (obj instanceof ByteBuffer) { return str((ByteBuffer) obj, charset); } return obj.toString(); } /** * 将byte数组转为字符串 * * @param bytes byte数组 * @param charset 字符集 * @return 字符串 */ public static String str(byte[] bytes, String charset) { return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); } /** * 解码字节码 * * @param data 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 解码后的字符串 */ public static String str(byte[] data, Charset charset) { if (data == null) { return null; } if (null == charset) { return new String(data); } return new String(data, charset); } /** * 将编码的byteBuffer数据转换为字符串 * * @param data 数据 * @param charset 字符集,如果为空使用当前系统字符集 * @return 字符串 */ public static String str(ByteBuffer data, String charset) { if (data == null) { return null; } return str(data, Charset.forName(charset)); } /** * 将编码的byteBuffer数据转换为字符串 * * @param data 数据 * @param charset 字符集,如果为空使用当前系统字符集 * @return 字符串 */ public static String str(ByteBuffer data, Charset charset) { if (null == charset) { charset = Charset.defaultCharset(); } return charset.decode(data).toString(); } // ----------------------------------------------------------------------- 全角半角转换 /** * 半角转全角 * * @param input String. * @return 全角字符串. */ public static String toSBC(String input) { return toSBC(input, null); } /** * 半角转全角 * * @param input String * @param notConvertSet 不替换的字符集合 * @return 全角字符串. */ public static String toSBC(String input, Set notConvertSet) { char[] c = input.toCharArray(); for (int i = 0; i < c.length; i++) { if (null != notConvertSet && notConvertSet.contains(c[i])) { // 跳过不替换的字符 continue; } if (c[i] == ' ') { c[i] = '\u3000'; } else if (c[i] < '\177') { c[i] = (char) (c[i] + 65248); } } return new String(c); } /** * 全角转半角 * * @param input String. * @return 半角字符串 */ public static String toDBC(String input) { return toDBC(input, null); } /** * 替换全角为半角 * * @param text 文本 * @param notConvertSet 不替换的字符集合 * @return 替换后的字符 */ public static String toDBC(String text, Set notConvertSet) { char[] c = text.toCharArray(); for (int i = 0; i < c.length; i++) { if (null != notConvertSet && notConvertSet.contains(c[i])) { // 跳过不替换的字符 continue; } if (c[i] == '\u3000') { c[i] = ' '; } else if (c[i] > '\uFF00' && c[i] < '\uFF5F') { c[i] = (char) (c[i] - 65248); } } String returnString = new String(c); return returnString; } /** * 数字金额大写转换 先写个完整的然后将如零拾替换成零 * * @param n 数字 * @return 中文大写数字 */ public static String digitUppercase(double n) { String[] fraction = {"角", "分"}; String[] digit = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}; String[][] unit = {{"元", "万", "亿"}, {"", "拾", "佰", "仟"}}; String head = n < 0 ? "负" : ""; n = Math.abs(n); String s = ""; for (int i = 0; i < fraction.length; i++) { s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); } if (s.length() < 1) { s = "整"; } int integerPart = (int) Math.floor(n); for (int i = 0; i < unit[0].length && integerPart > 0; i++) { String p = ""; for (int j = 0; j < unit[1].length && n > 0; j++) { p = digit[integerPart % 10] + unit[1][j] + p; integerPart = integerPart / 10; } s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; } return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/BaseEntity.java ================================================ package com.oddfar.campus.common.domain; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableLogic; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * Entity基类 * * @author oddfar */ @Data @AllArgsConstructor @NoArgsConstructor public class BaseEntity implements Serializable { private static final long serialVersionUID = 1L; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.UPDATE) private Date updateTime; @TableField(fill = FieldFill.UPDATE) private Long updateUser; @TableLogic @TableField(fill = FieldFill.INSERT) @JsonIgnore private Integer delFlag; @TableField(exist = false) @JsonInclude(JsonInclude.Include.NON_EMPTY) private Map params = new HashMap<>();; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/PageResult.java ================================================ package com.oddfar.campus.common.domain; import lombok.Data; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @Data public final class PageResult implements Serializable { private List rows; private long total; public PageResult() { } public PageResult(List rows, long total) { this.rows = rows; this.total = total; } public PageResult(int total) { this.rows = new ArrayList<>(); this.total = total; } public static PageResult empty() { return new PageResult<>(0); } public static PageResult empty(int total) { return new PageResult<>(total); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/R.java ================================================ package com.oddfar.campus.common.domain; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; import com.baomidou.mybatisplus.core.metadata.IPage; import com.oddfar.campus.common.enums.BizCodeEnum; import java.util.HashMap; import java.util.Map; /** * 返回数据 */ public class R extends HashMap { private static final long serialVersionUID = 1L; public R setData(Object data) { put("data", data); return this; } //利用fastjson进行逆转 public T getData(String key, TypeReference typeReference) { Object data = get(key);//默认是map String s = JSON.toJSONString(data); T t = JSON.parseObject(s, typeReference); return t; } //利用fastjson进行逆转 public T getData(TypeReference typeReference) { Object data = get("data"); String s = JSON.toJSONString(data); T t = JSON.parseObject(s, typeReference); return t; } public R() { put("code", 200); put("msg", "success"); } public static R error() { return error(500, "未知异常,请联系管理员"); } public static R error(String msg) { return error(500, msg); } public static R error(int code, String msg) { R r = new R(); r.put("code", code); r.put("msg", msg); return r; } public static R error(BizCodeEnum bizCodeEnum) { R r = new R(); r.put("code", bizCodeEnum.getCode()); r.put("msg", bizCodeEnum.getMsg()); return r; } public static R ok(String msg) { R r = new R(); r.put("msg", msg); return r; } public static R ok(Object data) { R r = new R(); r.put("data", data); return r; } public static R ok(Map map) { R r = new R(); r.putAll(map); return r; } public static R ok() { return new R(); } public R put(String key, Object value) { super.put(key, value); return this; } public R put(Object value) { super.put("data", value); return this; } public R put(PageResult pageResult) { super.put("rows", pageResult.getRows()); super.put("total", pageResult.getTotal()); return this; } public R put(IPage page) { super.put("rows", page.getRecords()); super.put("total", page.getTotal()); return this; } public Integer getCode() { return (Integer) this.get("code"); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/TreeSelect.java ================================================ package com.oddfar.campus.common.domain; import com.fasterxml.jackson.annotation.JsonInclude; import com.oddfar.campus.common.domain.entity.SysMenuEntity; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import java.io.Serializable; import java.util.List; import java.util.stream.Collectors; /** * Treeselect树结构实体类 * * @author ruoyi */ public class TreeSelect implements Serializable { private static final long serialVersionUID = 1L; /** * 节点ID */ private Long id; /** * 节点名称 */ private String label; /** * 子节点 */ @JsonInclude(JsonInclude.Include.NON_EMPTY) private List children; public TreeSelect() { } public TreeSelect(Long id, String label) { this.id = id; this.label = label; } public TreeSelect(Long id, String label, List resources) { this.id = id; this.label = label; this.children = resources.stream().map(TreeSelect::new).collect(Collectors.toList()); } public TreeSelect(SysMenuEntity menu) { this.id = menu.getMenuId(); this.label = menu.getMenuName(); this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); } public TreeSelect(SysResourceEntity resource) { this.id = resource.getResourceId(); this.label = resource.getResourceName(); } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public List getChildren() { return children; } public void setChildren(List children) { this.children = children; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysConfigEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.oddfar.campus.common.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; /** * 配置实体类 * * @author oddfar */ @Data @EqualsAndHashCode(callSuper = true) @TableName("sys_config") public class SysConfigEntity extends BaseEntity { private static final long serialVersionUID = 1L; // @JsonFormat(shape = JsonFormat.Shape.STRING) @TableId("config_id") private Long configId; /** * 参数名称 */ @NotBlank(message = "参数名称不能为空") @Size(min = 0, max = 100, message = "参数名称不能超过100个字符") private String configName; /** * 参数键名 */ @NotBlank(message = "参数键名不能为空") @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符") private String configKey; /** * 参数键值 */ @NotBlank(message = "参数键值不能为空") @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符") private String configValue; /** * 系统内置(Y是 N否) */ @NotBlank(message = "系统内置不能为空") private String configType; /** * 所属分类的编码 */ @NotBlank(message = "所属分类的编码不能为空") private String groupCode; /** * 备注 */ private String remark; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysDictDataEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.oddfar.campus.common.constant.UserConstants; import com.oddfar.campus.common.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; /** * 字典数据表 sys_dict_data * * @author oddfar */ @Data @EqualsAndHashCode(callSuper = true) @TableName("sys_dict_data") public class SysDictDataEntity extends BaseEntity { private static final long serialVersionUID = 1L; /** * 字典编码 */ @TableId("dict_code") private Long dictCode; /** * 字典排序 */ private Long dictSort; /** * 字典标签 */ @NotBlank(message = "字典标签不能为空") @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符") private String dictLabel; /** * 字典键值 */ @NotBlank(message = "字典键值不能为空") @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符") private String dictValue; /** * 字典类型 */ @NotBlank(message = "字典类型不能为空") @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符") private String dictType; /** * 样式属性(其他样式扩展) */ @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符") private String cssClass; /** * 表格字典样式 */ private String listClass; /** * 是否默认(Y是 N否) */ private String isDefault; /** * 状态(0正常 1停用) */ private String status; /** * 备注 */ private String remark; public boolean getDefault() { return UserConstants.YES.equals(this.isDefault); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysDictTypeEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.oddfar.campus.common.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; /** * 字典类型表 sys_dict_type */ @Data @EqualsAndHashCode(callSuper = true) @TableName("sys_dict_type") public class SysDictTypeEntity extends BaseEntity { private static final long serialVersionUID = 1L; /** * 字典主键 */ @TableId("dict_id") private Long dictId; /** * 字典名称 */ @NotBlank(message = "字典名称不能为空") @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") private String dictName; /** * 字典类型 */ @NotBlank(message = "字典类型不能为空") @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") private String dictType; /** * 状态(0正常 1停用) */ private String status; /** * 备注 */ private String remark; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysLoginLogEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 系统访问记录表 sys_logininfor * * @author ruoyi */ @Data @TableName("sys_log_login") public class SysLoginLogEntity { private static final long serialVersionUID = 1L; /** * ID */ @TableId("info_id") private Long infoId; /** * 登录成功的用户id */ private Long userId; /** * 用户账号 */ private String userName; /** * 登录状态 0成功 1失败 */ private String status; /** * 登录IP地址 */ private String ipaddr; /** * 登录地点 */ private String loginLocation; /** * 浏览器类型 */ private String browser; /** * 操作系统 */ private String os; /** * 提示消息 */ private String msg; /** * 访问时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date loginTime; private static final Integer PAGE_NUM = 1; private static final Integer PAGE_SIZE = 10; @TableField(exist = false) private Map params; @NotNull(message = "页码不能为空") @Min(value = 1, message = "页码最小值为 1") @TableField(exist = false) @JsonIgnore private Integer pageNum = PAGE_NUM; @NotNull(message = "每页条数不能为空") @Min(value = 1, message = "每页条数最小值为 1") @Max(value = 100, message = "每页条数最大值为 100") @TableField(exist = false) @JsonIgnore private Integer pageSize = PAGE_SIZE; public Map getParams() { if (params == null) { params = new HashMap<>(); } return params; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysMenuEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.oddfar.campus.common.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.ArrayList; import java.util.List; /** * 菜单权限表 sys_menu * (根据若依项目修改) * * @author oddfar */ @Data @EqualsAndHashCode(callSuper = true) @TableName("sys_menu") public class SysMenuEntity extends BaseEntity { private static final long serialVersionUID = 1L; /** * 菜单ID */ @TableId("menu_id") private Long menuId; /** * 菜单名称 */ @NotBlank(message = "菜单名称不能为空") @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") private String menuName; /** * 父菜单名称 */ @TableField(exist = false) private String parentName; /** * 父菜单ID */ private Long parentId; /** * 显示顺序 */ @NotNull(message = "显示顺序不能为空") private Integer orderNum; /** * 路由地址 */ @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") private String path; /** * 组件路径 */ @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") private String component; /** * 路由参数 */ private String query; /** * 是否为外链(0是 1否) */ private String isFrame; /** * 是否缓存(0缓存 1不缓存) */ private String isCache; /** * 类型(M目录 C菜单 F按钮) */ @NotBlank(message = "菜单类型不能为空") private String menuType; /** * 显示状态(0显示 1隐藏) */ private String visible; /** * 菜单状态(0显示 1隐藏) */ private String status; /** * 权限字符串 */ @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") private String perms; /** * 菜单图标 */ private String icon; /** 备注 */ private String remark; /** * 子菜单 */ @TableField(exist = false) private List children = new ArrayList(); } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysOperLogEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.*; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 日志实体类 sys_log */ @Data @TableName("sys_log_oper") public class SysOperLogEntity { private static final long serialVersionUID = 1L; /** * 日志主键 */ @TableId("oper_id") private Long operId; /** * 服务名称,一般为spring.application.name */ private String appName; /** * 操作模块 */ private String logName; /** * 日志记录内容 */ private String logContent; /** * 请求方法 */ private String method; /** * 请求方式 */ private String requestMethod; /** * 操作人员 id */ private Long operUserId; /** * 请求url */ private String operUrl; /** * 操作地址 */ private String operIp; /** * 请求参数 */ private String operParam; /** * 返回参数 */ private String jsonResult; /** * 操作状态(0正常 1异常) */ private Integer status; /** * 错误消息 */ private String errorMsg; /** * 操作时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date operTime; private static final Integer PAGE_NUM = 1; private static final Integer PAGE_SIZE = 10; @TableField(exist = false) private Map params; @NotNull(message = "页码不能为空") @Min(value = 1, message = "页码最小值为 1") @TableField(exist = false) @JsonIgnore private Integer pageNum = PAGE_NUM; @NotNull(message = "每页条数不能为空") @Min(value = 1, message = "每页条数最小值为 1") @Max(value = 100, message = "每页条数最大值为 100") @TableField(exist = false) @JsonIgnore private Integer pageSize = PAGE_SIZE; public Map getParams() { if (params == null) { params = new HashMap<>(); } return params; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysResourceEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.oddfar.campus.common.domain.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; import java.util.ArrayList; import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @TableName("sys_resource") public class SysResourceEntity extends BaseEntity { private static final long serialVersionUID = 1L; @TableId("resource_id") private Long resourceId; /** * 应用编码 */ private String appCode; /** * 资源编码 */ private String resourceCode; /** * 资源名称 */ private String resourceName; /** * 类名称 */ private String className; /** * 方法名称 */ private String methodName; /** * 资源模块编码,一般为控制器类名排除Controller */ @TableField(exist = false) private String modular_code; /** * 资源模块名称,一般为控制器名称 */ private String modularName; /** * 资源url */ private String url; /** * http请求方法 */ private String httpMethod; /** * 资源的业务类型:1-业务类,2-系统类 */ private Integer resourceBizType; /** * 是否需要鉴权:Y-是,N-否 */ private String requiredPermissionFlag; /** * 子菜单 */ @TableField(exist = false) private List children = new ArrayList(); } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysRoleEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.oddfar.campus.common.domain.BaseEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import java.util.Set; /** * 角色表 sys_role * * @author ruoyi */ @TableName("sys_role") @EqualsAndHashCode(callSuper = true) @Data @AllArgsConstructor @NoArgsConstructor public class SysRoleEntity extends BaseEntity { private static final long serialVersionUID = 1L; /** * 角色ID */ @TableId("role_id") private Long roleId; /** * 角色名称 */ @NotBlank(message = "角色名称不能为空") @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") private String roleName; /** * 角色权限 */ @NotBlank(message = "权限字符不能为空") @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") private String roleKey; /** * 角色排序 */ @NotBlank(message = "显示顺序不能为空") private String roleSort; /** * 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ private boolean menuCheckStrictly; /** * 角色状态(0正常 1停用) */ private String status; /** * 用户是否存在此角色标识 默认不存在 */ @TableField(exist = false) private boolean flag = false; /** * 菜单组 */ @TableField(exist = false) private Long[] menuIds; /** * 角色菜单权限 */ @TableField(exist = false) private Set permissions; /** * 备注 */ private String remark; public SysRoleEntity(Long roleId) { this.roleId = roleId; } public boolean isAdmin() { return isAdmin(this.roleId); } public static boolean isAdmin(Long roleId) { return roleId != null && 1L == roleId; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysRoleMenuEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; /** * 角色和菜单关联 sys_role_menu */ @Data @TableName("sys_role_menu") public class SysRoleMenuEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 角色ID */ @TableId(type = IdType.INPUT) private Long roleId; /** * 菜单ID */ private Long menuId; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysRoleResourceEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; /** * 角色与接口资源关系表 */ @Data @TableName("sys_role_resource") public class SysRoleResourceEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 资源编码 */ private String resourceCode; /** * 角色ID */ @TableId(type = IdType.INPUT) private Long roleId; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysUserEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonIgnore; import com.oddfar.campus.common.domain.BaseEntity; import com.oddfar.campus.common.validator.Xss; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import java.util.Date; import java.util.List; /** * 用户对象 sys_user * * @author oddfar * @since 1.0.0 2022-09-24 */ @TableName("sys_user") @EqualsAndHashCode(callSuper = true) @Data @NoArgsConstructor public class SysUserEntity extends BaseEntity { private static final long serialVersionUID = 1L; /** 用户ID */ @TableId("user_id") private Long userId; /** 用户账号 */ @Xss(message = "用户账号不能包含脚本字符") @NotBlank(message = "用户账号不能为空") @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") private String userName; /** 用户昵称 */ @Xss(message = "用户昵称不能包含脚本字符") @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") private String nickName; /** 用户邮箱 */ @Email(message = "邮箱格式不正确") @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") private String email; /** 手机号码 */ @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") private String phonenumber; /** 用户性别 */ private String sex; /** 用户头像 */ private String avatar; /** 密码 */ private String password; /** 帐号状态(0正常 1停用) */ private String status; /** 最后登录IP */ private String loginIp; /** 最后登录时间 */ private Date loginDate; /** 备注 */ private String remark; /** 角色对象 */ @TableField(exist = false) private List roles; /** 资源对象 */ // @TableField(exist = false) // private List resources; /** 角色组 */ @TableField(exist = false) private Long[] roleIds; /** 岗位组 */ @TableField(exist = false) private Long[] postIds; /** 角色ID */ @TableField(exist = false) private Long roleId; public SysUserEntity(Long userId){ this.userId = userId; } public boolean isAdmin() { return isAdmin(this.userId); } public static boolean isAdmin(Long userId) { return userId != null && 1L == userId; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/entity/SysUserRoleEntity.java ================================================ package com.oddfar.campus.common.domain.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; /** * 用户和角色关联 sys_user_role */ @Data @TableName("sys_user_role") public class SysUserRoleEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 用户ID */ @TableId(type = IdType.INPUT) private Long userId; /** * 角色ID */ private Long roleId; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/model/LoginBody.java ================================================ package com.oddfar.campus.common.domain.model; /** * 用户登录对象 * * @author ruoyi */ public class LoginBody { /** * 用户名 */ private String username; /** * 用户密码 */ private String password; /** * 验证码 */ private String code; /** * 唯一标识 */ private String uuid; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getUuid() { return uuid; } public void setUuid(String uuid) { this.uuid = uuid; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/model/LoginUser.java ================================================ package com.oddfar.campus.common.domain.model; import com.alibaba.fastjson2.annotation.JSONField; import com.oddfar.campus.common.domain.entity.SysUserEntity; import lombok.AllArgsConstructor; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.Set; /** * 登录用户身份权限 * * @author ruoyi */ @Data @AllArgsConstructor public class LoginUser implements UserDetails { private static final long serialVersionUID = 1L; /** * 用户ID */ private Long userId; /** * 用户唯一标识 */ private String token; /** * 过期时间 */ private Long expireTime; /** * 登录IP地址 */ private String ipaddr; /** * 权限列表 */ private Set permissions; /** * 资源列表 */ private Set resources; /** * 用户信息 */ private SysUserEntity user; public LoginUser(Long userId, SysUserEntity user, Set permissions, Set resources) { this.userId = userId; this.user = user; this.permissions = permissions; this.resources = resources; } @JSONField(serialize = false) @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUserName(); } /** * 账户是否未过期,过期无法验证 */ @JSONField(serialize = false) @Override public boolean isAccountNonExpired() { return true; } /** * 指定用户是否解锁,锁定的用户无法进行身份验证 * * @return */ @JSONField(serialize = false) @Override public boolean isAccountNonLocked() { return true; } /** * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 * * @return */ @JSONField(serialize = false) @Override public boolean isCredentialsNonExpired() { return true; } /** * 是否可用 ,禁用的用户不能身份验证 * * @return */ @JSONField(serialize = false) @Override public boolean isEnabled() { return true; } @Override public Collection getAuthorities() { return null; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/model/LoginUserToken.java ================================================ package com.oddfar.campus.common.domain.model; import lombok.Data; /** * 登录用户身份信息 * */ @Data public class LoginUserToken { /** * 用户ID */ private Long userId; /** * 用户唯一标识 */ private String token; /** * 登录时间 */ private Long loginTime; /** * 过期时间 */ private Long expireTime; /** * 登录IP地址 */ private String ipaddr; /** * 登录地点 */ private String loginLocation; /** * 浏览器类型 */ private String browser; /** * 操作系统 */ private String os; public LoginUserToken() { } public LoginUserToken(LoginUser loginUser) { this.userId = loginUser.getUserId(); this.token = loginUser.getToken(); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/model/RegisterBody.java ================================================ package com.oddfar.campus.common.domain.model; import lombok.Data; /** * 用户注册对象 */ @Data public class RegisterBody extends LoginBody { /** * 邮箱 */ private String email; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/model/SysRoleAuth.java ================================================ package com.oddfar.campus.common.domain.model; import com.oddfar.campus.common.domain.entity.SysRoleResourceEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class SysRoleAuth { /** * 角色id */ Long roleID; /** * 权限字符 */ String perms; /** * 资源编码 */ String resourceCode; public SysRoleAuth(SysRoleResourceEntity roleResourceEntity) { this.roleID = roleResourceEntity.getRoleId(); this.resourceCode = roleResourceEntity.getResourceCode(); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/model/SysRoleAuthList.java ================================================ package com.oddfar.campus.common.domain.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Set; @Data @AllArgsConstructor @NoArgsConstructor public class SysRoleAuthList { /** * 角色id */ Long roleID; /** * 权限字符 */ Set perms; /** * 资源编码 */ Set resourceCode; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/domain/vo/RouterVo.java ================================================ package com.oddfar.campus.common.domain.vo; import com.fasterxml.jackson.annotation.JsonInclude; import com.oddfar.campus.common.utils.MetaVo; import java.util.List; /** * 路由配置信息 * * @author ruoyi */ @JsonInclude(JsonInclude.Include.NON_EMPTY) public class RouterVo { /** * 路由名字 */ private String name; /** * 路由地址 */ private String path; /** * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 */ private boolean hidden; /** * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 */ private String redirect; /** * 组件地址 */ private String component; /** * 路由参数:如 {"id": 1, "name": "ry"} */ private String query; /** * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 */ private Boolean alwaysShow; /** * 其他元素 */ private MetaVo meta; /** * 子路由 */ private List children; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public boolean getHidden() { return hidden; } public void setHidden(boolean hidden) { this.hidden = hidden; } public String getRedirect() { return redirect; } public void setRedirect(String redirect) { this.redirect = redirect; } public String getComponent() { return component; } public void setComponent(String component) { this.component = component; } public String getQuery() { return query; } public void setQuery(String query) { this.query = query; } public Boolean getAlwaysShow() { return alwaysShow; } public void setAlwaysShow(Boolean alwaysShow) { this.alwaysShow = alwaysShow; } public MetaVo getMeta() { return meta; } public void setMeta(MetaVo meta) { this.meta = meta; } public List getChildren() { return children; } public void setChildren(List children) { this.children = children; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/enums/BizCodeEnum.java ================================================ package com.oddfar.campus.common.enums; /*** * 错误码和错误信息定义类 * 1. 错误码定义规则为5为数字 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式 * 错误码列表: * 10: 通用 * 001:参数格式校验 * * */ public enum BizCodeEnum { UNKNOWN_EXCEPTION(10000, "系统未知错误"), VALID_EXCEPTION(10001, "参数校验异常"); private Integer code; private String msg; private BizCodeEnum(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/enums/BusinessStatus.java ================================================ package com.oddfar.campus.common.enums; /** * 操作状态 * * @author ruoyi */ public enum BusinessStatus { /** * 成功 */ SUCCESS, /** * 失败 */ FAIL, } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/enums/ResBizTypeEnum.java ================================================ package com.oddfar.campus.common.enums; import lombok.Getter; /** * 接口资源的类别 */ @Getter public enum ResBizTypeEnum { /** * 业务类 */ BUSINESS(1, "业务类"), /** * 系统类 */ SYSTEM(2, "系统类"); private final Integer code; private final String message; ResBizTypeEnum(Integer code, String message) { this.code = code; this.message = message; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/enums/SensitiveStrategy.java ================================================ package com.oddfar.campus.common.enums; import cn.hutool.core.util.DesensitizedUtil; import lombok.AllArgsConstructor; import java.util.function.Function; /** * 脱敏策略 * * @author Yjoioooo * @version 3.6.0 */ @AllArgsConstructor public enum SensitiveStrategy { /** * 身份证脱敏 */ ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)), /** * 手机号脱敏 */ PHONE(DesensitizedUtil::mobilePhone), /** * 地址脱敏 */ ADDRESS(s -> DesensitizedUtil.address(s, 8)), /** * 邮箱脱敏 */ EMAIL(DesensitizedUtil::email), /** * 银行卡 */ BANK_CARD(DesensitizedUtil::bankCard); //可自行添加其他脱敏策略 private final Function desensitizer; public Function desensitizer() { return desensitizer; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/enums/UserStatusEnum.java ================================================ package com.oddfar.campus.common.enums; /** * 用户状态 * * @author ruoyi */ public enum UserStatusEnum { OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除"); private final String code; private final String info; UserStatusEnum(String code, String info) { this.code = code; this.info = info; } public String getCode() { return code; } public String getInfo() { return info; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/ServiceException.java ================================================ package com.oddfar.campus.common.exception; /** * 业务异常 * * @author ruoyi */ public final class ServiceException extends RuntimeException { private static final long serialVersionUID = 1L; /** * 错误码 */ private Integer code; /** * 错误提示 */ private String message; /** * 错误明细,内部调试错误 */ private String detailMessage; /** * 空构造方法,避免反序列化问题 */ public ServiceException() { } public ServiceException(String message) { this.message = message; } public ServiceException(String message, Integer code) { this.message = message; this.code = code; } public String getDetailMessage() { return detailMessage; } @Override public String getMessage() { return message; } public Integer getCode() { return code; } public ServiceException setMessage(String message) { this.message = message; return this; } public ServiceException setDetailMessage(String detailMessage) { this.detailMessage = detailMessage; return this; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/UtilException.java ================================================ package com.oddfar.campus.common.exception; /** * 工具类异常 * * @author ruoyi */ public class UtilException extends RuntimeException { private static final long serialVersionUID = 8247610319171014183L; public UtilException(Throwable e) { super(e.getMessage(), e); } public UtilException(String message) { super(message); } public UtilException(String message, Throwable throwable) { super(message, throwable); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/base/BaseException.java ================================================ package com.oddfar.campus.common.exception.base; import com.oddfar.campus.common.utils.MessageUtils; import com.oddfar.campus.common.utils.StringUtils; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 基础异常 * * @author ruoyi */ @Data @AllArgsConstructor @NoArgsConstructor public class BaseException extends RuntimeException { private static final long serialVersionUID = 1L; /** * 所属模块 */ private String module; /** * 错误码 */ private String code; /** * 错误码对应的参数 */ private Object[] args; /** * 错误消息 */ private String defaultMessage; @Override public String getMessage() { String message = null; if (!StringUtils.isEmpty(code)) { message = MessageUtils.message(code, args); } if (message == null) { message = defaultMessage; } return message; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/file/FileException.java ================================================ package com.oddfar.campus.common.exception.file; import com.oddfar.campus.common.exception.base.BaseException; /** * 文件信息异常类 * * @author ruoyi */ public class FileException extends BaseException { private static final long serialVersionUID = 1L; public FileException(String code, Object[] args) { super("file", code, args, null); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/file/FileNameLengthLimitExceededException.java ================================================ package com.oddfar.campus.common.exception.file; /** * 文件名称超长限制异常类 * * @author ruoyi */ public class FileNameLengthLimitExceededException extends FileException { private static final long serialVersionUID = 1L; public FileNameLengthLimitExceededException(int defaultFileNameLength) { super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/file/FileSizeLimitExceededException.java ================================================ package com.oddfar.campus.common.exception.file; /** * 文件名大小限制异常类 * * @author ruoyi */ public class FileSizeLimitExceededException extends FileException { private static final long serialVersionUID = 1L; public FileSizeLimitExceededException(long defaultMaxSize) { super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/file/InvalidExtensionException.java ================================================ package com.oddfar.campus.common.exception.file; import org.apache.commons.fileupload.FileUploadException; import java.util.Arrays; /** * 文件上传 误异常类 * * @author ruoyi */ public class InvalidExtensionException extends FileUploadException { private static final long serialVersionUID = 1L; private String[] allowedExtension; private String extension; private String filename; public InvalidExtensionException(String[] allowedExtension, String extension, String filename) { super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); this.allowedExtension = allowedExtension; this.extension = extension; this.filename = filename; } public String[] getAllowedExtension() { return allowedExtension; } public String getExtension() { return extension; } public String getFilename() { return filename; } public static class InvalidImageExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) { super(allowedExtension, extension, filename); } } public static class InvalidFlashExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) { super(allowedExtension, extension, filename); } } public static class InvalidMediaExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) { super(allowedExtension, extension, filename); } } public static class InvalidVideoExtensionException extends InvalidExtensionException { private static final long serialVersionUID = 1L; public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) { super(allowedExtension, extension, filename); } } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/user/CaptchaException.java ================================================ package com.oddfar.campus.common.exception.user; /** * 验证码错误异常类 * * @author ruoyi */ public class CaptchaException extends UserException { private static final long serialVersionUID = 1L; public CaptchaException() { super("user.jcaptcha.error", null); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/user/CaptchaExpireException.java ================================================ package com.oddfar.campus.common.exception.user; /** * 验证码失效异常类 * * @author ruoyi */ public class CaptchaExpireException extends UserException { private static final long serialVersionUID = 1L; public CaptchaExpireException() { super("user.jcaptcha.expire", null); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/user/UserException.java ================================================ package com.oddfar.campus.common.exception.user; import com.oddfar.campus.common.exception.base.BaseException; /** * 用户信息异常类 * * @author ruoyi */ public class UserException extends BaseException { private static final long serialVersionUID = 1L; public UserException(String code, Object[] args) { super("user", code, args, null); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/user/UserPasswordNotMatchException.java ================================================ package com.oddfar.campus.common.exception.user; /** * 用户密码不正确或不符合规范异常类 * * @author ruoyi */ public class UserPasswordNotMatchException extends UserException { private static final long serialVersionUID = 1L; public UserPasswordNotMatchException() { super("user.password.not.match", null); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/exception/user/UserPasswordRetryLimitExceedException.java ================================================ package com.oddfar.campus.common.exception.user; /** * 用户错误最大次数异常类 * * @author ruoyi */ public class UserPasswordRetryLimitExceedException extends UserException { private static final long serialVersionUID = 1L; public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) { super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/filter/PropertyPreExcludeFilter.java ================================================ package com.oddfar.campus.common.filter; import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; /** * 排除JSON敏感属性 * * @author ruoyi */ public class PropertyPreExcludeFilter extends SimplePropertyPreFilter { public PropertyPreExcludeFilter() { } public PropertyPreExcludeFilter addExcludes(String... filters) { for (int i = 0; i < filters.length; i++) { this.getExcludes().add(filters[i]); } return this; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/filter/RepeatedlyRequestWrapper.java ================================================ package com.oddfar.campus.common.filter; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.utils.http.HttpHelper; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; /** * 构建可重复读取inputStream的request * * @author ruoyi */ public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { super(request); request.setCharacterEncoding(Constants.UTF8); response.setCharacterEncoding(Constants.UTF8); body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public int available() throws IOException { return body.length; } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/DateUtils.java ================================================ package com.oddfar.campus.common.utils; import org.apache.commons.lang3.time.DateFormatUtils; import java.lang.management.ManagementFactory; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.*; import java.util.Date; /** * 时间工具类 * * @author ruoyi */ public class DateUtils extends org.apache.commons.lang3.time.DateUtils { public static String YYYY = "yyyy"; public static String YYYY_MM = "yyyy-MM"; public static String YYYY_MM_DD = "yyyy-MM-dd"; public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; private static String[] parsePatterns = { "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; /** * 获取当前Date型日期 * * @return Date() 当前日期 */ public static Date getNowDate() { return new Date(); } /** * 获取当前日期, 默认格式为yyyy-MM-dd * * @return String */ public static String getDate() { return dateTimeNow(YYYY_MM_DD); } public static final String getTime() { return dateTimeNow(YYYY_MM_DD_HH_MM_SS); } public static final String dateTimeNow() { return dateTimeNow(YYYYMMDDHHMMSS); } public static final String dateTimeNow(final String format) { return parseDateToStr(format, new Date()); } public static final String dateTime(final Date date) { return parseDateToStr(YYYY_MM_DD, date); } public static final String parseDateToStr(final String format, final Date date) { return new SimpleDateFormat(format).format(date); } public static final Date dateTime(final String format, final String ts) { try { return new SimpleDateFormat(format).parse(ts); } catch (ParseException e) { throw new RuntimeException(e); } } /** * 日期路径 即年/月/日 如2018/08/08 */ public static final String datePath() { Date now = new Date(); return DateFormatUtils.format(now, "yyyy/MM/dd"); } /** * 日期路径 即年/月/日 如20180808 */ public static final String dateTime() { Date now = new Date(); return DateFormatUtils.format(now, "yyyyMMdd"); } /** * 日期型字符串转化为日期 格式 */ public static Date parseDate(Object str) { if (str == null) { return null; } try { return parseDate(str.toString(), parsePatterns); } catch (ParseException e) { return null; } } /** * 获取服务器启动时间 */ public static Date getServerStartDate() { long time = ManagementFactory.getRuntimeMXBean().getStartTime(); return new Date(time); } /** * 计算相差天数 */ public static int differentDaysByMillisecond(Date date1, Date date2) { return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); } /** * 计算两个时间差 */ public static String getDatePoor(Date endDate, Date nowDate) { long nd = 1000 * 24 * 60 * 60; long nh = 1000 * 60 * 60; long nm = 1000 * 60; // long ns = 1000; // 获得两个时间的毫秒时间差异 long diff = endDate.getTime() - nowDate.getTime(); // 计算差多少天 long day = diff / nd; // 计算差多少小时 long hour = diff % nd / nh; // 计算差多少分钟 long min = diff % nd % nh / nm; // 计算差多少秒//输出结果 // long sec = diff % nd % nh % nm / ns; return day + "天" + hour + "小时" + min + "分钟"; } /** * 增加 LocalDateTime ==> Date */ public static Date toDate(LocalDateTime temporalAccessor) { ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); return Date.from(zdt.toInstant()); } /** * 增加 LocalDate ==> Date */ public static Date toDate(LocalDate temporalAccessor) { LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); return Date.from(zdt.toInstant()); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/DictUtils.java ================================================ package com.oddfar.campus.common.utils; import com.alibaba.fastjson2.JSONArray; import com.oddfar.campus.common.constant.CacheConstants; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.domain.entity.SysDictDataEntity; import java.util.Collection; import java.util.List; /** * 字典工具类 * */ public class DictUtils { /** * 分隔符 */ public static final String SEPARATOR = ","; /** * 设置字典缓存 * * @param key 参数键 * @param dictDatas 字典数据列表 */ public static void setDictCache(String key, List dictDatas) { SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas); } /** * 获取字典缓存 * * @param key 参数键 * @return dictDatas 字典数据列表 */ public static List getDictCache(String key) { JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); if (StringUtils.isNotNull(arrayCache)) { return arrayCache.toList(SysDictDataEntity.class); } return null; } /** * 根据字典类型和字典值获取字典标签 * * @param dictType 字典类型 * @param dictValue 字典值 * @return 字典标签 */ public static String getDictLabel(String dictType, String dictValue) { return getDictLabel(dictType, dictValue, SEPARATOR); } /** * 根据字典类型和字典标签获取字典值 * * @param dictType 字典类型 * @param dictLabel 字典标签 * @return 字典值 */ public static String getDictValue(String dictType, String dictLabel) { return getDictValue(dictType, dictLabel, SEPARATOR); } /** * 根据字典类型和字典值获取字典标签 * * @param dictType 字典类型 * @param dictValue 字典值 * @param separator 分隔符 * @return 字典标签 */ public static String getDictLabel(String dictType, String dictValue, String separator) { StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); if (StringUtils.isNotNull(datas)) { if (StringUtils.containsAny(separator, dictValue)) { for (SysDictDataEntity dict : datas) { for (String value : dictValue.split(separator)) { if (value.equals(dict.getDictValue())) { propertyString.append(dict.getDictLabel()).append(separator); break; } } } } else { for (SysDictDataEntity dict : datas) { if (dictValue.equals(dict.getDictValue())) { return dict.getDictLabel(); } } } } return StringUtils.stripEnd(propertyString.toString(), separator); } /** * 根据字典类型和字典标签获取字典值 * * @param dictType 字典类型 * @param dictLabel 字典标签 * @param separator 分隔符 * @return 字典值 */ public static String getDictValue(String dictType, String dictLabel, String separator) { StringBuilder propertyString = new StringBuilder(); List datas = getDictCache(dictType); if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) { for (SysDictDataEntity dict : datas) { for (String label : dictLabel.split(separator)) { if (label.equals(dict.getDictLabel())) { propertyString.append(dict.getDictValue()).append(separator); break; } } } } else { for (SysDictDataEntity dict : datas) { if (dictLabel.equals(dict.getDictLabel())) { return dict.getDictValue(); } } } return StringUtils.stripEnd(propertyString.toString(), separator); } /** * 删除指定字典缓存 * * @param key 字典键 */ public static void removeDictCache(String key) { SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key)); } /** * 清空字典缓存 */ public static void clearDictCache() { Collection keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*"); SpringUtils.getBean(RedisCache.class).deleteObject(keys); } /** * 设置cache key * * @param configKey 参数键 * @return 缓存键key */ public static String getCacheKey(String configKey) { return CacheConstants.SYS_DICT_KEY + configKey; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/LogUtils.java ================================================ package com.oddfar.campus.common.utils; /** * 处理并记录日志文件 * * @author ruoyi */ public class LogUtils { public static String getBlock(Object msg) { if (msg == null) { msg = ""; } return "[" + msg.toString() + "]"; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/MessageUtils.java ================================================ package com.oddfar.campus.common.utils; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; /** * 获取i18n资源文件 * * @author ruoyi */ public class MessageUtils { /** * 根据消息键和参数 获取消息 委托给spring messageSource * * @param code 消息键 * @param args 参数 * @return 获取国际化翻译值 */ public static String message(String code, Object... args) { MessageSource messageSource = SpringUtils.getBean(MessageSource.class); return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/MetaVo.java ================================================ package com.oddfar.campus.common.utils; /** * 路由显示信息 * * @author ruoyi */ public class MetaVo { /** * 设置该路由在侧边栏和面包屑中展示的名字 */ private String title; /** * 设置该路由的图标,对应路径src/assets/icons/svg */ private String icon; /** * 设置为true,则不会被 缓存 */ private boolean noCache; /** * 内链地址(http(s)://开头) */ private String link; public MetaVo() { } public MetaVo(String title, String icon) { this.title = title; this.icon = icon; } public MetaVo(String title, String icon, boolean noCache) { this.title = title; this.icon = icon; this.noCache = noCache; } public MetaVo(String title, String icon, String link) { this.title = title; this.icon = icon; this.link = link; } public MetaVo(String title, String icon, boolean noCache, String link) { this.title = title; this.icon = icon; this.noCache = noCache; if (StringUtils.ishttp(link)) { this.link = link; } } public boolean isNoCache() { return noCache; } public void setNoCache(boolean noCache) { this.noCache = noCache; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getIcon() { return icon; } public void setIcon(String icon) { this.icon = icon; } public String getLink() { return link; } public void setLink(String link) { this.link = link; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/SecurityUtils.java ================================================ package com.oddfar.campus.common.utils; import com.oddfar.campus.common.constant.HttpStatus; import com.oddfar.campus.common.constant.UserConstants; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.exception.ServiceException; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** * 安全服务工具类 * * @author ruoyi */ public class SecurityUtils { /** * 用户ID **/ public static Long getUserId() { try { return getLoginUser().getUserId(); } catch (Exception e) { throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); } } /** * 获取用户账户 **/ public static String getUsername() { try { return getLoginUser().getUsername(); } catch (Exception e) { throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); } } /** * 获取用户 **/ public static LoginUser getLoginUser() { try { LoginUser loginUser = (LoginUser) getAuthentication().getPrincipal(); //用户不正常 if (!loginUser.getUser().getStatus().equals(UserConstants.NORMAL)) { throw new ServiceException("用户被禁止", HttpStatus.FORBIDDEN); } return loginUser; } catch (Exception e) { throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); } } /** * 是否登录,true登录 * * @return */ public static boolean isLogin() { Authentication auth = getAuthentication(); if (auth instanceof AnonymousAuthenticationToken) { return false; } else { return true; } } /** * 获取Authentication */ public static Authentication getAuthentication() { return SecurityContextHolder.getContext().getAuthentication(); } /** * 生成BCryptPasswordEncoder密码 * * @param password 密码 * @return 加密字符串 */ public static String encryptPassword(String password) { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.encode(password); } /** * 判断密码是否相同 * * @param rawPassword 真实密码 * @param encodedPassword 加密后字符 * @return 结果 */ public static boolean matchesPassword(String rawPassword, String encodedPassword) { BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder.matches(rawPassword, encodedPassword); } /** * 是否为管理员 * * @param userId 用户ID * @return 结果 */ public static boolean isAdmin(Long userId) { return userId != null && 1L == userId; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/ServletUtils.java ================================================ package com.oddfar.campus.common.utils; import cn.hutool.core.convert.Convert; import com.oddfar.campus.common.constant.Constants; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; /** * 客户端工具类 * * @author ruoyi */ public class ServletUtils { /** * 获取String参数 */ public static String getParameter(String name) { return getRequest().getParameter(name); } /** * 获取String参数 */ public static String getParameter(String name, String defaultValue) { return Convert.toStr(getRequest().getParameter(name), defaultValue); } /** * 获取Integer参数 */ public static Integer getParameterToInt(String name) { return Convert.toInt(getRequest().getParameter(name)); } /** * 获取Integer参数 */ public static Integer getParameterToInt(String name, Integer defaultValue) { return Convert.toInt(getRequest().getParameter(name), defaultValue); } /** * 获取Boolean参数 */ public static Boolean getParameterToBool(String name) { return Convert.toBool(getRequest().getParameter(name)); } /** * 获取Boolean参数 */ public static Boolean getParameterToBool(String name, Boolean defaultValue) { return Convert.toBool(getRequest().getParameter(name), defaultValue); } /** * 获取request */ public static HttpServletRequest getRequest() { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (!(requestAttributes instanceof ServletRequestAttributes)) { return null; } ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; return servletRequestAttributes.getRequest(); } /** * 获取request */ public static HttpServletRequest getRequest2() { return getRequestAttributes().getRequest(); } /** * 获取response */ public static HttpServletResponse getResponse() { return getRequestAttributes().getResponse(); } /** * 获取session */ public static HttpSession getSession() { return getRequest().getSession(); } public static ServletRequestAttributes getRequestAttributes() { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); return (ServletRequestAttributes) attributes; } /** * 将字符串渲染到客户端 * * @param response 渲染对象 * @param string 待渲染的字符串 */ public static void renderString(HttpServletResponse response, String string) { try { response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(string); } catch (IOException e) { e.printStackTrace(); } } /** * 是否是Ajax异步请求 * * @param request */ public static boolean isAjaxRequest(HttpServletRequest request) { String accept = request.getHeader("accept"); if (accept != null && accept.contains("application/json")) { return true; } String xRequestedWith = request.getHeader("X-Requested-With"); if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) { return true; } String uri = request.getRequestURI(); if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) { return true; } String ajax = request.getParameter("__ajax"); return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); } /** * 内容编码 * * @param str 内容 * @return 编码后的内容 */ public static String urlEncode(String str) { try { return URLEncoder.encode(str, Constants.UTF8); } catch (UnsupportedEncodingException e) { return StringUtils.EMPTY; } } /** * 内容解码 * * @param str 内容 * @return 解码后的内容 */ public static String urlDecode(String str) { try { return URLDecoder.decode(str, Constants.UTF8); } catch (UnsupportedEncodingException e) { return StringUtils.EMPTY; } } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/SpringUtils.java ================================================ package com.oddfar.campus.common.utils; import org.springframework.aop.framework.AopContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * spring工具类 方便在非spring管理环境中获取bean * * @author ruoyi */ @Component public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { /** * Spring应用上下文环境 */ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws org.springframework.beans.BeansException */ @SuppressWarnings("unchecked") public static T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws org.springframework.beans.BeansException */ public static T getBean(Class clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** * @param name * @return Class 注册对象的类型 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException */ public static Class getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 * * @param name * @return * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } /** * 获取aop代理对象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } /** * 获取当前的环境配置,无配置返回null * * @return 当前的环境配置 */ public static String[] getActiveProfiles() { return applicationContext.getEnvironment().getActiveProfiles(); } /** * 获取当前的环境配置,当有多个环境配置时,只获取第一个 * * @return 当前的环境配置 */ public static String getActiveProfile() { final String[] activeProfiles = getActiveProfiles(); return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; } /** * 获取配置文件中的值 * * @param key 配置文件的key * @return 当前的配置文件的值 */ public static String getRequiredProperty(String key) { return applicationContext.getEnvironment().getRequiredProperty(key); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/StringUtils.java ================================================ package com.oddfar.campus.common.utils; import cn.hutool.core.text.StrFormatter; import com.oddfar.campus.common.constant.Constants; import org.springframework.util.AntPathMatcher; import java.util.*; /** * 字符串工具类 * * @author ruoyi */ public class StringUtils extends org.apache.commons.lang3.StringUtils { /** * 空字符串 */ private static final String NULLSTR = ""; /** * 下划线 */ private static final char SEPARATOR = '_'; /** * 获取参数不为空值 * * @param value defaultValue 要判断的value * @return value 返回值 */ public static T nvl(T value, T defaultValue) { return value != null ? value : defaultValue; } /** * * 判断一个Collection是否为空, 包含List,Set,Queue * * @param coll 要判断的Collection * @return true:为空 false:非空 */ public static boolean isEmpty(Collection coll) { return isNull(coll) || coll.isEmpty(); } /** * * 判断一个Collection是否非空,包含List,Set,Queue * * @param coll 要判断的Collection * @return true:非空 false:空 */ public static boolean isNotEmpty(Collection coll) { return !isEmpty(coll); } /** * * 判断一个对象数组是否为空 * * @param objects 要判断的对象数组 * * @return true:为空 false:非空 */ public static boolean isEmpty(Object[] objects) { return isNull(objects) || (objects.length == 0); } /** * * 判断一个对象数组是否非空 * * @param objects 要判断的对象数组 * @return true:非空 false:空 */ public static boolean isNotEmpty(Object[] objects) { return !isEmpty(objects); } /** * * 判断一个Map是否为空 * * @param map 要判断的Map * @return true:为空 false:非空 */ public static boolean isEmpty(Map map) { return isNull(map) || map.isEmpty(); } /** * * 判断一个Map是否为空 * * @param map 要判断的Map * @return true:非空 false:空 */ public static boolean isNotEmpty(Map map) { return !isEmpty(map); } /** * * 判断一个字符串是否为空串 * * @param str String * @return true:为空 false:非空 */ public static boolean isEmpty(String str) { return isNull(str) || NULLSTR.equals(str.trim()); } /** * * 判断一个字符串是否为非空串 * * @param str String * @return true:非空串 false:空串 */ public static boolean isNotEmpty(String str) { return !isEmpty(str); } /** * * 判断一个对象是否为空 * * @param object Object * @return true:为空 false:非空 */ public static boolean isNull(Object object) { return object == null; } /** * * 判断一个对象是否非空 * * @param object Object * @return true:非空 false:空 */ public static boolean isNotNull(Object object) { return !isNull(object); } /** * * 判断一个对象是否是数组类型(Java基本型别的数组) * * @param object 对象 * @return true:是数组 false:不是数组 */ public static boolean isArray(Object object) { return isNotNull(object) && object.getClass().isArray(); } /** * 去空格 */ public static String trim(String str) { return (str == null ? "" : str.trim()); } /** * 截取字符串 * * @param str 字符串 * @param start 开始 * @return 结果 */ public static String substring(final String str, int start) { if (str == null) { return NULLSTR; } if (start < 0) { start = str.length() + start; } if (start < 0) { start = 0; } if (start > str.length()) { return NULLSTR; } return str.substring(start); } /** * 截取字符串 * * @param str 字符串 * @param start 开始 * @param end 结束 * @return 结果 */ public static String substring(final String str, int start, int end) { if (str == null) { return NULLSTR; } if (end < 0) { end = str.length() + end; } if (start < 0) { start = str.length() + start; } if (end > str.length()) { end = str.length(); } if (start > end) { return NULLSTR; } if (start < 0) { start = 0; } if (end < 0) { end = 0; } return str.substring(start, end); } /** * 格式化文本, {} 表示占位符
* 此方法只是简单将占位符 {} 按照顺序替换为参数
* 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
* 例:
* 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
* 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
* 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
* * @param template 文本模板,被替换的部分用 {} 表示 * @param params 参数值 * @return 格式化后的文本 */ public static String format(String template, Object... params) { if (isEmpty(params) || isEmpty(template)) { return template; } return StrFormatter.format(template, params); } /** * 是否为http(s)://开头 * * @param link 链接 * @return 结果 */ public static boolean ishttp(String link) { return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); } /** * 字符串转set * * @param str 字符串 * @param sep 分隔符 * @return set集合 */ public static final Set str2Set(String str, String sep) { return new HashSet(str2List(str, sep, true, false)); } /** * 字符串转list * * @param str 字符串 * @param sep 分隔符 * @param filterBlank 过滤纯空白 * @param trim 去掉首尾空白 * @return list集合 */ public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) { List list = new ArrayList(); if (StringUtils.isEmpty(str)) { return list; } // 过滤空白字符串 if (filterBlank && StringUtils.isBlank(str)) { return list; } String[] split = str.split(sep); for (String string : split) { if (filterBlank && StringUtils.isBlank(string)) { continue; } if (trim) { string = string.trim(); } list.add(string); } return list; } /** * 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value * * @param collection 给定的集合 * @param array 给定的数组 * @return boolean 结果 */ public static boolean containsAny(Collection collection, String... array) { if (isEmpty(collection) || isEmpty(array)) { return false; } else { for (String str : array) { if (collection.contains(str)) { return true; } } return false; } } /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 * * @param cs 指定字符串 * @param searchCharSequences 需要检查的字符串数组 * @return 是否包含任意一个字符串 */ public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { if (isEmpty(cs) || isEmpty(searchCharSequences)) { return false; } for (CharSequence testStr : searchCharSequences) { if (containsIgnoreCase(cs, testStr)) { return true; } } return false; } /** * 驼峰转下划线命名 */ public static String toUnderScoreCase(String str) { if (str == null) { return null; } StringBuilder sb = new StringBuilder(); // 前置字符是否大写 boolean preCharIsUpperCase = true; // 当前字符是否大写 boolean curreCharIsUpperCase = true; // 下一字符是否大写 boolean nexteCharIsUpperCase = true; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (i > 0) { preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); } else { preCharIsUpperCase = false; } curreCharIsUpperCase = Character.isUpperCase(c); if (i < (str.length() - 1)) { nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); } if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { sb.append(SEPARATOR); } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { sb.append(SEPARATOR); } sb.append(Character.toLowerCase(c)); } return sb.toString(); } /** * 是否包含字符串 * * @param str 验证字符串 * @param strs 字符串组 * @return 包含返回true */ public static boolean inStringIgnoreCase(String str, String... strs) { if (str != null && strs != null) { for (String s : strs) { if (str.equalsIgnoreCase(trim(s))) { return true; } } } return false; } /** * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld * * @param name 转换前的下划线大写方式命名的字符串 * @return 转换后的驼峰式命名的字符串 */ public static String convertToCamelCase(String name) { StringBuilder result = new StringBuilder(); // 快速检查 if (name == null || name.isEmpty()) { // 没必要转换 return ""; } else if (!name.contains("_")) { // 不含下划线,仅将首字母大写 return name.substring(0, 1).toUpperCase() + name.substring(1); } // 用下划线将原始字符串分割 String[] camels = name.split("_"); for (String camel : camels) { // 跳过原始字符串中开头、结尾的下换线或双重下划线 if (camel.isEmpty()) { continue; } // 首字母大写 result.append(camel.substring(0, 1).toUpperCase()); result.append(camel.substring(1).toLowerCase()); } return result.toString(); } /** * 驼峰式命名法 例如:user_name->userName */ public static String toCamelCase(String s) { if (s == null) { return null; } s = s.toLowerCase(); StringBuilder sb = new StringBuilder(s.length()); boolean upperCase = false; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == SEPARATOR) { upperCase = true; } else if (upperCase) { sb.append(Character.toUpperCase(c)); upperCase = false; } else { sb.append(c); } } return sb.toString(); } /** * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 * * @param str 指定字符串 * @param strs 需要检查的字符串数组 * @return 是否匹配 */ public static boolean matches(String str, List strs) { if (isEmpty(str) || isEmpty(strs)) { return false; } for (String pattern : strs) { if (isMatch(pattern, str)) { return true; } } return false; } /** * 判断url是否与规则配置: * ? 表示单个字符; * * 表示一层路径内的任意字符串,不可跨层级; * ** 表示任意层路径; * * @param pattern 匹配规则 * @param url 需要匹配的url * @return */ public static boolean isMatch(String pattern, String url) { AntPathMatcher matcher = new AntPathMatcher(); return matcher.match(pattern, url); } @SuppressWarnings("unchecked") public static T cast(Object obj) { return (T) obj; } /** * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 * * @param num 数字对象 * @param size 字符串指定长度 * @return 返回数字的字符串格式,该字符串为指定长度。 */ public static final String padl(final Number num, final int size) { return padl(num.toString(), size, '0'); } /** * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 * * @param s 原始字符串 * @param size 字符串指定长度 * @param c 用于补齐的字符 * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 */ public static final String padl(final String s, final int size, final char c) { final StringBuilder sb = new StringBuilder(size); if (s != null) { final int len = s.length(); if (s.length() <= size) { for (int i = size - len; i > 0; i--) { sb.append(c); } sb.append(s); } else { return s.substring(len - size, len); } } else { for (int i = size; i > 0; i--) { sb.append(c); } } return sb.toString(); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/Threads.java ================================================ package com.oddfar.campus.common.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.*; /** * 线程相关工具类. * * @author ruoyi */ public class Threads { private static final Logger logger = LoggerFactory.getLogger(Threads.class); /** * sleep等待,单位为毫秒 */ public static void sleep(long milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException e) { return; } } /** * 停止线程池 * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. * 如果仍然超時,則強制退出. * 另对在shutdown时线程本身被调用中断做了处理. */ public static void shutdownAndAwaitTermination(ExecutorService pool) { if (pool != null && !pool.isShutdown()) { pool.shutdown(); try { if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { pool.shutdownNow(); if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { logger.info("Pool did not terminate"); } } } catch (InterruptedException ie) { pool.shutdownNow(); Thread.currentThread().interrupt(); } } } /** * 打印线程异常信息 */ public static void printException(Runnable r, Throwable t) { if (t == null && r instanceof Future) { try { Future future = (Future) r; if (future.isDone()) { future.get(); } } catch (CancellationException ce) { t = ce; } catch (ExecutionException ee) { t = ee.getCause(); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } if (t != null) { logger.error(t.getMessage(), t); } } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/http/HttpHelper.java ================================================ package com.oddfar.campus.common.utils.http; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; /** * 通用http工具封装 * * @author ruoyi */ public class HttpHelper { private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); public static String getBodyString(ServletRequest request) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; try (InputStream inputStream = request.getInputStream()) { reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { LOGGER.warn("getBodyString出现问题!"); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { LOGGER.error(ExceptionUtils.getMessage(e)); } } } return sb.toString(); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/http/HttpUtils.java ================================================ package com.oddfar.campus.common.utils.http; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.*; import java.io.*; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; /** * 通用http发送方法 * * @author ruoyi */ public class HttpUtils { private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); /** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @return 所代表远程资源的响应结果 */ public static String sendGet(String url) { return sendGet(url, StringUtils.EMPTY); } /** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { return sendGet(url, param, Constants.UTF8); } /** * 向指定 URL 发送GET方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @param contentType 编码类型 * @return 所代表远程资源的响应结果 */ public static String sendGet(String url, String param, String contentType) { StringBuilder result = new StringBuilder(); BufferedReader in = null; try { String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; log.info("sendGet - {}", urlNameString); URL realUrl = new URL(urlNameString); URLConnection connection = realUrl.openConnection(); connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); connection.connect(); in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); String line; while ((line = in.readLine()) != null) { result.append(line); } log.info("recv - {}", result); } catch (ConnectException e) { log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); } finally { try { if (in != null) { in.close(); } } catch (Exception ex) { log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); } } return result.toString(); } /** * 向指定 URL 发送POST方法的请求 * * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param) { PrintWriter out = null; BufferedReader in = null; StringBuilder result = new StringBuilder(); try { log.info("sendPost - {}", url); URL realUrl = new URL(url); URLConnection conn = realUrl.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("contentType", "utf-8"); conn.setDoOutput(true); conn.setDoInput(true); out = new PrintWriter(conn.getOutputStream()); out.print(param); out.flush(); in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); String line; while ((line = in.readLine()) != null) { result.append(line); } log.info("recv - {}", result); } catch (ConnectException e) { log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); } finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); } } return result.toString(); } public static String sendSSLPost(String url, String param) { StringBuilder result = new StringBuilder(); String urlNameString = url + "?" + param; try { log.info("sendSSLPost - {}", urlNameString); SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); URL console = new URL(urlNameString); HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("contentType", "utf-8"); conn.setDoOutput(true); conn.setDoInput(true); conn.setSSLSocketFactory(sc.getSocketFactory()); conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); conn.connect(); InputStream is = conn.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is)); String ret = ""; while ((ret = br.readLine()) != null) { if (ret != null && !"".equals(ret.trim())) { result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); } } log.info("recv - {}", result); conn.disconnect(); br.close(); } catch (ConnectException e) { log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); } catch (SocketTimeoutException e) { log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); } catch (IOException e) { log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); } catch (Exception e) { log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); } return result.toString(); } private static class TrustAnyTrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[] {}; } } private static class TrustAnyHostnameVerifier implements HostnameVerifier { @Override public boolean verify(String hostname, SSLSession session) { return true; } } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/ip/AddressUtils.java ================================================ package com.oddfar.campus.common.utils.ip; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.http.HttpUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 获取地址类 * * @author ruoyi */ public class AddressUtils { private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); // IP地址查询 public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; // 未知地址 public static final String UNKNOWN = "XX XX"; public static String getRealAddressByIP(String ip) { // 内网不查询 if (IpUtils.internalIp(ip)) { return "内网IP"; } // if (RuoYiConfig.isAddressEnabled()) { try { String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK); if (StringUtils.isEmpty(rspStr)) { log.error("获取地理位置异常 {}", ip); return UNKNOWN; } JSONObject obj = JSON.parseObject(rspStr); String region = obj.getString("pro"); String city = obj.getString("city"); return String.format("%s %s", region, city); } catch (Exception e) { log.error("获取地理位置异常 {}", ip); } } return UNKNOWN; } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/ip/IpUtils.java ================================================ package com.oddfar.campus.common.utils.ip; import com.oddfar.campus.common.utils.StringUtils; import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.UnknownHostException; /** * 获取IP方法 * * @author ruoyi */ public class IpUtils { /** * 获取客户端IP * * @param request 请求对象 * @return IP地址 */ public static String getIpAddr(HttpServletRequest request) { if (request == null) { return "unknown"; } String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Forwarded-For"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("X-Real-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); } /** * 检查是否为内部IP地址 * * @param ip IP地址 * @return 结果 */ public static boolean internalIp(String ip) { byte[] addr = textToNumericFormatV4(ip); return internalIp(addr) || "127.0.0.1".equals(ip); } /** * 检查是否为内部IP地址 * * @param addr byte地址 * @return 结果 */ private static boolean internalIp(byte[] addr) { if (StringUtils.isNull(addr) || addr.length < 2) { return true; } final byte b0 = addr[0]; final byte b1 = addr[1]; // 10.x.x.x/8 final byte SECTION_1 = 0x0A; // 172.16.x.x/12 final byte SECTION_2 = (byte) 0xAC; final byte SECTION_3 = (byte) 0x10; final byte SECTION_4 = (byte) 0x1F; // 192.168.x.x/16 final byte SECTION_5 = (byte) 0xC0; final byte SECTION_6 = (byte) 0xA8; switch (b0) { case SECTION_1: return true; case SECTION_2: if (b1 >= SECTION_3 && b1 <= SECTION_4) { return true; } case SECTION_5: switch (b1) { case SECTION_6: return true; } default: return false; } } /** * 将IPv4地址转换成字节 * * @param text IPv4地址 * @return byte 字节 */ public static byte[] textToNumericFormatV4(String text) { if (text.length() == 0) { return null; } byte[] bytes = new byte[4]; String[] elements = text.split("\\.", -1); try { long l; int i; switch (elements.length) { case 1: l = Long.parseLong(elements[0]); if ((l < 0L) || (l > 4294967295L)) { return null; } bytes[0] = (byte) (int) (l >> 24 & 0xFF); bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 2: l = Integer.parseInt(elements[0]); if ((l < 0L) || (l > 255L)) { return null; } bytes[0] = (byte) (int) (l & 0xFF); l = Integer.parseInt(elements[1]); if ((l < 0L) || (l > 16777215L)) { return null; } bytes[1] = (byte) (int) (l >> 16 & 0xFF); bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 3: for (i = 0; i < 2; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)) { return null; } bytes[i] = (byte) (int) (l & 0xFF); } l = Integer.parseInt(elements[2]); if ((l < 0L) || (l > 65535L)) { return null; } bytes[2] = (byte) (int) (l >> 8 & 0xFF); bytes[3] = (byte) (int) (l & 0xFF); break; case 4: for (i = 0; i < 4; ++i) { l = Integer.parseInt(elements[i]); if ((l < 0L) || (l > 255L)) { return null; } bytes[i] = (byte) (int) (l & 0xFF); } break; default: return null; } } catch (NumberFormatException e) { return null; } return bytes; } /** * 获取IP地址 * * @return 本地IP地址 */ public static String getHostIp() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { } return "127.0.0.1"; } /** * 获取主机名 * * @return 本地主机名 */ public static String getHostName() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { } return "未知"; } /** * 从多级反向代理中获得第一个非unknown IP地址 * * @param ip 获得的IP地址 * @return 第一个非unknown IP地址 */ public static String getMultistageReverseProxyIp(String ip) { // 多级反向代理检测 if (ip != null && ip.indexOf(",") > 0) { final String[] ips = ip.trim().split(","); for (String subIp : ips) { if (false == isUnknown(subIp)) { ip = subIp; break; } } } return ip; } /** * 检测给定字符串是否为未知,多用于检测HTTP请求相关 * * @param checkString 被检测的字符串 * @return 是否未知 */ public static boolean isUnknown(String checkString) { return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/pol/ExcelHandlerAdapter.java ================================================ package com.oddfar.campus.common.utils.pol; /** * Excel数据格式处理适配器 * * @author ruoyi */ public interface ExcelHandlerAdapter { /** * 格式化 * * @param value 单元格数据值 * @param args excel注解args参数组 * @return 处理后的值 */ Object format(Object value, String[] args); } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/reflect/ReflectUtils.java ================================================ package com.oddfar.campus.common.utils.reflect; import com.oddfar.campus.common.core.text.Convert; import com.oddfar.campus.common.utils.DateUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.poi.ss.usermodel.DateUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.*; import java.util.Date; /** * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. * * @author ruoyi */ @SuppressWarnings("rawtypes") public class ReflectUtils { private static final String SETTER_PREFIX = "set"; private static final String GETTER_PREFIX = "get"; private static final String CGLIB_CLASS_SEPARATOR = "$$"; private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); /** * 调用Getter方法. * 支持多级,如:对象名.对象名.方法 */ @SuppressWarnings("unchecked") public static E invokeGetter(Object obj, String propertyName) { Object object = obj; for (String name : StringUtils.split(propertyName, ".")) { String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); object = invokeMethod(object, getterMethodName, new Class[]{}, new Object[]{}); } return (E) object; } /** * 调用Setter方法, 仅匹配方法名。 * 支持多级,如:对象名.对象名.方法 */ public static void invokeSetter(Object obj, String propertyName, E value) { Object object = obj; String[] names = StringUtils.split(propertyName, "."); for (int i = 0; i < names.length; i++) { if (i < names.length - 1) { String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); object = invokeMethod(object, getterMethodName, new Class[]{}, new Object[]{}); } else { String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); invokeMethodByName(object, setterMethodName, new Object[]{value}); } } } /** * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. */ @SuppressWarnings("unchecked") public static E getFieldValue(final Object obj, final String fieldName) { Field field = getAccessibleField(obj, fieldName); if (field == null) { logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); return null; } E result = null; try { result = (E) field.get(obj); } catch (IllegalAccessException e) { logger.error("不可能抛出的异常{}", e.getMessage()); } return result; } /** * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. */ public static void setFieldValue(final Object obj, final String fieldName, final E value) { Field field = getAccessibleField(obj, fieldName); if (field == null) { // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); return; } try { field.set(obj, value); } catch (IllegalAccessException e) { logger.error("不可能抛出的异常: {}", e.getMessage()); } } /** * 直接调用对象方法, 无视private/protected修饰符. * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. * 同时匹配方法名+参数类型, */ @SuppressWarnings("unchecked") public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, final Object[] args) { if (obj == null || methodName == null) { return null; } Method method = getAccessibleMethod(obj, methodName, parameterTypes); if (method == null) { logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); return null; } try { return (E) method.invoke(obj, args); } catch (Exception e) { String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; throw convertReflectionExceptionToUnchecked(msg, e); } } /** * 直接调用对象方法, 无视private/protected修饰符, * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. * 只匹配函数名,如果有多个同名函数调用第一个。 */ @SuppressWarnings("unchecked") public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) { Method method = getAccessibleMethodByName(obj, methodName, args.length); if (method == null) { // 如果为空不报错,直接返回空。 logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); return null; } try { // 类型转换(将参数数据类型转换为目标方法参数类型) Class[] cs = method.getParameterTypes(); for (int i = 0; i < cs.length; i++) { if (args[i] != null && !args[i].getClass().equals(cs[i])) { if (cs[i] == String.class) { args[i] = Convert.toStr(args[i]); if (StringUtils.endsWith((String) args[i], ".0")) { args[i] = StringUtils.substringBefore((String) args[i], ".0"); } } else if (cs[i] == Integer.class) { args[i] = Convert.toInt(args[i]); } else if (cs[i] == Long.class) { args[i] = Convert.toLong(args[i]); } else if (cs[i] == Double.class) { args[i] = Convert.toDouble(args[i]); } else if (cs[i] == Float.class) { args[i] = Convert.toFloat(args[i]); } else if (cs[i] == Date.class) { if (args[i] instanceof String) { args[i] = DateUtils.parseDate(args[i]); } else { args[i] = DateUtil.getJavaDate((Double) args[i]); } } else if (cs[i] == boolean.class || cs[i] == Boolean.class) { args[i] = Convert.toBool(args[i]); } } } return (E) method.invoke(obj, args); } catch (Exception e) { String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; throw convertReflectionExceptionToUnchecked(msg, e); } } /** * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. * 如向上转型到Object仍无法找到, 返回null. */ public static Field getAccessibleField(final Object obj, final String fieldName) { // 为空不报错。直接返回 null if (obj == null) { return null; } Validate.notBlank(fieldName, "fieldName can't be blank"); for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) { try { Field field = superClass.getDeclaredField(fieldName); makeAccessible(field); return field; } catch (NoSuchFieldException e) { continue; } } return null; } /** * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. * 如向上转型到Object仍无法找到, 返回null. * 匹配函数名+参数类型。 * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) */ public static Method getAccessibleMethod(final Object obj, final String methodName, final Class... parameterTypes) { // 为空不报错。直接返回 null if (obj == null) { return null; } Validate.notBlank(methodName, "methodName can't be blank"); for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { try { Method method = searchType.getDeclaredMethod(methodName, parameterTypes); makeAccessible(method); return method; } catch (NoSuchMethodException e) { continue; } } return null; } /** * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. * 如向上转型到Object仍无法找到, 返回null. * 只匹配函数名。 * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) */ public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) { // 为空不报错。直接返回 null if (obj == null) { return null; } Validate.notBlank(methodName, "methodName can't be blank"); for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) { Method[] methods = searchType.getDeclaredMethods(); for (Method method : methods) { if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) { makeAccessible(method); return method; } } } return null; } /** * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 */ public static void makeAccessible(Method method) { if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { method.setAccessible(true); } } /** * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 */ public static void makeAccessible(Field field) { if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { field.setAccessible(true); } } /** * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 * 如无法找到, 返回Object.class. */ @SuppressWarnings("unchecked") public static Class getClassGenricType(final Class clazz) { return getClassGenricType(clazz, 0); } /** * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. * 如无法找到, 返回Object.class. */ public static Class getClassGenricType(final Class clazz, final int index) { Type genType = clazz.getGenericSuperclass(); if (!(genType instanceof ParameterizedType)) { logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); return Object.class; } Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); if (index >= params.length || index < 0) { logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + params.length); return Object.class; } if (!(params[index] instanceof Class)) { logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); return Object.class; } return (Class) params[index]; } public static Class getUserClass(Object instance) { if (instance == null) { throw new RuntimeException("Instance must not be null"); } Class clazz = instance.getClass(); if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { Class superClass = clazz.getSuperclass(); if (superClass != null && !Object.class.equals(superClass)) { return superClass; } } return clazz; } /** * 将反射时的checked exception转换为unchecked exception. */ public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) { if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException || e instanceof NoSuchMethodException) { return new IllegalArgumentException(msg, e); } else if (e instanceof InvocationTargetException) { return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); } return new RuntimeException(msg, e); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/spring/AopTargetUtils.java ================================================ /* * Copyright [2020-2030] [https://www.stylefeng.cn] * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Guns采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点: * * 1.请不要删除和修改根目录下的LICENSE文件。 * 2.请不要删除和修改Guns源码头部的版权声明。 * 3.请保留源码和相关描述文件的项目出处,作者声明等。 * 4.分发源码时候,请注明软件出处 https://gitee.com/stylefeng/guns * 5.在修改包名,模块名称,项目代码等时,请注明软件出处 https://gitee.com/stylefeng/guns * 6.若您的项目无法满足以上几点,可申请商业授权 */ package com.oddfar.campus.common.utils.spring; import lombok.extern.slf4j.Slf4j; import org.springframework.aop.framework.AdvisedSupport; import org.springframework.aop.framework.AopProxy; import org.springframework.aop.support.AopUtils; import java.lang.reflect.Field; /** * 获取代理原始对象的工具 * * @author fengshuonan * @date 2020/10/19 16:20 */ @Slf4j public class AopTargetUtils { /** * 获取代理对象的原始对象 * * @author fengshuonan * @date 2020/10/19 16:21 */ public static Object getTarget(Object proxy) { // 不是代理对象,直接返回参数对象 if (!AopUtils.isAopProxy(proxy)) { return proxy; } // 判断是否是jdk还是cglib代理的对象 try { if (AopUtils.isJdkDynamicProxy(proxy)) { return getJdkDynamicProxyTargetObject(proxy); } else { return getCglibProxyTargetObject(proxy); } } catch (Exception e) { log.error("获取代理对象异常", e); return null; } } /** * 获取cglib代理的对象 * * @author fengshuonan * @date 2020/10/19 16:21 */ 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(); } /** * 获取jdk代理的对象 * * @author fengshuonan * @date 2020/10/19 16:22 */ 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(); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/sql/SqlUtil.java ================================================ package com.oddfar.campus.common.utils.sql; import cn.hutool.core.exceptions.UtilException; import com.oddfar.campus.common.utils.StringUtils; /** * sql操作工具类 * * @author ruoyi */ public class SqlUtil { /** * 定义常用的 sql关键字 */ public static String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare "; /** * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) */ public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; /** * 检查字符,防止注入绕过 */ public static String escapeOrderBySql(String value) { if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { throw new UtilException("参数不符合规范,不能进行查询"); } return value; } /** * 验证 order by 语法是否符合规范 */ public static boolean isValidOrderBySql(String value) { return value.matches(SQL_PATTERN); } /** * SQL关键字检查 */ public static void filterKeyword(String value) { if (StringUtils.isEmpty(value)) { return; } String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); for (String sqlKeyword : sqlKeywords) { if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { throw new UtilException("参数存在SQL注入风险"); } } } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/uuid/IdUtils.java ================================================ package com.oddfar.campus.common.utils.uuid; import cn.hutool.core.lang.UUID; /** * ID生成器工具类 * * @author ruoyi */ public class IdUtils { /** * 获取随机UUID * * @return 随机UUID */ public static String randomUUID() { return UUID.randomUUID().toString(); } /** * 简化的UUID,去掉了横线 * * @return 简化的UUID,去掉了横线 */ public static String simpleUUID() { return UUID.randomUUID().toString(true); } /** * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID * * @return 随机UUID */ public static String fastUUID() { return UUID.fastUUID().toString(); } /** * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID * * @return 简化的UUID,去掉了横线 */ public static String fastSimpleUUID() { return UUID.fastUUID().toString(true); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/uuid/Seq.java ================================================ package com.oddfar.campus.common.utils.uuid; import com.oddfar.campus.common.utils.DateUtils; import com.oddfar.campus.common.utils.StringUtils; import java.util.concurrent.atomic.AtomicInteger; /** * @author ruoyi 序列生成类 */ public class Seq { // 通用序列类型 public static final String commSeqType = "COMMON"; // 上传序列类型 public static final String uploadSeqType = "UPLOAD"; // 通用接口序列数 private static AtomicInteger commSeq = new AtomicInteger(1); // 上传接口序列数 private static AtomicInteger uploadSeq = new AtomicInteger(1); // 机器标识 private static String machineCode = "A"; /** * 获取通用序列号 * * @return 序列值 */ public static String getId() { return getId(commSeqType); } /** * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 * * @return 序列值 */ public static String getId(String type) { AtomicInteger atomicInt = commSeq; if (uploadSeqType.equals(type)) { atomicInt = uploadSeq; } return getId(atomicInt, 3); } /** * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 * * @param atomicInt 序列数 * @param length 数值长度 * @return 序列值 */ public static String getId(AtomicInteger atomicInt, int length) { String result = DateUtils.dateTimeNow(); result += machineCode; result += getSeq(atomicInt, length); return result; } /** * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 * * @return 序列值 */ private synchronized static String getSeq(AtomicInteger atomicInt, int length) { // 先取值再+1 int value = atomicInt.getAndIncrement(); // 如果更新后值>=10 的 (length)幂次方则重置为1 int maxSeq = (int) Math.pow(10, length); if (atomicInt.get() >= maxSeq) { atomicInt.set(1); } // 转字符串,用0左补齐 return StringUtils.padl(value, length); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/utils/web/WebFrameworkUtils.java ================================================ package com.oddfar.campus.common.utils.web; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; /** * 专属于 web 包的工具类 * 参考 ruoyi-pro 芋道源码 * * @author oddfar */ public class WebFrameworkUtils { private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = "login_user_id"; public static void setLoginUserId(ServletRequest request, Long userId) { request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId); } /** * 获得当前用户的编号,从请求中 * 注意:该方法仅限于 framework 框架使用!!! @SecurityUtils.getUserId() * * @param request 请求 * @return 用户编号 */ public static Long getLoginUserId(HttpServletRequest request) { if (request == null) { return null; } return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID); } } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/validator/Xss.java ================================================ package com.oddfar.campus.common.validator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义xss校验注解 * * @author ruoyi */ @Retention(RetentionPolicy.RUNTIME) @Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) @Constraint(validatedBy = { XssValidator.class }) public @interface Xss { String message() default "不允许任何脚本运行"; Class[] groups() default {}; Class[] payload() default {}; } ================================================ FILE: campus-common/src/main/java/com/oddfar/campus/common/validator/XssValidator.java ================================================ package com.oddfar.campus.common.validator; import com.oddfar.campus.common.utils.StringUtils; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 自定义xss校验注解实现 * * @author ruoyi */ public class XssValidator implements ConstraintValidator { private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; @Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { if (StringUtils.isBlank(value)) { return true; } return !containsHtml(value); } public static boolean containsHtml(String value) { Pattern pattern = Pattern.compile(HTML_PATTERN); Matcher matcher = pattern.matcher(value); return matcher.matches(); } } ================================================ FILE: campus-framework/pom.xml ================================================ campus com.oddfar.campus ${revision} 4.0.0 campus-framework 8 8 com.oddfar.campus campus-common org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop com.github.penggle kaptcha javax.servlet-api javax.servlet mysql mysql-connector-java com.sun.mail javax.mail ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/file/FileOperatorApi.java ================================================ package com.oddfar.campus.framework.api.file; import org.springframework.web.multipart.MultipartFile; /** * 文件操作api * (后期整合一下阿里云 腾讯云) * * @author oddfar */ public interface FileOperatorApi { void storageFile(String bucketName, MultipartFile file, String[] allowedExtension); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/file/FileUploadUtils.java ================================================ package com.oddfar.campus.framework.api.file; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.exception.file.FileNameLengthLimitExceededException; import com.oddfar.campus.common.exception.file.FileSizeLimitExceededException; import com.oddfar.campus.common.exception.file.InvalidExtensionException; import com.oddfar.campus.common.utils.DateUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.uuid.Seq; import com.oddfar.campus.framework.api.sysconfig.ConfigExpander; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.nio.file.Paths; import java.util.Objects; /** * 文件上传工具类 * * @author ruoyi */ public class FileUploadUtils { private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class); /** * 默认大小 50M */ public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024; /** * 默认的文件名最大长度 100 */ public static final int DEFAULT_FILE_NAME_LENGTH = 100; /** * 默认上传的地址 */ private static String defaultBaseDir = ConfigExpander.getFileProfile(); public static void setDefaultBaseDir(String defaultBaseDir) { FileUploadUtils.defaultBaseDir = defaultBaseDir; } public static String getDefaultBaseDir() { return defaultBaseDir; } /** * 以默认配置进行文件上传 * * @param file 上传的文件 * @return 文件名称 * @throws Exception */ public static final String upload(MultipartFile file) throws IOException { try { return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); } catch (Exception e) { throw new IOException(e.getMessage(), e); } } /** * 根据文件路径上传 * * @param baseDir 相对应用的基目录 * @param file 上传的文件 * @return 文件名称 * @throws IOException */ public static final String upload(String baseDir, MultipartFile file) throws IOException { try { return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); } catch (Exception e) { throw new IOException(e.getMessage(), e); } } /** * 文件上传 * * @param baseDir 相对应用的基目录 * @param file 上传的文件 * @param allowedExtension 上传文件类型 * @return 返回上传成功的文件名 * @throws FileSizeLimitExceededException 如果超出最大大小 * @throws FileNameLengthLimitExceededException 文件名太长 * @throws IOException 比如读写文件出错时 * @throws InvalidExtensionException 文件校验异常 */ public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException { int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); } assertAllowed(file, allowedExtension); String fileName = extractFilename(file); String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); file.transferTo(Paths.get(absPath)); return getPathFileName(baseDir, fileName); } /** * 编码文件名 */ public static final String extractFilename(MultipartFile file) { return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); } public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { File desc = new File(uploadDir + File.separator + fileName); if (!desc.exists()) { if (!desc.getParentFile().exists()) { desc.getParentFile().mkdirs(); } } return desc; } public static final String getPathFileName(String uploadDir, String fileName) throws IOException { int dirLastIndex = ConfigExpander.getFileProfile().length() + 1; String currentDir = StringUtils.substring(uploadDir, dirLastIndex); return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; } /** * 文件大小校验 * * @param file 上传的文件 * @return * @throws FileSizeLimitExceededException 如果超出最大大小 * @throws InvalidExtensionException */ public static final void assertAllowed(MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, InvalidExtensionException { long size = file.getSize(); if (size > DEFAULT_MAX_SIZE) { throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); } String fileName = file.getOriginalFilename(); String extension = getExtension(file); if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, fileName); } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) { throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, fileName); } else { throw new InvalidExtensionException(allowedExtension, extension, fileName); } } } /** * 判断MIME类型是否是允许的MIME类型 * * @param extension * @param allowedExtension * @return */ public static final boolean isAllowedExtension(String extension, String[] allowedExtension) { for (String str : allowedExtension) { if (str.equalsIgnoreCase(extension)) { return true; } } return false; } /** * 获取文件名的后缀 * * @param file 表单文件 * @return 后缀名 */ public static final String getExtension(MultipartFile file) { String extension = FilenameUtils.getExtension(file.getOriginalFilename()); if (StringUtils.isEmpty(extension)) { extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); } return extension; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/file/LocalFileOperator.java ================================================ package com.oddfar.campus.framework.api.file; import org.springframework.web.multipart.MultipartFile; public class LocalFileOperator implements FileOperatorApi { private String currentSavePath; public LocalFileOperator(String currentSavePath) { this.currentSavePath = currentSavePath; // initClient(); } @Override public void storageFile(String bucketName, MultipartFile file, String[] allowedExtension) { } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/file/MimeTypeUtils.java ================================================ package com.oddfar.campus.framework.api.file; /** * 媒体类型工具类 * * @author ruoyi */ public class MimeTypeUtils { public static final String IMAGE_PNG = "image/png"; public static final String IMAGE_JPG = "image/jpg"; public static final String IMAGE_JPEG = "image/jpeg"; public static final String IMAGE_BMP = "image/bmp"; public static final String IMAGE_GIF = "image/gif"; public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"}; public static final String[] FLASH_EXTENSION = {"swf", "flv"}; public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", "asf", "rm", "rmvb"}; public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"}; public static final String[] IMAGE_VIDEO_EXTENSION = { // 图片 "bmp", "gif", "jpg", "jpeg", "png", // 视频格式 "mp4", "avi", "rmvb", }; public static final String[] DEFAULT_ALLOWED_EXTENSION = { // 图片 "bmp", "gif", "jpg", "jpeg", "png", // word excel powerpoint "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", // 压缩文件 "rar", "zip", "gz", "bz2", // 视频格式 "mp4", "avi", "rmvb", // pdf "pdf"}; public static String getExtension(String prefix) { switch (prefix) { case IMAGE_PNG: return "png"; case IMAGE_JPG: return "jpg"; case IMAGE_JPEG: return "jpeg"; case IMAGE_BMP: return "bmp"; case IMAGE_GIF: return "gif"; default: return ""; } } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/file/ZyFileAutoConfiguration.java ================================================ package com.oddfar.campus.framework.api.file; import com.oddfar.campus.framework.api.sysconfig.ConfigExpander; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 文件的自动配置 */ @Configuration public class ZyFileAutoConfiguration { /** * 本地文件操作 */ @Bean @ConditionalOnMissingBean(FileOperatorApi.class) public FileOperatorApi fileOperatorApi() { return new LocalFileOperator(ConfigExpander.getFileProfile()); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/Impl/MailServiceImpl.java ================================================ package com.oddfar.campus.framework.api.mail.Impl; import cn.hutool.extra.mail.MailAccount; import cn.hutool.extra.mail.MailUtil; import com.oddfar.campus.framework.api.mail.MailConfigRead; import com.oddfar.campus.framework.api.mail.MailSendApi; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.List; @Service public class MailServiceImpl implements MailSendApi { @Async//异步发送邮件 @Override public void sendQQMail(List tos, String subject, String content, Boolean isHtml) { String host = MailConfigRead.getSmtpHost(); String port = MailConfigRead.getSmtpPort(); String account = MailConfigRead.getSendAccount(); String password = MailConfigRead.getPassword(); MailAccount mailAccount = new MailAccount(); mailAccount.setHost(host); mailAccount.setPort(Integer.parseInt(port)); mailAccount.setAuth(true); mailAccount.setFrom(account); mailAccount.setPass(password); //在使用QQ或Gmail邮箱时,需要强制开启SSL支持 mailAccount.setSslEnable(true); MailUtil.send(mailAccount, tos, subject, content, isHtml); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/MailConfigRead.java ================================================ package com.oddfar.campus.framework.api.mail; import com.oddfar.campus.framework.api.sysconfig.ConfigContext; /** * 读取邮箱的配置 * * @author oddfar */ public class MailConfigRead { public static String getSmtpHost() { return ConfigContext.me().selectConfigByKey("sys.email.smtp.host", String.class); } public static String getSmtpPort() { return ConfigContext.me().selectConfigByKey("sys.email.smtp.port", String.class); } public static String getSendAccount() { return ConfigContext.me().selectConfigByKey("sys.email.send.account", String.class); } public static String getPassword() { return ConfigContext.me().selectConfigByKey("sys.email.password", String.class); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/MailSendApi.java ================================================ package com.oddfar.campus.framework.api.mail; import java.util.List; /** * 发送邮件Api * (后期整合下阿里云 腾讯云) * * @author oddfar */ public interface MailSendApi { /** * 以发送QQ邮箱为例子 * * @param tos 对方的邮箱地址,可以是单个,也可以是多个(Collection表示) * @param subject 标题 * @param content 邮件正文,可以是文本,也可以是HTML内容 * @param isHtml 是否为HTML,如果是,那参数content识别为HTML内容 */ void sendQQMail(List tos, String subject, String content, Boolean isHtml); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/MailSendContext.java ================================================ package com.oddfar.campus.framework.api.mail; import cn.hutool.extra.spring.SpringUtil; /** * 邮件发送的api上下文 * * @author oddfar */ public class MailSendContext { /** * 获取邮件发送的接口 */ public static MailSendApi me() { return SpringUtil.getBean(MailSendApi.class); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/mail/ZyMailAutoConfig.java ================================================ package com.oddfar.campus.framework.api.mail; import com.oddfar.campus.framework.api.mail.Impl.MailServiceImpl; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ZyMailAutoConfig { /** * mail发送邮件接口 */ @Bean @ConditionalOnMissingBean(MailSendApi.class) public MailSendApi mailSenderApi() { return new MailServiceImpl(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/resource/ApiResourceAutoConfig.java ================================================ package com.oddfar.campus.framework.api.resource; import com.oddfar.campus.framework.api.resource.impl.DefaultResourceCollector; import com.oddfar.campus.framework.listener.ApiResourceScanner; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 资源的自动配置 */ @Configuration public class ApiResourceAutoConfig { /** * 资源搜集器 */ @Bean @ConditionalOnMissingBean(ApiResourceScanner.class) public ApiResourceScanner apiResourceScanner(ResourceCollectorApi resourceCollectorApi) { return new ApiResourceScanner(resourceCollectorApi); } /** * 资源搜集api */ @Bean @ConditionalOnMissingBean(ResourceCollectorApi.class) public ResourceCollectorApi resourceCollectorApi() { return new DefaultResourceCollector(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/resource/ResourceCollectorApi.java ================================================ package com.oddfar.campus.framework.api.resource; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import com.oddfar.campus.common.domain.model.SysRoleAuth; import com.oddfar.campus.common.domain.model.SysRoleAuthList; import java.util.List; import java.util.Map; /** * 权限资源收集器,搜集本项目中的资源,仅搜集并缓存起来,不持久化 * 参考 https://gitee.com/stylefeng/guns 项目 */ public interface ResourceCollectorApi { /** * 保存所有扫描到的资源 * */ void collectResources(List apiResource); /** * 获取当前运行项目的所有资源 * */ List getAllResources(); /** * 设置角色的资源和菜单权限缓存 * @param rolePermsMap * @param roleResourceMap */ void setRoleAuthCache(Map> rolePermsMap, Map> roleResourceMap); /** * 获取缓存的角色的资源和菜单权限 */ Map getRoleListMap(); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/resource/impl/DefaultResourceCollector.java ================================================ package com.oddfar.campus.framework.api.resource.impl; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import com.oddfar.campus.common.domain.model.SysRoleAuth; import com.oddfar.campus.common.domain.model.SysRoleAuthList; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.api.resource.ResourceCollectorApi; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class DefaultResourceCollector implements ResourceCollectorApi { /** * 以资源编码为key,存放资源集合 */ private final Map resourceDefinitions = new ConcurrentHashMap<>(); private final Map roleListMap = new ConcurrentHashMap<>(); @Override public void collectResources(List apiResource) { if (apiResource != null && apiResource.size() > 0) { for (SysResourceEntity resourceEntity : apiResource) { SysResourceEntity alreadyFlag = resourceDefinitions.get(resourceEntity.getResourceCode()); if (alreadyFlag != null) { throw new RuntimeException("资源扫描过程中存在重复资源!\n已存在资源:" + alreadyFlag + "\n新资源为: " + resourceEntity); } resourceDefinitions.put(resourceEntity.getResourceCode(), resourceEntity); } } } @Override public List getAllResources() { Set> entries = resourceDefinitions.entrySet(); ArrayList resourceDefinitions = new ArrayList<>(); for (Map.Entry entry : entries) { resourceDefinitions.add(entry.getValue()); } return resourceDefinitions; } @Override public void setRoleAuthCache(Map> rolePermsMap, Map> roleResourceMap) { roleListMap.clear(); for (Long roleId : rolePermsMap.keySet()) { List list = rolePermsMap.get(roleId); //把关于roleId的perms数据建立成set集合 Set perms = new HashSet<>(); list.stream().forEach(r -> { if (StringUtils.isNotEmpty(r.getPerms())) { perms.add(r.getPerms()); } }); //如果resMap包含roleId if (roleListMap.containsKey(roleId)) { SysRoleAuthList sysRoleList = roleListMap.get(roleId); if (sysRoleList.getPerms() != null) { //如果存在perms,则添加数据 sysRoleList.getPerms().addAll(perms); } else { //无数据则直接set sysRoleList.setPerms(perms); } } else { //不包含roleId重新生成 SysRoleAuthList sysRoleList = new SysRoleAuthList(roleId, perms, null); roleListMap.put(roleId, sysRoleList); } } for (Long roleId : roleResourceMap.keySet()) { List list = roleResourceMap.get(roleId); //把关于roleId的resource数据建立成set集合 Set resourceSet = new HashSet<>(); list.stream().forEach(r -> { if (StringUtils.isNotEmpty(r.getResourceCode())) { resourceSet.add(r.getResourceCode()); } }); //如果map包含roleId if (roleListMap.containsKey(roleId)) { SysRoleAuthList sysRoleList = roleListMap.get(roleId); if (sysRoleList.getResourceCode() != null) { sysRoleList.getResourceCode().addAll(resourceSet); } else { sysRoleList.setResourceCode(resourceSet); } } else { SysRoleAuthList sysRoleList = new SysRoleAuthList(roleId, null, resourceSet); roleListMap.put(roleId, sysRoleList); } } } @Override public Map getRoleListMap() { return roleListMap; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/sysconfig/ConfigContext.java ================================================ package com.oddfar.campus.framework.api.sysconfig; import cn.hutool.extra.spring.SpringUtil; import com.oddfar.campus.framework.service.SysConfigService; /** * 系统配置 */ public class ConfigContext { /** * 获取系统配置操作接口 */ public static SysConfigService me() { return SpringUtil.getBean(SysConfigService.class); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/sysconfig/ConfigExpander.java ================================================ package com.oddfar.campus.framework.api.sysconfig; import cn.hutool.core.convert.Convert; public class ConfigExpander { /** * 用户默认头像url */ public static String getUserDefaultAvatar() { return ConfigContext.me().selectConfigByKey("sys.user.default.avatar"); } /** * 验证码类型 */ public static String getLoginCaptchaType() { return ConfigContext.me().selectConfigByKey("sys.login.captchaType", String.class, "math"); } /** * 获取文件保存目录 */ public static String getFileProfile() { String osName = System.getProperty("os.name").toLowerCase(); if (osName.contains("win")) { return ConfigContext.me().selectConfigByKey("sys.local.profile.win", String.class, "D:\\uploadPath"); } if (osName.contains("mac")) { return ConfigContext.me().selectConfigByKey("sys.local.profile.mac", String.class, "~/uploadPath"); } if (osName.contains("linux")) { return ConfigContext.me().selectConfigByKey("sys.local.profile.linux", String.class, "/data/uploadPath"); } return null; } /** * 获取头像上传路径 */ public static String getAvatarPath() { return getFileProfile() + "/avatar"; } /** * 全局日志记录,开启则所有请求都将记录日志 */ public static Boolean getGlobalControllerOpenFlag() { String flag = ConfigContext.me().selectConfigByKey("sys.log.global.flag", String.class, "false"); return Convert.toBool(flag); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/api/sysconfig/ZyConfigAutoConfiguration.java ================================================ package com.oddfar.campus.framework.api.sysconfig; import com.oddfar.campus.framework.service.SysConfigService; import com.oddfar.campus.framework.service.impl.SysConfigServiceImpl; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 系统配置自动配置 */ @Configuration public class ZyConfigAutoConfiguration { @Bean @ConditionalOnMissingBean(SysConfigService.class) public SysConfigService configService() { return new SysConfigServiceImpl(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/aspectj/LogAspect.java ================================================ package com.oddfar.campus.framework.aspectj; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.annotation.Log; import com.oddfar.campus.common.domain.entity.SysOperLogEntity; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.enums.BusinessStatus; import com.oddfar.campus.common.filter.PropertyPreExcludeFilter; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.ip.IpUtils; import com.oddfar.campus.framework.api.sysconfig.ConfigExpander; import com.oddfar.campus.framework.manager.AsyncFactory; import com.oddfar.campus.framework.manager.AsyncManager; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.validation.BindingResult; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.HandlerMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 操作日志记录处理 * * @author oddfar */ @Aspect @Component public class LogAspect { private static final Logger log = LoggerFactory.getLogger(LogAspect.class); /** * 排除敏感属性字段 */ public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"}; @Value("${spring.application.name:}") private String springApplicationName; /** * 切所有的controller包 */ @Pointcut("execution(* *..controller..*(..))") public void webLog() { } /** * 处理完请求后执行 * * @param joinPoint 切点 */ @AfterReturning(pointcut = "webLog()", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) { boolean ensureMakeLog = this.ensureMakeLog(joinPoint); if (!ensureMakeLog) { return; } // 获取接口上@GetMapper等的name属性 Map annotationProp = getAnnotationProp(joinPoint); handleLog(joinPoint, annotationProp, null, jsonResult); } /** * 拦截异常操作 * * @param joinPoint 切点 * @param e 异常 */ @AfterThrowing(pointcut = "webLog()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Exception e) { boolean ensureMakeLog = this.ensureMakeLog(joinPoint); if (!ensureMakeLog) { return; } // 获取接口上@GetMapper等的name属性 Map annotationProp = getAnnotationProp(joinPoint); handleLog(joinPoint, annotationProp, e, null); } /** * AOP获取 @GetMapping等 的属性信息 * * @param joinPoint joinPoint对象 * @return 返回K, V格式的参数,key是参数名称,v是参数值 */ private Map getAnnotationProp(JoinPoint joinPoint) { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); // 通过map封装参数和参数值,key参数名,value是参数值 Map propMap = new HashMap<>(2); // 获取接口上的@GetMapping等的name属性 填充到map ApiResource apiResource = method.getDeclaringClass().getAnnotation(ApiResource.class); for (Annotation annotation : method.getAnnotations()) { //若是 spring 的请求注解 if (annotation.toString().contains("Mapping(")) { // 填充其他属性 String name = invokeAnnotationMethod(annotation, "name", String.class); propMap.put("log_content", StringUtils.isNull(name) ? "" : name); } } propMap.put("app_name", apiResource != null && StrUtil.isNotBlank(apiResource.appCode()) ? apiResource.appCode() : springApplicationName); /** * 以下是只填充 GetMapping 和 PostMapping */ // GetMapping getResource = method.getAnnotation(GetMapping.class); // PostMapping postResource = method.getAnnotation(PostMapping.class); // if (getResource != null) { // propMap.put("log_content", getResource.name()); // } // // if (postResource != null) { // propMap.put("log_content", postResource.name()); // } return propMap; } protected void handleLog(final JoinPoint joinPoint, Map annotationProp, final Exception e, Object jsonResult) { try { // *========数据库日志=========*// SysOperLogEntity operLog = new SysOperLogEntity(); //设置appcode operLog.setAppName(annotationProp.get("app_name").toString()); operLog.setLogName("API接口日志记录"); operLog.setLogContent(annotationProp.get("log_content").toString()); operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); // 请求的地址 String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); operLog.setOperIp(ip); operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); if (SecurityUtils.isLogin()) { // 获取当前的用户 LoginUser loginUser = SecurityUtils.getLoginUser(); operLog.setOperUserId(loginUser.getUserId()); } if (e != null) { operLog.setStatus(BusinessStatus.FAIL.ordinal()); operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); } // 设置方法名称 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); operLog.setMethod(className + "." + methodName + "()"); // 设置请求方式 operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); // 处理设置注解上的参数 app name那些 getControllerMethodDescription(joinPoint, operLog, jsonResult); operLog.setOperTime(new Date()); // 保存数据库 AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } catch (Exception exp) { // 记录本地异常日志 log.error("==前置通知异常=="); log.error("异常信息:{}", exp.getMessage()); exp.printStackTrace(); } } /** * 获取注解中对方法的描述信息 用于Controller层注解 * * @param operLog 操作日志 * @throws Exception */ public void getControllerMethodDescription(JoinPoint joinPoint, SysOperLogEntity operLog, Object jsonResult) throws Exception { // 保存request,参数和值,获取参数的信息,传入到数据库中。 setRequestValue(joinPoint, operLog); //保存response,参数和值 if (StringUtils.isNotNull(jsonResult)) { operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); } } /** * 获取请求的参数,放到log中 * * @param operLog 操作日志 * @throws Exception 异常 */ private void setRequestValue(JoinPoint joinPoint, SysOperLogEntity operLog) throws Exception { String requestMethod = operLog.getRequestMethod(); if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) { String params = argsArrayToString(joinPoint.getArgs()); operLog.setOperParam(StringUtils.substring(params, 0, 2000)); } else { Map paramsMap = (Map) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000)); } } /** * 参数拼装 */ private String argsArrayToString(Object[] paramsArray) { String params = ""; if (paramsArray != null && paramsArray.length > 0) { for (Object o : paramsArray) { if (StringUtils.isNotNull(o) && !isFilterObject(o)) { try { String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter()); params += jsonObj.toString() + " "; } catch (Exception e) { } } } } return params.trim(); } /** * 确定当前接口是否需要记录日志 * 参考:https://gitee.com/stylefeng/guns */ private boolean ensureMakeLog(JoinPoint point) { // 判断是否需要记录日志,如果不需要直接返回 Boolean openFlag = ConfigExpander.getGlobalControllerOpenFlag(); // 获取类上的业务日志开关注解 Class controllerClass = point.getTarget().getClass(); Log businessLog = controllerClass.getAnnotation(Log.class); // 获取方法上的业务日志开关注解 Log methodBusinessLog = null; MethodSignature methodSignature = null; if (!(point.getSignature() instanceof MethodSignature)) { return false; } methodSignature = (MethodSignature) point.getSignature(); Object target = point.getTarget(); try { Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); methodBusinessLog = currentMethod.getAnnotation(Log.class); } catch (NoSuchMethodException e) { return false; } // 如果开关开启 if (openFlag) { // 如果控制器类上特意标明不做日志,则不记录日志 if (businessLog != null && !businessLog.openLog()) { return false; } // 如果方法上标明不记录日志,则不记录日志 return methodBusinessLog == null || methodBusinessLog.openLog(); } else { // 如果全局开关没开启,但是类上有特殊标记开启日志,则以类上标注为准 if (businessLog != null && businessLog.openLog()) { return true; } // 如果方法上标明不记录日志,则不记录日志 return methodBusinessLog != null && methodBusinessLog.openLog(); } } /** * 调用注解上的某个方法,并获取结果 */ private T invokeAnnotationMethod(Annotation apiResource, String methodName, Class resultType) { try { Class annotationType = apiResource.annotationType(); Method method = annotationType.getMethod(methodName); return (T) method.invoke(apiResource); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { } return null; } /** * 忽略敏感属性 */ public PropertyPreExcludeFilter excludePropertyPreFilter() { return new PropertyPreExcludeFilter().addExcludes(EXCLUDE_PROPERTIES); } /** * 判断是否需要过滤的对象。 * * @param o 对象信息。 * @return 如果是需要过滤的对象,则返回true;否则返回false。 */ @SuppressWarnings("rawtypes") public boolean isFilterObject(final Object o) { Class clazz = o.getClass(); if (clazz.isArray()) { return clazz.getComponentType().isAssignableFrom(MultipartFile.class); } else if (Collection.class.isAssignableFrom(clazz)) { Collection collection = (Collection) o; for (Object value : collection) { return value instanceof MultipartFile; } } else if (Map.class.isAssignableFrom(clazz)) { Map map = (Map) o; for (Object value : map.entrySet()) { Map.Entry entry = (Map.Entry) value; return entry.getValue() instanceof MultipartFile; } } return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof BindingResult; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/config/ApplicationConfig.java ================================================ package com.oddfar.campus.framework.config; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * 程序注解配置 */ @Configuration // 表示通过aop框架暴露该代理对象,AopContext能够访问 @EnableAspectJAutoProxy(exposeProxy = true) public class ApplicationConfig { } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/config/AsyncConfig.java ================================================ package com.oddfar.campus.framework.config; import cn.hutool.core.util.ArrayUtil; import cn.hutool.extra.spring.SpringUtil; import com.oddfar.campus.common.exception.ServiceException; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.EnableAsync; import java.util.Arrays; import java.util.concurrent.Executor; /** * 异步配置 * * @author Lion Li */ @EnableAsync(proxyTargetClass = true) @Configuration public class AsyncConfig implements AsyncConfigurer { /** * 自定义 @Async 注解使用系统线程池 */ @Override public Executor getAsyncExecutor() { return SpringUtil.getBean("scheduledExecutorService"); } /** * 异步执行异常处理 */ @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (throwable, method, objects) -> { throwable.printStackTrace(); StringBuilder sb = new StringBuilder(); sb.append("Exception message - ").append(throwable.getMessage()) .append(", Method name - ").append(method.getName()); if (ArrayUtil.isNotEmpty(objects)) { sb.append(", Parameter value - ").append(Arrays.toString(objects)); } throw new ServiceException(sb.toString()); }; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/config/JacksonConfig.java ================================================ package com.oddfar.campus.framework.config; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.oddfar.campus.framework.handler.BigNumberSerializer; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.math.BigDecimal; import java.math.BigInteger; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.TimeZone; /** * jackson 配置 * * @author Lion Li */ @Slf4j @Configuration @AutoConfigureBefore(JacksonAutoConfiguration.class) public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder -> { // 全局配置序列化返回 JSON 处理 JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE); javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE); javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE); javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); builder.modules(javaTimeModule); //时区配置 builder.timeZone(TimeZone.getDefault()); log.info("初始化 jackson 配置"); }; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/config/KaptchaTextCreator.java ================================================ package com.oddfar.campus.framework.config; import com.google.code.kaptcha.text.impl.DefaultTextCreator; import org.springframework.context.annotation.Configuration; import java.util.Random; /** * 验证码文本生成器 * * @author ruoyi */ public class KaptchaTextCreator extends DefaultTextCreator { private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); @Override public String getText() { Integer result = 0; Random random = new Random(); int x = random.nextInt(10); int y = random.nextInt(10); StringBuilder suChinese = new StringBuilder(); int randomoperands = random.nextInt(3); if (randomoperands == 0) { result = x * y; suChinese.append(CNUMBERS[x]); suChinese.append("*"); suChinese.append(CNUMBERS[y]); } else if (randomoperands == 1) { if ((x != 0) && y % x == 0) { result = y / x; suChinese.append(CNUMBERS[y]); suChinese.append("/"); suChinese.append(CNUMBERS[x]); } else { result = x + y; suChinese.append(CNUMBERS[x]); suChinese.append("+"); suChinese.append(CNUMBERS[y]); } } else { if (x >= y) { result = x - y; suChinese.append(CNUMBERS[x]); suChinese.append("-"); suChinese.append(CNUMBERS[y]); } else { result = y - x; suChinese.append(CNUMBERS[y]); suChinese.append("-"); suChinese.append(CNUMBERS[x]); } } suChinese.append("=?@" + result); return suChinese.toString(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/config/MyWebMvcConfig.java ================================================ package com.oddfar.campus.framework.config; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.framework.api.sysconfig.ConfigExpander; import com.oddfar.campus.framework.interceptor.RepeatSubmitInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextListener; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * 通用配置 * (根据若依修改) */ @Configuration public class MyWebMvcConfig implements WebMvcConfigurer { @Autowired private RepeatSubmitInterceptor repeatSubmitInterceptor; /** * 映射到访问本地的资源文件 * * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { /** 本地文件上传路径 */ registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") .addResourceLocations("file:" + ConfigExpander.getFileProfile() + "/"); /** swagger配置 */ registry.addResourceHandler("/swagger-ui/**") .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/"); } /** * 自定义拦截规则 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); } /** * 跨域配置 */ @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); // 设置访问源地址 config.addAllowedOriginPattern("*"); // 设置访问源请求头 config.addAllowedHeader("*"); // 设置访问源请求方法 config.addAllowedMethod("*"); // 有效期 1800秒 config.setMaxAge(1800L); // 添加映射路径,拦截一切请求 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); // 返回新的CorsFilter return new CorsFilter(source); } /** * RequestContextListener监听器 * bug:https://blog.csdn.net/qq_39575279/article/details/86562195 * * @return */ @Bean public RequestContextListener requestContextListenerBean() { return new RequestContextListener(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/config/MybatisPlusConfig.java ================================================ package com.oddfar.campus.framework.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.oddfar.campus.framework.handler.MyDBFieldHandler; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("${mybatis-plus.mapperPackage}") public class MybatisPlusConfig { /** * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除) */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } /** * 自动填充参数类 * * @return */ @Bean public MetaObjectHandler defaultMetaObjectHandler() { return new MyDBFieldHandler(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/config/SecurityConfig.java ================================================ package com.oddfar.campus.framework.config; import com.oddfar.campus.framework.security.filter.JwtAuthenticationTokenFilter; import com.oddfar.campus.framework.security.handle.AuthenticationEntryPointImpl; import com.oddfar.campus.framework.security.handle.LogoutSuccessHandlerImpl; import com.oddfar.campus.framework.security.properties.PermitAllUrlProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.web.filter.CorsFilter; /** * spring security配置 * * @author ruoyi */ @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 自定义用户认证逻辑 */ @Autowired private UserDetailsService userDetailsService; /** * 认证失败处理类 */ @Autowired private AuthenticationEntryPointImpl unauthorizedHandler; /** * 退出处理类 */ @Autowired private LogoutSuccessHandlerImpl logoutSuccessHandler; /** * token认证过滤器 */ @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; /** * 跨域过滤器 */ @Autowired private CorsFilter corsFilter; /** * 允许匿名访问的地址 */ @Autowired private PermitAllUrlProperties permitAllUrl; /** * 解决 无法直接注入 AuthenticationManager * * @return * @throws Exception */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * anyRequest | 匹配所有请求路径 * access | SpringEl表达式结果为true时可以访问 * anonymous | 匿名可以访问 * denyAll | 用户不能访问 * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 * hasRole | 如果有参数,参数表示角色,则其角色可以访问 * permitAll | 用户可以任意访问 * rememberMe | 允许通过remember-me登录的用户访问 * authenticated | 用户登录后可访问 */ @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 注解标记允许匿名访问的url ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); httpSecurity // CSRF禁用,因为不使用session .csrf().disable() // 认证失败处理类 .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // 过滤请求 .authorizeRequests() // 对于登录login 注册register 验证码captchaImage 允许匿名访问 .antMatchers("/login", "/register", "/captchaImage").anonymous() // 静态资源,可匿名访问 .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated() .and() .headers().frameOptions().disable(); // 添加Logout filter httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler); // 添加JWT filter httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 添加CORS filter httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class); httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class); } /** * 强散列哈希加密实现 */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } /** * 身份认证接口 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/config/ThreadPoolConfig.java ================================================ package com.oddfar.campus.framework.config; import com.oddfar.campus.common.utils.Threads; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; /** * 线程池配置 * * @author ruoyi **/ @Configuration public class ThreadPoolConfig { // 核心线程池大小 private int corePoolSize = 50; // 最大可创建的线程数 private int maxPoolSize = 200; // 队列最大长度 private int queueCapacity = 1000; // 线程池维护线程所允许的空闲时间 private int keepAliveSeconds = 300; @Bean(name = "threadPoolTaskExecutor") public ThreadPoolTaskExecutor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setMaxPoolSize(maxPoolSize); executor.setCorePoolSize(corePoolSize); executor.setQueueCapacity(queueCapacity); executor.setKeepAliveSeconds(keepAliveSeconds); // 线程池对拒绝任务(无线程可用)的处理策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; } /** * 执行周期性或定时任务 */ @Bean(name = "scheduledExecutorService") protected ScheduledExecutorService scheduledExecutorService() { return new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), new ThreadPoolExecutor.CallerRunsPolicy()) { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); Threads.printException(r, t); } }; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/expander/SysConfigExpander.java ================================================ package com.oddfar.campus.framework.expander; import com.oddfar.campus.framework.service.SysConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; /** * 系统配置读取 */ @Component public class SysConfigExpander { private static SysConfigService configService; @Autowired private SysConfigService sysConfigService; @PostConstruct public void init() { configService = sysConfigService; } /** * 用户默认头像url */ public static String getUserDefaultAvatar() { return configService.selectConfigByKey("sys.user.default.avatar"); } /** * 验证码类型 * * @return */ public static String getLoginCaptchaType() { return configService.selectConfigByKey("sys.login.captchaType", String.class, "math"); } /** * 获取文件保存目录 * * @return */ public static String getFileProfile() { String osName = System.getProperty("os.name").toLowerCase(); if (osName.contains("win")) { return configService.selectConfigByKey("sys.local.profile.win", String.class, "D:\\uploadPath"); } if (osName.contains("mac")) { return configService.selectConfigByKey("sys.local.profile.mac", String.class, "~/uploadPath"); } if (osName.contains("linux")) { return configService.selectConfigByKey("sys.local.profile.linux", String.class, "/data/uploadPath"); } return null; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/expander/SysFileConfigExpander.java ================================================ package com.oddfar.campus.framework.expander; import com.oddfar.campus.framework.api.sysconfig.ConfigContext; import org.springframework.stereotype.Component; /** * 系统文件配置读取 */ @Component public class SysFileConfigExpander { /** * 获取文件保存目录 */ public static String getProfile() { String osName = System.getProperty("os.name").toLowerCase(); if (osName.contains("win")) { return ConfigContext.me().selectConfigByKey("sys.local.profile.win", String.class, "D:\\uploadPath"); } if (osName.contains("mac")) { return ConfigContext.me().selectConfigByKey("sys.local.profile.mac", String.class, "~/uploadPath"); } if (osName.contains("linux")) { return ConfigContext.me().selectConfigByKey("sys.local.profile.linux", String.class, "/data/uploadPath"); } return null; } /** * 获取导入上传路径 */ public static String getImportPath() { return getProfile() + "/import"; } /** * 获取头像上传路径 */ public static String getAvatarPath() { return getProfile() + "/avatar"; } /** * 获取下载路径 */ public static String getDownloadPath() { return getProfile() + "/download/"; } /** * 获取上传路径 */ public static String getUploadPath() { return getProfile() + "/upload"; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/handler/BigNumberSerializer.java ================================================ package com.oddfar.campus.framework.handler; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; import com.fasterxml.jackson.databind.ser.std.NumberSerializer; import java.io.IOException; /** * 超出 JS 最大最小值 处理 * * @author Lion Li */ @JacksonStdImpl public class BigNumberSerializer extends NumberSerializer { /** * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来 */ private static final long MAX_SAFE_INTEGER = 9007199254740991L; private static final long MIN_SAFE_INTEGER = -9007199254740991L; /** * 提供实例 */ public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class); public BigNumberSerializer(Class rawType) { super(rawType); } @Override public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 超出范围 序列化位字符串 if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { super.serialize(value, gen, provider); } else { gen.writeString(value.toString()); } } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/handler/MyDBFieldHandler.java ================================================ package com.oddfar.campus.framework.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.oddfar.campus.common.domain.BaseEntity; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.web.WebFrameworkUtils; import org.apache.ibatis.reflection.MetaObject; import java.util.Date; import java.util.Objects; /** * mybatis-plus 通用参数填充实现类 */ public class MyDBFieldHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { // this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐) if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity) { BaseEntity baseEntity = (BaseEntity) metaObject.getOriginalObject(); Date current = new Date(); // 创建时间为空,则以当前时间为插入时间 if (Objects.isNull(baseEntity.getCreateTime())) { baseEntity.setCreateTime(current); } // 更新时间为空,则以当前时间为更新时间 if (Objects.isNull(baseEntity.getUpdateTime())) { baseEntity.setUpdateTime(current); } //TODO getRequest2 Long userId = WebFrameworkUtils.getLoginUserId(ServletUtils.getRequest()); // 当前登录用户不为空,创建人为空,则当前登录用户为创建人 if (Objects.nonNull(userId) && Objects.isNull(baseEntity.getCreateUser())) { baseEntity.setCreateUser(userId); } // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 if (Objects.nonNull(userId) && Objects.isNull(baseEntity.getUpdateUser())) { baseEntity.setUpdateUser(userId); } if (Objects.isNull(baseEntity.getDelFlag())) { baseEntity.setDelFlag(0); } } } @Override public void updateFill(MetaObject metaObject) { // this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐) // 更新时间为空,则以当前时间为更新时间 Object modifyTime = getFieldValByName("updateTime", metaObject); if (Objects.isNull(modifyTime)) { setFieldValByName("updateTime", new Date(), metaObject); } // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 Object modifier = getFieldValByName("updateUser", metaObject); Long userId = WebFrameworkUtils.getLoginUserId(ServletUtils.getRequest()); if (Objects.nonNull(userId) && Objects.isNull(modifier)) { setFieldValByName("updateUser", userId, metaObject); } } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/interceptor/RepeatSubmitInterceptor.java ================================================ package com.oddfar.campus.framework.interceptor; import com.alibaba.fastjson2.JSON; import com.oddfar.campus.common.annotation.RepeatSubmit; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.utils.ServletUtils; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * 防止重复提交拦截器 * * @author ruoyi */ @Component public abstract class RepeatSubmitInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); if (annotation != null) { if (this.isRepeatSubmit(request, annotation)) { R r = R.error(annotation.message()); ServletUtils.renderString(response, JSON.toJSONString(r)); return false; } } return true; } else { return true; } } /** * 验证是否重复提交由子类实现具体的防重复提交的规则 * * @param request * @return * @throws Exception */ public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/interceptor/impl/SameUrlDataInterceptor.java ================================================ package com.oddfar.campus.framework.interceptor.impl; import com.alibaba.fastjson2.JSON; import com.oddfar.campus.common.annotation.RepeatSubmit; import com.oddfar.campus.common.constant.CacheConstants; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.filter.RepeatedlyRequestWrapper; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.http.HttpHelper; import com.oddfar.campus.framework.interceptor.RepeatSubmitInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * 判断请求url和数据是否和上一次相同, * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 * * @author ruoyi */ @Component public class SameUrlDataInterceptor extends RepeatSubmitInterceptor { public final String REPEAT_PARAMS = "repeatParams"; public final String REPEAT_TIME = "repeatTime"; // 令牌自定义标识 @Value("${token.header}") private String header; @Autowired private RedisCache redisCache; @SuppressWarnings("unchecked") @Override public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) { String nowParams = ""; if (request instanceof RepeatedlyRequestWrapper) { RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; nowParams = HttpHelper.getBodyString(repeatedlyRequest); } // body参数为空,获取Parameter的数据 if (StringUtils.isEmpty(nowParams)) { nowParams = JSON.toJSONString(request.getParameterMap()); } Map nowDataMap = new HashMap(); nowDataMap.put(REPEAT_PARAMS, nowParams); nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); // 请求地址(作为存放cache的key值) String url = request.getRequestURI(); // 唯一值(没有消息头则使用请求地址) String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); // 唯一标识(指定key + url + 消息头) String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey; Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); if (sessionObj != null) { Map sessionMap = (Map) sessionObj; if (sessionMap.containsKey(url)) { Map preDataMap = (Map) sessionMap.get(url); if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) { return true; } } } Map cacheMap = new HashMap(); cacheMap.put(url, nowDataMap); redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); return false; } /** * 判断参数是否相同 */ private boolean compareParams(Map nowMap, Map preMap) { String nowParams = (String) nowMap.get(REPEAT_PARAMS); String preParams = (String) preMap.get(REPEAT_PARAMS); return nowParams.equals(preParams); } /** * 判断两次间隔时间 */ private boolean compareTime(Map nowMap, Map preMap, int interval) { long time1 = (Long) nowMap.get(REPEAT_TIME); long time2 = (Long) preMap.get(REPEAT_TIME); if ((time1 - time2) < interval) { return true; } return false; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/listener/ApiResourceScanner.java ================================================ package com.oddfar.campus.framework.listener; import cn.hutool.core.util.StrUtil; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import com.oddfar.campus.common.utils.spring.AopTargetUtils; import com.oddfar.campus.framework.api.resource.ResourceCollectorApi; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; /** * api接口资源扫描 * 参考 https://gitee.com/stylefeng/guns 项目 * * @author oddfar */ public class ApiResourceScanner implements BeanPostProcessor { @Value("${spring.application.name:}") private String springApplicationName; /** * 权限资源收集接口 */ private final ResourceCollectorApi resourceCollectorApi; public ApiResourceScanner(ResourceCollectorApi resourceCollectorApi) { this.resourceCollectorApi = resourceCollectorApi; } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Object aopTarget = AopTargetUtils.getTarget(bean); if (aopTarget == null) { aopTarget = bean; } Class clazz = aopTarget.getClass(); // 判断是不是控制器,不是控制器就略过 boolean controllerFlag = getControllerFlag(clazz); if (!controllerFlag) { return bean; } ApiResource classApiAnnotation = clazz.getAnnotation(ApiResource.class); if (classApiAnnotation != null) { // 扫描控制器的所有带ApiResource注解的方法 List apiResources = doScan(clazz); // 将扫描到的注解转化为资源实体存储到缓存 resourceCollectorApi.collectResources(apiResources); } return bean; } /** * 判断一个类是否是控制器 */ private boolean getControllerFlag(Class clazz) { Annotation[] annotations = clazz.getAnnotations(); for (Annotation annotation : annotations) { if (RestController.class.equals(annotation.annotationType())) { return true; } } return false; } /** * 扫描整个类中包含的所有@ApiResource资源 */ private List doScan(Class clazz) { ArrayList apiResources = new ArrayList<>(); Method[] declaredMethods = clazz.getDeclaredMethods(); if (declaredMethods.length > 0) { for (Method declaredMethod : declaredMethods) { Annotation annotation = null; Annotation[] annotations = declaredMethod.getAnnotations(); for (Annotation a : annotations) { //若是 spring 的请求注解 if (a.toString().contains("Mapping(")) { annotation = a; } } if (annotation != null) { SysResourceEntity definition = createDefinition(clazz, declaredMethod, annotation); apiResources.add(definition); } } } return apiResources; } /** * 根据类信息,方法信息,注解信息创建 SysResourceEntity 对象 */ private SysResourceEntity createDefinition(Class controllerClass, Method method, Annotation annotation) { SysResourceEntity resource = new SysResourceEntity(); //设置类和方法名称 resource.setClassName(controllerClass.getSimpleName()); resource.setMethodName(method.getName()); // 填充模块编码,模块编码就是控制器名称截取Controller关键字前边的字符串 String className = resource.getClassName(); int controllerIndex = className.indexOf("Controller"); if (controllerIndex == -1) { throw new IllegalArgumentException("controller class name is not illegal, it should end with Controller!"); } //去掉Controller String modular = className.substring(0, controllerIndex); resource.setModular_code(modular); // 填充模块的中文名称 ApiResource apiResource = controllerClass.getAnnotation(ApiResource.class); // 接口资源的类别 resource.setResourceBizType(apiResource.resBizType().getCode()); resource.setModularName(apiResource.name()); // 设置appCode if (StrUtil.isNotBlank(apiResource.appCode())) { resource.setAppCode(apiResource.appCode()); }else { resource.setAppCode(springApplicationName); } //资源唯一编码 String resourceCode = StrUtil.toUnderlineCase(resource.getAppCode()) + "." + StrUtil.toUnderlineCase(modular) + "." + StrUtil.toUnderlineCase(resource.getMethodName()); resource.setResourceCode(resourceCode); //是否需要鉴权 PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class); resource.setRequiredPermissionFlag(Constants.NO); if (preAuthorize != null) { if (preAuthorize.value().contains("@ss.resourceAuth")) { resource.setRequiredPermissionFlag(Constants.YES); } } // 填充其他属性 String name = invokeAnnotationMethod(annotation, "name", String.class); //不存在则资源名称为方法名 if (StringUtils.isNotEmpty(name)) { resource.setResourceName(name); } else { resource.setResourceName(resource.getMethodName()); } String[] value = invokeAnnotationMethod(annotation, "value", String[].class); String controllerMethodPath = createControllerPath(controllerClass, value); resource.setUrl(controllerMethodPath); //填充请求方法 resource.setHttpMethod(StringUtils.substringBetween(annotation.toString(), "annotation.", "Mapping").toLowerCase()); return resource; } /** * 根据控制器类上的RequestMapping注解的映射路径,以及方法上的路径,拼出整个接口的路径 */ private String createControllerPath(Class clazz, String[] paths) { String path = ""; if (paths.length > 0) { path = "/" + paths[0]; } String controllerPath; RequestMapping controllerRequestMapping = clazz.getDeclaredAnnotation(RequestMapping.class); if (controllerRequestMapping == null) { controllerPath = ""; } else { String[] values = controllerRequestMapping.value(); if (values.length > 0) { controllerPath = values[0]; } else { controllerPath = ""; } } // 控制器上的path要以/开头 if (!controllerPath.startsWith("/")) { controllerPath = "/" + controllerPath; } // 前缀多个左斜杠替换为一个 return (controllerPath + path).replaceAll("/+", "/"); } /** * 调用注解上的某个方法,并获取结果 * * @author fengshuonan * @date 2020/12/8 17:13 */ private T invokeAnnotationMethod(Annotation apiResource, String methodName, Class resultType) { try { Class annotationType = apiResource.annotationType(); Method method = annotationType.getMethod(methodName); return (T) method.invoke(apiResource); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { } throw new RuntimeException("扫描api资源时出错!"); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/listener/ReadyEventListener.java ================================================ package com.oddfar.campus.framework.listener; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import com.oddfar.campus.framework.api.resource.ResourceCollectorApi; import com.oddfar.campus.framework.service.SysResourceService; import com.oddfar.campus.framework.service.SysRoleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Component; import java.util.List; /** * 监听项目初始化完毕,执行的操作 * * @author oddfar */ @Component public class ReadyEventListener implements ApplicationListener { @Autowired private SysResourceService resourceService; @Autowired private SysRoleService roleService; @Override public void onApplicationEvent(ApplicationReadyEvent event) { //导入资源数据 ConfigurableApplicationContext applicationContext = event.getApplicationContext(); //清空数据 resourceService.truncateResource(); // 获取当前系统的所有资源 ResourceCollectorApi resourceCollectorApi = applicationContext.getBean(ResourceCollectorApi.class); List allResources = resourceCollectorApi.getAllResources(); //添加api接口资源到数据库 resourceService.saveBatch(allResources); //把用户资源和权限缓存 roleService.resetRoleAuthCache(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/listener/ResourceReportListener.java ================================================ package com.oddfar.campus.framework.listener; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import com.oddfar.campus.framework.service.SysResourceService; //import io.swagger.annotations.Api; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Map; import java.util.Optional; /** * 监听项目初始化完毕,导入资源信息(没用,暂存下代码) * * @author oddfar */ //@Component implements ApplicationListener public class ResourceReportListener { @Autowired WebApplicationContext applicationContext; @Autowired SysResourceService resourceService; // @Override public void onApplicationEvent(ApplicationReadyEvent event) { //清空表 resourceService.truncateResource(); RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); // 获取所有controller方法 Map map = mapping.getHandlerMethods(); map.keySet().forEach(info -> { HandlerMethod handlerMethod = map.get(info); PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), PreAuthorize.class); //如果方法上含有 @PreAuthorize 注解 Optional.ofNullable(preAuthorize).ifPresent(anonymous -> { if (preAuthorize.value().contains("@ss.test")) { Method m = handlerMethod.getMethod(); setSource(m); } }); }); } /** * 根据 Method 设置其内容 * * @param m */ private void setSource(Method m) { Class aClass = m.getDeclaringClass(); Annotation[] annotations = m.getDeclaredAnnotations(); for (Annotation a : annotations) { //若是 spring 的请求注解 if (a.toString().contains("Mapping(")) { SysResourceEntity resource = new SysResourceEntity(); resource.setClassName(aClass.getSimpleName()); //获取注解@Api,并设置模块名称 // Api api = AnnotationUtils.getAnnotation(aClass, Api.class); // resource.setModularName(api.value()); resource.setMethodName(m.getName()); //设置注解其他内容 setSource(resource, a); //保存到数据库 resourceService.insertResource(resource); } } } /** * 设置 SysResourceEntity 的其他内容 * * @param resource * @param a 例如 @GetMapping,设置其参数的内容 */ private void setSource(SysResourceEntity resource, Annotation a) { String s = a.toString(); resource.setResourceName(StringUtils.substringBetween(s, ", name=", ", ")); resource.setUrl(StringUtils.substringBetween(s, ", value=", ", ")); resource.setHttpMethod(StringUtils.substringBetween(s, "annotation.", "Mapping")); resource.setAppCode("zhiyuan"); //ClassName+MethodName(替换) resource.setResourceCode(resource.getClassName().toLowerCase().replace("controller", "") + ":" + resource.getMethodName()); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/manager/AsyncFactory.java ================================================ package com.oddfar.campus.framework.manager; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.domain.entity.SysLoginLogEntity; import com.oddfar.campus.common.domain.entity.SysOperLogEntity; import com.oddfar.campus.common.utils.LogUtils; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.SpringUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.ip.AddressUtils; import com.oddfar.campus.common.utils.ip.IpUtils; import com.oddfar.campus.framework.service.SysLoginLogService; import com.oddfar.campus.framework.service.SysOperLogService; import eu.bitwalker.useragentutils.UserAgent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; import java.util.TimerTask; /** * 异步工厂(产生任务用) * * @author ruoyi */ public class AsyncFactory { private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); /** * 记录登录信息 * * @param userName 用户名 * @param status 状态 * @param message 消息 * @param args 列表 * @return 任务task */ public static TimerTask recordLogininfor(final String userName, final Long userId, final String status, final String message, final Object... args) { final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); final String ip = IpUtils.getIpAddr(ServletUtils.getRequest2()); return new TimerTask() { @Override public void run() { String address = AddressUtils.getRealAddressByIP(ip); StringBuilder s = new StringBuilder(); s.append(LogUtils.getBlock(ip)); s.append(address); s.append(LogUtils.getBlock(userName)); s.append(LogUtils.getBlock(userId)); s.append(LogUtils.getBlock(status)); s.append(LogUtils.getBlock(message)); // 打印信息到日志 sys_user_logger.info(s.toString(), args); // 获取客户端操作系统 String os = userAgent.getOperatingSystem().getName(); // 获取客户端浏览器 String browser = userAgent.getBrowser().getName(); // 封装对象 SysLoginLogEntity logininfor = new SysLoginLogEntity(); logininfor.setLoginTime(new Date()); logininfor.setUserName(userName); logininfor.setUserId(userId); logininfor.setIpaddr(ip); logininfor.setLoginLocation(address); logininfor.setBrowser(browser); logininfor.setOs(os); logininfor.setMsg(message); // 日志状态 if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) { logininfor.setStatus(Constants.SUCCESS); } else if (Constants.LOGIN_FAIL.equals(status)) { logininfor.setStatus(Constants.FAIL); } // 插入数据 SpringUtils.getBean(SysLoginLogService.class).insertLogininfor(logininfor); } }; } /** * 操作日志记录 * * @param operLog 操作日志信息 * @return 任务task */ public static TimerTask recordOper(final SysOperLogEntity operLog) { return new TimerTask() { @Override public void run() { SpringUtils.getBean(SysOperLogService.class).insertOperlog(operLog); } }; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/manager/AsyncManager.java ================================================ package com.oddfar.campus.framework.manager; import com.oddfar.campus.common.utils.SpringUtils; import com.oddfar.campus.common.utils.Threads; import java.util.TimerTask; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * 异步任务管理器 * * @author ruoyi */ public class AsyncManager { /** * 操作延迟10毫秒 */ private final int OPERATE_DELAY_TIME = 10; /** * 异步操作任务调度线程池 */ private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); /** * 单例模式 */ private AsyncManager() { } private static AsyncManager me = new AsyncManager(); public static AsyncManager me() { return me; } /** * 执行任务 * * @param task 任务 */ public void execute(TimerTask task) { executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); } /** * 停止任务线程池 */ public void shutdown() { Threads.shutdownAndAwaitTermination(executor); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysConfigMapper.java ================================================ package com.oddfar.campus.framework.mapper; import cn.hutool.core.util.ObjectUtil; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysConfigEntity; public interface SysConfigMapper extends BaseMapperX { default PageResult selectPage(SysConfigEntity config) { return selectPage(new LambdaQueryWrapperX() .likeIfPresent(SysConfigEntity::getConfigName, config.getConfigName()) .likeIfPresent(SysConfigEntity::getConfigKey, config.getConfigKey()) .eqIfPresent(SysConfigEntity::getGroupCode, config.getGroupCode()) .betweenIfPresent(SysConfigEntity::getCreateTime, config.getParams()) ); } /** * 查询参数配置信息 * * @param config 参数配置信息 * @return 参数配置信息 */ default SysConfigEntity selectConfig(SysConfigEntity config) { return selectOne(new LambdaQueryWrapperX() .eq(ObjectUtil.isNotEmpty(config.getConfigId()), SysConfigEntity::getConfigId, config.getConfigId()) .eq(ObjectUtil.isNotEmpty(config.getConfigName()), SysConfigEntity::getConfigName, config.getConfigName()) .eq(ObjectUtil.isNotEmpty(config.getConfigKey()), SysConfigEntity::getConfigKey, config.getConfigKey())); } /** * 校验参数键名是否唯一 */ default SysConfigEntity checkConfigKeyUnique(SysConfigEntity config) { return selectOne(new LambdaQueryWrapperX() .eq(SysConfigEntity::getConfigKey, config.getConfigKey()) ); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysDictDataMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysDictDataEntity; import org.apache.ibatis.annotations.Param; import java.util.List; public interface SysDictDataMapper extends BaseMapperX { default PageResult selectPage(SysDictDataEntity dictData) { return selectPage( new LambdaQueryWrapperX() .likeIfPresent(SysDictDataEntity::getDictType, dictData.getDictType()) .likeIfPresent(SysDictDataEntity::getDictLabel, dictData.getDictLabel()) .eqIfPresent(SysDictDataEntity::getStatus, "0") .betweenIfPresent(SysDictDataEntity::getCreateTime, dictData.getParams()) .orderByAsc(SysDictDataEntity::getDictSort)); } default List selectDictDataByType(String dictType) { return selectList(new LambdaQueryWrapperX() .eq(SysDictDataEntity::getDictType, dictType) .eq(SysDictDataEntity::getStatus, "0") .orderByAsc(SysDictDataEntity::getDictSort)); } /** * 同步修改字典类型 * * @param oldDictType 旧字典类型 * @param newDictType 新旧字典类型 * @return 结果 */ // @Update("update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType}") default int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType) { SysDictDataEntity dictData = new SysDictDataEntity(); dictData.setDictType(newDictType); return update(dictData, new UpdateWrapper().eq("dict_type", oldDictType)); } /** * 查询字典数据 * * @param dictType 字典类型 * @return 字典数据 */ default Long countDictDataByType(String dictType) { // select count(1) from sys_dict_data where dict_type=#{dictType} return selectCount("dict_type", dictType); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysDictTypeMapper.java ================================================ package com.oddfar.campus.framework.mapper; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysDictTypeEntity; import java.util.Map; public interface SysDictTypeMapper extends BaseMapperX { default PageResult selectPage(SysDictTypeEntity dictType) { return selectPage(new LambdaQueryWrapperX() .likeIfPresent(SysDictTypeEntity::getDictName, dictType.getDictName()) .likeIfPresent(SysDictTypeEntity::getDictType, dictType.getDictType()) .eqIfPresent(SysDictTypeEntity::getStatus, dictType.getStatus()) .betweenIfPresent(SysDictTypeEntity::getCreateTime, dictType.getParams())); } default LambdaQueryWrapper creatWrapper(SysDictTypeEntity dictType) { Map params = dictType.getParams(); String beginTime = (String) params.get("beginTime"); String endTime = (String) params.get("endTime"); return new LambdaQueryWrapperX() .ge(ObjectUtil.isNotEmpty(beginTime), SysDictTypeEntity::getCreateTime, beginTime) .le(ObjectUtil.isNotEmpty(endTime), SysDictTypeEntity::getCreateTime, endTime); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysLoginLogMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysLoginLogEntity; import java.util.List; /** * 系统访问日志情况信息 数据层 * * @author ruoyi */ public interface SysLoginLogMapper extends BaseMapperX { default PageResult selectLogininforPage(SysLoginLogEntity logininfor) { return selectPage(new LambdaQueryWrapperX() .eqIfPresent(SysLoginLogEntity::getUserId, logininfor.getUserId()) .eqIfPresent(SysLoginLogEntity::getUserName,logininfor.getUserName()) .eqIfPresent(SysLoginLogEntity::getStatus, logininfor.getStatus()) .betweenIfPresent(SysLoginLogEntity::getLoginTime, logininfor.getParams()) .orderByDesc(SysLoginLogEntity::getInfoId) ); } /** * 查询系统登录日志集合 * * @param logininfor 访问日志对象 * @return 登录记录集合 */ default List selectLogininforList(SysLoginLogEntity logininfor) { return selectList(new LambdaQueryWrapperX() .eqIfPresent(SysLoginLogEntity::getStatus, logininfor.getStatus()) .likeIfPresent(SysLoginLogEntity::getUserName, logininfor.getUserName()) .eqIfPresent(SysLoginLogEntity::getUserId, logininfor.getUserId())); } /** * 清空系统登录日志 * * @return 结果 */ int cleanLogininfor(); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysMenuMapper.java ================================================ package com.oddfar.campus.framework.mapper; import cn.hutool.core.util.ObjectUtil; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysMenuEntity; import com.oddfar.campus.common.domain.model.SysRoleAuth; import org.apache.ibatis.annotations.Param; import java.util.List; public interface SysMenuMapper extends BaseMapperX { default PageResult selectPage(SysMenuEntity sysMenuEntity) { return selectPage(new LambdaQueryWrapperX()); } /** * 查询系统菜单列表 * * @param menu 菜单信息 * @return 菜单列表 */ default List selectMenuList(SysMenuEntity menu) { return selectList(new LambdaQueryWrapperX() .like(ObjectUtil.isNotNull(menu.getMenuName()), SysMenuEntity::getMenuName, menu.getMenuName()) .eq(ObjectUtil.isNotNull(menu.getVisible()), SysMenuEntity::getVisible, menu.getVisible()) .eq(ObjectUtil.isNotNull(menu.getStatus()), SysMenuEntity::getStatus, menu.getStatus())); } /** * 根据用户查询系统菜单列表 * * @param menu 菜单信息 * @return 菜单列表 */ List selectMenuListByUserId(SysMenuEntity menu); /** * 根据用户ID查询菜单 * * @return 菜单列表 */ List selectMenuTreeAll(); /** * 根据用户ID查询菜单 * * @param userId 用户ID * @return 菜单列表 */ List selectMenuTreeByUserId(Long userId); /** * 根据角色ID查询权限 * * @param roleId 角色ID * @return 权限列表 */ List selectMenuPermsByRoleId(Long roleId); /** * 查询所有角色的权限列表 * * @return SysRolePerms */ List getMenuPermsAll(); /** * 根据用户ID查询权限 * * @param userId 用户ID * @return 权限列表 */ List selectMenuPermsByUserId(Long userId); /** * 根据角色ID查询菜单树信息 * * @param roleId 角色ID * @param menuCheckStrictly 菜单树选择项是否关联显示 * @return 选中菜单列表 */ List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); /** * 校验菜单名称是否唯一 */ default SysMenuEntity checkMenuNameUnique(SysMenuEntity menu) { return selectOne(new LambdaQueryWrapperX() .eq(SysMenuEntity::getMenuName, menu.getMenuName()) .eq(SysMenuEntity::getParentId, menu.getParentId()) ); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysOperLogMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysOperLogEntity; import java.util.Arrays; import java.util.List; /** * 操作日志 数据层 */ public interface SysOperLogMapper extends BaseMapperX { /** * 分页查询系统操作日志集合 * * @param operLog 操作日志对象 * @return 操作日志集合 */ default PageResult selectOperLogPage(SysOperLogEntity operLog) { return selectPage(new LambdaQueryWrapperX() .likeIfPresent(SysOperLogEntity::getAppName, operLog.getAppName()) .likeIfPresent(SysOperLogEntity::getLogName, operLog.getLogName()) .eqIfPresent(SysOperLogEntity::getStatus,operLog.getStatus()) .eqIfPresent(SysOperLogEntity::getOperIp, operLog.getOperIp()) .eqIfPresent(SysOperLogEntity::getOperId,operLog.getOperId()) .betweenIfPresent(SysOperLogEntity::getOperTime, operLog.getParams()) .orderByDesc(SysOperLogEntity::getOperId) ); } /** * 批量删除系统操作日志 * * @param operIds 需要删除的操作日志ID * @return 结果 */ default int deleteOperLogByIds(Long[] operIds) { return deleteBatchIds(Arrays.asList(operIds)); } /** * 清空操作日志 */ public void cleanOperLog(); /** * 查询系统操作日志集合 * @return */ default List selectOperLogList(SysOperLogEntity operLog){ return selectList(new LambdaQueryWrapperX() .likeIfPresent(SysOperLogEntity::getAppName, operLog.getAppName()) .likeIfPresent(SysOperLogEntity::getLogName, operLog.getLogName()) .eqIfPresent(SysOperLogEntity::getOperIp, operLog.getOperIp()) .betweenIfPresent(SysOperLogEntity::getOperTime, operLog.getParams()) ); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysResourceMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import org.apache.ibatis.annotations.Update; import java.util.List; import java.util.Set; public interface SysResourceMapper extends BaseMapperX { default PageResult selectPage(SysResourceEntity resource) { return selectPage(new LambdaQueryWrapperX() .betweenIfPresent(SysResourceEntity::getCreateTime, resource.getParams())); } /** * 清空 sys_resource 数据库 */ @Update("truncate table sys_resource") void truncateResource(); /** * 根据角色ID查询资源编码列表 * * @param roleId 角色ID * @return 权限列表 */ Set selectResourceCodeByRoleId(Long roleId); /** * 根据角色ID查询资源树信息 * * @param roleId 角色ID * @return 选中接口资源列表 */ List selectResourceListByRoleId(Long roleId); /** * 根据用户查询api资源列表 * * @param resource * @return */ List selectResourceListByUserId(SysResourceEntity resource); /** * 查询资源列表 * * @param resource * @return */ List selectResourceList(SysResourceEntity resource); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysRoleMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import org.apache.ibatis.annotations.Param; import java.util.List; import java.util.Set; public interface SysRoleMapper extends BaseMapperX { default PageResult selectPage(SysRoleEntity role) { return selectPage(new LambdaQueryWrapperX() .likeIfPresent(SysRoleEntity::getRoleName, role.getRoleName()) .likeIfPresent(SysRoleEntity::getRoleKey, role.getRoleKey()) .eqIfPresent(SysRoleEntity::getStatus, role.getStatus()) ); } /** * 根据用户ID查询角色 * * @param userId 用户ID * @return 角色列表 */ List selectRolePermissionByUserId(Long userId); /** * 通过角色ID查询角色 * * @param roleId 角色ID * @return 角色对象信息 */ SysRoleEntity selectRoleById(Long roleId); /** * 根据条件分页查询角色数据 * * @param role 角色信息 * @return 角色数据集合信息 */ List selectRoleList(SysRoleEntity role); /** * 根据角色权限字符串查询角色数据 * @param RoleKeys * @return */ List selectRoleListByKey(@Param("RoleKeys") Set RoleKeys); /** * 校验角色名称是否唯一 * * @param roleName 角色名称 * @return 角色信息 */ SysRoleEntity checkRoleNameUnique(String roleName); /** * 校验角色权限是否唯一 * * @param roleKey 角色权限 * @return 角色信息 */ SysRoleEntity checkRoleKeyUnique(String roleKey); /** * 根据用户ID查询角色 * * @param userName 用户名 * @return 角色列表 */ public List selectRolesByUserName(String userName); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysRoleMenuMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.domain.entity.SysRoleMenuEntity; import java.util.List; public interface SysRoleMenuMapper extends BaseMapperX { /** * 查询菜单使用数量 * * @param menuId 菜单ID * @return 结果 */ public int checkMenuExistRole(Long menuId); /** * 通过角色ID删除角色和菜单关联 * * @param roleId 角色ID * @return 结果 */ public int deleteRoleMenuByRoleId(Long roleId); /** * 批量删除角色菜单关联信息 * * @param ids 需要删除的数据ID * @return 结果 */ public int deleteRoleMenu(Long[] ids); /** * 批量新增角色菜单信息 * * @param roleMenuList 角色菜单列表 * @return 结果 */ public int batchRoleMenu(List roleMenuList); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysRoleResourceMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.domain.entity.SysRoleResourceEntity; import java.util.List; public interface SysRoleResourceMapper extends BaseMapperX { /** * 删除角色与资源关联 * * @param roleId 角色id */ default int deleteRoleResourceByRoleId(Long roleId) { return delete(new QueryWrapper().eq("role_id", roleId)); } /** * 批量保存角色与资源关系 * * @param rrList */ int saveBatch(List rrList); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysUserMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysUserEntity; import org.apache.ibatis.annotations.Param; public interface SysUserMapper extends BaseMapperX { default PageResult selectPage(SysUserEntity user) { return selectPage(new LambdaQueryWrapperX() .likeIfPresent(SysUserEntity::getUserName, user.getUserName()) .likeIfPresent(SysUserEntity::getPhonenumber, user.getPhonenumber()) .eqIfPresent(SysUserEntity::getStatus, user.getStatus()) .betweenIfPresent(SysUserEntity::getCreateTime, user.getParams()) ); } /** * 通过用户名查询用户 * * @param userName * @return */ SysUserEntity selectUserByUserName(String userName); /** * 通过用户ID查询用户 * * @param userId 用户ID * @return 用户对象信息 */ SysUserEntity selectUserById(Long userId); /** * 根据条件分页查询已配用户角色列表 * * @return 用户信息集合信息 */ Page selectAllocatedList(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); /** * 根据条件分页查询未分配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ Page selectUnallocatedList(@Param("page") Page page, @Param("user") SysUserEntity user); /** * 修改用户头像 * * @param userName 用户名 * @param avatar 头像地址 * @return 结果 */ int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar); /** * 校验email是否唯一 * * @param email 用户邮箱 * @return 结果 */ default SysUserEntity checkEmailUnique(String email) { return selectOne(new LambdaQueryWrapperX().eq(SysUserEntity::getEmail, email)); } /** * 校验手机号码是否唯一 * * @param phonenumber 手机号码 * @return 结果 */ default SysUserEntity checkPhoneUnique(String phonenumber) { return selectOne(new LambdaQueryWrapperX().eq(SysUserEntity::getPhonenumber, phonenumber)); } /** * 重置用户密码 * * @param userName 用户名 * @param password 密码 * @return 结果 */ int resetUserPwd(@Param("userName") String userName, @Param("password") String password); /** * 校验用户名称是否唯一 * * @param userName 用户名称 * @return 结果 */ default SysUserEntity checkUserNameUnique(String userName) { return selectOne(new LambdaQueryWrapperX().eq(SysUserEntity::getUserName, userName)); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/mapper/SysUserRoleMapper.java ================================================ package com.oddfar.campus.framework.mapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.domain.entity.SysUserRoleEntity; import org.apache.ibatis.annotations.Param; public interface SysUserRoleMapper extends BaseMapperX { /** * 通过用户ID删除用户和角色关联 * * @param userId 用户ID * @return 结果 */ default int deleteUserRoleByUserId(Long userId) { return delete(new QueryWrapper() .eq("user_id" , userId)); } /** * 删除用户和角色关联信息 * * @param userRole 用户和角色关联信息 * @return 结果 */ default int deleteUserRoleInfo(SysUserRoleEntity userRole) { return delete(new QueryWrapper() .eq("user_id" , userRole.getUserId()) .eq("role_id" , userRole.getRoleId())); } /** * 批量取消授权用户角色 * * @param roleId 角色ID * @param userIds 需要删除的用户数据ID * @return 结果 */ default int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds) { return delete(new QueryWrapper() .eq("role_id" , roleId) .in("user_id" , userIds)); } /** * 通过角色ID查询角色使用数量 * * @param roleId 角色ID * @return 结果 */ default int countUserRoleByRoleId(Long roleId) { return selectCount("role_id" , roleId).intValue(); } /** * 批量删除用户和角色关联 * * @param userIds 需要删除的数据ID * @return 结果 */ void deleteUserRole(Long[] userIds); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/security/context/AuthenticationContextHolder.java ================================================ package com.oddfar.campus.framework.security.context; import org.springframework.security.core.Authentication; /** * 身份验证信息 * * @author ruoyi */ public class AuthenticationContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal<>(); public static Authentication getContext() { return contextHolder.get(); } public static void setContext(Authentication context) { contextHolder.set(context); } public static void clearContext() { contextHolder.remove(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/security/context/PermissionContextHolder.java ================================================ package com.oddfar.campus.framework.security.context; import cn.hutool.core.convert.Convert; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; /** * 权限信息 * * @author ruoyi */ public class PermissionContextHolder { private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; public static void setContext(String permission) { RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, RequestAttributes.SCOPE_REQUEST); } public static String getContext() { return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, RequestAttributes.SCOPE_REQUEST)); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/security/filter/JwtAuthenticationTokenFilter.java ================================================ package com.oddfar.campus.framework.security.filter; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.web.WebFrameworkUtils; import com.oddfar.campus.framework.web.service.TokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * token过滤器 验证token有效性 * * @author ruoyi */ @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private TokenService tokenService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { LoginUser loginUser = tokenService.getLoginUser(request); if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) { // 设置当前用户 setLoginUser(loginUser, request); tokenService.verifyToken(loginUser); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } chain.doFilter(request, response); } /** * 设置当前用户 * * @param loginUser 登录用户 * @param request 请求 */ public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { // 额外设置到 request 中,有的过滤器在 Spring Security 之前 WebFrameworkUtils.setLoginUserId(request, loginUser.getUserId()); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/security/handle/AuthenticationEntryPointImpl.java ================================================ package com.oddfar.campus.framework.security.handle; import com.alibaba.fastjson2.JSON; import com.oddfar.campus.common.constant.HttpStatus; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.StringUtils; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Serializable; /** * 认证失败处理类 返回未授权 * * @author ruoyi */ @Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable { private static final long serialVersionUID = -8970718410437077606L; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException { int code = HttpStatus.UNAUTHORIZED; String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); ServletUtils.renderString(response, JSON.toJSONString(R.error(code, msg))); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/security/handle/LogoutSuccessHandlerImpl.java ================================================ package com.oddfar.campus.framework.security.handle; import com.alibaba.fastjson2.JSON; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.constant.HttpStatus; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.utils.MessageUtils; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.manager.AsyncFactory; import com.oddfar.campus.framework.manager.AsyncManager; import com.oddfar.campus.framework.web.service.TokenService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 自定义退出处理类 返回成功 * * @author ruoyi */ @Configuration public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { @Autowired private TokenService tokenService; /** * 退出处理 * */ @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { LoginUser loginUser = tokenService.getLoginUser(request); if (StringUtils.isNotNull(loginUser)) { // 删除用户缓存记录 tokenService.delLoginUser(loginUser.getToken()); // 记录用户退出日志 AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getUsername(), loginUser.getUserId(), Constants.LOGOUT, MessageUtils.message("user.logout.success"))); } ServletUtils.renderString(response, JSON.toJSONString(R.error(HttpStatus.SUCCESS, MessageUtils.message("user.logout.success")))); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/security/properties/PermitAllUrlProperties.java ================================================ package com.oddfar.campus.framework.security.properties; import com.oddfar.campus.common.annotation.Anonymous; import org.apache.commons.lang3.RegExUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; /** * 设置Anonymous注解允许匿名访问的url * * @author ruoyi */ @Configuration public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware { private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); private ApplicationContext applicationContext; private List urls = new ArrayList<>(); public String ASTERISK = "*"; @Override public void afterPropertiesSet() { RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); Map map = mapping.getHandlerMethods(); map.keySet().forEach(info -> { HandlerMethod handlerMethod = map.get(info); // 获取方法上边的注解 替代path variable 为 * Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); Optional.ofNullable(method).ifPresent(anonymous -> info.getPatternsCondition().getPatterns() .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); // 获取类上边的注解, 替代path variable 为 * Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); Optional.ofNullable(controller).ifPresent(anonymous -> info.getPatternsCondition().getPatterns() .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); }); } @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.applicationContext = context; } public List getUrls() { return urls; } public void setUrls(List urls) { this.urls = urls; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysConfigService.java ================================================ package com.oddfar.campus.framework.service; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysConfigEntity; public interface SysConfigService { PageResult page(SysConfigEntity sysConfigEntity); /** * 查询参数配置信息 * * @param configId 参数配置ID * @return 参数配置信息 */ SysConfigEntity selectConfigById(Long configId); /** * 根据键名查询参数配置信息 * * @param configKey 参数键名 * @return 参数键值 */ String selectConfigByKey(String configKey); /** * 根据键名查询参数配置信息 * * @param configKey * @param clazz 转换的类 * @param * @return */ T selectConfigByKey(String configKey, Class clazz); /** * 根据键名查询参数配置信息 * 查询的值为null则返回defaultValue * * @param configKey * @param clazz * @param defaultValue 默认的内容 * @param * @return */ T selectConfigByKey(String configKey, Class clazz, T defaultValue); /** * 获取验证码开关 * * @return true开启,false关闭 */ boolean selectCaptchaEnabled(); /** * 新增参数配置 * * @param config 参数配置信息 * @return 结果 */ int insertConfig(SysConfigEntity config); /** * 修改参数配置 * * @param config 参数配置信息 * @return 结果 */ int updateConfig(SysConfigEntity config); /** * 批量删除参数信息 * * @param configIds 需要删除的参数ID */ void deleteConfigByIds(Long[] configIds); /** * 加载参数缓存数据 */ void loadingConfigCache(); /** * 校验参数键名是否唯一 * * @param config 参数配置信息 * @return 结果 true为唯一 */ boolean checkConfigKeyUnique(SysConfigEntity config); /** * 清空参数缓存数据 */ void clearConfigCache(); /** * 重置参数缓存数据 */ void resetConfigCache(); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysDictDataService.java ================================================ package com.oddfar.campus.framework.service; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysDictDataEntity; public interface SysDictDataService { PageResult page(SysDictDataEntity dictDataEntity); /** * 新增保存字典数据信息 * * @param dictData 字典数据信息 * @return 结果 */ int insertDictData(SysDictDataEntity dictData); /** * 根据字典数据ID查询信息 * * @param dictCode 字典数据ID * @return 字典数据 */ SysDictDataEntity selectDictDataById(Long dictCode); /** * 修改保存字典数据信息 * * @param dictData 字典数据信息 * @return 结果 */ int updateDictData(SysDictDataEntity dictData); /** * 批量删除字典数据信息 * * @param dictCodes 需要删除的字典数据ID */ void deleteDictDataByIds(Long[] dictCodes); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysDictTypeService.java ================================================ package com.oddfar.campus.framework.service; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysDictDataEntity; import com.oddfar.campus.common.domain.entity.SysDictTypeEntity; import java.util.List; public interface SysDictTypeService { PageResult page(SysDictTypeEntity sysDictTypeEntity); /** * 根据字典类型查询字典数据 * * @param dictType 字典类型 * @return 字典数据集合信息 */ List selectDictDataByType(String dictType); /** * 根据字典类型ID查询信息 * * @param dictId 字典类型ID * @return 字典类型 */ SysDictTypeEntity selectDictTypeById(Long dictId); /** * 查询所有字典类型 * * @return 字典类型集合信息 */ List selectDictTypeAll(); /** * 修改保存字典类型信息 * * @param dictType 字典类型信息 * @return 结果 */ int updateDictType(SysDictTypeEntity dictType); /** * 新增保存字典类型信息 * * @param dictType 字典类型信息 * @return 结果 */ int insertDictType(SysDictTypeEntity dictType); /** * 批量删除字典信息 * * @param dictIds 需要删除的字典ID */ void deleteDictTypeByIds(Long[] dictIds); /** * 重置字典缓存数据 */ void resetDictCache(); /** * 加载字典缓存数据 */ void loadingDictCache(); /** * 校验字典类型称是否唯一 * * @param dictType 字典类型 * @return 结果 */ boolean checkDictTypeUnique(SysDictTypeEntity dictType); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysLoginLogService.java ================================================ package com.oddfar.campus.framework.service; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysLoginLogEntity; import java.util.List; /** * 系统访问日志情况信息 服务层 */ public interface SysLoginLogService { /** * 查询系统登录日志分页数据 * * @param logininfor 访问日志对象 * @return 登录记录分页数据 */ public PageResult selectLogininforPage(SysLoginLogEntity logininfor); /** * 新增系统登录日志 * * @param logininfor 访问日志对象 */ public void insertLogininfor(SysLoginLogEntity logininfor); /** * 查询系统登录日志集合 * * @param logininfor 访问日志对象 * @return 登录记录集合 */ public List selectLogininforList(SysLoginLogEntity logininfor); /** * 批量删除系统登录日志 * * @param infoIds 需要删除的登录日志ID * @return 结果 */ public int deleteLogininforByIds(Long[] infoIds); /** * 清空系统登录日志 */ public void cleanLogininfor(); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysMenuService.java ================================================ package com.oddfar.campus.framework.service; import com.oddfar.campus.common.domain.TreeSelect; import com.oddfar.campus.common.domain.entity.SysMenuEntity; import com.oddfar.campus.common.domain.model.SysRoleAuth; import com.oddfar.campus.common.domain.vo.RouterVo; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; public interface SysMenuService { /** * 根据用户查询系统菜单列表 * * @param userId 用户ID * @return 菜单列表 */ List selectMenuList(Long userId); /** * 根据用户查询系统菜单列表 * * @param menu 菜单信息 * @param userId 用户ID * @return 菜单列表 */ List selectMenuList(SysMenuEntity menu, Long userId); /** * 根据用户ID查询菜单树信息 * * @param userId 用户ID * @return 菜单列表 */ List selectMenuTreeByUserId(Long userId); /** * 根据角色ID查询菜单树信息 * * @param roleId 角色ID * @return 选中菜单列表 */ List selectMenuListByRoleId(Long roleId); /** * 根据菜单ID查询信息 * * @param menuId 菜单ID * @return 菜单信息 */ SysMenuEntity selectMenuById(Long menuId); /** * 构建前端路由所需要的菜单 * * @param menus 菜单列表 * @return 路由列表 */ List buildMenus(List menus); /** * 构建前端所需要树结构 * * @param menus 菜单列表 * @return 树结构列表 */ List buildMenuTree(List menus); /** * 构建前端所需要下拉树结构 * * @param menus 菜单列表 * @return 下拉树结构列表 */ List buildMenuTreeSelect(List menus); /** * 根据角色ID查询权限 * * @param roleId 角色ID * @return 权限列表 */ Set selectMenuPermsByRoleId(Long roleId); /** * 所有权限 * * @return 根据角色id分组的权限列表 */ Map> selectMenuPermsAll(); /** * 根据用户ID查询权限 * * @param userId 用户ID * @return 权限列表 */ Collection selectMenuPermsByUserId(Long userId); /** * 新增保存菜单信息 * * @param menu 菜单信息 * @return 结果 */ int insertMenu(SysMenuEntity menu); /** * 修改保存菜单信息 * * @param menu 菜单信息 * @return 结果 */ int updateMenu(SysMenuEntity menu); /** * 删除菜单管理信息 * * @param menuId 菜单ID * @return 结果 */ int deleteMenuById(Long menuId); /** * 查询菜单是否存在角色 * * @param menuId 菜单ID * @return 结果 true 存在 false 不存在 */ boolean checkMenuExistRole(Long menuId); /** * 是否存在菜单子节点 * * @param menuId 菜单ID * @return 结果 true 存在 false 不存在 */ boolean hasChildByMenuId(Long menuId); /** * 校验菜单名称是否唯一 * * @param menu 菜单信息 * @return 结果 true为唯一 */ boolean checkMenuNameUnique(SysMenuEntity menu); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysOperLogService.java ================================================ package com.oddfar.campus.framework.service; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysOperLogEntity; import java.util.List; /** * 操作日志 服务层 */ public interface SysOperLogService { /** * 新增操作日志 * * @param operLog 操作日志对象 */ public void insertOperlog(SysOperLogEntity operLog); /** * 查询系统操作日志集合 * * @param operLog 操作日志对象 * @return 操作日志集合 */ PageResult selectOperLogPage(SysOperLogEntity operLog); /** * 批量删除系统操作日志 * * @param operIds 需要删除的操作日志ID * @return 结果 */ public int deleteOperLogByIds(Long[] operIds); /** * 查询操作日志详细 * * @param operId 操作ID * @return 操作日志对象 */ public SysOperLogEntity selectOperLogById(Long operId); /** * 清空操作日志 */ public void cleanOperLog(); /** * 查询操作日志列表 * * @param operLog 操作日志对象 * @return */ List selectOperLogList(SysOperLogEntity operLog); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysResourceService.java ================================================ package com.oddfar.campus.framework.service; import com.baomidou.mybatisplus.extension.service.IService; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.TreeSelect; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import com.oddfar.campus.common.domain.model.SysRoleAuth; import java.util.List; import java.util.Set; public interface SysResourceService extends IService { PageResult page(SysResourceEntity sysResourceEntity); /** * 新增接口资源信息 * * @param resource * @return */ int insertResource(SysResourceEntity resource); /** * 清空 sys_resource 数据库 */ void truncateResource(); /** * 根据角色ID查询资源编码列表 * * @param roleId 角色ID * @return 权限列表 */ Set selectResourceCodeByRoleId(Long roleId); /** * 根据用户id查询api资源列表 * * @param userId * @return */ List selectApiResourceList(Long userId); /** * 查询所有SysRoleAuth关系,关于resource的 */ List selectSysRoleAuthAll(); /** * 根据用户id查询api资源列表 * * @param userId * @return */ List selectApiResourceList(SysResourceEntity resource, Long userId); /** * 根据角色ID查询资源树信息 * * @param roleId 角色ID * @return 选中接口资源列表 */ List selectResourceListByRoleId(Long roleId); /** * 构建前端所需要下拉树结构 * * @param resources 资源列表 * @return 下拉树结构列表 */ List buildResourceTreeSelect(List resources); /** * 修改角色 * * @param roleId * @param resourceIds */ void editRoleResource(Long roleId, Long[] resourceIds); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysRoleService.java ================================================ package com.oddfar.campus.framework.service; import com.baomidou.mybatisplus.extension.service.IService; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import com.oddfar.campus.common.domain.entity.SysUserRoleEntity; import java.util.List; import java.util.Set; public interface SysRoleService extends IService { PageResult page(SysRoleEntity sysRoleEntity); /** * 根据条件分页查询角色数据 * * @param role 角色信息 * @return 角色数据集合信息 */ List selectRoleList(SysRoleEntity role); /** * 根据用户ID查询角色权限 * * @param userId 用户ID * @return 权限列表 */ Set selectRolePermissionByUserId(Long userId); /** * 通过角色ID查询角色 * * @param roleId 角色ID * @return 角色对象信息 */ SysRoleEntity selectRoleById(Long roleId); /** * 查询所有角色 * * @return 角色列表 */ List selectRoleAll(); /** * 根据用户ID查询角色列表 * * @param userId 用户ID * @return 角色列表 */ List selectRolesByUserId(Long userId); /** * 新增保存角色信息 * * @param role 角色信息 * @return 结果 */ int insertRole(SysRoleEntity role); /** * 修改保存角色信息 * * @param role 角色信息 * @return 结果 */ int updateRole(SysRoleEntity role); /** * 修改角色状态 * * @param role 角色信息 * @return 结果 */ int updateRoleStatus(SysRoleEntity role); /** * 批量删除角色信息 * * @param roleIds 需要删除的角色ID * @return 结果 */ int deleteRoleByIds(Long[] roleIds); /** * 取消授权用户角色 * * @param userRole 用户和角色关联信息 * @return 结果 */ int deleteAuthUser(SysUserRoleEntity userRole); /** * 批量取消授权用户角色 * * @param roleId 角色ID * @param userIds 需要取消授权的用户数据ID * @return 结果 */ int deleteAuthUsers(Long roleId, Long[] userIds); /** * 批量选择授权用户角色 * * @param roleId 角色ID * @param userIds 需要删除的用户数据ID * @return 结果 */ boolean insertAuthUsers(Long roleId, Long[] userIds); /** * 通过角色ID查询角色使用数量 * * @param roleId 角色ID * @return 结果 */ int countUserRoleByRoleId(Long roleId); /** * 校验角色名称是否唯一 * * @param role 角色信息 * @return 结果 */ boolean checkRoleNameUnique(SysRoleEntity role); /** * 校验角色权限是否唯一 * * @param role 角色信息 * @return 结果 */ boolean checkRoleKeyUnique(SysRoleEntity role); /** * 校验角色是否允许操作 * * @param role 角色信息 */ void checkRoleAllowed(SysRoleEntity role); /** * 重置角色的资源和菜单权限缓存 */ void resetRoleAuthCache(); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/SysUserService.java ================================================ package com.oddfar.campus.framework.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysUserEntity; import java.util.Set; public interface SysUserService { PageResult page(SysUserEntity sysUserEntity); /** * 通过用户名查询用户 * * @param userName 用户名 * @return 用户对象信息 */ SysUserEntity selectUserByUserName(String userName); /** * 通过用户ID查询用户 * * @param userId 用户ID * @return 用户对象信息 */ SysUserEntity selectUserById(Long userId); /** * 根据条件分页查询已分配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ Page selectAllocatedList(SysUserEntity user); /** * 根据条件分页查询未分配用户角色列表 * * @param user 用户信息 * @return 用户信息集合信息 */ Page selectUnallocatedList(SysUserEntity user); /** * 根据用户ID查询用户所属角色组 * * @param userName 用户名 * @return 结果 */ String selectUserRoleGroup(String userName); /** * 注册用户信息 * * @param user 用户信息 * @return 结果 */ boolean registerUser(SysUserEntity user); /** * 新增用户信息 * * @param user 用户信息 * @return 结果 */ int insertUser(SysUserEntity user); /** * 修改用户信息 * * @param user 用户信息 * @return 结果 */ int updateUser(SysUserEntity user); /** * 批量删除用户信息 * * @param userIds 需要删除的用户ID * @return 结果 */ int deleteUserByIds(Long[] userIds); /** * 修改用户基本信息 * * @param user 用户信息 * @return 结果 */ int updateUserProfile(SysUserEntity user); /** * 修改用户状态 * * @param user 用户信息 * @return 结果 */ int updateUserStatus(SysUserEntity user); /** * 修改用户头像 * * @param userName 用户名 * @param avatar 头像地址 * @return 结果 */ boolean updateUserAvatar(String userName, String avatar); /** * 重置用户密码 * * @param user 用户信息 * @return 结果 */ int resetPwd(SysUserEntity user); /** * 校验用户是否允许操作 * * @param user 用户信息 */ void checkUserAllowed(SysUserEntity user); /** * 用户授权角色(先删除再添加) * * @param userId 用户ID * @param roleIds 角色组 */ void insertUserAuth(Long userId, Long[] roleIds); /** * 用户授权角色 * * @param userId 用户ID * @param roleKey 角色权限字符串 */ void insertUserAuth(Long userId, Set roleKey); /** * 重置用户密码 * * @param userName 用户名 * @param password 密码 * @return 结果 */ int resetUserPwd(String userName, String password); /** * 校验用户名称是否唯一 * * @param user 用户信息 * @return 结果 */ boolean checkUserNameUnique(SysUserEntity user); /** * 校验手机号码是否唯一 * * @param user 用户信息 * @return 结果 true为唯一 */ boolean checkPhoneUnique(SysUserEntity user); /** * 校验email是否唯一 * * @param user 用户信息 * @return 结果 true为唯一 */ boolean checkEmailUnique(SysUserEntity user); } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysConfigServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ObjectUtil; import com.oddfar.campus.common.constant.CacheConstants; import com.oddfar.campus.common.constant.UserConstants; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysConfigEntity; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.mapper.SysConfigMapper; import com.oddfar.campus.framework.service.SysConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.util.Collection; import java.util.List; @Service public class SysConfigServiceImpl implements SysConfigService { @Resource private SysConfigMapper configMapper; @Autowired private RedisCache redisCache; /** * 项目启动时,初始化参数到缓存 */ @PostConstruct public void init() { loadingConfigCache(); } @Override public PageResult page(SysConfigEntity sysConfigEntity) { return configMapper.selectPage(sysConfigEntity); } @Override public SysConfigEntity selectConfigById(Long configId) { SysConfigEntity config = new SysConfigEntity(); config.setConfigId(configId); return configMapper.selectById(config); } /** * 根据键名查询参数配置信息 * * @param configKey 参数key * @return 参数键值 */ @Override public String selectConfigByKey(String configKey) { String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey))); if (StringUtils.isNotEmpty(configValue)) { return configValue; } SysConfigEntity config = new SysConfigEntity(); config.setConfigKey(configKey); SysConfigEntity retConfig = configMapper.selectConfig(config); if (StringUtils.isNotNull(retConfig)) { redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue()); return retConfig.getConfigValue(); } return StringUtils.EMPTY; } @Override public T selectConfigByKey(String configKey, Class clazz) { T configValue = redisCache.getCacheObject(getCacheKey(configKey)); if (ObjectUtil.isNotEmpty(configValue)) { return configValue; } SysConfigEntity config = new SysConfigEntity(); config.setConfigKey(configKey); SysConfigEntity retConfig = configMapper.selectConfig(config); if (ObjectUtil.isNotNull(retConfig)) { redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue()); return Convert.convert(clazz, retConfig.getConfigValue()); } return null; } @Override public T selectConfigByKey(String configKey, Class clazz, T defaultValue) { T value = this.selectConfigByKey(configKey, clazz); return value == null ? defaultValue : value; } /** * 获取验证码开关 * * @return true开启,false关闭 */ @Override public boolean selectCaptchaEnabled() { String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled"); if (StringUtils.isEmpty(captchaEnabled)) { return true; } return Convert.toBool(captchaEnabled); } @Override public int insertConfig(SysConfigEntity config) { int row = configMapper.insert(config); if (row > 0) { redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); } return row; } @Override public int updateConfig(SysConfigEntity config) { int row = configMapper.updateById(config); if (row > 0) { redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); } return row; } @Override public void deleteConfigByIds(Long[] configIds) { for (Long configId : configIds) { SysConfigEntity config = selectConfigById(configId); if (StringUtils.equals(UserConstants.YES, config.getConfigType())) { throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); } configMapper.deleteById(configId); redisCache.deleteObject(getCacheKey(config.getConfigKey())); } } /** * 加载参数缓存数据 */ @Override public void loadingConfigCache() { List configsList = configMapper.selectList(); for (SysConfigEntity config : configsList) { redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); } } @Override public boolean checkConfigKeyUnique(SysConfigEntity config) { Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId(); SysConfigEntity info = configMapper.checkConfigKeyUnique(config); if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) { return false; } return true; } @Override public void clearConfigCache() { Collection keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*"); redisCache.deleteObject(keys); } @Override public void resetConfigCache() { clearConfigCache(); loadingConfigCache(); } /** * 设置cache key * * @param configKey 参数键 * @return 缓存键key */ private String getCacheKey(String configKey) { return CacheConstants.SYS_CONFIG_KEY + configKey; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysDictDataServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysDictDataEntity; import com.oddfar.campus.common.utils.DictUtils; import com.oddfar.campus.framework.mapper.SysDictDataMapper; import com.oddfar.campus.framework.service.SysDictDataService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; @Service public class SysDictDataServiceImpl implements SysDictDataService { @Resource private SysDictDataMapper dictDataMapper; @Override public PageResult page(SysDictDataEntity dictDataEntity) { return dictDataMapper.selectPage(dictDataEntity); } @Override public int insertDictData(SysDictDataEntity dictData) { int row = dictDataMapper.insert(dictData); if (row > 0) { List dictDatas = dictDataMapper.selectDictDataByType(dictData.getDictType()); DictUtils.setDictCache(dictData.getDictType(), dictDatas); } return row; } @Override public SysDictDataEntity selectDictDataById(Long dictCode) { return dictDataMapper.selectById(dictCode); } @Override public int updateDictData(SysDictDataEntity data) { int row = dictDataMapper.updateById(data); if (row > 0) { List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); DictUtils.setDictCache(data.getDictType(), dictDatas); } return row; } @Override public void deleteDictDataByIds(Long[] dictCodes) { for (Long dictCode : dictCodes) { SysDictDataEntity data = dictDataMapper.selectById(dictCode); dictDataMapper.deleteById(dictCode); List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); DictUtils.setDictCache(data.getDictType(), dictDatas); } } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysDictTypeServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysDictDataEntity; import com.oddfar.campus.common.domain.entity.SysDictTypeEntity; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.DictUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.mapper.SysDictDataMapper; import com.oddfar.campus.framework.mapper.SysDictTypeMapper; import com.oddfar.campus.framework.service.SysDictTypeService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static com.oddfar.campus.common.utils.DictUtils.clearDictCache; @Service public class SysDictTypeServiceImpl implements SysDictTypeService { @Resource private SysDictTypeMapper dictTypeMapper; @Resource private SysDictDataMapper dictDataMapper; @Override public PageResult page(SysDictTypeEntity sysDictTypeEntity) { return dictTypeMapper.selectPage(sysDictTypeEntity); } /** * 根据字典类型查询字典数据 * * @param dictType 字典类型 * @return 字典数据集合信息 */ @Override public List selectDictDataByType(String dictType) { List dictDatas = DictUtils.getDictCache(dictType); if (StringUtils.isNotEmpty(dictDatas)) { return dictDatas; } dictDatas = dictDataMapper.selectDictDataByType(dictType); if (StringUtils.isNotEmpty(dictDatas)) { DictUtils.setDictCache(dictType, dictDatas); return dictDatas; } return null; } @Override public SysDictTypeEntity selectDictTypeById(Long dictId) { return dictTypeMapper.selectById(dictId); } @Override public List selectDictTypeAll() { return dictTypeMapper.selectList(); } @Override @Transactional public int updateDictType(SysDictTypeEntity dictType) { SysDictTypeEntity oldDict = dictTypeMapper.selectById(dictType.getDictId()); dictDataMapper.updateDictDataType(oldDict.getDictType(), dictType.getDictType()); //把要更新的dictType的内容赋值到oldDict // BeanUtil.copyProperties(dictType, oldDict); int row = dictTypeMapper.updateById(dictType); if (row > 0) { List dictDatas = dictDataMapper.selectDictDataByType(dictType.getDictType()); DictUtils.setDictCache(dictType.getDictType(), dictDatas); } return row; } @Override public int insertDictType(SysDictTypeEntity dictType) { int row = dictTypeMapper.insert(dictType); if (row > 0) { DictUtils.setDictCache(dictType.getDictType(), null); } return row; } @Override public void deleteDictTypeByIds(Long[] dictIds) { for (Long dictId : dictIds) { SysDictTypeEntity dictType = selectDictTypeById(dictId); if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0) { throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName())); } // this.removeById(dictId); dictTypeMapper.deleteById(dictId); DictUtils.removeDictCache(dictType.getDictType()); } } @Override public void resetDictCache() { clearDictCache(); loadingDictCache(); } /** * 加载字典缓存数据 */ @Override public void loadingDictCache() { Map> dictDataMap = dictDataMapper.selectList("status", "0").stream().collect(Collectors.groupingBy(SysDictDataEntity::getDictType)); for (Map.Entry> entry : dictDataMap.entrySet()) { DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictDataEntity::getDictSort)).collect(Collectors.toList())); } } @Override public boolean checkDictTypeUnique(SysDictTypeEntity dictType) { Long dictId = StringUtils.isNull(dictType.getDictId()) ? -1L : dictType.getDictId(); SysDictTypeEntity info = dictTypeMapper.selectOne(new LambdaQueryWrapperX() .eq(SysDictTypeEntity::getDictType, dictType.getDictType())); if (StringUtils.isNotNull(info) && info.getDictId().longValue() != dictId.longValue()) { return false; } return true; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysLoginLogServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysLoginLogEntity; import com.oddfar.campus.framework.mapper.SysLoginLogMapper; import com.oddfar.campus.framework.service.SysLoginLogService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Arrays; import java.util.List; /** * 系统访问日志情况信息 服务层处理 * * @author ruoyi */ @Service public class SysLoginLogServiceImpl implements SysLoginLogService { @Resource private SysLoginLogMapper loginLogMapper; /** * 查询系统登录日志分页数据 * * @param logininfor 访问日志对象 * @return 登录记录分页数据 */ @Override public PageResult selectLogininforPage(SysLoginLogEntity logininfor) { return loginLogMapper.selectLogininforPage(logininfor); } /** * 新增系统登录日志 * * @param logininfor 访问日志对象 */ @Override public void insertLogininfor(SysLoginLogEntity logininfor) { loginLogMapper.insert(logininfor); } /** * 查询系统登录日志集合 * * @param logininfor 访问日志对象 * @return 登录记录集合 */ @Override public List selectLogininforList(SysLoginLogEntity logininfor) { return loginLogMapper.selectLogininforList(logininfor); } /** * 批量删除系统登录日志 * * @param infoIds 需要删除的登录日志ID * @return 结果 */ @Override public int deleteLogininforByIds(Long[] infoIds) { return loginLogMapper.deleteBatchIds(Arrays.asList(infoIds)); } /** * 清空系统登录日志 */ @Override public void cleanLogininfor() { loginLogMapper.cleanLogininfor(); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysMenuServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.constant.UserConstants; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.TreeSelect; import com.oddfar.campus.common.domain.entity.SysMenuEntity; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import com.oddfar.campus.common.domain.entity.SysRoleMenuEntity; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.SysRoleAuth; import com.oddfar.campus.common.domain.vo.RouterVo; import com.oddfar.campus.common.utils.MetaVo; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.mapper.SysMenuMapper; import com.oddfar.campus.framework.mapper.SysRoleMapper; import com.oddfar.campus.framework.mapper.SysRoleMenuMapper; import com.oddfar.campus.framework.service.SysMenuService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.*; import java.util.stream.Collectors; /** * 菜单权限表(Menu)表服务实现类 */ @Service public class SysMenuServiceImpl implements SysMenuService { @Resource private SysMenuMapper menuMapper; @Resource private SysRoleMapper roleMapper; @Resource private SysRoleMenuMapper roleMenuMapper; /** * 根据用户查询系统菜单列表 * * @param userId 用户ID * @return 菜单列表 */ @Override public List selectMenuList(Long userId) { return selectMenuList(new SysMenuEntity(), userId); } /** * 查询系统菜单列表 * * @param menu 菜单信息 * @return 菜单列表 */ @Override public List selectMenuList(SysMenuEntity menu, Long userId) { List menuList = null; // 管理员显示所有菜单信息 if (SysUserEntity.isAdmin(userId)) { menuList = menuMapper.selectMenuList(menu); } else { menu.getParams().put("userId", userId); menuList = menuMapper.selectMenuListByUserId(menu); } return menuList; } /** * 根据用户ID查询菜单 * * @param userId 用户名称 * @return 菜单列表 */ @Override public List selectMenuTreeByUserId(Long userId) { List menus = null; if (SecurityUtils.isAdmin(userId)) { menus = menuMapper.selectMenuTreeAll(); } else { menus = menuMapper.selectMenuTreeByUserId(userId); } return getChildPerms(menus, 0); } /** * 根据角色ID查询菜单树信息 * * @param roleId 角色ID * @return 选中菜单列表 */ @Override public List selectMenuListByRoleId(Long roleId) { SysRoleEntity role = roleMapper.selectRoleById(roleId); return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); } @Override public SysMenuEntity selectMenuById(Long menuId) { return menuMapper.selectById(menuId); } /** * 构建前端路由所需要的菜单 * * @param menus 菜单列表 * @return 路由列表 */ @Override public List buildMenus(List menus) { List routers = new LinkedList(); for (SysMenuEntity menu : menus) { RouterVo router = new RouterVo(); router.setHidden("1".equals(menu.getVisible())); router.setName(getRouteName(menu)); router.setPath(getRouterPath(menu)); router.setComponent(getComponent(menu)); router.setQuery(menu.getQuery()); router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); List cMenus = menu.getChildren(); if (!cMenus.isEmpty() && cMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType())) { router.setAlwaysShow(true); router.setRedirect("noRedirect"); router.setChildren(buildMenus(cMenus)); } else if (isMenuFrame(menu)) { router.setMeta(null); List childrenList = new ArrayList(); RouterVo children = new RouterVo(); children.setPath(menu.getPath()); children.setComponent(menu.getComponent()); children.setName(StringUtils.capitalize(menu.getPath())); children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); children.setQuery(menu.getQuery()); childrenList.add(children); router.setChildren(childrenList); } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) { router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); router.setPath("/"); List childrenList = new ArrayList(); RouterVo children = new RouterVo(); String routerPath = innerLinkReplaceEach(menu.getPath()); children.setPath(routerPath); children.setComponent(UserConstants.INNER_LINK); children.setName(StringUtils.capitalize(routerPath)); children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); childrenList.add(children); router.setChildren(childrenList); } routers.add(router); } return routers; } /** * 构建前端所需要下拉树结构 * * @param menus 菜单列表 * @return 下拉树结构列表 */ @Override public List buildMenuTreeSelect(List menus) { List menuTrees = buildMenuTree(menus); return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); } /** * 构建前端所需要树结构 * * @param menus 菜单列表 * @return 树结构列表 */ @Override public List buildMenuTree(List menus) { List returnList = new ArrayList(); List tempList = new ArrayList(); for (SysMenuEntity dept : menus) { tempList.add(dept.getMenuId()); } for (Iterator iterator = menus.iterator(); iterator.hasNext(); ) { SysMenuEntity menu = (SysMenuEntity) iterator.next(); // 如果是顶级节点, 遍历该父节点的所有子节点 if (!tempList.contains(menu.getParentId())) { recursionFn(menus, menu); returnList.add(menu); } } if (returnList.isEmpty()) { returnList = menus; } return returnList; } /** * 根据角色ID查询权限 * * @param roleId 角色ID * @return 权限列表 */ @Override public Set selectMenuPermsByRoleId(Long roleId) { List perms = menuMapper.selectMenuPermsByRoleId(roleId); Set permsSet = new HashSet<>(); for (String perm : perms) { if (StringUtils.isNotEmpty(perm)) { permsSet.addAll(Arrays.asList(perm.trim().split(","))); } } return permsSet; } @Override public Map> selectMenuPermsAll() { List sysRolePerms = menuMapper.getMenuPermsAll(); //根据roleId分组 return sysRolePerms.stream().collect(Collectors.groupingBy(SysRoleAuth::getRoleID)); } /** * 根据用户ID查询权限 * * @param userId 用户ID * @return 权限列表 */ @Override public Set selectMenuPermsByUserId(Long userId) { List perms = menuMapper.selectMenuPermsByUserId(userId); Set permsSet = new HashSet<>(); for (String perm : perms) { if (StringUtils.isNotEmpty(perm)) { permsSet.addAll(Arrays.asList(perm.trim().split(","))); } } return permsSet; } @Override public int insertMenu(SysMenuEntity menu) { return menuMapper.insert(menu); } @Override public int updateMenu(SysMenuEntity menu) { return menuMapper.updateById(menu); } @Override public int deleteMenuById(Long menuId) { return menuMapper.deleteById(menuId); } @Override public boolean checkMenuExistRole(Long menuId) { Long result = roleMenuMapper.selectCount(new QueryWrapper().eq("menu_id", menuId) ); return result > 0; } @Override public boolean hasChildByMenuId(Long menuId) { Long result = menuMapper.selectCount( new LambdaQueryWrapperX().eq(SysMenuEntity::getParentId, menuId)); return result > 0; } @Override public boolean checkMenuNameUnique(SysMenuEntity menu) { Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId(); SysMenuEntity info = menuMapper.checkMenuNameUnique(menu); if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) { return false; } return true; } /** * 获取路由地址 * * @param menu 菜单信息 * @return 路由地址 */ public String getRouterPath(SysMenuEntity menu) { String routerPath = menu.getPath(); // 内链打开外网方式 if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) { routerPath = innerLinkReplaceEach(routerPath); } // 非外链并且是一级目录(类型为目录) if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) && UserConstants.NO_FRAME.equals(menu.getIsFrame())) { routerPath = "/" + menu.getPath(); } // 非外链并且是一级目录(类型为菜单) else if (isMenuFrame(menu)) { routerPath = "/"; } return routerPath; } /** * 是否为内链组件 * * @param menu 菜单信息 * @return 结果 */ public boolean isInnerLink(SysMenuEntity menu) { return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); } /** * 获取组件信息 * * @param menu 菜单信息 * @return 组件信息 */ public String getComponent(SysMenuEntity menu) { String component = UserConstants.LAYOUT; if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) { component = menu.getComponent(); } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 && isInnerLink(menu)) { component = UserConstants.INNER_LINK; } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) { component = UserConstants.PARENT_VIEW; } return component; } /** * 是否为parent_view组件 * * @param menu 菜单信息 * @return 结果 */ public boolean isParentView(SysMenuEntity menu) { return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()); } /** * 获取路由名称 * * @param menu 菜单信息 * @return 路由名称 */ public String getRouteName(SysMenuEntity menu) { String routerName = StringUtils.capitalize(menu.getPath()); // 非外链并且是一级目录(类型为目录) if (isMenuFrame(menu)) { routerName = StringUtils.EMPTY; } return routerName; } /** * 是否为菜单内部跳转 * * @param menu 菜单信息 * @return 结果 */ public boolean isMenuFrame(SysMenuEntity menu) { return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) && menu.getIsFrame().equals(UserConstants.NO_FRAME); } /** * 根据父节点的ID获取所有子节点 * * @param list 分类表 * @param parentId 传入的父节点ID * @return String */ public List getChildPerms(List list, int parentId) { List returnList = new ArrayList(); for (Iterator iterator = list.iterator(); iterator.hasNext(); ) { SysMenuEntity t = (SysMenuEntity) iterator.next(); // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点 if (t.getParentId() == parentId) { recursionFn(list, t); returnList.add(t); } } return returnList; } /** * 递归列表 * * @param list * @param t */ private void recursionFn(List list, SysMenuEntity t) { // 得到子节点列表 List childList = getChildList(list, t); t.setChildren(childList); for (SysMenuEntity tChild : childList) { if (hasChild(list, tChild)) { recursionFn(list, tChild); } } } /** * 判断是否有子节点 */ private boolean hasChild(List list, SysMenuEntity t) { return getChildList(list, t).size() > 0; } /** * 得到子节点列表 */ private List getChildList(List list, SysMenuEntity t) { List tlist = new ArrayList(); Iterator it = list.iterator(); while (it.hasNext()) { SysMenuEntity n = (SysMenuEntity) it.next(); if (n.getParentId().longValue() == t.getMenuId().longValue()) { tlist.add(n); } } return tlist; } /** * 内链域名特殊字符替换 * * @return */ public String innerLinkReplaceEach(String path) { return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS, Constants.WWW, "."}, new String[]{"", "", "", "/"}); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysOperLogServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysOperLogEntity; import com.oddfar.campus.framework.mapper.SysOperLogMapper; import com.oddfar.campus.framework.service.SysOperLogService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; /** * 操作日志 服务层处理 */ @Service public class SysOperLogServiceImpl implements SysOperLogService { @Resource private SysOperLogMapper operLogMapper; /** * 新增操作日志 * * @param operLog 操作日志对象 */ @Override public void insertOperlog(SysOperLogEntity operLog) { operLogMapper.insert(operLog); } /** * 查询系统操作日志集合 * * @param operLog 操作日志对象 * @return 操作日志集合 */ @Override public PageResult selectOperLogPage(SysOperLogEntity operLog) { return operLogMapper.selectOperLogPage(operLog); } /** * 批量删除系统操作日志 * * @param operIds 需要删除的操作日志ID * @return 结果 */ @Override public int deleteOperLogByIds(Long[] operIds) { return operLogMapper.deleteOperLogByIds(operIds); } /** * 查询操作日志详细 * * @param operId 操作ID * @return 操作日志对象 */ @Override public SysOperLogEntity selectOperLogById(Long operId) { return operLogMapper.selectById(operId); } /** * 清空操作日志 */ @Override public void cleanOperLog() { operLogMapper.cleanOperLog(); } @Override public List selectOperLogList(SysOperLogEntity operLog) { return operLogMapper.selectOperLogList(operLog); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysResourceServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.TreeSelect; import com.oddfar.campus.common.domain.entity.SysResourceEntity; import com.oddfar.campus.common.domain.entity.SysRoleResourceEntity; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.SysRoleAuth; import com.oddfar.campus.framework.mapper.SysResourceMapper; import com.oddfar.campus.framework.mapper.SysRoleResourceMapper; import com.oddfar.campus.framework.service.SysResourceService; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.*; import java.util.stream.Collectors; @Service public class SysResourceServiceImpl extends ServiceImpl implements SysResourceService { @Resource private SysResourceMapper resourceMapper; @Resource private SysRoleResourceMapper roleResourceMapper; @Override public PageResult page(SysResourceEntity sysResourceEntity) { return resourceMapper.selectPage(sysResourceEntity); } /** * 新增接口资源信息 * * @param resource * @return */ @Override public int insertResource(SysResourceEntity resource) { int row = resourceMapper.insert(resource); return row; } /** * 清空 sys_resource 数据库 */ @Override public void truncateResource() { resourceMapper.truncateResource(); } /** * 根据角色ID查询资源编码列表 * * @param roleId 角色ID * @return 权限列表 */ @Override public Set selectResourceCodeByRoleId(Long roleId) { return resourceMapper.selectResourceCodeByRoleId(roleId); } @Override public List selectApiResourceList(Long userId) { SysResourceEntity resourceEntity = new SysResourceEntity(); resourceEntity.setRequiredPermissionFlag(Constants.YES); return selectApiResourceList(resourceEntity, userId); } @Override public List selectSysRoleAuthAll() { return roleResourceMapper.selectList().stream() .map(SysRoleAuth::new).collect(Collectors.toList()); } @Override public List selectApiResourceList(SysResourceEntity resource, Long userId) { List resourceList = null; // 管理员显示所有资源信息 if (SysUserEntity.isAdmin(userId)) { resourceList = resourceMapper.selectResourceList(resource); } else { resource.getParams().put("userId", userId); resourceList = resourceMapper.selectResourceListByUserId(resource); } return resourceList; } @Override public List selectResourceListByRoleId(Long roleId) { return resourceMapper.selectResourceListByRoleId(roleId); } @Override public List buildResourceTreeSelect(List resources) { List treeSelects = new ArrayList<>(); Map> map = resources.stream().collect( Collectors.groupingBy(SysResourceEntity::getClassName)); long size = 0L; for (String key : map.keySet()) { String modularName = map.get(key).get(0).getModularName(); TreeSelect treeSelect = new TreeSelect(++size, modularName, map.get(key)); treeSelects.add(treeSelect); } return treeSelects; } @Override public void editRoleResource(Long roleId, Long[] resourceIds) { // 删除角色与api资源关联 roleResourceMapper.deleteRoleResourceByRoleId(roleId); //添加角色与api资源管理 if (resourceIds.length > 0) { List resourceEntities = resourceMapper.selectBatchIds(Arrays.asList(resourceIds)); insertRoleMenu(roleId, resourceEntities); } } public int insertRoleMenu(Long roleId, List resourceEntities) { List rrList = new ArrayList<>(); for (SysResourceEntity resourceEntity : resourceEntities) { SysRoleResourceEntity rr = new SysRoleResourceEntity(); rr.setRoleId(roleId); rr.setResourceCode(resourceEntity.getResourceCode()); rrList.add(rr); } return roleResourceMapper.saveBatch(rrList); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysRoleServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import com.oddfar.campus.common.domain.entity.SysRoleMenuEntity; import com.oddfar.campus.common.domain.entity.SysUserRoleEntity; import com.oddfar.campus.common.domain.model.SysRoleAuth; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.api.resource.ResourceCollectorApi; import com.oddfar.campus.framework.mapper.SysRoleMapper; import com.oddfar.campus.framework.mapper.SysRoleMenuMapper; import com.oddfar.campus.framework.mapper.SysUserRoleMapper; import com.oddfar.campus.framework.service.SysMenuService; import com.oddfar.campus.framework.service.SysResourceService; import com.oddfar.campus.framework.service.SysRoleService; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.*; import java.util.stream.Collectors; @Service public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { @Resource private SysRoleMapper roleMapper; @Resource private SysUserRoleMapper userRoleMapper; @Resource private SysRoleMenuMapper roleMenuMapper; @Resource private SysMenuService menuService; @Resource private SysResourceService resourceService; @Resource ApplicationContext applicationContext; @Override public PageResult page(SysRoleEntity sysRoleEntity) { return roleMapper.selectPage(sysRoleEntity); } @Override public List selectRoleList(SysRoleEntity role) { return roleMapper.selectRoleList(role); } /** * 根据用户ID查询权限 * * @param userId 用户ID * @return 权限列表 */ @Override public Set selectRolePermissionByUserId(Long userId) { List perms = roleMapper.selectRolePermissionByUserId(userId); Set permsSet = new HashSet<>(); for (SysRoleEntity perm : perms) { if (StringUtils.isNotNull(perm)) { permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); } } return permsSet; } /** * 通过角色ID查询角色 * * @param roleId 角色ID * @return 角色对象信息 */ @Override public SysRoleEntity selectRoleById(Long roleId) { return roleMapper.selectRoleById(roleId); } @Override public List selectRoleAll() { return this.selectRoleList(new SysRoleEntity()); // return SpringUtils.getAopProxy(this).selectRoleList(new SysRoleEntity()); } @Override public List selectRolesByUserId(Long userId) { List userRoles = roleMapper.selectRolePermissionByUserId(userId); List roles = selectRoleAll(); for (SysRoleEntity role : roles) { for (SysRoleEntity userRole : userRoles) { if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) { role.setFlag(true); break; } } } return roles; } @Override @Transactional public int insertRole(SysRoleEntity role) { // 新增角色信息 roleMapper.insert(role); return insertRoleMenu(role); } @Override public int updateRole(SysRoleEntity role) { // 修改角色信息 roleMapper.updateById(role); // 删除角色与菜单关联 roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId()); return insertRoleMenu(role); } @Override public int updateRoleStatus(SysRoleEntity role) { return roleMapper.updateById(role); } @Override @Transactional public int deleteRoleByIds(Long[] roleIds) { for (Long roleId : roleIds) { checkRoleAllowed(new SysRoleEntity(roleId)); SysRoleEntity role = selectRoleById(roleId); if (countUserRoleByRoleId(roleId) > 0) { throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); } } // 删除角色与菜单关联 roleMenuMapper.deleteRoleMenu(roleIds); // 删除角色 return roleMapper.deleteBatchIds(Arrays.asList(roleIds)); } @Override public int deleteAuthUser(SysUserRoleEntity userRole) { return userRoleMapper.deleteUserRoleInfo(userRole); } @Override public int deleteAuthUsers(Long roleId, Long[] userIds) { return userRoleMapper.deleteUserRoleInfos(roleId, userIds); } @Override public boolean insertAuthUsers(Long roleId, Long[] userIds) { // 新增用户与角色管理 List list = new ArrayList(); for (Long userId : userIds) { SysUserRoleEntity ur = new SysUserRoleEntity(); ur.setUserId(userId); ur.setRoleId(roleId); list.add(ur); } return this.saveBatch(list); } @Override public int countUserRoleByRoleId(Long roleId) { return userRoleMapper.countUserRoleByRoleId(roleId); } @Override public boolean checkRoleNameUnique(SysRoleEntity role) { Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); SysRoleEntity info = roleMapper.checkRoleNameUnique(role.getRoleName()); if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) { return false; } return true; } @Override public boolean checkRoleKeyUnique(SysRoleEntity role) { Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); SysRoleEntity info = roleMapper.checkRoleKeyUnique(role.getRoleKey()); if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) { return false; } return true; } @Override public void checkRoleAllowed(SysRoleEntity role) { if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) { throw new ServiceException("不允许操作超级管理员角色"); } } @Override public void resetRoleAuthCache() { //把用户资源和权限缓存 Map> rolePermsMap = menuService.selectMenuPermsAll(); Map> roleResourceMap = resourceService.selectSysRoleAuthAll().stream().collect(Collectors.groupingBy(SysRoleAuth::getRoleID)); ResourceCollectorApi resourceCollectorApi = applicationContext.getBean(ResourceCollectorApi.class); resourceCollectorApi.setRoleAuthCache(rolePermsMap, roleResourceMap); } /** * 新增角色菜单信息 * * @param role 角色对象 */ public int insertRoleMenu(SysRoleEntity role) { int rows = 1; // 新增用户与角色管理 List list = new ArrayList(); for (Long menuId : role.getMenuIds()) { SysRoleMenuEntity rm = new SysRoleMenuEntity(); rm.setRoleId(role.getRoleId()); rm.setMenuId(menuId); list.add(rm); } if (list.size() > 0) { rows = roleMenuMapper.batchRoleMenu(list); } return rows; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/service/impl/SysUserServiceImpl.java ================================================ package com.oddfar.campus.framework.service.impl; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.oddfar.campus.common.constant.UserConstants; import com.oddfar.campus.common.core.page.PageQuery; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.entity.SysUserRoleEntity; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.api.sysconfig.ConfigExpander; import com.oddfar.campus.framework.mapper.SysRoleMapper; import com.oddfar.campus.framework.mapper.SysUserMapper; import com.oddfar.campus.framework.mapper.SysUserRoleMapper; import com.oddfar.campus.framework.service.SysRoleService; import com.oddfar.campus.framework.service.SysUserService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @Service public class SysUserServiceImpl implements SysUserService { @Resource private SysUserMapper userMapper; @Resource private SysUserRoleMapper userRoleMapper; @Resource private SysRoleMapper roleMapper; @Resource private SysRoleService roleService; @Override public PageResult page(SysUserEntity sysUserEntity) { return userMapper.selectPage(sysUserEntity); } @Override public SysUserEntity selectUserByUserName(String userName) { SysUserEntity userEntity = userMapper.selectUserByUserName(userName); if (userEntity != null && StringUtils.isEmpty(userEntity.getAvatar())) { userEntity.setAvatar(ConfigExpander.getUserDefaultAvatar()); } return userEntity; } @Override public SysUserEntity selectUserById(Long userId) { SysUserEntity userEntity = userMapper.selectUserById(userId); if (userEntity != null && StringUtils.isEmpty(userEntity.getAvatar())) { userEntity.setAvatar(ConfigExpander.getUserDefaultAvatar()); } return userEntity; } @Override public Page selectAllocatedList(SysUserEntity user) { Page page = new PageQuery().buildPage(); QueryWrapper wrapper = Wrappers.query(); wrapper.eq("u.del_flag", UserConstants.NORMAL) .eq("r.role_id", user.getRoleId()) .eq(ObjectUtil.isNotNull(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber()) .like(ObjectUtil.isNotNull(user.getUserName()), "u.user_name", user.getUserName()); Page sysUserPage = userMapper.selectAllocatedList(page, wrapper); return sysUserPage; } @Override public Page selectUnallocatedList(SysUserEntity user) { Page page = new PageQuery().buildPage(); return userMapper.selectUnallocatedList(page, user); } @Override public String selectUserRoleGroup(String userName) { List list = roleMapper.selectRolesByUserName(userName); if (CollectionUtils.isEmpty(list)) { return StringUtils.EMPTY; } return list.stream().map(SysRoleEntity::getRoleName).collect(Collectors.joining(",")); } @Override public boolean registerUser(SysUserEntity user) { return userMapper.insert(user) > 0; } @Override @Transactional public int insertUser(SysUserEntity user) { if (StringUtils.isNotEmpty(user.getUserName()) && !checkUserNameUnique(user)) { throw new ServiceException("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); } if (StringUtils.isNotEmpty(user.getPhonenumber()) && !(checkPhoneUnique(user))) { throw new ServiceException("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); } if (StringUtils.isNotEmpty(user.getEmail()) && !(checkEmailUnique(user))) { throw new ServiceException("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); } user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); // 新增用户信息 int rows = userMapper.insert(user); // 新增用户与角色管理 insertUserRole(user); return rows; } @Override @Transactional public int updateUser(SysUserEntity user) { Long userId = user.getUserId(); // 删除用户与角色关联 userRoleMapper.deleteUserRoleByUserId(userId); // 新增用户与角色管理 insertUserRole(user); return userMapper.updateById(user); } @Override @Transactional public int deleteUserByIds(Long[] userIds) { for (Long userId : userIds) { checkUserAllowed(new SysUserEntity(userId)); } // 删除用户与角色关联 userRoleMapper.deleteUserRole(userIds); return userMapper.deleteBatchIds(Arrays.asList(userIds)); } @Override public int updateUserProfile(SysUserEntity user) { return userMapper.updateById(user); } @Override public int updateUserStatus(SysUserEntity user) { return userMapper.updateById(user); } @Override public boolean updateUserAvatar(String userName, String avatar) { return userMapper.updateUserAvatar(userName, avatar) > 0; } @Override public int resetPwd(SysUserEntity user) { return userMapper.updateById(user); } @Override public void checkUserAllowed(SysUserEntity user) { if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) { throw new ServiceException("不允许操作超级管理员用户"); } } @Override @Transactional public void insertUserAuth(Long userId, Long[] roleIds) { userRoleMapper.deleteUserRoleByUserId(userId); insertUserRole(userId, roleIds); } @Override public void insertUserAuth(Long userId, Set roleKey) { //查询现有的权限字符 Set roleSet = roleService.selectRolePermissionByUserId(userId); roleKey.addAll(roleSet); List sysRoleList = roleMapper.selectRoleListByKey(roleKey); List roleIds = sysRoleList.stream().map(SysRoleEntity::getRoleId).collect(Collectors.toList()); userRoleMapper.deleteUserRoleByUserId(userId); insertUserRole(userId, roleIds.toArray(new Long[0])); } @Override public int resetUserPwd(String userName, String password) { return userMapper.resetUserPwd(userName, password); } @Override public boolean checkUserNameUnique(SysUserEntity user) { Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); SysUserEntity info = userMapper.checkUserNameUnique(user.getUserName()); if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { return false; } return true; } @Override public boolean checkPhoneUnique(SysUserEntity user) { Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); SysUserEntity info = userMapper.checkPhoneUnique(user.getPhonenumber()); if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { return false; } return true; } @Override public boolean checkEmailUnique(SysUserEntity user) { Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); SysUserEntity info = userMapper.checkEmailUnique(user.getEmail()); if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { return false; } return true; } /** * 新增用户角色信息 * * @param user 用户对象 */ public void insertUserRole(SysUserEntity user) { this.insertUserRole(user.getUserId(), user.getRoleIds()); } /** * 新增用户角色信息 * * @param userId 用户ID * @param roleIds 角色组 */ public void insertUserRole(Long userId, Long[] roleIds) { if (StringUtils.isNotEmpty(roleIds)) { // 新增用户与角色管理 List list = new ArrayList(roleIds.length); for (Long roleId : roleIds) { if (roleId != null) { SysUserRoleEntity ur = new SysUserRoleEntity(); ur.setUserId(userId); ur.setRoleId(roleId); list.add(ur); } } userRoleMapper.insertBatch(list); } } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/web/exception/GlobalExceptionHandler.java ================================================ package com.oddfar.campus.framework.web.exception; import com.oddfar.campus.common.constant.HttpStatus; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.AccessDeniedException; import org.springframework.validation.BindException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; /** * 全局异常处理器 * * @author ruoyi */ @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 权限校验异常 */ @ExceptionHandler(AccessDeniedException.class) public R handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); return R.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); } /** * 请求方式不支持 */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); return R.error(e.getMessage()); } /** * 业务异常 */ @ExceptionHandler(ServiceException.class) public R handleServiceException(ServiceException e, HttpServletRequest request) { log.error(e.getMessage(), e); Integer code = e.getCode(); return StringUtils.isNotNull(code) ? R.error(code, e.getMessage()) : R.error(e.getMessage()); } /** * 拦截未知的运行时异常 */ @ExceptionHandler(RuntimeException.class) public R handleRuntimeException(RuntimeException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',发生未知异常.", requestURI, e); return R.error(e.getMessage()); } /** * 系统异常 */ @ExceptionHandler(Exception.class) public R handleException(Exception e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',发生系统异常.", requestURI, e); return R.error(e.getMessage()); } /** * 自定义验证异常 */ @ExceptionHandler(BindException.class) public R handleBindException(BindException e) { log.error(e.getMessage(), e); String message = e.getAllErrors().get(0).getDefaultMessage(); return R.error(message); } /** * 自定义验证异常 */ @ExceptionHandler(MethodArgumentNotValidException.class) public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { log.error(e.getMessage(), e); String message = e.getBindingResult().getFieldError().getDefaultMessage(); return R.error(message); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/web/service/PermissionService.java ================================================ package com.oddfar.campus.framework.web.service; import cn.hutool.core.util.StrUtil; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.security.context.PermissionContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Set; /** * 自定义权限实现,及自定义controller接口资源权限,ss取自SpringSecurity首字母 * (根据若依修改) * * @author oddfar */ @Service("ss") public class PermissionService { private static final Logger log = LoggerFactory.getLogger(PermissionService.class); @Value("${spring.application.name:}") private String springApplicationName; @Autowired WebApplicationContext applicationContext; /** * 所有权限标识 */ private static final String ALL_PERMISSION = "*:*:*"; /** * 管理员角色权限标识 */ private static final String SUPER_ADMIN = "admin"; private static final String ROLE_DELIMETER = ","; private static final String PERMISSION_DELIMETER = ","; /** * 验证用户是否具备某权限 * * @param permission 权限字符串 * @return 用户是否具备某权限 */ public boolean hasPermi(String permission) { if (StringUtils.isEmpty(permission)) { return false; } LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { return false; } PermissionContextHolder.setContext(permission); return hasPermissions(loginUser.getPermissions(), permission); } /** * 验证用户是否具备某接口 * @return 用户是否具备某接口 */ public boolean resourceAuth() { LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNull(loginUser)) { return false; } HttpServletRequest request = ServletUtils.getRequest(); RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class); HandlerExecutionChain handlerChain = null; try { handlerChain = mapping.getHandler(request); } catch (Exception e) { e.printStackTrace(); } //通过处理链找到对应的HandlerMethod类 HandlerMethod handler = (HandlerMethod) handlerChain.getHandler(); String resourceCode = getResourceCode(handler); return hasResources(loginUser.getResources(), resourceCode); } /** * 获取controller方法的编码 * * @param handler * @return */ private String getResourceCode(HandlerMethod handler) { Object bean = handler.getBean();//处理请求的类 Class aClass = bean.getClass(); Method method = handler.getMethod();//处理请求的方法 //获取@ApiResource接口 ApiResource apiResource = method.getDeclaringClass().getAnnotation(ApiResource.class); //资源唯一编码 StringBuffer resourceCode = new StringBuffer(); //应用编码 String appCode = springApplicationName; if (apiResource != null) { if (StringUtils.isNotEmpty(apiResource.appCode())) { appCode = apiResource.appCode(); } } String className = StrUtil.toUnderlineCase(StringUtils.substringBefore(aClass.getSimpleName(), "$$") .replace("Controller", "")); String methodName = StrUtil.toUnderlineCase(method.getName()); return resourceCode.append(appCode).append(".").append(className).append(".").append(methodName).toString(); } /** * 判断是否包含接口 * * @param resources 资源列表 * @param resource 资源接口字符串 * @return 用户是否具备某权限 */ private boolean hasResources(Set resources, String resource) { return resources.contains(ALL_PERMISSION) || resources.contains(StringUtils.trim(resource)); } /** * 验证用户是否不具备某权限,与 hasPermi逻辑相反 * * @param permission 权限字符串 * @return 用户是否不具备某权限 */ public boolean lacksPermi(String permission) { return hasPermi(permission) != true; } /** * 验证用户是否具有以下任意一个权限 * * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 * @return 用户是否具有以下任意一个权限 */ public boolean hasAnyPermi(String permissions) { if (StringUtils.isEmpty(permissions)) { return false; } LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { return false; } PermissionContextHolder.setContext(permissions); Set authorities = loginUser.getPermissions(); for (String permission : permissions.split(PERMISSION_DELIMETER)) { if (permission != null && hasPermissions(authorities, permission)) { return true; } } return false; } /** * 判断用户是否拥有某个角色 * * @param role 角色字符串 * @return 用户是否具备某角色 */ public boolean hasRole(String role) { if (StringUtils.isEmpty(role)) { return false; } LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { return false; } for (SysRoleEntity sysRole : loginUser.getUser().getRoles()) { String roleKey = sysRole.getRoleKey(); if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) { return true; } } return false; } /** * 验证用户是否不具备某角色,与 isRole逻辑相反。 * * @param role 角色名称 * @return 用户是否不具备某角色 */ public boolean lacksRole(String role) { return hasRole(role) != true; } /** * 验证用户是否具有以下任意一个角色 * * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 * @return 用户是否具有以下任意一个角色 */ public boolean hasAnyRoles(String roles) { if (StringUtils.isEmpty(roles)) { return false; } LoginUser loginUser = SecurityUtils.getLoginUser(); if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { return false; } for (String role : roles.split(ROLE_DELIMETER)) { if (hasRole(role)) { return true; } } return false; } /** * 判断是否包含权限 * * @param permissions 权限列表 * @param permission 权限字符串 * @return 用户是否具备某权限 */ private boolean hasPermissions(Set permissions, String permission) { return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/web/service/SysLoginService.java ================================================ package com.oddfar.campus.framework.web.service; import cn.hutool.core.date.DateUtil; import com.oddfar.campus.common.constant.CacheConstants; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.MessageUtils; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.ip.IpUtils; import com.oddfar.campus.common.utils.web.WebFrameworkUtils; import com.oddfar.campus.framework.manager.AsyncFactory; import com.oddfar.campus.framework.manager.AsyncManager; import com.oddfar.campus.framework.security.context.AuthenticationContextHolder; import com.oddfar.campus.framework.service.SysConfigService; import com.oddfar.campus.framework.service.SysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * 登录校验方法 * * @author ruoyi */ @Component public class SysLoginService { @Autowired private TokenService tokenService; @Resource private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Autowired private SysUserService userService; @Autowired private SysConfigService configService; /** * 登录验证 * * @param username 用户名 * @param password 密码 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public String login(String username, String password, String code, String uuid) { boolean captchaEnabled = configService.selectCaptchaEnabled(); // 验证码开关 if (captchaEnabled) { validateCaptcha(username, code, uuid); } // 用户验证 Authentication authentication = null; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { if (e instanceof BadCredentialsException) { //异步执行->保存登录信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new ServiceException("账号或密码错误"); } else { //异步执行->登录信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.LOGIN_FAIL, e.getMessage())); e.printStackTrace(); throw new ServiceException(e.getMessage()); } } finally { AuthenticationContextHolder.clearContext(); } LoginUser loginUser = (LoginUser) authentication.getPrincipal(); //异步记录登录成功日志 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, loginUser.getUserId(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); // 额外设置到 request 中,有的过滤器在 Spring Security 之前 WebFrameworkUtils.setLoginUserId(ServletUtils.getRequest(), loginUser.getUserId()); //记录登录信息 recordLoginInfo(loginUser.getUserId()); // 生成token return tokenService.createToken(loginUser); } /** * 校验验证码 * * @param username 用户名 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public void validateCaptcha(String username, String code, String uuid) { String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); String captcha = redisCache.getCacheObject(verifyKey); redisCache.deleteObject(verifyKey); if (captcha == null) { // AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); throw new ServiceException("验证码失效"); } if (!code.equalsIgnoreCase(captcha)) { // AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); throw new ServiceException("验证码错误"); } } /** * 记录登录信息 * * @param userId 用户ID */ public void recordLoginInfo(Long userId) { SysUserEntity sysUser = new SysUserEntity(); sysUser.setUserId(userId); sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest())); sysUser.setLoginDate(DateUtil.date()); userService.updateUserProfile(sysUser); } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/web/service/SysPasswordService.java ================================================ package com.oddfar.campus.framework.web.service; import com.oddfar.campus.common.constant.CacheConstants; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.exception.user.UserPasswordNotMatchException; import com.oddfar.campus.common.exception.user.UserPasswordRetryLimitExceedException; import com.oddfar.campus.common.utils.MessageUtils; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.framework.manager.AsyncFactory; import com.oddfar.campus.framework.manager.AsyncManager; import com.oddfar.campus.framework.security.context.AuthenticationContextHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * 登录密码方法 * * @author ruoyi */ @Component public class SysPasswordService { @Autowired private RedisCache redisCache; @Value(value = "${user.password.maxRetryCount}") private int maxRetryCount; @Value(value = "${user.password.lockTime}") private int lockTime; /** * 登录账户密码错误次数缓存键名 * * @param username 用户名 * @return 缓存键key */ private String getCacheKey(String username) { return CacheConstants.PWD_ERR_CNT_KEY + username; } public void validate(SysUserEntity user) { Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); String username = usernamePasswordAuthenticationToken.getName(); String password = usernamePasswordAuthenticationToken.getCredentials().toString(); Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); if (retryCount == null) { retryCount = 0; } if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime))); throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); } if (!matches(user, password)) { retryCount = retryCount + 1; AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.LOGIN_FAIL, MessageUtils.message("user.password.retry.limit.count", retryCount))); redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); throw new UserPasswordNotMatchException(); } else { clearLoginRecordCache(username); } } public boolean matches(SysUserEntity user, String rawPassword) { return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); } public void clearLoginRecordCache(String loginName) { if (redisCache.hasKey(getCacheKey(loginName))) { redisCache.deleteObject(getCacheKey(loginName)); } } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/web/service/SysPermissionService.java ================================================ package com.oddfar.campus.framework.web.service; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.domain.entity.SysRoleEntity; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.framework.service.SysMenuService; import com.oddfar.campus.framework.service.SysResourceService; import com.oddfar.campus.framework.service.SysRoleService; import com.oddfar.campus.framework.service.SysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashSet; import java.util.List; import java.util.Set; /** * 用户权限处理 * * @author ruoyi */ @Component public class SysPermissionService { @Autowired private SysRoleService roleService; @Autowired private SysMenuService menuService; @Autowired private SysResourceService resourceService; @Autowired private SysUserService userService; @Autowired private TokenService tokenService; @Autowired private RedisCache redisCache; /** * 获取角色数据权限 * * @param user 用户信息 * @return 角色权限信息 */ public Set getRolePermission(SysUserEntity user) { Set roles = new HashSet(); // 管理员拥有所有权限 if (user.isAdmin()) { roles.add("admin"); } else { roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); } return roles; } /** * 获取菜单数据权限 * * @param user 用户信息 * @return 菜单权限信息 */ public Set getMenuPermission(SysUserEntity user) { Set perms = new HashSet(); // 管理员拥有所有权限 if (user.isAdmin()) { perms.add("*:*:*"); } else { List roles = user.getRoles(); if (!roles.isEmpty() && roles.size() > 1) { // 多角色设置permissions属性,以便数据权限匹配权限 for (SysRoleEntity role : roles) { Set rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); role.setPermissions(rolePerms); perms.addAll(rolePerms); } } else { perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); } } return perms; } /** * 获取菜单数据权限 * * @param roleID 角色id * @return 菜单权限信息 */ public Set getMenuPermissionByRoleId(Long roleID) { Set perms = new HashSet(); // 管理员拥有所有权限 if (roleID == 1) { perms.add("*:*:*"); } else { perms = menuService.selectMenuPermsByRoleId(roleID); } return perms; } /** * 获取接口资源数据权限 * * @param user 用户信息 * @return 资源信息 */ public Set getResources(SysUserEntity user) { Set res = new HashSet(); // 超级管理员拥有所有权限 if (user.isAdmin()) { res.add("*:*:*"); } else { List roles = user.getRoles(); if (roles != null && !roles.isEmpty()) { for (SysRoleEntity role : roles) { Set code = resourceService.selectResourceCodeByRoleId(role.getRoleId()); res.addAll(code); } } } return res; } /** * 重置登录用户的权限缓存 * * @param roleId 角色id */ public void resetLoginUserRoleCache(long roleId) { // Collection keys = redisCache.keys(CacheConstants.LOGIN_USER_KEY + "*"); SysUserEntity user = new SysUserEntity(); user.setRoleId(roleId); List sysUserEntities = userService.selectAllocatedList(user).getRecords(); sysUserEntities.forEach(u -> resetUserRoleAuthCache(u.getUserId())); } /** * 重置redis里用户权限的缓存 * * @param userId 用户id */ public void resetUserRoleAuthCache(long userId) { LoginUser loginUser = tokenService.getLoginUserByUserId(userId); if (loginUser != null) { loginUser.setPermissions(getMenuPermission(loginUser.getUser())); loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); loginUser.setResources(getResources(loginUser.getUser())); tokenService.setLoginUser(loginUser); } } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/web/service/SysRegisterService.java ================================================ package com.oddfar.campus.framework.web.service; import com.oddfar.campus.common.constant.CacheConstants; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.constant.UserConstants; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.RegisterBody; import com.oddfar.campus.common.exception.user.CaptchaException; import com.oddfar.campus.common.exception.user.CaptchaExpireException; import com.oddfar.campus.common.utils.MessageUtils; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.manager.AsyncFactory; import com.oddfar.campus.framework.manager.AsyncManager; import com.oddfar.campus.framework.service.SysConfigService; import com.oddfar.campus.framework.service.SysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * 注册校验方法 */ @Component public class SysRegisterService { @Autowired private SysUserService userService; @Autowired private SysConfigService configService; @Autowired private RedisCache redisCache; /** * 注册 */ public String register(RegisterBody registerBody) { String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); SysUserEntity sysUser = new SysUserEntity(); sysUser.setUserName(username); // 验证码开关 boolean captchaEnabled = configService.selectCaptchaEnabled(); if (captchaEnabled) { validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); } if (StringUtils.isEmpty(username)) { msg = "用户名不能为空"; } else if (StringUtils.isEmpty(password)) { msg = "用户密码不能为空"; } else if (username.length() < UserConstants.USERNAME_MIN_LENGTH || username.length() > UserConstants.USERNAME_MAX_LENGTH) { msg = "账户长度必须在2到20个字符之间"; } else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH || password.length() > UserConstants.PASSWORD_MAX_LENGTH) { msg = "密码长度必须在5到20个字符之间"; } else if (!(userService.checkUserNameUnique(sysUser))) { msg = "保存用户'" + username + "'失败,注册账号已存在"; } else { sysUser.setNickName(username); sysUser.setPassword(SecurityUtils.encryptPassword(password)); boolean regFlag = userService.registerUser(sysUser); if (!regFlag) { msg = "注册失败,请联系系统管理人员"; } else { //注册失败异步记录信息 AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, null, Constants.REGISTER, MessageUtils.message("user.register.success"))); } } return msg; } /** * 校验验证码 * * @param username 用户名 * @param code 验证码 * @param uuid 唯一标识 * @return 结果 */ public void validateCaptcha(String username, String code, String uuid) { String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); String captcha = redisCache.getCacheObject(verifyKey); redisCache.deleteObject(verifyKey); if (captcha == null) { throw new CaptchaExpireException(); } if (!code.equalsIgnoreCase(captcha)) { throw new CaptchaException(); } } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/web/service/TokenService.java ================================================ package com.oddfar.campus.framework.web.service; import com.oddfar.campus.common.constant.CacheConstants; import com.oddfar.campus.common.constant.Constants; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.domain.model.LoginUserToken; import com.oddfar.campus.common.utils.ServletUtils; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.common.utils.ip.AddressUtils; import com.oddfar.campus.common.utils.ip.IpUtils; import com.oddfar.campus.common.utils.uuid.IdUtils; import eu.bitwalker.useragentutils.UserAgent; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; /** * token验证处理 * (根据若依修改,增加LoginUser) * * @author oddfar */ @Component public class TokenService { // 令牌自定义标识 @Value("${token.header}") private String header; // 令牌秘钥 @Value("${token.secret}") private String secret; // 令牌有效期(默认30分钟) @Value("${token.expireTime}") private int expireTime; protected static final long MILLIS_SECOND = 1000; protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L; @Autowired private RedisCache redisCache; /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUser getLoginUser(HttpServletRequest request) { LoginUserToken loginUserToken = getLoginUserToken(request); if (StringUtils.isNotNull(loginUserToken)) { LoginUser user = redisCache.getCacheObject(getLoginKey(loginUserToken.getUserId())); return user; } return null; } /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUserToken getLoginUserToken(HttpServletRequest request) { // 获取请求携带的令牌 String token = getToken(request); if (StringUtils.isNotEmpty(token)) { try { Claims claims = parseToken(token); // 解析对应的权限以及用户信息 String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); String userKey = getTokenKey(uuid); //获取用户id LoginUserToken loginUserToken = redisCache.getCacheObject(userKey); return loginUserToken; } catch (Exception e) { } } return null; } /** * 获取用户身份信息 * * @return 用户信息 */ public LoginUser getLoginUserByUserId(long userId) { String loginKey = getLoginKey(userId); if (redisCache.hasKey(loginKey)) { try { LoginUser user = redisCache.getCacheObject(getLoginKey(userId)); return user; } catch (Exception e) { } } return null; } /** * 设置用户身份信息 */ public void setLoginUser(LoginUser loginUser) { if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { refreshToken(loginUser); } } /** * 删除用户身份信息 */ public void delLoginUser(String token) { if (StringUtils.isNotEmpty(token)) { String userKey = getTokenKey(token); redisCache.deleteObject(userKey); } } /** * 创建令牌 * * @param loginUser 用户信息 * @return 令牌 */ public String createToken(LoginUser loginUser) { String token = IdUtils.fastUUID(); loginUser.setToken(token); //设置登录信息 LoginUserToken loginUserToken = new LoginUserToken(loginUser); setUserAgent(loginUserToken); refreshToken(loginUser, loginUserToken); Map claims = new HashMap<>(); claims.put(Constants.LOGIN_USER_KEY, token); return createToken(claims); } /** * 验证令牌有效期,相差不足20分钟,自动刷新缓存 * * @param loginUser * @return 令牌 */ public void verifyToken(LoginUser loginUser) { long expireTime = loginUser.getExpireTime(); long currentTime = System.currentTimeMillis(); if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { refreshToken(loginUser); } } /** * 刷新令牌有效期 * * @param loginUser 登录信息 */ public void refreshToken(LoginUser loginUser) { String userKey = getTokenKey(loginUser.getToken()); LoginUserToken loginUserToken = redisCache.getCacheObject(userKey); refreshToken(loginUser, loginUserToken); } /** * 刷新令牌有效期 * * @param loginUser 登录信息 */ public void refreshToken(LoginUser loginUser, LoginUserToken loginUserToken) { loginUserToken.setLoginTime(System.currentTimeMillis()); loginUser.setExpireTime(loginUserToken.getLoginTime() + expireTime * MILLIS_MINUTE); loginUserToken.setExpireTime(loginUserToken.getLoginTime() + expireTime * MILLIS_MINUTE); // 根据uuid将loginUser登录的缓存 String userKey = getTokenKey(loginUser.getToken()); redisCache.setCacheObject(userKey, loginUserToken, expireTime, TimeUnit.MINUTES); //设置loginUser缓存 redisCache.setCacheObject(getLoginKey(loginUser.getUserId()), loginUser, expireTime, TimeUnit.MINUTES); } /** * 设置用户代理信息 * * @param loginUserToken 登录信息 */ public void setUserAgent(LoginUserToken loginUserToken) { UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); String ip = IpUtils.getIpAddr(ServletUtils.getRequest()); loginUserToken.setIpaddr(ip); loginUserToken.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); loginUserToken.setBrowser(userAgent.getBrowser().getName()); loginUserToken.setOs(userAgent.getOperatingSystem().getName()); } /** * 从数据声明生成令牌 * * @param claims 数据声明 * @return 令牌 */ private String createToken(Map claims) { String token = Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS512, secret).compact(); return token; } /** * 从令牌中获取数据声明 * * @param token 令牌 * @return 数据声明 */ private Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } /** * 从令牌中获取用户名 * * @param token 令牌 * @return 用户名 */ public String getUsernameFromToken(String token) { Claims claims = parseToken(token); return claims.getSubject(); } /** * 获取请求token * * @param request * @return token */ private String getToken(HttpServletRequest request) { String token = request.getHeader(header); if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { token = token.replace(Constants.TOKEN_PREFIX, ""); } return token; } private String getTokenKey(String uuid) { return CacheConstants.LOGIN_TOKEN_KEY + uuid; } private String getLoginKey(Long userId) { return CacheConstants.LOGIN_USER_KEY + userId; } } ================================================ FILE: campus-framework/src/main/java/com/oddfar/campus/framework/web/service/UserDetailsServiceImpl.java ================================================ package com.oddfar.campus.framework.web.service; import com.oddfar.campus.common.domain.entity.SysUserEntity; import com.oddfar.campus.common.domain.model.LoginUser; import com.oddfar.campus.common.enums.UserStatusEnum; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.exception.user.UserPasswordNotMatchException; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.service.SysUserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; /** * 用户验证处理 * * @author ruoyi */ @Service public class UserDetailsServiceImpl implements UserDetailsService { private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); @Autowired private SysUserService userService; @Autowired private SysPasswordService passwordService; @Autowired private SysPermissionService permissionService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUserEntity user = userService.selectUserByUserName(username); if (StringUtils.isNull(user)) { log.info("登录用户:{} 不存在.", username); throw new UserPasswordNotMatchException(); } else if (UserStatusEnum.DELETED.getCode().equals(user.getStatus())) { log.info("登录用户:{} 已被删除.", username); throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); } else if (UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) { log.info("登录用户:{} 已被停用.", username); throw new ServiceException("对不起,您的账号:" + username + " 已停用"); } passwordService.validate(user); return createLoginUser(user); } public UserDetails createLoginUser(SysUserEntity user) { return new LoginUser(user.getUserId(), user, permissionService.getMenuPermission(user), permissionService.getResources(user)); } } ================================================ FILE: campus-framework/src/main/resources/mapper/SysConfigMapper.xml ================================================ ================================================ FILE: campus-framework/src/main/resources/mapper/SysDictDataMapper.xml ================================================ ================================================ FILE: campus-framework/src/main/resources/mapper/SysDictTypeMapper.xml ================================================ ================================================ FILE: campus-framework/src/main/resources/mapper/SysLoginLogMapper.xml ================================================ truncate table sys_log_login ================================================ FILE: campus-framework/src/main/resources/mapper/SysMenuMapper.xml ================================================ ================================================ FILE: campus-framework/src/main/resources/mapper/SysOperLogMapper.xml ================================================ truncate table sys_log_oper ================================================ FILE: campus-framework/src/main/resources/mapper/SysResourceMapper.xml ================================================ select resource_id, app_code, resource_code, resource_name, class_name, method_name, modular_name, url, http_method, resource_biz_type, required_permission_flag, create_time from sys_resource ================================================ FILE: campus-framework/src/main/resources/mapper/SysRoleMapper.xml ================================================ select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.menu_check_strictly, r.status, r.del_flag, r.create_time, r.remark from sys_role r left join sys_user_role ur on ur.role_id = r.role_id left join sys_user u on u.user_id = ur.user_id ================================================ FILE: campus-framework/src/main/resources/mapper/SysRoleMenuMapper.xml ================================================ delete from sys_role_menu where role_id = #{roleId} delete from sys_role_menu where role_id in #{roleId} insert into sys_role_menu(role_id, menu_id) values (#{item.roleId},#{item.menuId}) ================================================ FILE: campus-framework/src/main/resources/mapper/SysRoleResourceMapper.xml ================================================ insert into sys_role_resource(role_id, resource_code) values (#{item.roleId},#{item.resourceCode}) ================================================ FILE: campus-framework/src/main/resources/mapper/SysUserMapper.xml ================================================ select u.user_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_user, u.create_time, u.remark, r.role_id, r.role_name, r.role_key, r.role_sort, r.status as role_status from sys_user u left join sys_user_role ur on u.user_id = ur.user_id left join sys_role r on r.role_id = ur.role_id update sys_user set password = #{password} where user_name = #{userName} update sys_user set avatar = #{avatar} where user_name = #{userName} ================================================ FILE: campus-framework/src/main/resources/mapper/SysUserRoleMapper.xml ================================================ delete from sys_user_role where user_id in #{userId} ================================================ FILE: campus-modular/Dockerfile ================================================ FROM adoptopenjdk/openjdk8 #FROM java:8 #FROM openjdk:8 MAINTAINER oddfar # 创建目录 RUN mkdir -p /home/campus/conf WORKDIR /home/campus ENV SERVER_PORT=8160 EXPOSE ${SERVER_PORT} ADD ./target/campus-modular.jar ./app.jar ENTRYPOINT ["java", \ "-Djava.security.egd=file:/dev/./urandom", \ "-Dserver.port=${SERVER_PORT}", \ "-jar", "app.jar"] ================================================ FILE: campus-modular/pom.xml ================================================ campus com.oddfar.campus ${revision} 4.0.0 campus-modular 8 8 com.oddfar.campus campus-admin org.springframework.boot spring-boot-maven-plugin 2.5.15 true repackage org.apache.maven.plugins maven-war-plugin 3.0.0 false ${project.artifactId} ${project.artifactId} ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/CampusApplication.java ================================================ package com.oddfar.campus; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import java.time.ZoneOffset; import java.util.TimeZone; /** * GitHub: https://github.com/oddfar/campus-imaotai * @author oddfar */ @SpringBootApplication public class CampusApplication { public static void main(String[] args) { //设置+8时区,避免因为时区问题导致预约时间不正确 TimeZone.setDefault(TimeZone.getTimeZone(ZoneOffset.of("+8"))); SpringApplication.run(CampusApplication.class, args); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/api/PushPlusApi.java ================================================ package com.oddfar.campus.business.api; import cn.hutool.http.HttpUtil; import com.oddfar.campus.business.entity.ILog; import com.oddfar.campus.business.entity.IUser; import com.oddfar.campus.common.utils.StringUtils; import com.oddfar.campus.framework.manager.AsyncManager; import java.util.HashMap; import java.util.Map; import java.util.TimerTask; /** * @author zhiyuan */ public class PushPlusApi { public static void sendNotice(IUser iUser, ILog operLog) { String token = iUser.getPushPlusToken(); if (StringUtils.isEmpty(token)) { return; } String title, content; if (operLog.getStatus() == 0) { //预约成功 title = iUser.getRemark() + "-i茅台执行成功"; content = iUser.getMobile() + System.lineSeparator() + operLog.getLogContent(); AsyncManager.me().execute(sendNotice(token, title, content, "txt")); } else { //预约失败 title = iUser.getRemark() + "-i茅台执行失败"; content = iUser.getMobile() + System.lineSeparator() + operLog.getLogContent(); AsyncManager.me().execute(sendNotice(token, title, content, "txt")); } } /** * push推送 * * @param token token * @param title 消息标题 * @param content 具体消息内容 * @param template 发送消息模板 */ public static TimerTask sendNotice(String token, String title, String content, String template) { return new TimerTask() { @Override public void run() { String url = "http://www.pushplus.plus/send"; Map map = new HashMap<>(); map.put("token", token); map.put("title", title); map.put("content", content); if (StringUtils.isEmpty(template)) { map.put("template", "html"); } HttpUtil.post(url, map); } }; } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/controller/IItemController.java ================================================ package com.oddfar.campus.business.controller; import com.oddfar.campus.business.entity.IItem; import com.oddfar.campus.business.mapper.IItemMapper; import com.oddfar.campus.business.service.IShopService; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.R; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * I茅台预约商品列表Controller * * @author oddfar * @date 2023-07-05 */ @RestController @RequestMapping("/imt/item") @ApiResource(name = "I茅台预约商品列Controller") @RequiredArgsConstructor public class IItemController { private final IItemMapper iItemMapper; private final IShopService iShopService; /** * 查询I茅台预约商品列列表 */ @GetMapping(value = "/list", name = "查询I茅台预约商品列列表") public R list() { List iItems = iItemMapper.selectList(); return R.ok(iItems); } /** * 刷新i茅台预约商品列表 */ @GetMapping(value = "/refresh", name = "刷新i茅台预约商品列表") @PreAuthorize("@ss.resourceAuth()") public R refreshItem() { iShopService.refreshItem(); return R.ok(); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/controller/ILogController.java ================================================ package com.oddfar.campus.business.controller; import com.oddfar.campus.business.entity.ILog; import com.oddfar.campus.business.service.IMTLogService; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; /** * I茅台日志Controller * * @author oddfar * @date 2023-08-01 */ @RestController @RequestMapping("/imt/log") @ApiResource(name = "I茅台日志Controller") @RequiredArgsConstructor public class ILogController { private final IMTLogService logService; @PreAuthorize("@ss.resourceAuth()") @GetMapping(value = "/list", name = "操作日志-分页") public R list(ILog log) { PageResult page = logService.page(log); return R.ok().put(page); } @PreAuthorize("@ss.resourceAuth()") @DeleteMapping(value = "/{operIds}", name = "操作日志-删除") public R remove(@PathVariable Long[] operIds) { return R.ok(logService.deleteLogByIds(operIds)); } @PreAuthorize("@ss.resourceAuth()") @DeleteMapping(value = "/clean", name = "操作日志-清空") public R clean() { logService.cleanLog(); return R.ok(); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/controller/IShopController.java ================================================ package com.oddfar.campus.business.controller; import com.oddfar.campus.business.entity.IShop; import com.oddfar.campus.business.mapper.IShopMapper; import com.oddfar.campus.business.service.IShopService; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * i茅台商品Controller * * @author oddfar * @date 2023-07-05 */ @RestController @RequestMapping("/imt/shop") @ApiResource(name = "i茅台商品Controller") @RequiredArgsConstructor public class IShopController { private final IShopService iShopService; private final IShopMapper iShopMapper; /** * 查询i茅台商品列表 */ @GetMapping("/list") public R list(IShop iShop) { PageResult page = iShopMapper.selectPage(iShop); return R.ok().put(page); } /** * 刷新i茅台商品列表 */ @GetMapping(value = "/refresh", name = "刷新i茅台商品列表") @PreAuthorize("@ss.resourceAuth()") public R refreshShop() { iShopService.refreshShop(); return R.ok(); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/controller/IUserController.java ================================================ package com.oddfar.campus.business.controller; import com.oddfar.campus.business.entity.IShop; import com.oddfar.campus.business.entity.IUser; import com.oddfar.campus.business.mapper.IUserMapper; import com.oddfar.campus.business.service.IMTService; import com.oddfar.campus.business.service.IShopService; import com.oddfar.campus.business.service.IUserService; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.StringUtils; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; /** * I茅台用户Controller * * @author oddfar * @date 2023-07-06 */ @RestController @RequestMapping("/imt/user") @ApiResource(name = "I茅台用户Controller") @RequiredArgsConstructor public class IUserController { private final IUserService iUserService; private final IUserMapper iUserMapper; private final IMTService imtService; private final IShopService iShopService; /** * 查询I茅台用户列表 */ @GetMapping(value = "/list", name = "查询I茅台用户列表") @PreAuthorize("@ss.resourceAuth()") public R list(IUser iUser) { PageResult page = iUserService.page(iUser); return R.ok().put(page); } /** * 发送验证码 */ @GetMapping(value = "/sendCode", name = "发送验证码") @PreAuthorize("@ss.resourceAuth()") public R sendCode(String mobile, String deviceId) { imtService.sendCode(mobile, deviceId); return R.ok(); } /** * 预约 */ @GetMapping(value = "/reservation", name = "预约") @PreAuthorize("@ss.resourceAuth()") public R reservation(String mobile) { IUser user = iUserMapper.selectById(mobile); if (user == null) { return R.error("用户不存在"); } if (StringUtils.isEmpty(user.getItemCode())) { return R.error("商品预约code为空"); } imtService.reservation(user); return R.ok(); } /** * 小茅运旅行活动 */ @GetMapping(value = "/travelReward", name = "旅行") @PreAuthorize("@ss.resourceAuth()") public R travelReward(String mobile) { IUser user = iUserMapper.selectById(mobile); if (user == null) { return R.error("用户不存在"); } else { imtService.getTravelReward(user); return R.ok(); } } /** * 登录 */ @GetMapping(value = "/login", name = "登录") @PreAuthorize("@ss.resourceAuth()") public R login(String mobile, String code, String deviceId) { imtService.login(mobile, code, deviceId); return R.ok(); } /** * 获取I茅台用户详细信息 */ @PreAuthorize("@ss.resourceAuth()") @GetMapping(value = "/{mobile}", name = "获取I茅台用户详细信息") public R getInfo(@PathVariable("mobile") Long mobile) { return R.ok(iUserMapper.selectById(mobile)); } /** * 新增I茅台用户 */ @PreAuthorize("@ss.resourceAuth()") @PostMapping(name = "新增I茅台用户") public R add(@RequestBody IUser iUser) { IShop iShop = iShopService.selectByIShopId(iUser.getIshopId()); if (iShop == null) { throw new ServiceException("门店商品id不存在"); } iUser.setLng(iShop.getLng()); iUser.setLat(iShop.getLat()); iUser.setProvinceName(iShop.getProvinceName()); iUser.setCityName(iShop.getCityName()); return R.ok(iUserService.insertIUser(iUser)); } /** * 修改I茅台用户 */ @PreAuthorize("@ss.resourceAuth()") @PutMapping(name = "修改I茅台用户") public R edit(@RequestBody IUser iUser) { IShop iShop = iShopService.selectByIShopId(iUser.getIshopId()); if (iShop == null) { throw new ServiceException("门店商品id不存在"); } iUser.setLng(iShop.getLng()); iUser.setLat(iShop.getLat()); iUser.setProvinceName(iShop.getProvinceName()); iUser.setCityName(iShop.getCityName()); return R.ok(iUserService.updateIUser(iUser)); } /** * 删除I茅台用户 */ @PreAuthorize("@ss.resourceAuth()") @DeleteMapping(value = "/{mobiles}", name = "删除I茅台用户") public R remove(@PathVariable Long[] mobiles) { return R.ok(iUserMapper.deleteIUser(mobiles)); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/controller/TestController.java ================================================ package com.oddfar.campus.business.controller; import com.oddfar.campus.business.mapper.IUserMapper; import com.oddfar.campus.business.service.IMTService; import com.oddfar.campus.business.service.IShopService; import com.oddfar.campus.common.annotation.Anonymous; import com.oddfar.campus.common.annotation.ApiResource; import com.oddfar.campus.common.annotation.Log; import com.oddfar.campus.common.domain.R; import com.oddfar.campus.common.enums.ResBizTypeEnum; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/test") @ApiResource(name = "测试", appCode = "test", resBizType = ResBizTypeEnum.BUSINESS) @Log(openLog = false) public class TestController { @Autowired private IUserMapper iUserMapper; @Autowired private IMTService imtService; @Autowired private IShopService iShopService; /** * 需要接口权限 */ @PreAuthorize("@ss.resourceAuth()") @GetMapping(value = "/1", name = "测试1的接口") public R test1() { return R.ok(); } /** * 需要 'campus:test' 权限字符串 */ @PreAuthorize("@ss.hasPermi('campus:test')") @GetMapping(value = "/2", name = "测试2的接口") public R test2() { return R.ok(); } /** * 匿名接口,不需要认证,所有人都可访问 */ @Anonymous @GetMapping(value = "/3", name = "测试3的接口") public R test3() { iShopService.selectShopList(); return R.ok(); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/domain/IMTCacheConstants.java ================================================ package com.oddfar.campus.business.domain; /** * i茅台缓存常量 * * @author oddfar * @date 2024/4/12 */ public interface IMTCacheConstants { String MT_VERSION = "mt_version"; String MT_SESSION_ID = "mt_session_id"; String MT_SHOP_LIST = "mt_shop_list"; } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/domain/IMTItemInfo.java ================================================ package com.oddfar.campus.business.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * i茅台预约商品信息 */ @Data @AllArgsConstructor @NoArgsConstructor public class IMTItemInfo { private String shopId; private int count; private String itemId; /** * 库存 */ private int inventory; } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/domain/IUserRequest.java ================================================ package com.oddfar.campus.business.domain; import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * i茅台用户请求对象 */ @Data public class IUserRequest { private static final long serialVersionUID = 1L; /** * 手机号 */ private Long mobile; /** * 用户ID */ private Long userId; /** * token */ private String token; /** * cookie */ private String cookie; /** * 设备id */ private String deviceId; /** * 商品预约code,用@间隔 */ private String itemCode; /** * 完整地址 */ private String address; /** * 预约的分钟(1-59) */ private int minute; /** * 随机分钟预约,9点取一个时间(0:随机,1:不随机) */ private String randomMinute; /** * 类型 */ private int shopType; /** * 门店商品ID */ private String ishopId; /** * push_plus_token */ private String pushPlusToken; /** * 备注 */ private String remark; /** * token过期时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date expireTime; @TableField(exist = false) private Map params; public Map getParams() { if (params == null) { params = new HashMap<>(); } return params; } public int getMinute() { if (minute > 59 || minute < 1) { this.minute = 5; } return minute; } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/domain/MapPoint.java ================================================ package com.oddfar.campus.business.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class MapPoint { /** * 纬度 */ private Double latitude; /** * 经度 */ private Double longitude; } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/entity/IItem.java ================================================ package com.oddfar.campus.business.entity; import com.alibaba.fastjson2.JSONObject; 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; /** * I茅台预约商品对象 i_item * * @author oddfar * @date 2023-07-02 */ @Data @TableName("i_item") public class IItem { private static final long serialVersionUID = 1L; /** * ID */ @TableId private Long itemId; /** * 商品code */ private String itemCode; /** * 标题 */ private String title; /** * 内容 */ private String content; /** * 图片 */ private String picture; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime; public IItem() { } public IItem(JSONObject item) { this.itemCode = item.getString("itemCode"); this.title = item.getString("title");; this.content = item.getString("content");; this.picture = item.getString("picture");; this.createTime = new Date(); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/entity/ILog.java ================================================ package com.oddfar.campus.business.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import javax.validation.constraints.Max; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * I茅台日志 i_log * * @author oddfar * @date 2023-08-01 */ @Data @TableName("i_log") public class ILog { private static final long serialVersionUID = 1L; private static final Integer PAGE_NUM = 1; private static final Integer PAGE_SIZE = 10; /** * 日志主键 */ @TableId("log_id") private Long logId; /** * 日志记录内容 */ private String logContent; /** * 操作人员手机号 */ private Long mobile; /** * 操作状态(0正常 1异常) */ private Integer status; /** * 操作时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date operTime; /** * 上级人 */ private Long createUser; @TableField(exist = false) private Map params; public Map getParams() { if (params == null) { params = new HashMap<>(); } return params; } @NotNull(message = "页码不能为空") @Min(value = 1, message = "页码最小值为 1") @TableField(exist = false) @JsonIgnore private Integer pageNum = PAGE_NUM; @NotNull(message = "每页条数不能为空") @Min(value = 1, message = "每页条数最小值为 1") @Max(value = 100, message = "每页条数最大值为 100") @TableField(exist = false) @JsonIgnore private Integer pageSize = PAGE_SIZE; } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/entity/IShop.java ================================================ package com.oddfar.campus.business.entity; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.annotation.TableField; 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; import java.util.HashMap; import java.util.Map; /** * I茅台商品对象 i_shop * * @author oddfar * @date 2023-07-02 */ @Data @TableName("i_shop") public class IShop { private static final long serialVersionUID = 1L; /** * ID */ @TableId private Long shopId; /** * 商品ID */ private String iShopId; /** * 省份 */ private String provinceName; /** * 城市 */ private String cityName; /** * 地区 */ private String districtName; /** * 完整地址 */ private String fullAddress; /** * 纬度 */ private String lat; /** * 经度 */ private String lng; /** * 名称 */ private String name; /** * 公司名称 */ private String tenantName; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime; @TableField(exist = false) private Map params; /** * 距离 */ @TableField(exist = false) private Double distance; public IShop() { } public IShop(String iShopId, JSONObject jsonObject) { this.iShopId = iShopId; this.provinceName = jsonObject.getString("provinceName"); this.cityName = jsonObject.getString("cityName"); this.districtName = jsonObject.getString("districtName"); this.fullAddress = jsonObject.getString("fullAddress"); this.lat = jsonObject.getString("lat"); this.lng = jsonObject.getString("lng"); this.name = jsonObject.getString("name"); this.tenantName = jsonObject.getString("tenantName"); this.createTime = new Date(); } public Map getParams() { if (params == null) { params = new HashMap<>(); } return params; } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/entity/IUser.java ================================================ package com.oddfar.campus.business.entity; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.oddfar.campus.common.domain.BaseEntity; import com.oddfar.campus.common.utils.StringUtils; import lombok.Data; import lombok.EqualsAndHashCode; import java.util.*; /** * I茅台用户对象 i_user * * @author oddfar * @date 2023-07-02 */ @Data @EqualsAndHashCode(callSuper = true) @TableName("i_user") public class IUser extends BaseEntity { private static final long serialVersionUID = 1L; /** * 手机号 */ @TableId private Long mobile; /** * 用户ID */ private Long userId; /** * token */ private String token; /** * cookie */ private String cookie; /** * 设备id */ private String deviceId; /** * 商品预约code,用@间隔 */ private String itemCode; /** * 门店商品ID */ private String ishopId; /** * 省份 */ private String provinceName; /** * 城市 */ private String cityName; /** * 完整地址 */ private String address; /** * 纬度 */ private String lat; /** * 经度 */ private String lng; /** * 预约的分钟(1-59) */ private int minute; /** * 随机分钟预约,9点取一个时间(0:随机,1:不随机) */ private String randomMinute; /** * 类型(1:预约本市出货量最大的门店,2:预约你的位置附近门店) */ private int shopType; /** * push_plus_token */ private String pushPlusToken; /** * 返回参数 */ @JsonIgnore private String jsonResult; /** * 备注 */ private String remark; /** * token过期时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date expireTime; @TableField(exist = false) private Map params; public IUser() { } public IUser(Long mobile, JSONObject jsonObject) { JSONObject data = jsonObject.getJSONObject("data"); this.userId = data.getLong("userId"); this.mobile = mobile; this.token = data.getString("token"); this.cookie = data.getString("cookie"); this.jsonResult = StringUtils.substring(jsonObject.toJSONString(), 0, 2000); // if (StringUtils.isEmpty(this.remark)) { // this.remark = data.getString("userName"); // } Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_MONTH, 30); Date thirtyDaysLater = calendar.getTime(); this.expireTime = thirtyDaysLater; } public IUser(Long mobile, String deviceId, JSONObject jsonObject) { JSONObject data = jsonObject.getJSONObject("data"); this.userId = data.getLong("userId"); this.mobile = mobile; this.token = data.getString("token"); this.cookie = data.getString("cookie"); this.deviceId = deviceId.toLowerCase(); this.jsonResult = StringUtils.substring(jsonObject.toJSONString(), 0, 2000); if (StringUtils.isEmpty(this.remark)) { this.remark = data.getString("userName"); } Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_MONTH, 30); Date thirtyDaysLater = calendar.getTime(); this.expireTime = thirtyDaysLater; } public Map getParams() { if (params == null) { params = new HashMap<>(); } return params; } public int getMinute() { if (minute > 59 || minute < 1) { this.minute = 5; } return minute; } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/mapper/IItemMapper.java ================================================ package com.oddfar.campus.business.mapper; import com.oddfar.campus.business.entity.IItem; import com.oddfar.campus.common.core.BaseMapperX; import org.apache.ibatis.annotations.Update; /** * I茅台预约商品Mapper接口 * * @author oddfar * @date 2023-07-02 */ public interface IItemMapper extends BaseMapperX { //清空指定表 @Update("truncate table i_item") void truncateItem(); } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/mapper/ILogMapper.java ================================================ package com.oddfar.campus.business.mapper; import com.oddfar.campus.business.entity.ILog; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import org.apache.ibatis.annotations.Select; /** * I茅台日志Mapper接口 * * @author oddfar * @date 2023-08-01 */ public interface ILogMapper extends BaseMapperX { default PageResult selectPage(ILog iLog) { return selectPage(new LambdaQueryWrapperX() .eqIfPresent(ILog::getMobile, iLog.getMobile()) .eqIfPresent(ILog::getStatus, iLog.getStatus()) .betweenIfPresent(ILog::getOperTime, iLog.getParams()) .orderByDesc(ILog::getLogId) ); } default PageResult selectPage(ILog iLog, Long userId) { return selectPage(new LambdaQueryWrapperX() .eqIfPresent(ILog::getMobile, iLog.getMobile()) .eqIfPresent(ILog::getStatus, iLog.getStatus()) .eq(ILog::getCreateUser, userId) .betweenIfPresent(ILog::getOperTime, iLog.getParams()) .orderByDesc(ILog::getLogId) ); } @Select("truncate table i_log") void cleanLog(); } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/mapper/IShopMapper.java ================================================ package com.oddfar.campus.business.mapper; import com.oddfar.campus.business.entity.IShop; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import org.apache.ibatis.annotations.Update; /** * I茅台商品Mapper接口 * * @author oddfar * @date 2023-07-02 */ public interface IShopMapper extends BaseMapperX { //清空指定表 @Update("truncate table i_shop") void truncateShop(); default PageResult selectPage(IShop iShop) { return selectPage(new LambdaQueryWrapperX() .eqIfPresent(IShop::getIShopId,iShop.getIShopId()) .likeIfPresent(IShop::getProvinceName, iShop.getProvinceName()) .likeIfPresent(IShop::getDistrictName, iShop.getDistrictName()) .likeIfPresent(IShop::getCityName, iShop.getCityName()) .likeIfPresent(IShop::getTenantName, iShop.getTenantName()) .betweenIfPresent(IShop::getCreateTime, iShop.getParams()) ); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/mapper/IUserMapper.java ================================================ package com.oddfar.campus.business.mapper; import com.oddfar.campus.business.entity.IUser; import com.oddfar.campus.common.core.BaseMapperX; import com.oddfar.campus.common.core.LambdaQueryWrapperX; import com.oddfar.campus.common.domain.PageResult; import org.apache.ibatis.annotations.Update; import java.util.List; /** * I茅台用户Mapper接口 * * @author oddfar * @date 2023-07-02 */ public interface IUserMapper extends BaseMapperX { default PageResult selectPage(IUser iUser) { return selectPage(new LambdaQueryWrapperX() .eqIfPresent(IUser::getUserId, iUser.getUserId()) .eqIfPresent(IUser::getMobile, iUser.getMobile()) .eqIfPresent(IUser::getProvinceName, iUser.getProvinceName()) .betweenIfPresent(IUser::getExpireTime, iUser.getParams()) .orderByAsc(IUser::getCreateTime) ); } default PageResult selectPage(IUser iUser, Long userId) { return selectPage(new LambdaQueryWrapperX() .eqIfPresent(IUser::getUserId, iUser.getUserId()) .eqIfPresent(IUser::getMobile, iUser.getMobile()) .eqIfPresent(IUser::getProvinceName, iUser.getProvinceName()) .eq(IUser::getCreateUser, userId) .betweenIfPresent(IUser::getExpireTime, iUser.getParams()) .orderByAsc(IUser::getCreateTime) ); } default List selectReservationUser() { return selectList(new LambdaQueryWrapperX() // .gt(IUser::getExpireTime, new Date()) .ne(IUser::getLat, "") .ne(IUser::getLng, "") .ne(IUser::getItemCode, "") .isNotNull(IUser::getItemCode) ); } /** * 通过预约执行分钟查询预约用户列表 */ default List selectReservationUserByMinute(int minute) { return selectList(new LambdaQueryWrapperX() .eq(IUser::getMinute, minute) // .gt(IUser::getExpireTime, new Date()) .ne(IUser::getLat, "") .ne(IUser::getLng, "") .ne(IUser::getItemCode, "") .isNotNull(IUser::getItemCode) ); } @Update("UPDATE i_user SET `minute` = (SELECT FLOOR(RAND() * 50 + 1)) WHERE random_minute = \"0\"") void updateUserMinuteBatch(); @Update("SET @row_number = 0;\n" + "UPDATE i_user\n" + "SET `minute` = (@row_number := @row_number + 1) % 50 + 1\n" + "ORDER BY RAND();") void updateUserMinuteEven(); int deleteIUser(Long[] iUserId); } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/IMTLogFactory.java ================================================ package com.oddfar.campus.business.service; import com.oddfar.campus.business.api.PushPlusApi; import com.oddfar.campus.business.entity.ILog; import com.oddfar.campus.business.entity.IUser; import com.oddfar.campus.common.utils.SpringUtils; import com.oddfar.campus.framework.manager.AsyncManager; import java.util.Date; import java.util.TimerTask; /** * i茅台日志记录 */ public class IMTLogFactory { public static void reservation(IUser iUser, String logContent) { //{"code":2000,"data":{"successDesc":"申购完成,请于7月6日18:00查看预约申购结果","reservationList":[{"reservationId":17053404357,"sessionId":678,"shopId":"233331084001","reservationTime":1688608601720,"itemId":"10214","count":1}],"reservationDetail":{"desc":"申购成功后将以短信形式通知您,请您在申购成功次日18:00前确认支付方式,并在7天内完成提货。","lotteryTime":1688637600000,"cacheValidTime":1688637600000}}} ILog operLog = new ILog(); operLog.setOperTime(new Date()); if (logContent.contains("报错")) { //失败 operLog.setStatus(1); } else { operLog.setStatus(0); } operLog.setMobile(iUser.getMobile()); operLog.setCreateUser(iUser.getCreateUser()); operLog.setLogContent(logContent); AsyncManager.me().execute(recordOper(operLog)); //推送 PushPlusApi.sendNotice(iUser, operLog); } /** * 操作日志记录 * * @param operLog 操作日志信息 * @return 任务task */ public static TimerTask recordOper(final ILog operLog) { return new TimerTask() { @Override public void run() { SpringUtils.getBean(IMTLogService.class).insertLog(operLog); } }; } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/IMTLogService.java ================================================ package com.oddfar.campus.business.service; import com.oddfar.campus.business.entity.ILog; import com.oddfar.campus.common.domain.PageResult; /** * i茅台 日志 */ public interface IMTLogService { PageResult page(ILog iLog); /** * 新增操作日志 * * @param iLog 操作日志对象 */ public int insertLog(ILog iLog); /** * 批量删除系统操作日志 * * @param operIds 需要删除的操作日志ID * @return 结果 */ public int deleteLogByIds(Long[] operIds); /** * 清空操作日志 */ public void cleanLog(); } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/IMTService.java ================================================ package com.oddfar.campus.business.service; import com.oddfar.campus.business.entity.IUser; public interface IMTService { /** * 获取i茅台app版本号 * * @return */ String getMTVersion(); /** * 刷新i茅台app版本号 */ void refreshMTVersion(); /** * 发送手机验证码 * * @param mobile 手机号 * @param deviceId 设备id */ Boolean sendCode(String mobile, String deviceId); /** * 登录i茅台 * * @param mobile 手机号 * @param code 验证码 * @param deviceId 设备id * @return */ boolean login(String mobile, String code, String deviceId); /** * 预约 */ void reservation(IUser iUser); /** * 获取申购耐力值 */ String getEnergyAward(IUser iUser); /** * 获得旅行奖励 * * @param iUser * @return */ void getTravelReward(IUser iUser); // // /** // * 获取累计申购奖励 // * @param iUser // */ // void getCumulatively(IUser iUser); /** * 批量预约 */ void reservationBatch(); /** * 批量获得旅行奖励 */ void getTravelRewardBatch(); /** * 刷新版本号,预约item,门店shop列表, */ void refreshAll(); /** * 每日预约申购结果 */ void appointmentResults(); } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/IShopService.java ================================================ package com.oddfar.campus.business.service; import com.baomidou.mybatisplus.extension.service.IService; import com.oddfar.campus.business.domain.IMTItemInfo; import com.oddfar.campus.business.entity.IShop; import java.util.List; public interface IShopService extends IService { List selectShopList(); /** * 刷新数据库i茅台shop列表 */ void refreshShop(); /** * 获取当天的sessionId */ String getCurrentSessionId(); /** * 刷新i茅台预约商品列表 */ void refreshItem(); /** * 根据 iShopId 查询门店信息 *

* 预约选择门店时,根据城市、经纬度选择门店 * 之前这些参数是在账号配置里手动填写 * 现在是查询门店的信息,把门店的这些信息填到账号配置里 * 主要是这样避免用户填错信息 * * @param iShopId * @return 门店信息 */ IShop selectByIShopId(String iShopId); /** * 查询所在省市的投放产品和数量 * * @param province 省份,例如:河北省,北京市 * @param itemId 项目id即预约项目code */ List getShopsByProvince(String province, String itemId); /** * @param shopType 1:预约本市出货量最大的门店,2:预约你的位置附近门店 * @param itemId 项目id即预约项目code * @param province 省份,例如:河北省,北京市 * @param city 市:例如石家庄市 * @return */ String getShopId(int shopType, String itemId, String province, String city, String lat, String lng); } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/IUserService.java ================================================ package com.oddfar.campus.business.service; import com.alibaba.fastjson2.JSONObject; import com.oddfar.campus.business.entity.IUser; import com.oddfar.campus.common.domain.PageResult; import java.util.List; public interface IUserService { PageResult page(IUser iUser); /** * 添加i茅台用户 * * @param mobile * @param body * @return */ int insertIUser(Long mobile, String deviceId, JSONObject body); /** * 查询预约用户列表 * * @return */ List selectReservationUser(); /** * 通过预约执行分钟查询预约用户列表 * * @return */ List selectReservationUserByMinute(int minute); /** * 添加i茅台用户 * * @param iUser * @return */ int insertIUser(IUser iUser); /** * 修改I茅台用户 * * @param iUser I茅台用户 * @return 结果 */ int updateIUser(IUser iUser); /** * 批量修改用户随机预约的时间 * * @return */ void updateUserMinuteBatch(); /** * 删除用户 * * @param iUserId id * @return */ int deleteIUser(Long[] iUserId); } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/impl/IMTLogServiceImpl.java ================================================ package com.oddfar.campus.business.service.impl; import com.oddfar.campus.business.entity.ILog; import com.oddfar.campus.business.mapper.ILogMapper; import com.oddfar.campus.business.service.IMTLogService; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.utils.SecurityUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Arrays; @Service public class IMTLogServiceImpl implements IMTLogService { @Autowired private ILogMapper logMapper; @Override public PageResult page(ILog iLog) { Long userId = SecurityUtils.getUserId(); if (userId != 1) { return logMapper.selectPage(iLog, userId); } return logMapper.selectPage(iLog); } @Override public int deleteLogByIds(Long[] operIds) { return logMapper.deleteBatchIds(Arrays.asList(operIds)); } @Override public void cleanLog() { logMapper.cleanLog(); } @Override public int insertLog(ILog iLog) { return logMapper.insert(iLog); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/impl/IMTServiceImpl.java ================================================ package com.oddfar.campus.business.service.impl; import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.symmetric.AES; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import cn.hutool.http.Method; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.oddfar.campus.business.domain.IMTCacheConstants; import com.oddfar.campus.business.entity.IUser; import com.oddfar.campus.business.mapper.IUserMapper; import com.oddfar.campus.business.service.IMTLogFactory; import com.oddfar.campus.business.service.IMTService; import com.oddfar.campus.business.service.IShopService; import com.oddfar.campus.business.service.IUserService; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.Random; @Service public class IMTServiceImpl implements IMTService { private static final Logger logger = LoggerFactory.getLogger(IMTServiceImpl.class); @Autowired private IUserMapper iUserMapper; @Autowired private RedisCache redisCache; @Autowired private IUserService iUserService; @Autowired private IShopService iShopService; private final static String SALT = "2af72f100c356273d46284f6fd1dfc08"; private final static String AES_KEY = "qbhajinldepmucsonaaaccgypwuvcjaa"; private final static String AES_IV = "2018534749963515"; /** * 项目启动时,初始化数据 */ @PostConstruct public void init() { new Thread(new Runnable() { @Override public void run() { refreshAll(); } }).start(); } @Override public String getMTVersion() { String mtVersion = Convert.toStr(redisCache.getCacheObject(IMTCacheConstants.MT_VERSION)); if (StringUtils.isNotEmpty(mtVersion)) { return mtVersion; } String url = "https://apps.apple.com/cn/app/i%E8%8C%85%E5%8F%B0/id1600482450"; String htmlContent = HttpUtil.get(url); Pattern pattern = Pattern.compile("new__latest__version\">(.*?)

", Pattern.DOTALL); Matcher matcher = pattern.matcher(htmlContent); if (matcher.find()) { mtVersion = matcher.group(1); mtVersion = mtVersion.replace("版本 ", ""); } redisCache.setCacheObject(IMTCacheConstants.MT_VERSION, mtVersion); return mtVersion; } @Override public void refreshMTVersion() { redisCache.deleteObject(IMTCacheConstants.MT_VERSION); getMTVersion(); } @Override public Boolean sendCode(String mobile, String deviceId) { Map data = new HashMap<>(); data.put("mobile", mobile); final long curTime = System.currentTimeMillis(); data.put("md5", signature(mobile, curTime)); data.put("timestamp", String.valueOf(curTime)); // data.put("MT-APP-Version", MT_VERSION); HttpRequest request = HttpUtil.createRequest(Method.POST, "https://app.moutai519.com.cn/xhr/front/user/register/vcode"); request.header("MT-Device-ID", deviceId); request.header("MT-APP-Version", getMTVersion()); request.header("User-Agent", "iOS;16.3;Apple;?unrecognized?"); request.header("Content-Type", "application/json"); HttpResponse execute = request.body(JSONObject.toJSONString(data)).execute(); JSONObject jsonObject = JSONObject.parseObject(execute.body()); //成功返回 {"code":2000} logger.info("「发送验证码返回」:" + jsonObject.toJSONString()); if (jsonObject.getString("code").equals("2000")) { return Boolean.TRUE; } else { logger.error("「发送验证码-失败」:" + jsonObject.toJSONString()); throw new ServiceException("发送验证码错误"); // return false; } } @Override public boolean login(String mobile, String code, String deviceId) { Map map = new HashMap<>(); map.put("mobile", mobile); map.put("vCode", code); final long curTime = System.currentTimeMillis(); map.put("md5", signature(mobile + code + "" + "", curTime)); map.put("timestamp", String.valueOf(curTime)); map.put("MT-APP-Version", getMTVersion()); HttpRequest request = HttpUtil.createRequest(Method.POST, "https://app.moutai519.com.cn/xhr/front/user/register/login"); IUser user = iUserMapper.selectById(mobile); if (user != null) { deviceId = user.getDeviceId(); } request.header("MT-Device-ID", deviceId); request.header("MT-APP-Version", getMTVersion()); request.header("User-Agent", "iOS;16.3;Apple;?unrecognized?"); request.header("Content-Type", "application/json"); HttpResponse execute = request.body(JSONObject.toJSONString(map)).execute(); JSONObject body = JSONObject.parseObject(execute.body()); if (body.getString("code").equals("2000")) { // logger.info("「登录请求-成功」" + body.toJSONString()); iUserService.insertIUser(Long.parseLong(mobile), deviceId, body); return true; } else { logger.error("「登录请求-失败」" + body.toJSONString()); throw new ServiceException("登录失败,本地错误日志已记录"); // return false; } } @Override public void reservation(IUser iUser) { if (StringUtils.isEmpty(iUser.getItemCode())) { return; } String[] items = iUser.getItemCode().split("@"); String logContent = ""; for (String itemId : items) { try { String shopId = iShopService.getShopId(iUser.getShopType(), itemId, iUser.getProvinceName(), iUser.getCityName(), iUser.getLat(), iUser.getLng()); //预约 JSONObject json = reservation(iUser, itemId, shopId); logContent += String.format("[预约项目]:%s\n[shopId]:%s\n[结果返回]:%s\n\n", itemId, shopId, json.toString()); //随机延迟3~5秒 Random random = new Random(); int sleepTime = random.nextInt(3) + 3; Thread.sleep(sleepTime * 1000); } catch (Exception e) { logContent += String.format("执行报错--[预约项目]:%s\n[结果返回]:%s\n\n", itemId, e.getMessage()); } } // try { // //预约后领取耐力值 // String energyAward = getEnergyAward(iUser); // logContent += "[申购耐力值]:" + energyAward; // } catch (Exception e) { // logContent += "执行报错--[申购耐力值]:" + e.getMessage(); // } //日志记录 IMTLogFactory.reservation(iUser, logContent); //预约后延迟领取耐力值 getEnergyAwardDelay(iUser); } /** * 延迟执行:获取申购耐力值,并记录日志 * * @param iUser */ public void getEnergyAwardDelay(IUser iUser) { Runnable runnable = new Runnable() { @Override public void run() { String logContent = ""; //sleep 10秒 try { Thread.sleep(10000); //预约后领取耐力值 String energyAward = getEnergyAward(iUser); logContent += "[申购耐力值]:" + energyAward; } catch (Exception e) { logContent += "执行报错--[申购耐力值]:" + e.getMessage(); } //日志记录 IMTLogFactory.reservation(iUser, logContent); } }; new Thread(runnable).start(); } // 领取小茅运 public void receiveReward(IUser iUser) { String url = "https://h5.moutai519.com.cn/game/xmTravel/receiveReward"; HttpRequest request = HttpUtil.createRequest(Method.POST, url); request.header("MT-Device-ID", iUser.getDeviceId()) .header("MT-APP-Version", getMTVersion()) .header("User-Agent", "iOS;16.3;Apple;?unrecognized?") .header("MT-Lat", iUser.getLat()) .header("MT-Lng", iUser.getLng()) .cookie("MT-Token-Wap=" + iUser.getCookie() + ";MT-Device-ID-Wap=" + iUser.getDeviceId() + ";"); HttpResponse execute = request.execute(); JSONObject body = JSONObject.parseObject(execute.body()); if (body.getInteger("code") != 2000) { String message = "领取小茅运失败"; throw new ServiceException(message); } } public void shareReward(IUser iUser) { logger.info("「领取每日首次分享获取耐力」:" + iUser.getMobile()); String url = "https://h5.moutai519.com.cn/game/xmTravel/shareReward"; HttpRequest request = HttpUtil.createRequest(Method.POST, url); request.header("MT-Device-ID", iUser.getDeviceId()) .header("MT-APP-Version", getMTVersion()) .header("User-Agent", "iOS;16.3;Apple;?unrecognized?") .header("MT-Lat", iUser.getLat()) .header("MT-Lng", iUser.getLng()) .cookie("MT-Token-Wap=" + iUser.getCookie() + ";MT-Device-ID-Wap=" + iUser.getDeviceId() + ";"); HttpResponse execute = request.execute(); JSONObject body = JSONObject.parseObject(execute.body()); if (body.getInteger("code") != 2000) { String message = "领取每日首次分享获取耐力失败"; throw new ServiceException(message); } } //获取申购耐力值 @Override public String getEnergyAward(IUser iUser) { String url = "https://h5.moutai519.com.cn/game/isolationPage/getUserEnergyAward"; HttpRequest request = HttpUtil.createRequest(Method.POST, url); request.header("MT-Device-ID", iUser.getDeviceId()) .header("MT-APP-Version", getMTVersion()) .header("User-Agent", "iOS;16.3;Apple;?unrecognized?") .header("MT-Lat", iUser.getLat()) .header("MT-Lng", iUser.getLng()) .cookie("MT-Token-Wap=" + iUser.getCookie() + ";MT-Device-ID-Wap=" + iUser.getDeviceId() + ";"); String body = request.execute().body(); JSONObject jsonObject = JSONObject.parseObject(body); if (jsonObject.getInteger("code") != 200) { String message = jsonObject.getString("message"); throw new ServiceException(message); } return body; } @Override public void getTravelReward(IUser iUser) { String logContent = ""; try { String s = travelReward(iUser); logContent += "[获得旅行奖励]:" + s; } catch (Exception e) { // e.printStackTrace(); logContent += "执行报错--[获得旅行奖励]:" + e.getMessage(); } //日志记录 IMTLogFactory.reservation(iUser, logContent); } /** * 获得旅行奖励 * * @param iUser * @return */ public String travelReward(IUser iUser) { //9-20点才能领取旅行奖励 int hour = DateUtil.hour(new Date(), true); if (!(9 <= hour && hour < 20)) { String message = "活动未开始,开始时间9点-20点"; throw new ServiceException(message); } Map pageData = getUserIsolationPageData(iUser); Integer status = pageData.get("status"); if (status == 3) { Integer currentPeriodCanConvertXmyNum = pageData.get("currentPeriodCanConvertXmyNum"); Double travelRewardXmy = getXmTravelReward(iUser); // 获取小茅运 receiveReward(iUser); //首次分享获取耐力 shareReward(iUser); //本次旅行奖励领取后, 当月实际剩余旅行奖励 if (travelRewardXmy > currentPeriodCanConvertXmyNum) { String message = "当月无可领取奖励"; throw new ServiceException(message); } } Integer remainChance = pageData.get("remainChance"); if (remainChance < 1) { String message = "当日旅行次数已耗尽"; throw new ServiceException(message); } else { //小茅运旅行活动 return startTravel(iUser); } } //小茅运旅行活动 public String startTravel(IUser iUser) { String url = "https://h5.moutai519.com.cn/game/xmTravel/startTravel"; HttpRequest request = HttpUtil.createRequest(Method.POST, url); request.header("MT-Device-ID", iUser.getDeviceId()) .header("MT-APP-Version", getMTVersion()) .header("User-Agent", "iOS;16.3;Apple;?unrecognized?") .cookie("MT-Token-Wap=" + iUser.getCookie() + ";MT-Device-ID-Wap=" + iUser.getDeviceId() + ";"); String body = request.execute().body(); JSONObject jsonObject = JSONObject.parseObject(body); if (jsonObject.getInteger("code") != 2000) { String message = "开始旅行失败:" + jsonObject.getString("message"); throw new ServiceException(message); } JSONObject data = jsonObject.getJSONObject("data"); //最后返回 {"startTravelTs":1690798861076} return jsonObject.toString(); } //查询 可获取小茅运 public Double getXmTravelReward(IUser iUser) { //查询旅行奖励: String url = "https://h5.moutai519.com.cn/game/xmTravel/getXmTravelReward"; HttpRequest request = HttpUtil.createRequest(Method.GET, url); request.header("MT-Device-ID", iUser.getDeviceId()) .header("MT-APP-Version", getMTVersion()) .header("User-Agent", "iOS;16.3;Apple;?unrecognized?") .cookie("MT-Token-Wap=" + iUser.getCookie() + ";MT-Device-ID-Wap=" + iUser.getDeviceId() + ";"); String body = request.execute().body(); JSONObject jsonObject = JSONObject.parseObject(body); if (jsonObject.getInteger("code") != 2000) { String message = jsonObject.getString("message"); throw new ServiceException(message); } JSONObject data = jsonObject.getJSONObject("data"); Double travelRewardXmy = data.getDouble("travelRewardXmy"); //例如 1.95 return travelRewardXmy; } //获取用户页面数据 public Map getUserIsolationPageData(IUser iUser) { //查询小茅运信息 String url = "https://h5.moutai519.com.cn/game/isolationPage/getUserIsolationPageData"; HttpRequest request = HttpUtil.createRequest(Method.GET, url); request.header("MT-Device-ID", iUser.getDeviceId()) .header("MT-APP-Version", getMTVersion()) .header("User-Agent", "iOS;16.3;Apple;?unrecognized?") .cookie("MT-Token-Wap=" + iUser.getCookie() + ";MT-Device-ID-Wap=" + iUser.getDeviceId() + ";"); String body = request.form("__timestamp", DateUtil.currentSeconds()).execute().body(); JSONObject jsonObject = JSONObject.parseObject(body); if (jsonObject.getInteger("code") != 2000) { String message = jsonObject.getString("message"); throw new ServiceException(message); } JSONObject data = jsonObject.getJSONObject("data"); //xmy: 小茅运值 String xmy = data.getString("xmy"); //energy: 耐力值 int energy = data.getIntValue("energy"); JSONObject xmTravel = data.getJSONObject("xmTravel"); JSONObject energyReward = data.getJSONObject("energyReward"); //status: 1. 未开始 2. 进行中 3. 已完成 Integer status = xmTravel.getInteger("status"); // travelEndTime: 旅行结束时间 Long travelEndTime = xmTravel.getLong("travelEndTime"); //remainChance 今日剩余旅行次数 int remainChance = xmTravel.getIntValue("remainChance"); //可领取申购耐力值奖励 Integer energyValue = energyReward.getInteger("value"); if (energyValue > 0) { //获取申购耐力值 getEnergyAward(iUser); energy += energyValue; } // 本月剩余旅行奖励 int exchangeRateInfo = getExchangeRateInfo(iUser); if (exchangeRateInfo <= 0) { String message = "当月无可领取奖励"; throw new ServiceException(message); } Long endTime = travelEndTime * 1000; // 未开始 if (status == 1) { if (energy < 100) { String message = String.format("耐力不足100, 当前耐力值:%s", energy); throw new ServiceException(message); } } // 进行中 if (status == 2) { Date date = new Date(endTime); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String formattedDate = sdf.format(date); String message = String.format("旅行暂未结束,本次旅行结束时间:%s ", formattedDate); throw new ServiceException(message); } Map map = new HashMap<>(); map.put("remainChance", remainChance); map.put("status", status); map.put("currentPeriodCanConvertXmyNum", getExchangeRateInfo(iUser)); return map; } // 获取本月剩余奖励耐力值 public int getExchangeRateInfo(IUser iUser) { String url = "https://h5.moutai519.com.cn/game/synthesize/exchangeRateInfo"; HttpRequest request = HttpUtil.createRequest(Method.GET, url); request.header("MT-Device-ID", iUser.getDeviceId()) .header("MT-APP-Version", getMTVersion()) .header("User-Agent", "iOS;16.3;Apple;?unrecognized?") .cookie("MT-Token-Wap=" + iUser.getCookie() + ";MT-Device-ID-Wap=" + iUser.getDeviceId() + ";"); String body = request.form("__timestamp", DateUtil.currentSeconds()).execute().body(); JSONObject jsonObject = JSONObject.parseObject(body); if (jsonObject.getInteger("code") != 2000) { String message = jsonObject.getString("message"); throw new ServiceException(message); } //获取本月剩余奖励耐力值 return jsonObject.getJSONObject("data").getIntValue("currentPeriodCanConvertXmyNum"); } @Async @Override public void reservationBatch() { int minute = DateUtil.minute(new Date()); List iUsers = iUserService.selectReservationUserByMinute(minute); for (IUser iUser : iUsers) { logger.info("「开始预约用户」" + iUser.getMobile()); //预约 reservation(iUser); //延时3秒 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } } @Async @Override public void getTravelRewardBatch() { try { int minute = DateUtil.minute(new Date()); List iUsers = iUserService.selectReservationUserByMinute(minute); // List iUsers = iUserService.selectReservationUser(); for (IUser iUser : iUsers) { logger.info("「开始获得旅行奖励」" + iUser.getMobile()); getTravelReward(iUser); //延时3秒 TimeUnit.SECONDS.sleep(3); } } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void refreshAll() { refreshMTVersion(); iShopService.refreshShop(); iShopService.refreshItem(); } @Override public void appointmentResults() { logger.info("申购结果查询开始========================="); List iUsers = iUserService.selectReservationUser(); for (IUser iUser : iUsers) { try { String url = "https://app.moutai519.com.cn/xhr/front/mall/reservation/list/pageOne/query"; String body = HttpUtil.createRequest(Method.GET, url) .header("MT-Device-ID", iUser.getDeviceId()) .header("MT-APP-Version", getMTVersion()) .header("MT-Token", iUser.getToken()) .header("User-Agent", "iOS;16.3;Apple;?unrecognized?").execute().body(); JSONObject jsonObject = JSONObject.parseObject(body); logger.info("查询申购结果回调: user->{},response->{}", iUser.getMobile(), body); if (jsonObject.getInteger("code") != 2000) { String message = jsonObject.getString("message"); throw new ServiceException(message); } JSONArray itemVOs = jsonObject.getJSONObject("data").getJSONArray("reservationItemVOS"); if (Objects.isNull(itemVOs) || itemVOs.isEmpty()) { logger.info("申购记录为空: user->{}", iUser.getMobile()); continue; } for (Object itemVO : itemVOs) { JSONObject item = JSON.parseObject(itemVO.toString()); // 预约时间在24小时内的 if (item.getInteger("status") == 2 && DateUtil.between(item.getDate("reservationTime"), new Date(), DateUnit.HOUR) < 24) { String logContent = DateUtil.formatDate(item.getDate("reservationTime")) + " 申购" + item.getString("itemName") + "成功"; IMTLogFactory.reservation(iUser, logContent); } } } catch (Exception e) { logger.error("查询申购结果失败:失败原因->{}", e.getMessage(), e); } } logger.info("申购结果查询结束========================="); } public JSONObject reservation(IUser iUser, String itemId, String shopId) { Map map = new HashMap<>(); JSONArray itemArray = new JSONArray(); Map info = new HashMap<>(); info.put("count", 1); info.put("itemId", itemId); itemArray.add(info); map.put("itemInfoList", itemArray); map.put("sessionId", iShopService.getCurrentSessionId()); map.put("userId", iUser.getUserId().toString()); map.put("shopId", shopId); map.put("actParam", AesEncrypt(JSON.toJSONString(map))); HttpRequest request = HttpUtil.createRequest(Method.POST, "https://app.moutai519.com.cn/xhr/front/mall/reservation/add"); request.header("MT-Lat", iUser.getLat()); request.header("MT-Lng", iUser.getLng()); request.header("MT-Token", iUser.getToken()); request.header("MT-Info", "028e7f96f6369cafe1d105579c5b9377"); request.header("MT-Device-ID", iUser.getDeviceId()); request.header("MT-APP-Version", getMTVersion()); request.header("User-Agent", "iOS;16.3;Apple;?unrecognized?"); request.header("Content-Type", "application/json"); request.header("userId", iUser.getUserId().toString()); HttpResponse execute = request.body(JSONObject.toJSONString(map)).execute(); JSONObject body = JSONObject.parseObject(execute.body()); //{"code":2000,"data":{"successDesc":"申购完成,请于7月6日18:00查看预约申购结果","reservationList":[{"reservationId":17053404357,"sessionId":678,"shopId":"233331084001","reservationTime":1688608601720,"itemId":"10214","count":1}],"reservationDetail":{"desc":"申购成功后将以短信形式通知您,请您在申购成功次日18:00前确认支付方式,并在7天内完成提货。","lotteryTime":1688637600000,"cacheValidTime":1688637600000}}} if (body.getInteger("code") != 2000) { String message = body.getString("message"); throw new ServiceException(message); } // logger.info(body.toJSONString()); return body; } /** * 加密 * * @param params * @return */ public static String AesEncrypt(String params) { AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, AES_KEY.getBytes(), AES_IV.getBytes()); return aes.encryptBase64(params); } /** * 解密 * * @param params * @return */ public static String AesDecrypt(String params) { AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, AES_KEY.getBytes(), AES_IV.getBytes()); return aes.decryptStr(params); } /** * 获取验证码的md5签名,密钥+手机号+时间 * 登录的md5签名:密钥+mobile+vCode+ydLogId+ydToken * * @param content * @return */ private static String signature(String content, long time) { String text = SALT + content + time; String md5 = ""; try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] hashBytes = md.digest(text.getBytes()); StringBuilder sb = new StringBuilder(); for (byte b : hashBytes) { sb.append(String.format("%02x", b)); } md5 = sb.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return md5; } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/impl/IShopServiceImpl.java ================================================ package com.oddfar.campus.business.service.impl; import cn.hutool.core.convert.Convert; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpUtil; import cn.hutool.http.Method; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONException; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.oddfar.campus.business.domain.IMTCacheConstants; import com.oddfar.campus.business.domain.IMTItemInfo; import com.oddfar.campus.business.domain.MapPoint; import com.oddfar.campus.business.entity.IItem; import com.oddfar.campus.business.entity.IShop; import com.oddfar.campus.business.mapper.IItemMapper; import com.oddfar.campus.business.mapper.IShopMapper; import com.oddfar.campus.business.service.IShopService; import com.oddfar.campus.common.core.RedisCache; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.StringUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Service @Slf4j public class IShopServiceImpl extends ServiceImpl implements IShopService { @Autowired IShopMapper iShopMapper; @Autowired IItemMapper iItemMapper; @Autowired RedisCache redisCache; @Override public List selectShopList() { List shopList = redisCache.getCacheList(IMTCacheConstants.MT_SHOP_LIST); if (shopList != null && shopList.size() > 0) { return shopList; } else { refreshShop(); } shopList = iShopMapper.selectList(); return shopList; } // @Async @Override public void refreshShop() { HttpRequest request = HttpUtil.createRequest(Method.GET, "https://static.moutai519.com.cn/mt-backend/xhr/front/mall/resource/get"); JSONObject body = JSONObject.parseObject(request.execute().body()); //获取shop的url String shopUrl = body.getJSONObject("data").getJSONObject("mtshops_pc").getString("url"); //清空数据库 iShopMapper.truncateShop(); redisCache.deleteObject(IMTCacheConstants.MT_SHOP_LIST); String s = HttpUtil.get(shopUrl); JSONObject jsonObject = JSONObject.parseObject(s); Set shopIdSet = jsonObject.keySet(); List list = new ArrayList<>(); for (String iShopId : shopIdSet) { JSONObject shop = jsonObject.getJSONObject(iShopId); IShop iShop = new IShop(iShopId, shop); // iShopMapper.insert(iShop); list.add(iShop); } this.saveBatch(list); redisCache.setCacheList(IMTCacheConstants.MT_SHOP_LIST, list); redisCache.expire(IMTCacheConstants.MT_SHOP_LIST, 2, TimeUnit.HOURS); } @Override public String getCurrentSessionId() { String mtSessionId = Convert.toStr(redisCache.getCacheObject(IMTCacheConstants.MT_SESSION_ID)); long dayTime = LocalDate.now().atStartOfDay().toInstant(ZoneOffset.of("+8")).toEpochMilli(); if (StringUtils.isNotEmpty(mtSessionId)) { return mtSessionId; } String res = HttpUtil.get("https://static.moutai519.com.cn/mt-backend/xhr/front/mall/index/session/get/" + dayTime); //替换 current_session_id 673 ['data']['sessionId'] JSONObject jsonObject = JSONObject.parseObject(res); if (jsonObject.getString("code").equals("2000")) { JSONObject data = jsonObject.getJSONObject("data"); mtSessionId = data.getString("sessionId"); redisCache.setCacheObject(IMTCacheConstants.MT_SESSION_ID, mtSessionId, 2, TimeUnit.HOURS); iItemMapper.truncateItem(); //item插入数据库 JSONArray itemList = data.getJSONArray("itemList"); for (Object obj : itemList) { JSONObject item = (JSONObject) obj; IItem iItem = new IItem(item); iItemMapper.insert(iItem); } } return mtSessionId; } @Override public void refreshItem() { redisCache.deleteObject(IMTCacheConstants.MT_SESSION_ID); getCurrentSessionId(); } @Override public IShop selectByIShopId(String iShopId) { List iShopList = iShopMapper.selectList("i_shop_id", iShopId); if (iShopList != null && iShopList.size() > 0) { return iShopList.get(0); } else { return null; } } @Override public List getShopsByProvince(String province, String itemId) { String key = "mt_province:" + province + "." + getCurrentSessionId() + "." + itemId; List cacheList = redisCache.getCacheList(key); if (cacheList != null && cacheList.size() > 0) { return cacheList; } else { List imtItemInfoList = reGetShopsByProvince(province, itemId); redisCache.reSetCacheList(key, imtItemInfoList); redisCache.expire(key, 60, TimeUnit.MINUTES); return imtItemInfoList; } } public List reGetShopsByProvince(String province, String itemId) { long dayTime = LocalDate.now().atStartOfDay().toInstant(ZoneOffset.of("+8")).toEpochMilli(); String url = "https://static.moutai519.com.cn/mt-backend/xhr/front/mall/shop/list/slim/v3/" + getCurrentSessionId() + "/" + province + "/" + itemId + "/" + dayTime; String urlRes = HttpUtil.get(url); JSONObject res = null; try { res = JSONObject.parseObject(urlRes); } catch (JSONException jsonException) { String message = StringUtils.format("查询所在省市的投放产品和数量error: %s", url); log.error(message); throw new ServiceException(message); } // JSONObject res = JSONObject.parseObject(HttpUtil.get(url)); if (!res.containsKey("code") || !res.getString("code").equals("2000")) { String message = StringUtils.format("查询所在省市的投放产品和数量error: %s", url); log.error(message); throw new ServiceException(message); } //组合信息 List imtItemInfoList = new ArrayList<>(); JSONObject data = res.getJSONObject("data"); JSONArray shopList = data.getJSONArray("shops"); for (Object obj : shopList) { JSONObject shops = (JSONObject) obj; JSONArray items = shops.getJSONArray("items"); for (Object item : items) { JSONObject itemObj = (JSONObject) item; if (itemObj.getString("itemId").equals(itemId)) { IMTItemInfo iItem = new IMTItemInfo(shops.getString("shopId"), itemObj.getIntValue("count"), itemObj.getString("itemId"), itemObj.getIntValue("inventory")); //添加 imtItemInfoList.add(iItem); } } } return imtItemInfoList; } @Override public String getShopId(int shopType, String itemId, String province, String city, String lat, String lng) { //查询所在省市的投放产品和数量 List shopList = getShopsByProvince(province, itemId); //取id集合 List shopIdList = shopList.stream().map(IMTItemInfo::getShopId).collect(Collectors.toList()); //获取门店列表 List iShops = selectShopList(); //获取今日的门店信息列表 List list = iShops.stream().filter(i -> shopIdList.contains(i.getIShopId())).collect(Collectors.toList()); String shopId = ""; if (shopType == 1) { //预约本市出货量最大的门店 shopId = getMaxInventoryShopId(shopList, list, city); if (StringUtils.isEmpty(shopId)) { //本市没有则预约本省最近的 shopId = getMinDistanceShopId(list, province, lat, lng); } } else { //预约本省距离最近的门店 shopId = getMinDistanceShopId(list, province, lat, lng); } // if (shopType == 2) { // // 预约本省距离最近的门店 // shopId = getMinDistanceShopId(list, province, lat, lng); // } if (StringUtils.isEmpty(shopId)) { throw new ServiceException("申购时根据类型获取的门店商品id为空"); } return shopId; } /** * 预约本市出货量最大的门店 * * @param list1 * @param list2 * @param city * @return */ public String getMaxInventoryShopId(List list1, List list2, String city) { //本城市的shopId集合 List cityShopIdList = list2.stream().filter(iShop -> iShop.getCityName().contains(city)) .map(IShop::getIShopId).collect(Collectors.toList()); List collect = list1.stream().filter(i -> cityShopIdList.contains(i.getShopId())).sorted(Comparator.comparing(IMTItemInfo::getInventory).reversed()).collect(Collectors.toList()); if (collect != null && collect.size() > 0) { return collect.get(0).getShopId(); } return null; } /** * 预约本省距离最近的门店 * * @param list2 * @param province * @param lat * @param lng * @return */ public String getMinDistanceShopId(List list2, String province, String lat, String lng) { //本省的 List iShopList = list2.stream().filter(iShop -> iShop.getProvinceName().contains(province)) .collect(Collectors.toList()); MapPoint myPoint = new MapPoint(Double.parseDouble(lat), Double.parseDouble(lng)); for (IShop iShop : iShopList) { MapPoint point = new MapPoint(Double.parseDouble(iShop.getLat()), Double.parseDouble(iShop.getLng())); Double disdance = getDisdance(myPoint, point); iShop.setDistance(disdance); } List collect = iShopList.stream().sorted(Comparator.comparing(IShop::getDistance)).collect(Collectors.toList()); return collect.get(0).getIShopId(); } public static Double getDisdance(MapPoint point1, MapPoint point2) { double lat1 = (point1.getLatitude() * Math.PI) / 180; //将角度换算为弧度 double lat2 = (point2.getLatitude() * Math.PI) / 180; //将角度换算为弧度 double latDifference = lat1 - lat2; double lonDifference = (point1.getLongitude() * Math.PI) / 180 - (point2.getLongitude() * Math.PI) / 180; //计算两点之间距离 6378137.0 取自WGS84标准参考椭球中的地球长半径(单位:m) return 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(latDifference / 2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(lonDifference / 2), 2))) * 6378137.0; } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/service/impl/IUserServiceImpl.java ================================================ package com.oddfar.campus.business.service.impl; import cn.hutool.core.bean.BeanUtil; import com.alibaba.fastjson2.JSONObject; import com.oddfar.campus.business.entity.IUser; import com.oddfar.campus.business.mapper.IUserMapper; import com.oddfar.campus.business.service.IUserService; import com.oddfar.campus.common.domain.PageResult; import com.oddfar.campus.common.exception.ServiceException; import com.oddfar.campus.common.utils.SecurityUtils; import com.oddfar.campus.common.utils.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.List; import java.util.UUID; @Service public class IUserServiceImpl implements IUserService { @Autowired private IUserMapper iUserMapper; @Override public PageResult page(IUser iUser) { Long userId = SecurityUtils.getUserId(); if (userId != 1) { return iUserMapper.selectPage(iUser, userId); } return iUserMapper.selectPage(iUser); } @Override public int insertIUser(Long mobile, String deviceId, JSONObject jsonObject) { JSONObject data = jsonObject.getJSONObject("data"); IUser user = iUserMapper.selectById(mobile); if (user != null) { //存在则更新 IUser iUser = new IUser(mobile, jsonObject); iUser.setCreateUser(SecurityUtils.getUserId()); BeanUtil.copyProperties(iUser, user, "shopType", "minute"); return iUserMapper.updateById(user); } else { if (StringUtils.isEmpty(deviceId)) { deviceId = UUID.randomUUID().toString().toLowerCase(); } IUser iUser = new IUser(mobile, deviceId, jsonObject); iUser.setCreateUser(SecurityUtils.getUserId()); return iUserMapper.insert(iUser); } } @Override public List selectReservationUser() { return iUserMapper.selectReservationUser(); } @Override public List selectReservationUserByMinute(int minute) { return iUserMapper.selectReservationUserByMinute(minute); } @Override public int insertIUser(IUser iUser) { IUser user = iUserMapper.selectById(iUser.getMobile()); if (user != null) { throw new ServiceException("禁止重复添加"); } if (StringUtils.isEmpty(iUser.getDeviceId())) { iUser.setDeviceId(UUID.randomUUID().toString().toLowerCase()); } iUser.setCreateUser(SecurityUtils.getUserId()); return iUserMapper.insert(iUser); } @Override public int updateIUser(IUser iUser) { if (SecurityUtils.getUserId() != 1 && !iUser.getCreateUser().equals(SecurityUtils.getUserId())) { throw new ServiceException("只能修改自己创建的用户"); } return iUserMapper.updateById(iUser); } @Override @Async public void updateUserMinuteBatch() { Long userCount = iUserMapper.selectCount(); if (userCount > 60) { iUserMapper.updateUserMinuteEven(); }else { iUserMapper.updateUserMinuteBatch(); } } @Override public int deleteIUser(Long[] iUserId) { return iUserMapper.deleteIUser(iUserId); } } ================================================ FILE: campus-modular/src/main/java/com/oddfar/campus/business/task/CampusIMTTask.java ================================================ package com.oddfar.campus.business.task; import com.oddfar.campus.business.service.IMTService; import com.oddfar.campus.business.service.IUserService; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; /** * i茅台定时任务 */ @Configuration @EnableScheduling @RequiredArgsConstructor public class CampusIMTTask { private static final Logger logger = LoggerFactory.getLogger(CampusIMTTask.class); private final IMTService imtService; private final IUserService iUserService; /** * 1:10 批量修改用户随机预约的时间 */ @Async @Scheduled(cron = "0 10 1 ? * * ") public void updateUserMinuteBatch() { iUserService.updateUserMinuteBatch(); } /** * 11点期间,每分钟执行一次批量获得旅行奖励 */ @Async @Scheduled(cron = "0 0/1 11 ? * *") public void getTravelRewardBatch() { imtService.getTravelRewardBatch(); } /** * 9点期间,每分钟执行一次 */ @Async @Scheduled(cron = "0 0/1 9 ? * *") public void reservationBatchTask() { imtService.reservationBatch(); } @Async @Scheduled(cron = "0 10,55 7,8 ? * * ") public void refresh() { logger.info("「刷新数据」开始刷新版本号,预约item,门店shop列表 "); try { imtService.refreshAll(); } catch (Exception e) { logger.info("「刷新数据执行报错」%s", e.getMessage()); } } /** * 18.05分获取申购结果 */ @Async @Scheduled(cron = "0 5 18 ? * * ") public void appointmentResults() { imtService.appointmentResults(); } } ================================================ FILE: campus-modular/src/main/resources/application-dev.yml ================================================ --- # 数据源配置 spring: #数据源配置 datasource: type: com.zaxxer.hikari.HikariDataSource # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content dynamic: # 性能分析插件(有性能损耗 不建议生产环境使用) # 文档 https://baomidou.com/pages/833fab/ p6spy: true # 设置默认的数据源或者数据源组,默认值即为 master primary: master # 严格模式 匹配不到数据源则报错 strict: true datasource: # 主库数据源 master: type: ${spring.datasource.type} driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) url: jdbc:mysql://localhost:3306/campus_imaotai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true username: root password: 123456789 # 从库数据源 # slave: # lazy: true # type: ${spring.datasource.type} # driverClassName: com.mysql.cj.jdbc.Driver # url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true # username: root # password: 123456789 hikari: # 最大连接池数量 maxPoolSize: 20 # 最小空闲线程数量 minIdle: 10 # 配置获取连接等待超时的时间 connectionTimeout: 30000 # 校验超时时间 validationTimeout: 5000 # 空闲连接存活最大时间,默认10分钟 idleTimeout: 600000 # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟 maxLifetime: 1800000 # 连接测试query(配置检测连接是否有效) connectionTestQuery: SELECT 1 # 多久检查一次连接的活性 keepaliveTime: 30000 --- # redis spring: redis: # 地址 host: localhost # 端口,默认为6379 port: 6379 # 数据库索引 database: 0 # 密码(如没有密码请注释掉) # password: # 连接超时时间 timeout: 10s ================================================ FILE: campus-modular/src/main/resources/application-prod.yml ================================================ --- # 数据源配置 spring: #数据源配置 datasource: type: com.zaxxer.hikari.HikariDataSource # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content dynamic: # 性能分析插件(有性能损耗 不建议生产环境使用) p6spy: false # 设置默认的数据源或者数据源组,默认值即为 master primary: master # 严格模式 匹配不到数据源则报错 strict: true datasource: # 主库数据源 master: type: ${spring.datasource.type} driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) url: jdbc:mysql://localhost:3306/campus_imaotai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true username: root password: 123456789 # 从库数据源 # slave: # lazy: true # type: ${spring.datasource.type} # driverClassName: com.mysql.cj.jdbc.Driver # url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true # username: root # password: 123456789 hikari: # 最大连接池数量 maxPoolSize: 20 # 最小空闲线程数量 minIdle: 10 # 配置获取连接等待超时的时间 connectionTimeout: 30000 # 校验超时时间 validationTimeout: 5000 # 空闲连接存活最大时间,默认10分钟 idleTimeout: 600000 # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟 maxLifetime: 1800000 # 连接测试query(配置检测连接是否有效) connectionTestQuery: SELECT 1 # 多久检查一次连接的活性 keepaliveTime: 30000 --- # redis spring: redis: # 地址 host: localhost # 端口,默认为6379 port: 6379 # 数据库索引 database: 0 # 密码(如没有密码请注释掉) # password: # 连接超时时间 timeout: 10s ================================================ FILE: campus-modular/src/main/resources/application.yml ================================================ # 项目相关配置 campus: # 名称 name: campus-imaotai # 版本 version: ${revision} frameworkVersion: ${campus.revision} server: port: 8160 spring: application: name: ${campus.name} profiles: active: @profiles.active@ # 文件上传 servlet: multipart: # 单个文件大小 max-file-size: 20MB # 设置总上传的文件大小 max-request-size: 20MB #MyWebMvcConfig中开启@EnableWebMvc则失效 mvc: format: date-time: yyyy-MM-dd HH:mm:ss jackson: date-format: yyyy-MM-dd HH:mm:ss # time-zone: GMT+8 serialization: # 格式化输出 indent_output: false # 忽略无法转换的对象 fail_on_empty_beans: false deserialization: # 允许对象忽略json中不存在的属性 fail_on_unknown_properties: false # 资源信息 messages: # 国际化资源文件路径 basename: i18n/messages # token配置 token: # 令牌自定义标识 header: Authorization # 令牌密钥 secret: abcdefghijklmnopqrstuvwxyz # 令牌有效期(单位分钟) expireTime: 1440 # 用户配置 user: password: # 密码最大错误次数 maxRetryCount: 5 # 密码锁定时间(默认10分钟) lockTime: 10 # 日志配置 logging: level: com.oddfar: debug org.springframework: warn # MyBatisPlus配置 # https://baomidou.com/pages/56bac0/ mybatis-plus: # 不支持多包, 如有需要可在注解配置 或 提升扫包等级 # 例如 com.**.**.mapper mapperPackage: com.oddfar.**.mapper # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 实体扫描,多个package用逗号或者分号分隔 typeAliasesPackage: com.oddfar.**.domain configuration: # 自动驼峰命名规则(camel case)映射 mapUnderscoreToCamelCase: true # MyBatis 自动映射策略 # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射 autoMappingBehavior: FULL # MyBatis 自动映射时未知列或未知属性处理策 # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息 autoMappingUnknownColumnBehavior: NONE # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl global-config: #逻辑删除 dbConfig: logic-delete-value: 1 logic-not-delete-value: 0 # Swagger配置 swagger: # 是否开启swagger enabled: true ================================================ FILE: campus-modular/src/main/resources/i18n/messages.properties ================================================ #错误消息 not.null=* 必须填写 user.jcaptcha.error=验证码错误 user.jcaptcha.expire=验证码已失效 user.not.exists=用户不存在/密码错误 user.password.not.match=用户不存在/密码错误 user.password.retry.limit.count=密码输入错误{0}次 user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 user.password.delete=对不起,您的账号已被删除 user.blocked=用户已封禁,请联系管理员 role.blocked=角色已封禁,请联系管理员 user.logout.success=退出成功 length.not.valid=长度必须在{min}到{max}个字符之间 user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 user.password.not.valid=* 5-50个字符 user.email.not.valid=邮箱格式错误 user.mobile.phone.number.not.valid=手机号格式错误 user.login.success=登录成功 user.register.success=注册成功 user.notfound=请重新登录 user.forcelogout=管理员强制退出,请重新登录 user.unknown.error=未知错误,请重新登录 ##文件上传消息 upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! upload.filename.exceed.length=上传的文件名最长{0}个字符 ##权限 no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] ================================================ FILE: campus-modular/src/main/resources/logback.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/sys-info.log ${log.path}/sys-info.%d{yyyy-MM-dd}.log 60 ${log.pattern} INFO ACCEPT DENY ${log.path}/sys-error.log ${log.path}/sys-error.%d{yyyy-MM-dd}.log 60 ${log.pattern} ERROR ACCEPT DENY ${log.path}/sys-user.log ${log.path}/sys-user.%d{yyyy-MM-dd}.log 60 ${log.pattern} ================================================ FILE: campus-modular/src/main/resources/mapper/IItemMapper.xml ================================================ ================================================ FILE: campus-modular/src/main/resources/mapper/ILogMapper.xml ================================================ ================================================ FILE: campus-modular/src/main/resources/mapper/IShopMapper.xml ================================================ ================================================ FILE: campus-modular/src/main/resources/mapper/IUserMapper.xml ================================================ delete from i_user where mobile in #{id} ================================================ FILE: campus-modular/src/main/resources/spy.properties ================================================ # p6spy 性能分析插件配置文件 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger # 使用日志系统记录 sql #appender=com.p6spy.engine.spy.appender.Slf4JLogger # 设置 p6spy driver 代理 #deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # SQL语句打印时间格式 databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss # 实际驱动可多个 #driverlist=org.h2.Driver # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2 # 是否过滤 Log filter=true # 过滤 Log 时所排除的 sql 关键字,以逗号分隔 exclude=SELECT 1 ================================================ FILE: doc/docker/docker-compose.yml ================================================ version : '3' services: mysql: image: mysql:5.7 container_name: mysql environment: # root 密码 MYSQL_ROOT_PASSWORD: 123456789 # 初始化数据库(后续的初始化sql会在这个库执行) MYSQL_DATABASE: 'campus_imaotai' volumes: # 数据挂载 - /docker/mysql/data/:/var/lib/mysql/ # 配置挂载 - /docker/mysql/conf/:/etc/mysql/conf.d/ # 日志 - /docker/mysql/logs:/logs command: [ 'mysqld', '--innodb-buffer-pool-size=80M', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci', '--default-time-zone=+8:00', '--lower-case-table-names=1' ] privileged: true network_mode: "host" redis: image: redis:6.2.12 container_name: redis volumes: # 配置文件 - /docker/redis/conf:/redis/config # 数据文件 - /docker/redis/data/:/redis/data/ command: "redis-server /redis/config/redis.conf" privileged: true network_mode: "host" nginx-web: image: nginx:1.23.4 container_name: nginx-web environment: # 时区上海 TZ: Asia/Shanghai volumes: # 页面目录 - /docker/nginx/html:/usr/share/nginx/html # 证书映射 - /docker/nginx/cert:/etc/nginx/cert # 配置文件映射 - /docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf # 日志目录 - /docker/nginx/log:/var/log/nginx privileged: true network_mode: "host" campus-server: image: campus/campus-imaotai:1.0.13 container_name: campus-imaotai environment: # 时区上海 TZ: Asia/Shanghai SERVER_PORT: 8160 spring.config.additional-location: /home/campus/conf/application-prod.yml volumes: - /docker/server/conf:/home/campus/conf privileged: true network_mode: "host" ================================================ FILE: doc/docker/nginx/conf/nginx.conf ================================================ worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 80; server_name 127.0.0.1; # https配置参考 start #listen 443 ssl; # 证书直接存放 /docker/nginx/cert/ 目录下即可 更改证书名称即可 无需更改证书路径 #ssl on; #ssl_certificate /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ 为docker映射路径 不允许更改 #ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为docker映射路径 不允许更改 #ssl_session_timeout 5m; #ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #ssl_prefer_server_ciphers on; # https配置参考 end location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; index index.html index.htm; } location /prod-api/{ proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8160/; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } } ================================================ FILE: doc/docker/redis/conf/redis.conf ================================================ # redis 密码 # requirepass 123456 ================================================ FILE: doc/docker/redis/data/README.md ================================================ 数据目录 请执行 `chmod 777 /docker/redis/data` 赋予读写权限 否则将无法写入数据 ================================================ FILE: doc/docker/server/conf/README.md ================================================ 若您需要挂载使用外部配置文件 在 `/docker/server/conf` 创建 `application-prod.yml` 文件 并 `Environment` 加 `spring.config.additional-location: /home/campus/conf/application-prod.yml` ================================================ FILE: doc/sql/campus_imaotai-1.0.5.sql ================================================ /* Date: 02/08/2023 17:33:32 */ SET NAMES utf8; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for i_item -- ---------------------------- DROP TABLE IF EXISTS `i_item`; CREATE TABLE `i_item` ( `item_id` bigint DEFAULT NULL COMMENT 'id', `item_code` varchar(30) DEFAULT NULL COMMENT '预约商品编码', `title` varchar(50) DEFAULT NULL COMMENT '标题', `content` varchar(255) DEFAULT NULL COMMENT '内容\n', `picture` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '图片url', `create_time` datetime DEFAULT NULL COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='I茅台预约商品列表'; -- ---------------------------- -- Records of i_item -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for i_log -- ---------------------------- DROP TABLE IF EXISTS `i_log`; CREATE TABLE `i_log` ( `log_id` bigint NOT NULL COMMENT '主键', `mobile` bigint DEFAULT NULL COMMENT '操作人员', `log_content` varchar(2000) DEFAULT NULL COMMENT '日志记录内容', `status` int DEFAULT '0' COMMENT '操作状态(0正常 1异常)', `oper_time` datetime DEFAULT NULL COMMENT '操作时间', `create_user` bigint DEFAULT NULL COMMENT '创建人', PRIMARY KEY (`log_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; -- ---------------------------- -- Table structure for i_shop -- ---------------------------- DROP TABLE IF EXISTS `i_shop`; CREATE TABLE `i_shop` ( `shop_id` bigint NOT NULL COMMENT 'ID', `i_shop_id` varchar(255) DEFAULT NULL COMMENT '商品ID', `province_name` varchar(50) DEFAULT NULL COMMENT '省份', `city_name` varchar(50) DEFAULT NULL COMMENT '城市', `district_name` varchar(50) DEFAULT NULL COMMENT '地区', `full_address` varchar(255) DEFAULT NULL COMMENT '完整地址', `lat` varchar(50) DEFAULT NULL COMMENT '纬度', `lng` varchar(50) DEFAULT NULL COMMENT '经度', `name` varchar(255) DEFAULT NULL COMMENT '名称', `tenant_name` varchar(255) DEFAULT NULL COMMENT '公司名称', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`shop_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci; -- ---------------------------- -- Records of i_shop -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for i_user -- ---------------------------- DROP TABLE IF EXISTS `i_user`; CREATE TABLE `i_user` ( `mobile` bigint NOT NULL COMMENT 'I茅台手机号', `user_id` bigint DEFAULT NULL COMMENT 'I茅台用户id', `token` varchar(255) DEFAULT NULL COMMENT 'I茅台toekn', `cookie` varchar(255) DEFAULT NULL COMMENT 'I茅台cookie', `device_id` varchar(50) DEFAULT NULL COMMENT '设备id', `item_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '商品预约code,用@间隔', `ishop_id` varchar(50) DEFAULT NULL COMMENT '门店商品id', `province_name` varchar(50) DEFAULT NULL COMMENT '省份', `city_name` varchar(50) DEFAULT NULL COMMENT '城市', `address` varchar(255) DEFAULT NULL COMMENT '完整地址', `lat` varchar(50) DEFAULT NULL COMMENT '纬度', `lng` varchar(50) DEFAULT NULL COMMENT '经度', `minute` int DEFAULT '5' COMMENT '预约的分钟(0-59)', `shop_type` int DEFAULT '1' COMMENT '1:预约本市出货量最大的门店;2:预约你的位置(经纬度)附近门店;', `random_minute` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '0' COMMENT '随机分钟预约,9点取一个时间(0:随机,1:不随机)', `push_plus_token` varchar(50) DEFAULT NULL COMMENT 'push_plus_token', `json_result` varchar(2000) DEFAULT NULL COMMENT '返回参数', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `expire_time` datetime DEFAULT NULL COMMENT '到期时间', `del_flag` bit(1) DEFAULT b'0' COMMENT '逻辑删除(1:已删除,0:未删除)', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_user` bigint DEFAULT NULL COMMENT '创建人', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `update_user` bigint DEFAULT NULL COMMENT '更新人', PRIMARY KEY (`mobile`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='I茅台用户表'; -- ---------------------------- -- Table structure for sys_config -- ---------------------------- DROP TABLE IF EXISTS `sys_config`; CREATE TABLE `sys_config` ( `config_id` bigint NOT NULL AUTO_INCREMENT COMMENT '参数主键', `config_name` varchar(100) DEFAULT '' COMMENT '参数名称', `config_key` varchar(100) DEFAULT '' COMMENT '参数键名', `config_value` varchar(500) DEFAULT '' COMMENT '参数键值', `config_type` char(1) DEFAULT 'N' COMMENT '系统内置(Y是 N否)', `group_code` varchar(100) DEFAULT NULL COMMENT '所属分类的编码', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `del_flag` bit(1) DEFAULT b'0' COMMENT '逻辑删除(1:已删除,0:未删除)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_user` bigint DEFAULT NULL COMMENT '创建人', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `update_user` bigint DEFAULT NULL COMMENT '更新人', PRIMARY KEY (`config_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1666438862429286403 DEFAULT CHARSET=utf8mb3 COMMENT='参数配置表'; -- ---------------------------- -- Records of sys_config -- ---------------------------- BEGIN; INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (1, 'Mac本地文件路径', 'sys.local.profile.mac', '~/uploadPath', 'Y', 'file_config', NULL, b'0', '2022-11-10 14:06:44', 1, '2022-11-10 14:07:49', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (2, 'Linux本地文件路径', 'sys.local.profile.linux', '/data/uploadPath', 'Y', 'file_config', NULL, b'0', '2022-01-14 10:59:39', NULL, '2022-01-16 14:11:53', 1); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (3, 'Windows本地文件路径', 'sys.local.profile.win', 'D:\\uploadPath', 'Y', 'file_config', NULL, b'0', '2022-01-14 11:00:39', NULL, '2022-01-16 14:11:53', 1); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (4, '默认存储文件的bucket名称', 'sys.file.default.bucket', 'defaultBucket', 'Y', 'file_config', NULL, b'0', '2022-01-14 11:03:10', NULL, '2022-01-16 14:11:54', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (101, '阿里云邮件服务accessKeyId', 'sys.aliyun.mail.accessKeyId', '', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:04:08', NULL, '2022-01-19 10:49:30', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (102, '阿里云邮件服务accessKeySecret', 'sys.aliyun.mail.accessKeySecret', '', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:07:28', NULL, '2022-01-19 10:49:31', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (113, 'smtp服务器地址', 'sys.email.smtp.host', 'smtp.qq.com', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:33:50', NULL, '2022-01-24 11:28:13', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (114, 'smtp服务端口', 'sys.email.smtp.port', '465', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:35:29', NULL, '2022-01-24 11:28:14', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (115, '邮箱的发送方邮箱', 'sys.email.send.account', '3066693006@qq.com', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:38:17', NULL, '2022-01-24 11:28:15', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (116, '邮箱的密码或者授权码', 'sys.email.password', '**********', 'Y', 'mail_config', NULL, b'0', '2022-01-19 10:07:31', NULL, '2022-01-19 12:02:57', 1); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (117, '邮箱发送时的用户名', 'sys.email.name', '致远', 'Y', 'mail_config', NULL, b'0', '2022-01-19 11:10:47', NULL, '2022-01-24 11:28:19', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (202, '用户默认头像', 'sys.user.default.avatar', 'https://img0.baidu.com/it/u=1183896628,1403534286&fm=253&fmt=auto&app=138&f=PNG', 'Y', 'sys_config', NULL, b'0', '2022-02-08 11:35:31', NULL, '2022-02-08 11:40:15', 1); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (206, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'sys_config', '初始化密码 123456', b'0', '2022-11-09 01:41:52', 1, '2022-11-09 15:42:09', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (220, '全局日志记录', 'sys.log.global.flag', 'false', 'Y', 'sys_config', '全局日志记录,true则所有请求都将记录日志', b'0', '2023-06-07 21:36:00', 1, '2023-06-08 14:44:00', 1); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (300, '验证码类型', 'sys.login.captchaType', 'math', 'Y', 'sys_config', 'math 数组计算 char 字符验证', b'0', '2022-11-10 09:32:40', 1, '2022-11-30 12:14:30', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (301, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'sys_config', '是否开启验证码功能(true开启,false关闭)', b'0', '2023-02-01 21:48:05', 1, '2023-02-01 21:48:34', NULL); INSERT INTO `sys_config` (`config_id`, `config_name`, `config_key`, `config_value`, `config_type`, `group_code`, `remark`, `del_flag`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES (302, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'true', 'Y', 'sys_config', '是否开启注册用户功能(true开启,false关闭)', b'0', '2023-02-01 21:47:39', 1, '2023-02-01 21:48:31', NULL); COMMIT; -- ---------------------------- -- Table structure for sys_dict_data -- ---------------------------- DROP TABLE IF EXISTS `sys_dict_data`; CREATE TABLE `sys_dict_data` ( `dict_code` bigint NOT NULL AUTO_INCREMENT COMMENT '字典编码', `dict_sort` int DEFAULT '0' COMMENT '字典排序', `dict_label` varchar(100) DEFAULT '' COMMENT '字典标签', `dict_value` varchar(100) DEFAULT '' COMMENT '字典键值', `dict_type` varchar(100) DEFAULT '' COMMENT '字典类型', `css_class` varchar(100) DEFAULT NULL COMMENT '样式属性(其他样式扩展)', `list_class` varchar(100) DEFAULT NULL COMMENT '表格回显样式', `is_default` char(1) DEFAULT 'N' COMMENT '是否默认(Y是 N否)', `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_user` bigint DEFAULT NULL COMMENT '创建者', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', `update_user` bigint DEFAULT NULL COMMENT '更新者', `del_flag` bit(1) DEFAULT b'0' COMMENT '逻辑删除(1:已删除,0:未删除)', PRIMARY KEY (`dict_code`) ) ENGINE=InnoDB AUTO_INCREMENT=1666438168611713026 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='字典数据表'; -- ---------------------------- -- Records of sys_dict_data -- ---------------------------- BEGIN; INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (1, 1, '男', '0', 'sys_user_sex', NULL, 'default', 'Y', '0', '性别男', NULL, NULL, NULL, NULL, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (2, 2, '女', '1', 'sys_user_sex', NULL, 'default', 'N', '0', '性别女', NULL, NULL, NULL, NULL, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (3, 1, '是', 'Y', 'sys_yes_no', NULL, 'success', 'N', '0', NULL, '2022-11-06 06:37:31', 1, '2022-11-06 06:39:34', 1, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (4, 2, '否', 'N', 'sys_yes_no', NULL, 'danger', 'N', '0', NULL, '2022-11-06 06:37:42', 1, '2022-11-06 06:39:34', 1, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (6, 1, '正常', '0', 'sys_normal_disable', NULL, 'primary', 'Y', '0', '正常状态', NULL, NULL, NULL, NULL, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (7, 2, '停用', '1', 'sys_normal_disable', NULL, 'danger', 'N', '0', '停用状态', NULL, NULL, NULL, NULL, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (41, 1, '显示', '0', 'sys_show_hide', NULL, 'primary', 'N', '0', '显示菜单', '2022-12-26 21:49:47', 1, '2022-12-26 21:49:47', NULL, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (42, 2, '隐藏', '1', 'sys_show_hide', NULL, 'danger', 'N', '0', '隐藏菜单', '2022-12-26 21:50:10', 1, '2022-12-26 21:50:10', NULL, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (101, 1, '系统配置', 'sys_config', 'sys_config_group', NULL, 'primary', 'N', '0', '配置群组的系统配置', '2022-11-06 19:27:23', NULL, '2022-11-06 06:07:20', 1, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (102, 2, '邮件配置', 'mail_config', 'sys_config_group', NULL, 'primary', 'N', '0', NULL, '2022-11-06 05:38:04', 1, '2022-11-06 06:07:20', 1, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (103, 3, '文件配置', 'file_config', 'sys_config_group', NULL, 'primary', 'N', '0', NULL, '2022-11-06 06:32:45', 1, '2022-11-06 20:32:44', 1, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (1666438029385986049, 1, '成功', '0', 'sys_common_status', NULL, 'primary', 'N', '0', '正常状态', '2023-06-07 21:32:42', 1, '2023-06-07 21:32:41', NULL, b'0'); INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (1666438168611713025, 2, '失败', '1', 'sys_common_status', NULL, 'danger', 'N', '0', '停用状态', '2023-06-07 21:33:15', 1, '2023-06-07 21:33:14', 1, b'0'); COMMIT; -- ---------------------------- -- Table structure for sys_dict_type -- ---------------------------- DROP TABLE IF EXISTS `sys_dict_type`; CREATE TABLE `sys_dict_type` ( `dict_id` bigint NOT NULL AUTO_INCREMENT COMMENT '字典主键', `dict_name` varchar(100) DEFAULT '' COMMENT '字典名称', `dict_type` varchar(100) DEFAULT '' COMMENT '字典类型', `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)', `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `create_user` bigint DEFAULT NULL COMMENT '创建者', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', `update_user` bigint DEFAULT NULL COMMENT '更新者', `del_flag` bit(1) DEFAULT b'0' COMMENT '逻辑删除(1:已删除,0:未删除)', PRIMARY KEY (`dict_id`), UNIQUE KEY `dict_type` (`dict_type`) ) ENGINE=InnoDB AUTO_INCREMENT=1666437666566107138 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='字典类型表'; -- ---------------------------- -- Records of sys_dict_type -- ---------------------------- BEGIN; INSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (1, '用户性别', 'sys_user_sex', '0', '用户性别列表', NULL, NULL, '2022-11-06 01:10:21', 1, b'0'); INSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (2, '系统是否', 'sys_yes_no', '0', '系统是否列表', '2022-11-06 06:37:05', 1, '2022-11-06 20:37:04', 1, b'0'); INSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (3, '系统开关', 'sys_normal_disable', '0', '系统开关列表', NULL, NULL, NULL, NULL, b'0'); INSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (4, '菜单状态', 'sys_show_hide', '0', '菜单状态列表', '2022-12-26 21:49:15', 1, '2022-12-26 21:49:15', NULL, b'0'); INSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (10, '系统状态', 'sys_common_status', '0', '登录状态列表', '2023-06-07 21:31:15', 1, '2023-06-07 21:31:15', NULL, b'0'); INSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `remark`, `create_time`, `create_user`, `update_time`, `update_user`, `del_flag`) VALUES (101, '配置群组', 'sys_config_group', '0', '配置群组', '2022-11-06 05:32:37', 1, '2022-11-06 19:32:37', 1, b'0'); COMMIT; -- ---------------------------- -- Table structure for sys_log_login -- ---------------------------- DROP TABLE IF EXISTS `sys_log_login`; CREATE TABLE `sys_log_login` ( `info_id` bigint NOT NULL AUTO_INCREMENT COMMENT '访问ID', `user_id` bigint DEFAULT NULL COMMENT '登录成功的用户id\n', `user_name` varchar(255) DEFAULT NULL COMMENT '用户账号', `ipaddr` varchar(128) DEFAULT '' COMMENT '登录IP地址', `login_location` varchar(255) DEFAULT '' COMMENT '登录地点', `browser` varchar(50) DEFAULT '' COMMENT '浏览器类型', `os` varchar(50) DEFAULT '' COMMENT '操作系统', `status` char(1) DEFAULT '0' COMMENT '登录状态(0成功 1失败)', `msg` varchar(255) DEFAULT '' COMMENT '提示消息', `login_time` datetime DEFAULT NULL COMMENT '访问时间', PRIMARY KEY (`info_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1686301476458135555 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='系统访问记录'; -- ---------------------------- -- Records of sys_log_login -- ---------------------------- BEGIN; INSERT INTO `sys_log_login` (`info_id`, `user_id`, `user_name`, `ipaddr`, `login_location`, `browser`, `os`, `status`, `msg`, `login_time`) VALUES (1686301476458135554, 1, 'admin', '127.0.0.1', '内网IP', 'Chrome 11', 'Mac OS X', '0', '登录成功', '2023-08-01 17:02:57'); COMMIT; -- ---------------------------- -- Table structure for sys_log_oper -- ---------------------------- DROP TABLE IF EXISTS `sys_log_oper`; CREATE TABLE `sys_log_oper` ( `oper_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', `app_name` varchar(255) DEFAULT NULL COMMENT '服务名称,一般为spring.application.name', `log_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '日志名称', `log_content` varchar(255) DEFAULT '0' COMMENT '日志记录内容', `method` varchar(100) DEFAULT '' COMMENT '方法名称', `request_method` varchar(10) DEFAULT '' COMMENT '请求方式', `oper_user_id` bigint DEFAULT NULL COMMENT '操作人员user_id', `oper_url` varchar(255) DEFAULT '' COMMENT '请求URL', `oper_ip` varchar(128) DEFAULT '' COMMENT '主机地址', `oper_param` varchar(2000) DEFAULT '' COMMENT '请求参数', `json_result` varchar(2000) DEFAULT '' COMMENT '返回参数', `status` int DEFAULT '0' COMMENT '操作状态(0正常 1异常)', `error_msg` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '错误消息', `oper_time` datetime DEFAULT NULL COMMENT '操作时间', PRIMARY KEY (`oper_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1686011339283152899 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='操作日志记录'; -- ---------------------------- -- Records of sys_log_oper -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for sys_menu -- ---------------------------- DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( `menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID', `menu_name` varchar(50) NOT NULL COMMENT '菜单名称', `parent_id` bigint DEFAULT '0' COMMENT '父菜单ID', `order_num` int DEFAULT '0' COMMENT '显示顺序', `path` varchar(200) DEFAULT '' COMMENT '路由地址', `component` varchar(255) DEFAULT NULL COMMENT '组件路径', `query` varchar(255) DEFAULT NULL COMMENT '路由参数', `is_frame` int DEFAULT '1' COMMENT '是否为外链(0是 1否)', `is_cache` int DEFAULT '0' COMMENT '是否缓存(0缓存 1不缓存)', `menu_type` char(1) DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', `visible` char(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', `status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', `perms` varchar(100) DEFAULT NULL COMMENT '权限标识', `icon` varchar(100) DEFAULT '#' COMMENT '菜单图标', `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '备注', `del_flag` bit(1) DEFAULT NULL COMMENT '逻辑删除(1:已删除,0:未删除)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_user` bigint DEFAULT NULL COMMENT '更新者', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', `create_user` bigint DEFAULT NULL COMMENT '创建者', PRIMARY KEY (`menu_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1686232882739159042 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='菜单权限表'; -- ---------------------------- -- Records of sys_menu -- ---------------------------- BEGIN; INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1, '系统管理', 0, 1, 'system', NULL, '', 1, 0, 'M', '0', '0', '', 'system', '系统管理目录', b'0', '2022-10-05 15:28:43', 1, '2022-11-14 14:41:50', NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (2, '系统监控', 0, 2, 'monitor', NULL, '', 1, 0, 'M', '0', '0', '', 'monitor', '系统监控目录', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (3, '系统工具', 0, 3, 'tool', NULL, '', 1, 0, 'M', '0', '0', '', 'tool', '系统工具目录', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (4, '源码地址', 0, 10, 'http://github.com/oddfar/campus', NULL, '', 0, 0, 'M', '0', '0', '', 'guide', '若依官网地址', b'0', '2022-10-05 15:28:43', 1, '2022-11-21 17:11:40', NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', '用户管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', '角色管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', '菜单管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', '部门管理菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', '岗位管理菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', '字典管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (106, '参数设置', 1, 7, 'config', 'system/config/index', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', '参数设置菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (107, '通知公告', 1, 8, 'notice', 'system/notice/index', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', '通知公告菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (108, '日志管理', 1, 9, 'log', '', '', 1, 0, 'M', '0', '0', '', 'log', '日志管理菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', '在线用户菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (110, '定时任务', 2, 2, 'job', 'monitor/job/index', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', '定时任务菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', '数据监控菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (112, '服务监控', 2, 4, 'server', 'monitor/server/index', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', '服务监控菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', '缓存监控菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (114, '缓存列表', 2, 6, 'cacheList', 'monitor/cache/list', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', '缓存列表菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (115, '表单构建', 3, 1, 'build', 'tool/build/index', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', '表单构建菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (116, '代码生成', 3, 2, 'gen', 'tool/gen/index', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', '代码生成菜单', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (117, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', '系统接口菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', '操作日志菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', '登录日志菜单', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1000, '用户查询', 100, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1001, '用户新增', 100, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1002, '用户修改', 100, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1003, '用户删除', 100, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1004, '用户导出', 100, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1005, '用户导入', 100, 6, '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1006, '重置密码', 100, 7, '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1007, '角色查询', 101, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1008, '角色新增', 101, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1009, '角色修改', 101, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1010, '角色删除', 101, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1011, '角色导出', 101, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1012, '菜单查询', 102, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1013, '菜单新增', 102, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1014, '菜单修改', 102, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1015, '菜单删除', 102, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1016, '部门查询', 103, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1017, '部门新增', 103, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1018, '部门修改', 103, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1019, '部门删除', 103, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1020, '岗位查询', 104, 1, '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1021, '岗位新增', 104, 2, '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1022, '岗位修改', 104, 3, '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1023, '岗位删除', 104, 4, '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1024, '岗位导出', 104, 5, '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1025, '字典查询', 105, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1026, '字典新增', 105, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1027, '字典修改', 105, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1028, '字典删除', 105, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1029, '字典导出', 105, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1030, '参数查询', 106, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1031, '参数新增', 106, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1032, '参数修改', 106, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1033, '参数删除', 106, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1034, '参数导出', 106, 5, '#', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1035, '公告查询', 107, 1, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1036, '公告新增', 107, 2, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1037, '公告修改', 107, 3, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1038, '公告删除', 107, 4, '#', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1039, '操作查询', 500, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1040, '操作删除', 500, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1041, '日志导出', 500, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1042, '登录查询', 501, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1043, '登录删除', 501, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1044, '日志导出', 501, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1045, '账户解锁', 501, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', '', b'0', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1046, '在线查询', 109, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1047, '批量强退', 109, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1048, '单条强退', 109, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1049, '任务查询', 110, 1, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1050, '任务新增', 110, 2, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1051, '任务修改', 110, 3, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1052, '任务删除', 110, 4, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1053, '状态修改', 110, 5, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1054, '任务导出', 110, 6, '#', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1055, '生成查询', 116, 1, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1056, '生成修改', 116, 2, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1057, '生成删除', 116, 3, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1058, '导入代码', 116, 4, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1059, '预览代码', 116, 5, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1060, '生成代码', 116, 6, '#', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', '', b'1', '2022-10-05 15:28:43', NULL, NULL, NULL); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1676581553133699073, 'i茅台', 0, 4, 'imt', NULL, NULL, 1, 0, 'M', '0', '0', NULL, 'star', '', b'0', '2023-07-05 21:19:26', 1, '2023-07-05 21:19:26', 1); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1676586683316944898, '预约项目', 1676581553133699073, 2, 'item', 'imt/item/index', NULL, 1, 1, 'C', '0', '0', NULL, '#', '', b'0', '2023-07-05 21:39:49', 1, '2023-07-05 21:39:49', 1); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1676598335282122754, '门店列表', 1676581553133699073, 3, 'shop', 'imt/shop/index', NULL, 1, 1, 'C', '0', '0', NULL, '#', '', b'0', '2023-07-05 22:26:07', 1, '2023-07-05 22:26:07', 1); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1676847133803773954, '用户管理', 1676581553133699073, 1, 'user', 'imt/user/index', NULL, 1, 1, 'C', '0', '0', NULL, 'user', '', b'0', '2023-07-06 14:54:46', 1, '2023-07-06 14:54:45', 1); INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `remark`, `del_flag`, `create_time`, `update_user`, `update_time`, `create_user`) VALUES (1686232882739159041, '日志', 1676581553133699073, 4, 'log', 'imt/log/index', NULL, 1, 0, 'C', '0', '0', NULL, '#', '', b'0', '2023-08-01 12:30:23', 1, '2023-08-01 12:30:22', 1); COMMIT; -- ---------------------------- -- Table structure for sys_resource -- ---------------------------- DROP TABLE IF EXISTS `sys_resource`; CREATE TABLE `sys_resource` ( `resource_id` bigint NOT NULL COMMENT '资源id', `app_code` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '应用编码', `resource_code` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '资源编码', `resource_name` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '资源名称', `class_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '类名称', `method_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '方法名称', `modular_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '资源模块名称,一般为控制器名称', `url` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '资源url', `http_method` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'http请求方法', `resource_biz_type` tinyint DEFAULT '1' COMMENT '资源的业务类型:1-业务类,2-系统类', `required_permission_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '是否需要鉴权:Y-是,N-否', `del_flag` bit(1) DEFAULT b'0' COMMENT '删除标志(0代表存在 1代表删除)', `create_user` bigint DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_user` bigint DEFAULT NULL COMMENT '更新人', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`resource_id`) USING BTREE, KEY `RESOURCE_CODE_URL` (`resource_code`,`url`) USING BTREE COMMENT '资源code和url的联合索引' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC COMMENT='需要认证的接口资源controller'; -- ---------------------------- -- Records of sys_resource -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', `role_name` varchar(30) NOT NULL COMMENT '角色名称', `role_key` varchar(100) NOT NULL COMMENT '角色权限字符串', `role_sort` int NOT NULL COMMENT '显示顺序', `menu_check_strictly` tinyint(1) DEFAULT '1' COMMENT '菜单树选择项是否关联显示', `status` char(1) NOT NULL COMMENT '角色状态(0正常 1停用)', `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注', `del_flag` bit(1) DEFAULT b'0' COMMENT '删除标志(0代表存在 2代表删除)', `create_user` bigint DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_user` bigint DEFAULT NULL COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`role_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1685558345957654530 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='角色信息表'; -- ---------------------------- -- Records of sys_role -- ---------------------------- BEGIN; INSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_sort`, `menu_check_strictly`, `status`, `remark`, `del_flag`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (1, '超级管理员', 'admin', 1, 1, '0', '超级管理员', b'0', NULL, '2022-10-05 15:28:43', NULL, NULL); INSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_sort`, `menu_check_strictly`, `status`, `remark`, `del_flag`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (2, '普通角色', 'common', 2, 1, '0', '普通角色', b'0', 1, '2022-10-05 15:28:43', 1, '2023-04-23 09:25:30'); INSERT INTO `sys_role` (`role_id`, `role_name`, `role_key`, `role_sort`, `menu_check_strictly`, `status`, `remark`, `del_flag`, `create_user`, `create_time`, `update_user`, `update_time`) VALUES (1685558345957654529, 'i茅台', 'imaotai', 3, 1, '0', NULL, b'0', 1, '2023-07-30 15:50:00', 1, '2023-08-01 12:34:59'); COMMIT; -- ---------------------------- -- Table structure for sys_role_menu -- ---------------------------- DROP TABLE IF EXISTS `sys_role_menu`; CREATE TABLE `sys_role_menu` ( `role_id` bigint NOT NULL COMMENT '角色ID', `menu_id` bigint NOT NULL COMMENT '菜单ID', PRIMARY KEY (`role_id`,`menu_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='角色和菜单关联表'; -- ---------------------------- -- Records of sys_role_menu -- ---------------------------- BEGIN; INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 4); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 100); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 101); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1000); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1001); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1002); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1003); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1004); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1005); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1006); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1007); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1008); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1009); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1010); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (2, 1011); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1676581553133699073); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1676586683316944898); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1676598335282122754); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1676847133803773954); INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES (1685558345957654529, 1686232882739159041); COMMIT; -- ---------------------------- -- Table structure for sys_role_resource -- ---------------------------- DROP TABLE IF EXISTS `sys_role_resource`; CREATE TABLE `sys_role_resource` ( `resource_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '资源编码', `role_id` bigint NOT NULL COMMENT '角色id', PRIMARY KEY (`role_id`,`resource_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色资源关联'; -- ---------------------------- -- Records of sys_role_resource -- ---------------------------- BEGIN; INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_log.list', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.add', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.edit', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.get_info', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.list', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.login', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.remove', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.reservation', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.send_code', 1685558345957654529); INSERT INTO `sys_role_resource` (`resource_code`, `role_id`) VALUES ('campus-imaotai.I_user.travel_reward', 1685558345957654529); COMMIT; -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', `user_name` varchar(30) NOT NULL COMMENT '用户账号', `nick_name` varchar(30) NOT NULL COMMENT '用户昵称', `user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00系统用户)', `email` varchar(50) DEFAULT '' COMMENT '用户邮箱', `phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码', `sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', `avatar` varchar(100) DEFAULT '' COMMENT '头像地址', `password` varchar(100) DEFAULT '' COMMENT '密码', `status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', `login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP', `login_date` datetime DEFAULT NULL COMMENT '最后登录时间', `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注', `create_user` bigint DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_user` bigint DEFAULT NULL COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `del_flag` bit(1) DEFAULT NULL COMMENT '逻辑删除(1:已删除,0:未删除)', PRIMARY KEY (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1686027685614125058 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='用户信息表'; -- ---------------------------- -- Records of sys_user -- ---------------------------- BEGIN; INSERT INTO `sys_user` (`user_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `login_ip`, `login_date`, `remark`, `create_user`, `create_time`, `update_user`, `update_time`, `del_flag`) VALUES (1, 'admin', 'admin', '00', 'oddfar@163.com', '15888888888', '0', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '127.0.0.1', '2023-08-01 17:02:57', '管理员', 0, '2022-10-05 15:28:43', 1, '2023-08-01 17:02:57', b'0'); INSERT INTO `sys_user` (`user_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `login_ip`, `login_date`, `remark`, `create_user`, `create_time`, `update_user`, `update_time`, `del_flag`) VALUES (2, 'zhiyuan', '致远', '00', 'a_zhiyuan@163.com', '15666666666', '1', 'https://img0.baidu.com/it/u=1183896628,1403534286&fm=253&fmt=auto&app=138&f=PNG', '$2a$10$0522gOEarwIDNCk57dsrNeGqXTDwx2Zpy447d8R7W5MbH4/j1rcQi', '0', '127.0.0.1', '2023-02-25 23:01:16', '致远', 0, '2022-10-05 15:28:43', 1, '2023-07-15 23:18:08', b'0'); INSERT INTO `sys_user` (`user_id`, `user_name`, `nick_name`, `user_type`, `email`, `phonenumber`, `sex`, `avatar`, `password`, `status`, `login_ip`, `login_date`, `remark`, `create_user`, `create_time`, `update_user`, `update_time`, `del_flag`) VALUES (1686027685614125057, 'test', 'test', '00', '', '', '0', '', '$2a$10$23zCdQer/Killsky7gISDeFNQd5SDmf.LLai0sKF3jAR3BdcX2vTm', '0', '127.0.0.1', '2023-08-01 14:49:51', '测试', 0, '2023-07-31 22:55:00', 1, '2023-08-01 14:49:51', b'0'); COMMIT; -- ---------------------------- -- Table structure for sys_user_role -- ---------------------------- DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `user_id` bigint NOT NULL COMMENT '用户ID', `role_id` bigint NOT NULL COMMENT '角色ID', PRIMARY KEY (`user_id`,`role_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='用户和角色关联表'; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- BEGIN; INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (1, 1); INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (2, 2); INSERT INTO `sys_user_role` (`user_id`, `role_id`) VALUES (1686027685614125057, 1685558345957654529); COMMIT; SET FOREIGN_KEY_CHECKS = 1; ================================================ FILE: pom.xml ================================================ 4.0.0 com.oddfar.campus campus ${revision} pom campus http://campus.oddfar.com i茅台自动预约 campus-common campus-framework campus-admin campus-modular 1.0.13 1.1.9 UTF-8 1.8 1.21 3.5.4.1 3.9.1 4.2.0 3.0.0 2.0.42 5.8.23 1.4 1.5.3.Final 2.3.2 0.9.1 4.1.2 1.6.2 1.8.1 1.3.0 dev dev debug true prod prod warn org.springframework.boot spring-boot-dependencies 2.5.15 pom import com.baomidou mybatis-plus-boot-starter ${mybatis.plus.boot.version} p6spy p6spy ${p6spy.version} com.baomidou dynamic-datasource-spring-boot-starter ${dynamic-ds.version} io.springfox springfox-boot-starter ${swagger.version} io.swagger swagger-models eu.bitwalker UserAgentUtils ${bitwalker.version} com.github.penggle kaptcha ${kaptcha.version} org.apache.poi poi-ooxml ${poi.version} io.jsonwebtoken jjwt ${jwt.version} commons-fileupload commons-fileupload ${commons.fileupload.version} com.alibaba.fastjson2 fastjson2 ${fastjson.version} cn.hutool hutool-all ${hutool.version} org.mapstruct mapstruct ${mapstruct.version} com.sun.mail javax.mail ${sunmail.version} com.oddfar.campus campus-common ${revision} com.oddfar.campus campus-framework ${revision} com.oddfar.campus campus-admin ${revision} org.apache.maven.plugins maven-compiler-plugin 3.1 ${java.version} ${java.version} ${project.build.sourceEncoding} org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} true resolveCiFriendliesOnly flatten process-resources flatten flatten.clean clean clean src/main/resources true ================================================ FILE: vue_campus_admin/.editorconfig ================================================ # 告诉EditorConfig插件,这是根文件,不用继续往上查找 root = true # 匹配全部文件 [*] # 设置字符集 charset = utf-8 # 缩进风格,可选space、tab indent_style = space # 缩进的空格数 indent_size = 2 # 结尾换行符,可选lf、cr、crlf end_of_line = lf # 在文件结尾插入新行 insert_final_newline = true # 删除一行中的前后空格 trim_trailing_whitespace = true # 匹配md结尾的文件 [*.md] insert_final_newline = false trim_trailing_whitespace = false ================================================ FILE: vue_campus_admin/.eslintignore ================================================ # 忽略build目录下类型为js的文件的语法检查 build/*.js # 忽略src/assets目录下文件的语法检查 src/assets # 忽略public目录下文件的语法检查 public # 忽略当前目录下为js的文件的语法检查 *.js # 忽略当前目录下为vue的文件的语法检查 *.vue ================================================ FILE: vue_campus_admin/.eslintrc.js ================================================ // ESlint 检查配置 module.exports = { root: true, parserOptions: { parser: 'babel-eslint', sourceType: 'module' }, env: { browser: true, node: true, es6: true, }, extends: ['plugin:vue/recommended', 'eslint:recommended'], // add your custom rules here //it is base on https://github.com/vuejs/eslint-config-vue rules: { "vue/max-attributes-per-line": [2, { "singleline": 10, "multiline": { "max": 1, "allowFirstLine": false } }], "vue/singleline-html-element-content-newline": "off", "vue/multiline-html-element-content-newline":"off", "vue/name-property-casing": ["error", "PascalCase"], "vue/no-v-html": "off", 'accessor-pairs': 2, 'arrow-spacing': [2, { 'before': true, 'after': true }], 'block-spacing': [2, 'always'], 'brace-style': [2, '1tbs', { 'allowSingleLine': true }], 'camelcase': [0, { 'properties': 'always' }], 'comma-dangle': [2, 'never'], 'comma-spacing': [2, { 'before': false, 'after': true }], 'comma-style': [2, 'last'], 'constructor-super': 2, 'curly': [2, 'multi-line'], 'dot-location': [2, 'property'], 'eol-last': 2, 'eqeqeq': ["error", "always", {"null": "ignore"}], 'generator-star-spacing': [2, { 'before': true, 'after': true }], 'handle-callback-err': [2, '^(err|error)$'], 'indent': [2, 2, { 'SwitchCase': 1 }], 'jsx-quotes': [2, 'prefer-single'], 'key-spacing': [2, { 'beforeColon': false, 'afterColon': true }], 'keyword-spacing': [2, { 'before': true, 'after': true }], 'new-cap': [2, { 'newIsCap': true, 'capIsNew': false }], 'new-parens': 2, 'no-array-constructor': 2, 'no-caller': 2, 'no-console': 'off', 'no-class-assign': 2, 'no-cond-assign': 2, 'no-const-assign': 2, 'no-control-regex': 0, 'no-delete-var': 2, 'no-dupe-args': 2, 'no-dupe-class-members': 2, 'no-dupe-keys': 2, 'no-duplicate-case': 2, 'no-empty-character-class': 2, 'no-empty-pattern': 2, 'no-eval': 2, 'no-ex-assign': 2, 'no-extend-native': 2, 'no-extra-bind': 2, 'no-extra-boolean-cast': 2, 'no-extra-parens': [2, 'functions'], 'no-fallthrough': 2, 'no-floating-decimal': 2, 'no-func-assign': 2, 'no-implied-eval': 2, 'no-inner-declarations': [2, 'functions'], 'no-invalid-regexp': 2, 'no-irregular-whitespace': 2, 'no-iterator': 2, 'no-label-var': 2, 'no-labels': [2, { 'allowLoop': false, 'allowSwitch': false }], 'no-lone-blocks': 2, 'no-mixed-spaces-and-tabs': 2, 'no-multi-spaces': 2, 'no-multi-str': 2, 'no-multiple-empty-lines': [2, { 'max': 1 }], 'no-native-reassign': 2, 'no-negated-in-lhs': 2, 'no-new-object': 2, 'no-new-require': 2, 'no-new-symbol': 2, 'no-new-wrappers': 2, 'no-obj-calls': 2, 'no-octal': 2, 'no-octal-escape': 2, 'no-path-concat': 2, 'no-proto': 2, 'no-redeclare': 2, 'no-regex-spaces': 2, 'no-return-assign': [2, 'except-parens'], 'no-self-assign': 2, 'no-self-compare': 2, 'no-sequences': 2, 'no-shadow-restricted-names': 2, 'no-spaced-func': 2, 'no-sparse-arrays': 2, 'no-this-before-super': 2, 'no-throw-literal': 2, 'no-trailing-spaces': 2, 'no-undef': 2, 'no-undef-init': 2, 'no-unexpected-multiline': 2, 'no-unmodified-loop-condition': 2, 'no-unneeded-ternary': [2, { 'defaultAssignment': false }], 'no-unreachable': 2, 'no-unsafe-finally': 2, 'no-unused-vars': [2, { 'vars': 'all', 'args': 'none' }], 'no-useless-call': 2, 'no-useless-computed-key': 2, 'no-useless-constructor': 2, 'no-useless-escape': 0, 'no-whitespace-before-property': 2, 'no-with': 2, 'one-var': [2, { 'initialized': 'never' }], 'operator-linebreak': [2, 'after', { 'overrides': { '?': 'before', ':': 'before' } }], 'padded-blocks': [2, 'never'], 'quotes': [2, 'single', { 'avoidEscape': true, 'allowTemplateLiterals': true }], 'semi': [2, 'never'], 'semi-spacing': [2, { 'before': false, 'after': true }], 'space-before-blocks': [2, 'always'], 'space-before-function-paren': [2, 'never'], 'space-in-parens': [2, 'never'], 'space-infix-ops': 2, 'space-unary-ops': [2, { 'words': true, 'nonwords': false }], 'spaced-comment': [2, 'always', { 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] }], 'template-curly-spacing': [2, 'never'], 'use-isnan': 2, 'valid-typeof': 2, 'wrap-iife': [2, 'any'], 'yield-star-spacing': [2, 'both'], 'yoda': [2, 'never'], 'prefer-const': 2, 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 'object-curly-spacing': [2, 'always', { objectsInObjects: false }], 'array-bracket-spacing': [2, 'never'] } } ================================================ FILE: vue_campus_admin/.gitignore ================================================ .DS_Store node_modules/ dist/ npm-debug.log* yarn-debug.log* yarn-error.log* **/*.log tests/**/coverage/ tests/e2e/reports selenium-debug.log # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.local package-lock.json yarn.lock ================================================ FILE: vue_campus_admin/Dockerfile ================================================ FROM nginx:stable-alpine # author MAINTAINER oddfar # 复制html文件到路径 COPY dist /usr/share/nginx/html # 复制conf文件到路径 COPY ./doc/docker/nginx/conf/nginx.conf /etc/nginx/nginx.conf ================================================ FILE: vue_campus_admin/README.md ================================================ 此项目用的`若依`项目 ## 开发 ```bash # 克隆项目 git clone https://github.com/oddfar/campus # 进入项目目录 cd campus-ui # 安装依赖 npm install # 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 npm install --registry=https://registry.npmmirror.com # 启动服务 npm run dev ``` 浏览器访问 http://localhost:1024 ## 发布 ```bash # 构建测试环境 npm run build:stage # 构建生产环境 npm run build:prod ``` ================================================ FILE: vue_campus_admin/babel.config.js ================================================ module.exports = { presets: [ // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app '@vue/cli-plugin-babel/preset' ], 'env': { 'development': { // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). // This plugin can significantly increase the speed of hot updates, when you have a large number of pages. 'plugins': ['dynamic-import-node'] } } } ================================================ FILE: vue_campus_admin/bin/build.bat ================================================ @echo off echo. echo [Ϣ] Weḅdistļ echo. %~d0 cd %~dp0 cd .. npm run build:prod pause ================================================ FILE: vue_campus_admin/bin/package.bat ================================================ @echo off echo. echo [Ϣ] װWeḅnode_modulesļ echo. %~d0 cd %~dp0 cd .. npm install --registry=https://registry.npmmirror.com pause ================================================ FILE: vue_campus_admin/bin/run-web.bat ================================================ @echo off echo. echo [Ϣ] ʹ Vue CLI Web ̡ echo. %~d0 cd %~dp0 cd .. npm run dev pause ================================================ FILE: vue_campus_admin/package.json ================================================ { "name": "oddfar", "version": "1.1.0", "description": "campus-imaotai", "author": "oddfar", "license": "MIT", "scripts": { "dev": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve", "build:prod": "vue-cli-service build", "build:stage": "vue-cli-service build --mode staging", "preview": "node build/index.js --preview", "lint": "eslint --ext .js,.vue src" }, "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "src/**/*.{js,vue}": [ "eslint --fix", "git add" ] }, "keywords": [ "vue", "admin", "dashboard", "element-ui", "boilerplate", "admin-template", "management-system" ], "repository": { "type": "git", "url": "https://github.com/oddfar/campus-imaotai" }, "dependencies": { "@riophae/vue-treeselect": "0.4.0", "axios": "0.24.0", "clipboard": "2.0.8", "core-js": "3.25.2", "echarts": "4.9.0", "element-ui": "2.15.10", "file-saver": "2.0.5", "fuse.js": "6.4.3", "highlight.js": "9.18.5", "js-beautify": "1.13.0", "js-cookie": "3.0.1", "jsencrypt": "3.0.0-rc.1", "nprogress": "0.2.0", "quill": "1.3.7", "screenfull": "5.0.2", "sortablejs": "1.10.2", "vue": "2.6.12", "vue-count-to": "1.0.13", "vue-cropper": "0.5.5", "vue-meta": "2.4.0", "vue-router": "3.4.9", "vuedraggable": "2.24.3", "vuex": "3.6.0", "watermark-dom": "^2.3.0" }, "devDependencies": { "@vue/cli-plugin-babel": "4.4.6", "@vue/cli-plugin-eslint": "4.4.6", "@vue/cli-service": "4.4.6", "babel-eslint": "10.1.0", "babel-plugin-dynamic-import-node": "2.3.3", "chalk": "4.1.0", "compression-webpack-plugin": "5.0.2", "connect": "3.6.6", "crypto-js": "^4.1.1", "eslint": "7.15.0", "eslint-plugin-vue": "7.2.0", "lint-staged": "10.5.3", "runjs": "4.4.2", "sass": "1.32.13", "sass-loader": "10.1.1", "script-ext-html-webpack-plugin": "2.1.5", "svg-sprite-loader": "5.1.1", "vue-template-compiler": "2.6.12" }, "engines": { "node": ">=8.9", "npm": ">= 3.0.0" }, "browserslist": [ "> 1%", "last 2 versions" ] } ================================================ FILE: vue_campus_admin/public/html/ie.html ================================================ 请升级您的浏览器

请升级您的浏览器,以便我们更好的为您提供服务!

您正在使用 Internet Explorer 的早期版本(IE11以下版本或使用该内核的浏览器)。这意味着在升级浏览器前,您将无法访问此网站。


请注意:微软公司对Windows XP 及 Internet Explorer 早期版本的支持已经结束

自 2016 年 1 月 12 日起,Microsoft 不再为 IE 11 以下版本提供相应支持和更新。没有关键的浏览器安全更新,您的电脑可能易受有害病毒、间谍软件和其他恶意软件的攻击,它们可以窃取或损害您的业务数据和信息。请参阅 微软对 Internet Explorer 早期版本的支持将于 2016 年 1 月 12 日结束的说明


您可以选择更先进的浏览器

推荐使用以下浏览器的最新版本。如果您的电脑已有以下浏览器的最新版本则直接使用该浏览器访问即可。


================================================ FILE: vue_campus_admin/public/index.html ================================================ <%= webpackConfig.name %>
正在加载系统资源,请耐心等待
================================================ FILE: vue_campus_admin/public/robots.txt ================================================ User-agent: * Disallow: / ================================================ FILE: vue_campus_admin/src/App.vue ================================================ ================================================ FILE: vue_campus_admin/src/api/imt/item.js ================================================ import request from '@/utils/request' // 查询I茅台预约商品列列表 export function listItem() { return request({ url: '/imt/item/list', method: 'get', }) } // 刷新i茅台预约商品列表 export function refreshItem(itemId) { return request({ url: '/imt/item/refresh', method: 'get' }) } ================================================ FILE: vue_campus_admin/src/api/imt/log.js ================================================ import request from '@/utils/request' // 查询操作日志列表 export function list(query) { return request({ url: '/imt/log/list', method: 'get', params: query }) } // 删除操作日志 export function delOperlog(operId) { return request({ url: '/imt/log/' + operId, method: 'delete' }) } // 清空操作日志 export function cleanOperlog() { return request({ url: '/imt/log/clean', method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/imt/shop.js ================================================ import request from '@/utils/request' // 查询i茅台商品列表 export function listShop(query) { return request({ url: '/imt/shop/list', method: 'get', params: query }) } // 删除i茅台商品 export function refreshShop() { return request({ url: '/imt/shop/refresh' , method: 'get' }) } ================================================ FILE: vue_campus_admin/src/api/imt/user.js ================================================ import request from '@/utils/request' // reservation export function reservation(mobile) { return request({ url: '/imt/user/reservation', method: 'get', params: { mobile } }) } //travelReward export function travelReward(mobile) { return request({ url: '/imt/user/travelReward', method: 'get', params: { mobile } }) } // 发送验证码 export function sendCode(mobile, deviceId) { return request({ url: '/imt/user/sendCode', method: 'get', params: { mobile: mobile, deviceId: deviceId } }) } // 查询I茅台用户列表 export function login(mobile, code, deviceId) { return request({ url: '/imt/user/login', method: 'get', params: { mobile: mobile, code: code, deviceId: deviceId } }) } // 查询I茅台用户列表 export function listUser(query) { return request({ url: '/imt/user/list', method: 'get', params: query }) } // 查询I茅台用户详细 export function getUser(mobile) { return request({ url: '/imt/user/' + mobile, method: 'get' }) } // 新增I茅台用户 export function addUser(data) { return request({ url: '/imt/user', method: 'post', data: data }) } // 修改I茅台用户 export function updateUser(data) { return request({ url: '/imt/user', method: 'put', data: data }) } // 删除I茅台用户 export function delUser(mobile) { return request({ url: '/imt/user/' + mobile, method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/login.js ================================================ import request from '@/utils/request' // 登录方法 export function login(username, password, code, uuid) { const data = { username, password, code, uuid } return request({ url: '/login', headers: { isToken: false }, method: 'post', data: data }) } // 注册方法 export function register(data) { return request({ url: '/register', headers: { isToken: false }, method: 'post', data: data }) } // 获取用户详细信息 export function getInfo() { return request({ url: '/getInfo', method: 'get' }) } // 退出方法 export function logout() { return request({ url: '/logout', method: 'post' }) } // 获取验证码 export function getCodeImg() { return request({ url: '/captchaImage', headers: { isToken: false }, method: 'get', timeout: 20000 }) } ================================================ FILE: vue_campus_admin/src/api/menu.js ================================================ import request from '@/utils/request' // 获取路由 export const getRouters = () => { return request({ url: '/getRouters', method: 'get' }) } ================================================ FILE: vue_campus_admin/src/api/monitor/cache.js ================================================ import request from '@/utils/request' // 查询缓存详细 export function getCache() { return request({ url: '/monitor/cache', method: 'get' }) } // 查询缓存名称列表 export function listCacheName() { return request({ url: '/monitor/cache/getNames', method: 'get' }) } // 查询缓存键名列表 export function listCacheKey(cacheName) { return request({ url: '/monitor/cache/getKeys/' + cacheName, method: 'get' }) } // 查询缓存内容 export function getCacheValue(cacheName, cacheKey) { return request({ url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, method: 'get' }) } // 清理指定名称缓存 export function clearCacheName(cacheName) { return request({ url: '/monitor/cache/clearCacheName/' + cacheName, method: 'delete' }) } // 清理指定键名缓存 export function clearCacheKey(cacheKey) { return request({ url: '/monitor/cache/clearCacheKey/' + cacheKey, method: 'delete' }) } // 清理全部缓存 export function clearCacheAll() { return request({ url: '/monitor/cache/clearCacheAll', method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/monitor/job.js ================================================ import request from '@/utils/request' // 查询定时任务调度列表 export function listJob(query) { return request({ url: '/monitor/job/list', method: 'get', params: query }) } // 查询定时任务调度详细 export function getJob(jobId) { return request({ url: '/monitor/job/' + jobId, method: 'get' }) } // 新增定时任务调度 export function addJob(data) { return request({ url: '/monitor/job', method: 'post', data: data }) } // 修改定时任务调度 export function updateJob(data) { return request({ url: '/monitor/job', method: 'put', data: data }) } // 删除定时任务调度 export function delJob(jobId) { return request({ url: '/monitor/job/' + jobId, method: 'delete' }) } // 任务状态修改 export function changeJobStatus(jobId, status) { const data = { jobId, status } return request({ url: '/monitor/job/changeStatus', method: 'put', data: data }) } // 定时任务立即执行一次 export function runJob(jobId, jobGroup) { const data = { jobId, jobGroup } return request({ url: '/monitor/job/run', method: 'put', data: data }) } ================================================ FILE: vue_campus_admin/src/api/monitor/jobLog.js ================================================ import request from '@/utils/request' // 查询调度日志列表 export function listJobLog(query) { return request({ url: '/monitor/jobLog/list', method: 'get', params: query }) } // 删除调度日志 export function delJobLog(jobLogId) { return request({ url: '/monitor/jobLog/' + jobLogId, method: 'delete' }) } // 清空调度日志 export function cleanJobLog() { return request({ url: '/monitor/jobLog/clean', method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/monitor/logininfor.js ================================================ import request from '@/utils/request' // 查询登录日志列表 export function list(query) { return request({ url: '/monitor/logininfor/list', method: 'get', params: query }) } // 删除登录日志 export function delLogininfor(infoId) { return request({ url: '/monitor/logininfor/' + infoId, method: 'delete' }) } // 解锁用户登录状态 export function unlockLogininfor(userName) { return request({ url: '/monitor/logininfor/unlock/' + userName, method: 'get' }) } // 清空登录日志 export function cleanLogininfor() { return request({ url: '/monitor/logininfor/clean', method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/monitor/online.js ================================================ import request from '@/utils/request' // 查询在线用户列表 export function list(query) { return request({ url: '/monitor/online/list', method: 'get', params: query }) } // 强退用户 export function forceLogout(tokenId) { return request({ url: '/monitor/online/' + tokenId, method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/monitor/operlog.js ================================================ import request from '@/utils/request' // 查询操作日志列表 export function list(query) { return request({ url: '/monitor/operlog/list', method: 'get', params: query }) } // 删除操作日志 export function delOperlog(operId) { return request({ url: '/monitor/operlog/' + operId, method: 'delete' }) } // 清空操作日志 export function cleanOperlog() { return request({ url: '/monitor/operlog/clean', method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/monitor/server.js ================================================ import request from '@/utils/request' // 获取服务信息 export function getServer() { return request({ url: '/monitor/server', method: 'get' }) } ================================================ FILE: vue_campus_admin/src/api/system/config.js ================================================ import request from '@/utils/request' // 查询参数列表 export function listConfig(query) { return request({ url: '/system/config/page', method: 'get', params: query }) } // 查询参数详细 export function getConfig(configId) { return request({ url: '/system/config/' + configId, method: 'get' }) } // 根据参数键名查询参数值 export function getConfigKey(configKey) { return request({ url: '/system/config/configKey/' + configKey, method: 'get' }) } // 新增参数配置 export function addConfig(data) { return request({ url: '/system/config', method: 'post', data: data }) } // 修改参数配置 export function updateConfig(data) { return request({ url: '/system/config', method: 'put', data: data }) } // 删除参数配置 export function delConfig(configId) { return request({ url: '/system/config/' + configId, method: 'delete' }) } // 刷新参数缓存 export function refreshCache() { return request({ url: '/system/config/refreshCache', method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/system/dept.js ================================================ import request from '@/utils/request' // 查询部门列表 export function listDept(query) { return request({ url: '/system/dept/list', method: 'get', params: query }) } // 查询部门列表(排除节点) export function listDeptExcludeChild(deptId) { return request({ url: '/system/dept/list/exclude/' + deptId, method: 'get' }) } // 查询部门详细 export function getDept(deptId) { return request({ url: '/system/dept/' + deptId, method: 'get' }) } // 新增部门 export function addDept(data) { return request({ url: '/system/dept', method: 'post', data: data }) } // 修改部门 export function updateDept(data) { return request({ url: '/system/dept', method: 'put', data: data }) } // 删除部门 export function delDept(deptId) { return request({ url: '/system/dept/' + deptId, method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/system/dict/data.js ================================================ import request from '@/utils/request' // 查询字典数据列表 export function listData(query) { return request({ url: '/system/dict/data/list', method: 'get', params: query }) } // 查询字典数据详细 export function getData(dictCode) { return request({ url: '/system/dict/data/' + dictCode, method: 'get' }) } // 根据字典类型查询字典数据信息 export function getDicts(dictType) { return request({ url: '/system/dict/data/type/' + dictType, method: 'get' }) } // 新增字典数据 export function addData(data) { return request({ url: '/system/dict/data', method: 'post', data: data }) } // 修改字典数据 export function updateData(data) { return request({ url: '/system/dict/data', method: 'put', data: data }) } // 删除字典数据 export function delData(dictCode) { return request({ url: '/system/dict/data/' + dictCode, method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/system/dict/type.js ================================================ import request from '@/utils/request' // 查询字典类型列表 export function listType(query) { return request({ url: '/system/dict/type/list', method: 'get', params: query }) } // 查询字典类型详细 export function getType(dictId) { return request({ url: '/system/dict/type/' + dictId, method: 'get' }) } // 新增字典类型 export function addType(data) { return request({ url: '/system/dict/type', method: 'post', data: data }) } // 修改字典类型 export function updateType(data) { return request({ url: '/system/dict/type', method: 'put', data: data }) } // 删除字典类型 export function delType(dictId) { return request({ url: '/system/dict/type/' + dictId, method: 'delete' }) } // 刷新字典缓存 export function refreshCache() { return request({ url: '/system/dict/type/refreshCache', method: 'delete' }) } // 获取字典选择框列表 export function optionselect() { return request({ url: '/system/dict/type/optionselect', method: 'get' }) } ================================================ FILE: vue_campus_admin/src/api/system/index.js ================================================ import request from '@/utils/request' // 版本情况 export function getVersion() { return request({ url: '/version', method: 'get', }) } ================================================ FILE: vue_campus_admin/src/api/system/menu.js ================================================ import request from '@/utils/request' // 查询菜单列表 export function listMenu(query) { return request({ url: '/system/menu/list', method: 'get', params: query }) } // 查询菜单详细 export function getMenu(menuId) { return request({ url: '/system/menu/' + menuId, method: 'get' }) } // 查询菜单下拉树结构 export function treeselect() { return request({ url: '/system/menu/treeselect', method: 'get' }) } // 根据角色ID查询菜单下拉树结构 export function roleMenuTreeselect(roleId) { return request({ url: '/system/menu/roleMenuTreeselect/' + roleId, method: 'get' }) } // 新增菜单 export function addMenu(data) { return request({ url: '/system/menu', method: 'post', data: data }) } // 修改菜单 export function updateMenu(data) { return request({ url: '/system/menu', method: 'put', data: data }) } // 删除菜单 export function delMenu(menuId) { return request({ url: '/system/menu/' + menuId, method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/system/notice.js ================================================ import request from '@/utils/request' // 查询公告列表 export function listNotice(query) { return request({ url: '/system/notice/list', method: 'get', params: query }) } // 查询公告详细 export function getNotice(noticeId) { return request({ url: '/system/notice/' + noticeId, method: 'get' }) } // 新增公告 export function addNotice(data) { return request({ url: '/system/notice', method: 'post', data: data }) } // 修改公告 export function updateNotice(data) { return request({ url: '/system/notice', method: 'put', data: data }) } // 删除公告 export function delNotice(noticeId) { return request({ url: '/system/notice/' + noticeId, method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/system/post.js ================================================ import request from '@/utils/request' // 查询岗位列表 export function listPost(query) { return request({ url: '/system/post/list', method: 'get', params: query }) } // 查询岗位详细 export function getPost(postId) { return request({ url: '/system/post/' + postId, method: 'get' }) } // 新增岗位 export function addPost(data) { return request({ url: '/system/post', method: 'post', data: data }) } // 修改岗位 export function updatePost(data) { return request({ url: '/system/post', method: 'put', data: data }) } // 删除岗位 export function delPost(postId) { return request({ url: '/system/post/' + postId, method: 'delete' }) } ================================================ FILE: vue_campus_admin/src/api/system/resource.js ================================================ import request from '@/utils/request' // 根据角色ID查询资源下拉树结构 export function roleResourceTreeselect(roleId) { return request({ url: '/system/resource/roleApiTreeselect/' + roleId, method: 'get' }) } //修改角色与资源关联 export function editRoleResource(data) { return request({ url: '/system/resource/roleApi', method: 'put', params: data }) } ================================================ FILE: vue_campus_admin/src/api/system/role.js ================================================ import request from '@/utils/request' // 查询角色列表 export function listRole(query) { return request({ url: '/system/role/list', method: 'get', params: query }) } // 查询角色详细 export function getRole(roleId) { return request({ url: '/system/role/' + roleId, method: 'get' }) } // 新增角色 export function addRole(data) { return request({ url: '/system/role', method: 'post', data: data }) } // 修改角色 export function updateRole(data) { return request({ url: '/system/role', method: 'put', data: data }) } // 角色数据权限 export function dataScope(data) { return request({ url: '/system/role/dataScope', method: 'put', data: data }) } // 角色状态修改 export function changeRoleStatus(roleId, status) { const data = { roleId, status } return request({ url: '/system/role/changeStatus', method: 'put', data: data }) } // 删除角色 export function delRole(roleId) { return request({ url: '/system/role/' + roleId, method: 'delete' }) } // 查询角色已授权用户列表 export function allocatedUserList(query) { return request({ url: '/system/role/authUser/allocatedList', method: 'get', params: query }) } // 查询角色未授权用户列表 export function unallocatedUserList(query) { return request({ url: '/system/role/authUser/unallocatedList', method: 'get', params: query }) } // 取消用户授权角色 export function authUserCancel(data) { return request({ url: '/system/role/authUser/cancel', method: 'put', data: data }) } // 批量取消用户授权角色 export function authUserCancelAll(data) { return request({ url: '/system/role/authUser/cancelAll', method: 'put', params: data }) } // 授权用户选择 export function authUserSelectAll(data) { return request({ url: '/system/role/authUser/selectAll', method: 'put', params: data }) } // 根据角色ID查询部门树结构 export function deptTreeSelect(roleId) { return request({ url: '/system/role/deptTree/' + roleId, method: 'get' }) } ================================================ FILE: vue_campus_admin/src/api/system/user.js ================================================ import request from '@/utils/request' import { parseStrEmpty } from "@/utils/ruoyi"; // 查询用户列表 export function listUser(query) { return request({ url: '/system/user/list', method: 'get', params: query }) } // 查询用户详细 export function getUser(userId) { return request({ url: '/system/user/' + parseStrEmpty(userId), method: 'get' }) } // 新增用户 export function addUser(data) { return request({ url: '/system/user', method: 'post', data: data }) } // 修改用户 export function updateUser(data) { return request({ url: '/system/user', method: 'put', data: data }) } // 删除用户 export function delUser(userId) { return request({ url: '/system/user/' + userId, method: 'delete' }) } // 用户密码重置 export function resetUserPwd(userId, password) { const data = { userId, password } return request({ url: '/system/user/resetPwd', method: 'put', data: data }) } // 用户状态修改 export function changeUserStatus(userId, status) { const data = { userId, status } return request({ url: '/system/user/changeStatus', method: 'put', data: data }) } // 查询用户个人信息 export function getUserProfile() { return request({ url: '/system/user/profile', method: 'get' }) } // 修改用户个人信息 export function updateUserProfile(data) { return request({ url: '/system/user/profile', method: 'put', data: data }) } // 用户密码重置 export function updateUserPwd(oldPassword, newPassword) { const data = { oldPassword, newPassword } return request({ url: '/system/user/profile/updatePwd', method: 'put', params: data }) } // 用户头像上传 export function uploadAvatar(data) { return request({ url: '/system/user/profile/avatar', method: 'post', data: data }) } // 查询授权角色 export function getAuthRole(userId) { return request({ url: '/system/user/authRole/' + userId, method: 'get' }) } // 保存授权角色 export function updateAuthRole(data) { return request({ url: '/system/user/authRole', method: 'put', params: data }) } ================================================ FILE: vue_campus_admin/src/api/tool/gen.js ================================================ import request from '@/utils/request' // 查询生成表数据 export function listTable(query) { return request({ url: '/tool/gen/list', method: 'get', params: query }) } // 查询db数据库列表 export function listDbTable(query) { return request({ url: '/tool/gen/db/list', method: 'get', params: query }) } // 查询表详细信息 export function getGenTable(tableId) { return request({ url: '/tool/gen/' + tableId, method: 'get' }) } // 修改代码生成信息 export function updateGenTable(data) { return request({ url: '/tool/gen', method: 'put', data: data }) } // 导入表 export function importTable(data) { return request({ url: '/tool/gen/importTable', method: 'post', params: data }) } // 预览生成代码 export function previewTable(tableId) { return request({ url: '/tool/gen/preview/' + tableId, method: 'get' }) } // 删除表数据 export function delTable(tableId) { return request({ url: '/tool/gen/' + tableId, method: 'delete' }) } // 生成代码(自定义路径) export function genCode(tableName) { return request({ url: '/tool/gen/genCode/' + tableName, method: 'get' }) } // 同步数据库 export function synchDb(tableName) { return request({ url: '/tool/gen/synchDb/' + tableName, method: 'get' }) } ================================================ FILE: vue_campus_admin/src/assets/icons/index.js ================================================ import Vue from 'vue' import SvgIcon from '@/components/SvgIcon'// svg component // register globally Vue.component('svg-icon', SvgIcon) const req = require.context('./svg', false, /\.svg$/) const requireAll = requireContext => requireContext.keys().map(requireContext) requireAll(req) ================================================ FILE: vue_campus_admin/src/assets/icons/svgo.yml ================================================ # replace default config # multipass: true # full: true plugins: # - name # # or: # - name: false # - name: true # # or: # - name: # param1: 1 # param2: 2 - removeAttrs: attrs: - 'fill' - 'fill-rule' ================================================ FILE: vue_campus_admin/src/assets/styles/btn.scss ================================================ @import './variables.scss'; @mixin colorBtn($color) { background: $color; &:hover { color: $color; &:before, &:after { background: $color; } } } .blue-btn { @include colorBtn($blue) } .light-blue-btn { @include colorBtn($light-blue) } .red-btn { @include colorBtn($red) } .pink-btn { @include colorBtn($pink) } .green-btn { @include colorBtn($green) } .tiffany-btn { @include colorBtn($tiffany) } .yellow-btn { @include colorBtn($yellow) } .pan-btn { font-size: 14px; color: #fff; padding: 14px 36px; border-radius: 8px; border: none; outline: none; transition: 600ms ease all; position: relative; display: inline-block; &:hover { background: #fff; &:before, &:after { width: 100%; transition: 600ms ease all; } } &:before, &:after { content: ''; position: absolute; top: 0; right: 0; height: 2px; width: 0; transition: 400ms ease all; } &::after { right: inherit; top: inherit; left: 0; bottom: 0; } } .custom-button { display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background: #fff; color: #fff; -webkit-appearance: none; text-align: center; box-sizing: border-box; outline: 0; margin: 0; padding: 10px 15px; font-size: 14px; border-radius: 4px; } ================================================ FILE: vue_campus_admin/src/assets/styles/element-ui.scss ================================================ // cover some element-ui styles .el-breadcrumb__inner, .el-breadcrumb__inner a { font-weight: 400 !important; } .el-upload { input[type="file"] { display: none !important; } } .el-upload__input { display: none; } .cell { .el-tag { margin-right: 0px; } } .small-padding { .cell { padding-left: 5px; padding-right: 5px; } } .fixed-width { .el-button--mini { padding: 7px 10px; width: 60px; } } .status-col { .cell { padding: 0 10px; text-align: center; .el-tag { margin-right: 0px; } } } // to fixed https://github.com/ElemeFE/element/issues/2461 .el-dialog { transform: none; left: 0; position: relative; margin: 0 auto; } // refine element ui upload .upload-container { .el-upload { width: 100%; .el-upload-dragger { width: 100%; height: 200px; } } } // dropdown .el-dropdown-menu { a { display: block } } // fix date-picker ui bug in filter-item .el-range-editor.el-input__inner { display: inline-flex !important; } // to fix el-date-picker css style .el-range-separator { box-sizing: content-box; } .el-menu--collapse > div > .el-submenu > .el-submenu__title .el-submenu__icon-arrow { display: none; } ================================================ FILE: vue_campus_admin/src/assets/styles/element-variables.scss ================================================ /** * I think element-ui's default theme color is too light for long-term use. * So I modified the default color and you can modify it to your liking. **/ /* theme color */ $--color-primary: #1890ff; $--color-success: #13ce66; $--color-warning: #ffba00; $--color-danger: #ff4949; // $--color-info: #1E1E1E; $--button-font-weight: 400; // $--color-text-regular: #1f2d3d; $--border-color-light: #dfe4ed; $--border-color-lighter: #e6ebf5; $--table-border: 1px solid #dfe6ec; /* icon font path, required */ $--font-path: '~element-ui/lib/theme-chalk/fonts'; @import "~element-ui/packages/theme-chalk/src/index"; // the :export directive is the magic sauce for webpack // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass :export { theme: $--color-primary; } ================================================ FILE: vue_campus_admin/src/assets/styles/index.scss ================================================ @import './variables.scss'; @import './mixin.scss'; @import './transition.scss'; @import './element-ui.scss'; @import './sidebar.scss'; @import './btn.scss'; body { height: 100%; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; } label { font-weight: 700; } html { height: 100%; box-sizing: border-box; } #app { height: 100%; } *, *:before, *:after { box-sizing: inherit; } .no-padding { padding: 0px !important; } .padding-content { padding: 4px 0; } a:focus, a:active { outline: none; } a, a:focus, a:hover { cursor: pointer; color: inherit; text-decoration: none; } div:focus { outline: none; } .fr { float: right; } .fl { float: left; } .pr-5 { padding-right: 5px; } .pl-5 { padding-left: 5px; } .block { display: block; } .pointer { cursor: pointer; } .inlineBlock { display: block; } .clearfix { &:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } } aside { background: #eef1f6; padding: 8px 24px; margin-bottom: 20px; border-radius: 2px; display: block; line-height: 32px; font-size: 16px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; color: #2c3e50; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; a { color: #337ab7; cursor: pointer; &:hover { color: rgb(32, 160, 255); } } } //main-container全局样式 .app-container { padding: 20px; } .components-container { margin: 30px 50px; position: relative; } .pagination-container { margin-top: 30px; } .text-center { text-align: center } .sub-navbar { height: 50px; line-height: 50px; position: relative; width: 100%; text-align: right; padding-right: 20px; transition: 600ms ease position; background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); .subtitle { font-size: 20px; color: #fff; } &.draft { background: #d0d0d0; } &.deleted { background: #d0d0d0; } } .link-type, .link-type:focus { color: #337ab7; cursor: pointer; &:hover { color: rgb(32, 160, 255); } } .filter-container { padding-bottom: 10px; .filter-item { display: inline-block; vertical-align: middle; margin-bottom: 10px; } } //refine vue-multiselect plugin .multiselect { line-height: 16px; } .multiselect--active { z-index: 1000 !important; } ================================================ FILE: vue_campus_admin/src/assets/styles/mixin.scss ================================================ @mixin clearfix { &:after { content: ""; display: table; clear: both; } } @mixin scrollBar { &::-webkit-scrollbar-track-piece { background: #d3dce6; } &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: #99a9bf; border-radius: 20px; } } @mixin relative { position: relative; width: 100%; height: 100%; } @mixin pct($pct) { width: #{$pct}; position: relative; margin: 0 auto; } @mixin triangle($width, $height, $color, $direction) { $width: $width/2; $color-border-style: $height solid $color; $transparent-border-style: $width solid transparent; height: 0; width: 0; @if $direction==up { border-bottom: $color-border-style; border-left: $transparent-border-style; border-right: $transparent-border-style; } @else if $direction==right { border-left: $color-border-style; border-top: $transparent-border-style; border-bottom: $transparent-border-style; } @else if $direction==down { border-top: $color-border-style; border-left: $transparent-border-style; border-right: $transparent-border-style; } @else if $direction==left { border-right: $color-border-style; border-top: $transparent-border-style; border-bottom: $transparent-border-style; } } ================================================ FILE: vue_campus_admin/src/assets/styles/ruoyi.scss ================================================ /** * 通用css样式布局处理 * Copyright (c) 2019 ruoyi */ /** 基础通用 **/ .pt5 { padding-top: 5px; } .pr5 { padding-right: 5px; } .pb5 { padding-bottom: 5px; } .mt5 { margin-top: 5px; } .mr5 { margin-right: 5px; } .mb5 { margin-bottom: 5px; } .mb8 { margin-bottom: 8px; } .ml5 { margin-left: 5px; } .mt10 { margin-top: 10px; } .mr10 { margin-right: 10px; } .mb10 { margin-bottom: 10px; } .ml10 { margin-left: 10px; } .mt20 { margin-top: 20px; } .mr20 { margin-right: 20px; } .mb20 { margin-bottom: 20px; } .ml20 { margin-left: 20px; } .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { font-family: inherit; font-weight: 500; line-height: 1.1; color: inherit; } .el-dialog:not(.is-fullscreen) { margin-top: 6vh !important; } .el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body { overflow: auto; overflow-x: hidden; max-height: 70vh; padding: 10px 20px 0; } .el-table { .el-table__header-wrapper, .el-table__fixed-header-wrapper { th { word-break: break-word; background-color: #f8f8f9; color: #515a6e; height: 40px; font-size: 13px; } } .el-table__body-wrapper { .el-button [class*="el-icon-"] + span { margin-left: 1px; } } } /** 表单布局 **/ .form-header { font-size:15px; color:#6379bb; border-bottom:1px solid #ddd; margin:8px 10px 25px 10px; padding-bottom:5px } /** 表格布局 **/ .pagination-container { position: relative; height: 25px; margin-bottom: 10px; margin-top: 15px; padding: 10px 20px !important; } /* tree border */ .tree-border { margin-top: 5px; border: 1px solid #e5e6e7; background: #FFFFFF none; border-radius:4px; } .pagination-container .el-pagination { right: 0; position: absolute; } @media ( max-width : 768px) { .pagination-container .el-pagination > .el-pagination__jump { display: none !important; } .pagination-container .el-pagination > .el-pagination__sizes { display: none !important; } } .el-table .fixed-width .el-button--mini { padding-left: 0; padding-right: 0; width: inherit; } /** 表格更多操作下拉样式 */ .el-table .el-dropdown-link { cursor: pointer; color: #409EFF; margin-left: 5px; } .el-table .el-dropdown, .el-icon-arrow-down { font-size: 12px; } .el-tree-node__content > .el-checkbox { margin-right: 8px; } .list-group-striped > .list-group-item { border-left: 0; border-right: 0; border-radius: 0; padding-left: 0; padding-right: 0; } .list-group { padding-left: 0px; list-style: none; } .list-group-item { border-bottom: 1px solid #e7eaec; border-top: 1px solid #e7eaec; margin-bottom: -1px; padding: 11px 0px; font-size: 13px; } .pull-right { float: right !important; } .el-card__header { padding: 14px 15px 7px; min-height: 40px; } .el-card__body { padding: 15px 20px 20px 20px; } .card-box { padding-right: 15px; padding-left: 15px; margin-bottom: 10px; } /* button color */ .el-button--cyan.is-active, .el-button--cyan:active { background: #20B2AA; border-color: #20B2AA; color: #FFFFFF; } .el-button--cyan:focus, .el-button--cyan:hover { background: #48D1CC; border-color: #48D1CC; color: #FFFFFF; } .el-button--cyan { background-color: #20B2AA; border-color: #20B2AA; color: #FFFFFF; } /* text color */ .text-navy { color: #1ab394; } .text-primary { color: inherit; } .text-success { color: #1c84c6; } .text-info { color: #23c6c8; } .text-warning { color: #f8ac59; } .text-danger { color: #ed5565; } .text-muted { color: #888888; } /* image */ .img-circle { border-radius: 50%; } .img-lg { width: 120px; height: 120px; } .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; } /* 拖拽列样式 */ .sortable-ghost{ opacity: .8; color: #fff!important; background: #42b983!important; } .top-right-btn { position: relative; float: right; } ================================================ FILE: vue_campus_admin/src/assets/styles/sidebar.scss ================================================ #app { .main-container { min-height: 100%; transition: margin-left .28s; margin-left: $base-sidebar-width; position: relative; } .sidebarHide { margin-left: 0!important; } .sidebar-container { -webkit-transition: width .28s; transition: width 0.28s; width: $base-sidebar-width !important; background-color: $base-menu-background; height: 100%; position: fixed; font-size: 0px; top: 0; bottom: 0; left: 0; z-index: 1001; overflow: hidden; -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); box-shadow: 2px 0 6px rgba(0,21,41,.35); // reset element-ui css .horizontal-collapse-transition { transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; } .scrollbar-wrapper { overflow-x: hidden !important; } .el-scrollbar__bar.is-vertical { right: 0px; } .el-scrollbar { height: 100%; } &.has-logo { .el-scrollbar { height: calc(100% - 50px); } } .is-horizontal { display: none; } a { display: inline-block; width: 100%; overflow: hidden; } .svg-icon { margin-right: 16px; } .el-menu { border: none; height: 100%; width: 100% !important; } .el-menu-item, .el-submenu__title { overflow: hidden !important; text-overflow: ellipsis !important; white-space: nowrap !important; } // menu hover .submenu-title-noDropdown, .el-submenu__title { &:hover { background-color: rgba(0, 0, 0, 0.06) !important; } } & .theme-dark .is-active > .el-submenu__title { color: $base-menu-color-active !important; } & .nest-menu .el-submenu>.el-submenu__title, & .el-submenu .el-menu-item { min-width: $base-sidebar-width !important; &:hover { background-color: rgba(0, 0, 0, 0.06) !important; } } & .theme-dark .nest-menu .el-submenu>.el-submenu__title, & .theme-dark .el-submenu .el-menu-item { background-color: $base-sub-menu-background !important; &:hover { background-color: $base-sub-menu-hover !important; } } } .hideSidebar { .sidebar-container { width: 54px !important; } .main-container { margin-left: 54px; } .submenu-title-noDropdown { padding: 0 !important; position: relative; .el-tooltip { padding: 0 !important; .svg-icon { margin-left: 20px; } } } .el-submenu { overflow: hidden; &>.el-submenu__title { padding: 0 !important; .svg-icon { margin-left: 20px; } } } .el-menu--collapse { .el-submenu { &>.el-submenu__title { &>span { height: 0; width: 0; overflow: hidden; visibility: hidden; display: inline-block; } } } } } .el-menu--collapse .el-menu .el-submenu { min-width: $base-sidebar-width !important; } // mobile responsive .mobile { .main-container { margin-left: 0px; } .sidebar-container { transition: transform .28s; width: $base-sidebar-width !important; } &.hideSidebar { .sidebar-container { pointer-events: none; transition-duration: 0.3s; transform: translate3d(-$base-sidebar-width, 0, 0); } } } .withoutAnimation { .main-container, .sidebar-container { transition: none; } } } // when menu collapsed .el-menu--vertical { &>.el-menu { .svg-icon { margin-right: 16px; } } .nest-menu .el-submenu>.el-submenu__title, .el-menu-item { &:hover { // you can use $subMenuHover background-color: rgba(0, 0, 0, 0.06) !important; } } // the scroll bar appears when the subMenu is too long >.el-menu--popup { max-height: 100vh; overflow-y: auto; &::-webkit-scrollbar-track-piece { background: #d3dce6; } &::-webkit-scrollbar { width: 6px; } &::-webkit-scrollbar-thumb { background: #99a9bf; border-radius: 20px; } } } ================================================ FILE: vue_campus_admin/src/assets/styles/transition.scss ================================================ // global transition css /* fade */ .fade-enter-active, .fade-leave-active { transition: opacity 0.28s; } .fade-enter, .fade-leave-active { opacity: 0; } /* fade-transform */ .fade-transform--move, .fade-transform-leave-active, .fade-transform-enter-active { transition: all .5s; } .fade-transform-leave-active { position: absolute; } .fade-transform-enter { opacity: 0; transform: translateX(-30px); } .fade-transform-leave-to { opacity: 0; transform: translateX(30px); } /* breadcrumb transition */ .breadcrumb-enter-active, .breadcrumb-leave-active { transition: all .5s; } .breadcrumb-enter, .breadcrumb-leave-active { opacity: 0; transform: translateX(20px); } .breadcrumb-move { transition: all .5s; } .breadcrumb-leave-active { position: absolute; } ================================================ FILE: vue_campus_admin/src/assets/styles/variables.scss ================================================ // base color $blue:#324157; $light-blue:#3A71A8; $red:#C03639; $pink: #E65D6E; $green: #30B08F; $tiffany: #4AB7BD; $yellow:#FEC171; $panGreen: #30B08F; // 默认菜单主题风格 $base-menu-color:#bfcbd9; $base-menu-color-active:#f4f4f5; $base-menu-background:#304156; $base-logo-title-color: #ffffff; $base-menu-light-color:rgba(0,0,0,.70); $base-menu-light-background:#ffffff; $base-logo-light-title-color: #001529; $base-sub-menu-background:#1f2d3d; $base-sub-menu-hover:#001528; // 自定义暗色菜单风格 /** $base-menu-color:hsla(0,0%,100%,.65); $base-menu-color-active:#fff; $base-menu-background:#001529; $base-logo-title-color: #ffffff; $base-menu-light-color:rgba(0,0,0,.70); $base-menu-light-background:#ffffff; $base-logo-light-title-color: #001529; $base-sub-menu-background:#000c17; $base-sub-menu-hover:#001528; */ $base-sidebar-width: 200px; // the :export directive is the magic sauce for webpack // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass :export { menuColor: $base-menu-color; menuLightColor: $base-menu-light-color; menuColorActive: $base-menu-color-active; menuBackground: $base-menu-background; menuLightBackground: $base-menu-light-background; subMenuBackground: $base-sub-menu-background; subMenuHover: $base-sub-menu-hover; sideBarWidth: $base-sidebar-width; logoTitleColor: $base-logo-title-color; logoLightTitleColor: $base-logo-light-title-color } ================================================ FILE: vue_campus_admin/src/components/Breadcrumb/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/day.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/hour.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/min.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/month.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/result.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/second.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/week.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Crontab/year.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/DictData/index.js ================================================ import Vue from 'vue' import store from '@/store' import DataDict from '@/utils/dict' import { getDicts as getDicts } from '@/api/system/dict/data' function searchDictByKey(dict, key) { if (key == null && key == "") { return null } try { for (let i = 0; i < dict.length; i++) { if (dict[i].key == key) { return dict[i].value } } } catch (e) { return null } } function install() { Vue.use(DataDict, { metas: { '*': { labelField: 'dictLabel', valueField: 'dictValue', request(dictMeta) { const storeDict = searchDictByKey(store.getters.dict, dictMeta.type) if (storeDict) { return new Promise(resolve => { resolve(storeDict) }) } else { return new Promise((resolve, reject) => { getDicts(dictMeta.type).then(res => { store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data }) resolve(res.data) }).catch(error => { reject(error) }) }) } }, }, }, }) } export default { install, } ================================================ FILE: vue_campus_admin/src/components/DictTag/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Editor/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/FileUpload/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Hamburger/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/HeaderSearch/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/IconSelect/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/IconSelect/requireIcons.js ================================================ const req = require.context('../../assets/icons/svg', false, /\.svg$/) const requireAll = requireContext => requireContext.keys() const re = /\.\/(.*)\.svg/ const icons = requireAll(req).map(i => { return i.match(re)[1] }) export default icons ================================================ FILE: vue_campus_admin/src/components/ImagePreview/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/ImageUpload/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Pagination/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/PanThumb/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/ParentView/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/RightPanel/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/RightToolbar/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/RuoYi/Doc/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/RuoYi/Git/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/Screenfull/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/SizeSelect/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/SvgIcon/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/ThemePicker/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/TopNav/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/components/iFrame/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/Navbar.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/Settings/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/Sidebar/FixiOSBug.js ================================================ export default { computed: { device() { return this.$store.state.app.device } }, mounted() { // In order to fix the click on menu on the ios device will trigger the mouseleave bug this.fixBugIniOS() }, methods: { fixBugIniOS() { const $subMenu = this.$refs.subMenu if ($subMenu) { const handleMouseleave = $subMenu.handleMouseleave $subMenu.handleMouseleave = (e) => { if (this.device === 'mobile') { return } handleMouseleave(e) } } } } } ================================================ FILE: vue_campus_admin/src/layout/components/Sidebar/Item.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/Sidebar/Link.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/Sidebar/Logo.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/Sidebar/SidebarItem.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/Sidebar/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/TagsView/ScrollPane.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/TagsView/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/components/index.js ================================================ export { default as AppMain } from './AppMain' export { default as Navbar } from './Navbar' export { default as Settings } from './Settings' export { default as Sidebar } from './Sidebar/index.vue' export { default as TagsView } from './TagsView/index.vue' ================================================ FILE: vue_campus_admin/src/layout/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/layout/mixin/ResizeHandler.js ================================================ import store from '@/store' const { body } = document const WIDTH = 992 // refer to Bootstrap's responsive design export default { watch: { $route(route) { if (this.device === 'mobile' && this.sidebar.opened) { store.dispatch('app/closeSideBar', { withoutAnimation: false }) } } }, beforeMount() { window.addEventListener('resize', this.$_resizeHandler) }, beforeDestroy() { window.removeEventListener('resize', this.$_resizeHandler) }, mounted() { const isMobile = this.$_isMobile() if (isMobile) { store.dispatch('app/toggleDevice', 'mobile') store.dispatch('app/closeSideBar', { withoutAnimation: true }) } }, methods: { // use $_ for mixins properties // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential $_isMobile() { const rect = body.getBoundingClientRect() return rect.width - 1 < WIDTH }, $_resizeHandler() { if (!document.hidden) { const isMobile = this.$_isMobile() store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') if (isMobile) { store.dispatch('app/closeSideBar', { withoutAnimation: true }) } } } } } ================================================ FILE: vue_campus_admin/src/main.js ================================================ import Vue from 'vue' import Cookies from 'js-cookie' import Element from 'element-ui' import './assets/styles/element-variables.scss' import '@/assets/styles/index.scss' // global css import '@/assets/styles/ruoyi.scss' // ruoyi css import App from './App' import store from './store' import router from './router' import directive from './directive' // directive import plugins from './plugins' // plugins import { download } from '@/utils/request' import './assets/icons' // icon import './permission' // permission control import Crypto from "@/utils/crypto"; import { getDicts } from "@/api/system/dict/data"; import { getConfigKey } from "@/api/system/config"; import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi"; // 分页组件 import Pagination from "@/components/Pagination"; // 自定义表格工具组件 import RightToolbar from "@/components/RightToolbar" // 富文本组件 import Editor from "@/components/Editor" // 文件上传组件 import FileUpload from "@/components/FileUpload" // 图片上传组件 import ImageUpload from "@/components/ImageUpload" // 图片预览组件 import ImagePreview from "@/components/ImagePreview" // 字典标签组件 import DictTag from '@/components/DictTag' // 头部标签组件 import VueMeta from 'vue-meta' // 字典数据组件 import DictData from '@/components/DictData' // 全局方法挂载 Vue.prototype.getDicts = getDicts Vue.prototype.getConfigKey = getConfigKey Vue.prototype.parseTime = parseTime Vue.prototype.resetForm = resetForm Vue.prototype.addDateRange = addDateRange Vue.prototype.selectDictLabel = selectDictLabel Vue.prototype.selectDictLabels = selectDictLabels Vue.prototype.download = download Vue.prototype.handleTree = handleTree Vue.prototype.Crypto = Crypto // 全局组件挂载 Vue.component('DictTag', DictTag) Vue.component('Pagination', Pagination) Vue.component('RightToolbar', RightToolbar) Vue.component('Editor', Editor) Vue.component('FileUpload', FileUpload) Vue.component('ImageUpload', ImageUpload) Vue.component('ImagePreview', ImagePreview) Vue.use(directive) Vue.use(plugins) Vue.use(VueMeta) DictData.install() /** * If you don't want to use mock-server * you want to use MockJs for mock api * you can execute: mockXHR() * * Currently MockJs will be used in the production environment, * please remove it before going online! ! ! */ Vue.use(Element, { size: Cookies.get('size') || 'medium' // set element-ui default size }) Vue.config.productionTip = false new Vue({ el: '#app', router, store, render: h => h(App) }) ================================================ FILE: vue_campus_admin/src/permission.js ================================================ import router from './router' import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { getToken } from '@/utils/auth' import { isRelogin } from '@/utils/request' NProgress.configure({ showSpinner: false }) const whiteList = ['/login', '/auth-redirect', '/bind', '/register'] router.beforeEach((to, from, next) => { NProgress.start() if (getToken()) { to.meta.title && store.dispatch('settings/setTitle', to.meta.title) /* has token*/ if (to.path === '/login') { next({ path: '/' }) NProgress.done() } else { if (store.getters.roles.length === 0) { isRelogin.show = true // 判断当前用户是否已拉取完user_info信息 store.dispatch('GetInfo').then(() => { isRelogin.show = false store.dispatch('GenerateRoutes').then(accessRoutes => { // 根据roles权限生成可访问的路由表 router.addRoutes(accessRoutes) // 动态添加可访问路由表 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 }) }).catch(err => { store.dispatch('LogOut').then(() => { Message.error(err) next({ path: '/' }) }) }) } else { next() } } } else { // 没有token if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 next() } else { next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 NProgress.done() } } }) router.afterEach(() => { NProgress.done() }) ================================================ FILE: vue_campus_admin/src/plugins/auth.js ================================================ import store from '@/store' function authPermission(permission) { const all_permission = "*:*:*"; const permissions = store.getters && store.getters.permissions if (permission && permission.length > 0) { return permissions.some(v => { return all_permission === v || v === permission }) } else { return false } } function authRole(role) { const super_admin = "admin"; const roles = store.getters && store.getters.roles if (role && role.length > 0) { return roles.some(v => { return super_admin === v || v === role }) } else { return false } } export default { // 验证用户是否具备某权限 hasPermi(permission) { return authPermission(permission); }, // 验证用户是否含有指定权限,只需包含其中一个 hasPermiOr(permissions) { return permissions.some(item => { return authPermission(item) }) }, // 验证用户是否含有指定权限,必须全部拥有 hasPermiAnd(permissions) { return permissions.every(item => { return authPermission(item) }) }, // 验证用户是否具备某角色 hasRole(role) { return authRole(role); }, // 验证用户是否含有指定角色,只需包含其中一个 hasRoleOr(roles) { return roles.some(item => { return authRole(item) }) }, // 验证用户是否含有指定角色,必须全部拥有 hasRoleAnd(roles) { return roles.every(item => { return authRole(item) }) } } ================================================ FILE: vue_campus_admin/src/plugins/cache.js ================================================ const sessionCache = { set (key, value) { if (!sessionStorage) { return } if (key != null && value != null) { sessionStorage.setItem(key, value) } }, get (key) { if (!sessionStorage) { return null } if (key == null) { return null } return sessionStorage.getItem(key) }, setJSON (key, jsonValue) { if (jsonValue != null) { this.set(key, JSON.stringify(jsonValue)) } }, getJSON (key) { const value = this.get(key) if (value != null) { return JSON.parse(value) } }, remove (key) { sessionStorage.removeItem(key); } } const localCache = { set (key, value) { if (!localStorage) { return } if (key != null && value != null) { localStorage.setItem(key, value) } }, get (key) { if (!localStorage) { return null } if (key == null) { return null } return localStorage.getItem(key) }, setJSON (key, jsonValue) { if (jsonValue != null) { this.set(key, JSON.stringify(jsonValue)) } }, getJSON (key) { const value = this.get(key) if (value != null) { return JSON.parse(value) } }, remove (key) { localStorage.removeItem(key); } } export default { /** * 会话级缓存 */ session: sessionCache, /** * 本地缓存 */ local: localCache } ================================================ FILE: vue_campus_admin/src/plugins/download.js ================================================ import axios from 'axios' import { Message } from 'element-ui' import { saveAs } from 'file-saver' import { getToken } from '@/utils/auth' import errorCode from '@/utils/errorCode' import { blobValidate } from "@/utils/ruoyi"; const baseURL = process.env.VUE_APP_BASE_API export default { name(name, isDelete = true) { var url = baseURL + "/common/download?fileName=" + encodeURI(name) + "&delete=" + isDelete axios({ method: 'get', url: url, responseType: 'blob', headers: { 'Authorization': 'Bearer ' + getToken() } }).then(async (res) => { const isLogin = await blobValidate(res.data); if (isLogin) { const blob = new Blob([res.data]) this.saveAs(blob, decodeURI(res.headers['download-filename'])) } else { this.printErrMsg(res.data); } }) }, resource(resource) { var url = baseURL + "/common/download/resource?resource=" + encodeURI(resource); axios({ method: 'get', url: url, responseType: 'blob', headers: { 'Authorization': 'Bearer ' + getToken() } }).then(async (res) => { const isLogin = await blobValidate(res.data); if (isLogin) { const blob = new Blob([res.data]) this.saveAs(blob, decodeURI(res.headers['download-filename'])) } else { this.printErrMsg(res.data); } }) }, zip(url, name) { var url = baseURL + url axios({ method: 'get', url: url, responseType: 'blob', headers: { 'Authorization': 'Bearer ' + getToken() } }).then(async (res) => { const isLogin = await blobValidate(res.data); if (isLogin) { const blob = new Blob([res.data], { type: 'application/zip' }) this.saveAs(blob, name) } else { this.printErrMsg(res.data); } }) }, saveAs(text, name, opts) { saveAs(text, name, opts); }, async printErrMsg(data) { const resText = await data.text(); const rspObj = JSON.parse(resText); const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] Message.error(errMsg); } } ================================================ FILE: vue_campus_admin/src/plugins/index.js ================================================ import tab from './tab' import auth from './auth' import cache from './cache' import modal from './modal' import download from './download' export default { install(Vue) { // 页签操作 Vue.prototype.$tab = tab // 认证对象 Vue.prototype.$auth = auth // 缓存对象 Vue.prototype.$cache = cache // 模态框对象 Vue.prototype.$modal = modal // 下载文件 Vue.prototype.$download = download } } ================================================ FILE: vue_campus_admin/src/plugins/modal.js ================================================ import { Message, MessageBox, Notification, Loading } from 'element-ui' let loadingInstance; export default { // 消息提示 msg(content) { Message.info(content) }, // 错误消息 msgError(content) { Message.error(content) }, // 成功消息 msgSuccess(content) { Message.success(content) }, // 警告消息 msgWarning(content) { Message.warning(content) }, // 弹出提示 alert(content) { MessageBox.alert(content, "系统提示") }, // 错误提示 alertError(content) { MessageBox.alert(content, "系统提示", { type: 'error' }) }, // 成功提示 alertSuccess(content) { MessageBox.alert(content, "系统提示", { type: 'success' }) }, // 警告提示 alertWarning(content) { MessageBox.alert(content, "系统提示", { type: 'warning' }) }, // 通知提示 notify(content) { Notification.info(content) }, // 错误通知 notifyError(content) { Notification.error(content); }, // 成功通知 notifySuccess(content) { Notification.success(content) }, // 警告通知 notifyWarning(content) { Notification.warning(content) }, // 确认窗体 confirm(content) { return MessageBox.confirm(content, "系统提示", { confirmButtonText: '确定', cancelButtonText: '取消', type: "warning", }) }, // 提交内容 prompt(content) { return MessageBox.prompt(content, "系统提示", { confirmButtonText: '确定', cancelButtonText: '取消', type: "warning", }) }, // 打开遮罩层 loading(content) { loadingInstance = Loading.service({ lock: true, text: content, spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) }, // 关闭遮罩层 closeLoading() { loadingInstance.close(); } } ================================================ FILE: vue_campus_admin/src/plugins/tab.js ================================================ import store from '@/store' import router from '@/router'; export default { // 刷新当前tab页签 refreshPage(obj) { const { path, query, matched } = router.currentRoute; if (obj === undefined) { matched.forEach((m) => { if (m.components && m.components.default && m.components.default.name) { if (!['Layout', 'ParentView'].includes(m.components.default.name)) { obj = { name: m.components.default.name, path: path, query: query }; } } }); } return store.dispatch('tagsView/delCachedView', obj).then(() => { const { path, query } = obj router.replace({ path: '/redirect' + path, query: query }) }) }, // 关闭当前tab页签,打开新页签 closeOpenPage(obj) { store.dispatch("tagsView/delView", router.currentRoute); if (obj !== undefined) { return router.push(obj); } }, // 关闭指定tab页签 closePage(obj) { if (obj === undefined) { return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => { return router.push(lastPath || '/'); }); } return store.dispatch('tagsView/delView', obj); }, // 关闭所有tab页签 closeAllPage() { return store.dispatch('tagsView/delAllViews'); }, // 关闭左侧tab页签 closeLeftPage(obj) { return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute); }, // 关闭右侧tab页签 closeRightPage(obj) { return store.dispatch('tagsView/delRightTags', obj || router.currentRoute); }, // 关闭其他tab页签 closeOtherPage(obj) { return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute); }, // 添加tab页签 openPage(title, url, params) { var obj = { path: url, meta: { title: title } } store.dispatch('tagsView/addView', obj); return router.push({ path: url, query: params }); }, // 修改tab页签 updatePage(obj) { return store.dispatch('tagsView/updateVisitedView', obj); } } ================================================ FILE: vue_campus_admin/src/router/index.js ================================================ import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) /* Layout */ import Layout from '@/layout' /** * Note: 路由配置项 * * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 * roles: ['admin', 'common'] // 访问路由的角色权限 * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 * meta : { noCache: true // 如果设置为true,则不会被 缓存(默认 false) title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 } */ // 公共路由 export const constantRoutes = [ { path: '/redirect', component: Layout, hidden: true, children: [ { path: '/redirect/:path(.*)', component: () => import('@/views/redirect') } ] }, { path: '/login', component: () => import('@/views/login'), hidden: true }, { path: '/register', component: () => import('@/views/register'), hidden: true }, { path: '/404', component: () => import('@/views/error/404'), hidden: true }, { path: '/401', component: () => import('@/views/error/401'), hidden: true }, { path: '', component: Layout, redirect: 'index', children: [ { path: 'index', component: () => import('@/views/index'), name: 'Index', meta: { title: '首页', icon: 'dashboard', affix: true } } ] }, { path: '/user', component: Layout, hidden: true, redirect: 'noredirect', children: [ { path: 'profile', component: () => import('@/views/system/user/profile/index'), name: 'Profile', meta: { title: '个人中心', icon: 'user' } } ] } ] // 动态路由,基于用户权限动态去加载 export const dynamicRoutes = [ { path: '/system/user-auth', component: Layout, hidden: true, permissions: ['system:user:edit'], children: [ { path: 'role/:userId(\\d+)', component: () => import('@/views/system/user/authRole'), name: 'AuthRole', meta: { title: '分配角色', activeMenu: '/system/user' } } ] }, { path: '/system/role-auth', component: Layout, hidden: true, permissions: ['system:role:edit'], children: [ { path: 'user/:roleId(\\d+)', component: () => import('@/views/system/role/authUser'), name: 'AuthUser', meta: { title: '分配用户', activeMenu: '/system/role' } } ] }, { path: '/system/dict-data', component: Layout, hidden: true, permissions: ['system:dict:list'], children: [ { path: 'index/:dictId(\\d+)', component: () => import('@/views/system/dict/data'), name: 'Data', meta: { title: '字典数据', activeMenu: '/system/dict' } } ] }, { path: '/monitor/job-log', component: Layout, hidden: true, permissions: ['monitor:job:list'], children: [ { path: 'index', component: () => import('@/views/monitor/job/log'), name: 'JobLog', meta: { title: '调度日志', activeMenu: '/monitor/job' } } ] }, { path: '/tool/gen-edit', component: Layout, hidden: true, permissions: ['tool:gen:edit'], children: [ { path: 'index/:tableId(\\d+)', component: () => import('@/views/tool/gen/editTable'), name: 'GenEdit', meta: { title: '修改生成配置', activeMenu: '/tool/gen' } } ] } ] // 防止连续点击多次路由报错 let routerPush = Router.prototype.push; Router.prototype.push = function push(location) { return routerPush.call(this, location).catch(err => err) } export default new Router({ mode: 'history', // 去掉url中的# scrollBehavior: () => ({ y: 0 }), routes: constantRoutes }) ================================================ FILE: vue_campus_admin/src/settings.js ================================================ module.exports = { /** * 侧边栏主题 深色主题theme-dark,浅色主题theme-light */ sideTheme: 'theme-dark', /** * 是否系统布局配置 */ showSettings: false, /** * 是否显示顶部导航 */ topNav: false, /** * 是否显示 tagsView */ tagsView: true, /** * 是否固定头部 */ fixedHeader: false, /** * 是否显示logo */ sidebarLogo: true, /** * 是否显示动态标题 */ dynamicTitle: false, /** * @type {string | array} 'production' | ['production', 'development'] * @description Need show err logs component. * The default is only used in the production env * If you want to also use it in dev, you can pass ['production', 'development'] */ errorLog: 'production' } ================================================ FILE: vue_campus_admin/src/store/getters.js ================================================ const getters = { sidebar: state => state.app.sidebar, size: state => state.app.size, device: state => state.app.device, dict: state => state.dict.dict, visitedViews: state => state.tagsView.visitedViews, cachedViews: state => state.tagsView.cachedViews, token: state => state.user.token, avatar: state => state.user.avatar, name: state => state.user.name, introduction: state => state.user.introduction, roles: state => state.user.roles, permissions: state => state.user.permissions, permission_routes: state => state.permission.routes, topbarRouters:state => state.permission.topbarRouters, defaultRoutes:state => state.permission.defaultRoutes, sidebarRouters:state => state.permission.sidebarRouters, } export default getters ================================================ FILE: vue_campus_admin/src/store/index.js ================================================ import Vue from 'vue' import Vuex from 'vuex' import app from './modules/app' import dict from './modules/dict' import user from './modules/user' import tagsView from './modules/tagsView' import permission from './modules/permission' import settings from './modules/settings' import getters from './getters' Vue.use(Vuex) const store = new Vuex.Store({ modules: { app, dict, user, tagsView, permission, settings }, getters }) export default store ================================================ FILE: vue_campus_admin/src/store/modules/app.js ================================================ import Cookies from 'js-cookie' const state = { sidebar: { opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, withoutAnimation: false, hide: false }, device: 'desktop', size: Cookies.get('size') || 'medium' } const mutations = { TOGGLE_SIDEBAR: state => { if (state.sidebar.hide) { return false; } state.sidebar.opened = !state.sidebar.opened state.sidebar.withoutAnimation = false if (state.sidebar.opened) { Cookies.set('sidebarStatus', 1) } else { Cookies.set('sidebarStatus', 0) } }, CLOSE_SIDEBAR: (state, withoutAnimation) => { Cookies.set('sidebarStatus', 0) state.sidebar.opened = false state.sidebar.withoutAnimation = withoutAnimation }, TOGGLE_DEVICE: (state, device) => { state.device = device }, SET_SIZE: (state, size) => { state.size = size Cookies.set('size', size) }, SET_SIDEBAR_HIDE: (state, status) => { state.sidebar.hide = status } } const actions = { toggleSideBar({ commit }) { commit('TOGGLE_SIDEBAR') }, closeSideBar({ commit }, { withoutAnimation }) { commit('CLOSE_SIDEBAR', withoutAnimation) }, toggleDevice({ commit }, device) { commit('TOGGLE_DEVICE', device) }, setSize({ commit }, size) { commit('SET_SIZE', size) }, toggleSideBarHide({ commit }, status) { commit('SET_SIDEBAR_HIDE', status) } } export default { namespaced: true, state, mutations, actions } ================================================ FILE: vue_campus_admin/src/store/modules/dict.js ================================================ const state = { dict: new Array() } const mutations = { SET_DICT: (state, { key, value }) => { if (key !== null && key !== "") { state.dict.push({ key: key, value: value }) } }, REMOVE_DICT: (state, key) => { try { for (let i = 0; i < state.dict.length; i++) { if (state.dict[i].key == key) { state.dict.splice(i, i) return true } } } catch (e) { } }, CLEAN_DICT: (state) => { state.dict = new Array() } } const actions = { // 设置字典 setDict({ commit }, data) { commit('SET_DICT', data) }, // 删除字典 removeDict({ commit }, key) { commit('REMOVE_DICT', key) }, // 清空字典 cleanDict({ commit }) { commit('CLEAN_DICT') } } export default { namespaced: true, state, mutations, actions } ================================================ FILE: vue_campus_admin/src/store/modules/permission.js ================================================ import auth from '@/plugins/auth' import router, { constantRoutes, dynamicRoutes } from '@/router' import { getRouters } from '@/api/menu' import Layout from '@/layout/index' import ParentView from '@/components/ParentView' import InnerLink from '@/layout/components/InnerLink' const permission = { state: { routes: [], addRoutes: [], defaultRoutes: [], topbarRouters: [], sidebarRouters: [] }, mutations: { SET_ROUTES: (state, routes) => { state.addRoutes = routes state.routes = constantRoutes.concat(routes) }, SET_DEFAULT_ROUTES: (state, routes) => { state.defaultRoutes = constantRoutes.concat(routes) }, SET_TOPBAR_ROUTES: (state, routes) => { state.topbarRouters = routes }, SET_SIDEBAR_ROUTERS: (state, routes) => { state.sidebarRouters = routes }, }, actions: { // 生成路由 GenerateRoutes({ commit }) { return new Promise(resolve => { // 向后端请求路由数据 getRouters().then(res => { const sdata = JSON.parse(JSON.stringify(res.data)) const rdata = JSON.parse(JSON.stringify(res.data)) const sidebarRoutes = filterAsyncRouter(sdata) const rewriteRoutes = filterAsyncRouter(rdata, false, true) const asyncRoutes = filterDynamicRoutes(dynamicRoutes); rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) router.addRoutes(asyncRoutes); commit('SET_ROUTES', rewriteRoutes) commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) commit('SET_DEFAULT_ROUTES', sidebarRoutes) commit('SET_TOPBAR_ROUTES', sidebarRoutes) resolve(rewriteRoutes) }) }) } } } // 遍历后台传来的路由字符串,转换为组件对象 function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { return asyncRouterMap.filter(route => { if (type && route.children) { route.children = filterChildren(route.children) } if (route.component) { // Layout ParentView 组件特殊处理 if (route.component === 'Layout') { route.component = Layout } else if (route.component === 'ParentView') { route.component = ParentView } else if (route.component === 'InnerLink') { route.component = InnerLink } else { route.component = loadView(route.component) } } if (route.children != null && route.children && route.children.length) { route.children = filterAsyncRouter(route.children, route, type) } else { delete route['children'] delete route['redirect'] } return true }) } function filterChildren(childrenMap, lastRouter = false) { var children = [] childrenMap.forEach((el, index) => { if (el.children && el.children.length) { if (el.component === 'ParentView' && !lastRouter) { el.children.forEach(c => { c.path = el.path + '/' + c.path if (c.children && c.children.length) { children = children.concat(filterChildren(c.children, c)) return } children.push(c) }) return } } if (lastRouter) { el.path = lastRouter.path + '/' + el.path } children = children.concat(el) }) return children } // 动态路由遍历,验证是否具备权限 export function filterDynamicRoutes(routes) { const res = [] routes.forEach(route => { if (route.permissions) { if (auth.hasPermiOr(route.permissions)) { res.push(route) } } else if (route.roles) { if (auth.hasRoleOr(route.roles)) { res.push(route) } } }) return res } export const loadView = (view) => { if (process.env.NODE_ENV === 'development') { return (resolve) => require([`@/views/${view}`], resolve) } else { // 使用 import 实现生产环境的路由懒加载 return () => import(`@/views/${view}`) } } export default permission ================================================ FILE: vue_campus_admin/src/store/modules/settings.js ================================================ import defaultSettings from '@/settings' const { sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle } = defaultSettings const storageSetting = JSON.parse(localStorage.getItem('layout-setting')) || '' const state = { title: '', theme: storageSetting.theme || '#409EFF', sideTheme: storageSetting.sideTheme || sideTheme, showSettings: showSettings, topNav: storageSetting.topNav === undefined ? topNav : storageSetting.topNav, tagsView: storageSetting.tagsView === undefined ? tagsView : storageSetting.tagsView, fixedHeader: storageSetting.fixedHeader === undefined ? fixedHeader : storageSetting.fixedHeader, sidebarLogo: storageSetting.sidebarLogo === undefined ? sidebarLogo : storageSetting.sidebarLogo, dynamicTitle: storageSetting.dynamicTitle === undefined ? dynamicTitle : storageSetting.dynamicTitle } const mutations = { CHANGE_SETTING: (state, { key, value }) => { if (state.hasOwnProperty(key)) { state[key] = value } } } const actions = { // 修改布局设置 changeSetting({ commit }, data) { commit('CHANGE_SETTING', data) }, // 设置网页标题 setTitle({ commit }, title) { state.title = title } } export default { namespaced: true, state, mutations, actions } ================================================ FILE: vue_campus_admin/src/store/modules/tagsView.js ================================================ const state = { visitedViews: [], cachedViews: [], iframeViews: [] } const mutations = { ADD_IFRAME_VIEW: (state, view) => { if (state.iframeViews.some(v => v.path === view.path)) return state.iframeViews.push( Object.assign({}, view, { title: view.meta.title || 'no-name' }) ) }, ADD_VISITED_VIEW: (state, view) => { if (state.visitedViews.some(v => v.path === view.path)) return state.visitedViews.push( Object.assign({}, view, { title: view.meta.title || 'no-name' }) ) }, ADD_CACHED_VIEW: (state, view) => { if (state.cachedViews.includes(view.name)) return if (view.meta && !view.meta.noCache) { state.cachedViews.push(view.name) } }, DEL_VISITED_VIEW: (state, view) => { for (const [i, v] of state.visitedViews.entries()) { if (v.path === view.path) { state.visitedViews.splice(i, 1) break } } state.iframeViews = state.iframeViews.filter(item => item.path !== view.path) }, DEL_IFRAME_VIEW: (state, view) => { state.iframeViews = state.iframeViews.filter(item => item.path !== view.path) }, DEL_CACHED_VIEW: (state, view) => { const index = state.cachedViews.indexOf(view.name) index > -1 && state.cachedViews.splice(index, 1) }, DEL_OTHERS_VISITED_VIEWS: (state, view) => { state.visitedViews = state.visitedViews.filter(v => { return v.meta.affix || v.path === view.path }) state.iframeViews = state.iframeViews.filter(item => item.path === view.path) }, DEL_OTHERS_CACHED_VIEWS: (state, view) => { const index = state.cachedViews.indexOf(view.name) if (index > -1) { state.cachedViews = state.cachedViews.slice(index, index + 1) } else { state.cachedViews = [] } }, DEL_ALL_VISITED_VIEWS: state => { // keep affix tags const affixTags = state.visitedViews.filter(tag => tag.meta.affix) state.visitedViews = affixTags state.iframeViews = [] }, DEL_ALL_CACHED_VIEWS: state => { state.cachedViews = [] }, UPDATE_VISITED_VIEW: (state, view) => { for (let v of state.visitedViews) { if (v.path === view.path) { v = Object.assign(v, view) break } } }, DEL_RIGHT_VIEWS: (state, view) => { const index = state.visitedViews.findIndex(v => v.path === view.path) if (index === -1) { return } state.visitedViews = state.visitedViews.filter((item, idx) => { if (idx <= index || (item.meta && item.meta.affix)) { return true } const i = state.cachedViews.indexOf(item.name) if (i > -1) { state.cachedViews.splice(i, 1) } if(item.meta.link) { const fi = state.iframeViews.findIndex(v => v.path === item.path) state.iframeViews.splice(fi, 1) } return false }) }, DEL_LEFT_VIEWS: (state, view) => { const index = state.visitedViews.findIndex(v => v.path === view.path) if (index === -1) { return } state.visitedViews = state.visitedViews.filter((item, idx) => { if (idx >= index || (item.meta && item.meta.affix)) { return true } const i = state.cachedViews.indexOf(item.name) if (i > -1) { state.cachedViews.splice(i, 1) } if(item.meta.link) { const fi = state.iframeViews.findIndex(v => v.path === item.path) state.iframeViews.splice(fi, 1) } return false }) } } const actions = { addView({ dispatch }, view) { dispatch('addVisitedView', view) dispatch('addCachedView', view) }, addIframeView({ commit }, view) { commit('ADD_IFRAME_VIEW', view) }, addVisitedView({ commit }, view) { commit('ADD_VISITED_VIEW', view) }, addCachedView({ commit }, view) { commit('ADD_CACHED_VIEW', view) }, delView({ dispatch, state }, view) { return new Promise(resolve => { dispatch('delVisitedView', view) dispatch('delCachedView', view) resolve({ visitedViews: [...state.visitedViews], cachedViews: [...state.cachedViews] }) }) }, delVisitedView({ commit, state }, view) { return new Promise(resolve => { commit('DEL_VISITED_VIEW', view) resolve([...state.visitedViews]) }) }, delIframeView({ commit, state }, view) { return new Promise(resolve => { commit('DEL_IFRAME_VIEW', view) resolve([...state.iframeViews]) }) }, delCachedView({ commit, state }, view) { return new Promise(resolve => { commit('DEL_CACHED_VIEW', view) resolve([...state.cachedViews]) }) }, delOthersViews({ dispatch, state }, view) { return new Promise(resolve => { dispatch('delOthersVisitedViews', view) dispatch('delOthersCachedViews', view) resolve({ visitedViews: [...state.visitedViews], cachedViews: [...state.cachedViews] }) }) }, delOthersVisitedViews({ commit, state }, view) { return new Promise(resolve => { commit('DEL_OTHERS_VISITED_VIEWS', view) resolve([...state.visitedViews]) }) }, delOthersCachedViews({ commit, state }, view) { return new Promise(resolve => { commit('DEL_OTHERS_CACHED_VIEWS', view) resolve([...state.cachedViews]) }) }, delAllViews({ dispatch, state }, view) { return new Promise(resolve => { dispatch('delAllVisitedViews', view) dispatch('delAllCachedViews', view) resolve({ visitedViews: [...state.visitedViews], cachedViews: [...state.cachedViews] }) }) }, delAllVisitedViews({ commit, state }) { return new Promise(resolve => { commit('DEL_ALL_VISITED_VIEWS') resolve([...state.visitedViews]) }) }, delAllCachedViews({ commit, state }) { return new Promise(resolve => { commit('DEL_ALL_CACHED_VIEWS') resolve([...state.cachedViews]) }) }, updateVisitedView({ commit }, view) { commit('UPDATE_VISITED_VIEW', view) }, delRightTags({ commit }, view) { return new Promise(resolve => { commit('DEL_RIGHT_VIEWS', view) resolve([...state.visitedViews]) }) }, delLeftTags({ commit }, view) { return new Promise(resolve => { commit('DEL_LEFT_VIEWS', view) resolve([...state.visitedViews]) }) }, } export default { namespaced: true, state, mutations, actions } ================================================ FILE: vue_campus_admin/src/store/modules/user.js ================================================ import { login, logout, getInfo } from '@/api/login' import { getToken, setToken, removeToken } from '@/utils/auth' const user = { state: { token: getToken(), name: '', avatar: '', roles: [], permissions: [] }, mutations: { SET_TOKEN: (state, token) => { state.token = token }, SET_NAME: (state, name) => { state.name = name }, SET_AVATAR: (state, avatar) => { state.avatar = avatar }, SET_ROLES: (state, roles) => { state.roles = roles }, SET_PERMISSIONS: (state, permissions) => { state.permissions = permissions } }, actions: { // 登录 Login({ commit }, userInfo) { const username = userInfo.username.trim() const password = userInfo.password const code = userInfo.code const uuid = userInfo.uuid return new Promise((resolve, reject) => { login(username, password, code, uuid).then(res => { setToken(res.token) commit('SET_TOKEN', res.token) resolve() }).catch(error => { reject(error) }) }) }, // 获取用户信息 GetInfo({ commit, state }) { return new Promise((resolve, reject) => { getInfo().then(res => { const user = res.user let avatar = user.avatar; if (user.avatar.trim().toLowerCase().substring(0, 4) != "http") { avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar; } if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组 commit('SET_ROLES', res.roles) commit('SET_PERMISSIONS', res.permissions) } else { commit('SET_ROLES', ['ROLE_DEFAULT']) } commit('SET_NAME', user.userName) commit('SET_AVATAR', avatar) resolve(res) }).catch(error => { reject(error) }) }) }, // 退出系统 LogOut({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('SET_TOKEN', '') commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) removeToken() resolve() }).catch(error => { reject(error) }) }) }, // 前端 登出 FedLogOut({ commit }) { return new Promise(resolve => { commit('SET_TOKEN', '') removeToken() resolve() }) } } } export default user ================================================ FILE: vue_campus_admin/src/utils/auth.js ================================================ import Cookies from 'js-cookie' const TokenKey = 'Admin-Token' export function getToken() { return Cookies.get(TokenKey) } export function setToken(token) { return Cookies.set(TokenKey, token) } export function removeToken() { return Cookies.remove(TokenKey) } ================================================ FILE: vue_campus_admin/src/utils/crypto.js ================================================ //crypto.js文件内容 import CryptoJS from 'crypto-js' export default { // 加密 /** * @description: 加密 * @param {*} word * @param {*} keyStr */ set(word, keyStr) { keyStr = keyStr || 'abcdef0123456789' // 16位的密钥,自己定义,和下面的密钥要相同 var srcs = CryptoJS.enc.Utf8.parse(word) // 字符串到数组转换,解析明文 var key = CryptoJS.enc.Utf8.parse(keyStr) // 字符串到数组转换,解析秘钥 // mode:加密方式;padding:填充方式;iv便宜向量(可选) var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) return encrypted.toString() // 加密后的结果是对象,要转换为文本 }, /** * @description: 解密 * @param {*} word * @param {*} keyStr */ get(word, keyStr) { keyStr = keyStr || 'abcdef0123456789' var key = CryptoJS.enc.Utf8.parse(keyStr) // 字符串到数组转换 var decrypt = CryptoJS.AES.decrypt(word, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }) return CryptoJS.enc.Utf8.stringify(decrypt).toString() // 数组到字符串转换 } } ================================================ FILE: vue_campus_admin/src/utils/dict/Dict.js ================================================ import Vue from 'vue' import { mergeRecursive } from "@/utils/ruoyi"; import DictMeta from './DictMeta' import DictData from './DictData' const DEFAULT_DICT_OPTIONS = { types: [], } /** * @classdesc 字典 * @property {Object} label 标签对象,内部属性名为字典类型名称 * @property {Object} dict 字段数组,内部属性名为字典类型名称 * @property {Array.} _dictMetas 字典元数据数组 */ export default class Dict { constructor() { this.owner = null this.label = {} this.type = {} } init(options) { if (options instanceof Array) { options = { types: options } } const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options) if (opts.types === undefined) { throw new Error('need dict types') } const ps = [] this._dictMetas = opts.types.map(t => DictMeta.parse(t)) this._dictMetas.forEach(dictMeta => { const type = dictMeta.type Vue.set(this.label, type, {}) Vue.set(this.type, type, []) if (dictMeta.lazy) { return } ps.push(loadDict(this, dictMeta)) }) return Promise.all(ps) } /** * 重新加载字典 * @param {String} type 字典类型 */ reloadDict(type) { const dictMeta = this._dictMetas.find(e => e.type === type) if (dictMeta === undefined) { return Promise.reject(`the dict meta of ${type} was not found`) } return loadDict(this, dictMeta) } } /** * 加载字典 * @param {Dict} dict 字典 * @param {DictMeta} dictMeta 字典元数据 * @returns {Promise} */ function loadDict(dict, dictMeta) { return dictMeta.request(dictMeta) .then(response => { const type = dictMeta.type let dicts = dictMeta.responseConverter(response, dictMeta) if (!(dicts instanceof Array)) { console.error('the return of responseConverter must be Array.') dicts = [] } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) { console.error('the type of elements in dicts must be DictData') dicts = [] } dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts) dicts.forEach(d => { Vue.set(dict.label[type], d.value, d.label) }) return dicts }) } ================================================ FILE: vue_campus_admin/src/utils/dict/DictConverter.js ================================================ import DictOptions from './DictOptions' import DictData from './DictData' export default function(dict, dictMeta) { const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS) const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS) return new DictData(dict[label], dict[value], dict) } /** * 确定字典字段 * @param {DictData} dict * @param {...String} fields */ function determineDictField(dict, ...fields) { return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f)) } ================================================ FILE: vue_campus_admin/src/utils/dict/DictData.js ================================================ /** * @classdesc 字典数据 * @property {String} label 标签 * @property {*} value 标签 * @property {Object} raw 原始数据 */ export default class DictData { constructor(label, value, raw) { this.label = label this.value = value this.raw = raw } } ================================================ FILE: vue_campus_admin/src/utils/dict/DictMeta.js ================================================ import { mergeRecursive } from "@/utils/ruoyi"; import DictOptions from './DictOptions' /** * @classdesc 字典元数据 * @property {String} type 类型 * @property {Function} request 请求 * @property {String} label 标签字段 * @property {String} value 值字段 */ export default class DictMeta { constructor(options) { this.type = options.type this.request = options.request this.responseConverter = options.responseConverter this.labelField = options.labelField this.valueField = options.valueField this.lazy = options.lazy === true } } /** * 解析字典元数据 * @param {Object} options * @returns {DictMeta} */ DictMeta.parse= function(options) { let opts = null if (typeof options === 'string') { opts = DictOptions.metas[options] || {} opts.type = options } else if (typeof options === 'object') { opts = options } opts = mergeRecursive(DictOptions.metas['*'], opts) return new DictMeta(opts) } ================================================ FILE: vue_campus_admin/src/utils/dict/DictOptions.js ================================================ import { mergeRecursive } from "@/utils/ruoyi"; import dictConverter from './DictConverter' export const options = { metas: { '*': { /** * 字典请求,方法签名为function(dictMeta: DictMeta): Promise */ request: (dictMeta) => { console.log(`load dict ${dictMeta.type}`) return Promise.resolve([]) }, /** * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData */ responseConverter, labelField: 'label', valueField: 'value', }, }, /** * 默认标签字段 */ DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'], /** * 默认值字段 */ DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'], } /** * 映射字典 * @param {Object} response 字典数据 * @param {DictMeta} dictMeta 字典元数据 * @returns {DictData} */ function responseConverter(response, dictMeta) { const dicts = response.content instanceof Array ? response.content : response if (dicts === undefined) { console.warn(`no dict data of "${dictMeta.type}" found in the response`) return [] } return dicts.map(d => dictConverter(d, dictMeta)) } export function mergeOptions(src) { mergeRecursive(options, src) } export default options ================================================ FILE: vue_campus_admin/src/utils/dict/index.js ================================================ import Dict from './Dict' import { mergeOptions } from './DictOptions' export default function(Vue, options) { mergeOptions(options) Vue.mixin({ data() { if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) { return {} } const dict = new Dict() dict.owner = this return { dict } }, created() { if (!(this.dict instanceof Dict)) { return } options.onCreated && options.onCreated(this.dict) this.dict.init(this.$options.dicts).then(() => { options.onReady && options.onReady(this.dict) this.$nextTick(() => { this.$emit('dictReady', this.dict) if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) { this.$options.methods.onDictReady.call(this, this.dict) } }) }) }, }) } ================================================ FILE: vue_campus_admin/src/utils/errorCode.js ================================================ export default { '401': '认证失败,无法访问系统资源', '403': '当前操作没有权限', '404': '访问资源不存在', 'default': '系统未知错误,请反馈给管理员' } ================================================ FILE: vue_campus_admin/src/utils/generator/config.js ================================================ export const formConf = { formRef: 'elForm', formModel: 'formData', size: 'medium', labelPosition: 'right', labelWidth: 100, formRules: 'rules', gutter: 15, disabled: false, span: 24, formBtns: true } export const inputComponents = [ { label: '单行文本', tag: 'el-input', tagIcon: 'input', placeholder: '请输入', defaultValue: undefined, span: 24, labelWidth: null, style: { width: '100%' }, clearable: true, prepend: '', append: '', 'prefix-icon': '', 'suffix-icon': '', maxlength: null, 'show-word-limit': false, readonly: false, disabled: false, required: true, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/input' }, { label: '多行文本', tag: 'el-input', tagIcon: 'textarea', type: 'textarea', placeholder: '请输入', defaultValue: undefined, span: 24, labelWidth: null, autosize: { minRows: 4, maxRows: 4 }, style: { width: '100%' }, maxlength: null, 'show-word-limit': false, readonly: false, disabled: false, required: true, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/input' }, { label: '密码', tag: 'el-input', tagIcon: 'password', placeholder: '请输入', defaultValue: undefined, span: 24, 'show-password': true, labelWidth: null, style: { width: '100%' }, clearable: true, prepend: '', append: '', 'prefix-icon': '', 'suffix-icon': '', maxlength: null, 'show-word-limit': false, readonly: false, disabled: false, required: true, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/input' }, { label: '计数器', tag: 'el-input-number', tagIcon: 'number', placeholder: '', defaultValue: undefined, span: 24, labelWidth: null, min: undefined, max: undefined, step: undefined, 'step-strictly': false, precision: undefined, 'controls-position': '', disabled: false, required: true, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/input-number' } ] export const selectComponents = [ { label: '下拉选择', tag: 'el-select', tagIcon: 'select', placeholder: '请选择', defaultValue: undefined, span: 24, labelWidth: null, style: { width: '100%' }, clearable: true, disabled: false, required: true, filterable: false, multiple: false, options: [{ label: '选项一', value: 1 }, { label: '选项二', value: 2 }], regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/select' }, { label: '级联选择', tag: 'el-cascader', tagIcon: 'cascader', placeholder: '请选择', defaultValue: [], span: 24, labelWidth: null, style: { width: '100%' }, props: { props: { multiple: false } }, 'show-all-levels': true, disabled: false, clearable: true, filterable: false, required: true, options: [{ id: 1, value: 1, label: '选项1', children: [{ id: 2, value: 2, label: '选项1-1' }] }], dataType: 'dynamic', labelKey: 'label', valueKey: 'value', childrenKey: 'children', separator: '/', regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/cascader' }, { label: '单选框组', tag: 'el-radio-group', tagIcon: 'radio', defaultValue: undefined, span: 24, labelWidth: null, style: {}, optionType: 'default', border: false, size: 'medium', disabled: false, required: true, options: [{ label: '选项一', value: 1 }, { label: '选项二', value: 2 }], regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/radio' }, { label: '多选框组', tag: 'el-checkbox-group', tagIcon: 'checkbox', defaultValue: [], span: 24, labelWidth: null, style: {}, optionType: 'default', border: false, size: 'medium', disabled: false, required: true, options: [{ label: '选项一', value: 1 }, { label: '选项二', value: 2 }], regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/checkbox' }, { label: '开关', tag: 'el-switch', tagIcon: 'switch', defaultValue: false, span: 24, labelWidth: null, style: {}, disabled: false, required: true, 'active-text': '', 'inactive-text': '', 'active-color': null, 'inactive-color': null, 'active-value': true, 'inactive-value': false, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/switch' }, { label: '滑块', tag: 'el-slider', tagIcon: 'slider', defaultValue: null, span: 24, labelWidth: null, disabled: false, required: true, min: 0, max: 100, step: 1, 'show-stops': false, range: false, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/slider' }, { label: '时间选择', tag: 'el-time-picker', tagIcon: 'time', placeholder: '请选择', defaultValue: null, span: 24, labelWidth: null, style: { width: '100%' }, disabled: false, clearable: true, required: true, 'picker-options': { selectableRange: '00:00:00-23:59:59' }, format: 'HH:mm:ss', 'value-format': 'HH:mm:ss', regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/time-picker' }, { label: '时间范围', tag: 'el-time-picker', tagIcon: 'time-range', defaultValue: null, span: 24, labelWidth: null, style: { width: '100%' }, disabled: false, clearable: true, required: true, 'is-range': true, 'range-separator': '至', 'start-placeholder': '开始时间', 'end-placeholder': '结束时间', format: 'HH:mm:ss', 'value-format': 'HH:mm:ss', regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/time-picker' }, { label: '日期选择', tag: 'el-date-picker', tagIcon: 'date', placeholder: '请选择', defaultValue: null, type: 'date', span: 24, labelWidth: null, style: { width: '100%' }, disabled: false, clearable: true, required: true, format: 'yyyy-MM-dd', 'value-format': 'yyyy-MM-dd', readonly: false, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/date-picker' }, { label: '日期范围', tag: 'el-date-picker', tagIcon: 'date-range', defaultValue: null, span: 24, labelWidth: null, style: { width: '100%' }, type: 'daterange', 'range-separator': '至', 'start-placeholder': '开始日期', 'end-placeholder': '结束日期', disabled: false, clearable: true, required: true, format: 'yyyy-MM-dd', 'value-format': 'yyyy-MM-dd', readonly: false, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/date-picker' }, { label: '评分', tag: 'el-rate', tagIcon: 'rate', defaultValue: 0, span: 24, labelWidth: null, style: {}, max: 5, 'allow-half': false, 'show-text': false, 'show-score': false, disabled: false, required: true, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/rate' }, { label: '颜色选择', tag: 'el-color-picker', tagIcon: 'color', defaultValue: null, labelWidth: null, 'show-alpha': false, 'color-format': '', disabled: false, required: true, size: 'medium', regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/color-picker' }, { label: '上传', tag: 'el-upload', tagIcon: 'upload', action: 'https://jsonplaceholder.typicode.com/posts/', defaultValue: null, labelWidth: null, disabled: false, required: true, accept: '', name: 'file', 'auto-upload': true, showTip: false, buttonText: '点击上传', fileSize: 2, sizeUnit: 'MB', 'list-type': 'text', multiple: false, regList: [], changeTag: true, document: 'https://element.eleme.cn/#/zh-CN/component/upload' } ] export const layoutComponents = [ { layout: 'rowFormItem', tagIcon: 'row', type: 'default', justify: 'start', align: 'top', label: '行容器', layoutTree: true, children: [], document: 'https://element.eleme.cn/#/zh-CN/component/layout' }, { layout: 'colFormItem', label: '按钮', changeTag: true, labelWidth: null, tag: 'el-button', tagIcon: 'button', span: 24, default: '主要按钮', type: 'primary', icon: 'el-icon-search', size: 'medium', disabled: false, document: 'https://element.eleme.cn/#/zh-CN/component/button' } ] // 组件rule的触发方式,无触发方式的组件不生成rule export const trigger = { 'el-input': 'blur', 'el-input-number': 'blur', 'el-select': 'change', 'el-radio-group': 'change', 'el-checkbox-group': 'change', 'el-cascader': 'change', 'el-time-picker': 'change', 'el-date-picker': 'change', 'el-rate': 'change' } ================================================ FILE: vue_campus_admin/src/utils/generator/css.js ================================================ const styles = { 'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}', 'el-upload': '.el-upload__tip{line-height: 1.2;}' } function addCss(cssList, el) { const css = styles[el.tag] css && cssList.indexOf(css) === -1 && cssList.push(css) if (el.children) { el.children.forEach(el2 => addCss(cssList, el2)) } } export function makeUpCss(conf) { const cssList = [] conf.fields.forEach(el => addCss(cssList, el)) return cssList.join('\n') } ================================================ FILE: vue_campus_admin/src/utils/generator/drawingDefault.js ================================================ export default [ { layout: 'colFormItem', tagIcon: 'input', label: '手机号', vModel: 'mobile', formId: 6, tag: 'el-input', placeholder: '请输入手机号', defaultValue: '', span: 24, style: { width: '100%' }, clearable: true, prepend: '', append: '', 'prefix-icon': 'el-icon-mobile', 'suffix-icon': '', maxlength: 11, 'show-word-limit': true, readonly: false, disabled: false, required: true, changeTag: true, regList: [{ pattern: '/^1(3|4|5|7|8|9)\\d{9}$/', message: '手机号格式错误' }] } ] ================================================ FILE: vue_campus_admin/src/utils/generator/html.js ================================================ /* eslint-disable max-len */ import { trigger } from './config' let confGlobal let someSpanIsNot24 export function dialogWrapper(str) { return ` ${str}
取消 确定
` } export function vueTemplate(str) { return `` } export function vueScript(str) { return `` } export function cssStyle(cssStr) { return `` } function buildFormTemplate(conf, child, type) { let labelPosition = '' if (conf.labelPosition !== 'right') { labelPosition = `label-position="${conf.labelPosition}"` } const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : '' let str = ` ${child} ${buildFromBtns(conf, type)} ` if (someSpanIsNot24) { str = ` ${str} ` } return str } function buildFromBtns(conf, type) { let str = '' if (conf.formBtns && type === 'file') { str = ` 提交 重置 ` if (someSpanIsNot24) { str = ` ${str} ` } } return str } // span不为24的用el-col包裹 function colWrapper(element, str) { if (someSpanIsNot24 || element.span !== 24) { return ` ${str} ` } return str } const layouts = { colFormItem(element) { let labelWidth = '' if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) { labelWidth = `label-width="${element.labelWidth}px"` } const required = !trigger[element.tag] && element.required ? 'required' : '' const tagDom = tags[element.tag] ? tags[element.tag](element) : null let str = ` ${tagDom} ` str = colWrapper(element, str) return str }, rowFormItem(element) { const type = element.type === 'default' ? '' : `type="${element.type}"` const justify = element.type === 'default' ? '' : `justify="${element.justify}"` const align = element.type === 'default' ? '' : `align="${element.align}"` const gutter = element.gutter ? `gutter="${element.gutter}"` : '' const children = element.children.map(el => layouts[el.layout](el)) let str = ` ${children.join('\n')} ` str = colWrapper(element, str) return str } } const tags = { 'el-button': el => { const { tag, disabled } = attrBuilder(el) const type = el.type ? `type="${el.type}"` : '' const icon = el.icon ? `icon="${el.icon}"` : '' const size = el.size ? `size="${el.size}"` : '' let child = buildElButtonChild(el) if (child) child = `\n${child}\n` // 换行 return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}` }, 'el-input': el => { const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el) const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : '' const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : '' const readonly = el.readonly ? 'readonly' : '' const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : '' const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : '' const showPassword = el['show-password'] ? 'show-password' : '' const type = el.type ? `type="${el.type}"` : '' const autosize = el.autosize && el.autosize.minRows ? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"` : '' let child = buildElInputChild(el) if (child) child = `\n${child}\n` // 换行 return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}` }, 'el-input-number': el => { const { disabled, vModel, placeholder } = attrBuilder(el) const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : '' const min = el.min ? `:min='${el.min}'` : '' const max = el.max ? `:max='${el.max}'` : '' const step = el.step ? `:step='${el.step}'` : '' const stepStrictly = el['step-strictly'] ? 'step-strictly' : '' const precision = el.precision ? `:precision='${el.precision}'` : '' return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}>` }, 'el-select': el => { const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el) const filterable = el.filterable ? 'filterable' : '' const multiple = el.multiple ? 'multiple' : '' let child = buildElSelectChild(el) if (child) child = `\n${child}\n` // 换行 return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}` }, 'el-radio-group': el => { const { disabled, vModel } = attrBuilder(el) const size = `size="${el.size}"` let child = buildElRadioGroupChild(el) if (child) child = `\n${child}\n` // 换行 return `<${el.tag} ${vModel} ${size} ${disabled}>${child}` }, 'el-checkbox-group': el => { const { disabled, vModel } = attrBuilder(el) const size = `size="${el.size}"` const min = el.min ? `:min="${el.min}"` : '' const max = el.max ? `:max="${el.max}"` : '' let child = buildElCheckboxGroupChild(el) if (child) child = `\n${child}\n` // 换行 return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}` }, 'el-switch': el => { const { disabled, vModel } = attrBuilder(el) const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : '' const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : '' const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : '' const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : '' const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : '' const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : '' return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}>` }, 'el-cascader': el => { const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el) const options = el.options ? `:options="${el.vModel}Options"` : '' const props = el.props ? `:props="${el.vModel}Props"` : '' const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"' const filterable = el.filterable ? 'filterable' : '' const separator = el.separator === '/' ? '' : `separator="${el.separator}"` return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}>` }, 'el-slider': el => { const { disabled, vModel } = attrBuilder(el) const min = el.min ? `:min='${el.min}'` : '' const max = el.max ? `:max='${el.max}'` : '' const step = el.step ? `:step='${el.step}'` : '' const range = el.range ? 'range' : '' const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : '' return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}>` }, 'el-time-picker': el => { const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el) const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' const isRange = el['is-range'] ? 'is-range' : '' const format = el.format ? `format="${el.format}"` : '' const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : '' return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}>` }, 'el-date-picker': el => { const { disabled, vModel, clearable, placeholder, width } = attrBuilder(el) const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' const format = el.format ? `format="${el.format}"` : '' const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' const type = el.type === 'date' ? '' : `type="${el.type}"` const readonly = el.readonly ? 'readonly' : '' return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}>` }, 'el-rate': el => { const { disabled, vModel } = attrBuilder(el) const max = el.max ? `:max='${el.max}'` : '' const allowHalf = el['allow-half'] ? 'allow-half' : '' const showText = el['show-text'] ? 'show-text' : '' const showScore = el['show-score'] ? 'show-score' : '' return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}>` }, 'el-color-picker': el => { const { disabled, vModel } = attrBuilder(el) const size = `size="${el.size}"` const showAlpha = el['show-alpha'] ? 'show-alpha' : '' const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : '' return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}>` }, 'el-upload': el => { const disabled = el.disabled ? ':disabled=\'true\'' : '' const action = el.action ? `:action="${el.vModel}Action"` : '' const multiple = el.multiple ? 'multiple' : '' const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : '' const accept = el.accept ? `accept="${el.accept}"` : '' const name = el.name !== 'file' ? `name="${el.name}"` : '' const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : '' const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"` const fileList = `:file-list="${el.vModel}fileList"` const ref = `ref="${el.vModel}"` let child = buildElUploadChild(el) if (child) child = `\n${child}\n` // 换行 return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}` } } function attrBuilder(el) { return { vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`, clearable: el.clearable ? 'clearable' : '', placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '', width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '', disabled: el.disabled ? ':disabled=\'true\'' : '' } } // el-buttin 子级 function buildElButtonChild(conf) { const children = [] if (conf.default) { children.push(conf.default) } return children.join('\n') } // el-input innerHTML function buildElInputChild(conf) { const children = [] if (conf.prepend) { children.push(``) } if (conf.append) { children.push(``) } return children.join('\n') } function buildElSelectChild(conf) { const children = [] if (conf.options && conf.options.length) { children.push(``) } return children.join('\n') } function buildElRadioGroupChild(conf) { const children = [] if (conf.options && conf.options.length) { const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio' const border = conf.border ? 'border' : '' children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}`) } return children.join('\n') } function buildElCheckboxGroupChild(conf) { const children = [] if (conf.options && conf.options.length) { const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox' const border = conf.border ? 'border' : '' children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :disabled="item.disabled" ${border}>{{item.label}}`) } return children.join('\n') } function buildElUploadChild(conf) { const list = [] if (conf['list-type'] === 'picture-card') list.push('') else list.push(`${conf.buttonText}`) if (conf.showTip) list.push(`
只能上传不超过 ${conf.fileSize}${conf.sizeUnit} 的${conf.accept}文件
`) return list.join('\n') } export function makeUpHtml(conf, type) { const htmlList = [] confGlobal = conf someSpanIsNot24 = conf.fields.some(item => item.span !== 24) conf.fields.forEach(el => { htmlList.push(layouts[el.layout](el)) }) const htmlStr = htmlList.join('\n') let temp = buildFormTemplate(conf, htmlStr, type) if (type === 'dialog') { temp = dialogWrapper(temp) } confGlobal = null return temp } ================================================ FILE: vue_campus_admin/src/utils/generator/icon.json ================================================ ["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"] ================================================ FILE: vue_campus_admin/src/utils/generator/js.js ================================================ import { isArray } from 'util' import { exportDefault, titleCase } from '@/utils/index' import { trigger } from './config' const units = { KB: '1024', MB: '1024 / 1024', GB: '1024 / 1024 / 1024' } let confGlobal const inheritAttrs = { file: '', dialog: 'inheritAttrs: false,' } export function makeUpJs(conf, type) { confGlobal = conf = JSON.parse(JSON.stringify(conf)) const dataList = [] const ruleList = [] const optionsList = [] const propsList = [] const methodList = mixinMethod(type) const uploadVarList = [] conf.fields.forEach(el => { buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) }) const script = buildexport( conf, type, dataList.join('\n'), ruleList.join('\n'), optionsList.join('\n'), uploadVarList.join('\n'), propsList.join('\n'), methodList.join('\n') ) confGlobal = null return script } function buildAttributes(el, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) { buildData(el, dataList) buildRules(el, ruleList) if (el.options && el.options.length) { buildOptions(el, optionsList) if (el.dataType === 'dynamic') { const model = `${el.vModel}Options` const options = titleCase(model) buildOptionMethod(`get${options}`, model, methodList) } } if (el.props && el.props.props) { buildProps(el, propsList) } if (el.action && el.tag === 'el-upload') { uploadVarList.push( `${el.vModel}Action: '${el.action}', ${el.vModel}fileList: [],` ) methodList.push(buildBeforeUpload(el)) if (!el['auto-upload']) { methodList.push(buildSubmitUpload(el)) } } if (el.children) { el.children.forEach(el2 => { buildAttributes(el2, dataList, ruleList, optionsList, methodList, propsList, uploadVarList) }) } } function mixinMethod(type) { const list = []; const minxins = { file: confGlobal.formBtns ? { submitForm: `submitForm() { this.$refs['${confGlobal.formRef}'].validate(valid => { if(!valid) return // TODO 提交表单 }) },`, resetForm: `resetForm() { this.$refs['${confGlobal.formRef}'].resetFields() },` } : null, dialog: { onOpen: 'onOpen() {},', onClose: `onClose() { this.$refs['${confGlobal.formRef}'].resetFields() },`, close: `close() { this.$emit('update:visible', false) },`, handleConfirm: `handleConfirm() { this.$refs['${confGlobal.formRef}'].validate(valid => { if(!valid) return this.close() }) },` } } const methods = minxins[type] if (methods) { Object.keys(methods).forEach(key => { list.push(methods[key]) }) } return list } function buildData(conf, dataList) { if (conf.vModel === undefined) return let defaultValue if (typeof (conf.defaultValue) === 'string' && !conf.multiple) { defaultValue = `'${conf.defaultValue}'` } else { defaultValue = `${JSON.stringify(conf.defaultValue)}` } dataList.push(`${conf.vModel}: ${defaultValue},`) } function buildRules(conf, ruleList) { if (conf.vModel === undefined) return const rules = [] if (trigger[conf.tag]) { if (conf.required) { const type = isArray(conf.defaultValue) ? 'type: \'array\',' : '' let message = isArray(conf.defaultValue) ? `请至少选择一个${conf.vModel}` : conf.placeholder if (message === undefined) message = `${conf.label}不能为空` rules.push(`{ required: true, ${type} message: '${message}', trigger: '${trigger[conf.tag]}' }`) } if (conf.regList && isArray(conf.regList)) { conf.regList.forEach(item => { if (item.pattern) { rules.push(`{ pattern: ${eval(item.pattern)}, message: '${item.message}', trigger: '${trigger[conf.tag]}' }`) } }) } ruleList.push(`${conf.vModel}: [${rules.join(',')}],`) } } function buildOptions(conf, optionsList) { if (conf.vModel === undefined) return if (conf.dataType === 'dynamic') { conf.options = [] } const str = `${conf.vModel}Options: ${JSON.stringify(conf.options)},` optionsList.push(str) } function buildProps(conf, propsList) { if (conf.dataType === 'dynamic') { conf.valueKey !== 'value' && (conf.props.props.value = conf.valueKey) conf.labelKey !== 'label' && (conf.props.props.label = conf.labelKey) conf.childrenKey !== 'children' && (conf.props.props.children = conf.childrenKey) } const str = `${conf.vModel}Props: ${JSON.stringify(conf.props.props)},` propsList.push(str) } function buildBeforeUpload(conf) { const unitNum = units[conf.sizeUnit]; let rightSizeCode = ''; let acceptCode = ''; const returnList = [] if (conf.fileSize) { rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize} if(!isRightSize){ this.$message.error('文件大小超过 ${conf.fileSize}${conf.sizeUnit}') }` returnList.push('isRightSize') } if (conf.accept) { acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type) if(!isAccept){ this.$message.error('应该选择${conf.accept}类型的文件') }` returnList.push('isAccept') } const str = `${conf.vModel}BeforeUpload(file) { ${rightSizeCode} ${acceptCode} return ${returnList.join('&&')} },` return returnList.length ? str : '' } function buildSubmitUpload(conf) { const str = `submitUpload() { this.$refs['${conf.vModel}'].submit() },` return str } function buildOptionMethod(methodName, model, methodList) { const str = `${methodName}() { // TODO 发起请求获取数据 this.${model} },` methodList.push(str) } function buildexport(conf, type, data, rules, selectOptions, uploadVar, props, methods) { const str = `${exportDefault}{ ${inheritAttrs[type]} components: {}, props: [], data () { return { ${conf.formModel}: { ${data} }, ${conf.formRules}: { ${rules} }, ${uploadVar} ${selectOptions} ${props} } }, computed: {}, watch: {}, created () {}, mounted () {}, methods: { ${methods} } }` return str } ================================================ FILE: vue_campus_admin/src/utils/generator/render.js ================================================ import { makeMap } from '@/utils/index' // 参考https://github.com/vuejs/vue/blob/v2.6.10/src/platforms/web/server/util.js const isAttr = makeMap( 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + 'target,title,type,usemap,value,width,wrap' ) function vModel(self, dataObject, defaultValue) { dataObject.props.value = defaultValue dataObject.on.input = val => { self.$emit('input', val) } } const componentChild = { 'el-button': { default(h, conf, key) { return conf[key] }, }, 'el-input': { prepend(h, conf, key) { return }, append(h, conf, key) { return } }, 'el-select': { options(h, conf, key) { const list = [] conf.options.forEach(item => { list.push() }) return list } }, 'el-radio-group': { options(h, conf, key) { const list = [] conf.options.forEach(item => { if (conf.optionType === 'button') list.push({item.label}) else list.push({item.label}) }) return list } }, 'el-checkbox-group': { options(h, conf, key) { const list = [] conf.options.forEach(item => { if (conf.optionType === 'button') { list.push({item.label}) } else { list.push({item.label}) } }) return list } }, 'el-upload': { 'list-type': (h, conf, key) => { const list = [] if (conf['list-type'] === 'picture-card') { list.push() } else { list.push({conf.buttonText}) } if (conf.showTip) { list.push(
只能上传不超过 {conf.fileSize}{conf.sizeUnit} 的{conf.accept}文件
) } return list } } } export default { render(h) { const dataObject = { attrs: {}, props: {}, on: {}, style: {} } const confClone = JSON.parse(JSON.stringify(this.conf)) const children = [] const childObjs = componentChild[confClone.tag] if (childObjs) { Object.keys(childObjs).forEach(key => { const childFunc = childObjs[key] if (confClone[key]) { children.push(childFunc(h, confClone, key)) } }) } Object.keys(confClone).forEach(key => { const val = confClone[key] if (key === 'vModel') { vModel(this, dataObject, confClone.defaultValue) } else if (dataObject[key]) { dataObject[key] = val } else if (!isAttr(key)) { dataObject.props[key] = val } else { dataObject.attrs[key] = val } }) return h(this.conf.tag, dataObject, children) }, props: ['conf'] } ================================================ FILE: vue_campus_admin/src/utils/index.js ================================================ import { parseTime } from './ruoyi' /** * 表格时间格式化 */ export function formatDate(cellValue) { if (cellValue == null || cellValue == "") return ""; var date = new Date(cellValue) var year = date.getFullYear() var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds } /** * @param {number} time * @param {string} option * @returns {string} */ export function formatTime(time, option) { if (('' + time).length === 10) { time = parseInt(time) * 1000 } else { time = +time } const d = new Date(time) const now = Date.now() const diff = (now - d) / 1000 if (diff < 30) { return '刚刚' } else if (diff < 3600) { // less 1 hour return Math.ceil(diff / 60) + '分钟前' } else if (diff < 3600 * 24) { return Math.ceil(diff / 3600) + '小时前' } else if (diff < 3600 * 24 * 2) { return '1天前' } if (option) { return parseTime(time, option) } else { return ( d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分' ) } } /** * @param {string} url * @returns {Object} */ export function getQueryObject(url) { url = url == null ? window.location.href : url const search = url.substring(url.lastIndexOf('?') + 1) const obj = {} const reg = /([^?&=]+)=([^?&=]*)/g search.replace(reg, (rs, $1, $2) => { const name = decodeURIComponent($1) let val = decodeURIComponent($2) val = String(val) obj[name] = val return rs }) return obj } /** * @param {string} input value * @returns {number} output value */ export function byteLength(str) { // returns the byte length of an utf8 string let s = str.length for (var i = str.length - 1; i >= 0; i--) { const code = str.charCodeAt(i) if (code > 0x7f && code <= 0x7ff) s++ else if (code > 0x7ff && code <= 0xffff) s += 2 if (code >= 0xDC00 && code <= 0xDFFF) i-- } return s } /** * @param {Array} actual * @returns {Array} */ export function cleanArray(actual) { const newArray = [] for (let i = 0; i < actual.length; i++) { if (actual[i]) { newArray.push(actual[i]) } } return newArray } /** * @param {Object} json * @returns {Array} */ export function param(json) { if (!json) return '' return cleanArray( Object.keys(json).map(key => { if (json[key] === undefined) return '' return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) }) ).join('&') } /** * @param {string} url * @returns {Object} */ export function param2Obj(url) { const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') if (!search) { return {} } const obj = {} const searchArr = search.split('&') searchArr.forEach(v => { const index = v.indexOf('=') if (index !== -1) { const name = v.substring(0, index) const val = v.substring(index + 1, v.length) obj[name] = val } }) return obj } /** * @param {string} val * @returns {string} */ export function html2Text(val) { const div = document.createElement('div') div.innerHTML = val return div.textContent || div.innerText } /** * Merges two objects, giving the last one precedence * @param {Object} target * @param {(Object|Array)} source * @returns {Object} */ export function objectMerge(target, source) { if (typeof target !== 'object') { target = {} } if (Array.isArray(source)) { return source.slice() } Object.keys(source).forEach(property => { const sourceProperty = source[property] if (typeof sourceProperty === 'object') { target[property] = objectMerge(target[property], sourceProperty) } else { target[property] = sourceProperty } }) return target } /** * @param {HTMLElement} element * @param {string} className */ export function toggleClass(element, className) { if (!element || !className) { return } let classString = element.className const nameIndex = classString.indexOf(className) if (nameIndex === -1) { classString += '' + className } else { classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length) } element.className = classString } /** * @param {string} type * @returns {Date} */ export function getTime(type) { if (type === 'start') { return new Date().getTime() - 3600 * 1000 * 24 * 90 } else { return new Date(new Date().toDateString()) } } /** * @param {Function} func * @param {number} wait * @param {boolean} immediate * @return {*} */ export function debounce(func, wait, immediate) { let timeout, args, context, timestamp, result const later = function() { // 据上一次触发时间间隔 const last = +new Date() - timestamp // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait if (last < wait && last > 0) { timeout = setTimeout(later, wait - last) } else { timeout = null // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 if (!immediate) { result = func.apply(context, args) if (!timeout) context = args = null } } } return function(...args) { context = this timestamp = +new Date() const callNow = immediate && !timeout // 如果延时不存在,重新设定延时 if (!timeout) timeout = setTimeout(later, wait) if (callNow) { result = func.apply(context, args) context = args = null } return result } } /** * This is just a simple version of deep copy * Has a lot of edge cases bug * If you want to use a perfect deep copy, use lodash's _.cloneDeep * @param {Object} source * @returns {Object} */ export function deepClone(source) { if (!source && typeof source !== 'object') { throw new Error('error arguments', 'deepClone') } const targetObj = source.constructor === Array ? [] : {} Object.keys(source).forEach(keys => { if (source[keys] && typeof source[keys] === 'object') { targetObj[keys] = deepClone(source[keys]) } else { targetObj[keys] = source[keys] } }) return targetObj } /** * @param {Array} arr * @returns {Array} */ export function uniqueArr(arr) { return Array.from(new Set(arr)) } /** * @returns {string} */ export function createUniqueString() { const timestamp = +new Date() + '' const randomNum = parseInt((1 + Math.random()) * 65536) + '' return (+(randomNum + timestamp)).toString(32) } /** * Check if an element has a class * @param {HTMLElement} elm * @param {string} cls * @returns {boolean} */ export function hasClass(ele, cls) { return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) } /** * Add class to element * @param {HTMLElement} elm * @param {string} cls */ export function addClass(ele, cls) { if (!hasClass(ele, cls)) ele.className += ' ' + cls } /** * Remove class from element * @param {HTMLElement} elm * @param {string} cls */ export function removeClass(ele, cls) { if (hasClass(ele, cls)) { const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') ele.className = ele.className.replace(reg, ' ') } } export function makeMap(str, expectsLowerCase) { const map = Object.create(null) const list = str.split(',') for (let i = 0; i < list.length; i++) { map[list[i]] = true } return expectsLowerCase ? val => map[val.toLowerCase()] : val => map[val] } export const exportDefault = 'export default ' export const beautifierConf = { html: { indent_size: '2', indent_char: ' ', max_preserve_newlines: '-1', preserve_newlines: false, keep_array_indentation: false, break_chained_methods: false, indent_scripts: 'separate', brace_style: 'end-expand', space_before_conditional: true, unescape_strings: false, jslint_happy: false, end_with_newline: true, wrap_line_length: '110', indent_inner_html: true, comma_first: false, e4x: true, indent_empty_lines: true }, js: { indent_size: '2', indent_char: ' ', max_preserve_newlines: '-1', preserve_newlines: false, keep_array_indentation: false, break_chained_methods: false, indent_scripts: 'normal', brace_style: 'end-expand', space_before_conditional: true, unescape_strings: false, jslint_happy: true, end_with_newline: true, wrap_line_length: '110', indent_inner_html: true, comma_first: false, e4x: true, indent_empty_lines: true } } // 首字母大小 export function titleCase(str) { return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) } // 下划转驼峰 export function camelCase(str) { return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) } export function isNumberStr(str) { return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) } ================================================ FILE: vue_campus_admin/src/utils/jsencrypt.js ================================================ import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' // 密钥对生成 http://web.chacuo.net/netrsakeypair const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' + 'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' + '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' + 'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' + 'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' + 'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' + 'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' + 'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' + 'UP8iWi1Qw0Y=' // 加密 export function encrypt(txt) { const encryptor = new JSEncrypt() encryptor.setPublicKey(publicKey) // 设置公钥 return encryptor.encrypt(txt) // 对数据进行加密 } // 解密 export function decrypt(txt) { const encryptor = new JSEncrypt() encryptor.setPrivateKey(privateKey) // 设置私钥 return encryptor.decrypt(txt) // 对数据进行解密 } ================================================ FILE: vue_campus_admin/src/utils/permission.js ================================================ import store from '@/store' /** * 字符权限校验 * @param {Array} value 校验值 * @returns {Boolean} */ export function checkPermi(value) { if (value && value instanceof Array && value.length > 0) { const permissions = store.getters && store.getters.permissions const permissionDatas = value const all_permission = "*:*:*"; const hasPermission = permissions.some(permission => { return all_permission === permission || permissionDatas.includes(permission) }) if (!hasPermission) { return false } return true } else { console.error(`need roles! Like checkPermi="['system:user:add','system:user:edit']"`) return false } } /** * 角色权限校验 * @param {Array} value 校验值 * @returns {Boolean} */ export function checkRole(value) { if (value && value instanceof Array && value.length > 0) { const roles = store.getters && store.getters.roles const permissionRoles = value const super_admin = "admin"; const hasRole = roles.some(role => { return super_admin === role || permissionRoles.includes(role) }) if (!hasRole) { return false } return true } else { console.error(`need roles! Like checkRole="['admin','editor']"`) return false } } ================================================ FILE: vue_campus_admin/src/utils/request.js ================================================ import axios from 'axios' import { Notification, MessageBox, Message, Loading } from 'element-ui' import store from '@/store' import { getToken } from '@/utils/auth' import errorCode from '@/utils/errorCode' import { tansParams, blobValidate } from "@/utils/ruoyi"; import cache from '@/plugins/cache' import { saveAs } from 'file-saver' let downloadLoadingInstance; // 是否显示重新登录 export let isRelogin = { show: false }; axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' // 创建axios实例 const service = axios.create({ // axios中请求配置有baseURL选项,表示请求URL公共部分 baseURL: process.env.VUE_APP_BASE_API, // 超时 timeout: 20000 }) // request拦截器 service.interceptors.request.use(config => { // 是否需要设置 token const isToken = (config.headers || {}).isToken === false // 是否需要防止数据重复提交 const isRepeatSubmit = (config.headers || {}).repeatSubmit === false if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } // get请求映射params参数 if (config.method === 'get' && config.params) { let url = config.url + '?' + tansParams(config.params); url = url.slice(0, -1); config.params = {}; config.url = url; } if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { const requestObj = { url: config.url, data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, time: new Date().getTime() } const sessionObj = cache.session.getJSON('sessionObj') if (sessionObj === undefined || sessionObj === null || sessionObj === '') { cache.session.setJSON('sessionObj', requestObj) } else { const s_url = sessionObj.url; // 请求地址 const s_data = sessionObj.data; // 请求数据 const s_time = sessionObj.time; // 请求时间 const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { const message = '数据正在处理,请勿重复提交'; console.warn(`[${s_url}]: ` + message) return Promise.reject(new Error(message)) } else { cache.session.setJSON('sessionObj', requestObj) } } } return config }, error => { console.log(error) Promise.reject(error) }) // 响应拦截器 service.interceptors.response.use(res => { // 未设置状态码则默认成功状态 const code = res.data.code || 200; // 获取错误信息 const msg = errorCode[code] || res.data.msg || errorCode['default'] // 二进制数据则直接返回 if(res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer'){ return res.data } if (code === 401) { if (!isRelogin.show) { isRelogin.show = true; MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' } ).then(() => { isRelogin.show = false; store.dispatch('LogOut').then(() => { location.href = '/index'; }) }).catch(() => { isRelogin.show = false; }); } return Promise.reject('无效的会话,或者会话已过期,请重新登录。') } else if (code === 500) { Message({ message: msg, type: 'error' }) return Promise.reject(new Error(msg)) } else if (code !== 200) { Notification.error({ title: msg }) return Promise.reject('error') } else { return res.data } }, error => { console.log('err' + error) let { message } = error; if (message == "Network Error") { message = "后端接口连接异常"; } else if (message.includes("timeout")) { message = "系统接口请求超时"; } else if (message.includes("Request failed with status code")) { message = "系统接口" + message.substr(message.length - 3) + "异常"; } Message({ message: message, type: 'error', duration: 5 * 1000 }) return Promise.reject(error) } ) // 通用下载方法 export function download(url, params, filename, config) { downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) return service.post(url, params, { transformRequest: [(params) => { return tansParams(params) }], headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, responseType: 'blob', ...config }).then(async (data) => { const isLogin = await blobValidate(data); if (isLogin) { const blob = new Blob([data]) saveAs(blob, filename) } else { const resText = await data.text(); const rspObj = JSON.parse(resText); const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] Message.error(errMsg); } downloadLoadingInstance.close(); }).catch((r) => { console.error(r) Message.error('下载文件出现错误,请联系管理员!') downloadLoadingInstance.close(); }) } export default service ================================================ FILE: vue_campus_admin/src/utils/ruoyi.js ================================================ /** * 通用js方法封装处理 * Copyright (c) 2019 ruoyi */ // 日期格式化 export function parseTime(time, pattern) { if (arguments.length === 0 || !time) { return null } const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' let date if (typeof time === 'object') { date = time } else { if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { time = parseInt(time) } else if (typeof time === 'string') { time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); } if ((typeof time === 'number') && (time.toString().length === 10)) { time = time * 1000 } date = new Date(time) } const formatObj = { y: date.getFullYear(), m: date.getMonth() + 1, d: date.getDate(), h: date.getHours(), i: date.getMinutes(), s: date.getSeconds(), a: date.getDay() } const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { let value = formatObj[key] // Note: getDay() returns 0 on Sunday if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } if (result.length > 0 && value < 10) { value = '0' + value } return value || 0 }) return time_str } // 表单重置 export function resetForm(refName) { if (this.$refs[refName]) { this.$refs[refName].resetFields(); } } // 添加日期范围 export function addDateRange(params, dateRange, propName) { let search = params; search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; dateRange = Array.isArray(dateRange) ? dateRange : []; if (typeof (propName) === 'undefined') { search.params['beginTime'] = dateRange[0]; search.params['endTime'] = dateRange[1]; } else { search.params['begin' + propName] = dateRange[0]; search.params['end' + propName] = dateRange[1]; } return search; } // 回显数据字典 export function selectDictLabel(datas, value) { if (value === undefined) { return ""; } var actions = []; Object.keys(datas).some((key) => { if (datas[key].value == ('' + value)) { actions.push(datas[key].label); return true; } }) if (actions.length === 0) { actions.push(value); } return actions.join(''); } // 回显数据字典(字符串数组) export function selectDictLabels(datas, value, separator) { if (value === undefined) { return ""; } var actions = []; var currentSeparator = undefined === separator ? "," : separator; var temp = value.split(currentSeparator); Object.keys(value.split(currentSeparator)).some((val) => { var match = false; Object.keys(datas).some((key) => { if (datas[key].value == ('' + temp[val])) { actions.push(datas[key].label + currentSeparator); match = true; } }) if (!match) { actions.push(temp[val] + currentSeparator); } }) return actions.join('').substring(0, actions.join('').length - 1); } // 字符串格式化(%s ) export function sprintf(str) { var args = arguments, flag = true, i = 1; str = str.replace(/%s/g, function () { var arg = args[i++]; if (typeof arg === 'undefined') { flag = false; return ''; } return arg; }); return flag ? str : ''; } // 转换字符串,undefined,null等转化为"" export function parseStrEmpty(str) { if (!str || str == "undefined" || str == "null") { return ""; } return str; } // 数据合并 export function mergeRecursive(source, target) { for (var p in target) { try { if (target[p].constructor == Object) { source[p] = mergeRecursive(source[p], target[p]); } else { source[p] = target[p]; } } catch (e) { source[p] = target[p]; } } return source; }; /** * 构造树型结构数据 * @param {*} data 数据源 * @param {*} id id字段 默认 'id' * @param {*} parentId 父节点字段 默认 'parentId' * @param {*} children 孩子节点字段 默认 'children' */ export function handleTree(data, id, parentId, children) { let config = { id: id || 'id', parentId: parentId || 'parentId', childrenList: children || 'children' }; var childrenListMap = {}; var nodeIds = {}; var tree = []; for (let d of data) { let parentId = d[config.parentId]; if (childrenListMap[parentId] == null) { childrenListMap[parentId] = []; } nodeIds[d[config.id]] = d; childrenListMap[parentId].push(d); } for (let d of data) { let parentId = d[config.parentId]; if (nodeIds[parentId] == null) { tree.push(d); } } for (let t of tree) { adaptToChildrenList(t); } function adaptToChildrenList(o) { if (childrenListMap[o[config.id]] !== null) { o[config.childrenList] = childrenListMap[o[config.id]]; } if (o[config.childrenList]) { for (let c of o[config.childrenList]) { adaptToChildrenList(c); } } } return tree; } /** * 参数处理 * @param {*} params 参数 */ export function tansParams(params) { let result = '' for (const propName of Object.keys(params)) { const value = params[propName]; var part = encodeURIComponent(propName) + "="; if (value !== null && value !== "" && typeof (value) !== "undefined") { if (typeof value === 'object') { for (const key of Object.keys(value)) { if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { let params = propName + '[' + key + ']'; var subPart = encodeURIComponent(params) + "="; result += subPart + encodeURIComponent(value[key]) + "&"; } } } else { result += part + encodeURIComponent(value) + "&"; } } } return result } // 验证是否为blob格式 export async function blobValidate(data) { try { const text = await data.text(); JSON.parse(text); return false; } catch (error) { return true; } } ================================================ FILE: vue_campus_admin/src/utils/scroll-to.js ================================================ Math.easeInOutQuad = function(t, b, c, d) { t /= d / 2 if (t < 1) { return c / 2 * t * t + b } t-- return -c / 2 * (t * (t - 2) - 1) + b } // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts var requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } })() /** * Because it's so fucking difficult to detect the scrolling element, just move them all * @param {number} amount */ function move(amount) { document.documentElement.scrollTop = amount document.body.parentNode.scrollTop = amount document.body.scrollTop = amount } function position() { return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop } /** * @param {number} to * @param {number} duration * @param {Function} callback */ export function scrollTo(to, duration, callback) { const start = position() const change = to - start const increment = 20 let currentTime = 0 duration = (typeof (duration) === 'undefined') ? 500 : duration var animateScroll = function() { // increment the time currentTime += increment // find the value with the quadratic in-out easing function var val = Math.easeInOutQuad(currentTime, start, change, duration) // move the document.body move(val) // do the animation unless its over if (currentTime < duration) { requestAnimFrame(animateScroll) } else { if (callback && typeof (callback) === 'function') { // the animation is done so lets callback callback() } } } animateScroll() } ================================================ FILE: vue_campus_admin/src/utils/validate.js ================================================ /** * @param {string} path * @returns {Boolean} */ export function isExternal(path) { return /^(https?:|mailto:|tel:)/.test(path) } /** * @param {string} str * @returns {Boolean} */ export function validUsername(str) { const valid_map = ['admin', 'editor'] return valid_map.indexOf(str.trim()) >= 0 } /** * @param {string} url * @returns {Boolean} */ export function validURL(url) { const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ return reg.test(url) } /** * @param {string} str * @returns {Boolean} */ export function validLowerCase(str) { const reg = /^[a-z]+$/ return reg.test(str) } /** * @param {string} str * @returns {Boolean} */ export function validUpperCase(str) { const reg = /^[A-Z]+$/ return reg.test(str) } /** * @param {string} str * @returns {Boolean} */ export function validAlphabets(str) { const reg = /^[A-Za-z]+$/ return reg.test(str) } /** * @param {string} email * @returns {Boolean} */ export function validEmail(email) { const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ return reg.test(email) } /** * @param {string} str * @returns {Boolean} */ export function isString(str) { if (typeof str === 'string' || str instanceof String) { return true } return false } /** * @param {Array} arg * @returns {Boolean} */ export function isArray(arg) { if (typeof Array.isArray === 'undefined') { return Object.prototype.toString.call(arg) === '[object Array]' } return Array.isArray(arg) } ================================================ FILE: vue_campus_admin/src/views/components/icons/element-icons.js ================================================ const elementIcons = ['platform-eleme', 'eleme', 'delete-solid', 'delete', 's-tools', 'setting', 'user-solid', 'user', 'phone', 'phone-outline', 'more', 'more-outline', 'star-on', 'star-off', 's-goods', 'goods', 'warning', 'warning-outline', 'question', 'info', 'remove', 'circle-plus', 'success', 'error', 'zoom-in', 'zoom-out', 'remove-outline', 'circle-plus-outline', 'circle-check', 'circle-close', 's-help', 'help', 'minus', 'plus', 'check', 'close', 'picture', 'picture-outline', 'picture-outline-round', 'upload', 'upload2', 'download', 'camera-solid', 'camera', 'video-camera-solid', 'video-camera', 'message-solid', 'bell', 's-cooperation', 's-order', 's-platform', 's-fold', 's-unfold', 's-operation', 's-promotion', 's-home', 's-release', 's-ticket', 's-management', 's-open', 's-shop', 's-marketing', 's-flag', 's-comment', 's-finance', 's-claim', 's-custom', 's-opportunity', 's-data', 's-check', 's-grid', 'menu', 'share', 'd-caret', 'caret-left', 'caret-right', 'caret-bottom', 'caret-top', 'bottom-left', 'bottom-right', 'back', 'right', 'bottom', 'top', 'top-left', 'top-right', 'arrow-left', 'arrow-right', 'arrow-down', 'arrow-up', 'd-arrow-left', 'd-arrow-right', 'video-pause', 'video-play', 'refresh', 'refresh-right', 'refresh-left', 'finished', 'sort', 'sort-up', 'sort-down', 'rank', 'loading', 'view', 'c-scale-to-original', 'date', 'edit', 'edit-outline', 'folder', 'folder-opened', 'folder-add', 'folder-remove', 'folder-delete', 'folder-checked', 'tickets', 'document-remove', 'document-delete', 'document-copy', 'document-checked', 'document', 'document-add', 'printer', 'paperclip', 'takeaway-box', 'search', 'monitor', 'attract', 'mobile', 'scissors', 'umbrella', 'headset', 'brush', 'mouse', 'coordinate', 'magic-stick', 'reading', 'data-line', 'data-board', 'pie-chart', 'data-analysis', 'collection-tag', 'film', 'suitcase', 'suitcase-1', 'receiving', 'collection', 'files', 'notebook-1', 'notebook-2', 'toilet-paper', 'office-building', 'school', 'table-lamp', 'house', 'no-smoking', 'smoking', 'shopping-cart-full', 'shopping-cart-1', 'shopping-cart-2', 'shopping-bag-1', 'shopping-bag-2', 'sold-out', 'sell', 'present', 'box', 'bank-card', 'money', 'coin', 'wallet', 'discount', 'price-tag', 'news', 'guide', 'male', 'female', 'thumb', 'cpu', 'link', 'connection', 'open', 'turn-off', 'set-up', 'chat-round', 'chat-line-round', 'chat-square', 'chat-dot-round', 'chat-dot-square', 'chat-line-square', 'message', 'postcard', 'position', 'turn-off-microphone', 'microphone', 'close-notification', 'bangzhu', 'time', 'odometer', 'crop', 'aim', 'switch-button', 'full-screen', 'copy-document', 'mic', 'stopwatch', 'medal-1', 'medal', 'trophy', 'trophy-1', 'first-aid-kit', 'discover', 'place', 'location', 'location-outline', 'location-information', 'add-location', 'delete-location', 'map-location', 'alarm-clock', 'timer', 'watch-1', 'watch', 'lock', 'unlock', 'key', 'service', 'mobile-phone', 'bicycle', 'truck', 'ship', 'basketball', 'football', 'soccer', 'baseball', 'wind-power', 'light-rain', 'lightning', 'heavy-rain', 'sunrise', 'sunrise-1', 'sunset', 'sunny', 'cloudy', 'partly-cloudy', 'cloudy-and-sunny', 'moon', 'moon-night', 'dish', 'dish-1', 'food', 'chicken', 'fork-spoon', 'knife-fork', 'burger', 'tableware', 'sugar', 'dessert', 'ice-cream', 'hot-water', 'water-cup', 'coffee-cup', 'cold-drink', 'goblet', 'goblet-full', 'goblet-square', 'goblet-square-full', 'refrigerator', 'grape', 'watermelon', 'cherry', 'apple', 'pear', 'orange', 'coffee', 'ice-tea', 'ice-drink', 'milk-tea', 'potato-strips', 'lollipop', 'ice-cream-square', 'ice-cream-round'] export default elementIcons ================================================ FILE: vue_campus_admin/src/views/components/icons/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/components/icons/svg-icons.js ================================================ const req = require.context('../../../assets/icons/svg', false, /\.svg$/) const requireAll = requireContext => requireContext.keys() const re = /\.\/(.*)\.svg/ const svgIcons = requireAll(req).map(i => { return i.match(re)[1] }) export default svgIcons ================================================ FILE: vue_campus_admin/src/views/dashboard/BarChart.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/dashboard/LineChart.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/dashboard/PanelGroup.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/dashboard/PieChart.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/dashboard/RaddarChart.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/dashboard/mixins/resize.js ================================================ import { debounce } from '@/utils' export default { data() { return { $_sidebarElm: null, $_resizeHandler: null } }, mounted() { this.initListener() }, activated() { if (!this.$_resizeHandler) { // avoid duplication init this.initListener() } // when keep-alive chart activated, auto resize this.resize() }, beforeDestroy() { this.destroyListener() }, deactivated() { this.destroyListener() }, methods: { // use $_ for mixins properties // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential $_sidebarResizeHandler(e) { if (e.propertyName === 'width') { this.$_resizeHandler() } }, initListener() { this.$_resizeHandler = debounce(() => { this.resize() }, 100) window.addEventListener('resize', this.$_resizeHandler) this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0] this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler) }, destroyListener() { window.removeEventListener('resize', this.$_resizeHandler) this.$_resizeHandler = null this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler) }, resize() { const { chart } = this chart && chart.resize() } } } ================================================ FILE: vue_campus_admin/src/views/error/401.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/error/404.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/imt/item/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/imt/log/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/imt/shop/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/imt/user/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/index_v1.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/login.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/monitor/job/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/monitor/job/log.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/monitor/logininfor/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/monitor/online/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/monitor/operlog/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/redirect.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/register.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/config/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/dict/data.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/dict/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/menu/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/notice/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/role/authUser.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/role/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/role/selectUser.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/user/authRole.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/user/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/user/profile/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/user/profile/resetPwd.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/user/profile/userAvatar.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/system/user/profile/userInfo.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/tool/gen/basicInfoForm.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/tool/gen/editTable.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/tool/gen/genInfoForm.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/tool/gen/importTable.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/tool/gen/index.vue ================================================ ================================================ FILE: vue_campus_admin/src/views/tool/swagger/index.vue ================================================ ================================================ FILE: vue_campus_admin/vue.config.js ================================================ 'use strict' const path = require('path') function resolve(dir) { return path.join(__dirname, dir) } const CompressionPlugin = require('compression-webpack-plugin') const name = process.env.VUE_APP_TITLE || 'campus' // 网页标题 const port = process.env.port || process.env.npm_config_port || 80 // 端口 // vue.config.js 配置说明 //官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions // 这里只列一部分,具体配置参考文档 module.exports = { // 部署生产环境和开发环境下的URL。 // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上 // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 publicPath: process.env.NODE_ENV === "production" ? "/" : "/", // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist) outputDir: 'dist', // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) assetsDir: 'static', // 是否开启eslint保存检测,有效值:ture | false | 'error' lintOnSave: process.env.NODE_ENV === 'development', // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 productionSourceMap: false, // webpack-dev-server 相关配置 devServer: { host: '0.0.0.0', port: port, open: true, proxy: { // detail: https://cli.vuejs.org/config/#devserver-proxy [process.env.VUE_APP_BASE_API]: { target: `http://localhost:8160`, changeOrigin: true, pathRewrite: { ['^' + process.env.VUE_APP_BASE_API]: '' } } }, disableHostCheck: true }, css: { loaderOptions: { sass: { sassOptions: { outputStyle: "expanded" } } } }, configureWebpack: { name: name, resolve: { alias: { '@': resolve('src') } }, plugins: [ // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 new CompressionPlugin({ cache: false, // 不启用文件缓存 test: /\.(js|css|html)?$/i, // 压缩文件格式 filename: '[path].gz[query]', // 压缩后的文件名 algorithm: 'gzip', // 使用gzip压缩 minRatio: 0.8 // 压缩率小于1才会压缩 }) ], }, chainWebpack(config) { config.plugins.delete('preload') // TODO: need test config.plugins.delete('prefetch') // TODO: need test // set svg-sprite-loader config.module .rule('svg') .exclude.add(resolve('src/assets/icons')) .end() config.module .rule('icons') .test(/\.svg$/) .include.add(resolve('src/assets/icons')) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }) .end() config .when(process.env.NODE_ENV !== 'development', config => { config .plugin('ScriptExtHtmlWebpackPlugin') .after('html') .use('script-ext-html-webpack-plugin', [{ // `runtime` must same as runtimeChunk name. default is `runtime` inline: /runtime\..*\.js$/ }]) .end() config .optimization.splitChunks({ chunks: 'all', cacheGroups: { libs: { name: 'chunk-libs', test: /[\\/]node_modules[\\/]/, priority: 10, chunks: 'initial' // only package third parties that are initially dependent }, elementUI: { name: 'chunk-elementUI', // split elementUI into a single package priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm }, commons: { name: 'chunk-commons', test: resolve('src/components'), // can customize your rules minChunks: 3, // minimum common number priority: 5, reuseExistingChunk: true } } }) config.optimization.runtimeChunk('single'), { from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件 to: './' //到根目录下 } } ) } }