├── .gitattributes ├── .github ├── Microsoft 365 Admin.postman_collection.json ├── Microsoft 365 Admin.rp ├── 关于.png ├── 接口.png ├── 用户管理.png ├── 申请.png ├── 订阅管理.png ├── 邀请.png └── 首页.png ├── .gitignore ├── Azure.md ├── Config.md ├── Dockerfile ├── Dockerfile-mvn ├── LICENSE ├── Microsoft 365 Admin.md ├── README.md ├── pom.xml └── src └── main ├── java └── cn │ └── itbat │ └── microsoft │ ├── MicrosoftApplication.java │ ├── aspect │ ├── TokenCacheAspect.java │ └── TokenCacheRedisAspect.java │ ├── cache │ └── GraphCache.java │ ├── config │ ├── CorsConfig.java │ ├── ExceptionHandler.java │ ├── GraphConfiguration.java │ ├── GraphProperties.java │ └── ThreadPoolConfig.java │ ├── controller │ ├── FrontDeskController.java │ ├── InvitationCodeController.java │ ├── Microsoft365Controller.java │ └── SystemController.java │ ├── enums │ ├── AccountStatusEnum.java │ ├── CapabilityStatusEnum.java │ └── RefreshTypeEnum.java │ ├── model │ ├── GraphUser.java │ ├── Pager.java │ ├── entity │ │ └── InvitationCode.java │ └── support │ │ ├── Jvm.java │ │ ├── Mem.java │ │ ├── Sys.java │ │ └── SystemMonitorInfo.java │ ├── repository │ └── InvitationCodeRepository.java │ ├── security │ ├── Md5PasswordEncoder.java │ ├── MySecurityConfig.java │ └── MyUserDetailsServiceImpl.java │ ├── service │ ├── FrontDeskService.java │ ├── GraphService.java │ ├── InvitationCodeService.java │ ├── MailService.java │ ├── Microsoft365Service.java │ └── impl │ │ ├── FrontDeskServiceImpl.java │ │ ├── GraphServiceImpl.java │ │ ├── InvitationCodeServiceImpl.java │ │ ├── MailServiceImpl.java │ │ └── Microsoft365ServiceImpl.java │ ├── utils │ ├── HttpClientUtils.java │ ├── IpUtils.java │ ├── PageInfo.java │ └── PasswordGenerator.java │ └── vo │ ├── BaseResultVo.java │ ├── DirectoryRoleVo.java │ ├── DomainVo.java │ ├── FontSkuSku.java │ ├── FontUser.java │ ├── GraphUserSorterVo.java │ ├── GraphUserVo.java │ ├── HomePageVo.java │ ├── SkuVo.java │ ├── StatisticsVo.java │ ├── SubscribedSkuVo.java │ └── UserVo.java └── resources ├── application.yml ├── config └── application-dev.yml ├── db └── data.sql ├── favicon.ico └── static ├── Microsoft 365 Admin.html ├── about.html ├── code.html ├── css ├── animate.css ├── bootstrap.min.css ├── gm-diy.css ├── gm.css ├── materialdesignicons.min.css └── style.min.css ├── favicon.ico ├── fonts ├── materialdesignicons.eot ├── materialdesignicons.svg ├── materialdesignicons.ttf ├── materialdesignicons.woff └── materialdesignicons.woff2 ├── header.html ├── images ├── captcha.png ├── favicon.ico ├── gallery │ ├── 1.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 17.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ └── 9.jpg ├── img-slide-1.jpg ├── img-slide-2.jpg ├── img-slide-3.jpg ├── img-slide-4.jpg ├── img-slide-5.jpg ├── login-bg-2.jpg ├── login-bg-3.jpg ├── login-bg-4.jpg ├── login-bg.jpg ├── logo-ico.png ├── logo-sidebar--2.png ├── logo-sidebar-1.png ├── logo-sidebar.png ├── logo.png └── users │ └── avatar.jpg ├── index.html ├── invite.html ├── js ├── about.js ├── bootstrap-colorpicker │ ├── bootstrap-colorpicker.js │ ├── bootstrap-colorpicker.min.js │ ├── css │ │ ├── bootstrap-colorpicker.css │ │ ├── bootstrap-colorpicker.css.map │ │ ├── bootstrap-colorpicker.min.css │ │ └── bootstrap-colorpicker.min.css.map │ └── img │ │ └── bootstrap-colorpicker │ │ ├── alpha-horizontal.png │ │ ├── alpha.png │ │ ├── hue-horizontal.png │ │ ├── hue.png │ │ └── saturation.png ├── bootstrap-notify.min.js ├── bootstrap.min.js ├── chosen.jquery.min.js ├── code.js ├── common.js ├── config.js ├── index.js ├── invite.js ├── jconfirm │ ├── jquery-confirm.min.css │ └── jquery-confirm.min.js ├── jquery-tags-input │ ├── jquery.tagsinput.min.css │ └── jquery.tagsinput.min.js ├── jquery.bootstrap.wizard.min.js ├── jquery.min.js ├── licenses.js ├── lightyear.js ├── login.js ├── main.min.js ├── md5.min.js ├── perfect-scrollbar.min.js ├── plugins │ └── gm.js └── users.js ├── licenses.html ├── login.html ├── sidebar.html └── users.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/Microsoft 365 Admin.rp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/.github/Microsoft 365 Admin.rp -------------------------------------------------------------------------------- /.github/关于.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/.github/关于.png -------------------------------------------------------------------------------- /.github/接口.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/.github/接口.png -------------------------------------------------------------------------------- /.github/用户管理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/.github/用户管理.png -------------------------------------------------------------------------------- /.github/申请.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/.github/申请.png -------------------------------------------------------------------------------- /.github/订阅管理.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/.github/订阅管理.png -------------------------------------------------------------------------------- /.github/邀请.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/.github/邀请.png -------------------------------------------------------------------------------- /.github/首页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/.github/首页.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | .mvn 22 | mvnw.cmd 23 | mvnw 24 | 25 | ### NetBeans ### 26 | /nbproject/private/ 27 | /nbbuild/ 28 | /dist/ 29 | /nbdist/ 30 | /.nb-gradle/ 31 | build/ 32 | !**/src/main/**/build/ 33 | !**/src/test/**/build/ 34 | 35 | ### VS Code ### 36 | .vscode/ 37 | /src/main/resources/config/application-cit.yml 38 | -------------------------------------------------------------------------------- /Azure.md: -------------------------------------------------------------------------------- 1 | ## 原型截图 2 | 3 | 4 | [原型文档](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/Microsoft%20365%20Admin.rp) 5 | 6 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/首页.png) 7 | 8 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/许可.png) 9 | 10 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/用户.png) 11 | -------------------------------------------------------------------------------- /Config.md: -------------------------------------------------------------------------------- 1 | ### 邮件配置 2 | > 需要修改 3 | 4 | 该配置用于发送邮件功能,配置后新增账号时填写邮箱,即可将账号密码发送至该邮箱 5 | ```yaml 6 | spring: 7 | mail: # 邮件配置,自行修改 8 | host: smtp.qq.com # smtp.qq.com 9 | username: 123@qq.com # 发送邮箱地址 10 | password: 123 # 发送邮箱密码 11 | ``` 12 | 13 | ### 程序自定义配置(重点) 14 | 15 | 1、缓存超时时间配置,单位分钟:用户过期用户账号,许可证,绑定域名,默认即可 16 | > 默认即可 17 | 18 | ```yaml 19 | graph: 20 | cache: 21 | token: default # token 缓存方式默认内存,redis 方式 需要配置 redis 22 | timeout: # 缓存超时时间 单位分钟 23 | user: 60 # 默认一小时 24 | license: 1440 # 默认一天 25 | domain: 1440 # 默认一天 26 | ``` 27 | 28 | 2、后台登陆账号密码配置 29 | > 需要修改 30 | 31 | ```yaml 32 | graph: 33 | userName: root # 登陆账户,自行修改 34 | password: 123456 # 登陆密码,自行修改 35 | ``` 36 | 37 | 3、可以被用户注册的账号类型,为下面 configs.appName 的配置(必填,','分隔) 38 | > 需要修改 39 | 40 | ```yaml 41 | graph: 42 | invite: mjj,mjj2 43 | ``` 44 | 45 | 4、Microsoft API 配置(重点) 46 | > 需要修改 47 | 48 | ```yaml 49 | graph: 50 | configs: 51 | - appName: mjj # 自定义该账号的类型(建议使用英文:默认域名前缀,必填) 52 | appId: mjj # 应用程序(客户端) ID client(必填) 53 | appTenant: mjj # 目录(租户) tenant(必填) 54 | appSecret: mjj # secrets(必填) 55 | admin: mjj # 全局管理员账号(可以为空) 56 | - appName: mjj2 # 账号2配置,如果不需要请删除 57 | appId: mjj2 58 | appTenant: mjj2 59 | appSecret: mjj2 60 | domain: mjj2 61 | admin: mjj2 62 | ``` 63 | 64 | 5、订阅配置,用于转换订阅显示的名称,可自行按照订阅添加 65 | > 按照需求修改 66 | 67 | ```yaml 68 | graph: 69 | subscribed: # 订阅类型,自行添加其他 70 | - skuName: STANDARDWOFFPACK_STUDENT 71 | displayName: A1 学生版 72 | skuId: 314c4481-f395-4525-be8b-2ec4bb1e9d91 73 | - skuName: STANDARDWOFFPACK_FACULTY 74 | displayName: A1 教师版 75 | skuId: 94763226-9b3c-4e75-a931-5c89701abe66 76 | - skuName: OFFICE_365_A1_PLUS_FOR_STUDENT 77 | displayName: A1P 学生版 78 | skuId: e82ae690-a2d5-4d76-8d30-7c6e01e6022e 79 | - skuName: OFFICE_365_A1_PLUS_FOR_FACULTY 80 | displayName: A1P 教师版 81 | skuId: 78e66a63-337a-4a9a-8959-41c6654dfb56 82 | - skuName: M365EDU_A3_STUUSEBNFT_RPA1 83 | displayName: A3 无人值守版 84 | skuId: 1aa94593-ca12-4254-a738-81a5972958e8 85 | - skuName: Office_365_E3Y 86 | displayName: E3Y 87 | skuId: 6fd2c87f-b296-42f0-b197-1e91e994b900 88 | - skuName: DEVELOPERPACK_E5 89 | displayName: E5 开发者订阅 90 | skuId: c42b9cae-ea4f-4ab7-9717-81576235ccac 91 | 92 | ``` 93 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.6.3-jdk-8 AS build 2 | 3 | MAINTAINER mjj 4 | 5 | ENV LANG en_US.UTF-8 6 | ENV LANGUAGE en_US:UTF-8 7 | ENV LC_ALL en_US.UTF-8 8 | 9 | WORKDIR /src 10 | RUN git clone https://github.com/6mb/Microsoft-365-Admin.git \ 11 | && cd Microsoft-365-Admin \ 12 | && mvn package -Dmaven.test.skip=true 13 | 14 | # microsoft 15 | FROM logr/8-jre-alpine 16 | COPY --from=build /src/Microsoft-365-Admin/target/microsoft-365-admin-*-RELEASE.jar . 17 | RUN mv microsoft-365-admin-*-RELEASE.jar microsoft.jar 18 | 19 | #执行 20 | CMD java -jar microsoft.jar --spring.profile.active=dev 21 | -------------------------------------------------------------------------------- /Dockerfile-mvn: -------------------------------------------------------------------------------- 1 | FROM maven:3.6.3-jdk-8 AS build 2 | 3 | MAINTAINER mjj 4 | 5 | ENV LANG en_US.UTF-8 6 | ENV LANGUAGE en_US:UTF-8 7 | ENV LC_ALL en_US.UTF-8 8 | 9 | WORKDIR /src 10 | RUN git clone -b dev https://github.com/6mb/Microsoft-365-Admin.git \ 11 | && cd Microsoft-365-Admin \ 12 | && mvn package -Dmaven.test.skip=true 13 | 14 | # microsoft 15 | FROM logr/8-jre-alpine 16 | COPY --from=build /src/Microsoft-365-Admin/target/microsoft-0.0.1-SNAPSHOT.jar . 17 | RUN mv microsoft-0.0.1-SNAPSHOT.jar microsoft.jar 18 | 19 | #执行 20 | CMD java -jar microsoft.jar --spring.profile.active=dev 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft-365-Admin 2 | 3 | ![https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square](https://img.shields.io/badge/license-MIT-blue.svg?longCache=true&style=flat-square) 4 | ![https://img.shields.io/badge/springboot-2.0.6-orange.svg?style=flat-square](https://img.shields.io/badge/springboot-2.1.3-yellow.svg?longCache=true&style=popout-square) 5 | 6 | 此项目是一个基于API的 Microsoft 365 管理平台, 支持订阅查询,用户管理(查询,新增,删除,分配许可证等),支持批量创建用户,批量删除用户,支持邀请码. 支持多账户管理. 7 | 8 | 后端基于SpringBoot,使用 msal4j 和 microsoft-graph sdk 开发完成 9 | 10 | 前端?**嗯,能力不足,瞎写的,不要在意代码** 11 | 12 | 13 | ## 系统功能 14 | 15 | - 总览:订阅数,许可证数,可用许可证数,用户数,允许登陆用户数,禁止登陆用户数等信息展示 16 | 17 | - 许可查询:查询每个订阅的许可证信息 18 | 19 | - 用户管理:查询,新增,删除,分配许可证等 20 | 21 | - 多账户切换 22 | 23 | - **新增登陆功能(需要在配置文件中配置账号和密码)** 24 | 25 | - **新增Docker启动方式** 26 | 27 | - 2020-11-23 增加发送账号密码到指定邮箱功能(需要修改配置文件) 28 | 29 | - 2020-11-25 **增加邀请码功能,用户可以自行注册账号** 30 | 31 | - 2021-05-27 用户管理界面改变包括:使用GridManager,并提供列表排序功能,**增加用户提权功能**(@Herts)。 32 | 33 | **注意:出现执行新增/删除账户,启用禁用账户,授权账户等操作后,查询结果不变,请右上角点击刷新缓存,或列表中搜索该用户** 34 | 35 | 原因:调用微软API比较耗时,第一次查询时将需要的数据拉取到本地缓存(暂无自定义缓存失效功能,默认用户1天,域名1天,订阅30天)。 36 | 37 | 具体功能查看关于界面【操作教程】 38 | 39 | ## 权限 40 | 41 | **新增用户:** 42 | 43 | 权限类型 | 权限(从最低特权到最高特权) 44 | ---|--- 45 | 委派(工作或学校帐户) | User.ReadWrite.All、Directory.ReadWrite.All、Directory.AccessAsUser.All 46 | 委派(个人 Microsoft 帐户)) | 不支持。 47 | 应用程序 | User.ReadWrite.All、Directory.ReadWrite.All 48 | 49 | **删除用户:** 50 | 51 | 权限类型 | 权限(从最低特权到最高特权) 52 | ---|--- 53 | 委派(工作或学校帐户) | Directory.AccessAsUser.All 54 | 委派(个人 Microsoft 帐户)) | 不支持。 55 | 应用程序 | User.ReadWrite.All 56 | 57 | **分配/取消许可证:** 58 | 59 | 权限类型 | 权限(从最低特权到最高特权) 60 | ---|--- 61 | 委派(工作或学校帐户) | User.ReadWrite.All、Directory.ReadWrite.All 62 | 委派(个人 Microsoft 帐户)) | 不支持。 63 | 应用程序 | User.ReadWrite.All、Directory.ReadWrite.All 64 | 65 | **提权:** 66 | 67 | 权限类型 | 权限(从最低特权到最高特权) 68 | ---|--- 69 | 委派(工作或学校帐户) | 70 | 委派(个人 Microsoft 帐户)) | 不支持。 71 | 应用程序 | RoleManagement.ReadWrite.Directory 72 | 73 | ## 运行 74 | 75 | #### 1、关于配置文件 76 | 77 | 请参考该文档 [Config文档](https://github.com/6mb/Microsoft-365-Admin/blob/dev/Config.md) 78 | 79 | #### 2、存在Java环境的情况下 80 | 下载最新版本的`jar`包,然后在同目录新建config目录,下载 [application-dev.yml](https://github.com/6mb/Microsoft-365-Admin/blob/master/src/main/resources/config/application-dev.yml)文件放到该目录。 81 | 82 | 按照格式修改配置文件 83 | 84 | 执行 `java -jar microsoft-0.0.1-SNAPSHOT.jar --spring.profile.active=dev` 启动,端口:8099 85 | 86 | #### 3、Docker 安装 (推荐) 87 | 88 | 89 | 然后下载 [application-dev.yml](https://github.com/6mb/Microsoft-365-Admin/blob/master/src/main/resources/config/application-dev.yml)文件放到config目录。 90 | 91 | 按照格式修改配置文件 92 | 93 | ```shell script 94 | docker pull logr/microsoft:latest 95 | 96 | docker run -d --name=microsoft-admin \ 97 | -p 8099:8099 \ 98 | -v /home/microsoft/config:/config \ 99 | -v /home/microsoft/db:/root/.graph/db \ 100 | logr/microsoft:latest 101 | ``` 102 | 103 | ### 注意:如果前端文件单独部署(~~不建议部署到公网,没有登陆功能~~),请并修改config.js中的请求地址。 104 | 105 | 106 | ## 界面 107 | 108 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/首页.png) 109 | 110 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/订阅管理.png) 111 | 112 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/用户管理.png) 113 | 114 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/邀请.png) 115 | 116 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/申请.png) 117 | 118 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/关于.png) 119 | 120 | ## API接口 121 | 122 | [接口文档](https://github.com/6mb/Microsoft-365-Admin/blob/master/Microsoft%20365%20Admin.md) 123 | 124 | - 登陆 125 | - Microsoft 365 首页 126 | - 首页展示 127 | - 刷新缓存 128 | - Microsoft 365 订阅管理 129 | - 许可统计 130 | - 许可证列表查询 131 | - Microsoft 365 用户管理 132 | - 查询用户统计 133 | - 查询用户信息列表 134 | - 查询用户信息详情 135 | - 查询绑定域名 136 | - 添加账号 137 | - 删除账户 138 | - 添加订阅 139 | - 取消订阅 140 | - 启用账户 141 | - 禁用账户 142 | - 批量创建用户信息 143 | ## Postman 接口 144 | 145 | [点击下载](https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/master/.github/Microsoft%20365%20Admin.postman_collection.json) 146 | 147 | 请自行设置 .evn {host} 和 {port} 148 | 149 | ![image](https://github.com/6mb/Microsoft-365-Admin/blob/master/.github/接口.png) 150 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.4.5 9 | 10 | 11 | cn.itbat.microsoft 12 | microsoft-365-admin 13 | 1.6.2-RELEASE 14 | Microsoft 365 Admin 15 | Microsoft 365 Admin project for Spring Boot 16 | 17 | 18 | 1.8 19 | 2.11.4 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-aop 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-data-redis 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-security 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-data-jpa 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-mail 51 | 52 | 53 | 54 | com.fasterxml.jackson.core 55 | jackson-core 56 | ${jackson.version} 57 | 58 | 59 | com.fasterxml.jackson.core 60 | jackson-annotations 61 | ${jackson.version} 62 | 63 | 64 | com.fasterxml.jackson.core 65 | jackson-databind 66 | ${jackson.version} 67 | 68 | 69 | 70 | com.microsoft.graph 71 | microsoft-graph 72 | [3.5.0,) 73 | 74 | 75 | com.azure 76 | azure-identity 77 | [1.3.0,) 78 | 79 | 80 | 81 | com.squareup.okhttp3 82 | okhttp 83 | 3.14.9 84 | 85 | 86 | 87 | 88 | com.h2database 89 | h2 90 | 1.4.200 91 | runtime 92 | 93 | 94 | 95 | org.projectlombok 96 | lombok 97 | true 98 | 99 | 100 | 101 | 102 | com.alibaba 103 | fastjson 104 | 1.2.58 105 | 106 | 107 | 108 | 109 | com.google.guava 110 | guava 111 | 30.1.1-jre 112 | 113 | 114 | 115 | 116 | io.github.biezhi 117 | TinyPinyin 118 | 2.0.3.RELEASE 119 | 120 | 121 | 122 | org.apache.commons 123 | commons-lang3 124 | 3.5 125 | 126 | 127 | 128 | cn.hutool 129 | hutool-all 130 | 5.1.3 131 | 132 | 133 | 134 | 135 | com.github.binarywang 136 | java-testdata-generator 137 | 1.1.2 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | org.springframework.boot 149 | spring-boot-maven-plugin 150 | 151 | true 152 | true 153 | 154 | 155 | 156 | 157 | 158 | src/main/resources 159 | 160 | config/* 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/MicrosoftApplication.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | 7 | /** 8 | * Microsoft 365 管理平台 9 | * 10 | * @author mjj 11 | */ 12 | @EnableAsync 13 | @SpringBootApplication 14 | public class MicrosoftApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(MicrosoftApplication.class, args); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/aspect/TokenCacheAspect.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.aspect; 2 | 3 | import com.microsoft.aad.msal4j.ITokenCacheAccessAspect; 4 | import com.microsoft.aad.msal4j.ITokenCacheAccessContext; 5 | 6 | import java.util.HashMap; 7 | 8 | /** 9 | * TokenCacheAspect 10 | * 11 | * @author mjj 12 | * @date 2020年05月12日 09:16:33 13 | */ 14 | public class TokenCacheAspect implements ITokenCacheAccessAspect { 15 | 16 | private HashMap data = new HashMap<>(); 17 | 18 | private String appName; 19 | 20 | public TokenCacheAspect(String appName) { 21 | this.data.get(appName); 22 | this.appName = appName; 23 | } 24 | 25 | @Override 26 | public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { 27 | iTokenCacheAccessContext.tokenCache().deserialize(data.get(appName)); 28 | } 29 | 30 | @Override 31 | public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { 32 | data.put(appName, iTokenCacheAccessContext.tokenCache().serialize()); 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/aspect/TokenCacheRedisAspect.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.aspect; 2 | 3 | import com.microsoft.aad.msal4j.ITokenCacheAccessAspect; 4 | import com.microsoft.aad.msal4j.ITokenCacheAccessContext; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * @author mjj 11 | * @date 2020年05月12日 12:27:02 12 | */ 13 | public class TokenCacheRedisAspect implements ITokenCacheAccessAspect { 14 | 15 | private String data; 16 | 17 | private String appName; 18 | 19 | private RedisTemplate redisTemplate; 20 | 21 | public TokenCacheRedisAspect(String appName, RedisTemplate redisTemplate) { 22 | this.appName = "GraphToken-" + appName; 23 | this.redisTemplate = redisTemplate; 24 | this.data = redisTemplate.opsForValue().get(appName); 25 | } 26 | 27 | @Override 28 | public void beforeCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { 29 | iTokenCacheAccessContext.tokenCache().deserialize(data); 30 | } 31 | 32 | @Override 33 | public void afterCacheAccess(ITokenCacheAccessContext iTokenCacheAccessContext) { 34 | data = iTokenCacheAccessContext.tokenCache().serialize(); 35 | // you could implement logic here to write changes to redis 36 | redisTemplate.opsForValue().set(appName, data, 60 * 10, TimeUnit.MINUTES); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/cache/GraphCache.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.cache; 2 | 3 | import cn.itbat.microsoft.service.GraphService; 4 | import cn.itbat.microsoft.vo.DirectoryRoleVo; 5 | import com.google.common.cache.CacheBuilder; 6 | import com.google.common.cache.CacheLoader; 7 | import com.google.common.cache.LoadingCache; 8 | import com.microsoft.graph.models.Domain; 9 | import com.microsoft.graph.models.SubscribedSku; 10 | import com.microsoft.graph.models.User; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.checkerframework.checker.nullness.qual.NonNull; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.stereotype.Component; 15 | 16 | import javax.annotation.PostConstruct; 17 | import javax.annotation.Resource; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.Set; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | /** 24 | * 缓存器 25 | * 26 | * @author huahui.wu 27 | * @date 2020年11月24日 13:46:01 28 | */ 29 | @Component 30 | @Slf4j 31 | public class GraphCache { 32 | 33 | @Resource 34 | private GraphService graphService; 35 | 36 | /** 37 | * 缓存过期时间 38 | */ 39 | @Value("${graph.cache.timeout.user}") 40 | private long userTimeout; 41 | 42 | @Value("${graph.cache.timeout.license}") 43 | private long licenseTimeout; 44 | 45 | @Value("${graph.cache.timeout.domain}") 46 | private long domainTimeout; 47 | 48 | 49 | private LoadingCache> usersCache; 50 | 51 | private LoadingCache> licenseCache; 52 | 53 | private LoadingCache> domainCache; 54 | 55 | private LoadingCache>> roleCache; 56 | 57 | @PostConstruct 58 | public void init() { 59 | usersCache = CacheBuilder.newBuilder() 60 | .expireAfterWrite(userTimeout, TimeUnit.MINUTES) 61 | .build(new CacheLoader>() { 62 | @Override 63 | public List load(@NonNull String key) { 64 | return graphService.getUsers(key); 65 | } 66 | }); 67 | licenseCache = CacheBuilder.newBuilder() 68 | .expireAfterWrite(licenseTimeout, TimeUnit.MINUTES) 69 | .build(new CacheLoader>() { 70 | @Override 71 | public List load(@NonNull String key) { 72 | return graphService.getSubscribedSkus(key); 73 | } 74 | }); 75 | domainCache = CacheBuilder.newBuilder() 76 | .expireAfterWrite(domainTimeout, TimeUnit.MINUTES) 77 | .build(new CacheLoader>() { 78 | @Override 79 | public List load(@NonNull String key) { 80 | return graphService.getDomains(key); 81 | } 82 | }); 83 | roleCache = CacheBuilder.newBuilder() 84 | .expireAfterWrite(licenseTimeout, TimeUnit.MINUTES) 85 | .build(new CacheLoader>>() { 86 | @Override 87 | public Map> load(@NonNull String key) throws Exception { 88 | return graphService.directoryRoleToUserNameMap(key); 89 | } 90 | }); 91 | } 92 | 93 | /** 94 | * 获取用户缓存 95 | * 96 | * @param appName 组织名称 97 | * @return 结果 98 | */ 99 | public List getUsersCache(String appName) { 100 | return usersCache.getUnchecked(appName); 101 | } 102 | 103 | /** 104 | * 获取许可证缓存 105 | * 106 | * @param appName 组织名称 107 | * @return 结果 108 | */ 109 | public List getLicenseCache(String appName) { 110 | return licenseCache.getUnchecked(appName); 111 | } 112 | 113 | /** 114 | * 获取域名缓存 115 | * 116 | * @param appName 组织名称 117 | * @return 结果 118 | */ 119 | public List getDomainCache(String appName) { 120 | return domainCache.getUnchecked(appName); 121 | } 122 | 123 | /** 124 | * 获取角色缓存 125 | * 126 | * @param appName 组织名称 127 | * @return 结果 128 | */ 129 | public Map> getRoleCache(String appName) { 130 | return roleCache.getUnchecked(appName); 131 | } 132 | 133 | /** 134 | * 刷新用户缓存 135 | * 136 | * @param appName 组织名称 137 | */ 138 | public void refreshUsers(String appName) { 139 | usersCache.refresh(appName); 140 | } 141 | 142 | /** 143 | * 刷新许可证缓存 144 | * 145 | * @param appName 组织名称 146 | */ 147 | public void refreshLicense(String appName) { 148 | licenseCache.refresh(appName); 149 | } 150 | 151 | /** 152 | * 刷新域名缓存 153 | * 154 | * @param appName 组织名称 155 | */ 156 | public void refreshDomain(String appName) { 157 | domainCache.refresh(appName); 158 | } 159 | 160 | /** 161 | * 刷新角色缓存 162 | * 163 | * @param appName 组织名称 164 | */ 165 | public void refreshRole(String appName) { 166 | roleCache.refresh(appName); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.cors.CorsConfiguration; 6 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 7 | import org.springframework.web.filter.CorsFilter; 8 | 9 | /** 10 | * 跨域配置 11 | * 12 | * @author mjj 13 | * @date 2020年05月12日 10:52:56 14 | */ 15 | @Configuration 16 | public class CorsConfig { 17 | 18 | @Bean 19 | public CorsFilter corsFilter() { 20 | final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); 21 | final CorsConfiguration config = new CorsConfiguration(); 22 | // 允许cookies跨域 23 | config.setAllowCredentials(true); 24 | // #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin 25 | config.addAllowedOriginPattern("*"); 26 | // #允许访问的头信息,*表示全部 27 | config.addAllowedHeader("*"); 28 | // 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了 29 | config.setMaxAge(18000L); 30 | // 允许提交请求的方法,*表示全部允许 31 | config.addAllowedMethod("*"); 32 | source.registerCorsConfiguration("/**", config); 33 | return new CorsFilter(source); 34 | } 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/config/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.config; 2 | 3 | 4 | import cn.itbat.microsoft.vo.BaseResultVo; 5 | import com.microsoft.graph.http.GraphServiceException; 6 | import lombok.extern.log4j.Log4j2; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ResponseBody; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | /** 14 | * 异常处理 15 | * 16 | * @author log.r   (;¬_¬)    17 | * @date 2018-08-24 上午10:05 18 | **/ 19 | @Log4j2 20 | @ControllerAdvice 21 | @ResponseBody 22 | public class ExceptionHandler { 23 | /** 24 | * 自定义异常- 业务异常 25 | */ 26 | @org.springframework.web.bind.annotation.ExceptionHandler(RuntimeException.class) 27 | public BaseResultVo baseExceptionHandler(RuntimeException ex, HttpServletResponse response, HttpServletRequest request) { 28 | log.error(">>>>>>>>>>>>【业务异常】", ex.getMessage(), ex); 29 | return new BaseResultVo(500, ex.getMessage()); 30 | } 31 | 32 | @org.springframework.web.bind.annotation.ExceptionHandler(GraphServiceException.class) 33 | public BaseResultVo graphServiceException(RuntimeException ex, HttpServletResponse response, HttpServletRequest request) { 34 | log.error(">>>>>>>>>>>>【微软请求异常】", ex.getMessage(), ex); 35 | String[] split = ex.getMessage().split("\n"); 36 | return new BaseResultVo(500, split[0]+ split[1]); 37 | } 38 | 39 | /** 40 | * 500- server error 41 | */ 42 | @org.springframework.web.bind.annotation.ExceptionHandler(Exception.class) 43 | public BaseResultVo otherExceptionHandler(Exception ex, HttpServletResponse response, HttpServletRequest request) { 44 | log.error(">>>>>>>>>>>>otherException", ex.getMessage(), ex); 45 | return BaseResultVo.error("系统异常 " + ex.getMessage()); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/config/GraphConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.config; 2 | 3 | import cn.itbat.microsoft.aspect.TokenCacheAspect; 4 | import cn.itbat.microsoft.aspect.TokenCacheRedisAspect; 5 | import com.azure.identity.ClientSecretCredential; 6 | import com.azure.identity.ClientSecretCredentialBuilder; 7 | import com.google.common.collect.Maps; 8 | import com.microsoft.aad.msal4j.*; 9 | import com.microsoft.graph.authentication.TokenCredentialAuthProvider; 10 | import com.microsoft.graph.logger.DefaultLogger; 11 | import com.microsoft.graph.logger.LoggerLevel; 12 | import com.microsoft.graph.requests.GraphServiceClient; 13 | import lombok.Data; 14 | import lombok.extern.slf4j.Slf4j; 15 | import okhttp3.Request; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.beans.factory.annotation.Value; 18 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.data.redis.core.RedisTemplate; 21 | 22 | import javax.annotation.PostConstruct; 23 | import java.util.Collections; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.stream.Collectors; 27 | 28 | /** 29 | * Graph 初始化配置 30 | * 31 | * @author mjj 32 | * @date 2020年05月12日 10:52:56 33 | */ 34 | @Slf4j 35 | @Configuration 36 | @EnableConfigurationProperties(GraphProperties.class) 37 | public class GraphConfiguration { 38 | 39 | @Value("${graph.cache.token}") 40 | private String cache; 41 | /** 42 | * 配置类 43 | */ 44 | private final GraphProperties properties; 45 | 46 | private final RedisTemplate redisTemplate; 47 | 48 | private final static String AUTHORITY = "https://login.microsoftonline.com/"; 49 | private final static String GRAPH_DEFAULT_SCOPE = "https://graph.microsoft.com/.default"; 50 | private final static String CACHE_MODE = "redis"; 51 | 52 | private static Map graphConfigMap = Maps.newHashMap(); 53 | 54 | @Data 55 | private static class GraphConfig { 56 | private String appName; 57 | private String appId; 58 | private String appSecret; 59 | private String appTenant; 60 | private ITokenCacheAccessAspect accessAspect; 61 | } 62 | 63 | @Autowired 64 | public GraphConfiguration(GraphProperties properties, RedisTemplate redisTemplate) { 65 | this.properties = properties; 66 | this.redisTemplate = redisTemplate; 67 | } 68 | 69 | @PostConstruct 70 | public void init() { 71 | List configs = this.properties.getConfigs(); 72 | graphConfigMap = configs.stream() 73 | .map(a -> { 74 | GraphConfig config = new GraphConfig(); 75 | // 默认使用内存存储token 76 | if (CACHE_MODE.equals(cache)) { 77 | config.setAccessAspect(new TokenCacheRedisAspect(a.getAppName(), redisTemplate)); 78 | } else { 79 | config.setAccessAspect(new TokenCacheAspect(a.getAppName())); 80 | } 81 | config.setAppName(a.getAppName()); 82 | config.setAppId(a.getAppId()); 83 | config.setAppSecret(a.getAppSecret()); 84 | config.setAppTenant(a.getAppTenant()); 85 | return config; 86 | }).collect(Collectors.toMap(GraphConfig::getAppName, a -> a)); 87 | } 88 | 89 | /** 90 | * 构建 GraphClient 91 | * 92 | * @param appName 应用名 93 | * @return IGraphServiceClient 94 | */ 95 | public static synchronized GraphServiceClient getGraphClient(String appName) { 96 | // Create the auth provider 97 | // SimpleAuthProvider authProvider = new SimpleAuthProvider(GraphConfiguration.getToken(appName)); 98 | GraphConfig config = graphConfigMap.get(appName); 99 | final ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder() 100 | .clientId(config.appId) 101 | .clientSecret(config.appSecret) 102 | .tenantId(config.appTenant) 103 | .build(); 104 | 105 | final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(Collections.singletonList(GRAPH_DEFAULT_SCOPE), clientSecretCredential); 106 | 107 | // Create default logger to only log errors 108 | DefaultLogger logger = new DefaultLogger(); 109 | logger.setLoggingLevel(LoggerLevel.DEBUG); 110 | 111 | log.info("Graph client is built!"); 112 | // Build a Graph client 113 | return GraphServiceClient.builder() 114 | .authenticationProvider(tokenCredentialAuthProvider) 115 | .logger(logger) 116 | .buildClient(); 117 | } 118 | 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/config/GraphProperties.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.util.StringUtils; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Graph 配置 11 | * 12 | * @author mjj 13 | * @date 2020年05月11日 16:23:11 14 | */ 15 | @Data 16 | @ConfigurationProperties(prefix = "graph") 17 | public class GraphProperties { 18 | 19 | /** 20 | * 用户名 21 | */ 22 | private String userName; 23 | 24 | /** 25 | * 密码 26 | */ 27 | private String password; 28 | 29 | /** 30 | * 小程序配置 31 | */ 32 | private List configs; 33 | 34 | 35 | /** 36 | * 地区 37 | */ 38 | private String usageLocation; 39 | 40 | @Data 41 | public static class GraphConfig { 42 | 43 | /** 44 | * 名称 45 | */ 46 | private String appName; 47 | 48 | /** 49 | * client id 50 | */ 51 | private String appId; 52 | 53 | /** 54 | * Secret 55 | */ 56 | private String appSecret; 57 | 58 | /** 59 | * Tenant 60 | */ 61 | private String appTenant; 62 | 63 | /** 64 | * admin 账户 65 | */ 66 | private String admin; 67 | 68 | /** 69 | * 地区 70 | */ 71 | private String usageLocation; 72 | } 73 | 74 | /** 75 | * Office 配置 76 | * 77 | * @param appName office 名称 78 | * @return 配置 79 | */ 80 | public GraphProperties.GraphConfig getConfig(String appName) { 81 | for (GraphProperties.GraphConfig maConfig : configs) { 82 | if (maConfig.getAppName().equals(appName)) { 83 | return maConfig; 84 | } 85 | } 86 | return null; 87 | } 88 | 89 | /** 90 | * Office 订阅配置 91 | */ 92 | private List subscribed; 93 | 94 | @Data 95 | public static class GraphSubConfig { 96 | 97 | /** 98 | * skuName 99 | */ 100 | private String skuName; 101 | 102 | /** 103 | * 显示名称 104 | */ 105 | private String displayName; 106 | 107 | /** 108 | * skuId 109 | */ 110 | private String skuId; 111 | 112 | } 113 | 114 | /** 115 | * 获取对应订阅的配置 116 | * 117 | * @param skuName 订阅名称 118 | * @return 配置 119 | */ 120 | public GraphProperties.GraphSubConfig getSubConfig(String skuName) { 121 | for (GraphProperties.GraphSubConfig maConfig : subscribed) { 122 | if (maConfig.getSkuName().equals(skuName)) { 123 | return maConfig; 124 | } 125 | } 126 | return subscribed.get(0); 127 | } 128 | 129 | public String getSubConfigName(String skuId) { 130 | for (GraphProperties.GraphSubConfig maConfig : subscribed) { 131 | if (maConfig.getSkuId().equals(skuId)) { 132 | return maConfig.getSkuName(); 133 | } 134 | } 135 | return skuId; 136 | } 137 | 138 | public String getSubConfigDisplayName(String skuId) { 139 | for (GraphProperties.GraphSubConfig maConfig : subscribed) { 140 | if (maConfig.getSkuId().equals(skuId)) { 141 | return maConfig.getDisplayName(); 142 | } 143 | } 144 | return null; 145 | } 146 | 147 | public String[] listUsageLocation(String appName) { 148 | String usageLocations = getConfig(appName).getUsageLocation(); 149 | if (StringUtils.isEmpty(usageLocations)) { 150 | usageLocations = getUsageLocation(); 151 | if (StringUtils.isEmpty(usageLocations)) { 152 | return new String[]{"HK"}; 153 | } 154 | } 155 | return usageLocations.split(","); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/config/ThreadPoolConfig.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 6 | 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | 9 | /** 10 | * 线程池 11 | *

12 | * 使用方法 @Async("asyncPoolTaskExecutor") 13 | * 14 | * @author mjj  (;¬_¬)  15 | * @version 1.0 16 | * @date 2019/6/11 19:27 17 | */ 18 | @Configuration 19 | public class ThreadPoolConfig { 20 | 21 | /** 22 | * 配置线程池 23 | * 24 | * @return 线程池 25 | */ 26 | @Bean(name = "asyncPoolTaskExecutor") 27 | public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() { 28 | ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); 29 | taskExecutor.setCorePoolSize(20); 30 | taskExecutor.setMaxPoolSize(200); 31 | taskExecutor.setQueueCapacity(25); 32 | taskExecutor.setKeepAliveSeconds(200); 33 | taskExecutor.setThreadNamePrefix("microsoft-async-"); 34 | // 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean 35 | taskExecutor.setWaitForTasksToCompleteOnShutdown(true); 36 | // 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应⽤最后能够被关闭,⽽不是阻塞住。 37 | taskExecutor.setAwaitTerminationSeconds(60); 38 | // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者 39 | taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 40 | taskExecutor.initialize(); 41 | return taskExecutor; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/controller/FrontDeskController.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.controller; 2 | 3 | import cn.itbat.microsoft.config.GraphProperties; 4 | import cn.itbat.microsoft.service.FrontDeskService; 5 | import cn.itbat.microsoft.vo.BaseResultVo; 6 | import cn.itbat.microsoft.vo.GraphUserVo; 7 | import org.springframework.util.StringUtils; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.annotation.Resource; 14 | 15 | /** 16 | * 前台 17 | * 18 | * @author huahui.wu 19 | * @date 2020年11月24日 16:32:52 20 | */ 21 | @RestController 22 | @RequestMapping("/front") 23 | public class FrontDeskController { 24 | 25 | @Resource 26 | private FrontDeskService frontDeskService; 27 | 28 | @Resource 29 | private GraphProperties graphProperties; 30 | 31 | @GetMapping("/listLicense") 32 | public BaseResultVo listLicense() { 33 | return BaseResultVo.success(frontDeskService.listLicense()); 34 | } 35 | 36 | @PostMapping("/create") 37 | public BaseResultVo createUser(GraphUserVo graphUserVo) { 38 | 39 | // 参数判断 40 | if (graphUserVo == null) { 41 | return BaseResultVo.error("参数为空!"); 42 | } 43 | if (StringUtils.isEmpty(graphUserVo.getAppName())) { 44 | return BaseResultVo.error("参数为空!"); 45 | } 46 | if (StringUtils.isEmpty(graphUserVo.getCode())) { 47 | return BaseResultVo.error("邀请码为空!"); 48 | } 49 | if (StringUtils.isEmpty(graphUserVo.getSkuId())) { 50 | return BaseResultVo.error("订阅为空!"); 51 | } 52 | if (StringUtils.isEmpty(graphUserVo.getPassword())) { 53 | return BaseResultVo.error("密码为空!"); 54 | } 55 | if (StringUtils.isEmpty(graphUserVo.getMailNickname())) { 56 | return BaseResultVo.error("邮箱前缀为空!"); 57 | } 58 | if (StringUtils.isEmpty(graphUserVo.getDisplayName())) { 59 | return BaseResultVo.error("用户名为空!"); 60 | } 61 | return BaseResultVo.success(frontDeskService.create(graphUserVo)); 62 | } 63 | 64 | /** 65 | * 查询可注册地区 66 | * 67 | * @param appName 组织类型 68 | * @return 地区 69 | */ 70 | @GetMapping("/listUsageLocation") 71 | public BaseResultVo listUsageLocation(String appName) { 72 | return BaseResultVo.success(graphProperties.listUsageLocation(appName)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/controller/InvitationCodeController.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.controller; 2 | 3 | import cn.itbat.microsoft.model.Pager; 4 | import cn.itbat.microsoft.model.entity.InvitationCode; 5 | import cn.itbat.microsoft.service.InvitationCodeService; 6 | import cn.itbat.microsoft.vo.BaseResultVo; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.annotation.Resource; 14 | 15 | /** 16 | * @author huahui.wu 17 | * @date 2020年11月24日 16:15:19 18 | */ 19 | @Slf4j 20 | @RestController 21 | @RequestMapping("/microsoft/code") 22 | public class InvitationCodeController { 23 | 24 | @Resource 25 | private InvitationCodeService invitationCodeService; 26 | 27 | @GetMapping("/getCodeStatistics") 28 | public BaseResultVo getCodeStatistics() { 29 | return BaseResultVo.success(invitationCodeService.getStatistics()); 30 | } 31 | 32 | @GetMapping("/list") 33 | public BaseResultVo list(InvitationCode invitationCode, Pager pager) { 34 | return BaseResultVo.success(invitationCodeService.list(invitationCode, pager)); 35 | } 36 | 37 | @PostMapping("/generate") 38 | public BaseResultVo generate(Integer num) { 39 | invitationCodeService.generateInvitationCode(num); 40 | return BaseResultVo.success(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/controller/SystemController.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.controller; 2 | 3 | 4 | import cn.itbat.microsoft.model.support.SystemMonitorInfo; 5 | import cn.itbat.microsoft.vo.BaseResultVo; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | 11 | /** 12 | * 系统 Controller 13 | * 14 | * @author mjj 15 | * @date 2020年08月11日 09:50:37 16 | */ 17 | @RestController 18 | @RequestMapping("/system") 19 | public class SystemController { 20 | 21 | /** 22 | * 获取系统监控信息 23 | */ 24 | @GetMapping("/monitor") 25 | public BaseResultVo monitor() { 26 | return BaseResultVo.success(new SystemMonitorInfo()); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/enums/AccountStatusEnum.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * sku订阅状态 7 | * 8 | * @author mjj 9 | * @date 2019年09月24日 15:41:15 10 | */ 11 | @Getter 12 | public enum AccountStatusEnum { 13 | 14 | /** 15 | * 禁用 16 | */ 17 | DISABLE(Boolean.FALSE, "禁用"), 18 | 19 | /** 20 | * 启用 21 | */ 22 | ENABLE(Boolean.TRUE, "启用"), 23 | 24 | ; 25 | 26 | 27 | private Boolean type; 28 | 29 | private String name; 30 | 31 | 32 | AccountStatusEnum(Boolean type, String name) { 33 | this.name = name; 34 | this.type = type; 35 | } 36 | 37 | public static AccountStatusEnum parse(Boolean type) { 38 | for (AccountStatusEnum item : values()) { 39 | if (item.getType().equals(type)) { 40 | return item; 41 | } 42 | } 43 | return null; 44 | } 45 | 46 | public static String getName(Boolean type) { 47 | for (AccountStatusEnum item : values()) { 48 | if (item.getType().equals(type)) { 49 | return item.getName(); 50 | } 51 | } 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/enums/CapabilityStatusEnum.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * sku订阅状态 7 | * 8 | * @author mjj 9 | * @date 2019年09月24日 15:41:15 10 | */ 11 | @Getter 12 | public enum CapabilityStatusEnum { 13 | 14 | /** 15 | * 禁用 16 | */ 17 | ENABLE("Enabled", "启用"), 18 | 19 | /** 20 | * 启用 21 | */ 22 | WARNING("Warning", "警告"), 23 | 24 | /** 25 | * 启用 26 | */ 27 | SUSPENDED("Suspended", "暂停"), 28 | 29 | /** 30 | * 启用 31 | */ 32 | DELETED("Deleted", "删除"), 33 | 34 | /** 35 | * 启用 36 | */ 37 | LOCKED_OUT("锁定", "锁定"), 38 | 39 | 40 | ; 41 | 42 | 43 | private String type; 44 | 45 | private String name; 46 | 47 | 48 | CapabilityStatusEnum(String type, String name) { 49 | this.name = name; 50 | this.type = type; 51 | } 52 | 53 | public static CapabilityStatusEnum parse(String type) { 54 | for (CapabilityStatusEnum item : values()) { 55 | if (item.getType().equals(type)) { 56 | return item; 57 | } 58 | } 59 | return null; 60 | } 61 | 62 | public static String getName(String type) { 63 | for (CapabilityStatusEnum item : values()) { 64 | if (item.getType().equals(type)) { 65 | return item.getName(); 66 | } 67 | } 68 | return null; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/enums/RefreshTypeEnum.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 刷新缓存类型 7 | * 8 | * @author mjj 9 | * @date 2019年09月24日 15:41:15 10 | */ 11 | @Getter 12 | public enum RefreshTypeEnum { 13 | 14 | /** 15 | * 禁用 16 | */ 17 | USER(1, "用户"), 18 | 19 | /** 20 | * 启用 21 | */ 22 | SUB(2, "订阅"), 23 | 24 | /** 25 | * 启用 26 | */ 27 | DOMAIN(3, "域名"), 28 | ; 29 | 30 | 31 | private Integer type; 32 | 33 | private String name; 34 | 35 | 36 | RefreshTypeEnum(Integer type, String name) { 37 | this.name = name; 38 | this.type = type; 39 | } 40 | 41 | public static RefreshTypeEnum parse(Integer type) { 42 | for (RefreshTypeEnum item : values()) { 43 | if (item.getType().equals(type)) { 44 | return item; 45 | } 46 | } 47 | return null; 48 | } 49 | 50 | public static String getName(Integer type) { 51 | for (RefreshTypeEnum item : values()) { 52 | if (item.getType().equals(type)) { 53 | return item.getName(); 54 | } 55 | } 56 | return null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/model/GraphUser.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.format.annotation.DateTimeFormat; 9 | 10 | /** 11 | * office 365 用户 12 | * 13 | * @author mjj 14 | * @date 2020年05月12日 15:59:03 15 | */ 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class GraphUser { 21 | 22 | /** 23 | * 用户id 24 | */ 25 | private String userId; 26 | 27 | /** 28 | * 订阅 29 | */ 30 | private String skuId; 31 | 32 | /** 33 | * 订阅类型 34 | */ 35 | private String skuType; 36 | 37 | 38 | /** 39 | * 域名 40 | */ 41 | private String domain; 42 | 43 | /** 44 | * 姓 45 | */ 46 | private String surname; 47 | 48 | /** 49 | * 姓 50 | */ 51 | private String givenName; 52 | 53 | /** 54 | * 显示名称 55 | */ 56 | private String displayName; 57 | 58 | /** 59 | * 邮箱前缀 60 | */ 61 | private String mailNickname; 62 | 63 | /** 64 | * 用户名 65 | */ 66 | private String userPrincipalName; 67 | 68 | /** 69 | * 密码 70 | */ 71 | private String password; 72 | 73 | /** 74 | * 密码类型 75 | */ 76 | @Builder.Default 77 | private String passwordPolicies = "DisablePasswordExpiration, DisableStrongPassword"; 78 | 79 | /** 80 | * 手机号码 81 | */ 82 | private String mobilePhone; 83 | 84 | /** 85 | * 邮箱 86 | */ 87 | private String mail; 88 | 89 | /** 90 | * 公司名 91 | */ 92 | private String companyName; 93 | 94 | /** 95 | * 位置 96 | */ 97 | @Builder.Default 98 | private String usageLocation = "CN"; 99 | 100 | /** 101 | * 街道地址 102 | */ 103 | private String streetAddress; 104 | 105 | /** 106 | * 城市 107 | */ 108 | private String city; 109 | 110 | /** 111 | * 省 112 | */ 113 | private String state; 114 | 115 | /** 116 | * 国家 117 | */ 118 | private String country; 119 | 120 | /** 121 | * 是否启用 122 | */ 123 | @Builder.Default 124 | private Boolean accountEnabled = true; 125 | 126 | /** 127 | * 用户创建时间 128 | */ 129 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 130 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 131 | private String createdDateTime; 132 | 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/model/Pager.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.model; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 分页参数 7 | * 8 | * @author mjj 9 | * @date 2020年05月12日 16:30:29 10 | */ 11 | @Data 12 | public class Pager { 13 | 14 | 15 | /** 16 | * 当前页码 17 | */ 18 | private Integer pageIndex; 19 | 20 | /** 21 | * 每页数量 22 | */ 23 | private Integer pageSize; 24 | 25 | /** 26 | * 排序 公共枚举 SqlOrderByEnum 27 | */ 28 | private Integer orderByClause; 29 | 30 | 31 | public Pager() { 32 | 33 | } 34 | 35 | public Pager(Integer orderByClause) { 36 | this.orderByClause = orderByClause; 37 | } 38 | 39 | public Pager(Integer pageIndex, Integer pageSize, Integer orderByClause) { 40 | this.pageIndex = pageIndex; 41 | this.pageSize = pageSize; 42 | this.orderByClause = orderByClause; 43 | } 44 | 45 | public Pager(Integer pageIndex, Integer pageSize) { 46 | this.pageIndex = pageIndex; 47 | this.pageSize = pageSize; 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/model/entity/InvitationCode.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.model.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.format.annotation.DateTimeFormat; 9 | 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import java.util.Date; 15 | 16 | /** 17 | * @author huahui.wu 18 | * @date 2020年11月24日 14:53:11 19 | */ 20 | @Entity(name = "INVITATION_CODE") 21 | @Data 22 | @Builder 23 | @NoArgsConstructor 24 | @AllArgsConstructor 25 | public class InvitationCode { 26 | 27 | @Id 28 | @GeneratedValue(strategy = GenerationType.IDENTITY) 29 | private Integer id; 30 | 31 | /** 32 | * 邀请码 33 | */ 34 | private String code; 35 | 36 | /** 37 | * 是否有效 38 | */ 39 | private Boolean valid; 40 | 41 | /** 42 | * 失效时间 43 | */ 44 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 45 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 46 | private Date expirationTime; 47 | 48 | /** 49 | * 被邀请用户 50 | */ 51 | private String invitedUser; 52 | 53 | /** 54 | * 订阅 55 | */ 56 | private String subscribe; 57 | 58 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 59 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 60 | private Date createTime; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/model/support/Jvm.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.model.support; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * JVM 7 | * 8 | * @author mjj 9 | * @date 2020年05月13日 10:17:33 10 | */ 11 | @Data 12 | public class Jvm { 13 | 14 | /** 15 | * 当前 JVM 占用的内存总数 (M) 16 | */ 17 | private double total; 18 | 19 | /** 20 | * JVM 最大可用内存总数 (M) 21 | */ 22 | private double max; 23 | 24 | /** 25 | * JVM 空闲内存 (M) 26 | */ 27 | private double free; 28 | 29 | /** 30 | * JDK 版本 31 | */ 32 | private String version; 33 | 34 | public Jvm() { 35 | double mb = 1024 * 1024; 36 | Runtime runtime = Runtime.getRuntime(); 37 | this.total = runtime.totalMemory() / mb; 38 | this.free = runtime.freeMemory() / mb; 39 | this.max = runtime.maxMemory() / mb; 40 | this.version = System.getProperty("java.version"); 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/model/support/Mem.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.model.support; 2 | 3 | import com.sun.management.OperatingSystemMXBean; 4 | import lombok.Data; 5 | 6 | import java.lang.management.ManagementFactory; 7 | 8 | /** 9 | * @author mjj 10 | * @date 2020年05月13日 10:17:33 11 | */ 12 | @Data 13 | public class Mem { 14 | 15 | /** 16 | * 内存总量 17 | */ 18 | private double total; 19 | 20 | /** 21 | * 已用内存 22 | */ 23 | private double used; 24 | 25 | /** 26 | * 剩余内存 27 | */ 28 | private double free; 29 | 30 | public Mem() { 31 | double mb = 1024 * 1024; 32 | OperatingSystemMXBean osb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); 33 | // 总的物理内存+虚拟内存 34 | long totalVirtualMemory = osb.getTotalSwapSpaceSize(); 35 | // 剩余的物理内存 36 | long freePhysicalMemorySize = osb.getFreePhysicalMemorySize(); 37 | this.total = totalVirtualMemory / mb; 38 | this.free = freePhysicalMemorySize / mb; 39 | this.used = (totalVirtualMemory - freePhysicalMemorySize) / mb; 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/model/support/Sys.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.model.support; 2 | 3 | import cn.hutool.core.date.BetweenFormater; 4 | import cn.hutool.core.date.DateUtil; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | 8 | import java.lang.management.ManagementFactory; 9 | import java.util.Date; 10 | 11 | /** 12 | * @author mjj 13 | * @date 2020年05月13日 10:17:33 14 | */ 15 | @Data 16 | public class Sys { 17 | 18 | /** 19 | * 项目路径 20 | */ 21 | @Builder.Default 22 | private String projectDir = ""; 23 | 24 | /** 25 | * 操作系统 26 | */ 27 | private String osName; 28 | 29 | /** 30 | * 系统架构 31 | */ 32 | private String osArch; 33 | 34 | /** 35 | * 系统版本 36 | */ 37 | private String osVersion; 38 | 39 | /** 40 | * 启动时间 41 | */ 42 | private String upTime; 43 | 44 | public Sys() { 45 | this.osName = System.getProperty("os.name"); 46 | this.osArch = System.getProperty("os.arch"); 47 | this.osVersion = System.getProperty("os.version"); 48 | long uptime = ManagementFactory.getRuntimeMXBean().getUptime(); 49 | this.upTime = DateUtil.formatBetween(uptime, BetweenFormater.Level.SECOND); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/model/support/SystemMonitorInfo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.model.support; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 系统信息 9 | * 10 | * @author mjj 11 | * @date 2020年05月13日 10:17:33 12 | */ 13 | @Data 14 | public class SystemMonitorInfo implements Serializable { 15 | 16 | /** 17 | * 服务器基本信息 18 | */ 19 | private Sys sys; 20 | 21 | /** 22 | * JVM 信息 23 | */ 24 | private Jvm jvm; 25 | 26 | /** 27 | * 系统内存 28 | */ 29 | private Mem mem; 30 | 31 | public SystemMonitorInfo() { 32 | this.jvm = new Jvm(); 33 | this.sys = new Sys(); 34 | this.mem = new Mem(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/repository/InvitationCodeRepository.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.repository; 2 | 3 | import cn.itbat.microsoft.model.entity.InvitationCode; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author huahui.wu 11 | * @date 2020年11月24日 14:57:25 12 | */ 13 | @Repository 14 | public interface InvitationCodeRepository extends JpaRepository { 15 | 16 | /** 17 | * 根据code查询 18 | * 19 | * @param code 邀请码 20 | * @param valid 是否有效 21 | * @return InvitationCode 22 | */ 23 | InvitationCode findFirstByCodeAndValid(String code, Boolean valid); 24 | 25 | /** 26 | * 批量查询code 27 | * 28 | * @param codes 邀请码集合 29 | * @return List 30 | */ 31 | List findAllByCodeIn(List codes); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/security/Md5PasswordEncoder.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.security; 2 | 3 | import cn.hutool.crypto.SecureUtil; 4 | import org.springframework.security.crypto.password.PasswordEncoder; 5 | 6 | /** 7 | * @author mjj 8 | * @date 2020年05月11日 16:23:11 9 | */ 10 | public class Md5PasswordEncoder implements PasswordEncoder { 11 | 12 | @Override 13 | public String encode(CharSequence rawPassword) { 14 | return SecureUtil.md5(rawPassword.toString()); 15 | } 16 | 17 | @Override 18 | public boolean matches(CharSequence rawPassword, String encodedPassword) { 19 | return rawPassword.toString().equals(SecureUtil.md5(encodedPassword)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/security/MySecurityConfig.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.security; 2 | 3 | import cn.itbat.microsoft.vo.BaseResultVo; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.authentication.BadCredentialsException; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 13 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 14 | import org.springframework.security.crypto.password.PasswordEncoder; 15 | import org.springframework.security.web.firewall.HttpFirewall; 16 | import org.springframework.security.web.firewall.StrictHttpFirewall; 17 | 18 | import javax.annotation.Resource; 19 | import javax.servlet.http.HttpServletResponse; 20 | import java.io.PrintWriter; 21 | 22 | /** 23 | * 自定义 Security 配置类 24 | * 25 | * @author mjj 26 | * @date 2020年08月11日 09:50:37 27 | */ 28 | @EnableWebSecurity 29 | public class MySecurityConfig extends WebSecurityConfigurerAdapter { 30 | 31 | @Resource 32 | private ObjectMapper objectMapper; 33 | 34 | @Override 35 | protected void configure(HttpSecurity http) throws Exception { 36 | http 37 | // .authenticationProvider(authenticationProvider()) 38 | .exceptionHandling() 39 | // 未登录时,进行 json 格式的提示. 40 | .authenticationEntryPoint((request, response, authException) -> { 41 | response.setContentType("application/json;charset=utf-8"); 42 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 43 | PrintWriter out = response.getWriter(); 44 | out.write(objectMapper.writeValueAsString(BaseResultVo.error(1001, "未登录"))); 45 | out.flush(); 46 | out.close(); 47 | }) 48 | .and() 49 | .authorizeRequests() 50 | .antMatchers("/").permitAll() 51 | .antMatchers("/microsoft/**").authenticated() 52 | .and() 53 | // 使用自带的登录 54 | .formLogin().loginPage("/login").usernameParameter("userName").passwordParameter("password") 55 | // 登录失败,返回json 56 | .failureHandler((request, response, ex) -> { 57 | response.setContentType("application/json;charset=utf-8"); 58 | response.setStatus(HttpServletResponse.SC_FORBIDDEN); 59 | PrintWriter out = response.getWriter(); 60 | String msg; 61 | if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) { 62 | msg = "用户名或密码错误"; 63 | } else { 64 | msg = "登录失败"; 65 | } 66 | out.write(objectMapper.writeValueAsString(BaseResultVo.error(msg))); 67 | out.flush(); 68 | out.close(); 69 | }) 70 | // 登录成功,返回json 71 | .successHandler((request, response, authentication) -> { 72 | response.setContentType("application/json;charset=utf-8"); 73 | PrintWriter out = response.getWriter(); 74 | out.write(objectMapper.writeValueAsString(BaseResultVo.success(authentication))); 75 | out.flush(); 76 | out.close(); 77 | }) 78 | .and() 79 | .exceptionHandling() 80 | // 没有权限,返回json 81 | .accessDeniedHandler((request, response, ex) -> { 82 | response.setContentType("application/json;charset=utf-8"); 83 | response.setStatus(HttpServletResponse.SC_FORBIDDEN); 84 | PrintWriter out = response.getWriter(); 85 | out.write(objectMapper.writeValueAsString(BaseResultVo.error("权限不足"))); 86 | out.flush(); 87 | out.close(); 88 | }) 89 | .and() 90 | .logout() 91 | // 退出成功,返回 json 92 | .logoutSuccessHandler((request, response, authentication) -> { 93 | response.setContentType("application/json;charset=utf-8"); 94 | PrintWriter out = response.getWriter(); 95 | out.write(objectMapper.writeValueAsString(BaseResultVo.error("注销成功"))); 96 | out.flush(); 97 | out.close(); 98 | }) 99 | .and() 100 | .logout().permitAll(); 101 | 102 | http.cors(); 103 | http.csrf().disable(); 104 | http.headers().frameOptions().sameOrigin(); 105 | } 106 | 107 | @Bean 108 | public HttpFirewall allowUrlEncodedSlashHttpFirewall() { 109 | StrictHttpFirewall firewall = new StrictHttpFirewall(); 110 | firewall.setAllowUrlEncodedPercent(true); 111 | return firewall; 112 | } 113 | 114 | @Override 115 | public void configure(AuthenticationManagerBuilder web) throws Exception { 116 | web.userDetailsService(myUserDetailsServiceImpl()).passwordEncoder(passwordEncoder()); 117 | } 118 | 119 | @Bean 120 | public MyUserDetailsServiceImpl myUserDetailsServiceImpl() { 121 | return new MyUserDetailsServiceImpl(); 122 | } 123 | 124 | @Override 125 | public void configure(WebSecurity web) { 126 | // 对于在 header 里面增加 token 等类似情况,放行所有 OPTIONS 请求。 127 | web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**"); 128 | web.httpFirewall(allowUrlEncodedSlashHttpFirewall()); 129 | } 130 | 131 | @Bean 132 | public static PasswordEncoder passwordEncoder() { 133 | return new Md5PasswordEncoder(); 134 | } 135 | 136 | } -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/security/MyUserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.security; 2 | 3 | import cn.itbat.microsoft.config.GraphProperties; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | /** 17 | * @author mjj 18 | * @date 2020年05月11日 16:23:11 19 | */ 20 | public class MyUserDetailsServiceImpl implements UserDetailsService { 21 | 22 | @Resource 23 | private GraphProperties graphProperties; 24 | 25 | /** 26 | * 授权的时候是对角色授权,认证的时候应该基于资源,而不是角色,因为资源是不变的,而用户的角色是会变的 27 | */ 28 | @Override 29 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 30 | if (!username.equals(graphProperties.getUserName())){ 31 | throw new UsernameNotFoundException("用户名或密码错误!"); 32 | } 33 | return new User(graphProperties.getUserName(), graphProperties.getPassword(), Collections.emptyList()); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/service/FrontDeskService.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.service; 2 | 3 | import cn.itbat.microsoft.vo.FontSkuSku; 4 | import cn.itbat.microsoft.vo.FontUser; 5 | import cn.itbat.microsoft.vo.GraphUserVo; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author huahui.wu 11 | * @date 2020年11月24日 16:34:06 12 | */ 13 | public interface FrontDeskService { 14 | 15 | /** 16 | * 创建用户 17 | * 18 | * @param graphUserVo 用户信息 19 | * @return FontUser 20 | */ 21 | FontUser create(GraphUserVo graphUserVo); 22 | 23 | /** 24 | * 查询用户的订阅 25 | * 26 | * @return 成功 27 | */ 28 | List listLicense(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/service/GraphService.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.service; 2 | 3 | import cn.itbat.microsoft.model.GraphUser; 4 | import cn.itbat.microsoft.vo.DirectoryRoleVo; 5 | import cn.itbat.microsoft.vo.GraphUserVo; 6 | import com.microsoft.graph.models.*; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | /** 13 | * graph 的具体操作方法 14 | * 15 | * @author mjj 16 | * @date 2020年05月12日 16:30:29 17 | */ 18 | public interface GraphService { 19 | 20 | /** 21 | * 获取所有的订阅 22 | * 23 | * @param appName appName 24 | * @return List 25 | */ 26 | List getSubscribedSkus(String appName); 27 | 28 | /** 29 | * 获取绑定的域名 30 | * 31 | * @param appName appName 32 | * @return 域名 33 | */ 34 | List getDomains(String appName); 35 | 36 | /** 37 | * 获取当前用户信息 38 | * 39 | * @param appName appName 40 | * @return User 41 | */ 42 | User getUser(String appName); 43 | 44 | /** 45 | * 根据用户id获取用户 46 | * 47 | * @param appName appName 48 | * @param userId 用户id 49 | * @return User 50 | */ 51 | User getUser(String appName, String userId); 52 | 53 | /** 54 | * 获取所有的用户 55 | * 56 | * @param appName appName 57 | * @return IUserCollectionPage 58 | */ 59 | List getUsers(String appName); 60 | 61 | 62 | /** 63 | * 根据用户id获取用户 64 | * 65 | * @param graphUserVo 查询条件 66 | * @return List 67 | */ 68 | List getUsers(GraphUserVo graphUserVo); 69 | 70 | 71 | /** 72 | * 创建用户 73 | * 74 | * @param appName appName 75 | * @param graphUser 用户信息 76 | * @return User 77 | */ 78 | User createUser(String appName, GraphUser graphUser); 79 | 80 | /** 81 | * 添加许可证 82 | * 83 | * @param appName appName 84 | * @param skuId 订阅 85 | * @param userId 用户 86 | * @return User 87 | */ 88 | User addLicense(String appName, String userId, String skuId); 89 | 90 | 91 | /** 92 | * 取消许可证 93 | * 94 | * @param appName 组织类型 95 | * @param skuId 许可证 96 | * @param userId 用户 97 | * @return 用户信息 98 | */ 99 | User cancelLicense(String appName, String skuId, String userId); 100 | 101 | /** 102 | * 启用禁用账户 103 | * 104 | * @param appName 组织类型 105 | * @param userId 用户 106 | * @param accountEnabled 启用禁用 107 | */ 108 | void enableDisableUser(String appName, String userId, Boolean accountEnabled); 109 | 110 | /** 111 | * 重置密码 112 | * 113 | * @param appName 组织类型 114 | * @param userId 用户 115 | * @param password 密码 116 | */ 117 | void resetPassword(String appName, String userId, String password); 118 | 119 | 120 | /** 121 | * 更新用户信息 122 | * 123 | * @param appName 组织类型 124 | * @param graphUser 用户 125 | */ 126 | void updateUser(String appName, GraphUser graphUser); 127 | 128 | /** 129 | * 删除用户 130 | * 131 | * @param appName appName 132 | * @param userName 用户信息 133 | */ 134 | void deletedUser(String appName, String userName); 135 | 136 | /** 137 | * 列出当前激活的用户角色 138 | * 139 | * @param appName appName 140 | */ 141 | List listDirectoryRoles(String appName); 142 | 143 | /** 144 | * 列出用户角色下的用户 145 | * 146 | * @param appName appName 147 | * @param objectId --both the object ID and template ID are OK.-- Now only object ID is effective. 148 | */ 149 | List listMembersOfADirectoryRole(String appName, String objectId); 150 | 151 | /** 152 | * 查询当前有哪些角色,并查询其角色下有哪些用户 153 | * 154 | * @param appName appName 155 | */ 156 | Map> directoryRoleToUserNameMap(String appName); 157 | 158 | /** 159 | * 增加用户角色 160 | * 161 | * @param appName 组织类型 162 | * @param userId 用户id 163 | * @param roleId 角色id 164 | * @return 结果 165 | */ 166 | Boolean addDirectoryRoleMember(String appName, String userId, String roleId); 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/service/InvitationCodeService.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.service; 2 | 3 | import cn.itbat.microsoft.model.Pager; 4 | import cn.itbat.microsoft.model.entity.InvitationCode; 5 | import org.springframework.data.domain.Page; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * 邀请码 12 | * 13 | * @author huahui.wu 14 | * @date 2020年11月24日 15:17:45 15 | */ 16 | public interface InvitationCodeService { 17 | /** 18 | * 根据code查询(有效的邀请码) 19 | * 20 | * @param code 邀请码 21 | * @return InvitationCode 22 | */ 23 | InvitationCode selectByCode(String code); 24 | 25 | /** 26 | * 更新邀请码 27 | * 28 | * @param invitationCode 邀请码信息 29 | */ 30 | void update(InvitationCode invitationCode); 31 | 32 | /** 33 | * 生成邀请码 34 | * 35 | * @param num 数量 36 | */ 37 | void generateInvitationCode(Integer num); 38 | 39 | /** 40 | * 查询 41 | * 42 | * @param invitationCode 查询参数 43 | * @return List 44 | */ 45 | List list(InvitationCode invitationCode); 46 | 47 | /** 48 | * 分页查询 49 | * 50 | * @param invitationCode 查询参数 51 | * @param pager 分页参数 52 | * @return Page 53 | */ 54 | Page list(InvitationCode invitationCode, Pager pager); 55 | 56 | /** 57 | * 统计信息 58 | * 59 | * @return 结果 60 | */ 61 | Map getStatistics(); 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/service/MailService.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.service; 2 | 3 | /** 4 | * @author huahui.wu 5 | * @date 2020年11月23日 17:28:09 6 | */ 7 | public interface MailService { 8 | 9 | /** 10 | * 发送邮件 11 | * 12 | * @param to 接收人 13 | * @param userName office用户名 14 | * @param password office密码 15 | */ 16 | void sendMail(String to, String userName, String password); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/service/Microsoft365Service.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.service; 2 | 3 | 4 | import cn.itbat.microsoft.model.GraphUser; 5 | import cn.itbat.microsoft.model.Pager; 6 | import cn.itbat.microsoft.utils.PageInfo; 7 | import cn.itbat.microsoft.vo.*; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author mjj   (;¬_¬)   13 | * @version 1.0  14 | * @date 2019-07-16 19:06 15 | **/ 16 | public interface Microsoft365Service { 17 | 18 | /** 19 | * 刷新 20 | * 21 | * @param appName 组织类型 22 | * @param type 刷新类型 23 | */ 24 | void refresh(String appName, Integer type); 25 | 26 | /** 27 | * 查询订阅 28 | * 29 | * @param appName 组织类型 30 | * @return List 31 | */ 32 | List getSubscribed(String appName); 33 | 34 | 35 | /** 36 | * 查询域名 37 | * 38 | * @param appName 组织类型 39 | * @return List 40 | */ 41 | List getDomainVo(String appName); 42 | 43 | /** 44 | * 创建用户 45 | * 46 | * @param graphUserVo 用户信息 47 | * @return GraphUser 48 | */ 49 | GraphUserVo create(GraphUserVo graphUserVo); 50 | 51 | /** 52 | * 更新用户信息 53 | * 54 | * @param appName 组织类型 55 | * @param graphUser 用户 56 | */ 57 | 58 | void updateUser(String appName, GraphUser graphUser); 59 | 60 | /** 61 | * 启用禁用账户 62 | * 63 | * @param appName 组织类型 64 | * @param userId 用户 65 | * @param accountEnabled 启用禁用 66 | */ 67 | void enableDisableUser(String appName, String userId, Boolean accountEnabled); 68 | 69 | /** 70 | * 添加订阅 71 | * 72 | * @param appName 组织类型 73 | * @param userId 用户 74 | * @param skuId 订阅 75 | * @return 用户信息 76 | */ 77 | GraphUserVo addLicense(String appName, String userId, String skuId); 78 | 79 | 80 | /** 81 | * 取消订阅 82 | * 83 | * @param appName 组织类型 84 | * @param skuId 订阅 85 | * @param userId 用户 86 | * @return 用户信息 87 | */ 88 | GraphUserVo cancelLicense(String appName, String userId, String skuId); 89 | 90 | /** 91 | * 重置密码 92 | * 93 | * @param appName 组织类型 94 | * @param userName 用户 95 | * @param password 密码 96 | */ 97 | void resetPassword(String appName, String userName, String password); 98 | 99 | 100 | /** 101 | * 根据用户Id获取 102 | * 103 | * @param appName 组织类型 104 | * @param userId 用户 105 | * @return user 106 | */ 107 | GraphUserVo getGraphUserVo(String appName, String userId); 108 | 109 | 110 | /** 111 | * 获取所有的用户 112 | * 113 | * @param appName 组织类型 114 | * @param pager 分页参数 115 | * @return List 116 | */ 117 | PageInfo getGraphUserVos(String appName, Pager pager); 118 | 119 | /** 120 | * 根据用户id获取用户 121 | * 122 | * @param graphUserVo 查询条件 123 | * @param pager 分页参数 124 | * @param sorter 排序参数 125 | * @return List 126 | */ 127 | PageInfo getGraphUserVos(GraphUserVo graphUserVo, Pager pager, GraphUserSorterVo sorter); 128 | 129 | 130 | /** 131 | * 删除指定用户 132 | * 133 | * @param type 组织类型 134 | * @param userName 用户名 135 | */ 136 | void deletedUser(String type, String userName); 137 | 138 | /** 139 | * 删除用户 140 | * 141 | * @param appName 组织类型 142 | */ 143 | void deletedUsers(String appName); 144 | 145 | /** 146 | * 批量创建创建Office用户 147 | * 148 | * @param num num 149 | * @param appName 组织类型 150 | * @param skuId 订阅 151 | * @param domain 域名后缀 152 | */ 153 | void createBatch(Integer num, String appName, String skuId, String domain, String password, String usageLocation); 154 | 155 | /** 156 | * 首页 157 | * 158 | * @param appName 组织类型 159 | * @return 首页数据 160 | */ 161 | HomePageVo homePage(String appName); 162 | 163 | /** 164 | * 统计许可证信息 165 | * 166 | * @param appName 组织类型 167 | * @return 结果 168 | */ 169 | StatisticsVo getLicenseStatistics(String appName); 170 | 171 | /** 172 | * 统计用户信息 173 | * 174 | * @param appName 组织类型 175 | * @return 结果 176 | */ 177 | StatisticsVo getUsersStatistics(String appName); 178 | 179 | /** 180 | * 获取用户角色列表 181 | * 182 | * @param appName 组织类型 183 | * @return 结果 184 | */ 185 | List listRoles(String appName); 186 | 187 | /** 188 | * 增加用户角色 189 | * 190 | * @param appName 组织类型 191 | * @param userId 用户id 192 | * @param roleId 角色id 193 | * @return 结果 194 | */ 195 | Boolean addDirectoryRoleMember(String appName, String userId, String roleId); 196 | } 197 | 198 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/service/impl/FrontDeskServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.service.impl; 2 | 3 | import cn.itbat.microsoft.cache.GraphCache; 4 | import cn.itbat.microsoft.config.GraphProperties; 5 | import cn.itbat.microsoft.model.entity.InvitationCode; 6 | import cn.itbat.microsoft.service.FrontDeskService; 7 | import cn.itbat.microsoft.service.InvitationCodeService; 8 | import cn.itbat.microsoft.service.Microsoft365Service; 9 | import cn.itbat.microsoft.vo.*; 10 | import org.springframework.beans.BeanUtils; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | import org.springframework.util.CollectionUtils; 15 | import org.springframework.util.StringUtils; 16 | 17 | import javax.annotation.Resource; 18 | import java.util.ArrayList; 19 | import java.util.Date; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * @author huahui.wu 25 | * @date 2020年11月24日 16:34:12 26 | */ 27 | @Service 28 | public class FrontDeskServiceImpl implements FrontDeskService { 29 | 30 | @Resource 31 | private Microsoft365Service microsoft365Service; 32 | 33 | @Resource 34 | private InvitationCodeService invitationCodeService; 35 | 36 | @Resource 37 | private GraphCache graphCache; 38 | 39 | @Resource 40 | private GraphProperties graphProperties; 41 | 42 | @Value("${graph.invite}") 43 | private String invite; 44 | @Value("${graph.inviteDomain}") 45 | private String inviteDomain; 46 | 47 | @Transactional(rollbackFor = Exception.class) 48 | @Override 49 | public FontUser create(GraphUserVo graphUserVo) { 50 | // 判断邀请码是否有效 51 | InvitationCode invitationCode = invitationCodeService.selectByCode(graphUserVo.getCode()); 52 | if (invitationCode == null || !invitationCode.getValid()) { 53 | throw new RuntimeException("邀请码已失效!"); 54 | } 55 | if (!StringUtils.isEmpty(inviteDomain)) { 56 | List domainVo = microsoft365Service.getDomainVo(graphUserVo.getAppName()); 57 | if (!CollectionUtils.isEmpty(domainVo)) { 58 | if (domainVo.stream().map(DomainVo::getId).collect(Collectors.toList()).contains(inviteDomain)) { 59 | graphUserVo.setDomain(inviteDomain); 60 | } 61 | } 62 | } 63 | GraphUserVo userVo = microsoft365Service.create(graphUserVo); 64 | invitationCode.setValid(Boolean.FALSE); 65 | invitationCode.setExpirationTime(new Date()); 66 | invitationCode.setInvitedUser(userVo.getUserPrincipalName()); 67 | invitationCode.setSubscribe(StringUtils.isEmpty(userVo.getSkuType()) ? graphProperties.getSubConfigName(graphUserVo.getSkuId()) : graphProperties.getSubConfig(graphUserVo.getSkuType()).getDisplayName()); 68 | invitationCodeService.update(invitationCode); 69 | return FontUser.builder().displayName(userVo.getDisplayName()).userPrincipalName(userVo.getUserPrincipalName()).password(userVo.getPassword()).build(); 70 | } 71 | 72 | @Override 73 | public List listLicense() { 74 | List appNames = graphProperties.getConfigs().stream().map(GraphProperties.GraphConfig::getAppName).collect(Collectors.toList()); 75 | String[] split = invite.split(","); 76 | 77 | List fontSkuSkus = new ArrayList<>(); 78 | for (String appName : split) { 79 | if (!appNames.contains(appName)) { 80 | continue; 81 | } 82 | List subscribed = microsoft365Service.getSubscribed(appName); 83 | if (!CollectionUtils.isEmpty(subscribed)) { 84 | for (SubscribedSkuVo subscribedSkuVo : subscribed) { 85 | FontSkuSku fontSkuSku = new FontSkuSku(); 86 | BeanUtils.copyProperties(subscribedSkuVo, fontSkuSku); 87 | fontSkuSkus.add(fontSkuSku); 88 | } 89 | } 90 | } 91 | return fontSkuSkus; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/service/impl/InvitationCodeServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.service.impl; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import cn.hutool.core.util.RandomUtil; 5 | import cn.itbat.microsoft.model.Pager; 6 | import cn.itbat.microsoft.model.entity.InvitationCode; 7 | import cn.itbat.microsoft.repository.InvitationCodeRepository; 8 | import cn.itbat.microsoft.service.InvitationCodeService; 9 | import org.springframework.data.domain.*; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | import org.springframework.util.CollectionUtils; 13 | import org.springframework.util.StringUtils; 14 | 15 | import javax.annotation.Resource; 16 | import java.util.*; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * 邀请码 21 | * 22 | * @author huahui.wu 23 | * @date 2020年11月24日 15:18:03 24 | */ 25 | @Service 26 | public class InvitationCodeServiceImpl implements InvitationCodeService { 27 | 28 | @Resource 29 | private InvitationCodeRepository invitationCodeRepository; 30 | 31 | @Override 32 | public InvitationCode selectByCode(String code) { 33 | return invitationCodeRepository.findFirstByCodeAndValid(code, Boolean.TRUE); 34 | } 35 | 36 | @Transactional(rollbackFor = Exception.class) 37 | @Override 38 | public void update(InvitationCode invitationCode) { 39 | invitationCodeRepository.save(invitationCode); 40 | } 41 | 42 | @Transactional(rollbackFor = Exception.class) 43 | @Override 44 | public void generateInvitationCode(Integer num) { 45 | List invitationCodes = new ArrayList<>(); 46 | for (int i = 0; i < num; i++) { 47 | String randomString = "G-" + DateUtil.thisDayOfMonth() + "-" + RandomUtil.randomStringUpper(6); 48 | invitationCodes.add(InvitationCode.builder().code(randomString).createTime(new Date()).valid(Boolean.TRUE).build()); 49 | } 50 | List codes = invitationCodeRepository.findAllByCodeIn(invitationCodes.stream().map(InvitationCode::getCode).collect(Collectors.toList())); 51 | if (!CollectionUtils.isEmpty(codes)) { 52 | List userDelete = invitationCodes.stream().filter(l -> codes.stream().map(InvitationCode::getCode).collect(Collectors.toList()).contains(l.getCode())).collect(Collectors.toList()); 53 | invitationCodeRepository.saveAll(userDelete); 54 | } else { 55 | invitationCodeRepository.saveAll(invitationCodes); 56 | } 57 | } 58 | 59 | @Override 60 | public List list(InvitationCode invitationCode) { 61 | return invitationCodeRepository.findAll(Example.of(invitationCode)); 62 | } 63 | 64 | @Override 65 | public Page list(InvitationCode invitationCode, Pager pager) { 66 | // Sort sort = new Sort(Sort.Direction.DESC, Collections.singletonList("id")); 67 | if (StringUtils.isEmpty(invitationCode.getCode())) { 68 | invitationCode.setCode(null); 69 | } 70 | return invitationCodeRepository.findAll(Example.of(invitationCode, ExampleMatcher.matching() 71 | .withIgnoreNullValues()), PageRequest.of(pager.getPageIndex() - 1, pager.getPageSize())); 72 | } 73 | 74 | @Override 75 | public Map getStatistics() { 76 | List all = invitationCodeRepository.findAll(); 77 | if (CollectionUtils.isEmpty(all)) { 78 | return new HashMap<>(0); 79 | } 80 | Map result = new HashMap<>(3); 81 | result.put("codes", all.size()); 82 | result.put("valid", all.stream().filter(InvitationCode::getValid).count()); 83 | result.put("invalid", all.stream().filter(l -> !l.getValid()).count()); 84 | result.put("users", all.stream().filter(l -> l.getInvitedUser() != null).count()); 85 | return result; 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/service/impl/MailServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.service.impl; 2 | 3 | import cn.itbat.microsoft.service.MailService; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.mail.SimpleMailMessage; 6 | import org.springframework.mail.javamail.JavaMailSender; 7 | import org.springframework.scheduling.annotation.Async; 8 | import org.springframework.stereotype.Service; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @author huahui.wu 14 | * @date 2020年11月23日 17:28:17 15 | */ 16 | @Service 17 | public class MailServiceImpl implements MailService { 18 | 19 | @Resource 20 | private JavaMailSender mailSender; 21 | 22 | @Value("${spring.mail.username}") 23 | private String from; 24 | 25 | @Async("asyncPoolTaskExecutor") 26 | @Override 27 | public void sendMail(String to, String userName, String password) { 28 | //简单邮件 29 | SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); 30 | simpleMailMessage.setFrom(from); 31 | simpleMailMessage.setTo(to); 32 | simpleMailMessage.setSubject("Office 365帐户信息"); 33 | simpleMailMessage.setText("已创建或修改用户帐户\n" + 34 | " \n" + 35 | "用户名: " + userName + "\n" + 36 | "临时密码: " + password + "\n" + 37 | " \n" + 38 | " \n" + 39 | "要执行的后续步骤如下: \n" + 40 | "登陆地址:https://www.office.com/\n"); 41 | mailSender.send(simpleMailMessage); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/utils/HttpClientUtils.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.utils; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.PrintWriter; 7 | import java.net.URL; 8 | import java.net.URLConnection; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * http 请求工具 14 | * 15 | * @author mjj 16 | */ 17 | public class HttpClientUtils { 18 | /** 19 | * 向指定URL发送GET方法的请求 20 | * 21 | * @param url 发送请求的URL 22 | * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 23 | * @return URL 所代表远程资源的响应结果 24 | */ 25 | public static String sendGet(String url, String param) { 26 | StringBuilder result = new StringBuilder(); 27 | BufferedReader in = null; 28 | try { 29 | String urlNameString = url + "?" + param; 30 | URL realUrl = new URL(urlNameString); 31 | // 打开和URL之间的连接 32 | URLConnection connection = realUrl.openConnection(); 33 | // 设置通用的请求属性 34 | connection.setRequestProperty("accept", "*/*"); 35 | connection.setRequestProperty("connection", "Keep-Alive"); 36 | connection.setRequestProperty("user-agent", 37 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); 38 | // 建立实际的连接 39 | connection.connect(); 40 | // 获取所有响应头字段 41 | Map> map = connection.getHeaderFields(); 42 | // 遍历所有的响应头字段 43 | for (String key : map.keySet()) { 44 | System.out.println(key + "--->" + map.get(key)); 45 | } 46 | // 定义 BufferedReader输入流来读取URL的响应 47 | in = new BufferedReader(new InputStreamReader( 48 | connection.getInputStream())); 49 | String line; 50 | while ((line = in.readLine()) != null) { 51 | result.append(line); 52 | } 53 | } catch (Exception e) { 54 | System.out.println("发送GET请求出现异常!" + e); 55 | e.printStackTrace(); 56 | } 57 | // 使用finally块来关闭输入流 58 | finally { 59 | try { 60 | if (in != null) { 61 | in.close(); 62 | } 63 | } catch (Exception e2) { 64 | e2.printStackTrace(); 65 | } 66 | } 67 | return result.toString(); 68 | } 69 | 70 | /** 71 | * 向指定 URL 发送POST方法的请求 72 | * 73 | * @param url 发送请求的 URL 74 | * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 75 | * @return 所代表远程资源的响应结果 76 | */ 77 | public static String sendPost(String url, String param) { 78 | PrintWriter out = null; 79 | BufferedReader in = null; 80 | StringBuilder result = new StringBuilder(); 81 | try { 82 | URL realUrl = new URL(url); 83 | // 打开和URL之间的连接 84 | URLConnection conn = realUrl.openConnection(); 85 | // 设置通用的请求属性 86 | conn.setRequestProperty("accept", "*/*"); 87 | conn.setRequestProperty("connection", "Keep-Alive"); 88 | conn.setRequestProperty("user-agent", 89 | "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); 90 | // 发送POST请求必须设置如下两行 91 | conn.setDoOutput(true); 92 | conn.setDoInput(true); 93 | // 获取URLConnection对象对应的输出流 94 | out = new PrintWriter(conn.getOutputStream()); 95 | // 发送请求参数 96 | out.print(param); 97 | // flush输出流的缓冲 98 | out.flush(); 99 | // 定义BufferedReader输入流来读取URL的响应 100 | in = new BufferedReader( 101 | new InputStreamReader(conn.getInputStream())); 102 | String line; 103 | while ((line = in.readLine()) != null) { 104 | result.append(line); 105 | } 106 | } catch (Exception e) { 107 | System.out.println("发送 POST 请求出现异常!" + e); 108 | e.printStackTrace(); 109 | } 110 | //使用finally块来关闭输出流、输入流 111 | finally { 112 | try { 113 | if (out != null) { 114 | out.close(); 115 | } 116 | if (in != null) { 117 | in.close(); 118 | } 119 | } catch (IOException ex) { 120 | ex.printStackTrace(); 121 | } 122 | } 123 | return result.toString(); 124 | } 125 | } -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/utils/IpUtils.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.utils; 2 | 3 | import java.net.Inet4Address; 4 | import java.net.InetAddress; 5 | import java.net.NetworkInterface; 6 | import java.net.SocketException; 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.Enumeration; 10 | 11 | /** 12 | * @author huahui.wu 13 | * @date 2020年10月28日 17:12:27 14 | */ 15 | public class IpUtils { 16 | public static void main(String[] args) { 17 | System.out.println( getLocalIpAddr()); 18 | } 19 | 20 | public static ArrayList getLocalIpAddr() { 21 | ArrayList ipList = new ArrayList(); 22 | InetAddress[] addrList; 23 | try { 24 | Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); 25 | while (interfaces.hasMoreElements()) { 26 | NetworkInterface ni = (NetworkInterface) interfaces.nextElement(); 27 | Enumeration ipAddrEnum = ni.getInetAddresses(); 28 | while (ipAddrEnum.hasMoreElements()) { 29 | InetAddress addr = (InetAddress) ipAddrEnum.nextElement(); 30 | if (addr.isLoopbackAddress() == true) { 31 | continue; 32 | } 33 | 34 | String ip = addr.getHostAddress(); 35 | if (ip.indexOf(":") != -1) { 36 | //skip the IPv6 addr 37 | continue; 38 | } 39 | ipList.add(ip); 40 | } 41 | } 42 | 43 | Collections.sort(ipList); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | 48 | return ipList; 49 | } 50 | /** 51 | * 获取本地真正的IP地址,即获得有线或者 无线WiFi 地址。 52 | * 过滤虚拟机、蓝牙等地址 53 | * 54 | * @return IPv4 55 | */ 56 | public static String getRealIP() { 57 | try { 58 | Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces(); 59 | while (allNetInterfaces.hasMoreElements()) { 60 | NetworkInterface netInterface = allNetInterfaces.nextElement(); 61 | // 去除回环接口,子接口,未运行和接口 62 | if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) { 63 | continue; 64 | } 65 | 66 | if (!netInterface.getDisplayName().contains("Intel") 67 | && !netInterface.getDisplayName().contains("Realtek") 68 | && !netInterface.getDisplayName().contains("Ethernet")) { 69 | continue; 70 | } 71 | 72 | Enumeration addresses = netInterface.getInetAddresses(); 73 | while (addresses.hasMoreElements()) { 74 | InetAddress ip = addresses.nextElement(); 75 | if (ip != null) { 76 | // ipv4 77 | if (ip instanceof Inet4Address) { 78 | return ip.getHostAddress(); 79 | } 80 | } 81 | } 82 | break; 83 | } 84 | } catch (SocketException e) { 85 | //捕获异常 86 | } 87 | return null; 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/utils/PageInfo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.utils; 2 | 3 | 4 | import cn.itbat.microsoft.model.Pager; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | /** 10 | * 自定义内存分页 11 | * 12 | * @author mjj 13 | * @date 2020年08月06日 19:23:16 14 | */ 15 | public class PageInfo { 16 | 17 | private long total; 18 | 19 | private Integer pageNum; 20 | 21 | private Integer pageSize; 22 | 23 | private List list; 24 | 25 | public PageInfo() { 26 | 27 | } 28 | 29 | public PageInfo(List list, Pager pager) { 30 | if (list.isEmpty()) { 31 | this.total = 0L; 32 | this.pageNum = 1; 33 | this.pageSize = 10; 34 | this.list = Collections.emptyList(); 35 | } else { 36 | this.total = list.size(); 37 | this.list = list; 38 | this.pageNum = (pager.getPageSize() == null || 0 == pager.getPageIndex()) ? 1 : pager.getPageIndex(); 39 | this.pageSize = (pager.getPageIndex() == null || 0 == pager.getPageSize()) ? 10 : pager.getPageSize(); 40 | this.paging(); 41 | } 42 | } 43 | 44 | private void paging() { 45 | Integer totalNum = list.size(); 46 | Integer totalPage = 0; 47 | if (totalNum > 0) { 48 | totalPage = totalNum % pageSize == 0 ? totalNum / pageSize : totalNum / pageSize + 1; 49 | } 50 | if (pageNum > totalPage) { 51 | pageNum = totalPage; 52 | } 53 | int startPoint = (pageNum - 1) * pageSize; 54 | int endPoint = startPoint + pageSize; 55 | if (totalNum <= endPoint) { 56 | endPoint = totalNum; 57 | } 58 | //分页处理 59 | this.list = list.subList(startPoint, endPoint); 60 | } 61 | 62 | public List getList() { 63 | return this.list; 64 | } 65 | 66 | public long getTotal() { 67 | return this.total; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/utils/PasswordGenerator.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Random; 7 | 8 | /** 9 | * @author mjj 10 | * @date 2020年05月28日 16:37:46 11 | */ 12 | 13 | public class PasswordGenerator { 14 | /** 15 | * 密码能包含的特殊字符 16 | */ 17 | public static final char[] ALLOWED_SPECIAL_CHARACTORS = { 18 | '`', '~', '@', '#', '$', '%', '^', '&', 19 | '*', '(', ')', '-', '_', '=', '+', '[', 20 | '{', '}', ']', '\\', '|', ';', ':', '"', 21 | '\'', ',', '<', '.', '>', '/', '?'}; 22 | private static final int LETTER_RANGE = 26; 23 | private static final int NUMBER_RANGE = 10; 24 | private static final Random RANDOM = new Random(); 25 | /** 26 | * 密码的长度 27 | */ 28 | private int passwordLength; 29 | /** 30 | * 密码包含字符的最少种类 31 | */ 32 | private int minVariousType; 33 | 34 | public PasswordGenerator(int passwordLength, int minVariousType) { 35 | if (minVariousType > CharacterType.values().length) { 36 | minVariousType = CharacterType.values().length; 37 | } 38 | if (minVariousType > passwordLength) { 39 | minVariousType = passwordLength; 40 | } 41 | this.passwordLength = passwordLength; 42 | this.minVariousType = minVariousType; 43 | } 44 | 45 | public String generateRandomPassword() { 46 | char[] password = new char[passwordLength]; 47 | List pwCharsIndex = new ArrayList(); 48 | for (int i = 0; i < password.length; i++) { 49 | pwCharsIndex.add(i); 50 | } 51 | List takeTypes = new ArrayList(Arrays.asList(CharacterType.values())); 52 | List fixedTypes = Arrays.asList(CharacterType.values()); 53 | int typeCount = 0; 54 | while (pwCharsIndex.size() > 0) { 55 | //随机填充一位密码 56 | int pwIndex = pwCharsIndex.remove(RANDOM.nextInt(pwCharsIndex.size())); 57 | Character c; 58 | //生成不同种类字符 59 | if (typeCount < minVariousType) { 60 | c = generateCharacter(takeTypes.remove(RANDOM.nextInt(takeTypes.size()))); 61 | typeCount++; 62 | } else { 63 | //随机生成所有种类密码 64 | c = generateCharacter(fixedTypes.get(RANDOM.nextInt(fixedTypes.size()))); 65 | } 66 | password[pwIndex] = c; 67 | } 68 | return String.valueOf(password); 69 | } 70 | 71 | private Character generateCharacter(CharacterType type) { 72 | Character c = null; 73 | int rand; 74 | switch (type) { 75 | //随机小写字母 76 | case LOWERCASE: 77 | rand = RANDOM.nextInt(LETTER_RANGE); 78 | rand += 97; 79 | c = (char) rand; 80 | break; 81 | //随机大写字母 82 | case UPPERCASE: 83 | rand = RANDOM.nextInt(LETTER_RANGE); 84 | rand += 65; 85 | c = (char) rand; 86 | break; 87 | //随机数字 88 | case NUMBER: 89 | rand = RANDOM.nextInt(NUMBER_RANGE); 90 | rand += 48; 91 | c = (char) rand; 92 | break; 93 | default: 94 | } 95 | return c; 96 | } 97 | } 98 | 99 | enum CharacterType { 100 | /** 101 | * LOWERCASE 102 | */ 103 | LOWERCASE, 104 | /** 105 | * UPPERCASE 106 | */ 107 | UPPERCASE, 108 | /** 109 | * NUMBER 110 | */ 111 | NUMBER 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/BaseResultVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 接口返回封装对象 9 | * 10 | * @author mjj   (;¬_¬)   11 | * @version 1.0  12 | * @date 2019-07-10 11:34 13 | **/ 14 | @Data 15 | public class BaseResultVo implements Serializable { 16 | /** 17 | * 状态 18 | */ 19 | private int status = SUCCESS_CODE; 20 | 21 | /** 22 | * 消息 23 | */ 24 | private String message = SUCCESS_MSG; 25 | 26 | /** 27 | * 数据 28 | */ 29 | private Object data; 30 | 31 | public static final Integer SUCCESS_CODE = 200; 32 | public static final Integer ERROR_CODE = 500; 33 | public static final Integer NOT_LOGIN = 300; 34 | public static final Integer PAYMENT_FAILURE = 301; 35 | 36 | public static final String SUCCESS_MSG = "操作成功"; 37 | public static final String ERROR_MSG = "操作失败"; 38 | public static final String NOT_LOGIN_MSG = "未登录"; 39 | 40 | 41 | public BaseResultVo(Object data, int status, String message) { 42 | this.status = status; 43 | this.message = message; 44 | this.data = data; 45 | } 46 | 47 | public BaseResultVo(Object data) { 48 | this.data = data; 49 | } 50 | 51 | public BaseResultVo(int status, String message) { 52 | this.status = status; 53 | this.message = message; 54 | } 55 | 56 | public BaseResultVo() { 57 | } 58 | 59 | 60 | public static BaseResultVo success() { 61 | return new BaseResultVo(SUCCESS_CODE, SUCCESS_MSG); 62 | } 63 | 64 | public static BaseResultVo success(Object data) { 65 | return new BaseResultVo(data, SUCCESS_CODE, SUCCESS_MSG); 66 | } 67 | 68 | public static BaseResultVo error(String message) { 69 | return new BaseResultVo(ERROR_CODE, message); 70 | } 71 | 72 | public static BaseResultVo error() { 73 | BaseResultVo baseResultVo = new BaseResultVo(); 74 | baseResultVo.setStatus(BaseResultVo.ERROR_CODE); 75 | baseResultVo.setMessage(BaseResultVo.ERROR_MSG); 76 | return baseResultVo; 77 | } 78 | 79 | public static BaseResultVo notLogin() { 80 | return new BaseResultVo(NOT_LOGIN, NOT_LOGIN_MSG); 81 | } 82 | 83 | public static BaseResultVo error(Integer code, String message) { 84 | return new BaseResultVo(code, message); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/DirectoryRoleVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import com.microsoft.graph.models.DirectoryRole; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class DirectoryRoleVo { 8 | 9 | /** 10 | * 角色描述 11 | */ 12 | public String description; 13 | 14 | /** 15 | * 角色名称 16 | */ 17 | public String displayName; 18 | 19 | /** 20 | * 角色模板ID 21 | */ 22 | public String roleTemplateId; 23 | 24 | public DirectoryRoleVo(DirectoryRole role) { 25 | description = role.description; 26 | displayName = role.displayName; 27 | roleTemplateId = role.roleTemplateId; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/DomainVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * 绑定域名 12 | * 13 | * @author mjj 14 | * @date 2020年08月11日 14:57:32 15 | */ 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class DomainVo implements Serializable { 21 | 22 | /** 23 | * 域名 24 | */ 25 | private String id; 26 | 27 | private Boolean isDefault; 28 | 29 | /** 30 | * 是否是默认域名 31 | */ 32 | private String displayIsDefault; 33 | 34 | private Boolean isRoot; 35 | 36 | /** 37 | * 是否是根域名 38 | */ 39 | private String displayIsRoot; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/FontSkuSku.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * @author huahui.wu 10 | * @date 2020年11月25日 11:57:22 11 | */ 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class FontSkuSku { 17 | /** 18 | * 组织名称 19 | */ 20 | private String appName; 21 | 22 | /** 23 | * 订阅id 24 | */ 25 | private String id; 26 | 27 | /** 28 | * 许可证id 29 | */ 30 | private String skuId; 31 | 32 | /** 33 | * 许可证名称 34 | */ 35 | private String skuName; 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/FontUser.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * @author huahui.wu 10 | * @date 2020年11月25日 13:13:25 11 | */ 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class FontUser { 17 | 18 | /** 19 | * 显示名称 20 | */ 21 | private String displayName; 22 | 23 | /** 24 | * 用户名 25 | */ 26 | private String userPrincipalName; 27 | 28 | /** 29 | * 密码 30 | */ 31 | private String password; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/GraphUserSorterVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class GraphUserSorterVo { 7 | /** 8 | * 两个字母的国家/地区代码(ISO 标准 3166)。检查服务在国家/地区的可用性 9 | */ 10 | private String sortusageLocation; 11 | 12 | /** 13 | * 用户创建时间 14 | */ 15 | private String sortcreatedDateTime; 16 | 17 | /** 18 | * 用户名 19 | */ 20 | private String sortuserPrincipalName; 21 | 22 | /** 23 | * 角色 24 | */ 25 | private String sortroles; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/GraphUserVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import cn.itbat.microsoft.model.GraphUser; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.EqualsAndHashCode; 7 | import org.springframework.beans.BeanUtils; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * @author mjj 14 | * @date 2020年06月12日 18:29:58 15 | */ 16 | @EqualsAndHashCode(callSuper = true) 17 | @Data 18 | public class GraphUserVo extends GraphUser { 19 | 20 | /** 21 | * 组织类型 22 | */ 23 | private String appName; 24 | 25 | 26 | /** 27 | * 是否需要删除一个许可证 28 | */ 29 | private Boolean needDeleted; 30 | 31 | /** 32 | * 用户状态 33 | */ 34 | private String displayAccountEnable; 35 | 36 | /** 37 | * 订阅名称 38 | */ 39 | private String skuName; 40 | 41 | /** 42 | * 是否分配许可证 43 | */ 44 | private Boolean assignLicense; 45 | 46 | /** 47 | * 是否发送邮件 48 | */ 49 | private Boolean sendMail; 50 | 51 | /** 52 | * 邮箱 53 | */ 54 | private String mailbox; 55 | 56 | /** 57 | * 邀请码 58 | */ 59 | private String code; 60 | 61 | /** 62 | * 查询大小,默认999 63 | */ 64 | @Builder.Default 65 | private Integer top = 999; 66 | 67 | /** 68 | * 订阅信息 69 | */ 70 | private List skuVos; 71 | 72 | /** 73 | * 两个字母的国家/地区代码(ISO 标准 3166)。检查服务在国家/地区的可用性 74 | */ 75 | private String usageLocation; 76 | 77 | /** 78 | * 用户角色 79 | */ 80 | private List directoryRoles; 81 | 82 | public GraphUserVo() { 83 | directoryRoles = new ArrayList<>(); 84 | } 85 | 86 | public GraphUserVo(GraphUser graphUser) { 87 | if (null != graphUser) { 88 | BeanUtils.copyProperties(graphUser, this); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/HomePageVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | import java.util.List; 10 | 11 | /** 12 | * 首页展示信息 13 | * 14 | * @author mjj 15 | * @date 2020年08月11日 10:38:56 16 | */ 17 | @Data 18 | @Builder 19 | @NoArgsConstructor 20 | @AllArgsConstructor 21 | public class HomePageVo implements Serializable { 22 | 23 | /** 24 | * 统计信息 25 | */ 26 | private StatisticsVo statisticsVo; 27 | 28 | /** 29 | * 禁止登陆用户 30 | */ 31 | private List noLandingUsers; 32 | 33 | /** 34 | * 未授权用户 35 | */ 36 | private List unauthorizedUsers; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/SkuVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * 订阅信息 10 | * 11 | * @author mjj 12 | * @date 2020年08月06日 12:52:08 13 | */ 14 | @Data 15 | @Builder 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class SkuVo { 19 | 20 | 21 | private String skuId; 22 | 23 | /** 24 | * 订阅类型 25 | */ 26 | private String skuType; 27 | 28 | /** 29 | * 订阅名称 30 | */ 31 | private String skuName; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/StatisticsVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * 统计信息 12 | * 13 | * @author mjj 14 | * @date 2020年08月11日 10:40:09 15 | */ 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class StatisticsVo implements Serializable { 21 | 22 | /** 23 | * 产品订阅 24 | */ 25 | @Builder.Default 26 | private Integer productSubs = 0; 27 | 28 | /** 29 | * 许可证数 30 | */ 31 | @Builder.Default 32 | private Integer licenses = 0; 33 | 34 | /** 35 | * 已分配许可证数 36 | */ 37 | @Builder.Default 38 | private Integer allocatedLicenses = 0; 39 | 40 | /** 41 | * 可用许可证数 42 | */ 43 | @Builder.Default 44 | private Integer availableLicenses = 0; 45 | 46 | /** 47 | * 用户数 48 | */ 49 | @Builder.Default 50 | private Integer users = 0; 51 | 52 | /** 53 | * 允许登陆用户数 54 | */ 55 | @Builder.Default 56 | private Integer allowedUsers = 0; 57 | 58 | /** 59 | * 禁止登陆用户数 60 | */ 61 | @Builder.Default 62 | private Integer biddenUsers = 0; 63 | 64 | /** 65 | * 未授权用户数 66 | */ 67 | @Builder.Default 68 | private Integer unauthorizedUsers = 0; 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/SubscribedSkuVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import cn.itbat.microsoft.enums.CapabilityStatusEnum; 4 | import com.microsoft.graph.models.SubscribedSku; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * @author mjj 14 | * @date 2020年06月13日 15:56:26 15 | */ 16 | @Data 17 | @Builder 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | public class SubscribedSkuVo implements Serializable { 21 | 22 | /** 23 | * 组织名称 24 | */ 25 | private String appName; 26 | 27 | /** 28 | * 订阅id 29 | */ 30 | private String id; 31 | 32 | /** 33 | * 许可证id 34 | */ 35 | private String skuId; 36 | /** 37 | * 可取值为:Enabled、Warning、Suspended、Deleted、LockedOut。 38 | * 39 | * @see cn.itbat.microsoft.enums.CapabilityStatusEnum 40 | */ 41 | private String capabilityStatus; 42 | 43 | /** 44 | * 状态 45 | */ 46 | private String displayStatus; 47 | 48 | /** 49 | * 已分配的许可证数量。 50 | */ 51 | private Integer consumedUnits; 52 | 53 | /** 54 | * 唯一 SKU 显示名称 55 | */ 56 | private String skuPartNumber; 57 | 58 | /** 59 | * 许可证名称 60 | */ 61 | private String skuName; 62 | 63 | /** 64 | * 已启用 65 | */ 66 | private Integer enabled; 67 | 68 | /** 69 | * 已挂起 70 | */ 71 | private Integer suspended; 72 | 73 | /** 74 | * 警告 75 | */ 76 | private Integer warning; 77 | 78 | public SubscribedSkuVo(SubscribedSku subscribedSku) { 79 | id = subscribedSku.id; 80 | skuId = subscribedSku.skuId.toString(); 81 | enabled = subscribedSku.prepaidUnits.enabled; 82 | suspended = subscribedSku.prepaidUnits.suspended; 83 | warning = subscribedSku.prepaidUnits.warning; 84 | capabilityStatus = subscribedSku.capabilityStatus; 85 | consumedUnits = subscribedSku.consumedUnits; 86 | displayStatus = CapabilityStatusEnum.getName(subscribedSku.capabilityStatus); 87 | skuPartNumber = subscribedSku.skuPartNumber; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/cn/itbat/microsoft/vo/UserVo.java: -------------------------------------------------------------------------------- 1 | package cn.itbat.microsoft.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author mjj 7 | * @date 2020年05月28日 16:46:28 8 | */ 9 | @Data 10 | public class UserVo { 11 | private String id; 12 | private Boolean accountEnabled; 13 | private String name; 14 | private String surname; 15 | private String gender; 16 | private String region; 17 | private int age; 18 | private String title; 19 | private String phone; 20 | private String email; 21 | private String password; 22 | private String photo; 23 | 24 | } -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8099 3 | 4 | spring: 5 | application: 6 | name: Microsoft 365 Admin 7 | profiles: # 默认的profile为dev 8 | active: dev 9 | 10 | logging: 11 | level: 12 | global: debug 13 | -------------------------------------------------------------------------------- /src/main/resources/config/application-dev.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | mail: # 邮件配置,自行修改 3 | host: smtp.qq.com # smtp.qq.com 4 | username: 123@qq.com # 发送邮箱地址 5 | password: 123 # 发送邮箱密码 6 | output: 7 | ansi: 8 | enabled: always 9 | h2: 10 | console: 11 | settings: 12 | web-allow-others: true 13 | path: /h2-console 14 | enabled: false 15 | datasource: 16 | # 初始化数据导入 17 | # data: classpath*:db/data.sql 18 | # sql-script-encoding: utf-8 19 | 20 | initialization-mode: always 21 | continue-on-error: true 22 | 23 | # h2 内存数据库 配置 24 | driver-class-name: org.h2.Driver 25 | url: jdbc:h2:${graph.db.path} 26 | username: graph 27 | password: 123456 28 | jackson: 29 | date-format: yyyy-MM-dd HH:mm 30 | time-zone: GMT+8 31 | jpa: 32 | hibernate: 33 | ddl-auto: update 34 | properties: 35 | hibernate: 36 | format_sql: false 37 | show-sql: false 38 | # redis: # redis 配置 39 | # database: 0 40 | # host: 127.0.0.1 41 | # port: 6379 42 | 43 | # 程序自定义配置 44 | graph: 45 | cache: 46 | token: default # token 缓存方式默认内存,redis 方式 需要配置 redis 47 | timeout: # 缓存超时时间 单位分钟 48 | user: 60 # 默认一小时 49 | license: 1440 # 默认一天 50 | domain: 1440 # 默认一天 51 | db: 52 | path: ${user.home}/.graph/db/graph 53 | 54 | # 后台登陆配置 55 | userName: root # 登陆账户,自行修改 56 | password: 123456 # 登陆密码,自行修改 57 | 58 | # 邀请注册配置 59 | invite: mjj,mjj2 # 可以被注册的账号类型,为下面 configs.appName 的配置(必填,','分隔) 60 | inviteSub: # 为空表示全部 61 | inviteDomain: # 为空表示使用默认域名,格式:xxx.onmicrosoft.com 62 | usageLocation: HK,US #(此处会针对上面的配置覆盖)可选择的地区,用','分隔,首个将成为默认值,为空表示只使用HK(可以为空) 63 | 64 | # api配置 65 | configs: 66 | - appName: mjj # 自定义该账号的类型(建议使用英文:默认域名前缀,必填) 67 | appId: mjj # 应用程序(客户端) ID client(必填) 68 | appTenant: mjj # 目录(租户) tenant(必填) 69 | appSecret: mjj # secrets(必填) 70 | admin: mjj # 全局管理员账号(可以为空) 71 | usageLocation: HK,US #(此处会针对上面的配置覆盖)可选择的地区,用','分隔,首个将成为默认值,为空表示只使用HK(可以为空) 72 | - appName: mjj2 # 账号2配置,如果不需要请删除 73 | appId: mjj2 74 | appTenant: mjj2 75 | appSecret: mjj2 76 | domain: mjj2 77 | admin: mjj2 78 | usageLocation: US #(此处会针对上面的配置覆盖)可选择的地区,用','分隔,首个将成为默认值,为空表示只使用HK(可以为空) 79 | subscribed: # 订阅类型,自行添加其他 80 | - skuName: STANDARDWOFFPACK_STUDENT 81 | displayName: A1 学生版 82 | skuId: 314c4481-f395-4525-be8b-2ec4bb1e9d91 83 | - skuName: STANDARDWOFFPACK_FACULTY 84 | displayName: A1 教师版 85 | skuId: 94763226-9b3c-4e75-a931-5c89701abe66 86 | - skuName: OFFICE_365_A1_PLUS_FOR_STUDENT 87 | displayName: A1P 学生版 88 | skuId: e82ae690-a2d5-4d76-8d30-7c6e01e6022e 89 | - skuName: OFFICE_365_A1_PLUS_FOR_FACULTY 90 | displayName: A1P 教师版 91 | skuId: 78e66a63-337a-4a9a-8959-41c6654dfb56 92 | - skuName: M365EDU_A3_STUUSEBNFT_RPA1 93 | displayName: A3 无人值守版 94 | skuId: 1aa94593-ca12-4254-a738-81a5972958e8 95 | - skuName: Office_365_E3Y 96 | displayName: E3Y 97 | skuId: 6fd2c87f-b296-42f0-b197-1e91e994b900 98 | - skuName: DEVELOPERPACK_E5 99 | displayName: E5 开发者订阅 100 | skuId: c42b9cae-ea4f-4ab7-9717-81576235ccac 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/main/resources/db/data.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/db/data.sql -------------------------------------------------------------------------------- /src/main/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/css/gm-diy.css: -------------------------------------------------------------------------------- 1 | .gm-microsoft-365-skin .table-header{ 2 | background: white; 3 | } 4 | .gm-microsoft-365-skin .gm-toolbar{ 5 | background-color: white; 6 | } 7 | .gm-microsoft-365-skin .gm-toolbar .pagination { 8 | height: 10px; 9 | } -------------------------------------------------------------------------------- /src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/fonts/materialdesignicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/fonts/materialdesignicons.eot -------------------------------------------------------------------------------- /src/main/resources/static/fonts/materialdesignicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/fonts/materialdesignicons.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/materialdesignicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/fonts/materialdesignicons.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/materialdesignicons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/fonts/materialdesignicons.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/images/captcha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/captcha.png -------------------------------------------------------------------------------- /src/main/resources/static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/10.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/11.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/13.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/14.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/15.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/16.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/17.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/2.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/3.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/4.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/5.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/6.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/7.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/8.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/gallery/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/gallery/9.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/img-slide-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/img-slide-1.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/img-slide-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/img-slide-2.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/img-slide-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/img-slide-3.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/img-slide-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/img-slide-4.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/img-slide-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/img-slide-5.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/login-bg-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/login-bg-2.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/login-bg-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/login-bg-3.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/login-bg-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/login-bg-4.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/login-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/login-bg.jpg -------------------------------------------------------------------------------- /src/main/resources/static/images/logo-ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/logo-ico.png -------------------------------------------------------------------------------- /src/main/resources/static/images/logo-sidebar--2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/logo-sidebar--2.png -------------------------------------------------------------------------------- /src/main/resources/static/images/logo-sidebar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/logo-sidebar-1.png -------------------------------------------------------------------------------- /src/main/resources/static/images/logo-sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/logo-sidebar.png -------------------------------------------------------------------------------- /src/main/resources/static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/logo.png -------------------------------------------------------------------------------- /src/main/resources/static/images/users/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/images/users/avatar.jpg -------------------------------------------------------------------------------- /src/main/resources/static/js/about.js: -------------------------------------------------------------------------------- 1 | let path = url; 2 | 3 | $(window).on("load", function () { 4 | $('#titleName').html(" 关于平台 "); 5 | $("#appNameSelect").hide(); 6 | $("#clearCache").hide(); 7 | getSystemMonitor(); 8 | }); 9 | 10 | setInterval(function () { 11 | getSystemMonitor(); 12 | }, 10000); 13 | 14 | function getSystemMonitor() { 15 | $.ajax({ 16 | type: "get", 17 | url: path + "/system/monitor", 18 | data: {}, 19 | dataType: "json", 20 | success: function (r) { 21 | if (r.status !== 200) { 22 | lightyear.notify(r.message, 'danger', 1000); 23 | } else { 24 | $("#systemTable").empty(); 25 | $("#jvmTable").empty(); 26 | sys = r.data.sys; 27 | jvm = r.data.jvm; 28 | $("#systemTable").append('\n' + 29 | '\n' + 30 | '系统属性\n' + 31 | '\n' + 32 | '' 33 | ); 34 | $("#systemTable").append('' + ' 操作系统 ' + '' + sys.osName + '' + ''); 35 | $("#systemTable").append('' + ' 系统版本 ' + '' + sys.osVersion + '' + ''); 36 | $("#systemTable").append('' + ' 系统架构 ' + '' + sys.osArch + '' + ''); 37 | $("#systemTable").append('' + ' 系统启动时间 ' + '' + sys.upTime + '' + ''); 38 | $("#jvmTable").append('\n' + 39 | '\n' + 40 | 'JVM属性\n' + 41 | '\n' + 42 | '\n' + 43 | '' 44 | ); 45 | $("#jvmTable").append('' + ' JVM 版本' + '' + '' + jvm.version + '' + ''); 46 | $("#jvmTable").append('' + ' JVM 可占用内存' + '' + '' + jvm.max.toFixed(2) + ' MB' + ''); 47 | $("#jvmTable").append('' + ' JVM 已占用内存' + '' + '' + jvm.free.toFixed(2) + ' MB' + ''); 48 | $("#jvmTable").append('' + ' JVM 空闲内存' + '' + '' + jvm.total.toFixed(2) + ' MB' + ''); 49 | } 50 | }, 51 | error: function () { 52 | /*错误信息处理*/ 53 | lightyear.notify("服务器错误,请稍后再试~", 'danger', 100); 54 | } 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/css/bootstrap-colorpicker.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Colorpicker v2.3.3 3 | * http://mjolnic.github.io/bootstrap-colorpicker/ 4 | * 5 | * Originally written by (c) 2012 Stefan Petre 6 | * Licensed under the Apache License v2.0 7 | * http://www.apache.org/licenses/LICENSE-2.0.txt 8 | * 9 | */ 10 | .colorpicker-saturation { 11 | width: 100px; 12 | height: 100px; 13 | background-image: url("../img/bootstrap-colorpicker/saturation.png"); 14 | cursor: crosshair; 15 | float: left; 16 | } 17 | .colorpicker-saturation i { 18 | display: block; 19 | height: 5px; 20 | width: 5px; 21 | border: 1px solid #000; 22 | -webkit-border-radius: 5px; 23 | -moz-border-radius: 5px; 24 | border-radius: 5px; 25 | position: absolute; 26 | top: 0; 27 | left: 0; 28 | margin: -4px 0 0 -4px; 29 | } 30 | .colorpicker-saturation i b { 31 | display: block; 32 | height: 5px; 33 | width: 5px; 34 | border: 1px solid #fff; 35 | -webkit-border-radius: 5px; 36 | -moz-border-radius: 5px; 37 | border-radius: 5px; 38 | } 39 | .colorpicker-hue, 40 | .colorpicker-alpha { 41 | width: 15px; 42 | height: 100px; 43 | float: left; 44 | cursor: row-resize; 45 | margin-left: 4px; 46 | margin-bottom: 4px; 47 | } 48 | .colorpicker-hue i, 49 | .colorpicker-alpha i { 50 | display: block; 51 | height: 1px; 52 | background: #000; 53 | border-top: 1px solid #fff; 54 | position: absolute; 55 | top: 0; 56 | left: 0; 57 | width: 100%; 58 | margin-top: -1px; 59 | } 60 | .colorpicker-hue { 61 | background-image: url("../img/bootstrap-colorpicker/hue.png"); 62 | } 63 | .colorpicker-alpha { 64 | background-image: url("../img/bootstrap-colorpicker/alpha.png"); 65 | display: none; 66 | } 67 | .colorpicker-saturation, 68 | .colorpicker-hue, 69 | .colorpicker-alpha { 70 | background-size: contain; 71 | } 72 | .colorpicker { 73 | padding: 4px; 74 | min-width: 130px; 75 | margin-top: 1px; 76 | -webkit-border-radius: 4px; 77 | -moz-border-radius: 4px; 78 | border-radius: 4px; 79 | z-index: 2500; 80 | } 81 | .colorpicker:before, 82 | .colorpicker:after { 83 | display: table; 84 | content: ""; 85 | line-height: 0; 86 | } 87 | .colorpicker:after { 88 | clear: both; 89 | } 90 | .colorpicker:before { 91 | content: ''; 92 | display: inline-block; 93 | border-left: 7px solid transparent; 94 | border-right: 7px solid transparent; 95 | border-bottom: 7px solid #ccc; 96 | border-bottom-color: rgba(0, 0, 0, 0.2); 97 | position: absolute; 98 | top: -7px; 99 | left: 6px; 100 | } 101 | .colorpicker:after { 102 | content: ''; 103 | display: inline-block; 104 | border-left: 6px solid transparent; 105 | border-right: 6px solid transparent; 106 | border-bottom: 6px solid #ffffff; 107 | position: absolute; 108 | top: -6px; 109 | left: 7px; 110 | } 111 | .colorpicker div { 112 | position: relative; 113 | } 114 | .colorpicker.colorpicker-with-alpha { 115 | min-width: 140px; 116 | } 117 | .colorpicker.colorpicker-with-alpha .colorpicker-alpha { 118 | display: block; 119 | } 120 | .colorpicker-color { 121 | height: 10px; 122 | margin-top: 5px; 123 | clear: both; 124 | background-image: url("../img/bootstrap-colorpicker/alpha.png"); 125 | background-position: 0 100%; 126 | } 127 | .colorpicker-color div { 128 | height: 10px; 129 | } 130 | .colorpicker-selectors { 131 | display: none; 132 | height: 10px; 133 | margin-top: 5px; 134 | clear: both; 135 | } 136 | .colorpicker-selectors i { 137 | cursor: pointer; 138 | float: left; 139 | height: 10px; 140 | width: 10px; 141 | } 142 | .colorpicker-selectors i + i { 143 | margin-left: 3px; 144 | } 145 | .colorpicker-element .input-group-addon i, 146 | .colorpicker-element .add-on i { 147 | display: inline-block; 148 | cursor: pointer; 149 | height: 16px; 150 | vertical-align: text-top; 151 | width: 16px; 152 | } 153 | .colorpicker.colorpicker-inline { 154 | position: relative; 155 | display: inline-block; 156 | float: none; 157 | z-index: auto; 158 | } 159 | .colorpicker.colorpicker-horizontal { 160 | width: 110px; 161 | min-width: 110px; 162 | height: auto; 163 | } 164 | .colorpicker.colorpicker-horizontal .colorpicker-saturation { 165 | margin-bottom: 4px; 166 | } 167 | .colorpicker.colorpicker-horizontal .colorpicker-color { 168 | width: 100px; 169 | } 170 | .colorpicker.colorpicker-horizontal .colorpicker-hue, 171 | .colorpicker.colorpicker-horizontal .colorpicker-alpha { 172 | width: 100px; 173 | height: 15px; 174 | float: left; 175 | cursor: col-resize; 176 | margin-left: 0px; 177 | margin-bottom: 4px; 178 | } 179 | .colorpicker.colorpicker-horizontal .colorpicker-hue i, 180 | .colorpicker.colorpicker-horizontal .colorpicker-alpha i { 181 | display: block; 182 | height: 15px; 183 | background: #ffffff; 184 | position: absolute; 185 | top: 0; 186 | left: 0; 187 | width: 1px; 188 | border: none; 189 | margin-top: 0px; 190 | } 191 | .colorpicker.colorpicker-horizontal .colorpicker-hue { 192 | background-image: url("../img/bootstrap-colorpicker/hue-horizontal.png"); 193 | } 194 | .colorpicker.colorpicker-horizontal .colorpicker-alpha { 195 | background-image: url("../img/bootstrap-colorpicker/alpha-horizontal.png"); 196 | } 197 | .colorpicker.colorpicker-hidden { 198 | display: none; 199 | } 200 | .colorpicker.colorpicker-visible { 201 | display: block; 202 | } 203 | .colorpicker-inline.colorpicker-visible { 204 | display: inline-block; 205 | } 206 | .colorpicker-right:before { 207 | left: auto; 208 | right: 6px; 209 | } 210 | .colorpicker-right:after { 211 | left: auto; 212 | right: 7px; 213 | } 214 | .colorpicker-no-arrow:before { 215 | border-right: 0; 216 | border-left: 0; 217 | } 218 | .colorpicker-no-arrow:after { 219 | border-right: 0; 220 | border-left: 0; 221 | } 222 | /*# sourceMappingURL=bootstrap-colorpicker.css.map */ -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/css/bootstrap-colorpicker.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["src/less/colorpicker.less"],"names":[],"mappings":";;;;;;;;;AAqBA;EACE,YAAA;EACA,aAAA;EAXA,sBAAsB,8CAAtB;EAaA,iBAAA;EACA,WAAA;;AALF,uBAME;EACE,cAAA;EACA,WAAA;EACA,UAAA;EACA,sBAAA;EAfF,0BAAA;EACA,uBAAA;EACA,kBAAA;EAeE,kBAAA;EACA,MAAA;EACA,OAAA;EACA,qBAAA;;AAfJ,uBAME,EAUE;EACE,cAAA;EACA,WAAA;EACA,UAAA;EACA,sBAAA;EAzBJ,0BAAA;EACA,uBAAA;EACA,kBAAA;;AA6BF;AACA;EACE,WAAA;EACA,aAAA;EACA,WAAA;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;;AAGF,gBAAiB;AACjB,kBAAmB;EACjB,cAAA;EACA,WAAA;EACA,gBAAA;EACA,0BAAA;EACA,kBAAA;EACA,MAAA;EACA,OAAA;EACA,WAAA;EACA,gBAAA;;AAGF;EA1DE,sBAAsB,uCAAtB;;AA8DF;EA9DE,sBAAsB,yCAAtB;EAgEA,aAAA;;AAGF;AACA;AACA;EACE,wBAAA;;AAGF;EACE,YAAA;EACA,gBAAA;EACA,eAAA;EAxEA,0BAAA;EACA,uBAAA;EACA,kBAAA;EAwEA,aAAA;;AAGF,YAAY;AACZ,YAAY;EACV,cAAA;EACA,SAAS,EAAT;EACA,cAAA;;AAGF,YAAY;EACV,WAAA;;AAGF,YAAY;EACV,SAAS,EAAT;EACA,qBAAA;EACA,kCAAA;EACA,mCAAA;EACA,6BAAA;EACA,uCAAA;EACA,kBAAA;EACA,SAAA;EACA,SAAA;;AAGF,YAAY;EACV,SAAS,EAAT;EACA,qBAAA;EACA,kCAAA;EACA,mCAAA;EACA,gCAAA;EACA,kBAAA;EACA,SAAA;EACA,SAAA;;AAGF,YAAa;EACX,kBAAA;;AAGF,YAAY;EACV,gBAAA;;AAGF,YAAY,uBAAwB;EAClC,cAAA;;AAGF;EACE,YAAA;EACA,eAAA;EACA,WAAA;EAlIA,sBAAsB,yCAAtB;EAoIA,2BAAA;;AAGF,kBAAmB;EACjB,YAAA;;AAGF;EACE,aAAA;EACA,YAAA;EACA,eAAA;EACA,WAAA;;AAGF,sBAAuB;EACrB,eAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;;AAGF,sBAAuB,EAAE;EACvB,gBAAA;;AAGF,oBAAqB,mBAAmB;AACxC,oBAAqB,QAAQ;EAC3B,qBAAA;EACA,eAAA;EACA,YAAA;EACA,wBAAA;EACA,WAAA;;AAGF,YAAY;EACV,kBAAA;EACA,qBAAA;EACA,WAAA;EACA,aAAA;;AAGF,YAAY;EACV,YAAA;EACA,gBAAA;EACA,YAAA;;AAGF,YAAY,uBAAwB;EAClC,kBAAA;;AAGF,YAAY,uBAAwB;EAClC,YAAA;;AAGF,YAAY,uBAAwB;AACpC,YAAY,uBAAwB;EAClC,YAAA;EACA,YAAA;EACA,WAAA;EACA,kBAAA;EACA,gBAAA;EACA,kBAAA;;AAGF,YAAY,uBAAwB,iBAAiB;AACrD,YAAY,uBAAwB,mBAAmB;EACrD,cAAA;EACA,YAAA;EACA,mBAAA;EACA,kBAAA;EACA,MAAA;EACA,OAAA;EACA,UAAA;EACA,YAAA;EACA,eAAA;;AAGF,YAAY,uBAAwB;EAlNlC,sBAAsB,kDAAtB;;AAsNF,YAAY,uBAAwB;EAtNlC,sBAAsB,oDAAtB;;AA0NF,YAAY;EACV,aAAA;;AAGF,YAAY;EACV,cAAA;;AAGF,mBAAmB;EACjB,qBAAA;;AAGF,kBAAkB;EAChB,UAAA;EACA,UAAA;;AAGF,kBAAkB;EAChB,UAAA;EACA,UAAA;;AAGF,qBAAqB;EACnB,eAAA;EACA,cAAA;;AAGF,qBAAqB;EACnB,eAAA;EACA,cAAA","sourcesContent":["/*!\n * Bootstrap Colorpicker v2.3.3\n * http://mjolnic.github.io/bootstrap-colorpicker/\n *\n * Originally written by (c) 2012 Stefan Petre\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0.txt\n *\n */\n@imgPath: \"../img/bootstrap-colorpicker/\";\n\n.bgImg(@imgFilename) {\n background-image: url(\"@{imgPath}@{imgFilename}\");\n}\n\n.borderRadius(@size) {\n -webkit-border-radius: @size;\n -moz-border-radius: @size;\n border-radius: @size;\n}\n\n.colorpicker-saturation {\n width: 100px;\n height: 100px;\n .bgImg('saturation.png');\n cursor: crosshair;\n float: left;\n i {\n display: block;\n height: 5px;\n width: 5px;\n border: 1px solid #000;\n .borderRadius(5px);\n position: absolute;\n top: 0;\n left: 0;\n margin: -4px 0 0 -4px;\n b {\n display: block;\n height: 5px;\n width: 5px;\n border: 1px solid #fff;\n .borderRadius(5px);\n }\n }\n}\n\n.colorpicker-hue,\n.colorpicker-alpha {\n width: 15px;\n height: 100px;\n float: left;\n cursor: row-resize;\n margin-left: 4px;\n margin-bottom: 4px;\n}\n\n.colorpicker-hue i,\n.colorpicker-alpha i {\n display: block;\n height: 1px;\n background: #000;\n border-top: 1px solid #fff;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n margin-top: -1px;\n}\n\n.colorpicker-hue {\n .bgImg('hue.png');\n}\n\n.colorpicker-alpha {\n .bgImg('alpha.png');\n display: none;\n}\n\n.colorpicker-saturation,\n.colorpicker-hue,\n.colorpicker-alpha {\n background-size: contain;\n}\n\n.colorpicker {\n padding: 4px;\n min-width: 130px;\n margin-top: 1px;\n .borderRadius(4px);\n z-index: 2500;\n}\n\n.colorpicker:before,\n.colorpicker:after {\n display: table;\n content: \"\";\n line-height: 0;\n}\n\n.colorpicker:after {\n clear: both;\n}\n\n.colorpicker:before {\n content: '';\n display: inline-block;\n border-left: 7px solid transparent;\n border-right: 7px solid transparent;\n border-bottom: 7px solid #ccc;\n border-bottom-color: rgba(0, 0, 0, 0.2);\n position: absolute;\n top: -7px;\n left: 6px;\n}\n\n.colorpicker:after {\n content: '';\n display: inline-block;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-bottom: 6px solid #ffffff;\n position: absolute;\n top: -6px;\n left: 7px;\n}\n\n.colorpicker div {\n position: relative;\n}\n\n.colorpicker.colorpicker-with-alpha {\n min-width: 140px;\n}\n\n.colorpicker.colorpicker-with-alpha .colorpicker-alpha {\n display: block;\n}\n\n.colorpicker-color {\n height: 10px;\n margin-top: 5px;\n clear: both;\n .bgImg('alpha.png');\n background-position: 0 100%;\n}\n\n.colorpicker-color div {\n height: 10px;\n}\n\n.colorpicker-selectors {\n display: none;\n height: 10px;\n margin-top: 5px;\n clear: both;\n}\n\n.colorpicker-selectors i {\n cursor: pointer;\n float: left;\n height: 10px;\n width: 10px;\n}\n\n.colorpicker-selectors i + i {\n margin-left: 3px;\n}\n\n.colorpicker-element .input-group-addon i,\n.colorpicker-element .add-on i {\n display: inline-block;\n cursor: pointer;\n height: 16px;\n vertical-align: text-top;\n width: 16px;\n}\n\n.colorpicker.colorpicker-inline {\n position: relative;\n display: inline-block;\n float: none;\n z-index: auto;\n}\n\n.colorpicker.colorpicker-horizontal {\n width: 110px;\n min-width: 110px;\n height: auto;\n}\n\n.colorpicker.colorpicker-horizontal .colorpicker-saturation {\n margin-bottom: 4px;\n}\n\n.colorpicker.colorpicker-horizontal .colorpicker-color {\n width: 100px;\n}\n\n.colorpicker.colorpicker-horizontal .colorpicker-hue,\n.colorpicker.colorpicker-horizontal .colorpicker-alpha {\n width: 100px;\n height: 15px;\n float: left;\n cursor: col-resize;\n margin-left: 0px;\n margin-bottom: 4px;\n}\n\n.colorpicker.colorpicker-horizontal .colorpicker-hue i,\n.colorpicker.colorpicker-horizontal .colorpicker-alpha i {\n display: block;\n height: 15px;\n background: #ffffff;\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n border: none;\n margin-top: 0px;\n}\n\n.colorpicker.colorpicker-horizontal .colorpicker-hue {\n .bgImg('hue-horizontal.png');\n}\n\n.colorpicker.colorpicker-horizontal .colorpicker-alpha {\n .bgImg('alpha-horizontal.png');\n}\n\n.colorpicker.colorpicker-hidden {\n display: none;\n}\n\n.colorpicker.colorpicker-visible {\n display: block;\n}\n\n.colorpicker-inline.colorpicker-visible {\n display: inline-block;\n}\n\n.colorpicker-right:before {\n left: auto;\n right: 6px;\n}\n\n.colorpicker-right:after {\n left: auto;\n right: 7px;\n}\n\n.colorpicker-no-arrow:before {\n border-right: 0;\n border-left: 0;\n}\n\n.colorpicker-no-arrow:after {\n border-right: 0;\n border-left: 0;\n}\n"]} -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/css/bootstrap-colorpicker.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Colorpicker v2.3.3 3 | * http://mjolnic.github.io/bootstrap-colorpicker/ 4 | * 5 | * Originally written by (c) 2012 Stefan Petre 6 | * Licensed under the Apache License v2.0 7 | * http://www.apache.org/licenses/LICENSE-2.0.txt 8 | * 9 | */.colorpicker-saturation{width:100px;height:100px;background-image:url(../img/bootstrap-colorpicker/saturation.png);cursor:crosshair;float:left}.colorpicker-saturation i{display:block;height:5px;width:5px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;position:absolute;top:0;left:0;margin:-4px 0 0 -4px}.colorpicker-saturation i b{display:block;height:5px;width:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-alpha,.colorpicker-hue{width:15px;height:100px;float:left;cursor:row-resize;margin-left:4px;margin-bottom:4px}.colorpicker-alpha i,.colorpicker-hue i{display:block;height:1px;background:#000;border-top:1px solid #fff;position:absolute;top:0;left:0;width:100%;margin-top:-1px}.colorpicker-hue{background-image:url(../img/bootstrap-colorpicker/hue.png)}.colorpicker-alpha{background-image:url(../img/bootstrap-colorpicker/alpha.png);display:none}.colorpicker-alpha,.colorpicker-hue,.colorpicker-saturation{background-size:contain}.colorpicker{padding:4px;min-width:130px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;z-index:2500}.colorpicker:after,.colorpicker:before{display:table;content:"";line-height:0}.colorpicker:after{clear:both}.colorpicker:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,.2);position:absolute;top:-7px;left:6px}.colorpicker:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:7px}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url(../img/bootstrap-colorpicker/alpha.png);background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-selectors{display:none;height:10px;margin-top:5px;clear:both}.colorpicker-selectors i{cursor:pointer;float:left;height:10px;width:10px}.colorpicker-selectors i+i{margin-left:3px}.colorpicker-element .add-on i,.colorpicker-element .input-group-addon i{display:inline-block;cursor:pointer;height:16px;vertical-align:text-top;width:16px}.colorpicker.colorpicker-inline{position:relative;display:inline-block;float:none;z-index:auto}.colorpicker.colorpicker-horizontal{width:110px;min-width:110px;height:auto}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-alpha,.colorpicker.colorpicker-horizontal .colorpicker-hue{width:100px;height:15px;float:left;cursor:col-resize;margin-left:0;margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-alpha i,.colorpicker.colorpicker-horizontal .colorpicker-hue i{display:block;height:15px;background:#fff;position:absolute;top:0;left:0;width:1px;border:none;margin-top:0}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url(../img/bootstrap-colorpicker/hue-horizontal.png)}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url(../img/bootstrap-colorpicker/alpha-horizontal.png)}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block}.colorpicker-right:before{left:auto;right:6px}.colorpicker-right:after{left:auto;right:7px}.colorpicker-no-arrow:before{border-right:0;border-left:0}.colorpicker-no-arrow:after{border-right:0;border-left:0} 10 | /*# sourceMappingURL=bootstrap-colorpicker.min.css.map */ -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/css/bootstrap-colorpicker.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["src/less/colorpicker.less"],"names":[],"mappings":";;;;;;;;AAqBA,wBACE,MAAA,MACA,OAAA,MAXA,iBAAsB,iDAatB,OAAA,UACA,MAAA,KACA,0BACE,QAAA,MACA,OAAA,IACA,MAAA,IACA,OAAA,IAAA,MAAA,KAfF,sBAAA,IACA,mBAAA,IACA,cAAA,IAeE,SAAA,SACA,IAAA,EACA,KAAA,EACA,OAAA,KAAA,EAAA,EAAA,KACA,4BACE,QAAA,MACA,OAAA,IACA,MAAA,IACA,OAAA,IAAA,MAAA,KAzBJ,sBAAA,IACA,mBAAA,IACA,cAAA,IA8BF,mBADA,iBAEE,MAAA,KACA,OAAA,MACA,MAAA,KACA,OAAA,WACA,YAAA,IACA,cAAA,IAIiB,qBADF,mBAEf,QAAA,MACA,OAAA,IACA,WAAA,KACA,WAAA,IAAA,MAAA,KACA,SAAA,SACA,IAAA,EACA,KAAA,EACA,MAAA,KACA,WAAA,KAGF,iBA1DE,iBAAsB,0CA8DxB,mBA9DE,iBAAsB,4CAgEtB,QAAA,KAKF,mBADA,iBADA,wBAGE,gBAAA,QAGF,aACE,QAAA,IACA,UAAA,MACA,WAAA,IAxEA,sBAAA,IACA,mBAAA,IACA,cAAA,IAwEA,QAAA,KAIU,mBADA,oBAEV,QAAA,MACA,QAAA,GACA,YAAA,EAGU,mBACV,MAAA,KAGU,oBACV,QAAA,GACA,QAAA,aACA,YAAA,IAAA,MAAA,YACA,aAAA,IAAA,MAAA,YACA,cAAA,IAAA,MAAA,KACA,oBAAA,eACA,SAAA,SACA,IAAA,KACA,KAAA,IAGU,mBACV,QAAA,GACA,QAAA,aACA,YAAA,IAAA,MAAA,YACA,aAAA,IAAA,MAAA,YACA,cAAA,IAAA,MAAA,KACA,SAAA,SACA,IAAA,KACA,KAAA,IAGW,iBACX,SAAA,SAGU,oCACV,UAAA,MAGkC,uDAClC,QAAA,MAGF,mBACE,OAAA,KACA,WAAA,IACA,MAAA,KAlIA,iBAAsB,4CAoItB,oBAAA,EAAA,KAGiB,uBACjB,OAAA,KAGF,uBACE,QAAA,KACA,OAAA,KACA,WAAA,IACA,MAAA,KAGqB,yBACrB,OAAA,QACA,MAAA,KACA,OAAA,KACA,MAAA,KAGuB,2BACvB,YAAA,IAI2B,+BADW,0CAEtC,QAAA,aACA,OAAA,QACA,OAAA,KACA,eAAA,SACA,MAAA,KAGU,gCACV,SAAA,SACA,QAAA,aACA,MAAA,KACA,QAAA,KAGU,oCACV,MAAA,MACA,UAAA,MACA,OAAA,KAGkC,4DAClC,cAAA,IAGkC,uDAClC,MAAA,MAIkC,uDADA,qDAElC,MAAA,MACA,OAAA,KACA,MAAA,KACA,OAAA,WACA,YAAA,EACA,cAAA,IAIqD,yDADF,uDAEnD,QAAA,MACA,OAAA,KACA,WAAA,KACA,SAAA,SACA,IAAA,EACA,KAAA,EACA,MAAA,IACA,OAAA,KACA,WAAA,EAGkC,qDAlNlC,iBAAsB,qDAsNY,uDAtNlC,iBAAsB,uDA0NZ,gCACV,QAAA,KAGU,iCACV,QAAA,MAGiB,wCACjB,QAAA,aAGgB,0BAChB,KAAA,KACA,MAAA,IAGgB,yBAChB,KAAA,KACA,MAAA,IAGmB,6BACnB,aAAA,EACA,YAAA,EAGmB,4BACnB,aAAA,EACA,YAAA"} -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/alpha-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/alpha-horizontal.png -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/alpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/alpha.png -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/hue-horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/hue-horizontal.png -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/hue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/hue.png -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/saturation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/6mb/Microsoft-365-Admin/89ca444776dae12d49047e55ac0bbbd3daabc9cf/src/main/resources/static/js/bootstrap-colorpicker/img/bootstrap-colorpicker/saturation.png -------------------------------------------------------------------------------- /src/main/resources/static/js/bootstrap-notify.min.js: -------------------------------------------------------------------------------- 1 | /* Project: Bootstrap Growl = v3.1.3 | Description: Turns standard Bootstrap alerts into "Growl-like" notifications. | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */ 2 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){function e(e,i,n){var i={content:{message:"object"==typeof i?i.message:i,title:i.title?i.title:"",icon:i.icon?i.icon:"",url:i.url?i.url:"#",target:i.target?i.target:"-"}};n=t.extend(!0,{},i,n),this.settings=t.extend(!0,{},s,n),this._defaults=s,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var s={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:'

'};String.format=function(){for(var t=arguments[0],e=1;e .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[e],this.$ele.addClass("alert-"+i[e]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[e]);break;case"icon":var n=this.$ele.find('[data-notify="icon"]');"class"==t.settings.icon_type.toLowerCase()?n.removeClass(t.settings.content.icon).addClass(i[e]):(n.is("img")||n.find("img"),n.attr("src",i[e]));break;case"progress":var a=t.settings.delay-t.settings.delay*(i[e]/100);this.$ele.data("notify-delay",a),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[e]).css("width",i[e]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[e]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[e]);break;default:this.$ele.find('[data-notify="'+e+'"]').html(i[e])}var o=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(o)},close:function(){t.close()}}},buildNotify:function(){var e=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,e.title,e.message,e.url,e.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('Notify Icon')},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1}),this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},placement:function(){var e=this,s=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return s=Math.max(s,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),1==this.settings.newest_on_top&&(s=this.settings.offset.y),i[this.settings.placement.from]=s+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit","moz","o","ms",""),function(t,s){e.$ele[0].style[s+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(s=parseInt(s)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(s)),t.isFunction(e.settings.onShow)&&e.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)},600)},bind:function(){var e=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){e.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){e.$ele.data("notify-delay",e.settings.delay);var s=setInterval(function(){var t=parseInt(e.$ele.data("notify-delay"))-e.settings.timer;if("false"===e.$ele.data("data-hover")&&"pause"==e.settings.mouse_over||"pause"!=e.settings.mouse_over){var i=(e.settings.delay-t)/e.settings.delay*100;e.$ele.data("notify-delay",t),e.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-e.settings.timer&&(clearInterval(s),e.close())},e.settings.timer)}},close:function(){var e=this,s=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),e.reposition(s),t.isFunction(e.settings.onClose)&&e.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(e.settings.onClosed)&&e.settings.onClosed.call(this)}),setTimeout(function(){i||(e.$ele.remove(),e.settings.onClosed&&e.settings.onClosed(e.$ele))},600)},reposition:function(e){var s=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);1==this.settings.newest_on_top&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(s.settings.placement.from,e),e=parseInt(e)+parseInt(s.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(e){return s=t.extend(!0,{},s,e)},t.notifyClose=function(e){"undefined"==typeof e||"all"==e?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+e+'"]').find('[data-notify="dismiss"]').trigger("click")}}); -------------------------------------------------------------------------------- /src/main/resources/static/js/code.js: -------------------------------------------------------------------------------- 1 | let codeList; 2 | let pageITotal; 3 | let pageIndex = 1; 4 | 5 | $(window).load(function () { 6 | $('#titleName').html(" 邀请管理 "); 7 | lightyear.loading('show'); 8 | // 设置组织类型 9 | var success = setAppName(); 10 | if (success) { 11 | getCodeStatistics(); 12 | listCode(); 13 | } 14 | lightyear.loading('hide'); 15 | }); 16 | 17 | function getCodeStatistics() { 18 | $.ajax({ 19 | type: "get", 20 | async: false, 21 | url: path + "/code/getCodeStatistics", 22 | data: {}, 23 | dataType: "json", 24 | success: function (r) { 25 | if (r.status !== 200) { 26 | lightyear.notify(r.message, 'danger', 1000); 27 | } else { 28 | $("#codes").text(r.data.codes); 29 | $("#codeValid").text(r.data.valid); 30 | $("#codeInvalid").text(r.data.invalid); 31 | $("#codeInvalidUser").text(r.data.users); 32 | } 33 | }, 34 | error: function () { 35 | /*错误信息处理*/ 36 | lightyear.notify("服务器错误,请稍后再试~", 'danger', 100); 37 | } 38 | }); 39 | } 40 | 41 | function listCode() { 42 | $.ajax({ 43 | type: "get", 44 | async: false, 45 | url: path + "/code/list", 46 | data: { 47 | "code": getInput("#codeInput"), 48 | "valid": getSelect("#codeValidSelect"), 49 | "pageIndex": pageIndex, 50 | "pageSize": 10 51 | }, 52 | dataType: "json", 53 | success: function (r) { 54 | if (r.status !== 200) { 55 | lightyear.notify(r.message, 'danger', 1000); 56 | } else { 57 | // 表格 58 | let codeTable = r.data.content; 59 | let num = (pageIndex - 1) * 10; 60 | for (i in codeTable) { 61 | var tr = '' + (parseInt(num) + parseInt(i) + 1) + '' 62 | + '' + codeTable[i].code + '' 63 | + '' + (isNotNull(codeTable[i].createTime) ? codeTable[i].createTime : "-") + '' 64 | + '' + (Boolean(codeTable[i].valid) ? ('有效') : ('失效')) + '' 65 | + '' + (isNotNull(codeTable[i].expirationTime) ? codeTable[i].expirationTime : "-") + '' 66 | + '' + (isNotNull(codeTable[i].invitedUser) ? codeTable[i].invitedUser : '-') + '' 67 | + '' + (isNotNull(codeTable[i].subscribe) ? codeTable[i].subscribe : '-') + ''; 68 | $("#codeTable").append('' + tr + '') 69 | } 70 | // 分页处理,计算总页数 71 | pageITotal = r.data.totalPages; 72 | $("#codeTotal").text("总页数:" + pageITotal); 73 | $("#codeIndex").text("当前页:" + pageIndex); 74 | lightyear.loading('hide'); 75 | } 76 | }, 77 | error: function () { 78 | /*错误信息处理*/ 79 | lightyear.notify("服务器错误,请稍后再试~", 'danger', 100); 80 | } 81 | }); 82 | } 83 | 84 | function pageOnclickCode(data) { 85 | $("#codeTable tr:not(:first)").empty(); 86 | pageIndex = isNotNull(getCookie("pageIndexCode")) ? getCookie("pageIndexCode") : 1; 87 | if (data === 0 && pageIndex > 1) { 88 | pageIndex = parseInt(pageIndex) - 1; 89 | } 90 | if (data === 1 && pageIndex < pageITotal) { 91 | pageIndex = parseInt(pageIndex) + 1; 92 | } 93 | setCookie("pageIndexCode", pageIndex); 94 | listCode(); 95 | } 96 | 97 | function searchCodes() { 98 | lightyear.loading('show'); 99 | $("#codeTable tr:not(:first)").empty(); 100 | listCode(); 101 | } 102 | 103 | function addCodeClick() { 104 | lightyear.loading('show'); 105 | let num = $("#numCode").val(); 106 | // 提交请求 107 | $.ajax({ 108 | type: "post", 109 | url: path + "/code/generate", 110 | data: { 111 | "num": num 112 | }, 113 | dataType: "json", 114 | success: function (r) { 115 | if (r.status !== 200) { 116 | lightyear.loading('hide'); 117 | lightyear.notify(r.message, 'danger', delay); 118 | } else { 119 | console.log(r); 120 | lightyear.loading('hide'); 121 | lightyear.notify('创建邀请码成功数量:' + num, 'success', delay); 122 | } 123 | }, 124 | error: function () { 125 | /*错误信息处理*/ 126 | lightyear.notify("服务器错误,请稍后再试~", 'danger', delay); 127 | lightyear.loading('hide'); 128 | } 129 | }); 130 | } 131 | -------------------------------------------------------------------------------- /src/main/resources/static/js/common.js: -------------------------------------------------------------------------------- 1 | var path = url + "/microsoft"; 2 | var appNameList; 3 | $.ajax({ 4 | type: "get", 5 | async: false, 6 | url: path + "/365/getAppName", 7 | data: {}, 8 | dataType: "json", 9 | success: function (r) { 10 | if (r.status !== 200) { 11 | lightyear.notify(r.message, 'error', 1000); 12 | } else { 13 | appNameList = r.data; 14 | } 15 | }, 16 | error: function (r) { 17 | console.log(r) 18 | if (r.status === 401) { 19 | lightyear.notify(r.message, 'error', 200); 20 | window.location.href = "login.html"; 21 | } else { 22 | /*错误信息处理*/ 23 | lightyear.notify("服务器错误,请稍后再试~", 'danger', 100); 24 | } 25 | } 26 | }); 27 | 28 | function refreshCache(successFunc) { 29 | lightyear.loading('show'); 30 | $.ajax({ 31 | type: "get", 32 | url: path + "/365/refresh", 33 | data: { 34 | "appName": getAppName() 35 | }, 36 | dataType: "json", 37 | success: function (r) { 38 | lightyear.loading('hide'); 39 | if (r.status !== 200) { 40 | lightyear.notify(r.message, 'danger', 200); 41 | } else { 42 | lightyear.notify("刷新缓存成功!", 'success', 200); 43 | if (successFunc != null) { 44 | successFunc(); 45 | } else { 46 | reloadPage(); 47 | } 48 | } 49 | }, 50 | error: function () { 51 | /*错误信息处理*/ 52 | lightyear.notify("服务器错误,请稍后再试~", 'danger', 200); 53 | } 54 | 55 | }); 56 | } 57 | 58 | function reloadPage() { 59 | lightyear.loading('show'); 60 | setCookie("appName", getAppName()); 61 | location.reload(); 62 | lightyear.loading('hide'); 63 | } 64 | 65 | 66 | function setCookie(name, value) { 67 | var exp = new Date(); 68 | exp.setTime(exp.getTime() + 24 * 60 * 60 * 1000); 69 | document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString(); 70 | } 71 | 72 | function getCookie(name) { 73 | var regExp = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); 74 | var arr = document.cookie.match(regExp); 75 | if (arr == null) { 76 | return null; 77 | } 78 | return unescape(arr[2]); 79 | } 80 | 81 | function setAppName() { 82 | if (!isNotNull(appNameList)) { 83 | return false; 84 | } 85 | var appName = getCookie("appName"); 86 | for (i in appNameList) { 87 | if (appName !== null && appNameList[i] === appName) { 88 | var option = ""; 89 | } else { 90 | var option = ""; 91 | } 92 | $("#appNameSelect").append(option); 93 | } 94 | return true; 95 | } 96 | 97 | function getAppName() { 98 | var options = $("#appNameSelect option:selected"); 99 | return options.val(); 100 | } 101 | 102 | function getSelect(id) { 103 | var options = $(id + " option:selected"); 104 | return options.val(); 105 | } 106 | 107 | function getInput(id) { 108 | return $(id).val(); 109 | } 110 | 111 | function isNotNull(ele) { 112 | if (typeof ele === 'undefined') { 113 | return false; 114 | } else if (ele == null) { 115 | return false; 116 | } else if (ele === '') { 117 | return false; 118 | } 119 | return true; 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/main/resources/static/js/config.js: -------------------------------------------------------------------------------- 1 | // 如果前端文件单独部署,则需要将后端地址填写上,否则为空 2 | let url = ""; 3 | // let url = "http://127.0.0.1:8099"; -------------------------------------------------------------------------------- /src/main/resources/static/js/index.js: -------------------------------------------------------------------------------- 1 | $(window).on("load", function () { 2 | $('#titleName').html(" 后台首页 "); 3 | lightyear.loading('show'); 4 | // 设置组织类型 5 | var success = setAppName(); 6 | if (success) { 7 | homePage(); 8 | } 9 | }); 10 | 11 | function homePage() { 12 | $.ajax({ 13 | type: "get", 14 | url: path + "/365/homePage", 15 | data: { 16 | "appName": getAppName() 17 | }, 18 | dataType: "json", 19 | success: function (r) { 20 | if (r.status !== 200) { 21 | lightyear.notify(r.message, 'danger', 100); 22 | } else { 23 | $("#productSubs").text(r.data.statisticsVo.productSubs); 24 | $("#licenses").text(r.data.statisticsVo.licenses); 25 | $("#allocatedLicenses").text(r.data.statisticsVo.allocatedLicenses); 26 | $("#availableLicenses").text(r.data.statisticsVo.availableLicenses); 27 | $("#users").text(r.data.statisticsVo.users); 28 | $("#allowedUsers").text(r.data.statisticsVo.allowedUsers); 29 | $("#biddenUsers").text(r.data.statisticsVo.biddenUsers); 30 | $("#unauthorizedUsers").text(r.data.statisticsVo.unauthorizedUsers); 31 | 32 | // 表格 33 | var noLandingUsers = r.data.noLandingUsers; 34 | for (i in noLandingUsers) { 35 | var skuName = ""; 36 | if (noLandingUsers[i].skuVos !== null) { 37 | for (j in noLandingUsers[i].skuVos) { 38 | skuName = skuName + noLandingUsers[i].skuVos[j].skuName; 39 | } 40 | } 41 | var tr = '' + (parseInt(i) + 1) + '' 42 | + '' + noLandingUsers[i].userId + '' 43 | + '' + noLandingUsers[i].userPrincipalName + '' 44 | + '' + noLandingUsers[i].displayName + '' 45 | + '' + skuName + '' 46 | + '' + noLandingUsers[i].displayAccountEnable + ''; 47 | $("#noLandingUsersTable").append('' + tr + '') 48 | } 49 | 50 | // 表格 51 | var unauthorizedUsers = r.data.unauthorizedUsers; 52 | for (i in unauthorizedUsers) { 53 | var skuName = ""; 54 | if (unauthorizedUsers[i].skuVos !== null) { 55 | for (j in unauthorizedUsers[i].skuVos) { 56 | skuName = skuName + unauthorizedUsers[i].skuVos[j].skuName; 57 | } 58 | } else { 59 | skuName = "无"; 60 | } 61 | var tr = '' + (parseInt(i) + 1) + '' 62 | + '' + unauthorizedUsers[i].userId + '' 63 | + '' + unauthorizedUsers[i].userPrincipalName + '' 64 | + '' + unauthorizedUsers[i].displayName + '' 65 | + '' + skuName + '' 66 | + '' + unauthorizedUsers[i].displayAccountEnable + ''; 67 | $("#unauthorizedUsersTable").append('' + tr + '') 68 | } 69 | } 70 | lightyear.loading('hide'); 71 | }, 72 | error: function () { 73 | lightyear.loading('hide'); 74 | /*错误信息处理*/ 75 | lightyear.notify("服务器错误,请稍后再试~", 'danger', 100); 76 | } 77 | }); 78 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/invite.js: -------------------------------------------------------------------------------- 1 | let delay = 2000; 2 | let usageLocationList; 3 | $(window).on("load", function () { 4 | $('#titleName').html(" 申请账号 "); 5 | lightyear.loading('show'); 6 | 7 | listLicense(); 8 | }); 9 | 10 | function listLicense() { 11 | $.ajax({ 12 | type: "get", 13 | url: "/front/listLicense", 14 | data: {}, 15 | dataType: "json", 16 | success: function (r) { 17 | if (r.status !== 200) { 18 | lightyear.notify(r.message, 'danger', delay); 19 | } else { 20 | licenseList = r.data; 21 | setLicense("g-sub-select"); 22 | } 23 | lightyear.loading('hide'); 24 | }, 25 | error: function () { 26 | lightyear.loading('hide'); 27 | /*错误信息处理*/ 28 | lightyear.notify("服务器错误,请稍后再试~", 'danger', delay); 29 | } 30 | }); 31 | } 32 | 33 | 34 | function setLicense(id) { 35 | let licenseSelect = $("#" + id); 36 | // 先清空下数据 37 | licenseSelect.empty(); 38 | licenseSelect.append("\n"); 39 | for (i in licenseList) { 40 | let option = ""; 41 | licenseSelect.append(option); 42 | } 43 | licenseSelect.change(function () { 44 | let appName = $('#' + id + ' option:selected').data('appname'); 45 | listUsageLocation(appName); 46 | setUsageLocation('g-usageLocation-select'); 47 | }) 48 | } 49 | 50 | function listUsageLocation(appName) { 51 | $.ajax({ 52 | type: "get", 53 | url: "/front/listUsageLocation", 54 | data: { 55 | appName: appName 56 | }, 57 | success: function (r) { 58 | if (r.status !== 200) { 59 | lightyear.notify(r.message, 'danger', delay); 60 | } else { 61 | usageLocationList = r.data; 62 | setUsageLocation("g-usageLocation-select"); 63 | } 64 | lightyear.loading('hide'); 65 | }, 66 | error: function () { 67 | lightyear.loading('hide'); 68 | /*错误信息处理*/ 69 | lightyear.notify("服务器错误,请稍后再试~", 'danger', delay); 70 | } 71 | }); 72 | } 73 | 74 | function setUsageLocation(id) { 75 | let usageLocationSelect = $("#" + id); 76 | usageLocationSelect.empty(); 77 | for (let i in usageLocationList) { 78 | let option = ""; 79 | usageLocationSelect.append(option); 80 | } 81 | } 82 | 83 | function getSelect(id) { 84 | var options = $(id + " option:selected"); 85 | return options.val(); 86 | } 87 | 88 | function getInput(id) { 89 | return $(id).val(); 90 | } 91 | 92 | function addUserClick() { 93 | lightyear.loading('show'); 94 | // 参数获取 95 | let select = getSelect("#g-sub-select") 96 | let appName = select.split("_")[0]; 97 | let skuId = select.split("_")[1]; 98 | let displayName = getInput("#g_name"); 99 | let mailNickname = getInput("#g_mail_sub"); 100 | let password = getInput("#g-password"); 101 | let mailbox = getInput("#g-send_mail"); 102 | let code = getInput("#g-code"); 103 | let usageLocation = getSelect("#g-usageLocation"); 104 | 105 | // 参数校验 106 | 107 | // 提交请求 108 | $.ajax({ 109 | type: "post", 110 | url: "/front/create", 111 | data: { 112 | "appName": appName, 113 | "displayName": displayName, 114 | "skuId": skuId, 115 | "mailNickname": mailNickname, 116 | "password": password, 117 | "mailbox": mailbox, 118 | "code": code, 119 | "usageLocation": usageLocation 120 | }, 121 | dataType: "json", 122 | success: function (r) { 123 | if (r.status !== 200) { 124 | lightyear.loading('hide'); 125 | let message = r.message; 126 | let password_error = "Error code: Request_BadRequestError message: The specified password does not comply with password complexity requirements. Please provide a different password."; 127 | let name_error = "Error code: Request_BadRequestError message: Another object with the same value for property userPrincipalName already exists."; 128 | if (password_error === message) { 129 | message = "密码太简单啦,"; 130 | } 131 | if (name_error === message) { 132 | message = "该邮箱前缀已经被抢啦!"; 133 | } 134 | lightyear.notify(message, 'danger', delay); 135 | } else { 136 | console.log(r); 137 | let userInfo = '名称:' + r.data.displayName + '
账号:' + r.data.userPrincipalName + '
密码:' + r.data.password; 138 | lightyear.loading('hide'); 139 | $.alert({ 140 | title: '新增账号成功!', 141 | content: '
' + userInfo + '
', 142 | buttons: { 143 | confirm: { 144 | text: '确认', 145 | btnClass: 'btn-primary', 146 | action: function () { 147 | window.location.reload(); 148 | } 149 | } 150 | } 151 | }); 152 | } 153 | }, 154 | error: function () { 155 | lightyear.loading('hide'); 156 | /*错误信息处理*/ 157 | lightyear.notify("服务器错误,请稍后再试~", 'danger', delay); 158 | } 159 | }); 160 | 161 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/jquery-tags-input/jquery.tagsinput.min.css: -------------------------------------------------------------------------------- 1 | div.tagsinput{border:1px solid #ebebeb;background:#FFF;padding:6px 12px 1px 6px;width:300px;height:100px;overflow-y:auto;}div.tagsinput span.tag{border:none;-moz-border-radius:2px;-webkit-border-radius:2px;display:block;float:left;padding:2px 5px;text-decoration:none;background:#33cabb;color:#fff;margin-right:5px;margin-bottom:5px;font-family:'Microsoft yahei',Roboto,sans-serif;font-size:13px;line-height:18px}div.tagsinput span.tag a{font-weight:bold;color:rgba(255,255,255,0.5);text-decoration:none;font-size:12px;}div.tagsinput input{width:80px;margin:0px;font-family:'Microsoft yahei',Roboto,sans-serif;font-size:13px;border:1px solid transparent;padding:0;background:transparent;color:#000;outline:0px;margin-right:5px;margin-bottom:5px;}div.tagsinput div{display:block;float:left;}.tags_clear{clear:both;width:100%;height:0px;}.not_valid{background:#FBD8DB !important;color:#90111A !important;} -------------------------------------------------------------------------------- /src/main/resources/static/js/jquery-tags-input/jquery.tagsinput.min.js: -------------------------------------------------------------------------------- 1 | !function(a){var b=new Array,c=new Array;a.fn.doAutosize=function(b){var c=a(this).data("minwidth"),d=a(this).data("maxwidth"),e="",f=a(this),g=a("#"+a(this).data("tester_id"));if(e!==(e=f.val())){var h=e.replace(/&/g,"&").replace(/\s/g," ").replace(//g,">");g.html(h);var i=g.width(),j=i+b.comfortZone>=c?i+b.comfortZone:c,k=f.width(),l=k>j&&j>=c||j>c&&d>j;l&&f.width(j)}},a.fn.resetAutosize=function(b){var c=a(this).data("minwidth")||b.minInputWidth||a(this).width(),d=a(this).data("maxwidth")||b.maxInputWidth||a(this).closest(".tagsinput").width()-b.inputPadding,e=a(this),f=a("").css({position:"absolute",top:-9999,left:-9999,width:"auto",fontSize:e.css("fontSize"),fontFamily:e.css("fontFamily"),fontWeight:e.css("fontWeight"),letterSpacing:e.css("letterSpacing"),whiteSpace:"nowrap"}),g=a(this).attr("id")+"_autosize_tester";!a("#"+g).length>0&&(f.attr("id",g),f.appendTo("body")),e.data("minwidth",c),e.data("maxwidth",d),e.data("tester_id",g),e.css("width",c)},a.fn.addTag=function(d,e){return e=jQuery.extend({focus:!1,callback:!0},e),this.each(function(){var f=a(this).attr("id"),g=a(this).val().split(b[f]);if(""==g[0]&&(g=new Array),d=jQuery.trim(d),e.unique){var h=a(this).tagExist(d);1==h&&a("#"+f+"_tag").addClass("not_valid")}else var h=!1;if(""!=d&&1!=h){if(a("").addClass("tag").append(a("").text(d).append("  "),a("",{href:"#",title:"Removing tag",text:"x"}).click(function(){return a("#"+f).removeTag(escape(d))})).insertBefore("#"+f+"_addTag"),g.push(d),a("#"+f+"_tag").val(""),e.focus?a("#"+f+"_tag").focus():a("#"+f+"_tag").blur(),a.fn.tagsInput.updateTagsField(this,g),e.callback&&c[f]&&c[f].onAddTag){var i=c[f].onAddTag;i.call(this,d)}if(c[f]&&c[f].onChange){var j=g.length,i=c[f].onChange;i.call(this,a(this),g[j-1])}}}),!1},a.fn.removeTag=function(d){return d=unescape(d),this.each(function(){var e=a(this).attr("id"),f=a(this).val().split(b[e]);for(a("#"+e+"_tagsinput .tag").remove(),str="",i=0;i=0},a.fn.importTags=function(b){var c=a(this).attr("id");a("#"+c+"_tagsinput .tag").remove(),a.fn.tagsInput.importTags(this,b)},a.fn.tagsInput=function(e){var f=jQuery.extend({interactive:!0,defaultText:"添加标签",minChars:0,width:"300px",height:"100px",autocomplete:{selectFirst:!1},hide:!0,delimiter:",",unique:!0,removeWithBackspace:!0,placeholderColor:"#666666",autosize:!0,comfortZone:20,inputPadding:12},e),g=0;return this.each(function(){if("undefined"==typeof a(this).attr("data-tagsinput-init")){a(this).attr("data-tagsinput-init",!0),f.hide&&a(this).hide();var e=a(this).attr("id");(!e||b[a(this).attr("id")])&&(e=a(this).attr("id","tags"+(new Date).getTime()+g++).attr("id"));var h=jQuery.extend({pid:e,real_input:"#"+e,holder:"#"+e+"_tagsinput",input_wrapper:"#"+e+"_addTag",fake_input:"#"+e+"_tag"},f);b[e]=h.delimiter,(f.onAddTag||f.onRemoveTag||f.onChange)&&(c[e]=new Array,c[e].onAddTag=f.onAddTag,c[e].onRemoveTag=f.onRemoveTag,c[e].onChange=f.onChange);var i='
';if(f.interactive&&(i=i+''),i+='
',a(i).insertAfter(this),a(h.holder).css("width",f.width),a(h.holder).css("min-height",f.height),a(h.holder).css("height",f.height),""!=a(h.real_input).val()&&a.fn.tagsInput.importTags(a(h.real_input),a(h.real_input).val()),f.interactive){if(a(h.fake_input).val(a(h.fake_input).attr("data-default")),a(h.fake_input).css("color",f.placeholderColor),a(h.fake_input).resetAutosize(f),a(h.holder).bind("click",h,function(b){a(b.data.fake_input).focus()}),a(h.fake_input).bind("focus",h,function(b){a(b.data.fake_input).val()==a(b.data.fake_input).attr("data-default")&&a(b.data.fake_input).val(""),a(b.data.fake_input).css("color","#000000")}),void 0!=f.autocomplete_url){autocomplete_options={source:f.autocomplete_url};for(attrname in f.autocomplete)autocomplete_options[attrname]=f.autocomplete[attrname];void 0!==jQuery.Autocompleter?(a(h.fake_input).autocomplete(f.autocomplete_url,f.autocomplete),a(h.fake_input).bind("result",h,function(b,c,d){c&&a("#"+e).addTag(c[0]+"",{focus:!0,unique:f.unique})})):void 0!==jQuery.ui.autocomplete&&(a(h.fake_input).autocomplete(autocomplete_options),a(h.fake_input).bind("autocompleteselect",h,function(b,c){return a(b.data.real_input).addTag(c.item.value,{focus:!0,unique:f.unique}),!1}))}else a(h.fake_input).bind("blur",h,function(b){var c=a(this).attr("data-default");return""!=a(b.data.fake_input).val()&&a(b.data.fake_input).val()!=c?b.data.minChars<=a(b.data.fake_input).val().length&&(!b.data.maxChars||b.data.maxChars>=a(b.data.fake_input).val().length)&&a(b.data.real_input).addTag(a(b.data.fake_input).val(),{focus:!0,unique:f.unique}):(a(b.data.fake_input).val(a(b.data.fake_input).attr("data-default")),a(b.data.fake_input).css("color",f.placeholderColor)),!1});a(h.fake_input).bind("keypress",h,function(b){return d(b)?(b.preventDefault(),b.data.minChars<=a(b.data.fake_input).val().length&&(!b.data.maxChars||b.data.maxChars>=a(b.data.fake_input).val().length)&&a(b.data.real_input).addTag(a(b.data.fake_input).val(),{focus:!0,unique:f.unique}),a(b.data.fake_input).resetAutosize(f),!1):void(b.data.autosize&&a(b.data.fake_input).doAutosize(f))}),h.removeWithBackspace&&a(h.fake_input).bind("keydown",function(b){if(8==b.keyCode&&""==a(this).val()){b.preventDefault();var c=a(this).closest(".tagsinput").find(".tag:last").text(),d=a(this).attr("id").replace(/_tag$/,"");c=c.replace(/[\s]+x$/,""),a("#"+d).removeTag(escape(c)),a(this).trigger("focus")}}),a(h.fake_input).blur(),h.unique&&a(h.fake_input).keydown(function(b){(8==b.keyCode||String.fromCharCode(b.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/))&&a(this).removeClass("not_valid")})}}}),this},a.fn.tagsInput.updateTagsField=function(c,d){var e=a(c).attr("id");a(c).val(d.join(b[e]))},a.fn.tagsInput.importTags=function(d,e){a(d).val("");var f=a(d).attr("id"),g=e.split(b[f]);for(i=0;i=a.currentIndex());c(b.nextSelector,d).toggleClass("disabled",a.currentIndex()>=a.navigationLength());c(b.nextSelector,d).toggleClass("hidden", 13 | a.currentIndex()>=a.navigationLength()&&0=a.navigationLength()&&0=a.navigationLength()&&0a.navigationLength()||(h.push(g),e.find('li:has([data-toggle="tab"])'+ 15 | (b.withVisible?":visible":"")+":eq("+c+") a").tab("show"))};this.previous=function(g){if(d.hasClass("first")||b.onPrevious&&"function"===typeof b.onPrevious&&!1===b.onPrevious(f,e,a.previousIndex()))return!1;g=a.currentIndex();var c=a.previousIndex();0>c||(h.push(g),e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")+":eq("+c+") a").tab("show"))};this.first=function(g){if(b.onFirst&&"function"===typeof b.onFirst&&!1===b.onFirst(f,e,a.firstIndex())||d.hasClass("disabled"))return!1;h.push(a.currentIndex()); 16 | e.find('li:has([data-toggle="tab"]):eq(0) a').tab("show")};this.last=function(g){if(b.onLast&&"function"===typeof b.onLast&&!1===b.onLast(f,e,a.lastIndex())||d.hasClass("disabled"))return!1;h.push(a.currentIndex());e.find('li:has([data-toggle="tab"]):eq('+a.navigationLength()+") a").tab("show")};this.finish=function(g){if(b.onFinish&&"function"===typeof b.onFinish)b.onFinish(f,e,a.lastIndex())};this.back=function(){if(0==h.length)return null;var a=h.pop();if(b.onBack&&"function"===typeof b.onBack&& 17 | !1===b.onBack(f,e,a))return h.push(a),!1;d.find('li:has([data-toggle="tab"]):eq('+a+") a").tab("show")};this.currentIndex=function(){return e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")).index(f)};this.firstIndex=function(){return 0};this.lastIndex=function(){return a.navigationLength()};this.getIndex=function(a){return e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")).index(a)};this.nextIndex=function(){var a=this.currentIndex(),c;do a++,c=e.find('li:has([data-toggle="tab"])'+ 18 | (b.withVisible?":visible":"")+":eq("+a+")");while(c&&c.hasClass("disabled"));return a};this.previousIndex=function(){var a=this.currentIndex(),c;do a--,c=e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")+":eq("+a+")");while(c&&c.hasClass("disabled"));return a};this.navigationLength=function(){return e.find('li:has([data-toggle="tab"])'+(b.withVisible?":visible":"")).length-1};this.activeTab=function(){return f};this.nextTab=function(){return e.find('li:has([data-toggle="tab"]):eq('+ 19 | (a.currentIndex()+1)+")").length?e.find('li:has([data-toggle="tab"]):eq('+(a.currentIndex()+1)+")"):null};this.previousTab=function(){return 0>=a.currentIndex()?null:e.find('li:has([data-toggle="tab"]):eq('+parseInt(a.currentIndex()-1)+")")};this.show=function(b){b=isNaN(b)?d.find('li:has([data-toggle="tab"]) a[href="#'+b+'"]'):d.find('li:has([data-toggle="tab"]):eq('+b+") a");0' 56 | + '' + licenseTable[i].skuName + '' 57 | + '' + licenseTable[i].displayStatus + '' 58 | + '' + (licenseTable[i].enabled + licenseTable[i].suspended + licenseTable[i].warning) + '' 59 | + '' + licenseTable[i].consumedUnits + '' 60 | + '' + (licenseTable[i].enabled - licenseTable[i].consumedUnits) + '' 61 | + '' + licenseTable[i].enabled + '' 62 | + '' + licenseTable[i].suspended + '' 63 | + '' + licenseTable[i].warning + ''; 64 | $("#licenseTable").append('' + tr + '') 65 | } 66 | } 67 | }, 68 | error: function () { 69 | /*错误信息处理*/ 70 | lightyear.notify("服务器错误,请稍后再试~", 'danger', 100); 71 | } 72 | }); 73 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/lightyear.js: -------------------------------------------------------------------------------- 1 | var lightyear = function(){ 2 | 3 | /** 4 | * 页面loading 5 | */ 6 | var pageLoader = function($mode) { 7 | var $loadingEl = jQuery('#lyear-loading'); 8 | $mode = $mode || 'show'; 9 | if ($mode === 'show') { 10 | if ($loadingEl.length) { 11 | $loadingEl.fadeIn(250); 12 | } else { 13 | jQuery('body').prepend('
Loading...
'); 14 | } 15 | } else if ($mode === 'hide') { 16 | if ($loadingEl.length) { 17 | $loadingEl.fadeOut(250); 18 | } 19 | } 20 | return false; 21 | }; 22 | 23 | /** 24 | * 页面小提示 25 | * @param $msg 提示信息 26 | * @param $type 提示类型:'info', 'success', 'warning', 'danger' 27 | * @param $delay 毫秒数,例如:1000 28 | * @param $icon 图标,例如:'fa fa-user' 或 'glyphicon glyphicon-warning-sign' 29 | * @param $from 'top' 或 'bottom' 30 | * @param $align 'left', 'right', 'center' 31 | * @param $url 跳转链接 例如: https://www.xxxx.com 32 | * @author CaiWeiMing <314013107@qq.com> 33 | */ 34 | var tips = function ($msg, $type, $delay, $icon, $from, $align, $url) { 35 | $type = $type || 'info'; 36 | $delay = $delay || 1000; 37 | $from = $from || 'top'; 38 | $align = $align || 'center'; 39 | $enter = $type == 'danger' ? 'animated shake' : 'animated fadeInUp'; 40 | $url = $url || url; 41 | jQuery.notify({ 42 | icon: $icon, 43 | message: $msg 44 | }, 45 | { 46 | element: 'body', 47 | type: $type, 48 | allow_dismiss: true, 49 | newest_on_top: true, 50 | showProgressbar: false, 51 | placement: { 52 | from: $from, 53 | align: $align 54 | }, 55 | offset: 20, 56 | spacing: 10, 57 | z_index: 10800, 58 | delay: $delay, 59 | //timer: 1000, 60 | animate: { 61 | enter: $enter, 62 | exit: 'animated fadeOutDown' 63 | } 64 | }); 65 | if($url!=''){ 66 | setTimeout(function(){ 67 | window.location.href=$url; 68 | },$delay); 69 | } 70 | 71 | }; 72 | 73 | var url = ''; 74 | 75 | return { 76 | // 页面小提示 77 | notify : function ($msg, $type, $delay, $icon, $from, $align, $url) { 78 | tips($msg, $type, $delay, $icon, $from, $align, $url); 79 | }, 80 | url : function ($url){ 81 | url=$url; 82 | }, 83 | // 页面加载动画 84 | loading : function ($mode) { 85 | pageLoader($mode); 86 | } 87 | }; 88 | }(); -------------------------------------------------------------------------------- /src/main/resources/static/js/login.js: -------------------------------------------------------------------------------- 1 | let delay = 2000; 2 | function loginClick() { 3 | // lightyear.loading('show'); 4 | let username = $("#username").val(); 5 | let password = $("#password").val(); 6 | console.log(username + " "+password) 7 | // 对密码加密 8 | 9 | // 提交请求 10 | $.ajax({ 11 | type: "post", 12 | url: url + "/login", 13 | data: { 14 | "userName": username, 15 | "password": md5(password) 16 | }, 17 | dataType: "json", 18 | success: function (r) { 19 | if (r.status !== 200) { 20 | lightyear.loading('hide'); 21 | lightyear.notify(r.message, 'danger', delay); 22 | } else { 23 | console.log(r); 24 | lightyear.loading('hide'); 25 | lightyear.notify('登陆成功!', 'success', delay); 26 | window.location.href = "index.html"; 27 | } 28 | }, 29 | error: function (r) { 30 | /*错误信息处理*/ 31 | lightyear.notify(r.responseJSON.message, 'danger', delay); 32 | lightyear.loading('hide'); 33 | } 34 | }); 35 | 36 | } 37 | 38 | $(document).on('keypress', function (e) { 39 | if (e.which === 13) { 40 | loginClick(); 41 | } 42 | }); 43 | 44 | $('#invitePage').click( 45 | function () { 46 | window.location.href = '/invite.html' 47 | } 48 | ) -------------------------------------------------------------------------------- /src/main/resources/static/js/main.min.js: -------------------------------------------------------------------------------- 1 | ; 2 | 3 | jQuery( function() { 4 | $("body").on('click','[data-stopPropagation]',function (e) { 5 | e.stopPropagation(); 6 | }); 7 | 8 | // 滚动条 9 | const ps = new PerfectScrollbar('.lyear-layout-sidebar-scroll', { 10 | swipeEasing: false, 11 | suppressScrollX: true 12 | }); 13 | 14 | // 侧边栏 15 | $(document).on('click', '.lyear-aside-toggler', function() { 16 | $('.lyear-layout-sidebar').toggleClass('lyear-aside-open'); 17 | $("body").toggleClass('lyear-layout-sidebar-close'); 18 | 19 | if ($('.lyear-mask-modal').length == 0) { 20 | $('
').prependTo('body'); 21 | } else { 22 | $( '.lyear-mask-modal' ).remove(); 23 | } 24 | }); 25 | 26 | // 遮罩层 27 | $(document).on('click', '.lyear-mask-modal', function() { 28 | $( this ).remove(); 29 | $('.lyear-layout-sidebar').toggleClass('lyear-aside-open'); 30 | $('body').toggleClass('lyear-layout-sidebar-close'); 31 | }); 32 | 33 | // 侧边栏导航 34 | $(document).on('click', '.nav-item-has-subnav > a', function() { 35 | $subnavToggle = jQuery( this ); 36 | $navHasSubnav = $subnavToggle.parent(); 37 | $topHasSubNav = $subnavToggle.parents('.nav-item-has-subnav').last(); 38 | $subnav = $navHasSubnav.find('.nav-subnav').first(); 39 | $viSubHeight = $navHasSubnav.siblings().find('.nav-subnav:visible').outerHeight(); 40 | $scrollBox = $('.lyear-layout-sidebar-scroll'); 41 | $navHasSubnav.siblings().find('.nav-subnav:visible').slideUp(500).parent().removeClass('open'); 42 | $subnav.slideToggle( 300, function() { 43 | $navHasSubnav.toggleClass( 'open' ); 44 | 45 | // 新增滚动条处理 46 | var scrollHeight = 0; 47 | pervTotal = $topHasSubNav.prevAll().length, 48 | boxHeight = $scrollBox.outerHeight(), 49 | innerHeight = $('.sidebar-main').outerHeight(), 50 | thisScroll = $scrollBox.scrollTop(), 51 | thisSubHeight = $(this).outerHeight(), 52 | footHeight = 121; 53 | 54 | if (footHeight + innerHeight - boxHeight >= (pervTotal * 48)) { 55 | scrollHeight = pervTotal * 48; 56 | } 57 | if ($subnavToggle.parents('.nav-item-has-subnav').length == 1) { 58 | $scrollBox.animate({scrollTop: scrollHeight}, 300); 59 | } else { 60 | // 子菜单操作 61 | if (typeof($viSubHeight) != 'undefined' && $viSubHeight != null) { 62 | scrollHeight = thisScroll + thisSubHeight - $viSubHeight; 63 | $scrollBox.animate({scrollTop: scrollHeight}, 300); 64 | } else { 65 | if ((thisScroll + boxHeight - $scrollBox[0].scrollHeight) == 0) { 66 | scrollHeight = thisScroll - thisSubHeight; 67 | $scrollBox.animate({scrollTop: scrollHeight}, 300); 68 | } 69 | } 70 | } 71 | }); 72 | }); 73 | 74 | // 提示 75 | if($('[data-toggle="tooltip"]')[0]) { 76 | $('[data-toggle="tooltip"]').tooltip({ 77 | "container" : 'body', 78 | }); 79 | } 80 | 81 | // 弹出框 82 | if($('[data-toggle="popover"]')[0]) { 83 | $('[data-toggle="popover"]').popover(); 84 | } 85 | 86 | // 标签 87 | $('.js-tags-input').each(function() { 88 | var $this = $(this); 89 | $this.tagsInput({ 90 | height: $this.data('height') ? $this.data('height') : '38px', 91 | width: '100%', 92 | defaultText: $this.attr("placeholder"), 93 | removeWithBackspace: true, 94 | delimiter: [','] 95 | }); 96 | }); 97 | 98 | // 时间选择 99 | jQuery('.js-datetimepicker').each(function() { 100 | var $input = jQuery(this); 101 | $input.datetimepicker({ 102 | format: $input.data('format') ? $input.data('format') : false, 103 | useCurrent: $input.data('use-current') ? $input.data('use-current') : false, 104 | locale: moment.locale('' + ($input.data('locale') ? $input.data('locale') : '') + ''), 105 | showTodayButton: $input.data('show-today-button') ? $input.data('show-today-button') : false, 106 | showClear: $input.data('show-clear') ? $input.data('show-clear') : false, 107 | showClose: $input.data('show-close') ? $input.data('show-close') : false, 108 | sideBySide: $input.data('side-by-side') ? $input.data('side-by-side') : false, 109 | inline: $input.data('inline') ? $input.data('inline') : false, 110 | }); 111 | }); 112 | 113 | // 日期选择 114 | jQuery('.js-datepicker').each(function() { 115 | var options = { 116 | weekStart: 1, 117 | autoclose: typeof($(this).data('auto-close')) != 'undefined' ? $(this).data('auto-close') : true, 118 | language: 'zh-CN', // 默认简体中文 119 | multidateSeparator: ', ', // 默认多个日期用,分隔 120 | format: $(this).data('date-format') ? $(this).data('date-format') : 'yyyy-mm-dd', 121 | }; 122 | 123 | if ( $(this).prop("tagName") != 'INPUT' ) { 124 | options.inputs = [$(this).find('input:first'), $(this).find('input:last')]; 125 | } 126 | 127 | $(this).datepicker(options); 128 | }); 129 | 130 | // 颜色选取 131 | jQuery('.js-colorpicker').each(function() { 132 | var $colorpicker = jQuery(this); 133 | var $colorpickerMode = $colorpicker.data('colorpicker-mode') ? $colorpicker.data('colorpicker-mode') : 'hex'; 134 | var $colorpickerinline = $colorpicker.data('colorpicker-inline') ? true: false; 135 | $colorpicker.colorpicker({ 136 | 'format': $colorpickerMode, 137 | 'inline': $colorpickerinline 138 | }); 139 | }); 140 | 141 | // 复选框全选 142 | $("#check-all").change(function () { 143 | $("input[type='checkbox']").prop('checked', $(this).prop("checked")); 144 | }); 145 | 146 | // 设置主题配色 147 | setTheme = function(input_name, data_name) { 148 | $("input[name='"+input_name+"']").click(function(){ 149 | $('body').attr(data_name, $(this).val()); 150 | }); 151 | } 152 | setTheme('site_theme', 'data-theme'); 153 | setTheme('logo_bg', 'data-logobg'); 154 | setTheme('header_bg', 'data-headerbg'); 155 | setTheme('sidebar_bg', 'data-sidebarbg'); 156 | 157 | }); -------------------------------------------------------------------------------- /src/main/resources/static/js/md5.min.js: -------------------------------------------------------------------------------- 1 | !function(n){"use strict";function d(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function f(n,t,r,e,o,u){return d((c=d(d(t,n),d(e,u)))<<(f=o)|c>>>32-f,r);var c,f}function l(n,t,r,e,o,u,c){return f(t&r|~t&e,n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f(t&e|r&~e,n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function i(n,t){var r,e,o,u;n[t>>5]|=128<>>9<<4)]=t;for(var c=1732584193,f=-271733879,i=-1732584194,a=271733878,h=0;h>5]>>>e%32&255);return t}function h(n){var t=[];for(t[(n.length>>2)-1]=void 0,e=0;e>5]|=(255&n.charCodeAt(e/8))<>>4&15)+r.charAt(15&t);return e}function r(n){return unescape(encodeURIComponent(n))}function o(n){return a(i(h(t=r(n)),8*t.length));var t}function u(n,t){return function(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,16 2 | 3 | 4 | 5 | 登录 - Microsoft 365 管理系统 6 | 7 | 8 | 9 | 110 | 111 | 112 | 113 |
145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/main/resources/static/sidebar.html: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------